blob: 7783745c3699f5dae9243cdbfc57bbff5ab87dd9 [file] [log] [blame]
/*
* 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.app.ActivityManager;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHidDevice;
import android.bluetooth.BluetoothHidDeviceAppQosSettings;
import android.bluetooth.BluetoothHidDeviceAppSdpSettings;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.IBluetoothHidDevice;
import android.bluetooth.IBluetoothHidDeviceCallback;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
import android.util.Log;
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.internal.annotations.VisibleForTesting;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
/** @hide */
public class HidDeviceService extends ProfileService {
private static final boolean DBG = false;
private static final String TAG = HidDeviceService.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 static final int MESSAGE_IMPORTANCE_CHANGE = 8;
private static final int FOREGROUND_IMPORTANCE_CUTOFF =
ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
private static HidDeviceService sHidDeviceService;
private DatabaseManager mDatabaseManager;
private HidDeviceNativeInterface mHidDeviceNativeInterface;
private boolean mNativeAvailable = false;
private BluetoothDevice mHidDevice;
private int mHidDeviceState = BluetoothHidDevice.STATE_DISCONNECTED;
private int mUserUid = 0;
private IBluetoothHidDeviceCallback mCallback;
private BluetoothHidDeviceDeathRecipient mDeathRcpt;
private ActivityManager mActivityManager;
private HidDeviceServiceHandler mHandler;
private class HidDeviceServiceHandler extends Handler {
@Override
public void handleMessage(Message msg) {
if (DBG) {
Log.d(TAG, "handleMessage(): msg.what=" + msg.what);
}
switch (msg.what) {
case MESSAGE_APPLICATION_STATE_CHANGED: {
BluetoothDevice device = msg.obj != null ? (BluetoothDevice) msg.obj : null;
boolean success = (msg.arg1 != 0);
if (success) {
Log.d(TAG, "App registered, set device to: " + device);
mHidDevice = device;
} else {
mHidDevice = null;
}
try {
if (mCallback != null) {
mCallback.onAppStatusChanged(device, success);
} else {
break;
}
} catch (RemoteException e) {
Log.e(TAG, "e=" + e.toString());
e.printStackTrace();
}
if (success) {
mDeathRcpt = new BluetoothHidDeviceDeathRecipient(HidDeviceService.this);
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) {
mCallback = null;
}
break;
}
case MESSAGE_CONNECT_STATE_CHANGED: {
BluetoothDevice device = (BluetoothDevice) msg.obj;
int halState = msg.arg1;
int state = convertHalState(halState);
if (state != BluetoothHidDevice.STATE_DISCONNECTED) {
mHidDevice = device;
}
setAndBroadcastConnectionState(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.onInterruptData(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;
case MESSAGE_IMPORTANCE_CHANGE:
int importance = msg.arg1;
int uid = msg.arg2;
if (importance > FOREGROUND_IMPORTANCE_CUTOFF
&& uid >= Process.FIRST_APPLICATION_UID) {
unregisterAppUid(uid);
}
break;
}
}
}
private static class BluetoothHidDeviceDeathRecipient implements IBinder.DeathRecipient {
private HidDeviceService mService;
BluetoothHidDeviceDeathRecipient(HidDeviceService service) {
mService = service;
}
@Override
public void binderDied() {
Log.w(TAG, "Binder died, need to unregister app :(");
mService.unregisterApp();
}
public void cleanup() {
mService = null;
}
}
private ActivityManager.OnUidImportanceListener mUidImportanceListener =
new ActivityManager.OnUidImportanceListener() {
@Override
public void onUidImportance(final int uid, final int importance) {
Message message = mHandler.obtainMessage(MESSAGE_IMPORTANCE_CHANGE);
message.arg1 = importance;
message.arg2 = uid;
mHandler.sendMessage(message);
}
};
@VisibleForTesting
static class BluetoothHidDeviceBinder extends IBluetoothHidDevice.Stub
implements IProfileServiceBinder {
private static final String TAG = BluetoothHidDeviceBinder.class.getSimpleName();
private HidDeviceService mService;
BluetoothHidDeviceBinder(HidDeviceService service) {
mService = service;
}
@VisibleForTesting
HidDeviceService getServiceForTesting() {
if (mService != null && mService.isAvailable()) {
return mService;
}
return null;
}
@Override
public void cleanup() {
mService = null;
}
private HidDeviceService 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(BluetoothHidDeviceAppSdpSettings sdp,
BluetoothHidDeviceAppQosSettings inQos, BluetoothHidDeviceAppQosSettings outQos,
IBluetoothHidDeviceCallback callback) {
if (DBG) {
Log.d(TAG, "registerApp()");
}
HidDeviceService service = getService();
if (service == null) {
return false;
}
return service.registerApp(sdp, inQos, outQos, callback);
}
@Override
public boolean unregisterApp() {
if (DBG) {
Log.d(TAG, "unregisterApp()");
}
HidDeviceService service = getService();
if (service == null) {
return false;
}
return service.unregisterApp();
}
@Override
public boolean sendReport(BluetoothDevice device, int id, byte[] data) {
if (DBG) {
Log.d(TAG, "sendReport(): device=" + device + " id=" + id);
}
HidDeviceService 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.d(TAG, "replyReport(): device=" + device + " type=" + type + " id=" + id);
}
HidDeviceService service = getService();
if (service == null) {
return false;
}
return service.replyReport(device, type, id, data);
}
@Override
public boolean unplug(BluetoothDevice device) {
if (DBG) {
Log.d(TAG, "unplug(): device=" + device);
}
HidDeviceService service = getService();
if (service == null) {
return false;
}
return service.unplug(device);
}
@Override
public boolean connect(BluetoothDevice device) {
if (DBG) {
Log.d(TAG, "connect(): device=" + device);
}
HidDeviceService service = getService();
if (service == null) {
return false;
}
return service.connect(device);
}
@Override
public boolean disconnect(BluetoothDevice device) {
if (DBG) {
Log.d(TAG, "disconnect(): device=" + device);
}
HidDeviceService service = getService();
if (service == null) {
return false;
}
return service.disconnect(device);
}
@Override
public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
if (DBG) {
Log.d(TAG, "setConnectionPolicy(): device=" + device + " connectionPolicy="
+ connectionPolicy);
}
HidDeviceService service = getService();
if (service == null) {
return false;
}
return service.setConnectionPolicy(device, connectionPolicy);
}
@Override
public boolean reportError(BluetoothDevice device, byte error) {
if (DBG) {
Log.d(TAG, "reportError(): device=" + device + " error=" + error);
}
HidDeviceService service = getService();
if (service == null) {
return false;
}
return service.reportError(device, error);
}
@Override
public int getConnectionState(BluetoothDevice device) {
if (DBG) {
Log.d(TAG, "getConnectionState(): device=" + device);
}
HidDeviceService service = getService();
if (service == null) {
return BluetoothHidDevice.STATE_DISCONNECTED;
}
return service.getConnectionState(device);
}
@Override
public List<BluetoothDevice> getConnectedDevices() {
if (DBG) {
Log.d(TAG, "getConnectedDevices()");
}
return getDevicesMatchingConnectionStates(new int[]{BluetoothProfile.STATE_CONNECTED});
}
@Override
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
if (DBG) {
Log.d(TAG,
"getDevicesMatchingConnectionStates(): states=" + Arrays.toString(states));
}
HidDeviceService service = getService();
if (service == null) {
return new ArrayList<BluetoothDevice>(0);
}
return service.getDevicesMatchingConnectionStates(states);
}
@Override
public String getUserAppName() {
HidDeviceService service = getService();
if (service == null) {
return "";
}
return service.getUserAppName();
}
}
@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;
}
private boolean checkCallingUid() {
int callingUid = Binder.getCallingUid();
if (callingUid != mUserUid) {
Log.w(TAG, "checkCallingUid(): caller UID doesn't match registered user UID");
return false;
}
return true;
}
synchronized boolean registerApp(BluetoothHidDeviceAppSdpSettings sdp,
BluetoothHidDeviceAppQosSettings inQos, BluetoothHidDeviceAppQosSettings outQos,
IBluetoothHidDeviceCallback callback) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
if (mUserUid != 0) {
Log.w(TAG, "registerApp(): failed because another app is registered");
return false;
}
int callingUid = Binder.getCallingUid();
if (DBG) {
Log.d(TAG, "registerApp(): calling uid=" + callingUid);
}
if (callingUid >= Process.FIRST_APPLICATION_UID
&& mActivityManager.getUidImportance(callingUid) > FOREGROUND_IMPORTANCE_CUTOFF) {
Log.w(TAG, "registerApp(): failed because the app is not foreground");
return false;
}
mUserUid = callingUid;
mCallback = callback;
return mHidDeviceNativeInterface.registerApp(
sdp.getName(),
sdp.getDescription(),
sdp.getProvider(),
sdp.getSubclass(),
sdp.getDescriptors(),
inQos == null
? null
: new int[] {
inQos.getServiceType(),
inQos.getTokenRate(),
inQos.getTokenBucketSize(),
inQos.getPeakBandwidth(),
inQos.getLatency(),
inQos.getDelayVariation()
},
outQos == null
? null
: new int[] {
outQos.getServiceType(),
outQos.getTokenRate(),
outQos.getTokenBucketSize(),
outQos.getPeakBandwidth(),
outQos.getLatency(),
outQos.getDelayVariation()
});
}
synchronized boolean unregisterApp() {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
if (DBG) {
Log.d(TAG, "unregisterApp()");
}
int callingUid = Binder.getCallingUid();
return unregisterAppUid(callingUid);
}
private synchronized boolean unregisterAppUid(int uid) {
if (DBG) {
Log.d(TAG, "unregisterAppUid(): uid=" + uid);
}
if (mUserUid != 0 && (uid == mUserUid || uid < Process.FIRST_APPLICATION_UID)) {
mUserUid = 0;
return mHidDeviceNativeInterface.unregisterApp();
}
if (DBG) {
Log.d(TAG, "unregisterAppUid(): caller UID doesn't match user UID");
}
return false;
}
synchronized boolean sendReport(BluetoothDevice device, int id, byte[] data) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
if (DBG) {
Log.d(TAG, "sendReport(): device=" + device + " id=" + id);
}
return checkDevice(device) && checkCallingUid()
&& mHidDeviceNativeInterface.sendReport(id, data);
}
synchronized boolean replyReport(BluetoothDevice device, byte type, byte id, byte[] data) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
if (DBG) {
Log.d(TAG, "replyReport(): device=" + device + " type=" + type + " id=" + id);
}
return checkDevice(device) && checkCallingUid()
&& mHidDeviceNativeInterface.replyReport(type, id, data);
}
synchronized boolean unplug(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
if (DBG) {
Log.d(TAG, "unplug(): device=" + device);
}
return checkDevice(device) && checkCallingUid()
&& mHidDeviceNativeInterface.unplug();
}
/**
* Connects the Hid device profile for the remote bluetooth device
*
* @param device is the device with which we would like to connect the hid device profile
* @return true if the connection is successful, false otherwise
*/
public synchronized boolean connect(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
if (DBG) {
Log.d(TAG, "connect(): device=" + device);
}
return checkCallingUid() && mHidDeviceNativeInterface.connect(device);
}
/**
* Disconnects the hid device profile for the remote bluetooth device
*
* @param device is the device with which we would like to disconnect the hid device profile
* @return true if the disconnection is successful, false otherwise
*/
public synchronized boolean disconnect(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
if (DBG) {
Log.d(TAG, "disconnect(): device=" + device);
}
int callingUid = Binder.getCallingUid();
if (callingUid != mUserUid && callingUid >= Process.FIRST_APPLICATION_UID) {
Log.w(TAG, "disconnect(): caller UID doesn't match user UID");
return false;
}
return checkDevice(device) && mHidDeviceNativeInterface.disconnect();
}
/**
* Connects Hid Device if connectionPolicy is {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}
* and disconnects Hid device 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 determines whether hid device should be connected or disconnected
* @return true if hid device is connected or disconnected, false otherwise
*/
public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
enforceCallingOrSelfPermission(
BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission");
if (DBG) {
Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
}
if (!mDatabaseManager.setProfileConnectionPolicy(device, BluetoothProfile.HID_DEVICE,
connectionPolicy)) {
return false;
}
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 (device == null) {
throw new IllegalArgumentException("Null device");
}
enforceCallingOrSelfPermission(
BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission");
return mDatabaseManager
.getProfileConnectionPolicy(device, BluetoothProfile.HID_DEVICE);
}
synchronized boolean reportError(BluetoothDevice device, byte error) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
if (DBG) {
Log.d(TAG, "reportError(): device=" + device + " error=" + error);
}
return checkDevice(device) && checkCallingUid()
&& mHidDeviceNativeInterface.reportError(error);
}
synchronized String getUserAppName() {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
if (mUserUid < Process.FIRST_APPLICATION_UID) {
return "";
}
String appName = getPackageManager().getNameForUid(mUserUid);
return appName != null ? appName : "";
}
@Override
protected boolean start() {
if (DBG) {
Log.d(TAG, "start()");
}
mDatabaseManager = Objects.requireNonNull(AdapterService.getAdapterService().getDatabase(),
"DatabaseManager cannot be null when HidDeviceService starts");
mHandler = new HidDeviceServiceHandler();
mHidDeviceNativeInterface = HidDeviceNativeInterface.getInstance();
mHidDeviceNativeInterface.init();
mNativeAvailable = true;
mActivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
mActivityManager.addOnUidImportanceListener(mUidImportanceListener,
FOREGROUND_IMPORTANCE_CUTOFF);
setHidDeviceService(this);
return true;
}
@Override
protected boolean stop() {
if (DBG) {
Log.d(TAG, "stop()");
}
if (sHidDeviceService == null) {
Log.w(TAG, "stop() called before start()");
return true;
}
setHidDeviceService(null);
if (mNativeAvailable) {
mHidDeviceNativeInterface.cleanup();
mNativeAvailable = false;
}
mActivityManager.removeOnUidImportanceListener(mUidImportanceListener);
return true;
}
@Override
public boolean onUnbind(Intent intent) {
Log.d(TAG, "Need to unregister app");
unregisterApp();
return super.onUnbind(intent);
}
/**
* Get the HID Device Service instance
* @return HID Device Service instance
*/
public static synchronized HidDeviceService getHidDeviceService() {
if (sHidDeviceService == null) {
Log.d(TAG, "getHidDeviceService(): service is NULL");
return null;
}
if (!sHidDeviceService.isAvailable()) {
Log.d(TAG, "getHidDeviceService(): service is not available");
return null;
}
return sHidDeviceService;
}
private static synchronized void setHidDeviceService(HidDeviceService instance) {
if (DBG) {
Log.d(TAG, "setHidDeviceService(): set to: " + instance);
}
sHidDeviceService = instance;
}
/**
* Gets the connections state for the hid device profile for the passed in device
*
* @param device is the device whose conenction state we want to verify
* @return current connection state, one of {@link BluetoothProfile#STATE_DISCONNECTED},
* {@link BluetoothProfile#STATE_CONNECTING}, {@link BluetoothProfile#STATE_CONNECTED}, or
* {@link BluetoothProfile#STATE_DISCONNECTING}
*/
public int getConnectionState(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
if (mHidDevice != null && mHidDevice.equals(device)) {
return mHidDeviceState;
}
return BluetoothHidDevice.STATE_DISCONNECTED;
}
List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
List<BluetoothDevice> inputDevices = new ArrayList<BluetoothDevice>();
if (mHidDevice != null) {
for (int state : states) {
if (state == mHidDeviceState) {
inputDevices.add(mHidDevice);
break;
}
}
}
return inputDevices;
}
synchronized void onApplicationStateChangedFromNative(BluetoothDevice device,
boolean registered) {
if (DBG) {
Log.d(TAG, "onApplicationStateChanged(): registered=" + registered);
}
Message msg = mHandler.obtainMessage(MESSAGE_APPLICATION_STATE_CHANGED);
msg.obj = device;
msg.arg1 = registered ? 1 : 0;
mHandler.sendMessage(msg);
}
synchronized void onConnectStateChangedFromNative(BluetoothDevice device, int state) {
if (DBG) {
Log.d(TAG, "onConnectStateChanged(): device="
+ device + " state=" + state);
}
Message msg = mHandler.obtainMessage(MESSAGE_CONNECT_STATE_CHANGED);
msg.obj = device;
msg.arg1 = state;
mHandler.sendMessage(msg);
}
synchronized void onGetReportFromNative(byte type, byte id, short bufferSize) {
if (DBG) {
Log.d(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);
}
synchronized void onSetReportFromNative(byte reportType, byte reportId, byte[] data) {
if (DBG) {
Log.d(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);
}
synchronized void onSetProtocolFromNative(byte protocol) {
if (DBG) {
Log.d(TAG, "onSetProtocol(): protocol=" + protocol);
}
Message msg = mHandler.obtainMessage(MESSAGE_SET_PROTOCOL);
msg.arg1 = protocol;
mHandler.sendMessage(msg);
}
synchronized void onInterruptDataFromNative(byte reportId, byte[] data) {
if (DBG) {
Log.d(TAG, "onInterruptData(): reportId=" + reportId);
}
ByteBuffer bb = ByteBuffer.wrap(data);
Message msg = mHandler.obtainMessage(MESSAGE_INTR_DATA);
msg.arg1 = reportId;
msg.obj = bb;
mHandler.sendMessage(msg);
}
synchronized void onVirtualCableUnplugFromNative() {
if (DBG) {
Log.d(TAG, "onVirtualCableUnplug()");
}
Message msg = mHandler.obtainMessage(MESSAGE_VC_UNPLUG);
mHandler.sendMessage(msg);
}
private void setAndBroadcastConnectionState(BluetoothDevice device, int newState) {
if (DBG) {
Log.d(TAG, "setAndBroadcastConnectionState(): device=" + device.getAddress()
+ " oldState=" + mHidDeviceState + " newState=" + newState);
}
if (mHidDevice != null && !mHidDevice.equals(device)) {
Log.w(TAG, "Connection state changed for unknown device, ignoring");
return;
}
int prevState = mHidDeviceState;
mHidDeviceState = newState;
if (prevState == newState) {
Log.w(TAG, "Connection state is unchanged, ignoring");
return;
}
if (newState == BluetoothProfile.STATE_CONNECTED) {
MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.HID_DEVICE);
}
Intent intent = new Intent(BluetoothHidDevice.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 HAL_CONN_STATE_CONNECTED:
return BluetoothProfile.STATE_CONNECTED;
case HAL_CONN_STATE_CONNECTING:
return BluetoothProfile.STATE_CONNECTING;
case HAL_CONN_STATE_DISCONNECTED:
return BluetoothProfile.STATE_DISCONNECTED;
case HAL_CONN_STATE_DISCONNECTING:
return BluetoothProfile.STATE_DISCONNECTING;
default:
return BluetoothProfile.STATE_DISCONNECTED;
}
}
static final int HAL_CONN_STATE_CONNECTED = 0;
static final int HAL_CONN_STATE_CONNECTING = 1;
static final int HAL_CONN_STATE_DISCONNECTED = 2;
static final int HAL_CONN_STATE_DISCONNECTING = 3;
}