The bindService() and startService() calls have always had undefined behavior when used with an implicit Intent and there are multiple matching services. Because of this, it is not safe for applications to use such Intents when interacting with services, yet the platform would merrily go about doing... something. In KLP I want to cause this case to be invalid, resulting in an exception thrown back to the app. Unfortunately there are lots of (scary) things relying on this behavior, so we can't immediately turn it into an exception, even one qualified by the caller's target SDK version. In this change, we start loggin a WTF when such a call happens, and clean up some stuff in Bluetooth that was doing this behavior. Change-Id: I62e25d07890588d2362104e20b054aebb6c0e007
1259 lines
54 KiB
Java
1259 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;
|
|
mStateChangeCallbacks.register(callback);
|
|
break;
|
|
}
|
|
case MESSAGE_UNREGISTER_STATE_CHANGE_CALLBACK:
|
|
{
|
|
IBluetoothStateChangeCallback callback = (IBluetoothStateChangeCallback) msg.obj;
|
|
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.
|
|
}
|
|
}
|
|
}
|