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() { 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() { 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() { 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>() { public List work() throws Exception { return Plex.servers(account); } public void done(final List 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() { 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() { 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>() { public List work() throws Exception { return s.musicSections(); } public void done(List 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(); } }