/* * Copyright (C) 2016 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.hid; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHidDeviceAppConfiguration; import android.bluetooth.BluetoothHidDeviceAppQosSettings; import android.bluetooth.BluetoothHidDeviceAppSdpSettings; import android.bluetooth.BluetoothInputHost; import android.bluetooth.BluetoothProfile; import android.bluetooth.IBluetoothHidDeviceCallback; import android.bluetooth.IBluetoothInputHost; import android.content.Intent; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.RemoteException; import android.util.Log; import com.android.bluetooth.Utils; import com.android.bluetooth.btservice.ProfileService; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; /** @hide */ public class HidDevService extends ProfileService { private static final boolean DBG = false; private static final String TAG = HidDevService.class.getSimpleName(); private static final int MESSAGE_APPLICATION_STATE_CHANGED = 1; private static final int MESSAGE_CONNECT_STATE_CHANGED = 2; private static final int MESSAGE_GET_REPORT = 3; private static final int MESSAGE_SET_REPORT = 4; private static final int MESSAGE_SET_PROTOCOL = 5; private static final int MESSAGE_INTR_DATA = 6; private static final int MESSAGE_VC_UNPLUG = 7; private boolean mNativeAvailable = false; private BluetoothDevice mHidDevice = null; private int mHidDeviceState = BluetoothInputHost.STATE_DISCONNECTED; private BluetoothHidDeviceAppConfiguration mAppConfig = null; private IBluetoothHidDeviceCallback mCallback = null; private BluetoothHidDeviceDeathRecipient mDeathRcpt; static { classInitNative(); } private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { if (DBG) Log.v(TAG, "handleMessage(): msg.what=" + msg.what); switch (msg.what) { case MESSAGE_APPLICATION_STATE_CHANGED: { BluetoothDevice device = msg.obj != null ? getDevice((byte[]) msg.obj) : null; boolean success = (msg.arg1 != 0); if (success) { mHidDevice = device; } else { mHidDevice = null; } try { if (mCallback != null) mCallback.onAppStatusChanged(device, mAppConfig, success); else break; } catch (RemoteException e) { Log.e(TAG, "e=" + e.toString()); e.printStackTrace(); } if (success) { mDeathRcpt = new BluetoothHidDeviceDeathRecipient( HidDevService.this, mAppConfig); if (mCallback != null) { IBinder binder = mCallback.asBinder(); try { binder.linkToDeath(mDeathRcpt, 0); Log.i(TAG, "IBinder.linkToDeath() ok"); } catch (RemoteException e) { e.printStackTrace(); } } } else if (mDeathRcpt != null) { if (mCallback != null) { IBinder binder = mCallback.asBinder(); try { binder.unlinkToDeath(mDeathRcpt, 0); Log.i(TAG, "IBinder.unlinkToDeath() ok"); } catch (NoSuchElementException e) { e.printStackTrace(); } mDeathRcpt.cleanup(); mDeathRcpt = null; } } if (!success) { mAppConfig = null; mCallback = null; } break; } case MESSAGE_CONNECT_STATE_CHANGED: { BluetoothDevice device = getDevice((byte[]) msg.obj); int halState = msg.arg1; int state = convertHalState(halState); if (state != BluetoothInputHost.STATE_DISCONNECTED) { mHidDevice = device; } broadcastConnectionState(device, state); try { if (mCallback != null) mCallback.onConnectionStateChanged(device, state); } catch (RemoteException e) { e.printStackTrace(); } break; } case MESSAGE_GET_REPORT: byte type = (byte) msg.arg1; byte id = (byte) msg.arg2; int bufferSize = msg.obj == null ? 0 : ((Integer) msg.obj).intValue(); try { if (mCallback != null) mCallback.onGetReport(mHidDevice, type, id, bufferSize); } catch (RemoteException e) { e.printStackTrace(); } break; case MESSAGE_SET_REPORT: { byte reportType = (byte) msg.arg1; byte reportId = (byte) msg.arg2; byte[] data = ((ByteBuffer) msg.obj).array(); try { if (mCallback != null) mCallback.onSetReport(mHidDevice, reportType, reportId, data); } catch (RemoteException e) { e.printStackTrace(); } break; } case MESSAGE_SET_PROTOCOL: byte protocol = (byte) msg.arg1; try { if (mCallback != null) mCallback.onSetProtocol(mHidDevice, protocol); } catch (RemoteException e) { e.printStackTrace(); } break; case MESSAGE_INTR_DATA: byte reportId = (byte) msg.arg1; byte[] data = ((ByteBuffer) msg.obj).array(); try { if (mCallback != null) mCallback.onIntrData(mHidDevice, reportId, data); } catch (RemoteException e) { e.printStackTrace(); } break; case MESSAGE_VC_UNPLUG: try { if (mCallback != null) mCallback.onVirtualCableUnplug(mHidDevice); } catch (RemoteException e) { e.printStackTrace(); } mHidDevice = null; break; } } }; private static class BluetoothHidDeviceDeathRecipient implements IBinder.DeathRecipient { private HidDevService mService; private BluetoothHidDeviceAppConfiguration mAppConfig; public BluetoothHidDeviceDeathRecipient( HidDevService service, BluetoothHidDeviceAppConfiguration config) { mService = service; mAppConfig = config; } @Override public void binderDied() { Log.w(TAG, "Binder died, need to unregister app :("); mService.unregisterApp(mAppConfig); } public void cleanup() { mService = null; mAppConfig = null; } } private static class BluetoothHidDeviceBinder extends IBluetoothInputHost.Stub implements IProfileServiceBinder { private static final String TAG = BluetoothHidDeviceBinder.class.getSimpleName(); private HidDevService mService; public BluetoothHidDeviceBinder(HidDevService service) { mService = service; } @Override public boolean cleanup() { mService = null; return true; } private HidDevService getService() { if (!Utils.checkCaller()) { Log.w(TAG, "HidDevice call not allowed for non-active user"); return null; } if (mService != null && mService.isAvailable()) { return mService; } return null; } @Override public boolean registerApp(BluetoothHidDeviceAppConfiguration config, BluetoothHidDeviceAppSdpSettings sdp, BluetoothHidDeviceAppQosSettings inQos, BluetoothHidDeviceAppQosSettings outQos, IBluetoothHidDeviceCallback callback) { if (DBG) Log.v(TAG, "registerApp()"); HidDevService service = getService(); if (service == null) { return false; } return service.registerApp(config, sdp, inQos, outQos, callback); } @Override public boolean unregisterApp(BluetoothHidDeviceAppConfiguration config) { if (DBG) Log.v(TAG, "unregisterApp()"); HidDevService service = getService(); if (service == null) { return false; } return service.unregisterApp(config); } @Override public boolean sendReport(BluetoothDevice device, int id, byte[] data) { if (DBG) Log.v(TAG, "sendReport(): device=" + device + " id=" + id); HidDevService service = getService(); if (service == null) { return false; } return service.sendReport(device, id, data); } @Override public boolean replyReport(BluetoothDevice device, byte type, byte id, byte[] data) { if (DBG) Log.v(TAG, "replyReport(): device=" + device + " type=" + type + " id=" + id); HidDevService service = getService(); if (service == null) { return false; } return service.replyReport(device, type, id, data); } @Override public boolean unplug(BluetoothDevice device) { if (DBG) Log.v(TAG, "unplug(): device=" + device); HidDevService service = getService(); if (service == null) { return false; } return service.unplug(device); } @Override public boolean connect(BluetoothDevice device) { if (DBG) Log.v(TAG, "connect(): device=" + device); HidDevService service = getService(); if (service == null) { return false; } return service.connect(device); } @Override public boolean disconnect(BluetoothDevice device) { if (DBG) Log.v(TAG, "disconnect(): device=" + device); HidDevService service = getService(); if (service == null) { return false; } return service.disconnect(device); } @Override public boolean reportError(BluetoothDevice device, byte error) { if (DBG) Log.v(TAG, "reportError(): device=" + device + " error=" + error); HidDevService service = getService(); if (service == null) { return false; } return service.reportError(device, error); } @Override public int getConnectionState(BluetoothDevice device) { if (DBG) Log.v(TAG, "getConnectionState(): device=" + device); HidDevService service = getService(); if (service == null) { return BluetoothInputHost.STATE_DISCONNECTED; } return service.getConnectionState(device); } @Override public List getConnectedDevices() { if (DBG) Log.v(TAG, "getConnectedDevices()"); return getDevicesMatchingConnectionStates(new int[] {BluetoothProfile.STATE_CONNECTED}); } @Override public List getDevicesMatchingConnectionStates(int[] states) { if (DBG) Log.v(TAG, "getDevicesMatchingConnectionStates(): states=" + Arrays.toString(states)); HidDevService service = getService(); if (service == null) { return new ArrayList(0); } return service.getDevicesMatchingConnectionStates(states); } } @Override protected IProfileServiceBinder initBinder() { return new BluetoothHidDeviceBinder(this); } private boolean checkDevice(BluetoothDevice device) { if (mHidDevice == null || !mHidDevice.equals(device)) { Log.w(TAG, "Unknown device: " + device); return false; } return true; } synchronized boolean registerApp(BluetoothHidDeviceAppConfiguration config, BluetoothHidDeviceAppSdpSettings sdp, BluetoothHidDeviceAppQosSettings inQos, BluetoothHidDeviceAppQosSettings outQos, IBluetoothHidDeviceCallback callback) { if (DBG) Log.v(TAG, "registerApp()"); if (mAppConfig != null) { return false; } mAppConfig = config; mCallback = callback; return registerAppNative(sdp.name, sdp.description, sdp.provider, sdp.subclass, sdp.descriptors, inQos == null ? null : inQos.toArray(), outQos == null ? null : outQos.toArray()); } synchronized boolean unregisterApp(BluetoothHidDeviceAppConfiguration config) { if (DBG) Log.v(TAG, "unregisterApp()"); if (mAppConfig == null || config == null || !config.equals(mAppConfig)) { return false; } return unregisterAppNative(); } synchronized boolean sendReport(BluetoothDevice device, int id, byte[] data) { if (DBG) Log.v(TAG, "sendReport(): device=" + device + " id=" + id); if (!checkDevice(device)) { return false; } return sendReportNative(id, data); } synchronized boolean replyReport(BluetoothDevice device, byte type, byte id, byte[] data) { if (DBG) Log.v(TAG, "replyReport(): device=" + device + " type=" + type + " id=" + id); if (!checkDevice(device)) { return false; } return replyReportNative(type, id, data); } synchronized boolean unplug(BluetoothDevice device) { if (DBG) Log.v(TAG, "unplug(): device=" + device); if (!checkDevice(device)) { return false; } return unplugNative(); } synchronized boolean connect(BluetoothDevice device) { if (DBG) Log.v(TAG, "connect(): device=" + device); return connectNative(Utils.getByteAddress(device)); } synchronized boolean disconnect(BluetoothDevice device) { if (DBG) Log.v(TAG, "disconnect(): device=" + device); if (!checkDevice(device)) { return false; } return disconnectNative(); } synchronized boolean reportError(BluetoothDevice device, byte error) { if (DBG) Log.v(TAG, "reportError(): device=" + device + " error=" + error); if (!checkDevice(device)) { return false; } return reportErrorNative(error); } @Override protected boolean start() { if (DBG) Log.d(TAG, "start()"); initNative(); mNativeAvailable = true; return true; } @Override protected boolean stop() { if (DBG) Log.d(TAG, "stop()"); return true; } @Override protected boolean cleanup() { if (DBG) Log.d(TAG, "cleanup()"); if (mNativeAvailable) { cleanupNative(); mNativeAvailable = false; } return true; } int getConnectionState(BluetoothDevice device) { if (mHidDevice != null && mHidDevice.equals(device)) { return mHidDeviceState; } return BluetoothInputHost.STATE_DISCONNECTED; } List getDevicesMatchingConnectionStates(int[] states) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); List inputDevices = new ArrayList(); if (mHidDevice != null) { for (int state : states) { if (state == mHidDeviceState) { inputDevices.add(mHidDevice); break; } } } return inputDevices; } private synchronized void onApplicationStateChanged(byte[] address, boolean registered) { if (DBG) Log.v(TAG, "onApplicationStateChanged(): registered=" + registered); Message msg = mHandler.obtainMessage(MESSAGE_APPLICATION_STATE_CHANGED); msg.obj = address; msg.arg1 = registered ? 1 : 0; mHandler.sendMessage(msg); } private synchronized void onConnectStateChanged(byte[] address, int state) { if (DBG) Log.v(TAG, "onConnectStateChanged(): address=" + Arrays.toString(address) + " state=" + state); Message msg = mHandler.obtainMessage(MESSAGE_CONNECT_STATE_CHANGED); msg.obj = address; msg.arg1 = state; mHandler.sendMessage(msg); } private synchronized void onGetReport(byte type, byte id, short bufferSize) { if (DBG) Log.v(TAG, "onGetReport(): type=" + type + " id=" + id + " bufferSize=" + bufferSize); Message msg = mHandler.obtainMessage(MESSAGE_GET_REPORT); msg.obj = bufferSize > 0 ? new Integer(bufferSize) : null; msg.arg1 = type; msg.arg2 = id; mHandler.sendMessage(msg); } private synchronized void onSetReport(byte reportType, byte reportId, byte[] data) { if (DBG) Log.v(TAG, "onSetReport(): reportType=" + reportType + " reportId=" + reportId); ByteBuffer bb = ByteBuffer.wrap(data); Message msg = mHandler.obtainMessage(MESSAGE_SET_REPORT); msg.arg1 = reportType; msg.arg2 = reportId; msg.obj = bb; mHandler.sendMessage(msg); } private synchronized void onSetProtocol(byte protocol) { if (DBG) Log.v(TAG, "onSetProtocol(): protocol=" + protocol); Message msg = mHandler.obtainMessage(MESSAGE_SET_PROTOCOL); msg.arg1 = protocol; mHandler.sendMessage(msg); } private synchronized void onIntrData(byte reportId, byte[] data) { if (DBG) Log.v(TAG, "onIntrData(): reportId=" + reportId); ByteBuffer bb = ByteBuffer.wrap(data); Message msg = mHandler.obtainMessage(MESSAGE_INTR_DATA); msg.arg1 = reportId; msg.obj = bb; mHandler.sendMessage(msg); } private synchronized void onVirtualCableUnplug() { if (DBG) Log.v(TAG, "onVirtualCableUnplug()"); Message msg = mHandler.obtainMessage(MESSAGE_VC_UNPLUG); mHandler.sendMessage(msg); } private void broadcastConnectionState(BluetoothDevice device, int newState) { if (DBG) Log.v(TAG, "broadcastConnectionState(): device=" + device.getAddress() + " newState=" + newState); if (mHidDevice != null && !mHidDevice.equals(device)) { Log.w(TAG, "Connection state changed for unknown device, ignoring"); return; } int prevState = mHidDeviceState; mHidDeviceState = newState; Log.i(TAG, "connection state for " + device.getAddress() + ": " + prevState + " -> " + newState); if (prevState == newState) { return; } Intent intent = new Intent(BluetoothInputHost.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); } 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: return BluetoothProfile.STATE_DISCONNECTED; } } 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 initNative(); private native void cleanupNative(); private native boolean registerAppNative(String name, String description, String provider, byte subclass, byte[] descriptors, int[] inQos, int[] outQos); private native boolean unregisterAppNative(); private native boolean sendReportNative(int id, byte[] data); private native boolean replyReportNative(byte type, byte id, byte[] data); private native boolean unplugNative(); private native boolean connectNative(byte[] btAddress); private native boolean disconnectNative(); private native boolean reportErrorNative(byte error); }