package invalid.lena.scrcpy; import android.app.Activity; import android.app.AlertDialog; import android.content.Intent; import android.os.Bundle; import android.text.TextUtils; import android.view.View; import android.view.WindowInsets; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; import java.util.List; // Pairing form + saved-device list. // // Layout: device address ("ip:port"), pair-port, pair-code + a single // "Pair and save" button. The address is the connect endpoint printed on // the target's Wireless debugging screen; pairing happens on a different, // short-lived port from the pairing dialog, against the same host. After a // successful pair() against the daemon, the row is appended to devices.json // with the *connect* port. Tapping a saved row launches the Mirror activity // with the target host/port; Mirror owns its own Adb instance loaded from // the same on-disk keypair. public final class Main extends Activity { private volatile Adb adb; private ArrayAdapter adapter; private Button pairButton; @Override protected void onCreate(Bundle saved) { super.onCreate(saved); setContentView(R.layout.main); // The IME inset is in the mask too: the window is edge-to-edge, so // nothing resizes it when the keyboard opens. Padding the root // shrinks the weighted device list and keeps the form reachable. Ui.padForInsets(findViewById(R.id.root), WindowInsets.Type.systemBars() | WindowInsets.Type.ime()); EditText address = findViewById(R.id.device_address); EditText pairPort = findViewById(R.id.pair_port); EditText pairCode = findViewById(R.id.pair_code); pairButton = findViewById(R.id.pair); View settingsBtn = findViewById(R.id.settings); ListView devices = findViewById(R.id.devices); TextView devicesEmpty = findViewById(R.id.devices_empty); settingsBtn.setOnClickListener(v -> startActivity( new Intent(this, SettingsActivity.class))); adapter = new ArrayAdapter<>(this, R.layout.device_row, R.id.device_label, Devices.load(this)); devices.setAdapter(adapter); devices.setEmptyView(devicesEmpty); // RSA keygen on first launch can take 1-3 s; never on the UI thread. pairButton.setEnabled(false); new Thread(() -> { try { Adb a = Adb.getInstance(this); runOnUiThread(() -> { adb = a; pairButton.setEnabled(true); }); } catch (Exception e) { Log.e(e, "adb init failed"); runOnUiThread(() -> { Toast.makeText(this, "adb init failed: " + e.getMessage(), Toast.LENGTH_LONG).show(); finish(); }); } }, "adb-init").start(); devices.setOnItemClickListener((parent, view, pos, id) -> { Devices.Device d = adapter.getItem(pos); Log.i("connect tap: %s", d); Intent i = new Intent(this, Mirror.class); i.putExtra(Mirror.EXTRA_HOST, d.host); i.putExtra(Mirror.EXTRA_PORT, d.port); startActivity(i); }); devices.setOnItemLongClickListener((parent, view, pos, id) -> { Devices.Device d = adapter.getItem(pos); new AlertDialog.Builder(this) .setTitle(R.string.forget_device) .setMessage(d.toString()) .setPositiveButton(android.R.string.ok, (dlg, w) -> { Log.i("forget device: %s", d); try { List updated = Devices.remove(this, d); adapter.clear(); adapter.addAll(updated); adapter.notifyDataSetChanged(); } catch (Exception e) { Log.e(e, "forget device: save failed"); Toast.makeText(this, "could not save device list", Toast.LENGTH_LONG).show(); } }) .setNegativeButton(android.R.string.cancel, null) .show(); return true; }); pairButton.setOnClickListener(v -> { if (adb == null) return; // still initialising // The saved endpoint is the address field verbatim; pairing // reuses its host with the pairing port. Devices.Device target = Devices.parseAddress(address.getText().toString()); if (target == null) { Toast.makeText(this, R.string.bad_address, Toast.LENGTH_LONG).show(); return; } int pairP = Devices.parsePort(pairPort.getText().toString()); if (pairP < 0) { Toast.makeText(this, R.string.bad_pair_port, Toast.LENGTH_LONG).show(); return; } String pc = pairCode.getText().toString().trim(); if (TextUtils.isEmpty(pc)) { Toast.makeText(this, R.string.bad_pair_code, Toast.LENGTH_LONG).show(); return; } pairButton.setEnabled(false); Toast.makeText(this, R.string.pairing, Toast.LENGTH_SHORT).show(); new Thread(() -> pairAndSave(target, pairP, pc, pairButton), "pair").start(); }); } private void pairAndSave(Devices.Device target, int pairPort, String code, Button btn) { try { Log.i("pair: %s:%d", target.host, pairPort); boolean ok = adb.pair(target.host, pairPort, code); if (!ok) throw new IllegalStateException("pair returned false"); Log.i("pair ok host=%s pair_port=%d", target.host, pairPort); List updated = Devices.upsert(this, target); runOnUiThread(() -> { adapter.clear(); adapter.addAll(updated); adapter.notifyDataSetChanged(); Toast.makeText(this, "paired and saved", Toast.LENGTH_SHORT).show(); btn.setEnabled(true); }); } catch (Exception e) { Log.e(e, "pair failed"); runOnUiThread(() -> { Toast.makeText(this, "pair failed: " + e.getMessage(), Toast.LENGTH_LONG).show(); btn.setEnabled(true); }); } } }