blob: ec5c6bcb07eb6bc3ead22397b51cc0eeb435c2a1 [file] [log] [blame]
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.bluetooth;
import com.android.settings.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Config;
import android.util.Log;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
// TODO: have some notion of shutting down. Maybe a minute after they leave BT settings?
/**
* LocalBluetoothManager provides a simplified interface on top of a subset of
* the Bluetooth API.
*/
public class LocalBluetoothManager {
private static final String TAG = "LocalBluetoothManager";
static final boolean V = Config.LOGV;
static final boolean D = Config.LOGD;
private static final String SHARED_PREFERENCES_NAME = "bluetooth_settings";
private static LocalBluetoothManager INSTANCE;
/** Used when obtaining a reference to the singleton instance. */
private static Object INSTANCE_LOCK = new Object();
private boolean mInitialized;
private Context mContext;
/** If a BT-related activity is in the foreground, this will be it. */
private Activity mForegroundActivity;
private AlertDialog mErrorDialog = null;
private BluetoothAdapter mAdapter;
private CachedBluetoothDeviceManager mCachedDeviceManager;
private BluetoothEventRedirector mEventRedirector;
private BluetoothA2dp mBluetoothA2dp;
private int mState = BluetoothAdapter.ERROR;
private List<Callback> mCallbacks = new ArrayList<Callback>();
private static final int SCAN_EXPIRATION_MS = 5 * 60 * 1000; // 5 mins
// If a device was picked from the device picker or was in discoverable mode
// in the last 60 seconds, show the pairing dialogs in foreground instead
// of raising notifications
private static long GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND = 60 * 1000;
private static final String SHARED_PREFERENCES_KEY_LAST_SELECTED_DEVICE =
"last_selected_device";
private static final String SHARED_PREFERENCES_KEY_LAST_SELECTED_DEVICE_TIME =
"last_selected_device_time";
private static final String SHARED_PREFERENCES_KEY_DOCK_AUTO_CONNECT = "auto_connect_to_dock";
private long mLastScan;
public static LocalBluetoothManager getInstance(Context context) {
synchronized (INSTANCE_LOCK) {
if (INSTANCE == null) {
INSTANCE = new LocalBluetoothManager();
}
if (!INSTANCE.init(context)) {
return null;
}
LocalBluetoothProfileManager.init(INSTANCE);
return INSTANCE;
}
}
private boolean init(Context context) {
if (mInitialized) return true;
mInitialized = true;
// This will be around as long as this process is
mContext = context.getApplicationContext();
mAdapter = BluetoothAdapter.getDefaultAdapter();
if (mAdapter == null) {
return false;
}
mCachedDeviceManager = new CachedBluetoothDeviceManager(this);
mEventRedirector = new BluetoothEventRedirector(this);
mEventRedirector.start();
mBluetoothA2dp = new BluetoothA2dp(context);
return true;
}
public BluetoothAdapter getBluetoothAdapter() {
return mAdapter;
}
public Context getContext() {
return mContext;
}
public Activity getForegroundActivity() {
return mForegroundActivity;
}
public void setForegroundActivity(Activity activity) {
if (mErrorDialog != null) {
mErrorDialog.dismiss();
mErrorDialog = null;
}
mForegroundActivity = activity;
}
public SharedPreferences getSharedPreferences() {
return mContext.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
}
public CachedBluetoothDeviceManager getCachedDeviceManager() {
return mCachedDeviceManager;
}
List<Callback> getCallbacks() {
return mCallbacks;
}
public void registerCallback(Callback callback) {
synchronized (mCallbacks) {
mCallbacks.add(callback);
}
}
public void unregisterCallback(Callback callback) {
synchronized (mCallbacks) {
mCallbacks.remove(callback);
}
}
public void startScanning(boolean force) {
if (mAdapter.isDiscovering()) {
/*
* Already discovering, but give the callback that information.
* Note: we only call the callbacks, not the same path as if the
* scanning state had really changed (in that case the device
* manager would clear its list of unpaired scanned devices).
*/
dispatchScanningStateChanged(true);
} else {
if (!force) {
// Don't scan more than frequently than SCAN_EXPIRATION_MS,
// unless forced
if (mLastScan + SCAN_EXPIRATION_MS > System.currentTimeMillis()) {
return;
}
// If we are playing music, don't scan unless forced.
Set<BluetoothDevice> sinks = mBluetoothA2dp.getConnectedSinks();
if (sinks != null) {
for (BluetoothDevice sink : sinks) {
if (mBluetoothA2dp.getSinkState(sink) == BluetoothA2dp.STATE_PLAYING) {
return;
}
}
}
}
if (mAdapter.startDiscovery()) {
mLastScan = System.currentTimeMillis();
}
}
}
public int getBluetoothState() {
if (mState == BluetoothAdapter.ERROR) {
syncBluetoothState();
}
return mState;
}
void setBluetoothStateInt(int state) {
mState = state;
if (state == BluetoothAdapter.STATE_ON ||
state == BluetoothAdapter.STATE_OFF) {
mCachedDeviceManager.onBluetoothStateChanged(state ==
BluetoothAdapter.STATE_ON);
}
}
private void syncBluetoothState() {
int bluetoothState;
if (mAdapter != null) {
bluetoothState = mAdapter.isEnabled()
? BluetoothAdapter.STATE_ON
: BluetoothAdapter.STATE_OFF;
} else {
bluetoothState = BluetoothAdapter.ERROR;
}
setBluetoothStateInt(bluetoothState);
}
public void setBluetoothEnabled(boolean enabled) {
boolean wasSetStateSuccessful = enabled
? mAdapter.enable()
: mAdapter.disable();
if (wasSetStateSuccessful) {
setBluetoothStateInt(enabled
? BluetoothAdapter.STATE_TURNING_ON
: BluetoothAdapter.STATE_TURNING_OFF);
} else {
if (V) {
Log.v(TAG,
"setBluetoothEnabled call, manager didn't return success for enabled: "
+ enabled);
}
syncBluetoothState();
}
}
/**
* @param started True if scanning started, false if scanning finished.
*/
void onScanningStateChanged(boolean started) {
// TODO: have it be a callback (once we switch bluetooth state changed to callback)
mCachedDeviceManager.onScanningStateChanged(started);
dispatchScanningStateChanged(started);
}
private void dispatchScanningStateChanged(boolean started) {
synchronized (mCallbacks) {
for (Callback callback : mCallbacks) {
callback.onScanningStateChanged(started);
}
}
}
public void showError(BluetoothDevice device, int titleResId, int messageResId) {
CachedBluetoothDevice cachedDevice = mCachedDeviceManager.findDevice(device);
String name = null;
if (cachedDevice == null) {
if (device != null) name = device.getName();
if (name == null) {
name = mContext.getString(R.string.bluetooth_remote_device);
}
} else {
name = cachedDevice.getName();
}
String message = mContext.getString(messageResId, name);
if (mForegroundActivity != null) {
// Need an activity context to show a dialog
mErrorDialog = new AlertDialog.Builder(mForegroundActivity)
.setIcon(android.R.drawable.ic_dialog_alert)
.setTitle(titleResId)
.setMessage(message)
.setPositiveButton(android.R.string.ok, null)
.show();
} else {
// Fallback on a toast
Toast.makeText(mContext, message, Toast.LENGTH_LONG).show();
}
}
public interface Callback {
void onScanningStateChanged(boolean started);
void onDeviceAdded(CachedBluetoothDevice cachedDevice);
void onDeviceDeleted(CachedBluetoothDevice cachedDevice);
}
public boolean shouldShowDialogInForeground(String deviceAddress) {
// If Bluetooth Settings is visible
if (mForegroundActivity != null) return true;
long currentTimeMillis = System.currentTimeMillis();
SharedPreferences sharedPreferences = getSharedPreferences();
// If the device was in discoverable mode recently
long lastDiscoverableEndTime = sharedPreferences.getLong(
BluetoothDiscoverableEnabler.SHARED_PREFERENCES_KEY_DISCOVERABLE_END_TIMESTAMP, 0);
if ((lastDiscoverableEndTime + GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND)
> currentTimeMillis) {
return true;
}
// If the device was picked in the device picker recently
if (deviceAddress != null) {
String lastSelectedDevice = sharedPreferences.getString(
SHARED_PREFERENCES_KEY_LAST_SELECTED_DEVICE, null);
if (deviceAddress.equals(lastSelectedDevice)) {
long lastDeviceSelectedTime = sharedPreferences.getLong(
SHARED_PREFERENCES_KEY_LAST_SELECTED_DEVICE_TIME, 0);
if ((lastDeviceSelectedTime + GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND)
> currentTimeMillis) {
return true;
}
}
}
return false;
}
void persistSelectedDeviceInPicker(String deviceAddress) {
SharedPreferences.Editor editor = getSharedPreferences().edit();
editor.putString(LocalBluetoothManager.SHARED_PREFERENCES_KEY_LAST_SELECTED_DEVICE,
deviceAddress);
editor.putLong(LocalBluetoothManager.SHARED_PREFERENCES_KEY_LAST_SELECTED_DEVICE_TIME,
System.currentTimeMillis());
editor.commit();
}
public boolean hasDockAutoConnectSetting(String addr) {
return getSharedPreferences().contains(SHARED_PREFERENCES_KEY_DOCK_AUTO_CONNECT + addr);
}
public boolean getDockAutoConnectSetting(String addr) {
return getSharedPreferences().getBoolean(SHARED_PREFERENCES_KEY_DOCK_AUTO_CONNECT + addr,
false);
}
public void saveDockAutoConnectSetting(String addr, boolean autoConnect) {
SharedPreferences.Editor editor = getSharedPreferences().edit();
editor.putBoolean(SHARED_PREFERENCES_KEY_DOCK_AUTO_CONNECT + addr, autoConnect);
editor.commit();
}
public void removeDockAutoConnectSetting(String addr) {
SharedPreferences.Editor editor = getSharedPreferences().edit();
editor.remove(SHARED_PREFERENCES_KEY_DOCK_AUTO_CONNECT + addr);
editor.commit();
}
}