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.support.annotation.VisibleForTesting; 24 import android.support.v7.preference.Preference; 25 import android.support.v7.preference.PreferenceCategory; 26 import android.support.v7.preference.PreferenceGroup; 27 import android.text.BidiFormatter; 28 import android.util.Log; 29 30 import com.android.settings.dashboard.RestrictedDashboardFragment; 31 import com.android.settings.R; 32 import com.android.settingslib.bluetooth.BluetoothCallback; 33 import com.android.settingslib.bluetooth.BluetoothDeviceFilter; 34 import com.android.settingslib.bluetooth.CachedBluetoothDevice; 35 import com.android.settingslib.bluetooth.LocalBluetoothAdapter; 36 import com.android.settingslib.bluetooth.LocalBluetoothManager; 37 38 import java.util.Collection; 39 import java.util.WeakHashMap; 40 41 /** 42 * Parent class for settings fragments that contain a list of Bluetooth 43 * devices. 44 * 45 * @see BluetoothSettings 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 DevelopmentSettings.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 LocalBluetoothAdapter mLocalAdapter; 68 LocalBluetoothManager mLocalManager; 69 70 @VisibleForTesting 71 PreferenceGroup mDeviceListGroup; 72 73 final WeakHashMap<CachedBluetoothDevice, BluetoothDevicePreference> mDevicePreferenceMap = 74 new WeakHashMap<CachedBluetoothDevice, BluetoothDevicePreference>(); 75 76 boolean mShowDevicesWithoutNames; 77 78 DeviceListPreferenceFragment(String restrictedKey) { 79 super(restrictedKey); 80 mFilter = BluetoothDeviceFilter.ALL_FILTER; 81 } 82 83 final void setFilter(BluetoothDeviceFilter.Filter filter) { 84 mFilter = filter; 85 } 86 87 final void setFilter(int filterType) { 88 mFilter = BluetoothDeviceFilter.getFilter(filterType); 89 } 90 91 @Override 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 mLocalAdapter = mLocalManager.getBluetoothAdapter(); 101 102 initPreferencesFromPreferenceScreen(); 103 104 mDeviceListGroup = (PreferenceCategory) findPreference(getDeviceListKey()); 105 } 106 107 /** find and update preference that already existed in preference screen */ 108 abstract void initPreferencesFromPreferenceScreen(); 109 110 @Override 111 public void onStart() { 112 super.onStart(); 113 mShowDevicesWithoutNames = SystemProperties.getBoolean( 114 BLUETOOTH_SHOW_DEVICES_WITHOUT_NAMES_PROPERTY, false); 115 if (mLocalManager == null || isUiRestricted()) return; 116 117 mLocalManager.setForegroundActivity(getActivity()); 118 mLocalManager.getEventManager().registerCallback(this); 119 } 120 121 @Override 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 133 void removeAllDevices() { 134 mDevicePreferenceMap.clear(); 135 mDeviceListGroup.removeAll(); 136 } 137 138 void addCachedDevices() { 139 Collection<CachedBluetoothDevice> cachedDevices = 140 mLocalManager.getCachedDeviceManager().getCachedDevicesCopy(); 141 for (CachedBluetoothDevice cachedDevice : cachedDevices) { 142 onDeviceAdded(cachedDevice); 143 } 144 } 145 146 @Override 147 public boolean onPreferenceTreeClick(Preference preference) { 148 if (KEY_BT_SCAN.equals(preference.getKey())) { 149 mLocalAdapter.startScanning(true); 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 164 void onDevicePreferenceClick(BluetoothDevicePreference btPreference) { 165 btPreference.onClicked(); 166 } 167 168 @Override 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 (mLocalAdapter.getBluetoothState() != BluetoothAdapter.STATE_ON) return; 176 177 if (mFilter.matches(cachedDevice.getDevice())) { 178 createDevicePreference(cachedDevice); 179 } 180 } 181 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, this); 194 preference.setKey(key); 195 mDeviceListGroup.addPreference(preference); 196 } else { 197 // Tell the preference it is being re-used in case there is new info in the 198 // cached device. 199 preference.rebind(); 200 } 201 202 initDevicePreference(preference); 203 mDevicePreferenceMap.put(cachedDevice, preference); 204 } 205 206 /** 207 * Overridden in {@link BluetoothSettings} to add a listener. 208 * @param preference the newly added preference 209 */ 210 void initDevicePreference(BluetoothDevicePreference preference) { 211 // Does nothing by default 212 } 213 214 @VisibleForTesting 215 void updateFooterPreference(Preference myDevicePreference) { 216 final BidiFormatter bidiFormatter = BidiFormatter.getInstance(); 217 218 myDevicePreference.setTitle(getString( 219 R.string.bluetooth_footer_mac_message, 220 bidiFormatter.unicodeWrap(mLocalAdapter.getAddress()))); 221 } 222 223 @Override 224 public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) { 225 BluetoothDevicePreference preference = mDevicePreferenceMap.remove(cachedDevice); 226 if (preference != null) { 227 mDeviceListGroup.removePreference(preference); 228 } 229 } 230 231 @VisibleForTesting 232 void enableScanning() { 233 // LocalBluetoothAdapter already handles repeated scan requests 234 mLocalAdapter.startScanning(true); 235 mScanEnabled = true; 236 } 237 238 @VisibleForTesting 239 void disableScanning() { 240 mLocalAdapter.stopScanning(); 241 mScanEnabled = false; 242 } 243 244 @Override 245 public void onScanningStateChanged(boolean started) { 246 if (!started && mScanEnabled) { 247 mLocalAdapter.startScanning(true); 248 } 249 } 250 251 @Override 252 public void onBluetoothStateChanged(int bluetoothState) {} 253 254 /** 255 * Add bluetooth device preferences to {@code preferenceGroup} which satisfy the {@code filter} 256 * 257 * This method will also (1) set the title for {@code preferenceGroup} and (2) change the 258 * default preferenceGroup and filter 259 * @param preferenceGroup 260 * @param titleId 261 * @param filter 262 * @param addCachedDevices 263 */ 264 public void addDeviceCategory(PreferenceGroup preferenceGroup, int titleId, 265 BluetoothDeviceFilter.Filter filter, boolean addCachedDevices) { 266 cacheRemoveAllPrefs(preferenceGroup); 267 preferenceGroup.setTitle(titleId); 268 mDeviceListGroup = preferenceGroup; 269 setFilter(filter); 270 if (addCachedDevices) { 271 addCachedDevices(); 272 } 273 preferenceGroup.setEnabled(true); 274 removeCachedPrefs(preferenceGroup); 275 } 276 277 public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) { } 278 279 /** 280 * Return the key of the {@link PreferenceGroup} that contains the bluetooth devices 281 */ 282 public abstract String getDeviceListKey(); 283 284 public boolean shouldShowDevicesWithoutNames() { 285 return mShowDevicesWithoutNames; 286 } 287 } 288