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 68 public BluetoothDeviceUpdater(Context context, DashboardFragment fragment, 69 DevicePreferenceCallback devicePreferenceCallback) { 70 this(fragment, devicePreferenceCallback, Utils.getLocalBtManager(context)); 71 } 72 73 @VisibleForTesting 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 */ 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 */ 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 */ 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 115 public void onBluetoothStateChanged(int bluetoothState) { 116 forceUpdate(); 117 } 118 119 @Override 120 public void onScanningStateChanged(boolean started) {} 121 122 @Override 123 public void onDeviceAdded(CachedBluetoothDevice cachedDevice) { 124 update(cachedDevice); 125 } 126 127 @Override 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 136 public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) { 137 update(cachedDevice); 138 } 139 140 @Override 141 public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {} 142 143 @Override 144 public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) { 145 } 146 147 @Override 148 public void onAudioModeChanged() { 149 } 150 151 @Override 152 public void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state, 153 int bluetoothProfile) { 154 } 155 156 @Override 157 public void onServiceConnected() { 158 // When bluetooth service connected update the UI 159 forceUpdate(); 160 } 161 162 @Override 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 */ 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 */ 178 public abstract boolean isFilterMatched(CachedBluetoothDevice cachedBluetoothDevice); 179 180 /** 181 * Update whether to show {@link CachedBluetoothDevice} in the list. 182 */ 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 */ 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 */ 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 */ 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 */ 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