LocalBluetoothManager.java revision 3eb43fe7a4a508e8cd476525fd68ec8d900f06b8
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;
23import java.util.Set;
24
25import android.app.Activity;
26import android.app.AlertDialog;
27import android.bluetooth.BluetoothA2dp;
28import android.bluetooth.BluetoothAdapter;
29import android.bluetooth.BluetoothDevice;
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;
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 BluetoothAdapter mAdapter;
60
61    private CachedBluetoothDeviceManager mCachedDeviceManager;
62    private BluetoothEventRedirector mEventRedirector;
63    private BluetoothA2dp mBluetoothA2dp;
64
65    private int mState = BluetoothAdapter.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        mAdapter = (BluetoothAdapter) context.getSystemService(Context.BLUETOOTH_SERVICE);
94        if (mAdapter == null) {
95            return false;
96        }
97
98        mCachedDeviceManager = new CachedBluetoothDeviceManager(this);
99
100        mEventRedirector = new BluetoothEventRedirector(this);
101        mEventRedirector.start();
102
103        mBluetoothA2dp = new BluetoothA2dp(context);
104
105        return true;
106    }
107
108    public BluetoothAdapter getBluetoothAdapter() {
109        return mAdapter;
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 CachedBluetoothDeviceManager getCachedDeviceManager() {
133        return mCachedDeviceManager;
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 (mAdapter.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                Set<BluetoothDevice> sinks = mBluetoothA2dp.getConnectedSinks();
171                if (sinks != null) {
172                    for (BluetoothDevice sink : sinks) {
173                        if (mBluetoothA2dp.getSinkState(sink) == BluetoothA2dp.STATE_PLAYING) {
174                            return;
175                        }
176                    }
177                }
178            }
179
180            if (mAdapter.startDiscovery()) {
181                mLastScan = System.currentTimeMillis();
182            }
183        }
184    }
185
186    public int getBluetoothState() {
187
188        if (mState == BluetoothAdapter.ERROR) {
189            syncBluetoothState();
190        }
191
192        return mState;
193    }
194
195    void setBluetoothStateInt(int state) {
196        mState = state;
197        if (state == BluetoothAdapter.STATE_ON ||
198            state == BluetoothAdapter.STATE_OFF) {
199            mCachedDeviceManager.onBluetoothStateChanged(state ==
200                    BluetoothAdapter.STATE_ON);
201        }
202    }
203
204    private void syncBluetoothState() {
205        int bluetoothState;
206
207        if (mAdapter != null) {
208            bluetoothState = mAdapter.isEnabled()
209                    ? BluetoothAdapter.STATE_ON
210                    : BluetoothAdapter.STATE_OFF;
211        } else {
212            bluetoothState = BluetoothAdapter.ERROR;
213        }
214
215        setBluetoothStateInt(bluetoothState);
216    }
217
218    public void setBluetoothEnabled(boolean enabled) {
219        boolean wasSetStateSuccessful = enabled
220                ? mAdapter.enable()
221                : mAdapter.disable();
222
223        if (wasSetStateSuccessful) {
224            setBluetoothStateInt(enabled
225                ? BluetoothAdapter.STATE_TURNING_ON
226                : BluetoothAdapter.STATE_TURNING_OFF);
227        } else {
228            if (V) {
229                Log.v(TAG,
230                        "setBluetoothEnabled call, manager didn't return success for enabled: "
231                                + enabled);
232            }
233
234            syncBluetoothState();
235        }
236    }
237
238    /**
239     * @param started True if scanning started, false if scanning finished.
240     */
241    void onScanningStateChanged(boolean started) {
242        // TODO: have it be a callback (once we switch bluetooth state changed to callback)
243        mCachedDeviceManager.onScanningStateChanged(started);
244        dispatchScanningStateChanged(started);
245    }
246
247    private void dispatchScanningStateChanged(boolean started) {
248        synchronized (mCallbacks) {
249            for (Callback callback : mCallbacks) {
250                callback.onScanningStateChanged(started);
251            }
252        }
253    }
254
255    public void showError(BluetoothDevice device, int titleResId, int messageResId) {
256        CachedBluetoothDevice cachedDevice = mCachedDeviceManager.findDevice(device);
257        String name = null;
258        if (cachedDevice == null) {
259            name = device.getName();
260            if (name == null) {
261                name = mContext.getString(R.string.bluetooth_remote_device);
262            }
263        } else {
264            name = cachedDevice.getName();
265        }
266        String message = mContext.getString(messageResId, name);
267
268        if (mForegroundActivity != null) {
269            // Need an activity context to show a dialog
270            mErrorDialog = new AlertDialog.Builder(mForegroundActivity)
271                .setIcon(android.R.drawable.ic_dialog_alert)
272                .setTitle(titleResId)
273                .setMessage(message)
274                .setPositiveButton(android.R.string.ok, null)
275                .show();
276        } else {
277            // Fallback on a toast
278            Toast.makeText(mContext, message, Toast.LENGTH_LONG).show();
279        }
280    }
281
282    public interface Callback {
283        void onScanningStateChanged(boolean started);
284        void onDeviceAdded(CachedBluetoothDevice cachedDevice);
285        void onDeviceDeleted(CachedBluetoothDevice cachedDevice);
286    }
287
288}
289