| /* |
| * 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.bluetooth.hdp; |
| |
| import android.bluetooth.BluetoothDevice; |
| import android.bluetooth.BluetoothHealth; |
| import android.bluetooth.BluetoothHealthAppConfiguration; |
| import android.bluetooth.BluetoothProfile; |
| import android.bluetooth.IBluetooth; |
| import android.bluetooth.IBluetoothHealth; |
| import android.bluetooth.IBluetoothHealthCallback; |
| import android.content.Intent; |
| import android.content.pm.PackageManager; |
| import android.os.Handler; |
| import android.os.HandlerThread; |
| import android.os.IBinder; |
| import android.os.IBinder.DeathRecipient; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.os.ParcelFileDescriptor; |
| import android.os.RemoteException; |
| import android.os.ServiceManager; |
| import android.util.Log; |
| import com.android.bluetooth.btservice.ProfileService; |
| import com.android.bluetooth.btservice.ProfileService.IProfileServiceBinder; |
| import com.android.bluetooth.Utils; |
| import java.io.FileDescriptor; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.NoSuchElementException; |
| |
| |
| /** |
| * Provides Bluetooth Health Device profile, as a service in |
| * the Bluetooth application. |
| * @hide |
| */ |
| public class HealthService extends ProfileService { |
| private static final boolean DBG = false; |
| private static final String TAG="HealthService"; |
| |
| private List<HealthChannel> mHealthChannels; |
| private Map <BluetoothHealthAppConfiguration, AppInfo> mApps; |
| private Map <BluetoothDevice, Integer> mHealthDevices; |
| private boolean mNativeAvailable; |
| private HealthServiceMessageHandler mHandler; |
| private static final int MESSAGE_REGISTER_APPLICATION = 1; |
| private static final int MESSAGE_UNREGISTER_APPLICATION = 2; |
| private static final int MESSAGE_CONNECT_CHANNEL = 3; |
| private static final int MESSAGE_DISCONNECT_CHANNEL = 4; |
| private static final int MESSAGE_APP_REGISTRATION_CALLBACK = 11; |
| private static final int MESSAGE_CHANNEL_STATE_CALLBACK = 12; |
| |
| static { |
| classInitNative(); |
| } |
| |
| protected String getName() { |
| return TAG; |
| } |
| |
| protected IProfileServiceBinder initBinder() { |
| return new BluetoothHealthBinder(this); |
| } |
| |
| protected boolean start() { |
| mHealthChannels = Collections.synchronizedList(new ArrayList<HealthChannel>()); |
| mApps = Collections.synchronizedMap(new HashMap<BluetoothHealthAppConfiguration, |
| AppInfo>()); |
| mHealthDevices = Collections.synchronizedMap(new HashMap<BluetoothDevice, Integer>()); |
| |
| HandlerThread thread = new HandlerThread("BluetoothHdpHandler"); |
| thread.start(); |
| Looper looper = thread.getLooper(); |
| mHandler = new HealthServiceMessageHandler(looper); |
| initializeNative(); |
| mNativeAvailable=true; |
| return true; |
| } |
| |
| protected boolean stop() { |
| mHandler.removeCallbacksAndMessages(null); |
| Looper looper = mHandler.getLooper(); |
| if (looper != null) { |
| looper.quit(); |
| } |
| cleanupApps(); |
| return true; |
| } |
| |
| private void cleanupApps(){ |
| for (Entry<BluetoothHealthAppConfiguration, AppInfo> entry : mApps.entrySet()) { |
| try{ |
| AppInfo appInfo = entry.getValue(); |
| appInfo.cleanup(); |
| mApps.remove(entry.getKey()); |
| }catch(Exception e){ |
| Log.e(TAG,"Failed to cleanup appInfo for appid ="+entry.getKey()); |
| } |
| } |
| } |
| protected boolean cleanup() { |
| mHandler = null; |
| //Cleanup native |
| if (mNativeAvailable) { |
| cleanupNative(); |
| mNativeAvailable=false; |
| } |
| if(mHealthChannels != null) { |
| mHealthChannels.clear(); |
| } |
| if(mHealthDevices != null) { |
| mHealthDevices.clear(); |
| } |
| if(mApps != null) { |
| mApps.clear(); |
| } |
| return true; |
| } |
| |
| private final class HealthServiceMessageHandler extends Handler { |
| private HealthServiceMessageHandler(Looper looper) { |
| super(looper); |
| } |
| |
| @Override |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| case MESSAGE_REGISTER_APPLICATION: |
| { |
| BluetoothHealthAppConfiguration appConfig = |
| (BluetoothHealthAppConfiguration) msg.obj; |
| AppInfo appInfo = mApps.get(appConfig); |
| if (appInfo == null) break; |
| int halRole = convertRoleToHal(appConfig.getRole()); |
| int halChannelType = convertChannelTypeToHal(appConfig.getChannelType()); |
| if (DBG) log("register datatype: " + appConfig.getDataType() + " role: " + |
| halRole + " name: " + appConfig.getName() + " channeltype: " + |
| halChannelType); |
| int appId = registerHealthAppNative(appConfig.getDataType(), halRole, |
| appConfig.getName(), halChannelType); |
| if (appId == -1) { |
| callStatusCallback(appConfig, |
| BluetoothHealth.APP_CONFIG_REGISTRATION_FAILURE); |
| appInfo.cleanup(); |
| mApps.remove(appConfig); |
| } else { |
| //link to death with a recipient object to implement binderDead() |
| appInfo.mRcpObj = new BluetoothHealthDeathRecipient(HealthService.this,appConfig); |
| IBinder binder = appInfo.mCallback.asBinder(); |
| try { |
| binder.linkToDeath(appInfo.mRcpObj,0); |
| } catch (RemoteException e) { |
| Log.e(TAG,"LinktoDeath Exception:"+e); |
| } |
| appInfo.mAppId = appId; |
| callStatusCallback(appConfig, |
| BluetoothHealth.APP_CONFIG_REGISTRATION_SUCCESS); |
| } |
| } |
| break; |
| case MESSAGE_UNREGISTER_APPLICATION: |
| { |
| BluetoothHealthAppConfiguration appConfig = |
| (BluetoothHealthAppConfiguration) msg.obj; |
| int appId = (mApps.get(appConfig)).mAppId; |
| if (!unregisterHealthAppNative(appId)) { |
| Log.e(TAG, "Failed to unregister application: id: " + appId); |
| callStatusCallback(appConfig, |
| BluetoothHealth.APP_CONFIG_UNREGISTRATION_FAILURE); |
| } |
| } |
| break; |
| case MESSAGE_CONNECT_CHANNEL: |
| { |
| HealthChannel chan = (HealthChannel) msg.obj; |
| byte[] devAddr = Utils.getByteAddress(chan.mDevice); |
| int appId = (mApps.get(chan.mConfig)).mAppId; |
| chan.mChannelId = connectChannelNative(devAddr, appId); |
| if (chan.mChannelId == -1) { |
| callHealthChannelCallback(chan.mConfig, chan.mDevice, |
| BluetoothHealth.STATE_CHANNEL_DISCONNECTING, |
| BluetoothHealth.STATE_CHANNEL_DISCONNECTED, |
| chan.mChannelFd, chan.mChannelId); |
| callHealthChannelCallback(chan.mConfig, chan.mDevice, |
| BluetoothHealth.STATE_CHANNEL_DISCONNECTED, |
| BluetoothHealth.STATE_CHANNEL_DISCONNECTING, |
| chan.mChannelFd, chan.mChannelId); |
| } |
| } |
| break; |
| case MESSAGE_DISCONNECT_CHANNEL: |
| { |
| HealthChannel chan = (HealthChannel) msg.obj; |
| if (!disconnectChannelNative(chan.mChannelId)) { |
| callHealthChannelCallback(chan.mConfig, chan.mDevice, |
| BluetoothHealth.STATE_CHANNEL_DISCONNECTING, |
| BluetoothHealth.STATE_CHANNEL_CONNECTED, |
| chan.mChannelFd, chan.mChannelId); |
| callHealthChannelCallback(chan.mConfig, chan.mDevice, |
| BluetoothHealth.STATE_CHANNEL_CONNECTED, |
| BluetoothHealth.STATE_CHANNEL_DISCONNECTING, |
| chan.mChannelFd, chan.mChannelId); |
| } |
| } |
| break; |
| case MESSAGE_APP_REGISTRATION_CALLBACK: |
| { |
| BluetoothHealthAppConfiguration appConfig = findAppConfigByAppId(msg.arg1); |
| if (appConfig == null) break; |
| |
| int regStatus = convertHalRegStatus(msg.arg2); |
| callStatusCallback(appConfig, regStatus); |
| if (regStatus == BluetoothHealth.APP_CONFIG_REGISTRATION_FAILURE || |
| regStatus == BluetoothHealth.APP_CONFIG_UNREGISTRATION_SUCCESS) { |
| //unlink to death once app is unregistered |
| AppInfo appInfo = mApps.get(appConfig); |
| appInfo.cleanup(); |
| mApps.remove(appConfig); |
| } |
| } |
| break; |
| case MESSAGE_CHANNEL_STATE_CALLBACK: |
| { |
| ChannelStateEvent channelStateEvent = (ChannelStateEvent) msg.obj; |
| HealthChannel chan = findChannelById(channelStateEvent.mChannelId); |
| int newState; |
| if (chan == null) { |
| // incoming connection |
| BluetoothHealthAppConfiguration appConfig = |
| findAppConfigByAppId(channelStateEvent.mAppId); |
| BluetoothDevice device = getDevice(channelStateEvent.mAddr); |
| chan = new HealthChannel(device, appConfig, appConfig.getChannelType()); |
| chan.mChannelId = channelStateEvent.mChannelId; |
| mHealthChannels.add(chan); |
| } |
| newState = convertHalChannelState(channelStateEvent.mState); |
| if (newState == BluetoothHealth.STATE_CHANNEL_CONNECTED) { |
| try { |
| chan.mChannelFd = ParcelFileDescriptor.dup(channelStateEvent.mFd); |
| } catch (IOException e) { |
| Log.e(TAG, "failed to dup ParcelFileDescriptor"); |
| break; |
| } |
| } |
| /*set the channel fd to null if channel state isnot equal to connected*/ |
| else{ |
| chan.mChannelFd = null; |
| } |
| callHealthChannelCallback(chan.mConfig, chan.mDevice, newState, |
| chan.mState, chan.mChannelFd, chan.mChannelId); |
| chan.mState = newState; |
| if (channelStateEvent.mState == CONN_STATE_DESTROYED) { |
| mHealthChannels.remove(chan); |
| } |
| } |
| break; |
| } |
| } |
| } |
| |
| //Handler for DeathReceipient |
| private static class BluetoothHealthDeathRecipient implements IBinder.DeathRecipient{ |
| private BluetoothHealthAppConfiguration mConfig; |
| private HealthService mService; |
| |
| public BluetoothHealthDeathRecipient(HealthService service, BluetoothHealthAppConfiguration config) { |
| mService = service; |
| mConfig = config; |
| } |
| |
| public void binderDied() { |
| Log.d(TAG,"Binder is dead."); |
| mService.unregisterAppConfiguration(mConfig); |
| } |
| |
| public void cleanup(){ |
| mService = null; |
| mConfig = null; |
| } |
| } |
| |
| /** |
| * Handlers for incoming service calls |
| */ |
| private static class BluetoothHealthBinder extends IBluetoothHealth.Stub implements IProfileServiceBinder { |
| private HealthService mService; |
| |
| public BluetoothHealthBinder(HealthService svc) { |
| mService = svc; |
| } |
| |
| public boolean cleanup() { |
| mService = null; |
| return true; |
| } |
| |
| private HealthService getService() { |
| if (!Utils.checkCaller()) { |
| Log.w(TAG,"Health call not allowed for non-active user"); |
| return null; |
| } |
| |
| if (mService != null && mService.isAvailable()) { |
| return mService; |
| } |
| return null; |
| } |
| |
| public boolean registerAppConfiguration(BluetoothHealthAppConfiguration config, |
| IBluetoothHealthCallback callback) { |
| HealthService service = getService(); |
| if (service == null) return false; |
| return service.registerAppConfiguration(config, callback); |
| } |
| |
| public boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) { |
| HealthService service = getService(); |
| if (service == null) return false; |
| return service.unregisterAppConfiguration(config); |
| } |
| |
| public boolean connectChannelToSource(BluetoothDevice device, |
| BluetoothHealthAppConfiguration config) { |
| HealthService service = getService(); |
| if (service == null) return false; |
| return service.connectChannelToSource(device, config); |
| } |
| |
| public boolean connectChannelToSink(BluetoothDevice device, |
| BluetoothHealthAppConfiguration config, int channelType) { |
| HealthService service = getService(); |
| if (service == null) return false; |
| return service.connectChannelToSink(device, config, channelType); |
| } |
| |
| public boolean disconnectChannel(BluetoothDevice device, |
| BluetoothHealthAppConfiguration config, int channelId) { |
| HealthService service = getService(); |
| if (service == null) return false; |
| return service.disconnectChannel(device, config, channelId); |
| } |
| |
| public ParcelFileDescriptor getMainChannelFd(BluetoothDevice device, |
| BluetoothHealthAppConfiguration config) { |
| HealthService service = getService(); |
| if (service == null) return null; |
| return service.getMainChannelFd(device, config); |
| } |
| |
| public int getHealthDeviceConnectionState(BluetoothDevice device) { |
| HealthService service = getService(); |
| if (service == null) return BluetoothHealth.STATE_DISCONNECTED; |
| return service.getHealthDeviceConnectionState(device); |
| } |
| |
| public List<BluetoothDevice> getConnectedHealthDevices() { |
| HealthService service = getService(); |
| if (service == null) return new ArrayList<BluetoothDevice> (0); |
| return service.getConnectedHealthDevices(); |
| } |
| |
| public List<BluetoothDevice> getHealthDevicesMatchingConnectionStates(int[] states) { |
| HealthService service = getService(); |
| if (service == null) return new ArrayList<BluetoothDevice> (0); |
| return service.getHealthDevicesMatchingConnectionStates(states); |
| } |
| }; |
| |
| boolean registerAppConfiguration(BluetoothHealthAppConfiguration config, |
| IBluetoothHealthCallback callback) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, |
| "Need BLUETOOTH permission"); |
| if (mApps.get(config) != null) { |
| if (DBG) Log.d(TAG, "Config has already been registered"); |
| return false; |
| } |
| mApps.put(config, new AppInfo(callback)); |
| Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_APPLICATION,config); |
| mHandler.sendMessage(msg); |
| return true; |
| } |
| |
| boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| if (mApps.get(config) == null) { |
| if (DBG) Log.d(TAG,"unregisterAppConfiguration: no app found"); |
| return false; |
| } |
| Message msg = mHandler.obtainMessage(MESSAGE_UNREGISTER_APPLICATION,config); |
| mHandler.sendMessage(msg); |
| return true; |
| } |
| |
| boolean connectChannelToSource(BluetoothDevice device, |
| BluetoothHealthAppConfiguration config) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| return connectChannel(device, config, BluetoothHealth.CHANNEL_TYPE_ANY); |
| } |
| |
| boolean connectChannelToSink(BluetoothDevice device, |
| BluetoothHealthAppConfiguration config, int channelType) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| return connectChannel(device, config, channelType); |
| } |
| |
| boolean disconnectChannel(BluetoothDevice device, |
| BluetoothHealthAppConfiguration config, int channelId) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| HealthChannel chan = findChannelById(channelId); |
| if (chan == null) { |
| if (DBG) Log.d(TAG,"disconnectChannel: no channel found"); |
| return false; |
| } |
| Message msg = mHandler.obtainMessage(MESSAGE_DISCONNECT_CHANNEL,chan); |
| mHandler.sendMessage(msg); |
| return true; |
| } |
| |
| ParcelFileDescriptor getMainChannelFd(BluetoothDevice device, |
| BluetoothHealthAppConfiguration config) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| HealthChannel healthChan = null; |
| for (HealthChannel chan: mHealthChannels) { |
| if (chan.mDevice.equals(device) && chan.mConfig.equals(config)) { |
| healthChan = chan; |
| } |
| } |
| if (healthChan == null) { |
| Log.e(TAG, "No channel found for device: " + device + " config: " + config); |
| return null; |
| } |
| return healthChan.mChannelFd; |
| } |
| |
| int getHealthDeviceConnectionState(BluetoothDevice device) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| return getConnectionState(device); |
| } |
| |
| List<BluetoothDevice> getConnectedHealthDevices() { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| List<BluetoothDevice> devices = lookupHealthDevicesMatchingStates( |
| new int[] {BluetoothHealth.STATE_CONNECTED}); |
| return devices; |
| } |
| |
| List<BluetoothDevice> getHealthDevicesMatchingConnectionStates(int[] states) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| List<BluetoothDevice> devices = lookupHealthDevicesMatchingStates(states); |
| return devices; |
| } |
| |
| private void onAppRegistrationState(int appId, int state) { |
| Message msg = mHandler.obtainMessage(MESSAGE_APP_REGISTRATION_CALLBACK); |
| msg.arg1 = appId; |
| msg.arg2 = state; |
| mHandler.sendMessage(msg); |
| } |
| |
| private void onChannelStateChanged(int appId, byte[] addr, int cfgIndex, |
| int channelId, int state, FileDescriptor pfd) { |
| Message msg = mHandler.obtainMessage(MESSAGE_CHANNEL_STATE_CALLBACK); |
| ChannelStateEvent channelStateEvent = new ChannelStateEvent(appId, addr, cfgIndex, |
| channelId, state, pfd); |
| msg.obj = channelStateEvent; |
| mHandler.sendMessage(msg); |
| } |
| |
| private String getStringChannelType(int type) { |
| if (type == BluetoothHealth.CHANNEL_TYPE_RELIABLE) { |
| return "Reliable"; |
| } else if (type == BluetoothHealth.CHANNEL_TYPE_STREAMING) { |
| return "Streaming"; |
| } else { |
| return "Any"; |
| } |
| } |
| |
| private void callStatusCallback(BluetoothHealthAppConfiguration config, int status) { |
| if (DBG) log ("Health Device Application: " + config + " State Change: status:" + status); |
| IBluetoothHealthCallback callback = (mApps.get(config)).mCallback; |
| if (callback == null) { |
| Log.e(TAG, "Callback object null"); |
| } |
| |
| try { |
| callback.onHealthAppConfigurationStatusChange(config, status); |
| } catch (RemoteException e) { |
| Log.e(TAG, "Remote Exception:" + e); |
| } |
| } |
| |
| private BluetoothHealthAppConfiguration findAppConfigByAppId(int appId) { |
| BluetoothHealthAppConfiguration appConfig = null; |
| for (Entry<BluetoothHealthAppConfiguration, AppInfo> e : mApps.entrySet()) { |
| if (appId == (e.getValue()).mAppId) { |
| appConfig = e.getKey(); |
| break; |
| } |
| } |
| if (appConfig == null) { |
| Log.e(TAG, "No appConfig found for " + appId); |
| } |
| return appConfig; |
| } |
| |
| private int convertHalRegStatus(int halRegStatus) { |
| switch (halRegStatus) { |
| case APP_REG_STATE_REG_SUCCESS: |
| return BluetoothHealth.APP_CONFIG_REGISTRATION_SUCCESS; |
| case APP_REG_STATE_REG_FAILED: |
| return BluetoothHealth.APP_CONFIG_REGISTRATION_FAILURE; |
| case APP_REG_STATE_DEREG_SUCCESS: |
| return BluetoothHealth.APP_CONFIG_UNREGISTRATION_SUCCESS; |
| case APP_REG_STATE_DEREG_FAILED: |
| return BluetoothHealth.APP_CONFIG_UNREGISTRATION_FAILURE; |
| } |
| Log.e(TAG, "Unexpected App Registration state: " + halRegStatus); |
| return BluetoothHealth.APP_CONFIG_REGISTRATION_FAILURE; |
| } |
| |
| private int convertHalChannelState(int halChannelState) { |
| switch (halChannelState) { |
| case CONN_STATE_CONNECTED: |
| return BluetoothHealth.STATE_CHANNEL_CONNECTED; |
| case CONN_STATE_CONNECTING: |
| return BluetoothHealth.STATE_CHANNEL_CONNECTING; |
| case CONN_STATE_DISCONNECTING: |
| return BluetoothHealth.STATE_CHANNEL_DISCONNECTING; |
| case CONN_STATE_DISCONNECTED: |
| return BluetoothHealth.STATE_CHANNEL_DISCONNECTED; |
| case CONN_STATE_DESTROYED: |
| // TODO(BT) add BluetoothHealth.STATE_CHANNEL_DESTROYED; |
| return BluetoothHealth.STATE_CHANNEL_DISCONNECTED; |
| default: |
| Log.e(TAG, "Unexpected channel state: " + halChannelState); |
| return BluetoothHealth.STATE_CHANNEL_DISCONNECTED; |
| } |
| } |
| |
| private boolean connectChannel(BluetoothDevice device, |
| BluetoothHealthAppConfiguration config, int channelType) { |
| if (mApps.get(config) == null) { |
| Log.e(TAG, "connectChannel fail to get a app id from config"); |
| return false; |
| } |
| |
| HealthChannel chan = new HealthChannel(device, config, channelType); |
| |
| Message msg = mHandler.obtainMessage(MESSAGE_CONNECT_CHANNEL); |
| msg.obj = chan; |
| mHandler.sendMessage(msg); |
| |
| return true; |
| } |
| |
| private void callHealthChannelCallback(BluetoothHealthAppConfiguration config, |
| BluetoothDevice device, int state, int prevState, ParcelFileDescriptor fd, int id) { |
| broadcastHealthDeviceStateChange(device, state); |
| |
| log("Health Device Callback: " + device + " State Change: " + prevState + "->" + |
| state); |
| |
| ParcelFileDescriptor dupedFd = null; |
| if (fd != null) { |
| try { |
| dupedFd = fd.dup(); |
| } catch (IOException e) { |
| dupedFd = null; |
| Log.e(TAG, "Exception while duping: " + e); |
| } |
| } |
| |
| IBluetoothHealthCallback callback = (mApps.get(config)).mCallback; |
| if (callback == null) { |
| Log.e(TAG, "No callback found for config: " + config); |
| return; |
| } |
| |
| try { |
| callback.onHealthChannelStateChange(config, device, prevState, state, dupedFd, id); |
| } catch (RemoteException e) { |
| Log.e(TAG, "Remote Exception:" + e); |
| } |
| } |
| |
| /** |
| * This function sends the intent for the updates on the connection status to the remote device. |
| * Note that multiple channels can be connected to the remote device by multiple applications. |
| * This sends an intent for the update to the device connection status and not the channel |
| * connection status. Only the following state transitions are possible: |
| * |
| * {@link BluetoothHealth#STATE_DISCONNECTED} to {@link BluetoothHealth#STATE_CONNECTING} |
| * {@link BluetoothHealth#STATE_CONNECTING} to {@link BluetoothHealth#STATE_CONNECTED} |
| * {@link BluetoothHealth#STATE_CONNECTED} to {@link BluetoothHealth#STATE_DISCONNECTING} |
| * {@link BluetoothHealth#STATE_DISCONNECTING} to {@link BluetoothHealth#STATE_DISCONNECTED} |
| * {@link BluetoothHealth#STATE_DISCONNECTED} to {@link BluetoothHealth#STATE_CONNECTED} |
| * {@link BluetoothHealth#STATE_CONNECTED} to {@link BluetoothHealth#STATE_DISCONNECTED} |
| * {@link BluetoothHealth#STATE_CONNECTING} to {{@link BluetoothHealth#STATE_DISCONNECTED} |
| * |
| * @param device |
| * @param prevChannelState |
| * @param newChannelState |
| * @hide |
| */ |
| private void broadcastHealthDeviceStateChange(BluetoothDevice device, int newChannelState) { |
| if (mHealthDevices.get(device) == null) { |
| mHealthDevices.put(device, BluetoothHealth.STATE_DISCONNECTED); |
| } |
| |
| int currDeviceState = mHealthDevices.get(device); |
| int newDeviceState = convertState(newChannelState); |
| |
| if (currDeviceState == newDeviceState) return; |
| |
| boolean sendIntent = false; |
| List<HealthChannel> chan; |
| switch (currDeviceState) { |
| case BluetoothHealth.STATE_DISCONNECTED: |
| // there was no connection or connect/disconnect attemp with the remote device |
| sendIntent = true; |
| break; |
| case BluetoothHealth.STATE_CONNECTING: |
| // there was no connection, there was a connecting attempt going on |
| |
| // Channel got connected. |
| if (newDeviceState == BluetoothHealth.STATE_CONNECTED) { |
| sendIntent = true; |
| } else { |
| // Channel got disconnected |
| chan = findChannelByStates(device, new int [] { |
| BluetoothHealth.STATE_CHANNEL_CONNECTING, |
| BluetoothHealth.STATE_CHANNEL_DISCONNECTING}); |
| if (chan.isEmpty()) { |
| sendIntent = true; |
| } |
| } |
| break; |
| case BluetoothHealth.STATE_CONNECTED: |
| // there was at least one connection |
| |
| // Channel got disconnected or is in disconnecting state. |
| chan = findChannelByStates(device, new int [] { |
| BluetoothHealth.STATE_CHANNEL_CONNECTING, |
| BluetoothHealth.STATE_CHANNEL_CONNECTED}); |
| if (chan.isEmpty()) { |
| sendIntent = true; |
| } |
| break; |
| case BluetoothHealth.STATE_DISCONNECTING: |
| // there was no connected channel with the remote device |
| // We were disconnecting all the channels with the remote device |
| |
| // Channel got disconnected. |
| chan = findChannelByStates(device, new int [] { |
| BluetoothHealth.STATE_CHANNEL_CONNECTING, |
| BluetoothHealth.STATE_CHANNEL_DISCONNECTING}); |
| if (chan.isEmpty()) { |
| updateAndSendIntent(device, newDeviceState, currDeviceState); |
| } |
| break; |
| } |
| if (sendIntent) |
| updateAndSendIntent(device, newDeviceState, currDeviceState); |
| } |
| |
| private void updateAndSendIntent(BluetoothDevice device, int newDeviceState, |
| int prevDeviceState) { |
| if (newDeviceState == BluetoothHealth.STATE_DISCONNECTED) { |
| mHealthDevices.remove(device); |
| } else { |
| mHealthDevices.put(device, newDeviceState); |
| } |
| notifyProfileConnectionStateChanged(device, BluetoothProfile.HEALTH, newDeviceState, prevDeviceState); |
| } |
| |
| /** |
| * This function converts the channel connection state to device connection state. |
| * |
| * @param state |
| * @return |
| */ |
| private int convertState(int state) { |
| switch (state) { |
| case BluetoothHealth.STATE_CHANNEL_CONNECTED: |
| return BluetoothHealth.STATE_CONNECTED; |
| case BluetoothHealth.STATE_CHANNEL_CONNECTING: |
| return BluetoothHealth.STATE_CONNECTING; |
| case BluetoothHealth.STATE_CHANNEL_DISCONNECTING: |
| return BluetoothHealth.STATE_DISCONNECTING; |
| case BluetoothHealth.STATE_CHANNEL_DISCONNECTED: |
| return BluetoothHealth.STATE_DISCONNECTED; |
| } |
| Log.e(TAG, "Mismatch in Channel and Health Device State: " + state); |
| return BluetoothHealth.STATE_DISCONNECTED; |
| } |
| |
| private int convertRoleToHal(int role) { |
| if (role == BluetoothHealth.SOURCE_ROLE) return MDEP_ROLE_SOURCE; |
| if (role == BluetoothHealth.SINK_ROLE) return MDEP_ROLE_SINK; |
| Log.e(TAG, "unkonw role: " + role); |
| return MDEP_ROLE_SINK; |
| } |
| |
| private int convertChannelTypeToHal(int channelType) { |
| if (channelType == BluetoothHealth.CHANNEL_TYPE_RELIABLE) return CHANNEL_TYPE_RELIABLE; |
| if (channelType == BluetoothHealth.CHANNEL_TYPE_STREAMING) return CHANNEL_TYPE_STREAMING; |
| if (channelType == BluetoothHealth.CHANNEL_TYPE_ANY) return CHANNEL_TYPE_ANY; |
| Log.e(TAG, "unkonw channel type: " + channelType); |
| return CHANNEL_TYPE_ANY; |
| } |
| |
| private HealthChannel findChannelById(int id) { |
| for (HealthChannel chan : mHealthChannels) { |
| if (chan.mChannelId == id) return chan; |
| } |
| Log.e(TAG, "No channel found by id: " + id); |
| return null; |
| } |
| |
| private List<HealthChannel> findChannelByStates(BluetoothDevice device, int[] states) { |
| List<HealthChannel> channels = new ArrayList<HealthChannel>(); |
| for (HealthChannel chan: mHealthChannels) { |
| if (chan.mDevice.equals(device)) { |
| for (int state : states) { |
| if (chan.mState == state) { |
| channels.add(chan); |
| } |
| } |
| } |
| } |
| return channels; |
| } |
| |
| private int getConnectionState(BluetoothDevice device) { |
| if (mHealthDevices.get(device) == null) { |
| return BluetoothHealth.STATE_DISCONNECTED; |
| } |
| return mHealthDevices.get(device); |
| } |
| |
| List<BluetoothDevice> lookupHealthDevicesMatchingStates(int[] states) { |
| List<BluetoothDevice> healthDevices = new ArrayList<BluetoothDevice>(); |
| |
| for (BluetoothDevice device: mHealthDevices.keySet()) { |
| int healthDeviceState = getConnectionState(device); |
| for (int state : states) { |
| if (state == healthDeviceState) { |
| healthDevices.add(device); |
| break; |
| } |
| } |
| } |
| return healthDevices; |
| } |
| |
| private static class AppInfo { |
| private IBluetoothHealthCallback mCallback; |
| private BluetoothHealthDeathRecipient mRcpObj; |
| private int mAppId; |
| |
| private AppInfo(IBluetoothHealthCallback callback) { |
| mCallback = callback; |
| mRcpObj = null; |
| mAppId = -1; |
| } |
| |
| private void cleanup(){ |
| if(mCallback != null){ |
| if(mRcpObj != null){ |
| IBinder binder = mCallback.asBinder(); |
| try{ |
| binder.unlinkToDeath(mRcpObj,0); |
| }catch(NoSuchElementException e){ |
| Log.e(TAG,"No death recipient registered"+e); |
| } |
| mRcpObj.cleanup(); |
| mRcpObj = null; |
| } |
| mCallback = null; |
| } |
| else if(mRcpObj != null){ |
| mRcpObj.cleanup(); |
| mRcpObj = null; |
| } |
| } |
| } |
| |
| private class HealthChannel { |
| private ParcelFileDescriptor mChannelFd; |
| private BluetoothDevice mDevice; |
| private BluetoothHealthAppConfiguration mConfig; |
| // BluetoothHealth channel state |
| private int mState; |
| private int mChannelType; |
| private int mChannelId; |
| |
| private HealthChannel(BluetoothDevice device, BluetoothHealthAppConfiguration config, |
| int channelType) { |
| mChannelFd = null; |
| mDevice = device; |
| mConfig = config; |
| mState = BluetoothHealth.STATE_CHANNEL_DISCONNECTED; |
| mChannelType = channelType; |
| mChannelId = -1; |
| } |
| } |
| |
| // Channel state event from Hal |
| private class ChannelStateEvent { |
| int mAppId; |
| byte[] mAddr; |
| int mCfgIndex; |
| int mChannelId; |
| int mState; |
| FileDescriptor mFd; |
| |
| private ChannelStateEvent(int appId, byte[] addr, int cfgIndex, |
| int channelId, int state, FileDescriptor fileDescriptor) { |
| mAppId = appId; |
| mAddr = addr; |
| mCfgIndex = cfgIndex; |
| mState = state; |
| mChannelId = channelId; |
| mFd = fileDescriptor; |
| } |
| } |
| |
| // Constants matching Hal header file bt_hl.h |
| // bthl_app_reg_state_t |
| private static final int APP_REG_STATE_REG_SUCCESS = 0; |
| private static final int APP_REG_STATE_REG_FAILED = 1; |
| private static final int APP_REG_STATE_DEREG_SUCCESS = 2; |
| private static final int APP_REG_STATE_DEREG_FAILED = 3; |
| |
| // bthl_channel_state_t |
| private static final int CONN_STATE_CONNECTING = 0; |
| private static final int CONN_STATE_CONNECTED = 1; |
| private static final int CONN_STATE_DISCONNECTING = 2; |
| private static final int CONN_STATE_DISCONNECTED = 3; |
| private static final int CONN_STATE_DESTROYED = 4; |
| |
| // bthl_mdep_role_t |
| private static final int MDEP_ROLE_SOURCE = 0; |
| private static final int MDEP_ROLE_SINK = 1; |
| |
| // bthl_channel_type_t |
| private static final int CHANNEL_TYPE_RELIABLE = 0; |
| private static final int CHANNEL_TYPE_STREAMING = 1; |
| private static final int CHANNEL_TYPE_ANY =2; |
| |
| private native static void classInitNative(); |
| private native void initializeNative(); |
| private native void cleanupNative(); |
| private native int registerHealthAppNative(int dataType, int role, String name, int channelType); |
| private native boolean unregisterHealthAppNative(int appId); |
| private native int connectChannelNative(byte[] btAddress, int appId); |
| private native boolean disconnectChannelNative(int channelId); |
| |
| } |