2 * Copyright (C) 2008 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.settings.bluetooth;
19 import com.android.settings.R;
21 import android.app.Activity;
22 import android.app.AlertDialog;
23 import android.bluetooth.BluetoothA2dp;
24 import android.bluetooth.BluetoothAdapter;
25 import android.bluetooth.BluetoothDevice;
26 import android.content.Context;
27 import android.content.SharedPreferences;
28 import android.util.Config;
29 import android.util.Log;
30 import android.widget.Toast;
32 import java.util.ArrayList;
33 import java.util.List;
36 // TODO: have some notion of shutting down. Maybe a minute after they leave BT settings?
38 * LocalBluetoothManager provides a simplified interface on top of a subset of
41 public class LocalBluetoothManager {
42 private static final String TAG = "LocalBluetoothManager";
43 static final boolean V = Config.LOGV;
44 static final boolean D = Config.LOGD;
46 private static final String SHARED_PREFERENCES_NAME = "bluetooth_settings";
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;
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;
58 private BluetoothAdapter mAdapter;
60 private CachedBluetoothDeviceManager mCachedDeviceManager;
61 private BluetoothEventRedirector mEventRedirector;
62 private BluetoothA2dp mBluetoothA2dp;
64 private int mState = BluetoothAdapter.ERROR;
66 private List<Callback> mCallbacks = new ArrayList<Callback>();
68 private static final int SCAN_EXPIRATION_MS = 5 * 60 * 1000; // 5 mins
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;
75 private static final String SHARED_PREFERENCES_KEY_LAST_SELECTED_DEVICE =
76 "last_selected_device";
78 private static final String SHARED_PREFERENCES_KEY_LAST_SELECTED_DEVICE_TIME =
79 "last_selected_device_time";
81 private static final String SHARED_PREFERENCES_KEY_DOCK_AUTO_CONNECT = "auto_connect_to_dock";
83 private long mLastScan;
85 public static LocalBluetoothManager getInstance(Context context) {
86 synchronized (INSTANCE_LOCK) {
87 if (INSTANCE == null) {
88 INSTANCE = new LocalBluetoothManager();
91 if (!INSTANCE.init(context)) {
95 LocalBluetoothProfileManager.init(INSTANCE);
101 private boolean init(Context context) {
102 if (mInitialized) return true;
105 // This will be around as long as this process is
106 mContext = context.getApplicationContext();
108 mAdapter = BluetoothAdapter.getDefaultAdapter();
109 if (mAdapter == null) {
113 mCachedDeviceManager = new CachedBluetoothDeviceManager(this);
115 mEventRedirector = new BluetoothEventRedirector(this);
116 mEventRedirector.start();
118 mBluetoothA2dp = new BluetoothA2dp(context);
123 public BluetoothAdapter getBluetoothAdapter() {
127 public Context getContext() {
131 public Activity getForegroundActivity() {
132 return mForegroundActivity;
135 public void setForegroundActivity(Activity activity) {
136 if (mErrorDialog != null) {
137 mErrorDialog.dismiss();
140 mForegroundActivity = activity;
143 public SharedPreferences getSharedPreferences() {
144 return mContext.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
147 public CachedBluetoothDeviceManager getCachedDeviceManager() {
148 return mCachedDeviceManager;
151 List<Callback> getCallbacks() {
155 public void registerCallback(Callback callback) {
156 synchronized (mCallbacks) {
157 mCallbacks.add(callback);
161 public void unregisterCallback(Callback callback) {
162 synchronized (mCallbacks) {
163 mCallbacks.remove(callback);
167 public void startScanning(boolean force) {
168 if (mAdapter.isDiscovering()) {
170 * Already discovering, but give the callback that information.
171 * Note: we only call the callbacks, not the same path as if the
172 * scanning state had really changed (in that case the device
173 * manager would clear its list of unpaired scanned devices).
175 dispatchScanningStateChanged(true);
178 // Don't scan more than frequently than SCAN_EXPIRATION_MS,
180 if (mLastScan + SCAN_EXPIRATION_MS > System.currentTimeMillis()) {
184 // If we are playing music, don't scan unless forced.
185 Set<BluetoothDevice> sinks = mBluetoothA2dp.getConnectedSinks();
187 for (BluetoothDevice sink : sinks) {
188 if (mBluetoothA2dp.getSinkState(sink) == BluetoothA2dp.STATE_PLAYING) {
195 if (mAdapter.startDiscovery()) {
196 mLastScan = System.currentTimeMillis();
201 public void stopScanning() {
202 if (mAdapter.isDiscovering()) {
203 mAdapter.cancelDiscovery();
207 public int getBluetoothState() {
209 if (mState == BluetoothAdapter.ERROR) {
210 syncBluetoothState();
216 void setBluetoothStateInt(int state) {
218 if (state == BluetoothAdapter.STATE_ON ||
219 state == BluetoothAdapter.STATE_OFF) {
220 mCachedDeviceManager.onBluetoothStateChanged(state ==
221 BluetoothAdapter.STATE_ON);
225 private void syncBluetoothState() {
228 if (mAdapter != null) {
229 bluetoothState = mAdapter.isEnabled()
230 ? BluetoothAdapter.STATE_ON
231 : BluetoothAdapter.STATE_OFF;
233 bluetoothState = BluetoothAdapter.ERROR;
236 setBluetoothStateInt(bluetoothState);
239 public void setBluetoothEnabled(boolean enabled) {
240 boolean wasSetStateSuccessful = enabled
242 : mAdapter.disable();
244 if (wasSetStateSuccessful) {
245 setBluetoothStateInt(enabled
246 ? BluetoothAdapter.STATE_TURNING_ON
247 : BluetoothAdapter.STATE_TURNING_OFF);
251 "setBluetoothEnabled call, manager didn't return success for enabled: "
255 syncBluetoothState();
260 * @param started True if scanning started, false if scanning finished.
262 void onScanningStateChanged(boolean started) {
263 // TODO: have it be a callback (once we switch bluetooth state changed to callback)
264 mCachedDeviceManager.onScanningStateChanged(started);
265 dispatchScanningStateChanged(started);
268 private void dispatchScanningStateChanged(boolean started) {
269 synchronized (mCallbacks) {
270 for (Callback callback : mCallbacks) {
271 callback.onScanningStateChanged(started);
276 public void showError(BluetoothDevice device, int titleResId, int messageResId) {
277 CachedBluetoothDevice cachedDevice = mCachedDeviceManager.findDevice(device);
279 if (cachedDevice == null) {
280 if (device != null) name = device.getName();
283 name = mContext.getString(R.string.bluetooth_remote_device);
286 name = cachedDevice.getName();
288 String message = mContext.getString(messageResId, name);
290 if (mForegroundActivity != null) {
291 // Need an activity context to show a dialog
292 mErrorDialog = new AlertDialog.Builder(mForegroundActivity)
293 .setIcon(android.R.drawable.ic_dialog_alert)
294 .setTitle(titleResId)
296 .setPositiveButton(android.R.string.ok, null)
299 // Fallback on a toast
300 Toast.makeText(mContext, message, Toast.LENGTH_LONG).show();
304 public interface Callback {
305 void onScanningStateChanged(boolean started);
306 void onDeviceAdded(CachedBluetoothDevice cachedDevice);
307 void onDeviceDeleted(CachedBluetoothDevice cachedDevice);
310 public boolean shouldShowDialogInForeground(String deviceAddress) {
311 // If Bluetooth Settings is visible
312 if (mForegroundActivity != null) return true;
314 long currentTimeMillis = System.currentTimeMillis();
315 SharedPreferences sharedPreferences = getSharedPreferences();
317 // If the device was in discoverable mode recently
318 long lastDiscoverableEndTime = sharedPreferences.getLong(
319 BluetoothDiscoverableEnabler.SHARED_PREFERENCES_KEY_DISCOVERABLE_END_TIMESTAMP, 0);
320 if ((lastDiscoverableEndTime + GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND)
321 > currentTimeMillis) {
325 // If the device was picked in the device picker recently
326 if (deviceAddress != null) {
327 String lastSelectedDevice = sharedPreferences.getString(
328 SHARED_PREFERENCES_KEY_LAST_SELECTED_DEVICE, null);
330 if (deviceAddress.equals(lastSelectedDevice)) {
331 long lastDeviceSelectedTime = sharedPreferences.getLong(
332 SHARED_PREFERENCES_KEY_LAST_SELECTED_DEVICE_TIME, 0);
333 if ((lastDeviceSelectedTime + GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND)
334 > currentTimeMillis) {
342 void persistSelectedDeviceInPicker(String deviceAddress) {
343 SharedPreferences.Editor editor = getSharedPreferences().edit();
344 editor.putString(LocalBluetoothManager.SHARED_PREFERENCES_KEY_LAST_SELECTED_DEVICE,
346 editor.putLong(LocalBluetoothManager.SHARED_PREFERENCES_KEY_LAST_SELECTED_DEVICE_TIME,
347 System.currentTimeMillis());
351 public boolean hasDockAutoConnectSetting(String addr) {
352 return getSharedPreferences().contains(SHARED_PREFERENCES_KEY_DOCK_AUTO_CONNECT + addr);
355 public boolean getDockAutoConnectSetting(String addr) {
356 return getSharedPreferences().getBoolean(SHARED_PREFERENCES_KEY_DOCK_AUTO_CONNECT + addr,
360 public void saveDockAutoConnectSetting(String addr, boolean autoConnect) {
361 SharedPreferences.Editor editor = getSharedPreferences().edit();
362 editor.putBoolean(SHARED_PREFERENCES_KEY_DOCK_AUTO_CONNECT + addr, autoConnect);
366 public void removeDockAutoConnectSetting(String addr) {
367 SharedPreferences.Editor editor = getSharedPreferences().edit();
368 editor.remove(SHARED_PREFERENCES_KEY_DOCK_AUTO_CONNECT + addr);