Fix BluetoothPan closing sequence for unregsiter state change callback bug 11160007 Change-Id: I405ec91a938289fe9541de2ebd9abc1fb938f44a
1263 lines
54 KiB
Java
1263 lines
54 KiB
Java
/*
|
|
* 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.
|
|
}
|
|
}
|
|
}
|