summaryrefslogtreecommitdiffstats
path: root/services/core/java/com/android/server/BluetoothManagerService.java
diff options
context:
space:
mode:
Diffstat (limited to 'services/core/java/com/android/server/BluetoothManagerService.java')
-rw-r--r--services/core/java/com/android/server/BluetoothManagerService.java1262
1 files changed, 1262 insertions, 0 deletions
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
new file mode 100644
index 0000000..546324a
--- /dev/null
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -0,0 +1,1262 @@
+/*
+ * Copyright (C) 2012 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.server;
+
+import android.app.ActivityManager;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.IBluetooth;
+import android.bluetooth.IBluetoothGatt;
+import android.bluetooth.IBluetoothCallback;
+import android.bluetooth.IBluetoothManager;
+import android.bluetooth.IBluetoothManagerCallback;
+import android.bluetooth.IBluetoothStateChangeCallback;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Log;
+class BluetoothManagerService extends IBluetoothManager.Stub {
+ private static final String TAG = "BluetoothManagerService";
+ private static final boolean DBG = true;
+
+ private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
+ private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
+ private static final String ACTION_SERVICE_STATE_CHANGED="com.android.bluetooth.btservice.action.STATE_CHANGED";
+ private static final String EXTRA_ACTION="action";
+ private static final String SECURE_SETTINGS_BLUETOOTH_ADDR_VALID="bluetooth_addr_valid";
+ private static final String SECURE_SETTINGS_BLUETOOTH_ADDRESS="bluetooth_address";
+ private static final String SECURE_SETTINGS_BLUETOOTH_NAME="bluetooth_name";
+ private static final int TIMEOUT_BIND_MS = 3000; //Maximum msec to wait for a bind
+ private static final int TIMEOUT_SAVE_MS = 500; //Maximum msec to wait for a save
+ //Maximum msec to wait for service restart
+ private static final int SERVICE_RESTART_TIME_MS = 200;
+ //Maximum msec to wait for restart due to error
+ private static final int ERROR_RESTART_TIME_MS = 3000;
+ //Maximum msec to delay MESSAGE_USER_SWITCHED
+ private static final int USER_SWITCHED_TIME_MS = 200;
+
+ private static final int MESSAGE_ENABLE = 1;
+ private static final int MESSAGE_DISABLE = 2;
+ private static final int MESSAGE_REGISTER_ADAPTER = 20;
+ private static final int MESSAGE_UNREGISTER_ADAPTER = 21;
+ private static final int MESSAGE_REGISTER_STATE_CHANGE_CALLBACK = 30;
+ private static final int MESSAGE_UNREGISTER_STATE_CHANGE_CALLBACK = 31;
+ private static final int MESSAGE_BLUETOOTH_SERVICE_CONNECTED = 40;
+ private static final int MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED = 41;
+ private static final int MESSAGE_RESTART_BLUETOOTH_SERVICE = 42;
+ private static final int MESSAGE_BLUETOOTH_STATE_CHANGE=60;
+ private static final int MESSAGE_TIMEOUT_BIND =100;
+ private static final int MESSAGE_TIMEOUT_UNBIND =101;
+ private static final int MESSAGE_GET_NAME_AND_ADDRESS=200;
+ private static final int MESSAGE_SAVE_NAME_AND_ADDRESS=201;
+ private static final int MESSAGE_USER_SWITCHED = 300;
+ private static final int MAX_SAVE_RETRIES=3;
+ private static final int MAX_ERROR_RESTART_RETRIES=6;
+
+ // Bluetooth persisted setting is off
+ private static final int BLUETOOTH_OFF=0;
+ // Bluetooth persisted setting is on
+ // and Airplane mode won't affect Bluetooth state at start up
+ private static final int BLUETOOTH_ON_BLUETOOTH=1;
+ // Bluetooth persisted setting is on
+ // but Airplane mode will affect Bluetooth state at start up
+ // and Airplane mode will have higher priority.
+ private static final int BLUETOOTH_ON_AIRPLANE=2;
+
+ private static final int SERVICE_IBLUETOOTH = 1;
+ private static final int SERVICE_IBLUETOOTHGATT = 2;
+
+ private final Context mContext;
+
+ // Locks are not provided for mName and mAddress.
+ // They are accessed in handler or broadcast receiver, same thread context.
+ private String mAddress;
+ private String mName;
+ private final ContentResolver mContentResolver;
+ private final RemoteCallbackList<IBluetoothManagerCallback> mCallbacks;
+ private final RemoteCallbackList<IBluetoothStateChangeCallback> mStateChangeCallbacks;
+ private IBluetooth mBluetooth;
+ private IBluetoothGatt mBluetoothGatt;
+ private boolean mBinding;
+ private boolean mUnbinding;
+ // used inside handler thread
+ private boolean mQuietEnable = false;
+ // configuarion from external IBinder call which is used to
+ // synchronize with broadcast receiver.
+ private boolean mQuietEnableExternal;
+ // configuarion from external IBinder call which is used to
+ // synchronize with broadcast receiver.
+ private boolean mEnableExternal;
+ // used inside handler thread
+ private boolean mEnable;
+ private int mState;
+ private final BluetoothHandler mHandler;
+ private int mErrorRecoveryRetryCounter;
+
+ private void registerForAirplaneMode(IntentFilter filter) {
+ final ContentResolver resolver = mContext.getContentResolver();
+ final String airplaneModeRadios = Settings.Global.getString(resolver,
+ Settings.Global.AIRPLANE_MODE_RADIOS);
+ final String toggleableRadios = Settings.Global.getString(resolver,
+ Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS);
+ boolean mIsAirplaneSensitive = airplaneModeRadios == null ? true :
+ airplaneModeRadios.contains(Settings.Global.RADIO_BLUETOOTH);
+ if (mIsAirplaneSensitive) {
+ filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ }
+ }
+
+ private final IBluetoothCallback mBluetoothCallback = new IBluetoothCallback.Stub() {
+ @Override
+ public void onBluetoothStateChange(int prevState, int newState) throws RemoteException {
+ Message msg = mHandler.obtainMessage(MESSAGE_BLUETOOTH_STATE_CHANGE,prevState,newState);
+ mHandler.sendMessage(msg);
+ }
+ };
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED.equals(action)) {
+ String newName = intent.getStringExtra(BluetoothAdapter.EXTRA_LOCAL_NAME);
+ if (DBG) Log.d(TAG, "Bluetooth Adapter name changed to " + newName);
+ if (newName != null) {
+ storeNameAndAddress(newName, null);
+ }
+ } else if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(action)) {
+ synchronized(mReceiver) {
+ if (isBluetoothPersistedStateOn()) {
+ if (isAirplaneModeOn()) {
+ persistBluetoothSetting(BLUETOOTH_ON_AIRPLANE);
+ } else {
+ persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH);
+ }
+ }
+ if (isAirplaneModeOn()) {
+ // disable without persisting the setting
+ sendDisableMsg();
+ } else if (mEnableExternal) {
+ // enable without persisting the setting
+ sendEnableMsg(mQuietEnableExternal);
+ }
+ }
+ } else if (Intent.ACTION_USER_SWITCHED.equals(action)) {
+ mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_USER_SWITCHED,
+ intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0), 0));
+ } else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
+ synchronized(mReceiver) {
+ if (mEnableExternal && isBluetoothPersistedStateOnBluetooth()) {
+ //Enable
+ if (DBG) Log.d(TAG, "Auto-enabling Bluetooth.");
+ sendEnableMsg(mQuietEnableExternal);
+ }
+ }
+
+ if (!isNameAndAddressSet()) {
+ //Sync the Bluetooth name and address from the Bluetooth Adapter
+ if (DBG) Log.d(TAG,"Retrieving Bluetooth Adapter name and address...");
+ getNameAndAddress();
+ }
+ }
+ }
+ };
+
+ BluetoothManagerService(Context context) {
+ mHandler = new BluetoothHandler(IoThread.get().getLooper());
+
+ mContext = context;
+ mBluetooth = null;
+ mBinding = false;
+ mUnbinding = false;
+ mEnable = false;
+ mState = BluetoothAdapter.STATE_OFF;
+ mQuietEnableExternal = false;
+ mEnableExternal = false;
+ mAddress = null;
+ mName = null;
+ mErrorRecoveryRetryCounter = 0;
+ mContentResolver = context.getContentResolver();
+ mCallbacks = new RemoteCallbackList<IBluetoothManagerCallback>();
+ mStateChangeCallbacks = new RemoteCallbackList<IBluetoothStateChangeCallback>();
+ IntentFilter filter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
+ filter.addAction(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED);
+ filter.addAction(Intent.ACTION_USER_SWITCHED);
+ registerForAirplaneMode(filter);
+ mContext.registerReceiver(mReceiver, filter);
+ loadStoredNameAndAddress();
+ if (isBluetoothPersistedStateOn()) {
+ mEnableExternal = true;
+ }
+ }
+
+ /**
+ * Returns true if airplane mode is currently on
+ */
+ private final boolean isAirplaneModeOn() {
+ return Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.AIRPLANE_MODE_ON, 0) == 1;
+ }
+
+ /**
+ * Returns true if the Bluetooth saved state is "on"
+ */
+ private final boolean isBluetoothPersistedStateOn() {
+ return Settings.Global.getInt(mContentResolver,
+ Settings.Global.BLUETOOTH_ON, 0) != BLUETOOTH_OFF;
+ }
+
+ /**
+ * Returns true if the Bluetooth saved state is BLUETOOTH_ON_BLUETOOTH
+ */
+ private final boolean isBluetoothPersistedStateOnBluetooth() {
+ return Settings.Global.getInt(mContentResolver,
+ Settings.Global.BLUETOOTH_ON, 0) == BLUETOOTH_ON_BLUETOOTH;
+ }
+
+ /**
+ * Save the Bluetooth on/off state
+ *
+ */
+ private void persistBluetoothSetting(int value) {
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.BLUETOOTH_ON,
+ value);
+ }
+
+ /**
+ * Returns true if the Bluetooth Adapter's name and address is
+ * locally cached
+ * @return
+ */
+ private boolean isNameAndAddressSet() {
+ return mName !=null && mAddress!= null && mName.length()>0 && mAddress.length()>0;
+ }
+
+ /**
+ * Retrieve the Bluetooth Adapter's name and address and save it in
+ * in the local cache
+ */
+ private void loadStoredNameAndAddress() {
+ if (DBG) Log.d(TAG, "Loading stored name and address");
+ if (mContext.getResources().getBoolean
+ (com.android.internal.R.bool.config_bluetooth_address_validation) &&
+ Settings.Secure.getInt(mContentResolver, SECURE_SETTINGS_BLUETOOTH_ADDR_VALID, 0) == 0) {
+ // if the valid flag is not set, don't load the address and name
+ if (DBG) Log.d(TAG, "invalid bluetooth name and address stored");
+ return;
+ }
+ mName = Settings.Secure.getString(mContentResolver, SECURE_SETTINGS_BLUETOOTH_NAME);
+ mAddress = Settings.Secure.getString(mContentResolver, SECURE_SETTINGS_BLUETOOTH_ADDRESS);
+ if (DBG) Log.d(TAG, "Stored bluetooth Name=" + mName + ",Address=" + mAddress);
+ }
+
+ /**
+ * Save the Bluetooth name and address in the persistent store.
+ * Only non-null values will be saved.
+ * @param name
+ * @param address
+ */
+ private void storeNameAndAddress(String name, String address) {
+ if (name != null) {
+ Settings.Secure.putString(mContentResolver, SECURE_SETTINGS_BLUETOOTH_NAME, name);
+ mName = name;
+ if (DBG) Log.d(TAG,"Stored Bluetooth name: " +
+ Settings.Secure.getString(mContentResolver,SECURE_SETTINGS_BLUETOOTH_NAME));
+ }
+
+ if (address != null) {
+ Settings.Secure.putString(mContentResolver, SECURE_SETTINGS_BLUETOOTH_ADDRESS, address);
+ mAddress=address;
+ if (DBG) Log.d(TAG,"Stored Bluetoothaddress: " +
+ Settings.Secure.getString(mContentResolver,SECURE_SETTINGS_BLUETOOTH_ADDRESS));
+ }
+
+ if ((name != null) && (address != null)) {
+ Settings.Secure.putInt(mContentResolver, SECURE_SETTINGS_BLUETOOTH_ADDR_VALID, 1);
+ }
+ }
+
+ public IBluetooth registerAdapter(IBluetoothManagerCallback callback){
+ Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_ADAPTER);
+ msg.obj = callback;
+ mHandler.sendMessage(msg);
+ synchronized(mConnection) {
+ return mBluetooth;
+ }
+ }
+
+ public void unregisterAdapter(IBluetoothManagerCallback callback) {
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
+ "Need BLUETOOTH permission");
+ Message msg = mHandler.obtainMessage(MESSAGE_UNREGISTER_ADAPTER);
+ msg.obj = callback;
+ mHandler.sendMessage(msg);
+ }
+
+ public void registerStateChangeCallback(IBluetoothStateChangeCallback callback) {
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
+ "Need BLUETOOTH permission");
+ Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_STATE_CHANGE_CALLBACK);
+ msg.obj = callback;
+ mHandler.sendMessage(msg);
+ }
+
+ public void unregisterStateChangeCallback(IBluetoothStateChangeCallback callback) {
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
+ "Need BLUETOOTH permission");
+ Message msg = mHandler.obtainMessage(MESSAGE_UNREGISTER_STATE_CHANGE_CALLBACK);
+ msg.obj = callback;
+ mHandler.sendMessage(msg);
+ }
+
+ public boolean isEnabled() {
+ if ((Binder.getCallingUid() != Process.SYSTEM_UID) &&
+ (!checkIfCallerIsForegroundUser())) {
+ Log.w(TAG,"isEnabled(): not allowed for non-active and non system user");
+ return false;
+ }
+
+ synchronized(mConnection) {
+ try {
+ return (mBluetooth != null && mBluetooth.isEnabled());
+ } catch (RemoteException e) {
+ Log.e(TAG, "isEnabled()", e);
+ }
+ }
+ return false;
+ }
+
+ public void getNameAndAddress() {
+ if (DBG) {
+ Log.d(TAG,"getNameAndAddress(): mBluetooth = " + mBluetooth +
+ " mBinding = " + mBinding);
+ }
+ Message msg = mHandler.obtainMessage(MESSAGE_GET_NAME_AND_ADDRESS);
+ mHandler.sendMessage(msg);
+ }
+ public boolean enableNoAutoConnect()
+ {
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+ "Need BLUETOOTH ADMIN permission");
+
+ if (DBG) {
+ Log.d(TAG,"enableNoAutoConnect(): mBluetooth =" + mBluetooth +
+ " mBinding = " + mBinding);
+ }
+ int callingAppId = UserHandle.getAppId(Binder.getCallingUid());
+
+ if (callingAppId != Process.NFC_UID) {
+ throw new SecurityException("no permission to enable Bluetooth quietly");
+ }
+
+ synchronized(mReceiver) {
+ mQuietEnableExternal = true;
+ mEnableExternal = true;
+ sendEnableMsg(true);
+ }
+ return true;
+
+ }
+ public boolean enable() {
+ if ((Binder.getCallingUid() != Process.SYSTEM_UID) &&
+ (!checkIfCallerIsForegroundUser())) {
+ Log.w(TAG,"enable(): not allowed for non-active and non system user");
+ return false;
+ }
+
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+ "Need BLUETOOTH ADMIN permission");
+ if (DBG) {
+ Log.d(TAG,"enable(): mBluetooth =" + mBluetooth +
+ " mBinding = " + mBinding);
+ }
+
+ synchronized(mReceiver) {
+ mQuietEnableExternal = false;
+ mEnableExternal = true;
+ // waive WRITE_SECURE_SETTINGS permission check
+ long callingIdentity = Binder.clearCallingIdentity();
+ persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH);
+ Binder.restoreCallingIdentity(callingIdentity);
+ sendEnableMsg(false);
+ }
+ return true;
+ }
+
+ public boolean disable(boolean persist) {
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+ "Need BLUETOOTH ADMIN permissicacheNameAndAddresson");
+
+ if ((Binder.getCallingUid() != Process.SYSTEM_UID) &&
+ (!checkIfCallerIsForegroundUser())) {
+ Log.w(TAG,"disable(): not allowed for non-active and non system user");
+ return false;
+ }
+
+ if (DBG) {
+ Log.d(TAG,"disable(): mBluetooth = " + mBluetooth +
+ " mBinding = " + mBinding);
+ }
+
+ synchronized(mReceiver) {
+ if (persist) {
+ // waive WRITE_SECURE_SETTINGS permission check
+ long callingIdentity = Binder.clearCallingIdentity();
+ persistBluetoothSetting(BLUETOOTH_OFF);
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ mEnableExternal = false;
+ sendDisableMsg();
+ }
+ return true;
+ }
+
+ public void unbindAndFinish() {
+ if (DBG) {
+ Log.d(TAG,"unbindAndFinish(): " + mBluetooth +
+ " mBinding = " + mBinding);
+ }
+
+ synchronized (mConnection) {
+ if (mUnbinding) return;
+ mUnbinding = true;
+ if (mBluetooth != null) {
+ if (!mConnection.isGetNameAddressOnly()) {
+ //Unregister callback object
+ try {
+ mBluetooth.unregisterCallback(mBluetoothCallback);
+ } catch (RemoteException re) {
+ Log.e(TAG, "Unable to unregister BluetoothCallback",re);
+ }
+ }
+ if (DBG) Log.d(TAG, "Sending unbind request.");
+ mBluetooth = null;
+ //Unbind
+ mContext.unbindService(mConnection);
+ mUnbinding = false;
+ mBinding = false;
+ } else {
+ mUnbinding=false;
+ }
+ }
+ }
+
+ public IBluetoothGatt getBluetoothGatt() {
+ // sync protection
+ return mBluetoothGatt;
+ }
+
+ private void sendBluetoothStateCallback(boolean isUp) {
+ int n = mStateChangeCallbacks.beginBroadcast();
+ if (DBG) Log.d(TAG,"Broadcasting onBluetoothStateChange("+isUp+") to " + n + " receivers.");
+ for (int i=0; i <n;i++) {
+ try {
+ mStateChangeCallbacks.getBroadcastItem(i).onBluetoothStateChange(isUp);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to call onBluetoothStateChange() on callback #" + i , e);
+ }
+ }
+ mStateChangeCallbacks.finishBroadcast();
+ }
+
+ /**
+ * Inform BluetoothAdapter instances that Adapter service is up
+ */
+ private void sendBluetoothServiceUpCallback() {
+ if (!mConnection.isGetNameAddressOnly()) {
+ if (DBG) Log.d(TAG,"Calling onBluetoothServiceUp callbacks");
+ int n = mCallbacks.beginBroadcast();
+ Log.d(TAG,"Broadcasting onBluetoothServiceUp() to " + n + " receivers.");
+ for (int i=0; i <n;i++) {
+ try {
+ mCallbacks.getBroadcastItem(i).onBluetoothServiceUp(mBluetooth);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to call onBluetoothServiceUp() on callback #" + i, e);
+ }
+ }
+ mCallbacks.finishBroadcast();
+ }
+ }
+ /**
+ * Inform BluetoothAdapter instances that Adapter service is down
+ */
+ private void sendBluetoothServiceDownCallback() {
+ if (!mConnection.isGetNameAddressOnly()) {
+ if (DBG) Log.d(TAG,"Calling onBluetoothServiceDown callbacks");
+ int n = mCallbacks.beginBroadcast();
+ Log.d(TAG,"Broadcasting onBluetoothServiceDown() to " + n + " receivers.");
+ for (int i=0; i <n;i++) {
+ try {
+ mCallbacks.getBroadcastItem(i).onBluetoothServiceDown();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to call onBluetoothServiceDown() on callback #" + i, e);
+ }
+ }
+ mCallbacks.finishBroadcast();
+ }
+ }
+ public String getAddress() {
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
+ "Need BLUETOOTH permission");
+
+ if ((Binder.getCallingUid() != Process.SYSTEM_UID) &&
+ (!checkIfCallerIsForegroundUser())) {
+ Log.w(TAG,"getAddress(): not allowed for non-active and non system user");
+ return null;
+ }
+
+ synchronized(mConnection) {
+ if (mBluetooth != null) {
+ try {
+ return mBluetooth.getAddress();
+ } catch (RemoteException e) {
+ Log.e(TAG, "getAddress(): Unable to retrieve address remotely..Returning cached address",e);
+ }
+ }
+ }
+ // mAddress is accessed from outside.
+ // It is alright without a lock. Here, bluetooth is off, no other thread is
+ // changing mAddress
+ return mAddress;
+ }
+
+ public String getName() {
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
+ "Need BLUETOOTH permission");
+
+ if ((Binder.getCallingUid() != Process.SYSTEM_UID) &&
+ (!checkIfCallerIsForegroundUser())) {
+ Log.w(TAG,"getName(): not allowed for non-active and non system user");
+ return null;
+ }
+
+ synchronized(mConnection) {
+ if (mBluetooth != null) {
+ try {
+ return mBluetooth.getName();
+ } catch (RemoteException e) {
+ Log.e(TAG, "getName(): Unable to retrieve name remotely..Returning cached name",e);
+ }
+ }
+ }
+ // mName is accessed from outside.
+ // It alright without a lock. Here, bluetooth is off, no other thread is
+ // changing mName
+ return mName;
+ }
+
+ private class BluetoothServiceConnection implements ServiceConnection {
+
+ private boolean mGetNameAddressOnly;
+
+ public void setGetNameAddressOnly(boolean getOnly) {
+ mGetNameAddressOnly = getOnly;
+ }
+
+ public boolean isGetNameAddressOnly() {
+ return mGetNameAddressOnly;
+ }
+
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ if (DBG) Log.d(TAG, "BluetoothServiceConnection: " + className.getClassName());
+ Message msg = mHandler.obtainMessage(MESSAGE_BLUETOOTH_SERVICE_CONNECTED);
+ // TBD if (className.getClassName().equals(IBluetooth.class.getName())) {
+ if (className.getClassName().equals("com.android.bluetooth.btservice.AdapterService")) {
+ msg.arg1 = SERVICE_IBLUETOOTH;
+ // } else if (className.getClassName().equals(IBluetoothGatt.class.getName())) {
+ } else if (className.getClassName().equals("com.android.bluetooth.gatt.GattService")) {
+ msg.arg1 = SERVICE_IBLUETOOTHGATT;
+ } else {
+ Log.e(TAG, "Unknown service connected: " + className.getClassName());
+ return;
+ }
+ msg.obj = service;
+ mHandler.sendMessage(msg);
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ // Called if we unexpected disconnected.
+ if (DBG) Log.d(TAG, "BluetoothServiceConnection, disconnected: " +
+ className.getClassName());
+ Message msg = mHandler.obtainMessage(MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED);
+ if (className.getClassName().equals("com.android.bluetooth.btservice.AdapterService")) {
+ msg.arg1 = SERVICE_IBLUETOOTH;
+ } else if (className.getClassName().equals("com.android.bluetooth.gatt.GattService")) {
+ msg.arg1 = SERVICE_IBLUETOOTHGATT;
+ } else {
+ Log.e(TAG, "Unknown service disconnected: " + className.getClassName());
+ return;
+ }
+ mHandler.sendMessage(msg);
+ }
+ }
+
+ private BluetoothServiceConnection mConnection = new BluetoothServiceConnection();
+
+ private class BluetoothHandler extends Handler {
+ public BluetoothHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (DBG) Log.d (TAG, "Message: " + msg.what);
+ switch (msg.what) {
+ case MESSAGE_GET_NAME_AND_ADDRESS: {
+ if (DBG) Log.d(TAG,"MESSAGE_GET_NAME_AND_ADDRESS");
+ synchronized(mConnection) {
+ //Start bind request
+ if ((mBluetooth == null) && (!mBinding)) {
+ if (DBG) Log.d(TAG, "Binding to service to get name and address");
+ mConnection.setGetNameAddressOnly(true);
+ //Start bind timeout and bind
+ Message timeoutMsg = mHandler.obtainMessage(MESSAGE_TIMEOUT_BIND);
+ mHandler.sendMessageDelayed(timeoutMsg,TIMEOUT_BIND_MS);
+ Intent i = new Intent(IBluetooth.class.getName());
+ if (!doBind(i, mConnection,
+ Context.BIND_AUTO_CREATE, UserHandle.CURRENT)) {
+ mHandler.removeMessages(MESSAGE_TIMEOUT_BIND);
+ } else {
+ mBinding = true;
+ }
+ }
+ else {
+ Message saveMsg= mHandler.obtainMessage(MESSAGE_SAVE_NAME_AND_ADDRESS);
+ saveMsg.arg1 = 0;
+ if (mBluetooth != null) {
+ mHandler.sendMessage(saveMsg);
+ } else {
+ // if enable is also called to bind the service
+ // wait for MESSAGE_BLUETOOTH_SERVICE_CONNECTED
+ mHandler.sendMessageDelayed(saveMsg, TIMEOUT_SAVE_MS);
+ }
+ }
+ }
+ break;
+ }
+ case MESSAGE_SAVE_NAME_AND_ADDRESS: {
+ boolean unbind = false;
+ if (DBG) Log.d(TAG,"MESSAGE_SAVE_NAME_AND_ADDRESS");
+ synchronized(mConnection) {
+ if (!mEnable && mBluetooth != null) {
+ try {
+ mBluetooth.enable();
+ } catch (RemoteException e) {
+ Log.e(TAG,"Unable to call enable()",e);
+ }
+ }
+ }
+ if (mBluetooth != null) waitForOnOff(true, false);
+ synchronized(mConnection) {
+ if (mBluetooth != null) {
+ String name = null;
+ String address = null;
+ try {
+ name = mBluetooth.getName();
+ address = mBluetooth.getAddress();
+ } catch (RemoteException re) {
+ Log.e(TAG,"",re);
+ }
+
+ if (name != null && address != null) {
+ storeNameAndAddress(name,address);
+ if (mConnection.isGetNameAddressOnly()) {
+ unbind = true;
+ }
+ } else {
+ if (msg.arg1 < MAX_SAVE_RETRIES) {
+ Message retryMsg = mHandler.obtainMessage(MESSAGE_SAVE_NAME_AND_ADDRESS);
+ retryMsg.arg1= 1+msg.arg1;
+ if (DBG) Log.d(TAG,"Retrying name/address remote retrieval and save.....Retry count =" + retryMsg.arg1);
+ mHandler.sendMessageDelayed(retryMsg, TIMEOUT_SAVE_MS);
+ } else {
+ Log.w(TAG,"Maximum name/address remote retrieval retry exceeded");
+ if (mConnection.isGetNameAddressOnly()) {
+ unbind = true;
+ }
+ }
+ }
+ if (!mEnable) {
+ try {
+ mBluetooth.disable();
+ } catch (RemoteException e) {
+ Log.e(TAG,"Unable to call disable()",e);
+ }
+ }
+ } else {
+ // rebind service by Request GET NAME AND ADDRESS
+ // if service is unbinded by disable or
+ // MESSAGE_BLUETOOTH_SERVICE_CONNECTED is not received
+ Message getMsg = mHandler.obtainMessage(MESSAGE_GET_NAME_AND_ADDRESS);
+ mHandler.sendMessage(getMsg);
+ }
+ }
+ if (!mEnable && mBluetooth != null) waitForOnOff(false, true);
+ if (unbind) {
+ unbindAndFinish();
+ }
+ break;
+ }
+ case MESSAGE_ENABLE:
+ if (DBG) {
+ Log.d(TAG, "MESSAGE_ENABLE: mBluetooth = " + mBluetooth);
+ }
+ mHandler.removeMessages(MESSAGE_RESTART_BLUETOOTH_SERVICE);
+ mEnable = true;
+ handleEnable(msg.arg1 == 1);
+ break;
+
+ case MESSAGE_DISABLE:
+ mHandler.removeMessages(MESSAGE_RESTART_BLUETOOTH_SERVICE);
+ if (mEnable && mBluetooth != null) {
+ waitForOnOff(true, false);
+ mEnable = false;
+ handleDisable();
+ waitForOnOff(false, false);
+ } else {
+ mEnable = false;
+ handleDisable();
+ }
+ break;
+
+ case MESSAGE_REGISTER_ADAPTER:
+ {
+ IBluetoothManagerCallback callback = (IBluetoothManagerCallback) msg.obj;
+ boolean added = mCallbacks.register(callback);
+ Log.d(TAG,"Added callback: " + (callback == null? "null": callback) +":" +added );
+ }
+ break;
+ case MESSAGE_UNREGISTER_ADAPTER:
+ {
+ IBluetoothManagerCallback callback = (IBluetoothManagerCallback) msg.obj;
+ boolean removed = mCallbacks.unregister(callback);
+ Log.d(TAG,"Removed callback: " + (callback == null? "null": callback) +":" + removed);
+ break;
+ }
+ case MESSAGE_REGISTER_STATE_CHANGE_CALLBACK:
+ {
+ IBluetoothStateChangeCallback callback = (IBluetoothStateChangeCallback) msg.obj;
+ if (callback != null) {
+ mStateChangeCallbacks.register(callback);
+ }
+ break;
+ }
+ case MESSAGE_UNREGISTER_STATE_CHANGE_CALLBACK:
+ {
+ IBluetoothStateChangeCallback callback = (IBluetoothStateChangeCallback) msg.obj;
+ if (callback != null) {
+ mStateChangeCallbacks.unregister(callback);
+ }
+ break;
+ }
+ case MESSAGE_BLUETOOTH_SERVICE_CONNECTED:
+ {
+ if (DBG) Log.d(TAG,"MESSAGE_BLUETOOTH_SERVICE_CONNECTED: " + msg.arg1);
+
+ IBinder service = (IBinder) msg.obj;
+ synchronized(mConnection) {
+ if (msg.arg1 == SERVICE_IBLUETOOTHGATT) {
+ mBluetoothGatt = IBluetoothGatt.Stub.asInterface(service);
+ break;
+ } // else must be SERVICE_IBLUETOOTH
+
+ //Remove timeout
+ mHandler.removeMessages(MESSAGE_TIMEOUT_BIND);
+
+ mBinding = false;
+ mBluetooth = IBluetooth.Stub.asInterface(service);
+
+ try {
+ boolean enableHciSnoopLog = (Settings.Secure.getInt(mContentResolver,
+ Settings.Secure.BLUETOOTH_HCI_LOG, 0) == 1);
+ if (!mBluetooth.configHciSnoopLog(enableHciSnoopLog)) {
+ Log.e(TAG,"IBluetooth.configHciSnoopLog return false");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG,"Unable to call configHciSnoopLog", e);
+ }
+
+ if (mConnection.isGetNameAddressOnly()) {
+ //Request GET NAME AND ADDRESS
+ Message getMsg = mHandler.obtainMessage(MESSAGE_GET_NAME_AND_ADDRESS);
+ mHandler.sendMessage(getMsg);
+ if (!mEnable) return;
+ }
+
+ mConnection.setGetNameAddressOnly(false);
+ //Register callback object
+ try {
+ mBluetooth.registerCallback(mBluetoothCallback);
+ } catch (RemoteException re) {
+ Log.e(TAG, "Unable to register BluetoothCallback",re);
+ }
+ //Inform BluetoothAdapter instances that service is up
+ sendBluetoothServiceUpCallback();
+
+ //Do enable request
+ try {
+ if (mQuietEnable == false) {
+ if(!mBluetooth.enable()) {
+ Log.e(TAG,"IBluetooth.enable() returned false");
+ }
+ }
+ else
+ {
+ if(!mBluetooth.enableNoAutoConnect()) {
+ Log.e(TAG,"IBluetooth.enableNoAutoConnect() returned false");
+ }
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG,"Unable to call enable()",e);
+ }
+ }
+
+ if (!mEnable) {
+ waitForOnOff(true, false);
+ handleDisable();
+ waitForOnOff(false, false);
+ }
+ break;
+ }
+ case MESSAGE_TIMEOUT_BIND: {
+ Log.e(TAG, "MESSAGE_TIMEOUT_BIND");
+ synchronized(mConnection) {
+ mBinding = false;
+ }
+ break;
+ }
+ case MESSAGE_BLUETOOTH_STATE_CHANGE:
+ {
+ int prevState = msg.arg1;
+ int newState = msg.arg2;
+ if (DBG) Log.d(TAG, "MESSAGE_BLUETOOTH_STATE_CHANGE: prevState = " + prevState + ", newState=" + newState);
+ mState = newState;
+ bluetoothStateChangeHandler(prevState, newState);
+ // handle error state transition case from TURNING_ON to OFF
+ // unbind and rebind bluetooth service and enable bluetooth
+ if ((prevState == BluetoothAdapter.STATE_TURNING_ON) &&
+ (newState == BluetoothAdapter.STATE_OFF) &&
+ (mBluetooth != null) && mEnable) {
+ recoverBluetoothServiceFromError();
+ }
+ if (newState == BluetoothAdapter.STATE_ON) {
+ // bluetooth is working, reset the counter
+ if (mErrorRecoveryRetryCounter != 0) {
+ Log.w(TAG, "bluetooth is recovered from error");
+ mErrorRecoveryRetryCounter = 0;
+ }
+ }
+ break;
+ }
+ case MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED:
+ {
+ Log.e(TAG, "MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED: " + msg.arg1);
+ synchronized(mConnection) {
+ if (msg.arg1 == SERVICE_IBLUETOOTH) {
+ // if service is unbinded already, do nothing and return
+ if (mBluetooth == null) break;
+ mBluetooth = null;
+ } else if (msg.arg1 == SERVICE_IBLUETOOTHGATT) {
+ mBluetoothGatt = null;
+ break;
+ } else {
+ Log.e(TAG, "Bad msg.arg1: " + msg.arg1);
+ break;
+ }
+ }
+
+ if (mEnable) {
+ mEnable = false;
+ // Send a Bluetooth Restart message
+ Message restartMsg = mHandler.obtainMessage(
+ MESSAGE_RESTART_BLUETOOTH_SERVICE);
+ mHandler.sendMessageDelayed(restartMsg,
+ SERVICE_RESTART_TIME_MS);
+ }
+
+ if (!mConnection.isGetNameAddressOnly()) {
+ sendBluetoothServiceDownCallback();
+
+ // Send BT state broadcast to update
+ // the BT icon correctly
+ if ((mState == BluetoothAdapter.STATE_TURNING_ON) ||
+ (mState == BluetoothAdapter.STATE_ON)) {
+ bluetoothStateChangeHandler(BluetoothAdapter.STATE_ON,
+ BluetoothAdapter.STATE_TURNING_OFF);
+ mState = BluetoothAdapter.STATE_TURNING_OFF;
+ }
+ if (mState == BluetoothAdapter.STATE_TURNING_OFF) {
+ bluetoothStateChangeHandler(BluetoothAdapter.STATE_TURNING_OFF,
+ BluetoothAdapter.STATE_OFF);
+ }
+
+ mHandler.removeMessages(MESSAGE_BLUETOOTH_STATE_CHANGE);
+ mState = BluetoothAdapter.STATE_OFF;
+ }
+ break;
+ }
+ case MESSAGE_RESTART_BLUETOOTH_SERVICE:
+ {
+ Log.d(TAG, "MESSAGE_RESTART_BLUETOOTH_SERVICE:"
+ +" Restart IBluetooth service");
+ /* Enable without persisting the setting as
+ it doesnt change when IBluetooth
+ service restarts */
+ mEnable = true;
+ handleEnable(mQuietEnable);
+ break;
+ }
+
+ case MESSAGE_TIMEOUT_UNBIND:
+ {
+ Log.e(TAG, "MESSAGE_TIMEOUT_UNBIND");
+ synchronized(mConnection) {
+ mUnbinding = false;
+ }
+ break;
+ }
+
+ case MESSAGE_USER_SWITCHED:
+ {
+ if (DBG) {
+ Log.d(TAG, "MESSAGE_USER_SWITCHED");
+ }
+ mHandler.removeMessages(MESSAGE_USER_SWITCHED);
+ /* disable and enable BT when detect a user switch */
+ if (mEnable && mBluetooth != null) {
+ synchronized (mConnection) {
+ if (mBluetooth != null) {
+ //Unregister callback object
+ try {
+ mBluetooth.unregisterCallback(mBluetoothCallback);
+ } catch (RemoteException re) {
+ Log.e(TAG, "Unable to unregister",re);
+ }
+ }
+ }
+
+ if (mState == BluetoothAdapter.STATE_TURNING_OFF) {
+ // MESSAGE_USER_SWITCHED happened right after MESSAGE_ENABLE
+ bluetoothStateChangeHandler(mState, BluetoothAdapter.STATE_OFF);
+ mState = BluetoothAdapter.STATE_OFF;
+ }
+ if (mState == BluetoothAdapter.STATE_OFF) {
+ bluetoothStateChangeHandler(mState, BluetoothAdapter.STATE_TURNING_ON);
+ mState = BluetoothAdapter.STATE_TURNING_ON;
+ }
+
+ waitForOnOff(true, false);
+
+ if (mState == BluetoothAdapter.STATE_TURNING_ON) {
+ bluetoothStateChangeHandler(mState, BluetoothAdapter.STATE_ON);
+ }
+
+ // disable
+ handleDisable();
+ // Pbap service need receive STATE_TURNING_OFF intent to close
+ bluetoothStateChangeHandler(BluetoothAdapter.STATE_ON,
+ BluetoothAdapter.STATE_TURNING_OFF);
+
+ waitForOnOff(false, true);
+
+ bluetoothStateChangeHandler(BluetoothAdapter.STATE_TURNING_OFF,
+ BluetoothAdapter.STATE_OFF);
+ sendBluetoothServiceDownCallback();
+ synchronized (mConnection) {
+ if (mBluetooth != null) {
+ mBluetooth = null;
+ //Unbind
+ mContext.unbindService(mConnection);
+ }
+ }
+ SystemClock.sleep(100);
+
+ mHandler.removeMessages(MESSAGE_BLUETOOTH_STATE_CHANGE);
+ mState = BluetoothAdapter.STATE_OFF;
+ // enable
+ handleEnable(mQuietEnable);
+ } else if (mBinding || mBluetooth != null) {
+ Message userMsg = mHandler.obtainMessage(MESSAGE_USER_SWITCHED);
+ userMsg.arg2 = 1 + msg.arg2;
+ // if user is switched when service is being binding
+ // delay sending MESSAGE_USER_SWITCHED
+ mHandler.sendMessageDelayed(userMsg, USER_SWITCHED_TIME_MS);
+ if (DBG) {
+ Log.d(TAG, "delay MESSAGE_USER_SWITCHED " + userMsg.arg2);
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ private void handleEnable(boolean quietMode) {
+ mQuietEnable = quietMode;
+
+ synchronized(mConnection) {
+ if ((mBluetooth == null) && (!mBinding)) {
+ //Start bind timeout and bind
+ Message timeoutMsg=mHandler.obtainMessage(MESSAGE_TIMEOUT_BIND);
+ mHandler.sendMessageDelayed(timeoutMsg,TIMEOUT_BIND_MS);
+ mConnection.setGetNameAddressOnly(false);
+ Intent i = new Intent(IBluetooth.class.getName());
+ if (!doBind(i, mConnection,Context.BIND_AUTO_CREATE, UserHandle.CURRENT)) {
+ mHandler.removeMessages(MESSAGE_TIMEOUT_BIND);
+ } else {
+ mBinding = true;
+ }
+ } else if (mBluetooth != null) {
+ if (mConnection.isGetNameAddressOnly()) {
+ // if GetNameAddressOnly is set, we can clear this flag,
+ // so the service won't be unbind
+ // after name and address are saved
+ mConnection.setGetNameAddressOnly(false);
+ //Register callback object
+ try {
+ mBluetooth.registerCallback(mBluetoothCallback);
+ } catch (RemoteException re) {
+ Log.e(TAG, "Unable to register BluetoothCallback",re);
+ }
+ //Inform BluetoothAdapter instances that service is up
+ sendBluetoothServiceUpCallback();
+ }
+
+ //Enable bluetooth
+ try {
+ if (!mQuietEnable) {
+ if(!mBluetooth.enable()) {
+ Log.e(TAG,"IBluetooth.enable() returned false");
+ }
+ }
+ else {
+ if(!mBluetooth.enableNoAutoConnect()) {
+ Log.e(TAG,"IBluetooth.enableNoAutoConnect() returned false");
+ }
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG,"Unable to call enable()",e);
+ }
+ }
+ }
+ }
+
+ boolean doBind(Intent intent, ServiceConnection conn, int flags, UserHandle user) {
+ ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
+ intent.setComponent(comp);
+ if (comp == null || !mContext.bindServiceAsUser(intent, conn, flags, user)) {
+ Log.e(TAG, "Fail to bind to: " + intent);
+ return false;
+ }
+ return true;
+ }
+
+ private void handleDisable() {
+ synchronized(mConnection) {
+ // don't need to disable if GetNameAddressOnly is set,
+ // service will be unbinded after Name and Address are saved
+ if ((mBluetooth != null) && (!mConnection.isGetNameAddressOnly())) {
+ if (DBG) Log.d(TAG,"Sending off request.");
+
+ try {
+ if(!mBluetooth.disable()) {
+ Log.e(TAG,"IBluetooth.disable() returned false");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG,"Unable to call disable()",e);
+ }
+ }
+ }
+ }
+
+ private boolean checkIfCallerIsForegroundUser() {
+ int foregroundUser;
+ int callingUser = UserHandle.getCallingUserId();
+ int callingUid = Binder.getCallingUid();
+ long callingIdentity = Binder.clearCallingIdentity();
+ int callingAppId = UserHandle.getAppId(callingUid);
+ boolean valid = false;
+ try {
+ foregroundUser = ActivityManager.getCurrentUser();
+ valid = (callingUser == foregroundUser) ||
+ callingAppId == Process.NFC_UID;
+ if (DBG) {
+ Log.d(TAG, "checkIfCallerIsForegroundUser: valid=" + valid
+ + " callingUser=" + callingUser
+ + " foregroundUser=" + foregroundUser);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ return valid;
+ }
+
+ private void bluetoothStateChangeHandler(int prevState, int newState) {
+ if (prevState != newState) {
+ //Notify all proxy objects first of adapter state change
+ if (newState == BluetoothAdapter.STATE_ON || newState == BluetoothAdapter.STATE_OFF) {
+ boolean isUp = (newState==BluetoothAdapter.STATE_ON);
+ sendBluetoothStateCallback(isUp);
+
+ if (isUp) {
+ // connect to GattService
+ if (mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_BLUETOOTH_LE)) {
+ Intent i = new Intent(IBluetoothGatt.class.getName());
+ doBind(i, mConnection, Context.BIND_AUTO_CREATE, UserHandle.CURRENT);
+ }
+ } else {
+ //If Bluetooth is off, send service down event to proxy objects, and unbind
+ if (!isUp && canUnbindBluetoothService()) {
+ sendBluetoothServiceDownCallback();
+ unbindAndFinish();
+ }
+ }
+ }
+
+ //Send broadcast message to everyone else
+ Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED);
+ intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, prevState);
+ intent.putExtra(BluetoothAdapter.EXTRA_STATE, newState);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ if (DBG) Log.d(TAG,"Bluetooth State Change Intent: " + prevState + " -> " + newState);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
+ BLUETOOTH_PERM);
+ }
+ }
+
+ /**
+ * if on is true, wait for state become ON
+ * if off is true, wait for state become OFF
+ * if both on and off are false, wait for state not ON
+ */
+ private boolean waitForOnOff(boolean on, boolean off) {
+ int i = 0;
+ while (i < 10) {
+ synchronized(mConnection) {
+ try {
+ if (mBluetooth == null) break;
+ if (on) {
+ if (mBluetooth.getState() == BluetoothAdapter.STATE_ON) return true;
+ } else if (off) {
+ if (mBluetooth.getState() == BluetoothAdapter.STATE_OFF) return true;
+ } else {
+ if (mBluetooth.getState() != BluetoothAdapter.STATE_ON) return true;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "getState()", e);
+ break;
+ }
+ }
+ if (on || off) {
+ SystemClock.sleep(300);
+ } else {
+ SystemClock.sleep(50);
+ }
+ i++;
+ }
+ Log.e(TAG,"waitForOnOff time out");
+ return false;
+ }
+
+ private void sendDisableMsg() {
+ mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_DISABLE));
+ }
+
+ private void sendEnableMsg(boolean quietMode) {
+ mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_ENABLE,
+ quietMode ? 1 : 0, 0));
+ }
+
+ private boolean canUnbindBluetoothService() {
+ synchronized(mConnection) {
+ //Only unbind with mEnable flag not set
+ //For race condition: disable and enable back-to-back
+ //Avoid unbind right after enable due to callback from disable
+ //Only unbind with Bluetooth at OFF state
+ //Only unbind without any MESSAGE_BLUETOOTH_STATE_CHANGE message
+ try {
+ if (mEnable || (mBluetooth == null)) return false;
+ if (mHandler.hasMessages(MESSAGE_BLUETOOTH_STATE_CHANGE)) return false;
+ return (mBluetooth.getState() == BluetoothAdapter.STATE_OFF);
+ } catch (RemoteException e) {
+ Log.e(TAG, "getState()", e);
+ }
+ }
+ return false;
+ }
+
+ private void recoverBluetoothServiceFromError() {
+ Log.e(TAG,"recoverBluetoothServiceFromError");
+ synchronized (mConnection) {
+ if (mBluetooth != null) {
+ //Unregister callback object
+ try {
+ mBluetooth.unregisterCallback(mBluetoothCallback);
+ } catch (RemoteException re) {
+ Log.e(TAG, "Unable to unregister",re);
+ }
+ }
+ }
+
+ SystemClock.sleep(500);
+
+ // disable
+ handleDisable();
+
+ waitForOnOff(false, true);
+
+ sendBluetoothServiceDownCallback();
+ synchronized (mConnection) {
+ if (mBluetooth != null) {
+ mBluetooth = null;
+ //Unbind
+ mContext.unbindService(mConnection);
+ }
+ }
+
+ mHandler.removeMessages(MESSAGE_BLUETOOTH_STATE_CHANGE);
+ mState = BluetoothAdapter.STATE_OFF;
+
+ mEnable = false;
+
+ if (mErrorRecoveryRetryCounter++ < MAX_ERROR_RESTART_RETRIES) {
+ // Send a Bluetooth Restart message to reenable bluetooth
+ Message restartMsg = mHandler.obtainMessage(
+ MESSAGE_RESTART_BLUETOOTH_SERVICE);
+ mHandler.sendMessageDelayed(restartMsg, ERROR_RESTART_TIME_MS);
+ } else {
+ // todo: notify user to power down and power up phone to make bluetooth work.
+ }
+ }
+}