diff options
Diffstat (limited to 'core/java/android/server/BluetoothHealthProfileHandler.java')
| -rw-r--r-- | core/java/android/server/BluetoothHealthProfileHandler.java | 600 |
1 files changed, 600 insertions, 0 deletions
diff --git a/core/java/android/server/BluetoothHealthProfileHandler.java b/core/java/android/server/BluetoothHealthProfileHandler.java new file mode 100644 index 0000000..7f862e0 --- /dev/null +++ b/core/java/android/server/BluetoothHealthProfileHandler.java @@ -0,0 +1,600 @@ +/* + * Copyright (C) 2011 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 android.server; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHealth; +import android.bluetooth.BluetoothHealthAppConfiguration; +import android.bluetooth.BluetoothHealth; +import android.bluetooth.BluetoothInputDevice; +import android.content.Context; +import android.content.Intent; +import android.os.Handler; +import android.os.Message; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.provider.Settings; +import android.util.Log; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map.Entry; + +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.IOException; + +/** + * This handles all the operations on the Bluetooth Health profile. + * All functions are called by BluetoothService, as Bluetooth Service + * is the Service handler for the HDP profile. + * + * @hide + */ +final class BluetoothHealthProfileHandler { + private static final String TAG = "BluetoothHealthProfileHandler"; + /*STOPSHIP*/ + private static final boolean DBG = true; + + private static BluetoothHealthProfileHandler sInstance; + private Context mContext; + private BluetoothService mBluetoothService; + private ArrayList<HealthChannel> mHealthChannels; + private HashMap <BluetoothHealthAppConfiguration, String> mHealthAppConfigs; + private HashMap <BluetoothDevice, Integer> mHealthDevices; + + private static final int MESSAGE_REGISTER_APPLICATION = 0; + private static final int MESSAGE_UNREGISTER_APPLICATION = 1; + private static final int MESSAGE_CONNECT_CHANNEL = 2; + + class HealthChannel { + private ParcelFileDescriptor mChannelFd; + private boolean mMainChannel; + private String mChannelPath; + private BluetoothDevice mDevice; + private BluetoothHealthAppConfiguration mConfig; + private int mState; + private int mChannelType; + + HealthChannel(BluetoothDevice device, BluetoothHealthAppConfiguration config, + ParcelFileDescriptor fd, boolean mainChannel, String channelPath) { + mChannelFd = fd; + mMainChannel = mainChannel; + mChannelPath = channelPath; + mDevice = device; + mConfig = config; + mState = BluetoothHealth.STATE_CHANNEL_DISCONNECTED; + } + } + + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MESSAGE_REGISTER_APPLICATION: + BluetoothHealthAppConfiguration registerApp = + (BluetoothHealthAppConfiguration) msg.obj; + int role = registerApp.getRole(); + String path = null; + + if (role == BluetoothHealth.SINK_ROLE) { + path = mBluetoothService.registerHealthApplicationNative( + registerApp.getDataType(), getStringRole(role), registerApp.getName()); + } else { + path = mBluetoothService.registerHealthApplicationNative( + registerApp.getDataType(), getStringRole(role), registerApp.getName(), + getStringChannelType(registerApp.getChannelType())); + } + + if (path == null) { + callHealthApplicationStatusCallback(registerApp, + BluetoothHealth.APPLICATION_REGISTRATION_FAILURE); + } else { + mHealthAppConfigs.put(registerApp, path); + callHealthApplicationStatusCallback(registerApp, + BluetoothHealth.APPLICATION_REGISTRATION_SUCCESS); + } + + break; + case MESSAGE_UNREGISTER_APPLICATION: + BluetoothHealthAppConfiguration unregisterApp = + (BluetoothHealthAppConfiguration) msg.obj; + boolean result = mBluetoothService.unregisterHealthApplicationNative( + mHealthAppConfigs.get(unregisterApp)); + if (result) { + callHealthApplicationStatusCallback(unregisterApp, + BluetoothHealth.APPLICATION_UNREGISTRATION_SUCCESS); + } else { + callHealthApplicationStatusCallback(unregisterApp, + BluetoothHealth.APPLICATION_UNREGISTRATION_FAILURE); + } + break; + case MESSAGE_CONNECT_CHANNEL: + HealthChannel chan = (HealthChannel)msg.obj; + String deviceObjectPath = + mBluetoothService.getObjectPathFromAddress(chan.mDevice.getAddress()); + String configPath = mHealthAppConfigs.get(chan.mConfig); + String channelType = getStringChannelType(chan.mChannelType); + + if (!mBluetoothService.createChannelNative(deviceObjectPath, configPath, + channelType)) { + int prevState = chan.mState; + int state = BluetoothHealth.STATE_CHANNEL_DISCONNECTED; + callHealthChannelCallback(chan.mConfig, chan.mDevice, prevState, state, null); + mHealthChannels.remove(chan); + } + } + } + }; + + private BluetoothHealthProfileHandler(Context context, BluetoothService service) { + mContext = context; + mBluetoothService = service; + mHealthAppConfigs = new HashMap<BluetoothHealthAppConfiguration, String>(); + mHealthChannels = new ArrayList<HealthChannel>(); + mHealthDevices = new HashMap<BluetoothDevice, Integer>(); + } + + static synchronized BluetoothHealthProfileHandler getInstance(Context context, + BluetoothService service) { + if (sInstance == null) sInstance = new BluetoothHealthProfileHandler(context, service); + return sInstance; + } + + boolean registerAppConfiguration(BluetoothHealthAppConfiguration config) { + Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_APPLICATION); + msg.obj = config; + mHandler.sendMessage(msg); + return true; + } + + boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) { + String path = mHealthAppConfigs.get(config); + if (path == null) return false; + + Message msg = mHandler.obtainMessage(MESSAGE_UNREGISTER_APPLICATION); + msg.obj = config; + mHandler.sendMessage(msg); + return true; + } + + boolean connectChannelToSource(BluetoothDevice device, + BluetoothHealthAppConfiguration config) { + return connectChannel(device, config, BluetoothHealth.CHANNEL_TYPE_ANY); + } + + private HealthChannel getMainChannel(BluetoothDevice device, + BluetoothHealthAppConfiguration config) { + for (HealthChannel chan: mHealthChannels) { + if (chan.mDevice.equals(device) && chan.mConfig.equals(config)) { + if (chan.mMainChannel) return chan; + } + } + return null; + } + + boolean connectChannel(BluetoothDevice device, + BluetoothHealthAppConfiguration config, int channelType) { + String deviceObjectPath = + mBluetoothService.getObjectPathFromAddress(device.getAddress()); + if (deviceObjectPath == null) return false; + + String configPath = mHealthAppConfigs.get(config); + if (configPath == null) return false; + + HealthChannel chan = new HealthChannel(device, config, null, false, null); + chan.mState = BluetoothHealth.STATE_CHANNEL_CONNECTING; + chan.mChannelType = channelType; + mHealthChannels.add(chan); + + int prevState = BluetoothHealth.STATE_CHANNEL_DISCONNECTED; + int state = BluetoothHealth.STATE_CHANNEL_CONNECTING; + callHealthChannelCallback(config, device, prevState, state, null); + + Message msg = mHandler.obtainMessage(MESSAGE_CONNECT_CHANNEL); + msg.obj = chan; + mHandler.sendMessage(msg); + + return true; + } + + 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 String getStringRole(int role) { + if (role == BluetoothHealth.SINK_ROLE) { + return "Sink"; + } else if (role == BluetoothHealth.SOURCE_ROLE) { + return "Streaming"; + } else { + return null; + } + } + + boolean disconnectChannel(BluetoothDevice device, + BluetoothHealthAppConfiguration config, ParcelFileDescriptor fd) { + HealthChannel chan = findChannelByFd(device, config, fd); + if (chan == null) return false; + + String deviceObjectPath = + mBluetoothService.getObjectPathFromAddress(device.getAddress()); + if (mBluetoothService.destroyChannelNative(deviceObjectPath, chan.mChannelPath)) { + int prevState = chan.mState; + chan.mState = BluetoothHealth.STATE_CHANNEL_DISCONNECTING; + callHealthChannelCallback(config, device, prevState, chan.mState, + chan.mChannelFd); + return true; + } else { + return false; + } + } + + private HealthChannel findChannelByFd(BluetoothDevice device, + BluetoothHealthAppConfiguration config, ParcelFileDescriptor fd) { + for (HealthChannel chan : mHealthChannels) { + if (chan.mChannelFd.equals(fd) && chan.mDevice.equals(device) && + chan.mConfig.equals(config)) return chan; + } + return null; + } + + private HealthChannel findChannelByPath(BluetoothDevice device, + BluetoothHealthAppConfiguration config, String path) { + for (HealthChannel chan : mHealthChannels) { + if (chan.mChannelPath.equals(path) && chan.mDevice.equals(device) && + chan.mConfig.equals(config)) return chan; + } + 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 HealthChannel findConnectingChannel(BluetoothDevice device, + BluetoothHealthAppConfiguration config) { + for (HealthChannel chan : mHealthChannels) { + if (chan.mDevice.equals(device) && chan.mConfig.equals(config) && + chan.mState == BluetoothHealth.STATE_CHANNEL_CONNECTING) return chan; + } + return null; + } + + ParcelFileDescriptor getMainChannelFd(BluetoothDevice device, + BluetoothHealthAppConfiguration config) { + HealthChannel chan = getMainChannel(device, config); + if (chan != null) return chan.mChannelFd; + + String objectPath = + mBluetoothService.getObjectPathFromAddress(device.getAddress()); + if (objectPath == null) return null; + + String mainChannelPath = mBluetoothService.getMainChannelNative(objectPath); + if (mainChannelPath == null) return null; + + // We had no record of the main channel but querying Bluez we got a + // main channel. We might not have received the PropertyChanged yet for + // the main channel creation so update our data structure here. + chan = findChannelByPath(device, config, mainChannelPath); + if (chan == null) { + errorLog("Main Channel present but we don't have any account of it:" + + device +":" + config); + return null; + } + chan.mMainChannel = true; + return chan.mChannelFd; + } + + /*package*/ void onHealthDevicePropertyChanged(String devicePath, + String channelPath) { + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + String address = mBluetoothService.getAddressFromObjectPath(devicePath); + if (address == null) return; + + //TODO: Fix this in Bluez + if (channelPath.equals("/")) { + // This means that the main channel is being destroyed. + return; + } + + BluetoothDevice device = adapter.getRemoteDevice(address); + BluetoothHealthAppConfiguration config = findHealthApplication(device, + channelPath); + if (config != null) { + HealthChannel chan = findChannelByPath(device, config, channelPath); + if (chan == null) { + errorLog("Health Channel is not present:" + channelPath); + } else { + chan.mMainChannel = true; + } + } + } + + private BluetoothHealthAppConfiguration findHealthApplication( + BluetoothDevice device, String channelPath) { + BluetoothHealthAppConfiguration config = null; + String configPath = mBluetoothService.getChannelApplicationNative(channelPath); + + if (configPath == null) { + errorLog("No associated application for Health Channel:" + channelPath); + return null; + } else { + for (Entry<BluetoothHealthAppConfiguration, String> e : + mHealthAppConfigs.entrySet()) { + if (e.getValue().equals(configPath)) { + config = e.getKey(); + } + } + if (config == null) { + errorLog("No associated application for application path:" + configPath); + return null; + } + } + return config; + } + + /*package*/ void onHealthDeviceChannelChanged(String devicePath, + String channelPath, boolean exists) { + debugLog("onHealthDeviceChannelChanged: devicePath: " + devicePath + + "ChannelPath: " + channelPath + "Exists: " + exists); + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + String address = mBluetoothService.getAddressFromObjectPath(devicePath); + if (address == null) return; + + BluetoothDevice device = adapter.getRemoteDevice(address); + + BluetoothHealthAppConfiguration config = findHealthApplication(device, + channelPath); + int state, prevState = BluetoothHealth.STATE_CHANNEL_DISCONNECTED; + ParcelFileDescriptor fd; + HealthChannel channel; + + if (config != null) { + if (exists) { + fd = mBluetoothService.getChannelFdNative(channelPath); + + if (fd == null) { + errorLog("Error obtaining fd for channel:" + channelPath); + return; + } + + boolean mainChannel = + getMainChannel(device, config) == null ? false : true; + if (!mainChannel) { + String mainChannelPath = + mBluetoothService.getMainChannelNative(devicePath); + if (mainChannelPath == null) { + errorLog("Main Channel Path is null for devicePath:" + devicePath); + return; + } + if (mainChannelPath.equals(channelPath)) mainChannel = true; + } + + channel = findConnectingChannel(device, config); + if (channel != null) { + channel.mChannelFd = fd; + channel.mMainChannel = mainChannel; + channel.mChannelPath = channelPath; + prevState = channel.mState; + } else { + channel = new HealthChannel(device, config, fd, mainChannel, + channelPath); + mHealthChannels.add(channel); + prevState = BluetoothHealth.STATE_CHANNEL_DISCONNECTED; + } + state = BluetoothHealth.STATE_CHANNEL_CONNECTED; + } else { + channel = findChannelByPath(device, config, channelPath); + if (channel == null) { + errorLog("Channel not found:" + config + ":" + channelPath); + return; + } + + fd = channel.mChannelFd; + // CLOSE FD + mBluetoothService.releaseChannelFdNative(channel.mChannelPath); + mHealthChannels.remove(channel); + + prevState = channel.mState; + state = BluetoothHealth.STATE_CHANNEL_DISCONNECTED; + } + channel.mState = state; + callHealthChannelCallback(config, device, prevState, state, fd); + } + } + + private void callHealthChannelCallback(BluetoothHealthAppConfiguration config, + BluetoothDevice device, int prevState, int state, ParcelFileDescriptor fd) { + broadcastHealthDeviceStateChange(device, prevState, state); + + debugLog("Health Device Callback: " + device + " State Change: " + + prevState + "->" + state); + try { + config.getCallback().onHealthChannelStateChange(config, device, prevState, + state, fd); + } catch (RemoteException e) { + errorLog("Error while making health channel state change callback: " + e); + } + } + + private void callHealthApplicationStatusCallback( + BluetoothHealthAppConfiguration config, int status) { + debugLog("Health Device Application: " + config + " State Change: status:" + + status); + try { + config.getCallback().onHealthAppConfigurationStatusChange(config, status); + } catch (RemoteException e) { + errorLog("Error while making health app registration state change callback: " + e); + } + } + + int getHealthDeviceConnectionState(BluetoothDevice device) { + if (mHealthDevices.get(device) == null) { + return BluetoothHealth.STATE_DISCONNECTED; + } + return mHealthDevices.get(device); + } + + List<BluetoothDevice> getConnectedHealthDevices() { + List<BluetoothDevice> devices = lookupHealthDevicesMatchingStates( + new int[] {BluetoothHealth.STATE_CONNECTED}); + return devices; + } + + List<BluetoothDevice> getHealthDevicesMatchingConnectionStates(int[] states) { + List<BluetoothDevice> devices = lookupHealthDevicesMatchingStates(states); + return devices; + } + + List<BluetoothDevice> lookupHealthDevicesMatchingStates(int[] states) { + List<BluetoothDevice> healthDevices = new ArrayList<BluetoothDevice>(); + + for (BluetoothDevice device: mHealthDevices.keySet()) { + int healthDeviceState = getHealthDeviceConnectionState(device); + for (int state : states) { + if (state == healthDeviceState) { + healthDevices.add(device); + break; + } + } + } + return healthDevices; + } + + /** + * 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 prevChannelState, + 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) { + List<HealthChannel> chan; + switch (currDeviceState) { + case BluetoothHealth.STATE_DISCONNECTED: + updateAndsendIntent(device, currDeviceState, newDeviceState); + break; + case BluetoothHealth.STATE_CONNECTING: + // Channel got connected. + if (newDeviceState == BluetoothHealth.STATE_CONNECTED) { + updateAndsendIntent(device, currDeviceState, newDeviceState); + } else { + // Channel got disconnected + chan = findChannelByStates(device, new int [] { + BluetoothHealth.STATE_CHANNEL_CONNECTING, + BluetoothHealth.STATE_CHANNEL_DISCONNECTING}); + if (chan.isEmpty()) { + updateAndsendIntent(device, currDeviceState, newDeviceState); + } + } + break; + case BluetoothHealth.STATE_CONNECTED: + // Channel got disconnected or is in disconnecting state. + chan = findChannelByStates(device, new int [] { + BluetoothHealth.STATE_CHANNEL_CONNECTING, + BluetoothHealth.STATE_CHANNEL_CONNECTED}); + if (chan.isEmpty()) { + updateAndsendIntent(device, currDeviceState, newDeviceState); + } + case BluetoothHealth.STATE_DISCONNECTING: + // Channel got disconnected. + chan = findChannelByStates(device, new int [] { + BluetoothHealth.STATE_CHANNEL_CONNECTING, + BluetoothHealth.STATE_CHANNEL_DISCONNECTING}); + if (chan.isEmpty()) { + updateAndsendIntent(device, currDeviceState, newDeviceState); + } + break; + } + } + } + + private void updateAndsendIntent(BluetoothDevice device, int prevDeviceState, + int newDeviceState) { + mHealthDevices.put(device, newDeviceState); + mBluetoothService.sendConnectionStateChange(device, prevDeviceState, newDeviceState); + } + + /** + * 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; + } + errorLog("Mismatch in Channel and Health Device State"); + return -1; + } + + private static void debugLog(String msg) { + if (DBG) Log.d(TAG, msg); + } + + private static void errorLog(String msg) { + Log.e(TAG, msg); + } +} |
