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