/* * Copyright (C) 2012 Google Inc. */ package com.android.server; import android.bluetooth.BluetoothAdapter; import android.bluetooth.IBluetooth; 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.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.RemoteException; import android.provider.Settings; import android.util.Log; import java.util.List; import java.util.ArrayList; class BluetoothManagerService extends IBluetoothManager.Stub { private static final String TAG = "BluetoothManagerService"; private static final boolean DBG = true; private static final boolean ALWAYS_SYNC_NAME_ADDRESS=true; //If true, always load name and address 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_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 private static final int MESSAGE_ENABLE = 1; private static final int MESSAGE_DISABLE = 2; private static final int MESSAGE_AIRPLANE_MODE_OFF=10; private static final int MESSAGE_AIRPLANE_MODE_ON=11; 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_BLUETOOTH_ON = 50; private static final int MESSAGE_BLUETOOTH_OFF = 51; 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 MAX_SAVE_RETRIES=3; private final Context mContext; private String mAddress; private String mName; private ContentResolver mContentResolver; private List mCallbacks; private List mStateChangeCallbacks; private IBluetooth mBluetooth; private boolean mBinding; private boolean mUnbinding; private void registerForAirplaneMode(IntentFilter filter) { final ContentResolver resolver = mContext.getContentResolver(); final String airplaneModeRadios = Settings.System.getString(resolver, Settings.System.AIRPLANE_MODE_RADIOS); final String toggleableRadios = Settings.System.getString(resolver, Settings.System.AIRPLANE_MODE_TOGGLEABLE_RADIOS); boolean mIsAirplaneSensitive = airplaneModeRadios == null ? true : airplaneModeRadios.contains(Settings.System.RADIO_BLUETOOTH); if (mIsAirplaneSensitive) { filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED); } } private BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if(BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) { int state= intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1); if (state == BluetoothAdapter.STATE_OFF) { Message msg = mHandler.obtainMessage(MESSAGE_BLUETOOTH_OFF); mHandler.sendMessage(msg); } else if (state == BluetoothAdapter.STATE_ON) { Message msg = mHandler.obtainMessage(MESSAGE_BLUETOOTH_ON); mHandler.sendMessage(msg); } } else if (BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED.equals(action)) { String newName = intent.getStringExtra(BluetoothAdapter.EXTRA_LOCAL_NAME); Log.d(TAG, "Bluetooth Adapter name changed to " + newName); if (newName != null) { storeNameAndAddress(newName, null); } } else if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(action)) { if (isAirplaneModeOn()) { Message msg = mHandler.obtainMessage(MESSAGE_AIRPLANE_MODE_ON); msg.arg1=0; mHandler.sendMessage(msg); } else { Message msg = mHandler.obtainMessage(MESSAGE_AIRPLANE_MODE_OFF); msg.arg1=0; mHandler.sendMessage(msg); } } } }; BluetoothManagerService(Context context) { mContext = context; mBluetooth = null; mBinding = false; mUnbinding = false; mAddress = null; mName = null; mContentResolver = context.getContentResolver(); mCallbacks = new ArrayList(); mStateChangeCallbacks = new ArrayList(); IntentFilter mFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); mFilter.addAction(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED); registerForAirplaneMode(mFilter); mContext.registerReceiver(mReceiver, mFilter); boolean airplaneModeOn = isAirplaneModeOn(); boolean bluetoothOn = isBluetoothPersistedStateOn(); loadStoredNameAndAddress(); if (DBG) Log.d(TAG, "airplaneModeOn: " + airplaneModeOn + " bluetoothOn: " + bluetoothOn); if (!airplaneModeOn && bluetoothOn) { //Enable if (DBG) Log.d(TAG, "Auto-enabling Bluetooth."); enable(); } else if (ALWAYS_SYNC_NAME_ADDRESS || !isNameAndAddressSet()) { //Sync the Bluetooth name and address from the Bluetooth Adapter if (DBG) Log.d(TAG,"Retrieving Bluetooth Adapter name and address..."); getNameAndAddress(); } } /** * Returns true if airplane mode is currently on */ private final boolean isAirplaneModeOn() { return Settings.System.getInt(mContext.getContentResolver(), Settings.System.AIRPLANE_MODE_ON, 0) == 1; } /** * Returns true if the Bluetooth saved state is "on" */ private final boolean isBluetoothPersistedStateOn() { return Settings.Secure.getInt(mContentResolver, Settings.Secure.BLUETOOTH_ON, 0) ==1; } /** * Save the Bluetooth on/off state * */ private void persistBluetoothSetting(boolean setOn) { Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.BLUETOOTH_ON, setOn ? 1 : 0); } /** * 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"); mName = Settings.Secure.getString(mContentResolver, SECURE_SETTINGS_BLUETOOTH_NAME); mAddress = Settings.Secure.getString(mContentResolver, SECURE_SETTINGS_BLUETOOTH_ADDRESS); if (mName == null || mAddress == null) { if (DBG) Log.d(TAG, "Name or address not cached..."); } } /** * 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)); } } public IBluetooth registerAdapter(IBluetoothManagerCallback callback){ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 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() { synchronized(mConnection) { try { return (mBluetooth != null && mBluetooth.isEnabled()); } catch (RemoteException e) { Log.e(TAG, "isEnabled()", e); } } return false; } private boolean isConnected() { return mBluetooth != null; } public void getNameAndAddress() { if (DBG) { Log.d(TAG,"getNameAndAddress(): mBluetooth = " + (mBluetooth==null?"null":mBluetooth) + " mBinding = " + mBinding + " isConnected = " + isConnected()); } synchronized(mConnection) { if (mBinding) return ; if (!isConnected()) mBinding = true; } Message msg = mHandler.obtainMessage(MESSAGE_GET_NAME_AND_ADDRESS); mHandler.sendMessage(msg); } public boolean enable() { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission"); if (DBG) { Log.d(TAG,"enable(): mBluetooth =" + (mBluetooth==null?"null":mBluetooth) + " mBinding = " + mBinding + " isConnected = " + isConnected()); } synchronized(mConnection) { //if (mBluetooth != null) return false; if (mBinding) { Log.w(TAG,"enable(): binding in progress. Returning.."); return true; } if (!isConnected()) mBinding = true; } Message msg = mHandler.obtainMessage(MESSAGE_ENABLE); msg.arg1=1; //persist mHandler.sendMessage(msg); return true; } public boolean disable(boolean persist) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permissicacheNameAndAddresson"); if (DBG) { Log.d(TAG,"disable(): mBluetooth = " + (mBluetooth==null?"null":mBluetooth) + " mBinding = " + mBinding + " isConnected = " + isConnected());} synchronized(mConnection) { if (mBluetooth == null) return false; } Message msg = mHandler.obtainMessage(MESSAGE_DISABLE); msg.arg1=(persist?1:0); mHandler.sendMessage(msg); return true; } public void unbindAndFinish() { if (DBG) { Log.d(TAG,"unbindAndFinish(): " + (mBluetooth==null?"null":mBluetooth) + " mBinding = " + mBinding + " isConnected = " + isConnected()); } synchronized (mConnection) { if (mUnbinding) return; mUnbinding = true; if (isConnected()) { if (DBG) Log.d(TAG, "Sending unbind request."); mContext.unbindService(mConnection); mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED)); } else { mUnbinding=false; } } } public String getAddress() { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission"); 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); } } } return mAddress; } public String getName() { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission"); 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); } } } 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: connected to AdapterService"); Message msg = mHandler.obtainMessage(MESSAGE_BLUETOOTH_SERVICE_CONNECTED); msg.obj = service; mHandler.sendMessage(msg); } public void onServiceDisconnected(ComponentName className) { // Called if we unexpected disconnected. if (DBG) Log.d(TAG, "BluetoothServiceConnection: disconnected from AdapterService"); Message msg = mHandler.obtainMessage(MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED); mHandler.sendMessage(msg); } } private BluetoothServiceConnection mConnection = new BluetoothServiceConnection(); private final Handler mHandler = new Handler() { @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"); if (mBluetooth == null) { //Start bind request if (!isConnected()) { 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 (!mContext.bindService(i, mConnection, Context.BIND_AUTO_CREATE)) { mHandler.removeMessages(MESSAGE_TIMEOUT_BIND); Log.e(TAG, "fail to bind to: " + IBluetooth.class.getName()); } } } else { Message saveMsg= mHandler.obtainMessage(MESSAGE_SAVE_NAME_AND_ADDRESS); mHandler.sendMessage(saveMsg); } } break; case MESSAGE_SAVE_NAME_AND_ADDRESS: { if (DBG) Log.d(TAG,"MESSAGE_SAVE_NAME_AND_ADDRESS"); 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); Intent i = new Intent(IBluetooth.class.getName()); i.putExtra(EXTRA_ACTION, ACTION_SERVICE_STATE_CHANGED); i.putExtra(BluetoothAdapter.EXTRA_STATE,BluetoothAdapter.STATE_OFF); mContext.startService(i); unbindAndFinish(); } 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"); unbindAndFinish(); } } } } break; case MESSAGE_AIRPLANE_MODE_OFF: { if (DBG) Log.d(TAG,"MESSAGE_AIRPLANE_MODE_OFF"); //Check if we should turn on bluetooth if (!isBluetoothPersistedStateOn()) { if (DBG)Log.d(TAG, "Bluetooth persisted state is off. Not turning on Bluetooth."); return; } //Fall through to MESSAGE_ENABLE } case MESSAGE_ENABLE: { if (DBG) { Log.d(TAG, "MESSAGE_ENABLE: mBluetooth = " + mBluetooth + " isConnected = " + isConnected()); } boolean persist = (1==msg.arg1); if (persist) { persistBluetoothSetting(true); } if (mBluetooth == null) { //Start bind request if (!isConnected()) { //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()); i.putExtra(EXTRA_ACTION, ACTION_SERVICE_STATE_CHANGED); i.putExtra(BluetoothAdapter.EXTRA_STATE,BluetoothAdapter.STATE_ON); mContext.startService(i); mConnection.setGetNameAddressOnly(false); if (!mContext.bindService(i, mConnection,Context.BIND_AUTO_CREATE)) { mHandler.removeMessages(MESSAGE_TIMEOUT_BIND); Log.e(TAG, "Fail to bind to: " + IBluetooth.class.getName()); } } } else { //Check if name and address is loaded if not get it first. if (ALWAYS_SYNC_NAME_ADDRESS || !isNameAndAddressSet()) { try { if (DBG) Log.d(TAG,"Getting and storing Bluetooth name and address prior to enable."); storeNameAndAddress(mBluetooth.getName(),mBluetooth.getAddress()); } catch (RemoteException e) {Log.e(TAG, "", e);}; } Intent i = new Intent(IBluetooth.class.getName()); i.putExtra(EXTRA_ACTION, ACTION_SERVICE_STATE_CHANGED); i.putExtra(BluetoothAdapter.EXTRA_STATE,BluetoothAdapter.STATE_ON); mContext.startService(i); } // TODO(BT) what if service failed to start: // [fc] fixed: watch for bind timeout and handle accordingly // TODO(BT) persist the setting depending on argument // [fc]: let AdapterServiceHandle } break; case MESSAGE_AIRPLANE_MODE_ON:; if (DBG) { Log.d(TAG, "MESSAGE_AIRPLANE_MODE_ON: mBluetooth = " + mBluetooth + " isConnected = " + isConnected()); //Fall through to MESSAGE_DISABLE } case MESSAGE_DISABLE: if (mBluetooth != null ) { boolean persist = (1==msg.arg1); if (persist) { persistBluetoothSetting(false); } mConnection.setGetNameAddressOnly(false); if (DBG) Log.d(TAG,"Sending off request."); Intent i = new Intent(IBluetooth.class.getName()); i.putExtra(EXTRA_ACTION, ACTION_SERVICE_STATE_CHANGED); i.putExtra(BluetoothAdapter.EXTRA_STATE,BluetoothAdapter.STATE_OFF); mContext.startService(i); } // TODO(BT) what if service failed to stop: // [fc] fixed: watch for disable event and unbind accordingly // TODO(BT) persist the setting depending on argument // [fc]: let AdapterServiceHandle break; case MESSAGE_REGISTER_ADAPTER: { IBluetoothManagerCallback callback = (IBluetoothManagerCallback) msg.obj; mCallbacks.add(callback); } break; case MESSAGE_UNREGISTER_ADAPTER: { IBluetoothManagerCallback callback = (IBluetoothManagerCallback) msg.obj; mCallbacks.remove(callback); } break; case MESSAGE_REGISTER_STATE_CHANGE_CALLBACK: { IBluetoothStateChangeCallback callback = (IBluetoothStateChangeCallback) msg.obj; mStateChangeCallbacks.add(callback); } break; case MESSAGE_UNREGISTER_STATE_CHANGE_CALLBACK: { IBluetoothStateChangeCallback callback = (IBluetoothStateChangeCallback) msg.obj; mStateChangeCallbacks.remove(callback); } break; case MESSAGE_BLUETOOTH_SERVICE_CONNECTED: { if (DBG) Log.d(TAG,"MESSAGE_BLUETOOTH_SERVICE_CONNECTED"); //Remove timeout mHandler.removeMessages(MESSAGE_TIMEOUT_BIND); IBinder service = (IBinder) msg.obj; synchronized(mConnection) { mBinding = false; mBluetooth = IBluetooth.Stub.asInterface(service); } if (mConnection.isGetNameAddressOnly()) { //Request GET NAME AND ADDRESS Message getMsg = mHandler.obtainMessage(MESSAGE_GET_NAME_AND_ADDRESS); mHandler.sendMessage(getMsg); return; } try { for (IBluetoothManagerCallback callback : mCallbacks) { callback.onBluetoothServiceUp(mBluetooth); } } catch (RemoteException e) { Log.e(TAG, "", e); } } break; case MESSAGE_TIMEOUT_BIND: { Log.e(TAG, "MESSAGE_TIMEOUT_BIND"); synchronized(mConnection) { mBinding = false; } } break; case MESSAGE_BLUETOOTH_ON: { if (DBG) Log.d(TAG, "MESSAGE_BLUETOOTH_ON"); try { for (IBluetoothStateChangeCallback callback : mStateChangeCallbacks) { callback.onBluetoothStateChange(true); } } catch (RemoteException e) { Log.e(TAG, "", e); } } break; case MESSAGE_BLUETOOTH_OFF: { if (DBG) Log.d(TAG, "MESSAGE_BLUETOOTH_OFF"); try { for (IBluetoothStateChangeCallback callback : mStateChangeCallbacks) { callback.onBluetoothStateChange(false); } } catch (RemoteException e) { Log.e(TAG, "", e); } unbindAndFinish(); } break; case MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED: { if (DBG) Log.d(TAG, "MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED"); boolean isUnexpectedDisconnect = false; synchronized(mConnection) { mBluetooth = null; if (mUnbinding) { mUnbinding = false; } else { isUnexpectedDisconnect = true; } } if (!mConnection.isGetNameAddressOnly()) { if (DBG) Log.d(TAG,"MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED: Calling onBluetoothSerivceDown callbacks"); try { for (IBluetoothManagerCallback callback : mCallbacks) { callback.onBluetoothServiceDown(); } } catch (RemoteException e) { Log.e(TAG, "", e); } } } break; case MESSAGE_TIMEOUT_UNBIND: { Log.e(TAG, "MESSAGE_TIMEOUT_UNBIND"); synchronized(mConnection) { mUnbinding = false; } } break; } } }; }