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