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