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 startScanning(); 233 mScanEnabled = true; 234 } 235 236 @VisibleForTesting disableScanning()237 void disableScanning() { 238 stopScanning(); 239 mScanEnabled = false; 240 } 241 242 @Override onScanningStateChanged(boolean started)243 public void onScanningStateChanged(boolean started) { 244 if (!started && mScanEnabled) { 245 startScanning(); 246 } 247 } 248 249 /** 250 * Add bluetooth device preferences to {@code preferenceGroup} which satisfy the {@code filter} 251 * 252 * This method will also (1) set the title for {@code preferenceGroup} and (2) change the 253 * default preferenceGroup and filter 254 * @param preferenceGroup 255 * @param titleId 256 * @param filter 257 * @param addCachedDevices 258 */ addDeviceCategory(PreferenceGroup preferenceGroup, int titleId, BluetoothDeviceFilter.Filter filter, boolean addCachedDevices)259 public void addDeviceCategory(PreferenceGroup preferenceGroup, int titleId, 260 BluetoothDeviceFilter.Filter filter, boolean addCachedDevices) { 261 cacheRemoveAllPrefs(preferenceGroup); 262 preferenceGroup.setTitle(titleId); 263 mDeviceListGroup = preferenceGroup; 264 if (addCachedDevices) { 265 // Don't show bonded devices when screen turned back on 266 setFilter(BluetoothDeviceFilter.UNBONDED_DEVICE_FILTER); 267 addCachedDevices(); 268 } 269 setFilter(filter); 270 preferenceGroup.setEnabled(true); 271 removeCachedPrefs(preferenceGroup); 272 } 273 274 /** 275 * Return the key of the {@link PreferenceGroup} that contains the bluetooth devices 276 */ getDeviceListKey()277 public abstract String getDeviceListKey(); 278 shouldShowDevicesWithoutNames()279 public boolean shouldShowDevicesWithoutNames() { 280 return mShowDevicesWithoutNames; 281 } 282 startScanning()283 void startScanning() { 284 if (!mBluetoothAdapter.isDiscovering()) { 285 mBluetoothAdapter.startDiscovery(); 286 } 287 } 288 stopScanning()289 void stopScanning() { 290 if (mBluetoothAdapter.isDiscovering()) { 291 mBluetoothAdapter.cancelDiscovery(); 292 } 293 } 294 } 295