diff options
Diffstat (limited to 'services/core/java/com/android/server/BluetoothManagerService.java')
-rw-r--r-- | services/core/java/com/android/server/BluetoothManagerService.java | 1262 |
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. + } + } +} |