1 /* 2 * Copyright (C) 2011 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.bluetooth; 18 19 import android.bluetooth.BluetoothAdapter; 20 import android.bluetooth.BluetoothDevice; 21 import android.os.Bundle; 22 import android.os.SystemProperties; 23 import android.text.BidiFormatter; 24 import android.util.Log; 25 26 import androidx.annotation.VisibleForTesting; 27 import androidx.preference.Preference; 28 import androidx.preference.PreferenceCategory; 29 import androidx.preference.PreferenceGroup; 30 31 import com.android.settings.R; 32 import com.android.settings.dashboard.RestrictedDashboardFragment; 33 import com.android.settingslib.bluetooth.BluetoothCallback; 34 import com.android.settingslib.bluetooth.BluetoothDeviceFilter; 35 import com.android.settingslib.bluetooth.CachedBluetoothDevice; 36 import com.android.settingslib.bluetooth.LocalBluetoothManager; 37 38 import java.util.Collection; 39 import java.util.HashMap; 40 import java.util.WeakHashMap; 41 42 /** 43 * Parent class for settings fragments that contain a list of Bluetooth 44 * devices. 45 * 46 * @see DevicePickerFragment 47 */ 48 // TODO: Refactor this fragment 49 public abstract class DeviceListPreferenceFragment extends 50 RestrictedDashboardFragment implements BluetoothCallback { 51 52 private static final String TAG = "DeviceListPreferenceFragment"; 53 54 private static final String KEY_BT_SCAN = "bt_scan"; 55 56 // Copied from BluetoothDeviceNoNamePreferenceController.java 57 private static final String BLUETOOTH_SHOW_DEVICES_WITHOUT_NAMES_PROPERTY = 58 "persist.bluetooth.showdeviceswithoutnames"; 59 60 private BluetoothDeviceFilter.Filter mFilter; 61 62 @VisibleForTesting 63 boolean mScanEnabled; 64 65 BluetoothDevice mSelectedDevice; 66 67 BluetoothAdapter mBluetoothAdapter; 68 LocalBluetoothManager mLocalManager; 69 70 @VisibleForTesting 71 PreferenceGroup mDeviceListGroup; 72 73 final HashMap<CachedBluetoothDevice, BluetoothDevicePreference> mDevicePreferenceMap = 74 new HashMap<>(); 75 76 boolean mShowDevicesWithoutNames; 77 DeviceListPreferenceFragment(String restrictedKey)78 DeviceListPreferenceFragment(String restrictedKey) { 79 super(restrictedKey); 80 mFilter = BluetoothDeviceFilter.ALL_FILTER; 81 } 82 setFilter(BluetoothDeviceFilter.Filter filter)83 final void setFilter(BluetoothDeviceFilter.Filter filter) { 84 mFilter = filter; 85 } 86 setFilter(int filterType)87 final void setFilter(int filterType) { 88 mFilter = BluetoothDeviceFilter.getFilter(filterType); 89 } 90 91 @Override onCreate(Bundle savedInstanceState)92 public void onCreate(Bundle savedInstanceState) { 93 super.onCreate(savedInstanceState); 94 95 mLocalManager = Utils.getLocalBtManager(getActivity()); 96 if (mLocalManager == null) { 97 Log.e(TAG, "Bluetooth is not supported on this device"); 98 return; 99 } 100 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 101 mShowDevicesWithoutNames = SystemProperties.getBoolean( 102 BLUETOOTH_SHOW_DEVICES_WITHOUT_NAMES_PROPERTY, false); 103 104 initPreferencesFromPreferenceScreen(); 105 106 mDeviceListGroup = (PreferenceCategory) findPreference(getDeviceListKey()); 107 } 108 109 /** find and update preference that already existed in preference screen */ initPreferencesFromPreferenceScreen()110 abstract void initPreferencesFromPreferenceScreen(); 111 112 @Override onStart()113 public void onStart() { 114 super.onStart(); 115 if (mLocalManager == null || isUiRestricted()) return; 116 117 mLocalManager.setForegroundActivity(getActivity()); 118 mLocalManager.getEventManager().registerCallback(this); 119 } 120 121 @Override onStop()122 public void onStop() { 123 super.onStop(); 124 if (mLocalManager == null || isUiRestricted()) { 125 return; 126 } 127 128 removeAllDevices(); 129 mLocalManager.setForegroundActivity(null); 130 mLocalManager.getEventManager().unregisterCallback(this); 131 } 132 removeAllDevices()133 void removeAllDevices() { 134 mDevicePreferenceMap.clear(); 135 mDeviceListGroup.removeAll(); 136 } 137 addCachedDevices()138 void addCachedDevices() { 139 Collection<CachedBluetoothDevice> cachedDevices = 140 mLocalManager.getCachedDeviceManager().getCachedDevicesCopy(); 141 for (CachedBluetoothDevice cachedDevice : cachedDevices) { 142 onDeviceAdded(cachedDevice); 143 } 144 } 145 146 @Override onPreferenceTreeClick(Preference preference)147 public boolean onPreferenceTreeClick(Preference preference) { 148 if (KEY_BT_SCAN.equals(preference.getKey())) { 149 startScanning(); 150 return true; 151 } 152 153 if (preference instanceof BluetoothDevicePreference) { 154 BluetoothDevicePreference btPreference = (BluetoothDevicePreference) preference; 155 CachedBluetoothDevice device = btPreference.getCachedDevice(); 156 mSelectedDevice = device.getDevice(); 157 onDevicePreferenceClick(btPreference); 158 return true; 159 } 160 161 return super.onPreferenceTreeClick(preference); 162 } 163 onDevicePreferenceClick(BluetoothDevicePreference btPreference)164 void onDevicePreferenceClick(BluetoothDevicePreference btPreference) { 165 btPreference.onClicked(); 166 } 167 168 @Override onDeviceAdded(CachedBluetoothDevice cachedDevice)169 public void onDeviceAdded(CachedBluetoothDevice cachedDevice) { 170 if (mDevicePreferenceMap.get(cachedDevice) != null) { 171 return; 172 } 173 174 // Prevent updates while the list shows one of the state messages 175 if (mBluetoothAdapter.getState() != BluetoothAdapter.STATE_ON) return; 176 177 if (mFilter.matches(cachedDevice.getDevice())) { 178 createDevicePreference(cachedDevice); 179 } 180 } 181 createDevicePreference(CachedBluetoothDevice cachedDevice)182 void createDevicePreference(CachedBluetoothDevice cachedDevice) { 183 if (mDeviceListGroup == null) { 184 Log.w(TAG, "Trying to create a device preference before the list group/category " 185 + "exists!"); 186 return; 187 } 188 189 String key = cachedDevice.getDevice().getAddress(); 190 BluetoothDevicePreference preference = (BluetoothDevicePreference) getCachedPreference(key); 191 192 if (preference == null) { 193 preference = new BluetoothDevicePreference(getPrefContext(), cachedDevice, 194 mShowDevicesWithoutNames); 195 preference.setKey(key); 196 //Set hideSecondTarget is true if it's bonded device. 197 preference.hideSecondTarget(true); 198 mDeviceListGroup.addPreference(preference); 199 } 200 201 initDevicePreference(preference); 202 mDevicePreferenceMap.put(cachedDevice, preference); 203 } 204 initDevicePreference(BluetoothDevicePreference preference)205 protected void initDevicePreference(BluetoothDevicePreference preference) { 206 // Does nothing by default 207 } 208 209 @VisibleForTesting updateFooterPreference(Preference myDevicePreference)210 void updateFooterPreference(Preference myDevicePreference) { 211 final BidiFormatter bidiFormatter = BidiFormatter.getInstance(); 212 213 myDevicePreference.setTitle(getString( 214 R.string.bluetooth_footer_mac_message, 215 bidiFormatter.unicodeWrap(mBluetoothAdapter.getAddress()))); 216 } 217 218 @Override onDeviceDeleted(CachedBluetoothDevice cachedDevice)219 public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) { 220 BluetoothDevicePreference preference = mDevicePreferenceMap.remove(cachedDevice); 221 if (preference != null) { 222 mDeviceListGroup.removePreference(preference); 223 } 224 } 225 226 @VisibleForTesting enableScanning()227 void enableScanning() { 228 // BluetoothAdapter already handles repeated scan requests 229 startScanning(); 230 mScanEnabled = true; 231 } 232 233 @VisibleForTesting disableScanning()234 void disableScanning() { 235 stopScanning(); 236 mScanEnabled = false; 237 } 238 239 @Override onScanningStateChanged(boolean started)240 public void onScanningStateChanged(boolean started) { 241 if (!started && mScanEnabled) { 242 startScanning(); 243 } 244 } 245 246 /** 247 * Add bluetooth device preferences to {@code preferenceGroup} which satisfy the {@code filter} 248 * 249 * This method will also (1) set the title for {@code preferenceGroup} and (2) change the 250 * default preferenceGroup and filter 251 * @param preferenceGroup 252 * @param titleId 253 * @param filter 254 * @param addCachedDevices 255 */ addDeviceCategory(PreferenceGroup preferenceGroup, int titleId, BluetoothDeviceFilter.Filter filter, boolean addCachedDevices)256 public void addDeviceCategory(PreferenceGroup preferenceGroup, int titleId, 257 BluetoothDeviceFilter.Filter filter, boolean addCachedDevices) { 258 cacheRemoveAllPrefs(preferenceGroup); 259 preferenceGroup.setTitle(titleId); 260 mDeviceListGroup = preferenceGroup; 261 if (addCachedDevices) { 262 // Don't show bonded devices when screen turned back on 263 setFilter(BluetoothDeviceFilter.UNBONDED_DEVICE_FILTER); 264 addCachedDevices(); 265 } 266 setFilter(filter); 267 preferenceGroup.setEnabled(true); 268 removeCachedPrefs(preferenceGroup); 269 } 270 271 /** 272 * Return the key of the {@link PreferenceGroup} that contains the bluetooth devices 273 */ getDeviceListKey()274 public abstract String getDeviceListKey(); 275 shouldShowDevicesWithoutNames()276 public boolean shouldShowDevicesWithoutNames() { 277 return mShowDevicesWithoutNames; 278 } 279 startScanning()280 void startScanning() { 281 if (!mBluetoothAdapter.isDiscovering()) { 282 mBluetoothAdapter.startDiscovery(); 283 } 284 } 285 stopScanning()286 void stopScanning() { 287 if (mBluetoothAdapter.isDiscovering()) { 288 mBluetoothAdapter.cancelDiscovery(); 289 } 290 } 291 } 292