LocalBluetoothManager.java revision 4fcf94c47b5526e1ee64854d7e25bfcac171885b
1/* 2 * Copyright (C) 2008 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 17package com.android.settings.bluetooth; 18 19import com.android.settings.R; 20 21import android.app.Activity; 22import android.app.AlertDialog; 23import android.bluetooth.BluetoothA2dp; 24import android.bluetooth.BluetoothAdapter; 25import android.bluetooth.BluetoothDevice; 26import android.content.Context; 27import android.content.SharedPreferences; 28import android.util.Config; 29import android.util.Log; 30import android.widget.Toast; 31 32import java.util.ArrayList; 33import java.util.List; 34import java.util.Set; 35 36// TODO: have some notion of shutting down. Maybe a minute after they leave BT settings? 37/** 38 * LocalBluetoothManager provides a simplified interface on top of a subset of 39 * the Bluetooth API. 40 */ 41public class LocalBluetoothManager { 42 private static final String TAG = "LocalBluetoothManager"; 43 static final boolean V = Config.LOGV; 44 static final boolean D = Config.LOGD; 45 46 private static final String SHARED_PREFERENCES_NAME = "bluetooth_settings"; 47 48 private static LocalBluetoothManager INSTANCE; 49 /** Used when obtaining a reference to the singleton instance. */ 50 private static Object INSTANCE_LOCK = new Object(); 51 private boolean mInitialized; 52 53 private Context mContext; 54 /** If a BT-related activity is in the foreground, this will be it. */ 55 private Activity mForegroundActivity; 56 private AlertDialog mErrorDialog = null; 57 58 private BluetoothAdapter mAdapter; 59 60 private CachedBluetoothDeviceManager mCachedDeviceManager; 61 private BluetoothEventRedirector mEventRedirector; 62 private BluetoothA2dp mBluetoothA2dp; 63 64 private int mState = BluetoothAdapter.ERROR; 65 66 private List<Callback> mCallbacks = new ArrayList<Callback>(); 67 68 private static final int SCAN_EXPIRATION_MS = 5 * 60 * 1000; // 5 mins 69 70 // If a device was picked from the device picker or was in discoverable mode 71 // in the last 60 seconds, show the pairing dialogs in foreground instead 72 // of raising notifications 73 private static long GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND = 60 * 1000; 74 75 private static final String SHARED_PREFERENCES_KEY_LAST_SELECTED_DEVICE = 76 "last_selected_device"; 77 78 private static final String SHARED_PREFERENCES_KEY_LAST_SELECTED_DEVICE_TIME = 79 "last_selected_device_time"; 80 81 private long mLastScan; 82 83 public static LocalBluetoothManager getInstance(Context context) { 84 synchronized (INSTANCE_LOCK) { 85 if (INSTANCE == null) { 86 INSTANCE = new LocalBluetoothManager(); 87 } 88 89 if (!INSTANCE.init(context)) { 90 return null; 91 } 92 93 LocalBluetoothProfileManager.init(INSTANCE); 94 95 return INSTANCE; 96 } 97 } 98 99 private boolean init(Context context) { 100 if (mInitialized) return true; 101 mInitialized = true; 102 103 // This will be around as long as this process is 104 mContext = context.getApplicationContext(); 105 106 mAdapter = BluetoothAdapter.getDefaultAdapter(); 107 if (mAdapter == null) { 108 return false; 109 } 110 111 mCachedDeviceManager = new CachedBluetoothDeviceManager(this); 112 113 mEventRedirector = new BluetoothEventRedirector(this); 114 mEventRedirector.start(); 115 116 mBluetoothA2dp = new BluetoothA2dp(context); 117 118 return true; 119 } 120 121 public BluetoothAdapter getBluetoothAdapter() { 122 return mAdapter; 123 } 124 125 public Context getContext() { 126 return mContext; 127 } 128 129 public Activity getForegroundActivity() { 130 return mForegroundActivity; 131 } 132 133 public void setForegroundActivity(Activity activity) { 134 if (mErrorDialog != null) { 135 mErrorDialog.dismiss(); 136 mErrorDialog = null; 137 } 138 mForegroundActivity = activity; 139 } 140 141 public SharedPreferences getSharedPreferences() { 142 return mContext.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); 143 } 144 145 public CachedBluetoothDeviceManager getCachedDeviceManager() { 146 return mCachedDeviceManager; 147 } 148 149 List<Callback> getCallbacks() { 150 return mCallbacks; 151 } 152 153 public void registerCallback(Callback callback) { 154 synchronized (mCallbacks) { 155 mCallbacks.add(callback); 156 } 157 } 158 159 public void unregisterCallback(Callback callback) { 160 synchronized (mCallbacks) { 161 mCallbacks.remove(callback); 162 } 163 } 164 165 public void startScanning(boolean force) { 166 if (mAdapter.isDiscovering()) { 167 /* 168 * Already discovering, but give the callback that information. 169 * Note: we only call the callbacks, not the same path as if the 170 * scanning state had really changed (in that case the device 171 * manager would clear its list of unpaired scanned devices). 172 */ 173 dispatchScanningStateChanged(true); 174 } else { 175 if (!force) { 176 // Don't scan more than frequently than SCAN_EXPIRATION_MS, 177 // unless forced 178 if (mLastScan + SCAN_EXPIRATION_MS > System.currentTimeMillis()) { 179 return; 180 } 181 182 // If we are playing music, don't scan unless forced. 183 Set<BluetoothDevice> sinks = mBluetoothA2dp.getConnectedSinks(); 184 if (sinks != null) { 185 for (BluetoothDevice sink : sinks) { 186 if (mBluetoothA2dp.getSinkState(sink) == BluetoothA2dp.STATE_PLAYING) { 187 return; 188 } 189 } 190 } 191 } 192 193 if (mAdapter.startDiscovery()) { 194 mLastScan = System.currentTimeMillis(); 195 } 196 } 197 } 198 199 public void stopScanning() { 200 if (mAdapter.isDiscovering()) { 201 mAdapter.cancelDiscovery(); 202 } 203 } 204 205 public int getBluetoothState() { 206 207 if (mState == BluetoothAdapter.ERROR) { 208 syncBluetoothState(); 209 } 210 211 return mState; 212 } 213 214 void setBluetoothStateInt(int state) { 215 mState = state; 216 if (state == BluetoothAdapter.STATE_ON || 217 state == BluetoothAdapter.STATE_OFF) { 218 mCachedDeviceManager.onBluetoothStateChanged(state == 219 BluetoothAdapter.STATE_ON); 220 } 221 } 222 223 private void syncBluetoothState() { 224 int bluetoothState; 225 226 if (mAdapter != null) { 227 bluetoothState = mAdapter.isEnabled() 228 ? BluetoothAdapter.STATE_ON 229 : BluetoothAdapter.STATE_OFF; 230 } else { 231 bluetoothState = BluetoothAdapter.ERROR; 232 } 233 234 setBluetoothStateInt(bluetoothState); 235 } 236 237 public void setBluetoothEnabled(boolean enabled) { 238 boolean wasSetStateSuccessful = enabled 239 ? mAdapter.enable() 240 : mAdapter.disable(); 241 242 if (wasSetStateSuccessful) { 243 setBluetoothStateInt(enabled 244 ? BluetoothAdapter.STATE_TURNING_ON 245 : BluetoothAdapter.STATE_TURNING_OFF); 246 } else { 247 if (V) { 248 Log.v(TAG, 249 "setBluetoothEnabled call, manager didn't return success for enabled: " 250 + enabled); 251 } 252 253 syncBluetoothState(); 254 } 255 } 256 257 /** 258 * @param started True if scanning started, false if scanning finished. 259 */ 260 void onScanningStateChanged(boolean started) { 261 // TODO: have it be a callback (once we switch bluetooth state changed to callback) 262 mCachedDeviceManager.onScanningStateChanged(started); 263 dispatchScanningStateChanged(started); 264 } 265 266 private void dispatchScanningStateChanged(boolean started) { 267 synchronized (mCallbacks) { 268 for (Callback callback : mCallbacks) { 269 callback.onScanningStateChanged(started); 270 } 271 } 272 } 273 274 public void showError(BluetoothDevice device, int titleResId, int messageResId) { 275 CachedBluetoothDevice cachedDevice = mCachedDeviceManager.findDevice(device); 276 String name = null; 277 if (cachedDevice == null) { 278 if (device != null) name = device.getName(); 279 280 if (name == null) { 281 name = mContext.getString(R.string.bluetooth_remote_device); 282 } 283 } else { 284 name = cachedDevice.getName(); 285 } 286 String message = mContext.getString(messageResId, name); 287 288 if (mForegroundActivity != null) { 289 // Need an activity context to show a dialog 290 mErrorDialog = new AlertDialog.Builder(mForegroundActivity) 291 .setIcon(android.R.drawable.ic_dialog_alert) 292 .setTitle(titleResId) 293 .setMessage(message) 294 .setPositiveButton(android.R.string.ok, null) 295 .show(); 296 } else { 297 // Fallback on a toast 298 Toast.makeText(mContext, message, Toast.LENGTH_LONG).show(); 299 } 300 } 301 302 public interface Callback { 303 void onScanningStateChanged(boolean started); 304 void onDeviceAdded(CachedBluetoothDevice cachedDevice); 305 void onDeviceDeleted(CachedBluetoothDevice cachedDevice); 306 } 307 308 public boolean shouldShowDialogInForeground(String deviceAddress) { 309 // If Bluetooth Settings is visible 310 if (mForegroundActivity != null) return true; 311 312 long currentTimeMillis = System.currentTimeMillis(); 313 SharedPreferences sharedPreferences = getSharedPreferences(); 314 315 // If the device was in discoverable mode recently 316 long lastDiscoverableEndTime = sharedPreferences.getLong( 317 BluetoothDiscoverableEnabler.SHARED_PREFERENCES_KEY_DISCOVERABLE_END_TIMESTAMP, 0); 318 if ((lastDiscoverableEndTime + GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND) 319 > currentTimeMillis) { 320 return true; 321 } 322 323 // If the device was picked in the device picker recently 324 if (deviceAddress != null) { 325 String lastSelectedDevice = sharedPreferences.getString( 326 SHARED_PREFERENCES_KEY_LAST_SELECTED_DEVICE, null); 327 328 if (deviceAddress.equals(lastSelectedDevice)) { 329 long lastDeviceSelectedTime = sharedPreferences.getLong( 330 SHARED_PREFERENCES_KEY_LAST_SELECTED_DEVICE_TIME, 0); 331 if ((lastDeviceSelectedTime + GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND) 332 > currentTimeMillis) { 333 return true; 334 } 335 } 336 } 337 return false; 338 } 339 340 void persistSelectedDeviceInPicker(String deviceAddress) { 341 SharedPreferences.Editor editor = getSharedPreferences().edit(); 342 editor.putString(LocalBluetoothManager.SHARED_PREFERENCES_KEY_LAST_SELECTED_DEVICE, 343 deviceAddress); 344 editor.putLong(LocalBluetoothManager.SHARED_PREFERENCES_KEY_LAST_SELECTED_DEVICE_TIME, 345 System.currentTimeMillis()); 346 editor.commit(); 347 } 348} 349