| /* |
| * Copyright (C) 2012 Google Inc. |
| */ |
| |
| package com.android.bluetooth.hid; |
| |
| import android.bluetooth.BluetoothDevice; |
| import android.bluetooth.BluetoothInputDevice; |
| import android.bluetooth.BluetoothProfile; |
| import android.bluetooth.IBluetooth; |
| import android.bluetooth.IBluetoothInputDevice; |
| import android.content.Intent; |
| import android.os.Bundle; |
| import android.os.IBinder; |
| import android.os.Handler; |
| import android.os.Message; |
| import android.os.RemoteException; |
| import android.os.ServiceManager; |
| import android.provider.Settings; |
| import android.util.Log; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import com.android.bluetooth.Utils; |
| import android.content.pm.PackageManager; |
| import com.android.bluetooth.btservice.ProfileService; |
| |
| /** |
| * Provides Bluetooth Hid Host profile, as a service in |
| * the Bluetooth application. |
| * @hide |
| */ |
| public class HidService extends ProfileService { |
| private static final boolean DBG = true; |
| private static final String TAG = "HidService"; |
| |
| private Map<BluetoothDevice, Integer> mInputDevices; |
| private boolean mNativeAvailable; |
| |
| private static final int MESSAGE_CONNECT = 1; |
| private static final int MESSAGE_DISCONNECT = 2; |
| private static final int MESSAGE_CONNECT_STATE_CHANGED = 3; |
| private static final int MESSAGE_GET_PROTOCOL_MODE = 4; |
| private static final int MESSAGE_VIRTUAL_UNPLUG = 5; |
| private static final int MESSAGE_ON_GET_PROTOCOL_MODE = 6; |
| private static final int MESSAGE_SET_PROTOCOL_MODE = 7; |
| private static final int MESSAGE_GET_REPORT = 8; |
| private static final int MESSAGE_ON_GET_REPORT = 9; |
| private static final int MESSAGE_SET_REPORT = 10; |
| private static final int MESSAGE_SEND_DATA = 11; |
| private static final int MESSAGE_ON_VIRTUAL_UNPLUG = 12; |
| |
| static { |
| classInitNative(); |
| } |
| |
| public String getName() { |
| return TAG; |
| } |
| |
| public IProfileServiceBinder initBinder() { |
| return new BluetoothInputDeviceBinder(this); |
| } |
| |
| protected boolean start() { |
| mInputDevices = Collections.synchronizedMap(new HashMap<BluetoothDevice, Integer>()); |
| initializeNative(); |
| mNativeAvailable=true; |
| return true; |
| } |
| |
| protected boolean stop() { |
| if (DBG) log("Stopping Bluetooth HidService"); |
| return true; |
| } |
| |
| protected boolean cleanup() { |
| if (mNativeAvailable) { |
| cleanupNative(); |
| mNativeAvailable=false; |
| } |
| |
| if(mInputDevices != null) { |
| mInputDevices.clear(); |
| mInputDevices = null; |
| } |
| return true; |
| } |
| |
| private final Handler mHandler = new Handler() { |
| |
| @Override |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| case MESSAGE_CONNECT: |
| { |
| BluetoothDevice device = (BluetoothDevice) msg.obj; |
| if (!connectHidNative(Utils.getByteAddress(device)) ) { |
| broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTING); |
| broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED); |
| break; |
| } |
| } |
| break; |
| case MESSAGE_DISCONNECT: |
| { |
| BluetoothDevice device = (BluetoothDevice) msg.obj; |
| if (!disconnectHidNative(Utils.getByteAddress(device)) ) { |
| broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTING); |
| broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED); |
| break; |
| } |
| } |
| break; |
| case MESSAGE_CONNECT_STATE_CHANGED: |
| { |
| BluetoothDevice device = getDevice((byte[]) msg.obj); |
| int halState = msg.arg1; |
| broadcastConnectionState(device, convertHalState(halState)); |
| } |
| break; |
| case MESSAGE_GET_PROTOCOL_MODE: |
| { |
| BluetoothDevice device = (BluetoothDevice) msg.obj; |
| if(!getProtocolModeNative(Utils.getByteAddress(device)) ) { |
| Log.e(TAG, "Error: get protocol mode native returns false"); |
| } |
| } |
| break; |
| |
| case MESSAGE_ON_GET_PROTOCOL_MODE: |
| { |
| BluetoothDevice device = getDevice((byte[]) msg.obj); |
| int protocolMode = msg.arg1; |
| broadcastProtocolMode(device, protocolMode); |
| } |
| break; |
| case MESSAGE_VIRTUAL_UNPLUG: |
| { |
| BluetoothDevice device = (BluetoothDevice) msg.obj; |
| if(!virtualUnPlugNative(Utils.getByteAddress(device))) { |
| Log.e(TAG, "Error: virtual unplug native returns false"); |
| } |
| } |
| break; |
| case MESSAGE_SET_PROTOCOL_MODE: |
| { |
| BluetoothDevice device = (BluetoothDevice) msg.obj; |
| byte protocolMode = (byte) msg.arg1; |
| log("sending set protocol mode(" + protocolMode + ")"); |
| if(!setProtocolModeNative(Utils.getByteAddress(device), protocolMode)) { |
| Log.e(TAG, "Error: set protocol mode native returns false"); |
| } |
| } |
| break; |
| case MESSAGE_GET_REPORT: |
| { |
| BluetoothDevice device = (BluetoothDevice) msg.obj; |
| Bundle data = msg.getData(); |
| byte reportType = data.getByte(BluetoothInputDevice.EXTRA_REPORT_TYPE); |
| byte reportId = data.getByte(BluetoothInputDevice.EXTRA_REPORT_ID); |
| int bufferSize = data.getInt(BluetoothInputDevice.EXTRA_REPORT_BUFFER_SIZE); |
| if(!getReportNative(Utils.getByteAddress(device), reportType, reportId, bufferSize)) { |
| Log.e(TAG, "Error: get report native returns false"); |
| } |
| } |
| break; |
| case MESSAGE_SET_REPORT: |
| { |
| BluetoothDevice device = (BluetoothDevice) msg.obj; |
| Bundle data = msg.getData(); |
| byte reportType = data.getByte(BluetoothInputDevice.EXTRA_REPORT_TYPE); |
| String report = data.getString(BluetoothInputDevice.EXTRA_REPORT); |
| if(!setReportNative(Utils.getByteAddress(device), reportType, report)) { |
| Log.e(TAG, "Error: set report native returns false"); |
| } |
| } |
| break; |
| case MESSAGE_SEND_DATA: |
| { |
| BluetoothDevice device = (BluetoothDevice) msg.obj; |
| Bundle data = msg.getData(); |
| String report = data.getString(BluetoothInputDevice.EXTRA_REPORT); |
| if(!sendDataNative(Utils.getByteAddress(device), report)) { |
| Log.e(TAG, "Error: send data native returns false"); |
| } |
| } |
| break; |
| case MESSAGE_ON_VIRTUAL_UNPLUG: |
| { |
| BluetoothDevice device = getDevice((byte[]) msg.obj); |
| int status = msg.arg1; |
| broadcastVirtualUnplugStatus(device, status); |
| } |
| break; |
| } |
| } |
| }; |
| |
| /** |
| * Handlers for incoming service calls |
| */ |
| private static class BluetoothInputDeviceBinder extends IBluetoothInputDevice.Stub implements IProfileServiceBinder{ |
| private HidService mService; |
| public BluetoothInputDeviceBinder(HidService svc) { |
| mService = svc; |
| } |
| |
| public boolean cleanup() { |
| mService = null; |
| return true; |
| } |
| |
| private HidService getService() { |
| if (mService != null && mService.isAvailable()) { |
| return mService; |
| } |
| return null; |
| } |
| |
| public boolean connect(BluetoothDevice device) { |
| HidService service = getService(); |
| if (service == null) return false; |
| return service.connect(device); |
| } |
| |
| public boolean disconnect(BluetoothDevice device) { |
| HidService service = getService(); |
| if (service == null) return false; |
| return service.disconnect(device); |
| } |
| |
| public int getConnectionState(BluetoothDevice device) { |
| HidService service = getService(); |
| if (service == null) return BluetoothInputDevice.STATE_DISCONNECTED; |
| return service.getConnectionState(device); |
| } |
| |
| public List<BluetoothDevice> getConnectedDevices() { |
| return getDevicesMatchingConnectionStates( |
| new int[] {BluetoothProfile.STATE_CONNECTED}); |
| } |
| |
| public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { |
| HidService service = getService(); |
| if (service == null) return new ArrayList<BluetoothDevice>(0); |
| return service.getDevicesMatchingConnectionStates(states); |
| } |
| |
| public boolean setPriority(BluetoothDevice device, int priority) { |
| HidService service = getService(); |
| if (service == null) return false; |
| return service.setPriority(device, priority); |
| } |
| |
| public int getPriority(BluetoothDevice device) { |
| HidService service = getService(); |
| if (service == null) return BluetoothProfile.PRIORITY_UNDEFINED; |
| return service.getPriority(device); |
| } |
| |
| /* The following APIs regarding test app for compliance */ |
| public boolean getProtocolMode(BluetoothDevice device) { |
| HidService service = getService(); |
| if (service == null) return false; |
| return service.getProtocolMode(device); |
| } |
| |
| public boolean virtualUnplug(BluetoothDevice device) { |
| HidService service = getService(); |
| if (service == null) return false; |
| return service.virtualUnplug(device); |
| } |
| |
| public boolean setProtocolMode(BluetoothDevice device, int protocolMode) { |
| HidService service = getService(); |
| if (service == null) return false; |
| return service.setProtocolMode(device, protocolMode); |
| } |
| |
| public boolean getReport(BluetoothDevice device, byte reportType, byte reportId, int bufferSize) { |
| HidService service = getService(); |
| if (service == null) return false; |
| return service.getReport(device, reportType, reportId, bufferSize) ; |
| } |
| |
| public boolean setReport(BluetoothDevice device, byte reportType, String report) { |
| HidService service = getService(); |
| if (service == null) return false; |
| return service.setReport(device, reportType, report); |
| } |
| |
| public boolean sendData(BluetoothDevice device, String report) { |
| HidService service = getService(); |
| if (service == null) return false; |
| return service.sendData(device, report); |
| } |
| }; |
| |
| //APIs |
| boolean connect(BluetoothDevice device) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| if (getConnectionState(device) != BluetoothInputDevice.STATE_DISCONNECTED) { |
| Log.e(TAG, "Hid Device not disconnected: " + device); |
| return false; |
| } |
| if (getPriority(device) == BluetoothInputDevice.PRIORITY_OFF) { |
| Log.e(TAG, "Hid Device PRIORITY_OFF: " + device); |
| return false; |
| } |
| |
| Message msg = mHandler.obtainMessage(MESSAGE_CONNECT, device); |
| mHandler.sendMessage(msg); |
| return true; |
| } |
| |
| boolean disconnect(BluetoothDevice device) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| Message msg = mHandler.obtainMessage(MESSAGE_DISCONNECT,device); |
| mHandler.sendMessage(msg); |
| return true; |
| } |
| |
| int getConnectionState(BluetoothDevice device) { |
| if (mInputDevices.get(device) == null) { |
| return BluetoothInputDevice.STATE_DISCONNECTED; |
| } |
| return mInputDevices.get(device); |
| } |
| |
| List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { |
| enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); |
| List<BluetoothDevice> inputDevices = new ArrayList<BluetoothDevice>(); |
| |
| for (BluetoothDevice device: mInputDevices.keySet()) { |
| int inputDeviceState = getConnectionState(device); |
| for (int state : states) { |
| if (state == inputDeviceState) { |
| inputDevices.add(device); |
| break; |
| } |
| } |
| } |
| return inputDevices; |
| } |
| |
| boolean setPriority(BluetoothDevice device, int priority) { |
| enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, |
| "Need BLUETOOTH_ADMIN permission"); |
| Settings.Secure.putInt(getContentResolver(), |
| Settings.Secure.getBluetoothInputDevicePriorityKey(device.getAddress()), |
| priority); |
| if (DBG) Log.d(TAG,"Saved priority " + device + " = " + priority); |
| return true; |
| } |
| |
| int getPriority(BluetoothDevice device) { |
| enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, |
| "Need BLUETOOTH_ADMIN permission"); |
| int priority = Settings.Secure.getInt(getContentResolver(), |
| Settings.Secure.getBluetoothInputDevicePriorityKey(device.getAddress()), |
| BluetoothProfile.PRIORITY_UNDEFINED); |
| return priority; |
| } |
| |
| /* The following APIs regarding test app for compliance */ |
| boolean getProtocolMode(BluetoothDevice device) { |
| enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, |
| "Need BLUETOOTH_ADMIN permission"); |
| int state = this.getConnectionState(device); |
| if (state != BluetoothInputDevice.STATE_CONNECTED) { |
| return false; |
| } |
| Message msg = mHandler.obtainMessage(MESSAGE_GET_PROTOCOL_MODE,device); |
| mHandler.sendMessage(msg); |
| return true; |
| /* String objectPath = getObjectPathFromAddress(device.getAddress()); |
| return getProtocolModeInputDeviceNative(objectPath);*/ |
| } |
| |
| boolean virtualUnplug(BluetoothDevice device) { |
| enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, |
| "Need BLUETOOTH_ADMIN permission"); |
| int state = this.getConnectionState(device); |
| if (state != BluetoothInputDevice.STATE_CONNECTED) { |
| return false; |
| } |
| Message msg = mHandler.obtainMessage(MESSAGE_VIRTUAL_UNPLUG,device); |
| mHandler.sendMessage(msg); |
| return true; |
| } |
| |
| boolean setProtocolMode(BluetoothDevice device, int protocolMode) { |
| enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, |
| "Need BLUETOOTH_ADMIN permission"); |
| int state = this.getConnectionState(device); |
| if (state != BluetoothInputDevice.STATE_CONNECTED) { |
| return false; |
| } |
| Message msg = mHandler.obtainMessage(MESSAGE_SET_PROTOCOL_MODE); |
| msg.obj = device; |
| msg.arg1 = protocolMode; |
| mHandler.sendMessage(msg); |
| return true ; |
| } |
| |
| boolean getReport(BluetoothDevice device, byte reportType, byte reportId, int bufferSize) { |
| enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, |
| "Need BLUETOOTH_ADMIN permission"); |
| int state = this.getConnectionState(device); |
| if (state != BluetoothInputDevice.STATE_CONNECTED) { |
| return false; |
| } |
| Message msg = mHandler.obtainMessage(MESSAGE_GET_REPORT); |
| msg.obj = device; |
| Bundle data = new Bundle(); |
| data.putByte(BluetoothInputDevice.EXTRA_REPORT_TYPE, reportType); |
| data.putByte(BluetoothInputDevice.EXTRA_REPORT_ID, reportId); |
| data.putInt(BluetoothInputDevice.EXTRA_REPORT_BUFFER_SIZE, bufferSize); |
| msg.setData(data); |
| mHandler.sendMessage(msg); |
| return true ; |
| } |
| |
| boolean setReport(BluetoothDevice device, byte reportType, String report) { |
| enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, |
| "Need BLUETOOTH_ADMIN permission"); |
| int state = this.getConnectionState(device); |
| if (state != BluetoothInputDevice.STATE_CONNECTED) { |
| return false; |
| } |
| Message msg = mHandler.obtainMessage(MESSAGE_SET_REPORT); |
| msg.obj = device; |
| Bundle data = new Bundle(); |
| data.putByte(BluetoothInputDevice.EXTRA_REPORT_TYPE, reportType); |
| data.putString(BluetoothInputDevice.EXTRA_REPORT, report); |
| msg.setData(data); |
| mHandler.sendMessage(msg); |
| return true ; |
| |
| } |
| |
| boolean sendData(BluetoothDevice device, String report) { |
| enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, |
| "Need BLUETOOTH_ADMIN permission"); |
| int state = this.getConnectionState(device); |
| if (state != BluetoothInputDevice.STATE_CONNECTED) { |
| return false; |
| } |
| |
| return sendDataNative(Utils.getByteAddress(device), report); |
| /*Message msg = mHandler.obtainMessage(MESSAGE_SEND_DATA); |
| msg.obj = device; |
| Bundle data = new Bundle(); |
| data.putString(BluetoothInputDevice.EXTRA_REPORT, report); |
| msg.setData(data); |
| mHandler.sendMessage(msg); |
| return true ;*/ |
| } |
| |
| private void onGetProtocolMode(byte[] address, int mode) { |
| Message msg = mHandler.obtainMessage(MESSAGE_ON_GET_PROTOCOL_MODE); |
| msg.obj = address; |
| msg.arg1 = mode; |
| mHandler.sendMessage(msg); |
| } |
| |
| private void onVirtualUnplug(byte[] address, int status) { |
| Message msg = mHandler.obtainMessage(MESSAGE_ON_VIRTUAL_UNPLUG); |
| msg.obj = address; |
| msg.arg1 = status; |
| mHandler.sendMessage(msg); |
| } |
| |
| private void onConnectStateChanged(byte[] address, int state) { |
| Message msg = mHandler.obtainMessage(MESSAGE_CONNECT_STATE_CHANGED); |
| msg.obj = address; |
| msg.arg1 = state; |
| mHandler.sendMessage(msg); |
| } |
| |
| // This method does not check for error conditon (newState == prevState) |
| private void broadcastConnectionState(BluetoothDevice device, int newState) { |
| Integer prevStateInteger = mInputDevices.get(device); |
| int prevState = (prevStateInteger == null) ? BluetoothInputDevice.STATE_DISCONNECTED : |
| prevStateInteger; |
| if (prevState == newState) { |
| Log.w(TAG, "no state change: " + newState); |
| return; |
| } |
| mInputDevices.put(device, newState); |
| |
| Intent intent = new Intent(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED); |
| intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); |
| intent.putExtra(BluetoothProfile.EXTRA_STATE, newState); |
| intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); |
| intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); |
| sendBroadcast(intent, BLUETOOTH_PERM); |
| if (DBG) log("Connection state " + device + ": " + prevState + "->" + newState); |
| notifyProfileConnectionStateChanged(device, BluetoothProfile.INPUT_DEVICE, newState, prevState); |
| } |
| |
| private void broadcastProtocolMode(BluetoothDevice device, int protocolMode) { |
| Intent intent = new Intent(BluetoothInputDevice.ACTION_PROTOCOL_MODE_CHANGED); |
| intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); |
| intent.putExtra(BluetoothInputDevice.EXTRA_PROTOCOL_MODE, protocolMode); |
| intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); |
| sendBroadcast(intent, BLUETOOTH_PERM); |
| if (DBG) log("Protocol Mode (" + device + "): " + protocolMode); |
| } |
| |
| private void broadcastVirtualUnplugStatus(BluetoothDevice device, int status) { |
| Intent intent = new Intent(BluetoothInputDevice.ACTION_VIRTUAL_UNPLUG_STATUS); |
| intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); |
| intent.putExtra(BluetoothInputDevice.EXTRA_VIRTUAL_UNPLUG_STATUS, status); |
| intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); |
| sendBroadcast(intent, BLUETOOTH_PERM); |
| } |
| |
| private static int convertHalState(int halState) { |
| switch (halState) { |
| case CONN_STATE_CONNECTED: |
| return BluetoothProfile.STATE_CONNECTED; |
| case CONN_STATE_CONNECTING: |
| return BluetoothProfile.STATE_CONNECTING; |
| case CONN_STATE_DISCONNECTED: |
| return BluetoothProfile.STATE_DISCONNECTED; |
| case CONN_STATE_DISCONNECTING: |
| return BluetoothProfile.STATE_DISCONNECTING; |
| default: |
| Log.e(TAG, "bad hid connection state: " + halState); |
| return BluetoothProfile.STATE_DISCONNECTED; |
| } |
| } |
| |
| // Constants matching Hal header file bt_hh.h |
| // bthh_connection_state_t |
| private final static int CONN_STATE_CONNECTED = 0; |
| private final static int CONN_STATE_CONNECTING = 1; |
| private final static int CONN_STATE_DISCONNECTED = 2; |
| private final static int CONN_STATE_DISCONNECTING = 3; |
| |
| private native static void classInitNative(); |
| private native void initializeNative(); |
| private native void cleanupNative(); |
| private native boolean connectHidNative(byte[] btAddress); |
| private native boolean disconnectHidNative(byte[] btAddress); |
| private native boolean getProtocolModeNative(byte[] btAddress); |
| private native boolean virtualUnPlugNative(byte[] btAddress); |
| private native boolean setProtocolModeNative(byte[] btAddress, byte protocolMode); |
| private native boolean getReportNative(byte[]btAddress, byte reportType, byte reportId, int bufferSize); |
| private native boolean setReportNative(byte[] btAddress, byte reportType, String report); |
| private native boolean sendDataNative(byte[] btAddress, String report); |
| } |