• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 java.util.ArrayList;
22 import java.util.List;
23 
24 import android.app.Activity;
25 import android.app.AlertDialog;
26 import android.bluetooth.BluetoothA2dp;
27 import android.bluetooth.BluetoothDevice;
28 import android.bluetooth.BluetoothError;
29 import android.bluetooth.BluetoothIntent;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.SharedPreferences;
33 import android.util.Config;
34 import android.util.Log;
35 import android.widget.Toast;
36 
37 // TODO: have some notion of shutting down.  Maybe a minute after they leave BT settings?
38 /**
39  * LocalBluetoothManager provides a simplified interface on top of a subset of
40  * the Bluetooth API.
41  */
42 public class LocalBluetoothManager {
43     private static final String TAG = "LocalBluetoothManager";
44     static final boolean V = Config.LOGV;
45     static final boolean D = Config.LOGD && false;
46 
47     private static final String SHARED_PREFERENCES_NAME = "bluetooth_settings";
48 
49     private static LocalBluetoothManager INSTANCE;
50     /** Used when obtaining a reference to the singleton instance. */
51     private static Object INSTANCE_LOCK = new Object();
52     private boolean mInitialized;
53 
54     private Context mContext;
55     /** If a BT-related activity is in the foreground, this will be it. */
56     private Activity mForegroundActivity;
57     private AlertDialog mErrorDialog = null;
58 
59     private BluetoothDevice mManager;
60 
61     private LocalBluetoothDeviceManager mLocalDeviceManager;
62     private BluetoothEventRedirector mEventRedirector;
63     private BluetoothA2dp mBluetoothA2dp;
64 
65     private int mState = BluetoothError.ERROR;
66 
67     private List<Callback> mCallbacks = new ArrayList<Callback>();
68 
69     private static final int SCAN_EXPIRATION_MS = 5 * 60 * 1000; // 5 mins
70     private long mLastScan;
71 
getInstance(Context context)72     public static LocalBluetoothManager getInstance(Context context) {
73         synchronized (INSTANCE_LOCK) {
74             if (INSTANCE == null) {
75                 INSTANCE = new LocalBluetoothManager();
76             }
77 
78             if (!INSTANCE.init(context)) {
79                 return null;
80             }
81 
82             return INSTANCE;
83         }
84     }
85 
init(Context context)86     private boolean init(Context context) {
87         if (mInitialized) return true;
88         mInitialized = true;
89 
90         // This will be around as long as this process is
91         mContext = context.getApplicationContext();
92 
93         mManager = (BluetoothDevice) context.getSystemService(Context.BLUETOOTH_SERVICE);
94         if (mManager == null) {
95             return false;
96         }
97 
98         mLocalDeviceManager = new LocalBluetoothDeviceManager(this);
99 
100         mEventRedirector = new BluetoothEventRedirector(this);
101         mEventRedirector.start();
102 
103         mBluetoothA2dp = new BluetoothA2dp(context);
104 
105         return true;
106     }
107 
getBluetoothManager()108     public BluetoothDevice getBluetoothManager() {
109         return mManager;
110     }
111 
getContext()112     public Context getContext() {
113         return mContext;
114     }
115 
getForegroundActivity()116     public Activity getForegroundActivity() {
117         return mForegroundActivity;
118     }
119 
setForegroundActivity(Activity activity)120     public void setForegroundActivity(Activity activity) {
121         if (mErrorDialog != null) {
122             mErrorDialog.dismiss();
123             mErrorDialog = null;
124         }
125         mForegroundActivity = activity;
126     }
127 
getSharedPreferences()128     public SharedPreferences getSharedPreferences() {
129         return mContext.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
130     }
131 
getLocalDeviceManager()132     public LocalBluetoothDeviceManager getLocalDeviceManager() {
133         return mLocalDeviceManager;
134     }
135 
getCallbacks()136     List<Callback> getCallbacks() {
137         return mCallbacks;
138     }
139 
registerCallback(Callback callback)140     public void registerCallback(Callback callback) {
141         synchronized (mCallbacks) {
142             mCallbacks.add(callback);
143         }
144     }
145 
unregisterCallback(Callback callback)146     public void unregisterCallback(Callback callback) {
147         synchronized (mCallbacks) {
148             mCallbacks.remove(callback);
149         }
150     }
151 
startScanning(boolean force)152     public void startScanning(boolean force) {
153         if (mManager.isDiscovering()) {
154             /*
155              * Already discovering, but give the callback that information.
156              * Note: we only call the callbacks, not the same path as if the
157              * scanning state had really changed (in that case the device
158              * manager would clear its list of unpaired scanned devices).
159              */
160             dispatchScanningStateChanged(true);
161         } else {
162             if (!force) {
163                 // Don't scan more than frequently than SCAN_EXPIRATION_MS,
164                 // unless forced
165                 if (mLastScan + SCAN_EXPIRATION_MS > System.currentTimeMillis()) {
166                     return;
167                 }
168 
169                 // If we are playing music, don't scan unless forced.
170                 List<String> sinks = mBluetoothA2dp.listConnectedSinks();
171                 if (sinks != null) {
172                     for (String address : sinks) {
173                         if (mBluetoothA2dp.getSinkState(address) == BluetoothA2dp.STATE_PLAYING) {
174                             return;
175                         }
176                     }
177                 }
178             }
179 
180             if (mManager.startDiscovery(true)) {
181                 mLastScan = System.currentTimeMillis();
182             }
183         }
184     }
185 
getBluetoothState()186     public int getBluetoothState() {
187 
188         if (mState == BluetoothError.ERROR) {
189             syncBluetoothState();
190         }
191 
192         return mState;
193     }
194 
setBluetoothStateInt(int state)195     void setBluetoothStateInt(int state) {
196         mState = state;
197         if (state == BluetoothDevice.BLUETOOTH_STATE_ON ||
198             state == BluetoothDevice.BLUETOOTH_STATE_OFF) {
199             mLocalDeviceManager.onBluetoothStateChanged(state == BluetoothDevice.BLUETOOTH_STATE_ON);
200         }
201     }
202 
syncBluetoothState()203     private void syncBluetoothState() {
204         int bluetoothState;
205 
206         if (mManager != null) {
207             bluetoothState = mManager.isEnabled()
208                     ? BluetoothDevice.BLUETOOTH_STATE_ON
209                     : BluetoothDevice.BLUETOOTH_STATE_OFF;
210         } else {
211             bluetoothState = BluetoothError.ERROR;
212         }
213 
214         setBluetoothStateInt(bluetoothState);
215     }
216 
setBluetoothEnabled(boolean enabled)217     public void setBluetoothEnabled(boolean enabled) {
218         boolean wasSetStateSuccessful = enabled
219                 ? mManager.enable()
220                 : mManager.disable();
221 
222         if (wasSetStateSuccessful) {
223             setBluetoothStateInt(enabled
224                 ? BluetoothDevice.BLUETOOTH_STATE_TURNING_ON
225                 : BluetoothDevice.BLUETOOTH_STATE_TURNING_OFF);
226         } else {
227             if (V) {
228                 Log.v(TAG,
229                         "setBluetoothEnabled call, manager didn't return success for enabled: "
230                                 + enabled);
231             }
232 
233             syncBluetoothState();
234         }
235     }
236 
237     /**
238      * @param started True if scanning started, false if scanning finished.
239      */
onScanningStateChanged(boolean started)240     void onScanningStateChanged(boolean started) {
241         // TODO: have it be a callback (once we switch bluetooth state changed to callback)
242         mLocalDeviceManager.onScanningStateChanged(started);
243         dispatchScanningStateChanged(started);
244     }
245 
dispatchScanningStateChanged(boolean started)246     private void dispatchScanningStateChanged(boolean started) {
247         synchronized (mCallbacks) {
248             for (Callback callback : mCallbacks) {
249                 callback.onScanningStateChanged(started);
250             }
251         }
252     }
253 
showError(String address, int titleResId, int messageResId)254     public void showError(String address, int titleResId, int messageResId) {
255         LocalBluetoothDevice device = mLocalDeviceManager.findDevice(address);
256         if (device == null) return;
257 
258         String name = device.getName();
259         String message = mContext.getString(messageResId, name);
260 
261         if (mForegroundActivity != null) {
262             // Need an activity context to show a dialog
263             mErrorDialog = new AlertDialog.Builder(mForegroundActivity)
264                 .setIcon(android.R.drawable.ic_dialog_alert)
265                 .setTitle(titleResId)
266                 .setMessage(message)
267                 .setPositiveButton(android.R.string.ok, null)
268                 .show();
269         } else {
270             // Fallback on a toast
271             Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show();
272         }
273     }
274 
275     public interface Callback {
onScanningStateChanged(boolean started)276         void onScanningStateChanged(boolean started);
onDeviceAdded(LocalBluetoothDevice device)277         void onDeviceAdded(LocalBluetoothDevice device);
onDeviceDeleted(LocalBluetoothDevice device)278         void onDeviceDeleted(LocalBluetoothDevice device);
279     }
280 
281 }
282