summaryrefslogtreecommitdiffstats
path: root/core/java/android/server/BluetoothHealthProfileHandler.java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/android/server/BluetoothHealthProfileHandler.java')
-rw-r--r--core/java/android/server/BluetoothHealthProfileHandler.java600
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);
+ }
+}