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