1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.development; 18 19 import android.app.Activity; 20 import android.app.Dialog; 21 import android.app.settings.SettingsEnums; 22 import android.content.BroadcastReceiver; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.IntentFilter; 26 import android.debug.AdbManager; 27 import android.debug.IAdbManager; 28 import android.debug.PairDevice; 29 import android.os.Build; 30 import android.os.Bundle; 31 import android.os.RemoteException; 32 import android.os.ServiceManager; 33 import android.provider.Settings; 34 import android.util.Log; 35 36 import androidx.preference.Preference; 37 import androidx.preference.PreferenceCategory; 38 39 import com.android.settings.R; 40 import com.android.settings.SettingsActivity; 41 import com.android.settings.core.SubSettingLauncher; 42 import com.android.settings.dashboard.DashboardFragment; 43 import com.android.settings.search.Indexable; 44 import com.android.settings.widget.SwitchBarController; 45 import com.android.settingslib.core.AbstractPreferenceController; 46 import com.android.settingslib.core.lifecycle.Lifecycle; 47 import com.android.settingslib.search.SearchIndexable; 48 import com.android.settingslib.widget.FooterPreference; 49 50 import java.util.ArrayList; 51 import java.util.HashMap; 52 import java.util.List; 53 import java.util.Map; 54 55 /** 56 * Fragment shown when clicking in the "Wireless Debugging" preference in 57 * the developer options. 58 */ 59 @SearchIndexable 60 public class WirelessDebuggingFragment extends DashboardFragment 61 implements Indexable, WirelessDebuggingEnabler.OnEnabledListener { 62 63 private static final String TAG = "WirelessDebuggingFrag"; 64 65 // Activity result from clicking on a paired device. 66 private static final int PAIRED_DEVICE_REQUEST = 0; 67 public static final String PAIRED_DEVICE_REQUEST_TYPE = "request_type"; 68 public static final int FORGET_ACTION = 0; 69 70 // Activity result from pairing a device. 71 private static final int PAIRING_DEVICE_REQUEST = 1; 72 public static final String PAIRING_DEVICE_REQUEST_TYPE = "request_type_pairing"; 73 public static final int SUCCESS_ACTION = 0; 74 public static final int FAIL_ACTION = 1; 75 76 public static final String PAIRED_DEVICE_EXTRA = "paired_device"; 77 public static final String DEVICE_NAME_EXTRA = "device_name"; 78 public static final String IP_ADDR_EXTRA = "ip_addr"; 79 80 private WirelessDebuggingEnabler mWifiDebuggingEnabler; 81 82 private static AdbIpAddressPreferenceController sAdbIpAddressPreferenceController; 83 // UI components 84 private static final String PREF_KEY_ADB_DEVICE_NAME = "adb_device_name_pref"; 85 private static final String PREF_KEY_ADB_IP_ADDR = "adb_ip_addr_pref"; 86 private static final String PREF_KEY_PAIRING_METHODS_CATEGORY = "adb_pairing_methods_category"; 87 private static final String PREF_KEY_ADB_QRCODE_PAIRING = "adb_pair_method_qrcode_pref"; 88 private static final String PREF_KEY_ADB_CODE_PAIRING = "adb_pair_method_code_pref"; 89 private static final String PREF_KEY_PAIRED_DEVICES_CATEGORY = "adb_paired_devices_category"; 90 private static final String PREF_KEY_FOOTER_CATEGORY = "adb_wireless_footer_category"; 91 92 private Preference mDeviceNamePreference; 93 private Preference mIpAddrPreference; 94 95 private PreferenceCategory mPairingMethodsCategory; 96 private Preference mQrcodePairingPreference; 97 private Preference mCodePairingPreference; 98 99 private PreferenceCategory mPairedDevicesCategory; 100 101 private PreferenceCategory mFooterCategory; 102 private FooterPreference mOffMessagePreference; 103 104 // Map of paired devices, with the device GUID is the key 105 private Map<String, AdbPairedDevicePreference> mPairedDevicePreferences; 106 107 private IAdbManager mAdbManager; 108 private int mConnectionPort; 109 110 class PairingCodeDialogListener implements AdbWirelessDialog.AdbWirelessDialogListener { 111 @Override onDismiss()112 public void onDismiss() { 113 Log.i(TAG, "onDismiss"); 114 mPairingCodeDialog = null; 115 try { 116 mAdbManager.disablePairing(); 117 } catch (RemoteException e) { 118 Log.e(TAG, "Unable to cancel pairing"); 119 } 120 } 121 } 122 final PairingCodeDialogListener mPairingCodeDialogListener = new PairingCodeDialogListener(); 123 AdbWirelessDialog mPairingCodeDialog; 124 125 private IntentFilter mIntentFilter; 126 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 127 @Override 128 public void onReceive(Context context, Intent intent) { 129 String action = intent.getAction(); 130 if (AdbManager.WIRELESS_DEBUG_PAIRED_DEVICES_ACTION.equals(action)) { 131 Map<String, PairDevice> newPairedDevicesList = 132 (HashMap<String, PairDevice>) intent.getSerializableExtra( 133 AdbManager.WIRELESS_DEVICES_EXTRA); 134 updatePairedDevicePreferences(newPairedDevicesList); 135 } else if (AdbManager.WIRELESS_DEBUG_STATE_CHANGED_ACTION.equals(action)) { 136 int status = intent.getIntExtra(AdbManager.WIRELESS_STATUS_EXTRA, 137 AdbManager.WIRELESS_STATUS_DISCONNECTED); 138 if (status == AdbManager.WIRELESS_STATUS_CONNECTED) { 139 int port = intent.getIntExtra(AdbManager.WIRELESS_DEBUG_PORT_EXTRA, 0); 140 Log.i(TAG, "Got adbwifi port=" + port); 141 } else { 142 Log.i(TAG, "adbwifi server disconnected"); 143 } 144 } else if (AdbManager.WIRELESS_DEBUG_PAIRING_RESULT_ACTION.equals(action)) { 145 Integer res = intent.getIntExtra( 146 AdbManager.WIRELESS_STATUS_EXTRA, 147 AdbManager.WIRELESS_STATUS_FAIL); 148 149 if (res.equals(AdbManager.WIRELESS_STATUS_PAIRING_CODE)) { 150 String pairingCode = intent.getStringExtra( 151 AdbManager.WIRELESS_PAIRING_CODE_EXTRA); 152 if (mPairingCodeDialog != null) { 153 mPairingCodeDialog.getController().setPairingCode(pairingCode); 154 } 155 } else if (res.equals(AdbManager.WIRELESS_STATUS_SUCCESS)) { 156 removeDialog(AdbWirelessDialogUiBase.MODE_PAIRING); 157 mPairingCodeDialog = null; 158 } else if (res.equals(AdbManager.WIRELESS_STATUS_FAIL)) { 159 removeDialog(AdbWirelessDialogUiBase.MODE_PAIRING); 160 mPairingCodeDialog = null; 161 showDialog(AdbWirelessDialogUiBase.MODE_PAIRING_FAILED); 162 } else if (res.equals(AdbManager.WIRELESS_STATUS_CONNECTED)) { 163 int port = intent.getIntExtra(AdbManager.WIRELESS_DEBUG_PORT_EXTRA, 0); 164 Log.i(TAG, "Got pairing code port=" + port); 165 String ipAddr = sAdbIpAddressPreferenceController.getIpv4Address() + ":" + port; 166 if (mPairingCodeDialog != null) { 167 mPairingCodeDialog.getController().setIpAddr(ipAddr); 168 } 169 } 170 } 171 } 172 }; 173 174 @Override onActivityCreated(Bundle savedInstanceState)175 public void onActivityCreated(Bundle savedInstanceState) { 176 super.onActivityCreated(savedInstanceState); 177 final SettingsActivity activity = (SettingsActivity) getActivity(); 178 mWifiDebuggingEnabler = new WirelessDebuggingEnabler(activity, 179 new SwitchBarController(activity.getSwitchBar()), this, 180 getSettingsLifecycle()); 181 } 182 183 @Override onCreate(Bundle icicle)184 public void onCreate(Bundle icicle) { 185 super.onCreate(icicle); 186 187 addPreferences(); 188 mIntentFilter = new IntentFilter(AdbManager.WIRELESS_DEBUG_PAIRED_DEVICES_ACTION); 189 mIntentFilter.addAction(AdbManager.WIRELESS_DEBUG_STATE_CHANGED_ACTION); 190 mIntentFilter.addAction(AdbManager.WIRELESS_DEBUG_PAIRING_RESULT_ACTION); 191 } 192 addPreferences()193 private void addPreferences() { 194 mDeviceNamePreference = 195 (Preference) findPreference(PREF_KEY_ADB_DEVICE_NAME); 196 mIpAddrPreference = 197 (Preference) findPreference(PREF_KEY_ADB_IP_ADDR); 198 mPairingMethodsCategory = 199 (PreferenceCategory) findPreference(PREF_KEY_PAIRING_METHODS_CATEGORY); 200 mCodePairingPreference = 201 (Preference) findPreference(PREF_KEY_ADB_CODE_PAIRING); 202 mCodePairingPreference.setOnPreferenceClickListener(preference -> { 203 showDialog(AdbWirelessDialogUiBase.MODE_PAIRING); 204 return true; 205 }); 206 mQrcodePairingPreference = 207 (Preference) findPreference(PREF_KEY_ADB_QRCODE_PAIRING); 208 mQrcodePairingPreference.setOnPreferenceClickListener(preference -> { 209 launchQrcodeScannerFragment(); 210 return true; 211 }); 212 213 mPairedDevicesCategory = 214 (PreferenceCategory) findPreference(PREF_KEY_PAIRED_DEVICES_CATEGORY); 215 mFooterCategory = 216 (PreferenceCategory) findPreference(PREF_KEY_FOOTER_CATEGORY); 217 218 mOffMessagePreference = 219 new FooterPreference(mFooterCategory.getContext()); 220 final CharSequence title = getText(R.string.adb_wireless_list_empty_off); 221 mOffMessagePreference.setTitle(title); 222 mFooterCategory.addPreference(mOffMessagePreference); 223 } 224 225 @Override onDestroyView()226 public void onDestroyView() { 227 super.onDestroyView(); 228 229 mWifiDebuggingEnabler.teardownSwitchController(); 230 } 231 232 @Override onResume()233 public void onResume() { 234 super.onResume(); 235 236 getActivity().registerReceiver(mReceiver, mIntentFilter); 237 } 238 239 @Override onPause()240 public void onPause() { 241 super.onPause(); 242 243 removeDialog(AdbWirelessDialogUiBase.MODE_PAIRING); 244 getActivity().unregisterReceiver(mReceiver); 245 } 246 247 @Override onActivityResult(int requestCode, int resultCode, Intent data)248 public void onActivityResult(int requestCode, int resultCode, Intent data) { 249 super.onActivityResult(requestCode, resultCode, data); 250 251 if (requestCode == PAIRED_DEVICE_REQUEST) { 252 handlePairedDeviceRequest(resultCode, data); 253 } else if (requestCode == PAIRING_DEVICE_REQUEST) { 254 handlePairingDeviceRequest(resultCode, data); 255 } 256 } 257 258 @Override getMetricsCategory()259 public int getMetricsCategory() { 260 return SettingsEnums.SETTINGS_ADB_WIRELESS; 261 } 262 263 @Override getDialogMetricsCategory(int dialogId)264 public int getDialogMetricsCategory(int dialogId) { 265 return SettingsEnums.ADB_WIRELESS_DEVICE_PAIRING_DIALOG; 266 } 267 268 @Override onCreateDialog(int dialogId)269 public Dialog onCreateDialog(int dialogId) { 270 Dialog d = AdbWirelessDialog.createModal(getActivity(), 271 dialogId == AdbWirelessDialogUiBase.MODE_PAIRING 272 ? mPairingCodeDialogListener : null, dialogId); 273 if (dialogId == AdbWirelessDialogUiBase.MODE_PAIRING) { 274 mPairingCodeDialog = (AdbWirelessDialog) d; 275 try { 276 mAdbManager.enablePairingByPairingCode(); 277 } catch (RemoteException e) { 278 Log.e(TAG, "Unable to enable pairing"); 279 mPairingCodeDialog = null; 280 d = AdbWirelessDialog.createModal(getActivity(), null, 281 AdbWirelessDialogUiBase.MODE_PAIRING_FAILED); 282 } 283 } 284 if (d != null) { 285 return d; 286 } 287 return super.onCreateDialog(dialogId); 288 } 289 290 @Override getPreferenceScreenResId()291 protected int getPreferenceScreenResId() { 292 return R.xml.adb_wireless_settings; 293 } 294 295 @Override createPreferenceControllers(Context context)296 protected List<AbstractPreferenceController> createPreferenceControllers(Context context) { 297 return buildPreferenceControllers(context, getActivity(), this /* fragment */, 298 getSettingsLifecycle()); 299 } 300 buildPreferenceControllers( Context context, Activity activity, WirelessDebuggingFragment fragment, Lifecycle lifecycle)301 private static List<AbstractPreferenceController> buildPreferenceControllers( 302 Context context, Activity activity, WirelessDebuggingFragment fragment, 303 Lifecycle lifecycle) { 304 final List<AbstractPreferenceController> controllers = new ArrayList<>(); 305 sAdbIpAddressPreferenceController = 306 new AdbIpAddressPreferenceController(context, lifecycle); 307 controllers.add(sAdbIpAddressPreferenceController); 308 309 return controllers; 310 } 311 312 @Override getLogTag()313 protected String getLogTag() { 314 return TAG; 315 } 316 317 @Override onEnabled(boolean enabled)318 public void onEnabled(boolean enabled) { 319 if (enabled) { 320 showDebuggingPreferences(); 321 mAdbManager = IAdbManager.Stub.asInterface(ServiceManager.getService( 322 Context.ADB_SERVICE)); 323 try { 324 Map<String, PairDevice> newList = mAdbManager.getPairedDevices(); 325 updatePairedDevicePreferences(newList); 326 mConnectionPort = mAdbManager.getAdbWirelessPort(); 327 if (mConnectionPort > 0) { 328 Log.i(TAG, "onEnabled(): connect_port=" + mConnectionPort); 329 } 330 } catch (RemoteException e) { 331 Log.e(TAG, "Unable to request the paired list for Adb wireless"); 332 } 333 sAdbIpAddressPreferenceController.updateState(mIpAddrPreference); 334 } else { 335 showOffMessage(); 336 } 337 } 338 showOffMessage()339 private void showOffMessage() { 340 mDeviceNamePreference.setVisible(false); 341 mIpAddrPreference.setVisible(false); 342 mPairingMethodsCategory.setVisible(false); 343 mPairedDevicesCategory.setVisible(false); 344 mFooterCategory.setVisible(true); 345 } 346 showDebuggingPreferences()347 private void showDebuggingPreferences() { 348 mDeviceNamePreference.setVisible(true); 349 mIpAddrPreference.setVisible(true); 350 mPairingMethodsCategory.setVisible(true); 351 mPairedDevicesCategory.setVisible(true); 352 mFooterCategory.setVisible(false); 353 } 354 updatePairedDevicePreferences(Map<String, PairDevice> newList)355 private void updatePairedDevicePreferences(Map<String, PairDevice> newList) { 356 // TODO(joshuaduong): Move the non-UI stuff into another thread 357 // as the processing could take some time. 358 if (newList == null) { 359 mPairedDevicesCategory.removeAll(); 360 return; 361 } 362 if (mPairedDevicePreferences == null) { 363 mPairedDevicePreferences = new HashMap<String, AdbPairedDevicePreference>(); 364 } 365 if (mPairedDevicePreferences.isEmpty()) { 366 for (Map.Entry<String, PairDevice> entry : newList.entrySet()) { 367 AdbPairedDevicePreference p = 368 new AdbPairedDevicePreference(entry.getValue(), 369 mPairedDevicesCategory.getContext()); 370 mPairedDevicePreferences.put( 371 entry.getKey(), 372 p); 373 p.setOnPreferenceClickListener(preference -> { 374 AdbPairedDevicePreference pref = 375 (AdbPairedDevicePreference) preference; 376 launchPairedDeviceDetailsFragment(pref); 377 return true; 378 }); 379 mPairedDevicesCategory.addPreference(p); 380 } 381 } else { 382 // Remove any devices no longer on the newList 383 mPairedDevicePreferences.entrySet().removeIf(entry -> { 384 if (newList.get(entry.getKey()) == null) { 385 mPairedDevicesCategory.removePreference(entry.getValue()); 386 return true; 387 } else { 388 // It is in the newList. Just update the PairDevice value 389 AdbPairedDevicePreference p = 390 entry.getValue(); 391 p.setPairedDevice(newList.get(entry.getKey())); 392 p.refresh(); 393 return false; 394 } 395 }); 396 // Add new devices if any. 397 for (Map.Entry<String, PairDevice> entry : 398 newList.entrySet()) { 399 if (mPairedDevicePreferences.get(entry.getKey()) == null) { 400 AdbPairedDevicePreference p = 401 new AdbPairedDevicePreference(entry.getValue(), 402 mPairedDevicesCategory.getContext()); 403 mPairedDevicePreferences.put( 404 entry.getKey(), 405 p); 406 p.setOnPreferenceClickListener(preference -> { 407 AdbPairedDevicePreference pref = 408 (AdbPairedDevicePreference) preference; 409 launchPairedDeviceDetailsFragment(pref); 410 return true; 411 }); 412 mPairedDevicesCategory.addPreference(p); 413 } 414 } 415 } 416 } 417 launchPairedDeviceDetailsFragment(AdbPairedDevicePreference p)418 private void launchPairedDeviceDetailsFragment(AdbPairedDevicePreference p) { 419 // For sending to the device details fragment. 420 p.savePairedDeviceToExtras(p.getExtras()); 421 new SubSettingLauncher(getContext()) 422 .setTitleRes(R.string.adb_wireless_device_details_title) 423 .setDestination(AdbDeviceDetailsFragment.class.getName()) 424 .setArguments(p.getExtras()) 425 .setSourceMetricsCategory(getMetricsCategory()) 426 .setResultListener(this, PAIRED_DEVICE_REQUEST) 427 .launch(); 428 } 429 handlePairedDeviceRequest(int result, Intent data)430 void handlePairedDeviceRequest(int result, Intent data) { 431 if (result != Activity.RESULT_OK) { 432 return; 433 } 434 435 Log.i(TAG, "Processing paired device request"); 436 int requestType = data.getIntExtra(PAIRED_DEVICE_REQUEST_TYPE, -1); 437 438 PairDevice p; 439 440 switch (requestType) { 441 case FORGET_ACTION: 442 try { 443 p = (PairDevice) data.getParcelableExtra(PAIRED_DEVICE_EXTRA); 444 mAdbManager.unpairDevice(p.getGuid()); 445 } catch (RemoteException e) { 446 Log.e(TAG, "Unable to forget the device"); 447 } 448 break; 449 default: 450 break; 451 } 452 } 453 handlePairingDeviceRequest(int result, Intent data)454 void handlePairingDeviceRequest(int result, Intent data) { 455 if (result != Activity.RESULT_OK) { 456 return; 457 } 458 459 int requestType = data.getIntExtra(PAIRING_DEVICE_REQUEST_TYPE, -1); 460 switch (requestType) { 461 case FAIL_ACTION: 462 showDialog(AdbWirelessDialogUiBase.MODE_PAIRING_FAILED); 463 break; 464 default: 465 break; 466 } 467 } 468 getDeviceName()469 private String getDeviceName() { 470 // Keep device name in sync with Settings > About phone > Device name 471 String deviceName = Settings.Global.getString(getContext().getContentResolver(), 472 Settings.Global.DEVICE_NAME); 473 if (deviceName == null) { 474 deviceName = Build.MODEL; 475 } 476 return deviceName; 477 } 478 launchQrcodeScannerFragment()479 private void launchQrcodeScannerFragment() { 480 new SubSettingLauncher(getContext()) 481 .setDestination(AdbQrcodeScannerFragment.class.getName()) 482 .setSourceMetricsCategory(getMetricsCategory()) 483 .setResultListener(this, PAIRING_DEVICE_REQUEST) 484 .launch(); 485 } 486 } 487