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