blob: 7352d5833bc9bf2c5a59ce5518b3ee6fde79e8a6 [file] [log] [blame]
/*
* 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.hid;
import static android.Manifest.permission.BLUETOOTH_CONNECT;
import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission;
import android.annotation.RequiresPermission;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHidHost;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.IBluetoothHidHost;
import android.content.AttributionSource;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.UserHandle;
import android.sysprop.BluetoothProperties;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import com.android.bluetooth.BluetoothMetricsProto;
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.MetricsLogger;
import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.btservice.storage.DatabaseManager;
import com.android.modules.utils.SynchronousResultReceiver;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* Provides Bluetooth Hid Host profile, as a service in
* the Bluetooth application.
* @hide
*/
public class HidHostService extends ProfileService {
private static final boolean DBG = false;
private static final String TAG = "BluetoothHidHostService";
private Map<BluetoothDevice, Integer> mInputDevices;
private boolean mNativeAvailable;
private static HidHostService sHidHostService;
private BluetoothDevice mTargetDevice = null;
private DatabaseManager mDatabaseManager;
private AdapterService mAdapterService;
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_ON_VIRTUAL_UNPLUG = 12;
private static final int MESSAGE_ON_HANDSHAKE = 13;
private static final int MESSAGE_GET_IDLE_TIME = 14;
private static final int MESSAGE_ON_GET_IDLE_TIME = 15;
private static final int MESSAGE_SET_IDLE_TIME = 16;
static {
classInitNative();
}
public static boolean isEnabled() {
return BluetoothProperties.isProfileHidHostEnabled().orElse(false);
}
@Override
public IProfileServiceBinder initBinder() {
return new BluetoothHidHostBinder(this);
}
@Override
protected boolean start() {
mDatabaseManager = Objects.requireNonNull(AdapterService.getAdapterService().getDatabase(),
"DatabaseManager cannot be null when HidHostService starts");
mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(),
"AdapterService cannot be null when HidHostService starts");
mInputDevices = Collections.synchronizedMap(new HashMap<BluetoothDevice, Integer>());
initializeNative();
mNativeAvailable = true;
setHidHostService(this);
return true;
}
@Override
protected boolean stop() {
if (DBG) {
Log.d(TAG, "Stopping Bluetooth HidHostService");
}
return true;
}
@Override
protected void cleanup() {
if (DBG) Log.d(TAG, "Stopping Bluetooth HidHostService");
if (mNativeAvailable) {
cleanupNative();
mNativeAvailable = false;
}
if (mInputDevices != null) {
for (BluetoothDevice device : mInputDevices.keySet()) {
int inputDeviceState = getConnectionState(device);
if (inputDeviceState != BluetoothProfile.STATE_DISCONNECTED) {
broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED);
}
}
mInputDevices.clear();
}
// TODO(b/72948646): this should be moved to stop()
setHidHostService(null);
}
private BluetoothDevice getDevice(byte[] address) {
return mAdapterService.getDeviceFromByte(address);
}
private byte[] getByteAddress(BluetoothDevice device) {
return mAdapterService.getByteIdentityAddress(device);
}
public static synchronized HidHostService getHidHostService() {
if (sHidHostService == null) {
Log.w(TAG, "getHidHostService(): service is null");
return null;
}
if (!sHidHostService.isAvailable()) {
Log.w(TAG, "getHidHostService(): service is not available ");
return null;
}
return sHidHostService;
}
private static synchronized void setHidHostService(HidHostService instance) {
if (DBG) {
Log.d(TAG, "setHidHostService(): set to: " + instance);
}
sHidHostService = instance;
}
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_CONNECT: {
BluetoothDevice device = (BluetoothDevice) msg.obj;
if (!connectHidNative(getByteAddress(device))) {
broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTING);
broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED);
break;
}
mTargetDevice = device;
}
break;
case MESSAGE_DISCONNECT: {
BluetoothDevice device = (BluetoothDevice) msg.obj;
if (!disconnectHidNative(getByteAddress(device))) {
broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTING);
broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED);
break;
}
}
break;
case MESSAGE_CONNECT_STATE_CHANGED: {
BluetoothDevice device = mAdapterService.getDeviceFromByte((byte[]) msg.obj);
int halState = msg.arg1;
Integer prevStateInteger = mInputDevices.get(device);
int prevState =
(prevStateInteger == null) ? BluetoothHidHost.STATE_DISCONNECTED
: prevStateInteger;
if (DBG) {
Log.d(TAG, "MESSAGE_CONNECT_STATE_CHANGED newState:" + convertHalState(
halState) + ", prevState:" + prevState);
}
if (halState == CONN_STATE_CONNECTED
&& prevState == BluetoothHidHost.STATE_DISCONNECTED
&& (!okToConnect(device))) {
if (DBG) {
Log.d(TAG, "Incoming HID connection rejected");
}
virtualUnPlugNative(getByteAddress(device));
} else {
broadcastConnectionState(device, convertHalState(halState));
}
if (halState == CONN_STATE_CONNECTED && (mTargetDevice != null
&& mTargetDevice.equals(device))) {
mTargetDevice = null;
// local device originated connection to hid device, move out
// of quiet mode
AdapterService adapterService = AdapterService.getAdapterService();
adapterService.enable(false);
}
}
break;
case MESSAGE_GET_PROTOCOL_MODE: {
BluetoothDevice device = (BluetoothDevice) msg.obj;
if (!getProtocolModeNative(getByteAddress(device))) {
Log.e(TAG, "Error: get protocol mode native returns false");
}
}
break;
case MESSAGE_ON_GET_PROTOCOL_MODE: {
BluetoothDevice device = mAdapterService.getDeviceFromByte((byte[]) msg.obj);
int protocolMode = msg.arg1;
broadcastProtocolMode(device, protocolMode);
}
break;
case MESSAGE_VIRTUAL_UNPLUG: {
BluetoothDevice device = (BluetoothDevice) msg.obj;
if (!virtualUnPlugNative(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.d(TAG, "sending set protocol mode(" + protocolMode + ")");
if (!setProtocolModeNative(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(BluetoothHidHost.EXTRA_REPORT_TYPE);
byte reportId = data.getByte(BluetoothHidHost.EXTRA_REPORT_ID);
int bufferSize = data.getInt(BluetoothHidHost.EXTRA_REPORT_BUFFER_SIZE);
if (!getReportNative(getByteAddress(device), reportType, reportId,
bufferSize)) {
Log.e(TAG, "Error: get report native returns false");
}
}
break;
case MESSAGE_ON_GET_REPORT: {
BluetoothDevice device = mAdapterService.getDeviceFromByte((byte[]) msg.obj);
Bundle data = msg.getData();
byte[] report = data.getByteArray(BluetoothHidHost.EXTRA_REPORT);
int bufferSize = data.getInt(BluetoothHidHost.EXTRA_REPORT_BUFFER_SIZE);
broadcastReport(device, report, bufferSize);
}
break;
case MESSAGE_ON_HANDSHAKE: {
BluetoothDevice device = mAdapterService.getDeviceFromByte((byte[]) msg.obj);
int status = msg.arg1;
broadcastHandshake(device, status);
}
break;
case MESSAGE_SET_REPORT: {
BluetoothDevice device = (BluetoothDevice) msg.obj;
Bundle data = msg.getData();
byte reportType = data.getByte(BluetoothHidHost.EXTRA_REPORT_TYPE);
String report = data.getString(BluetoothHidHost.EXTRA_REPORT);
if (!setReportNative(getByteAddress(device), reportType, report)) {
Log.e(TAG, "Error: set report native returns false");
}
}
break;
case MESSAGE_ON_VIRTUAL_UNPLUG: {
BluetoothDevice device = mAdapterService.getDeviceFromByte((byte[]) msg.obj);
int status = msg.arg1;
broadcastVirtualUnplugStatus(device, status);
}
break;
case MESSAGE_GET_IDLE_TIME: {
BluetoothDevice device = (BluetoothDevice) msg.obj;
if (!getIdleTimeNative(getByteAddress(device))) {
Log.e(TAG, "Error: get idle time native returns false");
}
}
break;
case MESSAGE_ON_GET_IDLE_TIME: {
BluetoothDevice device = mAdapterService.getDeviceFromByte((byte[]) msg.obj);
int idleTime = msg.arg1;
broadcastIdleTime(device, idleTime);
}
break;
case MESSAGE_SET_IDLE_TIME: {
BluetoothDevice device = (BluetoothDevice) msg.obj;
Bundle data = msg.getData();
byte idleTime = data.getByte(BluetoothHidHost.EXTRA_IDLE_TIME);
if (!setIdleTimeNative(getByteAddress(device), idleTime)) {
Log.e(TAG, "Error: get idle time native returns false");
}
}
break;
}
}
};
/**
* Handlers for incoming service calls
*/
@VisibleForTesting
static class BluetoothHidHostBinder extends IBluetoothHidHost.Stub
implements IProfileServiceBinder {
private HidHostService mService;
BluetoothHidHostBinder(HidHostService svc) {
mService = svc;
}
@Override
public void cleanup() {
mService = null;
}
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
private HidHostService getService(AttributionSource source) {
if (Utils.isInstrumentationTestMode()) {
return mService;
}
if (!Utils.checkServiceAvailable(mService, TAG)
|| !Utils.checkCallerIsSystemOrActiveOrManagedUser(mService, TAG)
|| !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) {
return null;
}
return mService;
}
@Override
public void connect(BluetoothDevice device, AttributionSource source,
SynchronousResultReceiver receiver) {
try {
HidHostService service = getService(source);
boolean defaultValue = false;
if (service != null) {
enforceBluetoothPrivilegedPermission(service);
defaultValue = service.connect(device);
}
receiver.send(defaultValue);
} catch (RuntimeException e) {
receiver.propagateException(e);
}
}
@Override
public void disconnect(BluetoothDevice device, AttributionSource source,
SynchronousResultReceiver receiver) {
try {
HidHostService service = getService(source);
boolean defaultValue = false;
if (service != null) {
enforceBluetoothPrivilegedPermission(service);
defaultValue = service.disconnect(device);
}
receiver.send(defaultValue);
} catch (RuntimeException e) {
receiver.propagateException(e);
}
}
@Override
public void getConnectionState(BluetoothDevice device, AttributionSource source,
SynchronousResultReceiver receiver) {
try {
HidHostService service = getService(source);
int defaultValue = BluetoothHidHost.STATE_DISCONNECTED;
if (service != null) {
defaultValue = service.getConnectionState(device);
}
receiver.send(defaultValue);
} catch (RuntimeException e) {
receiver.propagateException(e);
}
}
@Override
public void getConnectedDevices(AttributionSource source,
SynchronousResultReceiver receiver) {
getDevicesMatchingConnectionStates(new int[] { BluetoothProfile.STATE_CONNECTED },
source, receiver);
}
@Override
public void getDevicesMatchingConnectionStates(int[] states,
AttributionSource source, SynchronousResultReceiver receiver) {
try {
HidHostService service = getService(source);
List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(0);
if (service != null) {
defaultValue = service.getDevicesMatchingConnectionStates(states);
}
receiver.send(defaultValue);
} catch (RuntimeException e) {
receiver.propagateException(e);
}
}
@Override
public void setConnectionPolicy(BluetoothDevice device, int connectionPolicy,
AttributionSource source, SynchronousResultReceiver receiver) {
try {
HidHostService service = getService(source);
boolean defaultValue = false;
if (service != null) {
enforceBluetoothPrivilegedPermission(service);
defaultValue = service.setConnectionPolicy(device, connectionPolicy);
}
receiver.send(defaultValue);
} catch (RuntimeException e) {
receiver.propagateException(e);
}
}
@Override
public void getConnectionPolicy(BluetoothDevice device, AttributionSource source,
SynchronousResultReceiver receiver) {
try {
HidHostService service = getService(source);
int defaultValue = BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
if (service != null) {
enforceBluetoothPrivilegedPermission(service);
defaultValue = service.getConnectionPolicy(device);
}
receiver.send(defaultValue);
} catch (RuntimeException e) {
receiver.propagateException(e);
}
}
/* The following APIs regarding test app for compliance */
@Override
public void getProtocolMode(BluetoothDevice device, AttributionSource source,
SynchronousResultReceiver receiver) {
try {
HidHostService service = getService(source);
boolean defaultValue = false;
if (service != null) {
defaultValue = service.getProtocolMode(device);
}
receiver.send(defaultValue);
} catch (RuntimeException e) {
receiver.propagateException(e);
}
}
@Override
public void virtualUnplug(BluetoothDevice device, AttributionSource source,
SynchronousResultReceiver receiver) {
try {
HidHostService service = getService(source);
boolean defaultValue = false;
if (service != null) {
defaultValue = service.virtualUnplug(device);
}
receiver.send(defaultValue);
} catch (RuntimeException e) {
receiver.propagateException(e);
}
}
@Override
public void setProtocolMode(BluetoothDevice device, int protocolMode,
AttributionSource source, SynchronousResultReceiver receiver) {
try {
HidHostService service = getService(source);
boolean defaultValue = false;
if (service != null) {
defaultValue = service.setProtocolMode(device, protocolMode);
}
receiver.send(defaultValue);
} catch (RuntimeException e) {
receiver.propagateException(e);
}
}
@Override
public void getReport(BluetoothDevice device, byte reportType, byte reportId,
int bufferSize, AttributionSource source, SynchronousResultReceiver receiver) {
try {
HidHostService service = getService(source);
boolean defaultValue = false;
if (service != null) {
defaultValue = service.getReport(device, reportType, reportId, bufferSize);
}
receiver.send(defaultValue);
} catch (RuntimeException e) {
receiver.propagateException(e);
}
}
@Override
public void setReport(BluetoothDevice device, byte reportType, String report,
AttributionSource source, SynchronousResultReceiver receiver) {
try {
HidHostService service = getService(source);
boolean defaultValue = false;
if (service != null) {
defaultValue = service.setReport(device, reportType, report);
}
receiver.send(defaultValue);
} catch (RuntimeException e) {
receiver.propagateException(e);
}
}
@Override
public void sendData(BluetoothDevice device, String report, AttributionSource source,
SynchronousResultReceiver receiver) {
try {
HidHostService service = getService(source);
boolean defaultValue = false;
if (service != null) {
defaultValue = service.sendData(device, report);
}
receiver.send(defaultValue);
} catch (RuntimeException e) {
receiver.propagateException(e);
}
}
@Override
public void setIdleTime(BluetoothDevice device, byte idleTime,
AttributionSource source, SynchronousResultReceiver receiver) {
try {
HidHostService service = getService(source);
boolean defaultValue = false;
if (service != null) {
defaultValue = service.setIdleTime(device, idleTime);
}
receiver.send(defaultValue);
} catch (RuntimeException e) {
receiver.propagateException(e);
}
}
@Override
public void getIdleTime(BluetoothDevice device, AttributionSource source,
SynchronousResultReceiver receiver) {
try {
HidHostService service = getService(source);
boolean defaultValue = false;
if (service != null) {
defaultValue = service.getIdleTime(device);
}
receiver.send(defaultValue);
} catch (RuntimeException e) {
receiver.propagateException(e);
}
}
}
;
//APIs
/**
* Connects the hid host profile for the passed in device
*
* @param device is the device with which to connect the hid host profile
* @return true if connection request is passed down to mHandler.
*/
public boolean connect(BluetoothDevice device) {
if (DBG) Log.d(TAG, "connect: " + device.getAddress());
if (getConnectionState(device) != BluetoothHidHost.STATE_DISCONNECTED) {
Log.e(TAG, "Hid Device not disconnected: " + device);
return false;
}
if (getConnectionPolicy(device) == BluetoothHidHost.CONNECTION_POLICY_FORBIDDEN) {
Log.e(TAG, "Hid Device CONNECTION_POLICY_FORBIDDEN: " + device);
return false;
}
Message msg = mHandler.obtainMessage(MESSAGE_CONNECT, device);
mHandler.sendMessage(msg);
return true;
}
/**
* Disconnects the hid host profile from the passed in device
*
* @param device is the device with which to disconnect the hid host profile
* @return true
*/
public boolean disconnect(BluetoothDevice device) {
if (DBG) Log.d(TAG, "disconnect: " + device.getAddress());
Message msg = mHandler.obtainMessage(MESSAGE_DISCONNECT, device);
mHandler.sendMessage(msg);
return true;
}
/**
* Get the current connection state of the profile
*
* @param device is the remote bluetooth device
* @return {@link BluetoothProfile#STATE_DISCONNECTED} if this profile is disconnected,
* {@link BluetoothProfile#STATE_CONNECTING} if this profile is being connected,
* {@link BluetoothProfile#STATE_CONNECTED} if this profile is connected, or
* {@link BluetoothProfile#STATE_DISCONNECTING} if this profile is being disconnected
*/
public int getConnectionState(BluetoothDevice device) {
if (DBG) Log.d(TAG, "getConnectionState: " + device.getAddress());
if (mInputDevices.get(device) == null) {
return BluetoothHidHost.STATE_DISCONNECTED;
}
return mInputDevices.get(device);
}
List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
if (DBG) Log.d(TAG, "getDevicesMatchingConnectionStates()");
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;
}
/**
* Set connection policy of the profile and connects it if connectionPolicy is
* {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is
* {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}
*
* <p> The device should already be paired.
* Connection policy can be one of:
* {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
* {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
* {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
*
* @param device Paired bluetooth device
* @param connectionPolicy is the connection policy to set to for this profile
* @return true if connectionPolicy is set, false on error
*/
public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
if (DBG) {
Log.d(TAG, "setConnectionPolicy: " + device.getAddress());
}
if (!mDatabaseManager.setProfileConnectionPolicy(device, BluetoothProfile.HID_HOST,
connectionPolicy)) {
return false;
}
if (DBG) {
Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
}
if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
connect(device);
} else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
disconnect(device);
}
return true;
}
/**
* Get the connection policy of the profile.
*
* <p> The connection policy can be any of:
* {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
* {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
* {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
*
* @param device Bluetooth device
* @return connection policy of the device
* @hide
*/
public int getConnectionPolicy(BluetoothDevice device) {
if (DBG) {
Log.d(TAG, "getConnectionPolicy: " + device.getAddress());
}
return mDatabaseManager
.getProfileConnectionPolicy(device, BluetoothProfile.HID_HOST);
}
/* The following APIs regarding test app for compliance */
boolean getProtocolMode(BluetoothDevice device) {
if (DBG) {
Log.d(TAG, "getProtocolMode: " + device.getAddress());
}
int state = this.getConnectionState(device);
if (state != BluetoothHidHost.STATE_CONNECTED) {
return false;
}
Message msg = mHandler.obtainMessage(MESSAGE_GET_PROTOCOL_MODE, device);
mHandler.sendMessage(msg);
return true;
}
boolean virtualUnplug(BluetoothDevice device) {
if (DBG) {
Log.d(TAG, "virtualUnplug: " + device.getAddress());
}
int state = this.getConnectionState(device);
if (state != BluetoothHidHost.STATE_CONNECTED) {
return false;
}
Message msg = mHandler.obtainMessage(MESSAGE_VIRTUAL_UNPLUG, device);
mHandler.sendMessage(msg);
return true;
}
boolean setProtocolMode(BluetoothDevice device, int protocolMode) {
if (DBG) {
Log.d(TAG, "setProtocolMode: " + device.getAddress());
}
int state = this.getConnectionState(device);
if (state != BluetoothHidHost.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) {
if (DBG) {
Log.d(TAG, "getReport: " + device.getAddress());
}
int state = this.getConnectionState(device);
if (state != BluetoothHidHost.STATE_CONNECTED) {
return false;
}
Message msg = mHandler.obtainMessage(MESSAGE_GET_REPORT);
msg.obj = device;
Bundle data = new Bundle();
data.putByte(BluetoothHidHost.EXTRA_REPORT_TYPE, reportType);
data.putByte(BluetoothHidHost.EXTRA_REPORT_ID, reportId);
data.putInt(BluetoothHidHost.EXTRA_REPORT_BUFFER_SIZE, bufferSize);
msg.setData(data);
mHandler.sendMessage(msg);
return true;
}
boolean setReport(BluetoothDevice device, byte reportType, String report) {
if (DBG) {
Log.d(TAG, "setReport: " + device.getAddress());
}
int state = this.getConnectionState(device);
if (state != BluetoothHidHost.STATE_CONNECTED) {
return false;
}
Message msg = mHandler.obtainMessage(MESSAGE_SET_REPORT);
msg.obj = device;
Bundle data = new Bundle();
data.putByte(BluetoothHidHost.EXTRA_REPORT_TYPE, reportType);
data.putString(BluetoothHidHost.EXTRA_REPORT, report);
msg.setData(data);
mHandler.sendMessage(msg);
return true;
}
boolean sendData(BluetoothDevice device, String report) {
if (DBG) {
Log.d(TAG, "sendData: " + device.getAddress());
}
int state = this.getConnectionState(device);
if (state != BluetoothHidHost.STATE_CONNECTED) {
return false;
}
return sendDataNative(getByteAddress(device), report);
}
boolean getIdleTime(BluetoothDevice device) {
if (DBG) Log.d(TAG, "getIdleTime: " + device.getAddress());
int state = this.getConnectionState(device);
if (state != BluetoothHidHost.STATE_CONNECTED) {
return false;
}
Message msg = mHandler.obtainMessage(MESSAGE_GET_IDLE_TIME, device);
mHandler.sendMessage(msg);
return true;
}
boolean setIdleTime(BluetoothDevice device, byte idleTime) {
if (DBG) Log.d(TAG, "setIdleTime: " + device.getAddress());
int state = this.getConnectionState(device);
if (state != BluetoothHidHost.STATE_CONNECTED) {
return false;
}
Message msg = mHandler.obtainMessage(MESSAGE_SET_IDLE_TIME);
msg.obj = device;
Bundle data = new Bundle();
data.putByte(BluetoothHidHost.EXTRA_IDLE_TIME, idleTime);
msg.setData(data);
mHandler.sendMessage(msg);
return true;
}
private void onGetProtocolMode(byte[] address, int mode) {
if (DBG) Log.d(TAG, "onGetProtocolMode()");
Message msg = mHandler.obtainMessage(MESSAGE_ON_GET_PROTOCOL_MODE);
msg.obj = address;
msg.arg1 = mode;
mHandler.sendMessage(msg);
}
private void onGetIdleTime(byte[] address, int idleTime) {
if (DBG) Log.d(TAG, "onGetIdleTime()");
Message msg = mHandler.obtainMessage(MESSAGE_ON_GET_IDLE_TIME);
msg.obj = address;
msg.arg1 = idleTime;
mHandler.sendMessage(msg);
}
private void onGetReport(byte[] address, byte[] report, int rptSize) {
if (DBG) Log.d(TAG, "onGetReport()");
Message msg = mHandler.obtainMessage(MESSAGE_ON_GET_REPORT);
msg.obj = address;
Bundle data = new Bundle();
data.putByteArray(BluetoothHidHost.EXTRA_REPORT, report);
data.putInt(BluetoothHidHost.EXTRA_REPORT_BUFFER_SIZE, rptSize);
msg.setData(data);
mHandler.sendMessage(msg);
}
private void onHandshake(byte[] address, int status) {
if (DBG) Log.d(TAG, "onHandshake: status=" + status);
Message msg = mHandler.obtainMessage(MESSAGE_ON_HANDSHAKE);
msg.obj = address;
msg.arg1 = status;
mHandler.sendMessage(msg);
}
private void onVirtualUnplug(byte[] address, int status) {
if (DBG) Log.d(TAG, "onVirtualUnplug: status=" + 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) {
if (DBG) Log.d(TAG, "onConnectStateChanged: state=" + 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) ? BluetoothHidHost.STATE_DISCONNECTED
: prevStateInteger;
if (prevState == newState) {
Log.w(TAG, "no state change: " + newState);
return;
}
if (newState == BluetoothProfile.STATE_CONNECTED) {
MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.HID_HOST);
}
mInputDevices.put(device, newState);
/* Notifying the connection state change of the profile before sending the intent for
connection state change, as it was causing a race condition, with the UI not being
updated with the correct connection state. */
Log.d(TAG, "Connection state " + device + ": " + prevState + "->" + newState);
Intent intent = new Intent(BluetoothHidHost.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);
sendBroadcastAsUser(intent, UserHandle.ALL, BLUETOOTH_CONNECT,
Utils.getTempAllowlistBroadcastOptions());
}
private void broadcastHandshake(BluetoothDevice device, int status) {
Intent intent = new Intent(BluetoothHidHost.ACTION_HANDSHAKE);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
intent.putExtra(BluetoothHidHost.EXTRA_STATUS, status);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions());
}
private void broadcastProtocolMode(BluetoothDevice device, int protocolMode) {
Intent intent = new Intent(BluetoothHidHost.ACTION_PROTOCOL_MODE_CHANGED);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
intent.putExtra(BluetoothHidHost.EXTRA_PROTOCOL_MODE, protocolMode);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions());
if (DBG) {
Log.d(TAG, "Protocol Mode (" + device + "): " + protocolMode);
}
}
private void broadcastReport(BluetoothDevice device, byte[] report, int rptSize) {
Intent intent = new Intent(BluetoothHidHost.ACTION_REPORT);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
intent.putExtra(BluetoothHidHost.EXTRA_REPORT, report);
intent.putExtra(BluetoothHidHost.EXTRA_REPORT_BUFFER_SIZE, rptSize);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions());
}
private void broadcastVirtualUnplugStatus(BluetoothDevice device, int status) {
Intent intent = new Intent(BluetoothHidHost.ACTION_VIRTUAL_UNPLUG_STATUS);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
intent.putExtra(BluetoothHidHost.EXTRA_VIRTUAL_UNPLUG_STATUS, status);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions());
}
private void broadcastIdleTime(BluetoothDevice device, int idleTime) {
Intent intent = new Intent(BluetoothHidHost.ACTION_IDLE_TIME_CHANGED);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
intent.putExtra(BluetoothHidHost.EXTRA_IDLE_TIME, idleTime);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions());
if (DBG) {
Log.d(TAG, "Idle time (" + device + "): " + idleTime);
}
}
/**
* Check whether can connect to a peer device.
* The check considers a number of factors during the evaluation.
*
* @param device the peer device to connect to
* @return true if connection is allowed, otherwise false
*/
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
public boolean okToConnect(BluetoothDevice device) {
AdapterService adapterService = AdapterService.getAdapterService();
// Check if adapter service is null.
if (adapterService == null) {
Log.w(TAG, "okToConnect: adapter service is null");
return false;
}
// Check if this is an incoming connection in Quiet mode.
if (adapterService.isQuietModeEnabled() && mTargetDevice == null) {
Log.w(TAG, "okToConnect: return false as quiet mode enabled");
return false;
}
// Check connection policy and accept or reject the connection.
int connectionPolicy = getConnectionPolicy(device);
int bondState = adapterService.getBondState(device);
// Allow this connection only if the device is bonded. Any attempt to connect while
// bonding would potentially lead to an unauthorized connection.
if (bondState != BluetoothDevice.BOND_BONDED) {
Log.w(TAG, "okToConnect: return false, bondState=" + bondState);
return false;
} else if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_UNKNOWN
&& connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
// Otherwise, reject the connection if connectionPolicy is not valid.
Log.w(TAG, "okToConnect: return false, connectionPolicy=" + connectionPolicy);
return false;
}
return true;
}
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;
}
}
@Override
public void dump(StringBuilder sb) {
super.dump(sb);
println(sb, "mTargetDevice: " + mTargetDevice);
println(sb, "mInputDevices:");
for (BluetoothDevice device : mInputDevices.keySet()) {
println(sb, " " + device + " : " + mInputDevices.get(device));
}
}
// Constants matching Hal header file bt_hh.h
// bthh_connection_state_t
private static final int CONN_STATE_CONNECTED = 0;
private static final int CONN_STATE_CONNECTING = 1;
private static final int CONN_STATE_DISCONNECTED = 2;
private static final int CONN_STATE_DISCONNECTING = 3;
private static native 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);
private native boolean setIdleTimeNative(byte[] btAddress, byte idleTime);
private native boolean getIdleTimeNative(byte[] btAddress);
}