OSDN Git Service

am 79270566: (-s ours) am d9d8ef26: Merge "b/2335780 Fixed race conditions which...
[android-x86/packages-apps-Settings.git] / src / com / android / settings / bluetooth / LocalBluetoothManager.java
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
17 package com.android.settings.bluetooth;
18
19 import com.android.settings.R;
20
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;
31
32 import java.util.ArrayList;
33 import java.util.List;
34 import 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  */
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;
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 static final String SHARED_PREFERENCES_KEY_DOCK_AUTO_CONNECT = "auto_connect_to_dock";
82
83     private long mLastScan;
84
85     public static LocalBluetoothManager getInstance(Context context) {
86         synchronized (INSTANCE_LOCK) {
87             if (INSTANCE == null) {
88                 INSTANCE = new LocalBluetoothManager();
89             }
90
91             if (!INSTANCE.init(context)) {
92                 return null;
93             }
94
95             LocalBluetoothProfileManager.init(INSTANCE);
96
97             return INSTANCE;
98         }
99     }
100
101     private boolean init(Context context) {
102         if (mInitialized) return true;
103         mInitialized = true;
104
105         // This will be around as long as this process is
106         mContext = context.getApplicationContext();
107
108         mAdapter = BluetoothAdapter.getDefaultAdapter();
109         if (mAdapter == null) {
110             return false;
111         }
112
113         mCachedDeviceManager = new CachedBluetoothDeviceManager(this);
114
115         mEventRedirector = new BluetoothEventRedirector(this);
116         mEventRedirector.start();
117
118         mBluetoothA2dp = new BluetoothA2dp(context);
119
120         return true;
121     }
122
123     public BluetoothAdapter getBluetoothAdapter() {
124         return mAdapter;
125     }
126
127     public Context getContext() {
128         return mContext;
129     }
130
131     public Activity getForegroundActivity() {
132         return mForegroundActivity;
133     }
134
135     public void setForegroundActivity(Activity activity) {
136         if (mErrorDialog != null) {
137             mErrorDialog.dismiss();
138             mErrorDialog = null;
139         }
140         mForegroundActivity = activity;
141     }
142
143     public SharedPreferences getSharedPreferences() {
144         return mContext.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
145     }
146
147     public CachedBluetoothDeviceManager getCachedDeviceManager() {
148         return mCachedDeviceManager;
149     }
150
151     List<Callback> getCallbacks() {
152         return mCallbacks;
153     }
154
155     public void registerCallback(Callback callback) {
156         synchronized (mCallbacks) {
157             mCallbacks.add(callback);
158         }
159     }
160
161     public void unregisterCallback(Callback callback) {
162         synchronized (mCallbacks) {
163             mCallbacks.remove(callback);
164         }
165     }
166
167     public void startScanning(boolean force) {
168         if (mAdapter.isDiscovering()) {
169             /*
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).
174              */
175             dispatchScanningStateChanged(true);
176         } else {
177             if (!force) {
178                 // Don't scan more than frequently than SCAN_EXPIRATION_MS,
179                 // unless forced
180                 if (mLastScan + SCAN_EXPIRATION_MS > System.currentTimeMillis()) {
181                     return;
182                 }
183
184                 // If we are playing music, don't scan unless forced.
185                 Set<BluetoothDevice> sinks = mBluetoothA2dp.getConnectedSinks();
186                 if (sinks != null) {
187                     for (BluetoothDevice sink : sinks) {
188                         if (mBluetoothA2dp.getSinkState(sink) == BluetoothA2dp.STATE_PLAYING) {
189                             return;
190                         }
191                     }
192                 }
193             }
194
195             if (mAdapter.startDiscovery()) {
196                 mLastScan = System.currentTimeMillis();
197             }
198         }
199     }
200
201     public void stopScanning() {
202         if (mAdapter.isDiscovering()) {
203             mAdapter.cancelDiscovery();
204         }
205     }
206
207     public int getBluetoothState() {
208
209         if (mState == BluetoothAdapter.ERROR) {
210             syncBluetoothState();
211         }
212
213         return mState;
214     }
215
216     void setBluetoothStateInt(int state) {
217         mState = state;
218         if (state == BluetoothAdapter.STATE_ON ||
219             state == BluetoothAdapter.STATE_OFF) {
220             mCachedDeviceManager.onBluetoothStateChanged(state ==
221                     BluetoothAdapter.STATE_ON);
222         }
223     }
224
225     private void syncBluetoothState() {
226         int bluetoothState;
227
228         if (mAdapter != null) {
229             bluetoothState = mAdapter.isEnabled()
230                     ? BluetoothAdapter.STATE_ON
231                     : BluetoothAdapter.STATE_OFF;
232         } else {
233             bluetoothState = BluetoothAdapter.ERROR;
234         }
235
236         setBluetoothStateInt(bluetoothState);
237     }
238
239     public void setBluetoothEnabled(boolean enabled) {
240         boolean wasSetStateSuccessful = enabled
241                 ? mAdapter.enable()
242                 : mAdapter.disable();
243
244         if (wasSetStateSuccessful) {
245             setBluetoothStateInt(enabled
246                 ? BluetoothAdapter.STATE_TURNING_ON
247                 : BluetoothAdapter.STATE_TURNING_OFF);
248         } else {
249             if (V) {
250                 Log.v(TAG,
251                         "setBluetoothEnabled call, manager didn't return success for enabled: "
252                                 + enabled);
253             }
254
255             syncBluetoothState();
256         }
257     }
258
259     /**
260      * @param started True if scanning started, false if scanning finished.
261      */
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);
266     }
267
268     private void dispatchScanningStateChanged(boolean started) {
269         synchronized (mCallbacks) {
270             for (Callback callback : mCallbacks) {
271                 callback.onScanningStateChanged(started);
272             }
273         }
274     }
275
276     public void showError(BluetoothDevice device, int titleResId, int messageResId) {
277         CachedBluetoothDevice cachedDevice = mCachedDeviceManager.findDevice(device);
278         String name = null;
279         if (cachedDevice == null) {
280             if (device != null) name = device.getName();
281
282             if (name == null) {
283                 name = mContext.getString(R.string.bluetooth_remote_device);
284             }
285         } else {
286             name = cachedDevice.getName();
287         }
288         String message = mContext.getString(messageResId, name);
289
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)
295                 .setMessage(message)
296                 .setPositiveButton(android.R.string.ok, null)
297                 .show();
298         } else {
299             // Fallback on a toast
300             Toast.makeText(mContext, message, Toast.LENGTH_LONG).show();
301         }
302     }
303
304     public interface Callback {
305         void onScanningStateChanged(boolean started);
306         void onDeviceAdded(CachedBluetoothDevice cachedDevice);
307         void onDeviceDeleted(CachedBluetoothDevice cachedDevice);
308     }
309
310     public boolean shouldShowDialogInForeground(String deviceAddress) {
311         // If Bluetooth Settings is visible
312         if (mForegroundActivity != null) return true;
313
314         long currentTimeMillis = System.currentTimeMillis();
315         SharedPreferences sharedPreferences = getSharedPreferences();
316
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) {
322             return true;
323         }
324
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);
329
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) {
335                     return true;
336                 }
337             }
338         }
339         return false;
340     }
341
342     void persistSelectedDeviceInPicker(String deviceAddress) {
343         SharedPreferences.Editor editor = getSharedPreferences().edit();
344         editor.putString(LocalBluetoothManager.SHARED_PREFERENCES_KEY_LAST_SELECTED_DEVICE,
345                 deviceAddress);
346         editor.putLong(LocalBluetoothManager.SHARED_PREFERENCES_KEY_LAST_SELECTED_DEVICE_TIME,
347                 System.currentTimeMillis());
348         editor.commit();
349     }
350
351     public boolean hasDockAutoConnectSetting(String addr) {
352         return getSharedPreferences().contains(SHARED_PREFERENCES_KEY_DOCK_AUTO_CONNECT + addr);
353     }
354
355     public boolean getDockAutoConnectSetting(String addr) {
356         return getSharedPreferences().getBoolean(SHARED_PREFERENCES_KEY_DOCK_AUTO_CONNECT + addr,
357                 false);
358     }
359
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);
363         editor.commit();
364     }
365
366     public void removeDockAutoConnectSetting(String addr) {
367         SharedPreferences.Editor editor = getSharedPreferences().edit();
368         editor.remove(SHARED_PREFERENCES_KEY_DOCK_AUTO_CONNECT + addr);
369         editor.commit();
370     }
371 }