Private
Public Access
Renames the app (label, texts, log files, Plex product name, User-Agent, APK). The package
name, preferences and signing key stay, so it installs over Minamp and keeps its settings.
tools/strings.py catches up with the strings added straight to the XML in 9440536.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
348 lines
15 KiB
Java
348 lines
15 KiB
Java
package com.minamp;
|
|
|
|
import java.util.List;
|
|
|
|
import android.content.Intent;
|
|
import android.os.Bundle;
|
|
import android.view.KeyEvent;
|
|
import android.view.View;
|
|
import android.widget.EditText;
|
|
import android.widget.LinearLayout;
|
|
import android.widget.TextView;
|
|
|
|
import com.minamp.plex.Plex;
|
|
import com.minamp.plex.Server;
|
|
import com.minamp.util.Json;
|
|
|
|
/** Sign in with a plex.tv/link code (or a manual address), then pick a server and a music library. */
|
|
public class SetupActivity extends BaseActivity {
|
|
|
|
public static final String EXTRA_MODE = "mode";
|
|
public static final int MODE_WELCOME = 0, MODE_SERVER = 1, MODE_LIBRARY = 2;
|
|
|
|
private View welcome, pinPage, manual, choose, busy;
|
|
private TextView pinCode, pinStatus, busyText, chooseTitle, chooseSub;
|
|
private LinearLayout chooseList;
|
|
private Runnable chooseBack;
|
|
private long pinId;
|
|
private long pinStarted;
|
|
private int pinSeq;
|
|
|
|
@Override
|
|
protected void onCreate(Bundle b) {
|
|
super.onCreate(b);
|
|
setContentView(R.layout.activity_setup);
|
|
showCrashReportIfAny();
|
|
welcome = findViewById(R.id.page_welcome);
|
|
pinPage = findViewById(R.id.page_pin);
|
|
manual = findViewById(R.id.page_manual);
|
|
choose = findViewById(R.id.page_choose);
|
|
busy = findViewById(R.id.page_busy);
|
|
pinCode = (TextView) findViewById(R.id.pin_code);
|
|
pinStatus = (TextView) findViewById(R.id.pin_status);
|
|
busyText = (TextView) findViewById(R.id.busy_text);
|
|
chooseTitle = (TextView) findViewById(R.id.choose_title);
|
|
chooseSub = (TextView) findViewById(R.id.choose_sub);
|
|
chooseList = (LinearLayout) findViewById(R.id.choose_list);
|
|
|
|
findViewById(R.id.btn_signin).setOnClickListener(new View.OnClickListener() { public void onClick(View v) { startPin(); } });
|
|
findViewById(R.id.btn_manual).setOnClickListener(new View.OnClickListener() { public void onClick(View v) { show(manual); } });
|
|
findViewById(R.id.btn_pin_cancel).setOnClickListener(new View.OnClickListener() { public void onClick(View v) { pinSeq++; clearPendingPin(); show(welcome); } });
|
|
findViewById(R.id.btn_pin_check).setOnClickListener(new View.OnClickListener() { public void onClick(View v) { checkPinNow(pinSeq); } });
|
|
findViewById(R.id.btn_setup_log).setOnClickListener(new View.OnClickListener() { public void onClick(View v) { showDiagnostics(); } });
|
|
findViewById(R.id.btn_manual_back).setOnClickListener(new View.OnClickListener() { public void onClick(View v) { show(welcome); } });
|
|
findViewById(R.id.btn_manual_go).setOnClickListener(new View.OnClickListener() { public void onClick(View v) { connectManual(); } });
|
|
findViewById(R.id.btn_choose_back).setOnClickListener(new View.OnClickListener() {
|
|
public void onClick(View v) { if (chooseBack != null) chooseBack.run(); else show(welcome); }
|
|
});
|
|
|
|
int mode = getIntent().getIntExtra(EXTRA_MODE, MODE_WELCOME);
|
|
String account = app.prefs.getString("account", null);
|
|
if (mode == MODE_LIBRARY && app.server != null) {
|
|
loadSections(app.server, null);
|
|
} else if (mode == MODE_SERVER && account != null) {
|
|
loadServers(account);
|
|
} else if (!resumePendingPin()) {
|
|
show(welcome);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onDestroy() {
|
|
pinSeq++;
|
|
super.onDestroy();
|
|
}
|
|
|
|
@Override
|
|
public boolean onCreateOptionsMenu(android.view.Menu menu) { return false; }
|
|
|
|
private void show(View page) {
|
|
View[] all = {welcome, pinPage, manual, choose, busy};
|
|
for (int i = 0; i < all.length; i++) all[i].setVisibility(all[i] == page ? View.VISIBLE : View.GONE);
|
|
}
|
|
|
|
private void showBusy(String text) {
|
|
busyText.setText(text);
|
|
show(busy);
|
|
}
|
|
|
|
@Override
|
|
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
|
if (keyCode == KeyEvent.KEYCODE_BACK && welcome.getVisibility() != View.VISIBLE && app.isConfigured() == false) {
|
|
pinSeq++;
|
|
show(welcome);
|
|
return true;
|
|
}
|
|
return super.onKeyDown(keyCode, event);
|
|
}
|
|
|
|
// ------------------------------------------------------------------ PIN sign-in
|
|
|
|
private int pinChecks;
|
|
private boolean pinCheckRunning;
|
|
|
|
private void startPin() {
|
|
clearPendingPin();
|
|
showBusy(str(R.string.contacting_plextv));
|
|
final int my = ++pinSeq;
|
|
run(new Task<Plex.Pin>() {
|
|
public Plex.Pin work() throws Exception { return Plex.createPin(); }
|
|
public void done(Plex.Pin p) {
|
|
if (my != pinSeq) return;
|
|
// Remember the code: the phone may close Plexamp Lite while you type it in elsewhere.
|
|
app.prefs.edit().putLong("pinId", p.id).putString("pinCode", p.code)
|
|
.putLong("pinStart", System.currentTimeMillis()).commit();
|
|
showPin(p.id, p.code, System.currentTimeMillis());
|
|
}
|
|
public void fail(Exception e) {
|
|
if (my != pinSeq) return;
|
|
show(welcome);
|
|
toast(str(R.string.cant_reach_plextv, msg(e)));
|
|
}
|
|
});
|
|
}
|
|
|
|
/** Resume waiting for a code created earlier (Plexamp Lite was closed while the user typed it in). */
|
|
private boolean resumePendingPin() {
|
|
long id = app.prefs.getLong("pinId", 0);
|
|
String code = app.prefs.getString("pinCode", null);
|
|
long start = app.prefs.getLong("pinStart", 0);
|
|
if (id == 0 || code == null || System.currentTimeMillis() - start > 14 * 60 * 1000) {
|
|
clearPendingPin();
|
|
return false;
|
|
}
|
|
com.minamp.net.DiagLog.log("resuming sign-in code " + code);
|
|
showPin(id, code, start);
|
|
return true;
|
|
}
|
|
|
|
private void clearPendingPin() {
|
|
app.prefs.edit().remove("pinId").remove("pinCode").remove("pinStart").commit();
|
|
}
|
|
|
|
private void showPin(long id, String code, long started) {
|
|
pinId = id;
|
|
pinStarted = started;
|
|
pinChecks = 0;
|
|
StringBuilder spaced = new StringBuilder();
|
|
for (int i = 0; i < code.length(); i++) { if (i > 0) spaced.append(' '); spaced.append(code.charAt(i)); }
|
|
pinCode.setText(spaced.toString());
|
|
pinStatus.setText(R.string.pin_waiting);
|
|
show(pinPage);
|
|
int my = ++pinSeq;
|
|
checkPinNow(my);
|
|
}
|
|
|
|
@Override
|
|
protected void onResume() {
|
|
super.onResume();
|
|
// back from typing the code somewhere else: check straight away
|
|
if (pinPage != null && pinPage.getVisibility() == View.VISIBLE && !pinCheckRunning) checkPinNow(pinSeq);
|
|
}
|
|
|
|
private void schedulePinCheck(final int my) {
|
|
app.handler.postDelayed(new Runnable() {
|
|
public void run() { checkPinNow(my); }
|
|
}, 3000);
|
|
}
|
|
|
|
private void checkPinNow(final int my) {
|
|
if (my != pinSeq || destroyed || pinCheckRunning) return;
|
|
long age = System.currentTimeMillis() - pinStarted;
|
|
if (age > 14 * 60 * 1000) {
|
|
pinStatus.setText(R.string.pin_expired);
|
|
clearPendingPin();
|
|
return;
|
|
}
|
|
pinCheckRunning = true;
|
|
run(new Task<String>() {
|
|
public String work() throws Exception { return Plex.checkPin(pinId); }
|
|
public void done(String token) {
|
|
pinCheckRunning = false;
|
|
if (my != pinSeq) return;
|
|
pinChecks++;
|
|
if (token == null) {
|
|
long left = (14 * 60 * 1000 - (System.currentTimeMillis() - pinStarted)) / 60000;
|
|
pinStatus.setText(str(R.string.pin_not_yet, pinChecks, (int) left));
|
|
schedulePinCheck(my);
|
|
return;
|
|
}
|
|
pinStatus.setText(R.string.pin_linked);
|
|
pinSeq++;
|
|
clearPendingPin();
|
|
signedIn(token);
|
|
}
|
|
public void fail(Exception e) {
|
|
pinCheckRunning = false;
|
|
if (my != pinSeq) return;
|
|
pinChecks++;
|
|
pinStatus.setText(str(R.string.pin_check_failed, pinChecks, msg(e)));
|
|
schedulePinCheck(my);
|
|
}
|
|
});
|
|
}
|
|
|
|
private void signedIn(final String account) {
|
|
showBusy(str(R.string.signed_in_finding));
|
|
app.prefs.edit().putString("account", account).commit();
|
|
run(new Task<String>() {
|
|
public String work() throws Exception { return Plex.username(account); }
|
|
public void done(String u) { app.prefs.edit().putString("username", u).commit(); }
|
|
});
|
|
loadServers(account);
|
|
}
|
|
|
|
private void loadServers(final String account) {
|
|
showBusy(str(R.string.finding_servers));
|
|
run(new Task<List<Server>>() {
|
|
public List<Server> work() throws Exception { return Plex.servers(account); }
|
|
public void done(final List<Server> servers) {
|
|
if (servers.isEmpty()) {
|
|
show(welcome);
|
|
toast(str(R.string.no_servers));
|
|
return;
|
|
}
|
|
if (servers.size() == 1) { pickServer(servers.get(0), null); return; }
|
|
chooseTitle.setText(R.string.choose_server);
|
|
chooseSub.setText(str(R.string.signed_in_as, app.prefs.getString("username", str(R.string.you))));
|
|
chooseList.removeAllViews();
|
|
for (int i = 0; i < servers.size(); i++) {
|
|
final Server s = servers.get(i);
|
|
int remote = 0, local = 0;
|
|
for (int k = 0; k < s.connections.size(); k++) {
|
|
int kind = s.connections.get(k).kind;
|
|
if (kind == Server.LOCAL || kind == Server.LOCAL_HTTP) local++; else remote++;
|
|
}
|
|
String sub = str(s.owned ? R.string.server_yours : R.string.server_shared) + " \u00b7 " + (local > 0 ? str(R.string.server_home_plus) : "") + str(remote > 0 ? R.string.server_remote : R.string.server_local_only);
|
|
addChoice(s.name, sub, new View.OnClickListener() {
|
|
public void onClick(View v) { pickServer(s, new Runnable() { public void run() { done(servers); } }); }
|
|
});
|
|
}
|
|
chooseBack = new Runnable() { public void run() { show(welcome); } };
|
|
show(choose);
|
|
}
|
|
public void fail(Exception e) {
|
|
show(welcome);
|
|
toast(str(R.string.cant_list_servers, msg(e)));
|
|
}
|
|
});
|
|
}
|
|
|
|
private void addChoice(String title, String sub, View.OnClickListener l) {
|
|
View row = getLayoutInflater().inflate(R.layout.row_choice, chooseList, false);
|
|
((TextView) row.findViewById(R.id.choice_title)).setText(title);
|
|
((TextView) row.findViewById(R.id.choice_sub)).setText(sub);
|
|
row.setOnClickListener(l);
|
|
chooseList.addView(row);
|
|
}
|
|
|
|
private void pickServer(final Server s, final Runnable back) {
|
|
showBusy(str(R.string.connecting_to, s.name));
|
|
run(new Task<Server.Conn>() {
|
|
public Server.Conn work() throws Exception { return s.pick(); }
|
|
public void done(Server.Conn c) {
|
|
loadSections(s, back);
|
|
}
|
|
public void fail(Exception e) {
|
|
if (back != null) back.run(); else show(welcome);
|
|
toast(msg(e));
|
|
}
|
|
});
|
|
}
|
|
|
|
// ------------------------------------------------------------------ manual
|
|
|
|
private void connectManual() {
|
|
String url = ((EditText) findViewById(R.id.edit_url)).getText().toString().trim();
|
|
String token = ((EditText) findViewById(R.id.edit_token)).getText().toString().trim();
|
|
if (url.length() == 0) { toast(str(R.string.enter_address)); return; }
|
|
final Server s = new Server();
|
|
s.name = str(R.string.plex_server);
|
|
s.token = token;
|
|
s.add(Server.normalizeManual(url), Server.MANUAL);
|
|
showBusy(str(R.string.connecting));
|
|
run(new Task<String>() {
|
|
public String work() throws Exception {
|
|
s.pick();
|
|
Json.Obj mc = s.get("/");
|
|
return mc == null ? "" : mc.optString("friendlyName", "");
|
|
}
|
|
public void done(String name) {
|
|
if (name.length() > 0) s.name = name;
|
|
loadSections(s, new Runnable() { public void run() { show(manual); } });
|
|
}
|
|
public void fail(Exception e) {
|
|
show(manual);
|
|
boolean auth = e instanceof Plex.PlexException && ((Plex.PlexException) e).code == 401;
|
|
toast(auth ? str(R.string.server_wants_token) : msg(e));
|
|
}
|
|
});
|
|
}
|
|
|
|
// ------------------------------------------------------------------ libraries
|
|
|
|
private void loadSections(final Server s, final Runnable back) {
|
|
showBusy(str(R.string.loading_libraries));
|
|
run(new Task<List<Server.Section>>() {
|
|
public List<Server.Section> work() throws Exception { return s.musicSections(); }
|
|
public void done(List<Server.Section> secs) {
|
|
if (secs.isEmpty()) {
|
|
if (back != null) back.run(); else show(welcome);
|
|
toast(str(R.string.no_music_libraries, s.name));
|
|
return;
|
|
}
|
|
if (secs.size() == 1) { finishWith(s, secs.get(0)); return; }
|
|
chooseTitle.setText(R.string.choose_library);
|
|
chooseSub.setText(s.name);
|
|
chooseList.removeAllViews();
|
|
for (int i = 0; i < secs.size(); i++) {
|
|
final Server.Section sec = secs.get(i);
|
|
addChoice(sec.title, str(R.string.music), new View.OnClickListener() {
|
|
public void onClick(View v) { finishWith(s, sec); }
|
|
});
|
|
}
|
|
chooseBack = back != null ? back : new Runnable() { public void run() { finish(); } };
|
|
show(choose);
|
|
}
|
|
public void fail(Exception e) {
|
|
if (back != null) back.run(); else show(welcome);
|
|
toast(msg(e));
|
|
}
|
|
});
|
|
}
|
|
|
|
private void finishWith(Server s, Server.Section sec) {
|
|
boolean sameServer = app.server != null && app.server.machineId.equals(s.machineId) && s.machineId.length() > 0;
|
|
if (!sameServer || app.server != s) {
|
|
if (!sameServer) {
|
|
PlayerService.send(this, PlayerService.ACTION_STOP);
|
|
PlayQueue.get().clear();
|
|
}
|
|
app.saveServer(s);
|
|
}
|
|
app.saveSection(sec.key, sec.title);
|
|
startActivity(new Intent(this, HomeActivity.class).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP));
|
|
finish();
|
|
}
|
|
}
|