1 /*
2  * Copyright (C) 2017 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 package com.android.settings.connecteddevice;
17 
18 import android.content.Context;
19 import android.content.pm.PackageManager;
20 import android.hardware.input.InputManager;
21 import android.util.FeatureFlagUtils;
22 import android.util.Log;
23 import android.view.InputDevice;
24 
25 import androidx.annotation.VisibleForTesting;
26 import androidx.preference.Preference;
27 import androidx.preference.PreferenceGroup;
28 import androidx.preference.PreferenceScreen;
29 
30 import com.android.settings.R;
31 import com.android.settings.bluetooth.BluetoothDeviceUpdater;
32 import com.android.settings.bluetooth.ConnectedBluetoothDeviceUpdater;
33 import com.android.settings.bluetooth.Utils;
34 import com.android.settings.connecteddevice.dock.DockUpdater;
35 import com.android.settings.connecteddevice.stylus.StylusDeviceUpdater;
36 import com.android.settings.connecteddevice.usb.ConnectedUsbDeviceUpdater;
37 import com.android.settings.core.BasePreferenceController;
38 import com.android.settings.core.PreferenceControllerMixin;
39 import com.android.settings.dashboard.DashboardFragment;
40 import com.android.settings.flags.Flags;
41 import com.android.settings.overlay.DockUpdaterFeatureProvider;
42 import com.android.settings.overlay.FeatureFactory;
43 import com.android.settingslib.bluetooth.BluetoothDeviceFilter;
44 import com.android.settingslib.bluetooth.BluetoothUtils;
45 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
46 import com.android.settingslib.bluetooth.LocalBluetoothManager;
47 import com.android.settingslib.core.lifecycle.LifecycleObserver;
48 import com.android.settingslib.core.lifecycle.events.OnStart;
49 import com.android.settingslib.core.lifecycle.events.OnStop;
50 import com.android.settingslib.search.SearchIndexableRaw;
51 
52 import java.util.List;
53 
54 /**
55  * Controller to maintain the {@link androidx.preference.PreferenceGroup} for all
56  * connected devices. It uses {@link DevicePreferenceCallback} to add/remove {@link Preference}
57  */
58 public class ConnectedDeviceGroupController extends BasePreferenceController
59         implements PreferenceControllerMixin, LifecycleObserver, OnStart, OnStop,
60         DevicePreferenceCallback {
61 
62     private static final String KEY = "connected_device_list";
63     private static final String TAG = "ConnectedDeviceGroupController";
64 
65     @VisibleForTesting
66     PreferenceGroup mPreferenceGroup;
67     private BluetoothDeviceUpdater mBluetoothDeviceUpdater;
68     private ConnectedUsbDeviceUpdater mConnectedUsbDeviceUpdater;
69     private DockUpdater mConnectedDockUpdater;
70     private StylusDeviceUpdater mStylusDeviceUpdater;
71     private final PackageManager mPackageManager;
72     private final InputManager mInputManager;
73     private final LocalBluetoothManager mLocalBluetoothManager;
74 
ConnectedDeviceGroupController(Context context)75     public ConnectedDeviceGroupController(Context context) {
76         super(context, KEY);
77         mPackageManager = context.getPackageManager();
78         mInputManager = context.getSystemService(InputManager.class);
79         mLocalBluetoothManager = Utils.getLocalBluetoothManager(context);
80     }
81 
82     @Override
onStart()83     public void onStart() {
84         if (mBluetoothDeviceUpdater != null) {
85             mBluetoothDeviceUpdater.registerCallback();
86             mBluetoothDeviceUpdater.refreshPreference();
87         }
88 
89         if (mConnectedUsbDeviceUpdater != null) {
90             mConnectedUsbDeviceUpdater.registerCallback();
91         }
92 
93         if (mConnectedDockUpdater != null) {
94             mConnectedDockUpdater.registerCallback();
95         }
96 
97         if (mStylusDeviceUpdater != null) {
98             mStylusDeviceUpdater.registerCallback();
99         }
100     }
101 
102     @Override
onStop()103     public void onStop() {
104         if (mBluetoothDeviceUpdater != null) {
105             mBluetoothDeviceUpdater.unregisterCallback();
106         }
107 
108         if (mConnectedUsbDeviceUpdater != null) {
109             mConnectedUsbDeviceUpdater.unregisterCallback();
110         }
111 
112         if (mConnectedDockUpdater != null) {
113             mConnectedDockUpdater.unregisterCallback();
114         }
115 
116         if (mStylusDeviceUpdater != null) {
117             mStylusDeviceUpdater.unregisterCallback();
118         }
119     }
120 
121     @Override
displayPreference(PreferenceScreen screen)122     public void displayPreference(PreferenceScreen screen) {
123         super.displayPreference(screen);
124 
125         mPreferenceGroup = screen.findPreference(KEY);
126         mPreferenceGroup.setVisible(false);
127 
128         if (isAvailable()) {
129             final Context context = screen.getContext();
130             if (mBluetoothDeviceUpdater != null) {
131                 mBluetoothDeviceUpdater.setPrefContext(context);
132                 mBluetoothDeviceUpdater.forceUpdate();
133             }
134 
135             if (mConnectedUsbDeviceUpdater != null) {
136                 mConnectedUsbDeviceUpdater.initUsbPreference(context);
137             }
138 
139             if (mConnectedDockUpdater != null) {
140                 mConnectedDockUpdater.setPreferenceContext(context);
141                 mConnectedDockUpdater.forceUpdate();
142             }
143 
144             if (mStylusDeviceUpdater != null) {
145                 mStylusDeviceUpdater.setPreferenceContext(context);
146                 mStylusDeviceUpdater.forceUpdate();
147             }
148         }
149     }
150 
151     @Override
getAvailabilityStatus()152     public int getAvailabilityStatus() {
153         return (hasBluetoothFeature()
154                 || hasUsbFeature()
155                 || hasUsiStylusFeature()
156                 || mConnectedDockUpdater != null)
157                 ? AVAILABLE_UNSEARCHABLE
158                 : UNSUPPORTED_ON_DEVICE;
159     }
160 
161     @Override
getPreferenceKey()162     public String getPreferenceKey() {
163         return KEY;
164     }
165 
166     @Override
onDeviceAdded(Preference preference)167     public void onDeviceAdded(Preference preference) {
168         if (mPreferenceGroup.getPreferenceCount() == 0) {
169             mPreferenceGroup.setVisible(true);
170         }
171         mPreferenceGroup.addPreference(preference);
172     }
173 
174     @Override
onDeviceRemoved(Preference preference)175     public void onDeviceRemoved(Preference preference) {
176         mPreferenceGroup.removePreference(preference);
177         if (mPreferenceGroup.getPreferenceCount() == 0) {
178             mPreferenceGroup.setVisible(false);
179         }
180     }
181 
182     @VisibleForTesting
init(BluetoothDeviceUpdater bluetoothDeviceUpdater, ConnectedUsbDeviceUpdater connectedUsbDeviceUpdater, DockUpdater connectedDockUpdater, StylusDeviceUpdater connectedStylusDeviceUpdater)183     void init(BluetoothDeviceUpdater bluetoothDeviceUpdater,
184             ConnectedUsbDeviceUpdater connectedUsbDeviceUpdater,
185             DockUpdater connectedDockUpdater,
186             StylusDeviceUpdater connectedStylusDeviceUpdater) {
187 
188         mBluetoothDeviceUpdater = bluetoothDeviceUpdater;
189         mConnectedUsbDeviceUpdater = connectedUsbDeviceUpdater;
190         mConnectedDockUpdater = connectedDockUpdater;
191         mStylusDeviceUpdater = connectedStylusDeviceUpdater;
192     }
193 
init(DashboardFragment fragment)194     public void init(DashboardFragment fragment) {
195         final Context context = fragment.getContext();
196         DockUpdaterFeatureProvider dockUpdaterFeatureProvider =
197                 FeatureFactory.getFeatureFactory().getDockUpdaterFeatureProvider();
198         final DockUpdater connectedDockUpdater =
199                 dockUpdaterFeatureProvider.getConnectedDockUpdater(context, this);
200         init(hasBluetoothFeature()
201                         ? new ConnectedBluetoothDeviceUpdater(context, this,
202                         fragment.getMetricsCategory())
203                         : null,
204                 hasUsbFeature()
205                         ? new ConnectedUsbDeviceUpdater(context, fragment, this)
206                         : null,
207                 connectedDockUpdater,
208                 hasUsiStylusFeature()
209                         ? new StylusDeviceUpdater(context, fragment, this)
210                         : null);
211     }
212 
hasBluetoothFeature()213     private boolean hasBluetoothFeature() {
214         return mPackageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);
215     }
216 
hasUsbFeature()217     private boolean hasUsbFeature() {
218         return mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_ACCESSORY)
219                 || mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST);
220     }
221 
hasUsiStylusFeature()222     private boolean hasUsiStylusFeature() {
223         if (!FeatureFlagUtils.isEnabled(mContext,
224                 FeatureFlagUtils.SETTINGS_SHOW_STYLUS_PREFERENCES)) {
225             return false;
226         }
227 
228         for (int deviceId : mInputManager.getInputDeviceIds()) {
229             InputDevice device = mInputManager.getInputDevice(deviceId);
230             if (device != null
231                     && device.supportsSource(InputDevice.SOURCE_STYLUS)
232                     && !device.isExternal()) {
233                 return true;
234             }
235         }
236         return false;
237     }
238 
239     @Override
updateDynamicRawDataToIndex(List<SearchIndexableRaw> rawData)240     public void updateDynamicRawDataToIndex(List<SearchIndexableRaw> rawData) {
241         if (!Flags.enableBondedBluetoothDeviceSearchable()) {
242             return;
243         }
244         if (mLocalBluetoothManager == null) {
245             Log.d(TAG, "Bluetooth is not supported");
246             return;
247         }
248         for (CachedBluetoothDevice cachedDevice :
249                 mLocalBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy()) {
250             if (!BluetoothDeviceFilter.BONDED_DEVICE_FILTER.matches(cachedDevice.getDevice())) {
251                 continue;
252             }
253             if (BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
254                     cachedDevice.getDevice())) {
255                 continue;
256             }
257             SearchIndexableRaw data = new SearchIndexableRaw(mContext);
258             // Include the identity address as well to ensure the key is unique.
259             data.key = cachedDevice.getName() + cachedDevice.getIdentityAddress();
260             data.title = cachedDevice.getName();
261             data.summaryOn = mContext.getString(R.string.connected_devices_dashboard_title);
262             rawData.add(data);
263         }
264     }
265 }
266