blob: 4fd708eccf01777e1beb39dd835f551c3d573770 [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 java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.app.AlertDialog;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.util.Log;
import android.widget.Toast;
// 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 = true;
public static final String EXTENDED_BLUETOOTH_STATE_CHANGED_ACTION =
"com.android.settings.bluetooth.intent.action.EXTENDED_BLUETOOTH_STATE_CHANGED";
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 BluetoothDevice mManager;
private LocalBluetoothDeviceManager mLocalDeviceManager;
private BluetoothEventRedirector mEventRedirector;
public static enum ExtendedBluetoothState { ENABLED, ENABLING, DISABLED, DISABLING, UNKNOWN }
private ExtendedBluetoothState mState = ExtendedBluetoothState.UNKNOWN;
private List<Callback> mCallbacks = new ArrayList<Callback>();
private static final int SCAN_EXPIRATION_MS = 5 * 60 * 1000; // 5 mins
private long mLastScan;
public static LocalBluetoothManager getInstance(Context context) {
synchronized (INSTANCE_LOCK) {
if (INSTANCE == null) {
INSTANCE = new LocalBluetoothManager();
}
if (!INSTANCE.init(context)) {
return null;
}
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();
mManager = (BluetoothDevice) context.getSystemService(Context.BLUETOOTH_SERVICE);
if (mManager == null) {
return false;
}
mLocalDeviceManager = new LocalBluetoothDeviceManager(this);
mEventRedirector = new BluetoothEventRedirector(this);
mEventRedirector.start();
return true;
}
public BluetoothDevice getBluetoothManager() {
return mManager;
}
public Context getContext() {
return mContext;
}
public Activity getForegroundActivity() {
return mForegroundActivity;
}
public void setForegroundActivity(Activity activity) {
mForegroundActivity = activity;
}
public SharedPreferences getSharedPreferences() {
return mContext.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
}
public LocalBluetoothDeviceManager getLocalDeviceManager() {
return mLocalDeviceManager;
}
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 (mManager.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 {
// Don't scan more than frequently than SCAN_EXPIRATION_MS, unless forced
if (!force && mLastScan + SCAN_EXPIRATION_MS > System.currentTimeMillis()) return;
if (mManager.startDiscovery(true)) {
mLastScan = System.currentTimeMillis();
}
}
}
public ExtendedBluetoothState getBluetoothState() {
if (mState == ExtendedBluetoothState.UNKNOWN) {
syncBluetoothState();
}
return mState;
}
void setBluetoothStateInt(ExtendedBluetoothState state) {
mState = state;
/*
* TODO: change to callback method. originally it was broadcast to
* parallel the framework's method, but it just complicates things here.
*/
// If this were a real API, I'd add as an extra
mContext.sendBroadcast(new Intent(EXTENDED_BLUETOOTH_STATE_CHANGED_ACTION));
if (state == ExtendedBluetoothState.ENABLED || state == ExtendedBluetoothState.DISABLED) {
mLocalDeviceManager.onBluetoothStateChanged(state == ExtendedBluetoothState.ENABLED);
}
}
private void syncBluetoothState() {
setBluetoothStateInt(mManager.isEnabled()
? ExtendedBluetoothState.ENABLED
: ExtendedBluetoothState.DISABLED);
}
public void setBluetoothEnabled(boolean enabled) {
boolean wasSetStateSuccessful = enabled
? mManager.enable()
: mManager.disable();
if (wasSetStateSuccessful) {
setBluetoothStateInt(enabled
? ExtendedBluetoothState.ENABLING
: ExtendedBluetoothState.DISABLING);
} 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)
mLocalDeviceManager.onScanningStateChanged(started);
dispatchScanningStateChanged(started);
}
private void dispatchScanningStateChanged(boolean started) {
synchronized (mCallbacks) {
for (Callback callback : mCallbacks) {
callback.onScanningStateChanged(started);
}
}
}
public void showError(String address, int titleResId, int messageResId) {
LocalBluetoothDevice device = mLocalDeviceManager.findDevice(address);
if (device == null) return;
String name = device.getName();
String message = mContext.getString(messageResId, name);
if (mForegroundActivity != null) {
// Need an activity context to show a dialog
AlertDialog ad = 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_SHORT).show();
}
}
public interface Callback {
void onScanningStateChanged(boolean started);
void onDeviceAdded(LocalBluetoothDevice device);
void onDeviceDeleted(LocalBluetoothDevice device);
}
}