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