/* * 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. */ /** * @hide */ package com.android.bluetooth.btservice; import android.app.Application; import android.app.Service; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.bluetooth.IBluetooth; import android.bluetooth.IBluetoothCallback; import android.bluetooth.IBluetoothManager; import android.bluetooth.IBluetoothManagerCallback; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.ParcelUuid; import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.provider.Settings; import android.util.Log; import android.util.Pair; import com.android.bluetooth.a2dp.A2dpService; import com.android.bluetooth.hid.HidService; import com.android.bluetooth.hfp.HeadsetService; import com.android.bluetooth.hdp.HealthService; import com.android.bluetooth.pan.PanService; import com.android.bluetooth.R; import com.android.bluetooth.Utils; import com.android.bluetooth.btservice.RemoteDevices.DeviceProperties; import java.io.FileDescriptor; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Set; import java.util.Map; import java.util.Iterator; import java.util.Map.Entry; import java.util.List; import android.content.pm.PackageManager; import android.os.ServiceManager; public class AdapterService extends Service { private static final String TAG = "BluetoothAdapterService"; private static final boolean DBG = false; private static final boolean TRACE_REF = true; //For Debugging only private static int sRefCount=0; public static final String ACTION_LOAD_ADAPTER_PROPERTIES="com.android.bluetooth.btservice.action.LOAD_ADAPTER_PROPERTIES"; public static final String ACTION_SERVICE_STATE_CHANGED="com.android.bluetooth.btservice.action.STATE_CHANGED"; public static final String EXTRA_ACTION="action"; public static final int PROFILE_CONN_CONNECTED = 1; public static final int PROFILE_CONN_REJECTED = 2; static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; private static final int ADAPTER_SERVICE_TYPE=Service.START_STICKY; static { classInitNative(); } private static AdapterService sAdapterService; public static synchronized AdapterService getAdapterService(){ if (sAdapterService != null && !sAdapterService.mCleaningUp) { if (DBG) Log.d(TAG, "getAdapterService(): returning " + sAdapterService); return sAdapterService; } if (DBG) { if (sAdapterService == null) { Log.d(TAG, "getAdapterService(): service not available"); } else if (sAdapterService.mCleaningUp) { Log.d(TAG,"getAdapterService(): service is cleaning up"); } } return null; } private static synchronized void setAdapterService(AdapterService instance) { if (instance != null && !instance.mCleaningUp) { if (DBG) Log.d(TAG, "setAdapterService(): set to: " + sAdapterService); sAdapterService = instance; } else { if (DBG) { if (sAdapterService == null) { Log.d(TAG, "setAdapterService(): service not available"); } else if (sAdapterService.mCleaningUp) { Log.d(TAG,"setAdapterService(): service is cleaning up"); } } } } private static synchronized void clearAdapterService() { sAdapterService = null; } private AdapterProperties mAdapterProperties; private AdapterState mAdapterStateMachine; private BondStateMachine mBondStateMachine; private JniCallbacks mJniCallbacks; private RemoteDevices mRemoteDevices; private boolean mProfilesStarted; private boolean mNativeAvailable; private boolean mCleaningUp; private HashMap mProfileServicesState = new HashMap(); private RemoteCallbackList mCallbacks;//Only BluetoothManagerService should be registered private int mCurrentRequestId; private boolean mQuietmode = false; public AdapterService() { super(); if (TRACE_REF) { synchronized (AdapterService.class) { sRefCount++; Log.d(TAG, "REFCOUNT: CREATED. INSTANCE_COUNT" + sRefCount); } } } public void onProfileConnectionStateChanged(BluetoothDevice device, int profileId, int newState, int prevState) { Message m = mHandler.obtainMessage(MESSAGE_PROFILE_CONNECTION_STATE_CHANGED); m.obj = device; m.arg1 = profileId; m.arg2 = newState; Bundle b = new Bundle(1); b.putInt("prevState", prevState); m.setData(b); mHandler.sendMessage(m); } private void processProfileStateChanged(BluetoothDevice device, int profileId, int newState, int prevState) { if (((profileId == BluetoothProfile.A2DP) ||(profileId == BluetoothProfile.HEADSET)) && (newState == BluetoothProfile.STATE_CONNECTED)){ if (DBG) debugLog( "Profile connected. Schedule missing profile connection if any"); connectOtherProfile(device, PROFILE_CONN_CONNECTED); setProfileAutoConnectionPriority(device, profileId); } IBluetooth.Stub binder = mBinder; if (binder != null) { try { binder.sendConnectionStateChange(device, profileId, newState,prevState); } catch (RemoteException re) { Log.e(TAG, "",re); } } } public void onProfileServiceStateChanged(String serviceName, int state) { Message m = mHandler.obtainMessage(MESSAGE_PROFILE_SERVICE_STATE_CHANGED); m.obj=serviceName; m.arg1 = state; mHandler.sendMessage(m); } private void processProfileServiceStateChanged(String serviceName, int state) { boolean doUpdate=false; boolean isTurningOn; boolean isTurningOff; synchronized (mProfileServicesState) { Integer prevState = mProfileServicesState.get(serviceName); if (prevState != null && prevState != state) { mProfileServicesState.put(serviceName,state); doUpdate=true; } } if (DBG) Log.d(TAG,"onProfileServiceStateChange: serviceName=" + serviceName + ", state = " + state +", doUpdate = " + doUpdate); if (!doUpdate) { return; } synchronized (mAdapterStateMachine) { isTurningOff = mAdapterStateMachine.isTurningOff(); isTurningOn = mAdapterStateMachine.isTurningOn(); } if (isTurningOff) { //Process stop or disable pending //Check if all services are stopped if so, do cleanup //if (DBG) Log.d(TAG,"Checking if all profiles are stopped..."); synchronized (mProfileServicesState) { Iterator> i = mProfileServicesState.entrySet().iterator(); while (i.hasNext()) { Map.Entry entry = i.next(); if (BluetoothAdapter.STATE_OFF != entry.getValue()) { Log.d(TAG, "Profile still running: " + entry.getKey()); return; } } } if (DBG) Log.d(TAG, "All profile services stopped..."); //Send message to state machine mProfilesStarted=false; mAdapterStateMachine.sendMessage(mAdapterStateMachine.obtainMessage(AdapterState.STOPPED)); } else if (isTurningOn) { //Process start pending //Check if all services are started if so, update state //if (DBG) Log.d(TAG,"Checking if all profiles are running..."); synchronized (mProfileServicesState) { Iterator> i = mProfileServicesState.entrySet().iterator(); while (i.hasNext()) { Map.Entry entry = i.next(); if (BluetoothAdapter.STATE_ON != entry.getValue()) { Log.d(TAG, "Profile still not running:" + entry.getKey()); return; } } } if (DBG) Log.d(TAG, "All profile services started."); mProfilesStarted=true; //Send message to state machine mAdapterStateMachine.sendMessage(mAdapterStateMachine.obtainMessage(AdapterState.STARTED)); } } @Override public void onCreate() { super.onCreate(); if (DBG) debugLog("onCreate"); mBinder = new AdapterServiceBinder(this); mAdapterProperties = new AdapterProperties(this); mAdapterStateMachine = AdapterState.make(this, mAdapterProperties); mJniCallbacks = new JniCallbacks(mAdapterStateMachine, mAdapterProperties); initNative(); mNativeAvailable=true; mCallbacks = new RemoteCallbackList(); //Load the name and address getAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_BDADDR); getAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_BDNAME); } @Override public IBinder onBind(Intent intent) { if (DBG) debugLog("onBind"); return mBinder; } public boolean onUnbind(Intent intent) { if (DBG) debugLog("onUnbind, calling cleanup"); cleanup(); return super.onUnbind(intent); } public void onDestroy() { debugLog("****onDestroy()********"); } void processStart() { if (DBG) debugLog("processStart()"); Class[] supportedProfileServices = Config.getSupportedProfiles(); //Initialize data objects for (int i=0; i < supportedProfileServices.length;i++) { mProfileServicesState.put(supportedProfileServices[i].getName(),BluetoothAdapter.STATE_OFF); } mRemoteDevices = new RemoteDevices(this); mAdapterProperties.init(mRemoteDevices); if (DBG) {debugLog("processStart(): Make Bond State Machine");} mBondStateMachine = BondStateMachine.make(this, mAdapterProperties, mRemoteDevices); mJniCallbacks.init(mBondStateMachine,mRemoteDevices); //FIXME: Set static instance here??? setAdapterService(this); //Start profile services if (!mProfilesStarted && supportedProfileServices.length >0) { //Startup all profile services setProfileServiceState(supportedProfileServices,BluetoothAdapter.STATE_ON); }else { if (DBG) {debugLog("processStart(): Profile Services alreay started");} mAdapterStateMachine.sendMessage(mAdapterStateMachine.obtainMessage(AdapterState.STARTED)); } } void startBluetoothDisable() { mAdapterStateMachine.sendMessage(mAdapterStateMachine.obtainMessage(AdapterState.BEGIN_DISABLE)); } boolean stopProfileServices() { Class[] supportedProfileServices = Config.getSupportedProfiles(); if (mProfilesStarted && supportedProfileServices.length>0) { setProfileServiceState(supportedProfileServices,BluetoothAdapter.STATE_OFF); return true; } else { if (DBG) {debugLog("stopProfileServices(): No profiles services to stop or already stopped.");} return false; } } void updateAdapterState(int prevState, int newState){ if (mCallbacks !=null) { int n=mCallbacks.beginBroadcast(); Log.d(TAG,"Broadcasting updateAdapterState() to " + n + " receivers."); for (int i=0; i a2dpConnDevList= a2dpService.getConnectedDevices(); List hfConnDevList= hsService.getConnectedDevices(); // Check if the device is in disconnected state and if so return // We ned to connect other profile only if one of the profile is still in connected state // This is required to avoide a race condition in which profiles would // automaticlly connect if the disconnection is initiated within 6 seconds of connection //First profile connection being rejected is an exception if((hfConnDevList.isEmpty() && a2dpConnDevList.isEmpty())&& (PROFILE_CONN_CONNECTED == firstProfileStatus)){ return; } if((hfConnDevList.isEmpty()) && (hsService.getPriority(device) >= BluetoothProfile.PRIORITY_ON)){ hsService.connect(device); } else if((a2dpConnDevList.isEmpty()) && (a2dpService.getPriority(device) >= BluetoothProfile.PRIORITY_ON)){ a2dpService.connect(device); } } private void adjustOtherHeadsetPriorities(HeadsetService hsService, BluetoothDevice connectedDevice) { for (BluetoothDevice device : getBondedDevices()) { if (hsService.getPriority(device) >= BluetoothProfile.PRIORITY_AUTO_CONNECT && !device.equals(connectedDevice)) { hsService.setPriority(device, BluetoothProfile.PRIORITY_ON); } } } private void adjustOtherSinkPriorities(A2dpService a2dpService, BluetoothDevice connectedDevice) { for (BluetoothDevice device : getBondedDevices()) { if (a2dpService.getPriority(device) >= BluetoothProfile.PRIORITY_AUTO_CONNECT && !device.equals(connectedDevice)) { a2dpService.setPriority(device, BluetoothProfile.PRIORITY_ON); } } } void setProfileAutoConnectionPriority (BluetoothDevice device, int profileId){ if (profileId == BluetoothProfile.HEADSET) { HeadsetService hsService = HeadsetService.getHeadsetService(); if ((hsService != null) && (BluetoothProfile.PRIORITY_AUTO_CONNECT != hsService.getPriority(device))){ adjustOtherHeadsetPriorities(hsService, device); hsService.setPriority(device,BluetoothProfile.PRIORITY_AUTO_CONNECT); } } else if (profileId == BluetoothProfile.A2DP) { A2dpService a2dpService = A2dpService.getA2dpService(); if ((a2dpService != null) && (BluetoothProfile.PRIORITY_AUTO_CONNECT != a2dpService.getPriority(device))){ adjustOtherSinkPriorities(a2dpService, device); a2dpService.setPriority(device,BluetoothProfile.PRIORITY_AUTO_CONNECT); } } } boolean cancelBondProcess(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission"); byte[] addr = Utils.getBytesFromAddress(device.getAddress()); return cancelBondNative(addr); } boolean removeBond(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission"); DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device); if (deviceProp == null || deviceProp.getBondState() != BluetoothDevice.BOND_BONDED) { return false; } Message msg = mBondStateMachine.obtainMessage(BondStateMachine.REMOVE_BOND); msg.obj = device; mBondStateMachine.sendMessage(msg); return true; } int getBondState(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device); if (deviceProp == null) { return BluetoothDevice.BOND_NONE; } return deviceProp.getBondState(); } String getRemoteName(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device); if (deviceProp == null) return null; return deviceProp.getName(); } int getRemoteType(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device); if (deviceProp == null) return BluetoothDevice.DEVICE_TYPE_UNKNOWN; return deviceProp.getDeviceType(); } String getRemoteAlias(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device); if (deviceProp == null) return null; return deviceProp.getAlias(); } boolean setRemoteAlias(BluetoothDevice device, String name) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device); if (deviceProp == null) return false; deviceProp.setAlias(name); return true; } int getRemoteClass(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device); if (deviceProp == null) return 0; return deviceProp.getBluetoothClass(); } ParcelUuid[] getRemoteUuids(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device); if (deviceProp == null) return null; return deviceProp.getUuids(); } boolean fetchRemoteUuids(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); mRemoteDevices.fetchUuids(device); return true; } boolean setPin(BluetoothDevice device, boolean accept, int len, byte[] pinCode) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device); if (deviceProp == null || deviceProp.getBondState() != BluetoothDevice.BOND_BONDING) { return false; } byte[] addr = Utils.getBytesFromAddress(device.getAddress()); return pinReplyNative(addr, accept, len, pinCode); } boolean setPasskey(BluetoothDevice device, boolean accept, int len, byte[] passkey) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device); if (deviceProp == null || deviceProp.getBondState() != BluetoothDevice.BOND_BONDING) { return false; } byte[] addr = Utils.getBytesFromAddress(device.getAddress()); return sspReplyNative(addr, AbstractionLayer.BT_SSP_VARIANT_PASSKEY_ENTRY, accept, Utils.byteArrayToInt(passkey)); } boolean setPairingConfirmation(BluetoothDevice device, boolean accept) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device); if (deviceProp == null || deviceProp.getBondState() != BluetoothDevice.BOND_BONDING) { return false; } byte[] addr = Utils.getBytesFromAddress(device.getAddress()); return sspReplyNative(addr, AbstractionLayer.BT_SSP_VARIANT_PASSKEY_CONFIRMATION, accept, 0); } void sendConnectionStateChange(BluetoothDevice device, int profile, int state, int prevState) { // TODO(BT) permission check? // Since this is a binder call check if Bluetooth is on still if (getState() == BluetoothAdapter.STATE_OFF) return; mAdapterProperties.sendConnectionStateChange(device, profile, state, prevState); } ParcelFileDescriptor connectSocket(BluetoothDevice device, int type, ParcelUuid uuid, int port, int flag) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); int fd = connectSocketNative(Utils.getBytesFromAddress(device.getAddress()), type, Utils.uuidToByteArray(uuid), port, flag); if (fd < 0) { errorLog("Failed to connect socket"); return null; } return ParcelFileDescriptor.adoptFd(fd); } ParcelFileDescriptor createSocketChannel(int type, String serviceName, ParcelUuid uuid, int port, int flag) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); int fd = createSocketChannelNative(type, serviceName, Utils.uuidToByteArray(uuid), port, flag); if (fd < 0) { errorLog("Failed to create socket channel"); return null; } return ParcelFileDescriptor.adoptFd(fd); } void registerCallback(IBluetoothCallback cb) { mCallbacks.register(cb); } void unregisterCallback(IBluetoothCallback cb) { mCallbacks.unregister(cb); } private static int convertScanModeToHal(int mode) { switch (mode) { case BluetoothAdapter.SCAN_MODE_NONE: return AbstractionLayer.BT_SCAN_MODE_NONE; case BluetoothAdapter.SCAN_MODE_CONNECTABLE: return AbstractionLayer.BT_SCAN_MODE_CONNECTABLE; case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE: return AbstractionLayer.BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE; } // errorLog("Incorrect scan mode in convertScanModeToHal"); return -1; } static int convertScanModeFromHal(int mode) { switch (mode) { case AbstractionLayer.BT_SCAN_MODE_NONE: return BluetoothAdapter.SCAN_MODE_NONE; case AbstractionLayer.BT_SCAN_MODE_CONNECTABLE: return BluetoothAdapter.SCAN_MODE_CONNECTABLE; case AbstractionLayer.BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE: return BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE; } //errorLog("Incorrect scan mode in convertScanModeFromHal"); return -1; } private void debugLog(String msg) { Log.d(TAG +"(" +hashCode()+")", msg); } private void errorLog(String msg) { Log.e(TAG +"(" +hashCode()+")", msg); } private native static void classInitNative(); private native boolean initNative(); private native void cleanupNative(); /*package*/ native boolean enableNative(); /*package*/ native boolean disableNative(); /*package*/ native boolean setAdapterPropertyNative(int type, byte[] val); /*package*/ native boolean getAdapterPropertiesNative(); /*package*/ native boolean getAdapterPropertyNative(int type); /*package*/ native boolean setAdapterPropertyNative(int type); /*package*/ native boolean setDevicePropertyNative(byte[] address, int type, byte[] val); /*package*/ native boolean getDevicePropertyNative(byte[] address, int type); /*package*/ native boolean createBondNative(byte[] address); /*package*/ native boolean removeBondNative(byte[] address); /*package*/ native boolean cancelBondNative(byte[] address); private native boolean startDiscoveryNative(); private native boolean cancelDiscoveryNative(); private native boolean pinReplyNative(byte[] address, boolean accept, int len, byte[] pin); private native boolean sspReplyNative(byte[] address, int type, boolean accept, int passkey); /*package*/ native boolean getRemoteServicesNative(byte[] address); // TODO(BT) move this to ../btsock dir private native int connectSocketNative(byte[] address, int type, byte[] uuid, int port, int flag); private native int createSocketChannelNative(int type, String serviceName, byte[] uuid, int port, int flag); protected void finalize() { cleanup(); if (TRACE_REF) { synchronized (AdapterService.class) { sRefCount--; Log.d(TAG, "REFCOUNT: FINALIZED. INSTANCE_COUNT= " + sRefCount); } } } }