LocalBluetoothManager.java revision f25063aee3d8eeaf3767b3a4bf43e1895f2deb2d
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 java.util.ArrayList;
22import java.util.List;
23
24import android.app.Activity;
25import android.app.AlertDialog;
26import android.bluetooth.BluetoothA2dp;
27import android.bluetooth.BluetoothDevice;
28import android.bluetooth.BluetoothError;
29import android.bluetooth.BluetoothIntent;
30import android.content.Context;
31import android.content.Intent;
32import android.content.SharedPreferences;
33import android.util.Config;
34import android.util.Log;
35import 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 */
42public 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
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
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
108    public BluetoothDevice getBluetoothManager() {
109        return mManager;
110    }
111
112    public Context getContext() {
113        return mContext;
114    }
115
116    public Activity getForegroundActivity() {
117        return mForegroundActivity;
118    }
119
120    public void setForegroundActivity(Activity activity) {
121        if (mErrorDialog != null) {
122            mErrorDialog.dismiss();
123            mErrorDialog = null;
124        }
125        mForegroundActivity = activity;
126    }
127
128    public SharedPreferences getSharedPreferences() {
129        return mContext.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
130    }
131
132    public LocalBluetoothDeviceManager getLocalDeviceManager() {
133        return mLocalDeviceManager;
134    }
135
136    List<Callback> getCallbacks() {
137        return mCallbacks;
138    }
139
140    public void registerCallback(Callback callback) {
141        synchronized (mCallbacks) {
142            mCallbacks.add(callback);
143        }
144    }
145
146    public void unregisterCallback(Callback callback) {
147        synchronized (mCallbacks) {
148            mCallbacks.remove(callback);
149        }
150    }
151
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
186    public int getBluetoothState() {
187
188        if (mState == BluetoothError.ERROR) {
189            syncBluetoothState();
190        }
191
192        return mState;
193    }
194
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
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
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     */
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
246    private void dispatchScanningStateChanged(boolean started) {
247        synchronized (mCallbacks) {
248            for (Callback callback : mCallbacks) {
249                callback.onScanningStateChanged(started);
250            }
251        }
252    }
253
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 {
276        void onScanningStateChanged(boolean started);
277        void onDeviceAdded(LocalBluetoothDevice device);
278        void onDeviceDeleted(LocalBluetoothDevice device);
279    }
280
281}
282