| /* |
| * 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; |
| |
| public static final String SHARED_PREFERENCES_KEY_DISCOVERING_TIMESTAMP = |
| "last_discovering_time"; |
| |
| 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 void stopScanning() { |
| if (mAdapter.isDiscovering()) { |
| mAdapter.cancelDiscovery(); |
| } |
| } |
| |
| 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 discoverING recently |
| if (mAdapter != null && mAdapter.isDiscovering()) { |
| return true; |
| } else if ((sharedPreferences.getLong(SHARED_PREFERENCES_KEY_DISCOVERING_TIMESTAMP, 0) + |
| 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(); |
| } |
| } |