blob: c378f8e8939e8cc2e9c6e89df5bfcaed9b36f3f0 [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 android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHidHost;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.IBluetoothHidHost;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.UserHandle;
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 java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 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 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;
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();
}
@Override
public IProfileServiceBinder initBinder() {
return new BluetoothHidHostBinder(this);
}
@Override
protected boolean start() {
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);
}
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(Utils.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(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;
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(Utils.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(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.d(TAG, "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(BluetoothHidHost.EXTRA_REPORT_TYPE);
byte reportId = data.getByte(BluetoothHidHost.EXTRA_REPORT_ID);
int bufferSize = data.getInt(BluetoothHidHost.EXTRA_REPORT_BUFFER_SIZE);
if (!getReportNative(Utils.getByteAddress(device), reportType, reportId,
bufferSize)) {
Log.e(TAG, "Error: get report native returns false");
}
}
break;
case MESSAGE_ON_GET_REPORT: {
BluetoothDevice device = getDevice((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 = getDevice((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(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(BluetoothHidHost.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;
case MESSAGE_GET_IDLE_TIME: {
BluetoothDevice device = (BluetoothDevice) msg.obj;
if (!getIdleTimeNative(Utils.getByteAddress(device))) {
Log.e(TAG, "Error: get idle time native returns false");
}
}
break;
case MESSAGE_ON_GET_IDLE_TIME: {
BluetoothDevice device = getDevice((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(Utils.getByteAddress(device), idleTime)) {
Log.e(TAG, "Error: get idle time native returns false");
}
}
break;
}
}
};
/**
* Handlers for incoming service calls
*/
private static class BluetoothHidHostBinder extends IBluetoothHidHost.Stub
implements IProfileServiceBinder {
private HidHostService mService;
BluetoothHidHostBinder(HidHostService svc) {
mService = svc;
}
@Override
public void cleanup() {
mService = null;
}
private HidHostService getService() {
if (!Utils.checkCaller()) {
Log.w(TAG, "InputDevice call not allowed for non-active user");
return null;
}
if (mService != null && mService.isAvailable()) {
return mService;
}
Log.w(TAG, "Service is null");
return null;
}
@Override
public boolean connect(BluetoothDevice device) {
HidHostService service = getService();
if (service == null) {
return false;
}
return service.connect(device);
}
@Override
public boolean disconnect(BluetoothDevice device) {
HidHostService service = getService();
if (service == null) {
return false;
}
return service.disconnect(device);
}
@Override
public int getConnectionState(BluetoothDevice device) {
HidHostService service = getService();
if (service == null) {
return BluetoothHidHost.STATE_DISCONNECTED;
}
return service.getConnectionState(device);
}
@Override
public List<BluetoothDevice> getConnectedDevices() {
return getDevicesMatchingConnectionStates(new int[]{BluetoothProfile.STATE_CONNECTED});
}
@Override
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
HidHostService service = getService();
if (service == null) {
return new ArrayList<BluetoothDevice>(0);
}
return service.getDevicesMatchingConnectionStates(states);
}
@Override
public boolean setPriority(BluetoothDevice device, int priority) {
HidHostService service = getService();
if (service == null) {
return false;
}
return service.setPriority(device, priority);
}
@Override
public int getPriority(BluetoothDevice device) {
HidHostService service = getService();
if (service == null) {
return BluetoothProfile.PRIORITY_UNDEFINED;
}
return service.getPriority(device);
}
/* The following APIs regarding test app for compliance */
@Override
public boolean getProtocolMode(BluetoothDevice device) {
HidHostService service = getService();
if (service == null) {
return false;
}
return service.getProtocolMode(device);
}
@Override
public boolean virtualUnplug(BluetoothDevice device) {
HidHostService service = getService();
if (service == null) {
return false;
}
return service.virtualUnplug(device);
}
@Override
public boolean setProtocolMode(BluetoothDevice device, int protocolMode) {
HidHostService service = getService();
if (service == null) {
return false;
}
return service.setProtocolMode(device, protocolMode);
}
@Override
public boolean getReport(BluetoothDevice device, byte reportType, byte reportId,
int bufferSize) {
HidHostService service = getService();
if (service == null) {
return false;
}
return service.getReport(device, reportType, reportId, bufferSize);
}
@Override
public boolean setReport(BluetoothDevice device, byte reportType, String report) {
HidHostService service = getService();
if (service == null) {
return false;
}
return service.setReport(device, reportType, report);
}
@Override
public boolean sendData(BluetoothDevice device, String report) {
HidHostService service = getService();
if (service == null) {
return false;
}
return service.sendData(device, report);
}
@Override
public boolean setIdleTime(BluetoothDevice device, byte idleTime) {
HidHostService service = getService();
if (service == null) {
return false;
}
return service.setIdleTime(device, idleTime);
}
@Override
public boolean getIdleTime(BluetoothDevice device) {
HidHostService service = getService();
if (service == null) {
return false;
}
return service.getIdleTime(device);
}
}
;
//APIs
boolean connect(BluetoothDevice device) {
if (DBG) Log.d(TAG, "connect: " + device.getAddress());
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
if (getConnectionState(device) != BluetoothHidHost.STATE_DISCONNECTED) {
Log.e(TAG, "Hid Device not disconnected: " + device);
return false;
}
if (getPriority(device) == BluetoothHidHost.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) {
if (DBG) Log.d(TAG, "disconnect: " + device.getAddress());
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
Message msg = mHandler.obtainMessage(MESSAGE_DISCONNECT, device);
mHandler.sendMessage(msg);
return true;
}
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()");
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;
}
public boolean setPriority(BluetoothDevice device, int priority) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
if (DBG) {
Log.d(TAG, "setPriority: " + device.getAddress());
}
if (DBG) {
Log.d(TAG, "Saved priority " + device + " = " + priority);
}
AdapterService.getAdapterService().getDatabase()
.setProfilePriority(device, BluetoothProfile.HID_HOST, priority);
return true;
}
public int getPriority(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
if (DBG) {
Log.d(TAG, "getPriority: " + device.getAddress());
}
return AdapterService.getAdapterService().getDatabase()
.getProfilePriority(device, BluetoothProfile.HID_HOST);
}
/* The following APIs regarding test app for compliance */
boolean getProtocolMode(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
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;
/* String objectPath = getObjectPathFromAddress(device.getAddress());
return getProtocolModeInputDeviceNative(objectPath);*/
}
boolean virtualUnplug(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
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) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
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) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
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) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
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) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
if (DBG) {
Log.d(TAG, "sendData: " + device.getAddress());
}
int state = this.getConnectionState(device);
if (state != BluetoothHidHost.STATE_CONNECTED) {
return false;
}
return sendDataNative(Utils.getByteAddress(device), report);
}
boolean getIdleTime(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
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) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
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_PERM);
}
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_PERM);
}
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_PERM);
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_PERM);
}
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_PERM);
}
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_PERM);
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 priority and accept or reject the connection.
int priority = getPriority(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 (priority != BluetoothProfile.PRIORITY_UNDEFINED
&& priority != BluetoothProfile.PRIORITY_ON
&& priority != BluetoothProfile.PRIORITY_AUTO_CONNECT) {
// Otherwise, reject the connection if priority is not valid.
Log.w(TAG, "okToConnect: return false, priority=" + priority);
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);
}