• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.bluetooth;
17 
18 import android.bluetooth.BluetoothAdapter;
19 import android.bluetooth.BluetoothDevice;
20 import android.content.Context;
21 import android.os.Bundle;
22 import android.util.Log;
23 
24 import androidx.annotation.VisibleForTesting;
25 import androidx.preference.Preference;
26 
27 import com.android.settings.R;
28 import com.android.settings.connecteddevice.DevicePreferenceCallback;
29 import com.android.settings.core.SubSettingLauncher;
30 import com.android.settings.dashboard.DashboardFragment;
31 import com.android.settings.widget.GearPreference;
32 import com.android.settingslib.bluetooth.BluetoothCallback;
33 import com.android.settingslib.bluetooth.BluetoothDeviceFilter;
34 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
35 import com.android.settingslib.bluetooth.LocalBluetoothManager;
36 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
37 
38 import java.util.Collection;
39 import java.util.HashMap;
40 import java.util.Map;
41 
42 /**
43  * Update the bluetooth devices. It gets bluetooth event from {@link LocalBluetoothManager} using
44  * {@link BluetoothCallback}. It notifies the upper level whether to add/remove the preference
45  * through {@link DevicePreferenceCallback}
46  *
47  * In {@link BluetoothDeviceUpdater}, it uses {@link BluetoothDeviceFilter.Filter} to detect
48  * whether the {@link CachedBluetoothDevice} is relevant.
49  */
50 public abstract class BluetoothDeviceUpdater implements BluetoothCallback,
51         LocalBluetoothProfileManager.ServiceListener {
52     private static final String TAG = "BluetoothDeviceUpdater";
53     private static final boolean DBG = false;
54 
55     protected final DevicePreferenceCallback mDevicePreferenceCallback;
56     protected final Map<BluetoothDevice, Preference> mPreferenceMap;
57     protected Context mPrefContext;
58     protected DashboardFragment mFragment;
59     @VisibleForTesting
60     protected LocalBluetoothManager mLocalManager;
61 
62     @VisibleForTesting
63     final GearPreference.OnGearClickListener mDeviceProfilesListener = pref -> {
64         launchDeviceDetails(pref);
65     };
66 
BluetoothDeviceUpdater(Context context, DashboardFragment fragment, DevicePreferenceCallback devicePreferenceCallback)67     public BluetoothDeviceUpdater(Context context, DashboardFragment fragment,
68             DevicePreferenceCallback devicePreferenceCallback) {
69         this(fragment, devicePreferenceCallback, Utils.getLocalBtManager(context));
70     }
71 
72     @VisibleForTesting
BluetoothDeviceUpdater(DashboardFragment fragment, DevicePreferenceCallback devicePreferenceCallback, LocalBluetoothManager localManager)73     BluetoothDeviceUpdater(DashboardFragment fragment,
74             DevicePreferenceCallback devicePreferenceCallback, LocalBluetoothManager localManager) {
75         mFragment = fragment;
76         mDevicePreferenceCallback = devicePreferenceCallback;
77         mPreferenceMap = new HashMap<>();
78         mLocalManager = localManager;
79     }
80 
81     /**
82      * Register the bluetooth event callback and update the list
83      */
registerCallback()84     public void registerCallback() {
85         if (mLocalManager == null) {
86             Log.e(TAG, "registerCallback() Bluetooth is not supported on this device");
87             return;
88         }
89         mLocalManager.setForegroundActivity(mFragment.getContext());
90         mLocalManager.getEventManager().registerCallback(this);
91         mLocalManager.getProfileManager().addServiceListener(this);
92         forceUpdate();
93     }
94 
95     /**
96      * Unregister the bluetooth event callback
97      */
unregisterCallback()98     public void unregisterCallback() {
99         if (mLocalManager == null) {
100             Log.e(TAG, "unregisterCallback() Bluetooth is not supported on this device");
101             return;
102         }
103         mLocalManager.setForegroundActivity(null);
104         mLocalManager.getEventManager().unregisterCallback(this);
105         mLocalManager.getProfileManager().removeServiceListener(this);
106     }
107 
108     /**
109      * Force to update the list of bluetooth devices
110      */
forceUpdate()111     public void forceUpdate() {
112         if (mLocalManager == null) {
113             Log.e(TAG, "forceUpdate() Bluetooth is not supported on this device");
114             return;
115         }
116         if (BluetoothAdapter.getDefaultAdapter().isEnabled()) {
117             final Collection<CachedBluetoothDevice> cachedDevices =
118                     mLocalManager.getCachedDeviceManager().getCachedDevicesCopy();
119             for (CachedBluetoothDevice cachedBluetoothDevice : cachedDevices) {
120                 update(cachedBluetoothDevice);
121             }
122         } else {
123           removeAllDevicesFromPreference();
124         }
125     }
126 
removeAllDevicesFromPreference()127     public void removeAllDevicesFromPreference() {
128         if (mLocalManager == null) {
129             Log.e(TAG, "removeAllDevicesFromPreference() BT is not supported on this device");
130             return;
131         }
132         final Collection<CachedBluetoothDevice> cachedDevices =
133                 mLocalManager.getCachedDeviceManager().getCachedDevicesCopy();
134         for (CachedBluetoothDevice cachedBluetoothDevice : cachedDevices) {
135             removePreference(cachedBluetoothDevice);
136         }
137     }
138 
139     @Override
onBluetoothStateChanged(int bluetoothState)140     public void onBluetoothStateChanged(int bluetoothState) {
141         if (BluetoothAdapter.STATE_ON == bluetoothState) {
142             forceUpdate();
143         } else if (BluetoothAdapter.STATE_OFF == bluetoothState) {
144             removeAllDevicesFromPreference();
145         }
146     }
147 
148     @Override
onDeviceAdded(CachedBluetoothDevice cachedDevice)149     public void onDeviceAdded(CachedBluetoothDevice cachedDevice) {
150         update(cachedDevice);
151     }
152 
153     @Override
onDeviceDeleted(CachedBluetoothDevice cachedDevice)154     public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {
155         // Used to combine the hearing aid entries just after pairing. Once both the hearing aids
156         // get connected and their hiSyncId gets populated, this gets called for one of the
157         // 2 hearing aids so that only one entry in the connected devices list will be seen.
158         removePreference(cachedDevice);
159     }
160 
161     @Override
onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState)162     public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
163         update(cachedDevice);
164     }
165 
166     @Override
onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state, int bluetoothProfile)167     public void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state,
168             int bluetoothProfile) {
169         if (DBG) {
170             Log.d(TAG, "onProfileConnectionStateChanged() device: " + cachedDevice.getName()
171                     + ", state: " + state + ", bluetoothProfile: " + bluetoothProfile);
172         }
173         update(cachedDevice);
174     }
175 
176     @Override
onAclConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state)177     public void onAclConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
178         if (DBG) {
179             Log.d(TAG, "onAclConnectionStateChanged() device: " + cachedDevice.getName()
180                     + ", state: " + state);
181         }
182         update(cachedDevice);
183     }
184 
185     @Override
onServiceConnected()186     public void onServiceConnected() {
187         // When bluetooth service connected update the UI
188         forceUpdate();
189     }
190 
191     @Override
onServiceDisconnected()192     public void onServiceDisconnected() {
193 
194     }
195 
196     /**
197      * Set the context to generate the {@link Preference}, so it could get the correct theme.
198      */
setPrefContext(Context context)199     public void setPrefContext(Context context) {
200         mPrefContext = context;
201     }
202 
203     /**
204      * Return {@code true} if {@code cachedBluetoothDevice} matches this
205      * {@link BluetoothDeviceUpdater} and should stay in the list, otherwise return {@code false}
206      */
isFilterMatched(CachedBluetoothDevice cachedBluetoothDevice)207     public abstract boolean isFilterMatched(CachedBluetoothDevice cachedBluetoothDevice);
208 
209     /**
210      * Update whether to show {@link CachedBluetoothDevice} in the list.
211      */
update(CachedBluetoothDevice cachedBluetoothDevice)212     protected void update(CachedBluetoothDevice cachedBluetoothDevice) {
213         if (isFilterMatched(cachedBluetoothDevice)) {
214             // Add the preference if it is new one
215             addPreference(cachedBluetoothDevice);
216         } else {
217             removePreference(cachedBluetoothDevice);
218         }
219     }
220 
221     /**
222      * Add the {@link Preference} that represents the {@code cachedDevice}
223      */
addPreference(CachedBluetoothDevice cachedDevice)224     protected void addPreference(CachedBluetoothDevice cachedDevice) {
225         final BluetoothDevice device = cachedDevice.getDevice();
226         if (!mPreferenceMap.containsKey(device)) {
227             BluetoothDevicePreference btPreference =
228                     new BluetoothDevicePreference(mPrefContext, cachedDevice,
229                             true /* showDeviceWithoutNames */);
230             btPreference.setOnGearClickListener(mDeviceProfilesListener);
231             if (this instanceof Preference.OnPreferenceClickListener) {
232                 btPreference.setOnPreferenceClickListener(
233                         (Preference.OnPreferenceClickListener)this);
234             }
235             mPreferenceMap.put(device, btPreference);
236             mDevicePreferenceCallback.onDeviceAdded(btPreference);
237         }
238     }
239 
240     /**
241      * Remove the {@link Preference} that represents the {@code cachedDevice}
242      */
removePreference(CachedBluetoothDevice cachedDevice)243     protected void removePreference(CachedBluetoothDevice cachedDevice) {
244         final BluetoothDevice device = cachedDevice.getDevice();
245         final CachedBluetoothDevice subCachedDevice = cachedDevice.getSubDevice();
246         if (mPreferenceMap.containsKey(device)) {
247             mDevicePreferenceCallback.onDeviceRemoved(mPreferenceMap.get(device));
248             mPreferenceMap.remove(device);
249         } else if (subCachedDevice != null) {
250             // When doing remove, to check if preference maps to sub device.
251             // This would happen when connection state is changed in detail page that there is no
252             // callback from SettingsLib.
253             final BluetoothDevice subDevice = subCachedDevice.getDevice();
254             if (mPreferenceMap.containsKey(subDevice)) {
255                 mDevicePreferenceCallback.onDeviceRemoved(mPreferenceMap.get(subDevice));
256                 mPreferenceMap.remove(subDevice);
257             }
258         }
259     }
260 
261     /**
262      * Get {@link CachedBluetoothDevice} from {@link Preference} and it is used to init
263      * {@link SubSettingLauncher} to launch {@link BluetoothDeviceDetailsFragment}
264      */
launchDeviceDetails(Preference preference)265     protected void launchDeviceDetails(Preference preference) {
266         final CachedBluetoothDevice device =
267                 ((BluetoothDevicePreference) preference).getBluetoothDevice();
268         if (device == null) {
269             return;
270         }
271         final Bundle args = new Bundle();
272         args.putString(BluetoothDeviceDetailsFragment.KEY_DEVICE_ADDRESS,
273                 device.getDevice().getAddress());
274 
275         new SubSettingLauncher(mFragment.getContext())
276                 .setDestination(BluetoothDeviceDetailsFragment.class.getName())
277                 .setArguments(args)
278                 .setTitleRes(R.string.device_details_title)
279                 .setSourceMetricsCategory(mFragment.getMetricsCategory())
280                 .launch();
281     }
282 
283     /**
284      * @return {@code true} if {@code cachedBluetoothDevice} is connected
285      * and the bond state is bonded.
286      */
isDeviceConnected(CachedBluetoothDevice cachedDevice)287     public boolean isDeviceConnected(CachedBluetoothDevice cachedDevice) {
288         if (cachedDevice == null) {
289             return false;
290         }
291         final BluetoothDevice device = cachedDevice.getDevice();
292         if (DBG) {
293             Log.d(TAG, "isDeviceConnected() device name : " + cachedDevice.getName() +
294                     ", is connected : " + device.isConnected() + " , is profile connected : "
295                     + cachedDevice.isConnected());
296         }
297         return device.getBondState() == BluetoothDevice.BOND_BONDED && device.isConnected();
298     }
299 }
300