Android 15 CarService源码06-CarInputService
接口¶
ICarInput¶
// packages/services/Car/car-lib/src/android/car/input/ICarInput.aidl
/**
* CarInputManager 的 Binder API
*
* @hide
*/
interface ICarInput {
/**
* 请求捕获输入事件。
*
* @param callback 用于接收输入事件的回调接口。
* @param targetDisplayType 目标显示类型,指定在哪个显示设备上捕获事件。
* @param inputTypes 要捕获的输入类型数组。
* @param requestFlags 请求标志,用于指定捕获行为的附加选项。
* @return 请求的结果状态。
*
* 参见 {@code CarInputManager.requestInputEventCapture(...)}
*/
int requestInputEventCapture(in ICarInputCallback callback, int targetDisplayType,
in int[] inputTypes, int requestFlags) = 1;
/**
* 释放输入事件捕获。
*
* @param callback 用于接收输入事件的回调接口。
* @param targetDisplayType 目标显示类型,指定在哪个显示设备上释放事件捕获。
*
* 参见 {@code CarInputManager.requestInputEventCapture(...)}
*/
void releaseInputEventCapture(in ICarInputCallback callback, int targetDisplayType) = 2;
/**
* 注入按键事件。
*
* @param event 要注入的按键事件。
* @param targetDisplayType 目标显示类型,指定在哪个显示设备上注入事件。
*
* 参见 {@code CarInputManager.injectKeyEvent(...)}
*/
void injectKeyEvent(in KeyEvent event, int targetDisplayType) = 3;
}
ICarInput
接口提供了与汽车输入管理器交互的功能,允许客户端请求和释放输入事件的捕获,以及注入按键事件。
requestInputEventCapture
- 请求捕获特定类型的输入事件。
- 接受一个回调接口
ICarInputCallback
,用于接收捕获的输入事件。 targetDisplayType
指定在哪个显示设备上捕获事件。inputTypes
是一个数组,指定要捕获的输入类型。requestFlags
用于指定捕获行为的附加选项。- 返回一个整数,表示请求的结果状态。
releaseInputEventCapture
- 释放之前请求的输入事件捕获。
- 接受一个回调接口
ICarInputCallback
和targetDisplayType
,用于指定在哪个显示设备上释放事件捕获。
injectKeyEvent
- 注入一个按键事件到指定的显示设备。
- 接受一个
KeyEvent
对象,表示要注入的按键事件,以及targetDisplayType
,指定事件注入的目标显示设备。
ICarInputCallback¶
// packages/services/Car/car-lib/src/android/car/input/ICarInputCallback.aidl
/**
* 输入服务的 Binder API。
*
* @hide
*/
oneway interface ICarInputCallback {
/**
* 当按键事件发生时调用。
*
* @param targetDisplayType 目标显示类型,指定在哪个显示设备上发生事件。
* @param keyEvents 发生的按键事件列表。
*/
void onKeyEvents(int targetDisplayType, in List<KeyEvent> keyEvents) = 1;
/**
* 当旋转事件发生时调用。
*
* @param targetDisplayType 目标显示类型,指定在哪个显示设备上发生事件。
* @param events 发生的旋转事件列表。
*/
void onRotaryEvents(int targetDisplayType, in List<RotaryEvent> events) = 2;
/**
* 当输入捕获状态改变时调用。
*
* @param targetDisplayType 目标显示类型,指定在哪个显示设备上发生状态改变。
* @param activeInputTypes 当前活动的输入类型数组。
*/
void onCaptureStateChanged(int targetDisplayType, in int[] activeInputTypes) = 3;
/**
* 当自定义输入事件发生时调用。
*
* @param targetDisplayType 目标显示类型,指定在哪个显示设备上发生事件。
* @param events 发生的自定义输入事件列表。
*/
void onCustomInputEvents(int targetDisplayType, in List<CustomInputEvent> events) = 4;
}
onKeyEvents
:当按键事件发生时调用。onRotaryEvents
:当旋转事件发生时调用。onCaptureStateChanged
:当输入捕获状态改变时调用。onCustomInputEvents
:当自定义输入事件发生时调用。
CarInputManager¶
CarInputManager
通过 ICarInput
接口调用 CarInputService
,并通过 ICarInputCallback
接口监听 CarInputService
的回调。由于重点不在此,这里不再详细分析。
CarInputManager
如何能调用到 CarInputService
,可以参考 Android 15 CarService源码03-服务及接口 。
初始化¶
根据 Android 15 CarService源码02-服务初始化 的分析,CarService 服务的初始化过程实际上包括以下几个步骤:
-
Native服务的初始化:
- 首先,构建每个
HalService
实例。 - 然后,调用
HalService
的takeProperties()
方法。 - 接着,调用
HalService
的init()
方法。
- 首先,构建每个
-
Java服务的初始化:
- 构建每个
CarSystemService
实例。 - 调用
CarSystemService
的init()
方法。 - 最后,调用
CarSystemService
的onInitComplete()
方法。
- 构建每个
接下来,我们将按照这个思路,从 InputHalService
开始进行分析。
InputHalService¶
InputHalService构造函数¶
// packages/services/Car/service/src/com/android/car/hal/InputHalService.java
/**
* 将 HAL 输入事件转换为input服务。
*/
public class InputHalService extends HalServiceBase {
// VehicleHal 实例,用于与车辆硬件抽象层进行交互
private final VehicleHal mHal;
/**
* 一个函数,用于检索当前系统的运行时间(以毫秒为单位) - 可替换以用于测试。
*/
private final LongSupplier mUptimeSupplier;
/**
* InputHalService 的构造函数。
*
* @param hal VehicleHal 实例,用于与车辆 HAL 进行交互。
*/
public InputHalService(VehicleHal hal) {
this(hal, SystemClock::uptimeMillis); // 调用另一个构造函数,使用系统时钟的运行时间
}
/**
* 用于测试的 InputHalService 构造函数。
*
* @param hal VehicleHal 实例,用于与车辆 HAL 进行交互。
* @param uptimeSupplier 一个 LongSupplier,用于提供系统运行时间。
*/
@VisibleForTesting
InputHalService(VehicleHal hal, LongSupplier uptimeSupplier) {
super(); // 调用父类构造函数
mHal = hal; // 初始化 mHal
mUptimeSupplier = uptimeSupplier; // 初始化 mUptimeSupplier
}
}
InputHalService
是一个服务类,用于将来自硬件抽象层(HAL)的输入事件转换成input服务。
InputHalService.takeProperties()¶
// packages/services/Car/service/src/com/android/car/hal/InputHalService.java
public class InputHalService extends HalServiceBase {
// 用于线程同步的锁对象
private final Object mLock = new Object();
// 以下变量用于标识是否支持特定类型的输入功能,受 mLock 保护以确保线程安全
@GuardedBy("mLock")
private boolean mKeyInputSupported; // 是否支持硬件按键输入
@GuardedBy("mLock")
private boolean mKeyInputV2Supported; // 是否支持硬件按键输入(版本 2)
@GuardedBy("mLock")
private boolean mMotionInputSupported; // 是否支持运动输入(如触摸板或手势)
@GuardedBy("mLock")
private boolean mRotaryInputSupported; // 是否支持旋转输入(如旋钮)
@GuardedBy("mLock")
private boolean mCustomInputSupported; // 是否支持自定义输入
/**
* 处理 HAL 属性配置,判断支持哪些输入功能。
*
* @param properties HAL 属性配置的集合。
*/
@Override
public void takeProperties(Collection<HalPropConfig> properties) {
synchronized (mLock) { // 使用 mLock 确保线程安全
for (HalPropConfig property : properties) { // 遍历所有属性配置
switch (property.getPropId()) { // 根据属性 ID 判断支持的输入类型
case HW_KEY_INPUT: // 硬件按键输入
mKeyInputSupported = true;
break;
case HW_KEY_INPUT_V2: // 硬件按键输入(版本 2)
mKeyInputV2Supported = true;
break;
case HW_MOTION_INPUT: // 运动输入(如触摸板或手势)
mMotionInputSupported = true;
break;
case HW_ROTARY_INPUT: // 旋转输入(如旋钮)
mRotaryInputSupported = true;
break;
case HW_CUSTOM_INPUT: // 自定义输入
mCustomInputSupported = true;
break;
default: // 未知的属性 ID,忽略
break;
}
}
}
}
}
支持的输入类型:
- HW_KEY_INPUT
:硬件按键输入。
- HW_KEY_INPUT_V2
:硬件按键输入(版本 2)。
- HW_MOTION_INPUT
:运动输入(如触摸板或手势)。
- HW_ROTARY_INPUT
:旋转输入(如旋钮)。
- HW_CUSTOM_INPUT
:自定义输入。
InputHalService.init()¶
这里的 init()
函数不做任何操作。
InputHalService.getAllSupportedProperties()¶
实际上,在 VehicleHal.priorityInit()
调用 fetchAllPropConfigs()
获取所有配置的过程中,会通过调用具体服务的 getAllSupportedProperties()
方法来进行判断。
// packages/services/Car/service/src/com/android/car/hal/InputHalService.java
public class InputHalService extends HalServiceBase {
// 定义支持的属性数组,包括各种输入类型
private static final int[] SUPPORTED_PROPERTIES = new int[]{
HW_KEY_INPUT, // 硬件按键输入
HW_KEY_INPUT_V2, // 硬件按键输入(版本 2)
HW_MOTION_INPUT, // 运动输入(如触摸板或手势)
HW_ROTARY_INPUT, // 旋转输入(如旋钮)
HW_CUSTOM_INPUT // 自定义输入
};
/**
* 获取所有支持的属性。
*
* @return 一个整数数组,包含所有支持的属性 ID。
*/
@Override
int[] getAllSupportedProperties() {
return SUPPORTED_PROPERTIES; // 返回支持的属性数组
}
}
InputHalService
类通过定义 SUPPORTED_PROPERTIES
数组和实现 getAllSupportedProperties()
方法,明确了该服务支持的输入类型。
CarInputService¶
CarInputService构造函数¶
// packages/services/Car/service/src/com/android/car/CarInputService.java
/**
* CarInputService 负责通过车辆 HAL(硬件抽象层)监控和处理输入事件。
* 它继承了 ICarInput.Stub,并实现了 CarServiceBase 和 InputHalService.InputListener 接口。
* 该类主要用于管理汽车环境中的输入事件,例如按键事件和触摸事件。
*/
public class CarInputService extends ICarInput.Stub
implements CarServiceBase, InputHalService.InputListener {
// 用于处理语音按键长按事件的计时器
private final KeyPressTimer mVoiceKeyTimer;
// 用于处理呼叫按键长按事件的计时器
private final KeyPressTimer mCallKeyTimer;
// 主显示按键事件的默认处理器。默认情况下,通过 InputManager 将事件注入输入队列,但可以在测试中重写。
private final KeyEventListener mDefaultKeyHandler;
// 主显示触摸事件的默认处理器。默认情况下,通过 InputManager 将事件注入输入队列,但可以在测试中重写。
private final MotionEventListener mDefaultMotionHandler;
// 用于确定按下呼叫按钮是否应结束正在进行的通话的布尔值提供者
private final BooleanSupplier mShouldCallButtonEndOngoingCallSupplier;
/**
* CarInputService 的主构造函数。
*
* @param context 应用程序上下文,用于访问系统服务和资源。
* @param inputHalService 用于与车辆 HAL 通信的服务。
* @param userService 管理用户相关功能的服务。
* @param occupantZoneService 管理车辆座位区域的服务。
* @param bluetoothService 管理车辆蓝牙功能的服务。
* @param carPowerService 管理车辆电源状态的服务。
* @param systemInterface 提供系统接口的抽象层。
*/
public CarInputService(Context context, InputHalService inputHalService,
CarUserService userService, CarOccupantZoneService occupantZoneService,
CarBluetoothService bluetoothService, CarPowerManagementService carPowerService,
SystemInterface systemInterface) {
// 调用另一个构造函数,并传递额外的参数进行初始化。
this(context, inputHalService, userService, occupantZoneService, bluetoothService,
carPowerService, systemInterface,
new Handler(getCommonHandlerThread().getLooper()), // 用于处理消息的 Handler
context.getSystemService(TelecomManager.class), // TelecomManager,用于管理通信相关任务
new KeyEventListener() { // 默认按键事件监听器
@Override
public void onKeyEvent(KeyEvent event, @DisplayTypeEnum int displayType,
@VehicleAreaSeat.Enum int seat) {
// 将按键事件注入到 InputManager 中
InputManagerHelper.injectInputEvent(
context.getSystemService(InputManager.class), event);
}
},
/* defaultMotionHandler= */ event -> InputManagerHelper.injectInputEvent(
context.getSystemService(InputManager.class), event), // 默认触摸事件处理器
/* lastCalledNumberSupplier= */ () -> Calls.getLastOutgoingCall(context), // 获取最后拨打的电话号码
/* longPressDelaySupplier= */ () -> getViewLongPressDelay(context), // 获取长按延迟时间
/* shouldCallButtonEndOngoingCallSupplier= */ () -> context.getResources()
.getBoolean(R.bool.config_callButtonEndsOngoingCall), // 配置是否按下呼叫按钮结束当前通话
new InputCaptureClientController(context), // 输入捕获客户端控制器
sDefaultShowCallback); // 默认的语音交互显示回调
}
/**
* CarInputService 的内部构造函数,提供更细粒度的参数控制。
*
* @param context 应用程序上下文。
* @param inputHalService 用于与车辆 HAL 通信的服务。
* @param userService 管理用户相关功能的服务。
* @param occupantZoneService 管理车辆座位区域的服务。
* @param bluetoothService 管理车辆蓝牙功能的服务。
* @param carPowerService 管理车辆电源状态的服务。
* @param systemInterface 提供系统接口的抽象层。
* @param handler 用于处理异步任务的 Handler。
* @param telecomManager 管理通信功能的服务。
* @param defaultKeyHandler 默认的按键事件处理器。
* @param defaultMotionHandler 默认的触摸事件处理器。
* @param lastCalledNumberSupplier 提供最后拨打号码的接口。
* @param longPressDelaySupplier 提供长按延迟时间的接口。
* @param shouldCallButtonEndOngoingCallSupplier 提供是否按下呼叫按钮结束通话的接口。
* @param captureController 输入捕获客户端控制器。
* @param showCallback 语音交互显示回调。
*/
@VisibleForTesting
CarInputService(Context context, InputHalService inputHalService, CarUserService userService,
CarOccupantZoneService occupantZoneService, CarBluetoothService bluetoothService,
CarPowerManagementService carPowerService, SystemInterface systemInterface,
Handler handler, TelecomManager telecomManager,
KeyEventListener defaultKeyHandler, MotionEventListener defaultMotionHandler,
Supplier<String> lastCalledNumberSupplier, IntSupplier longPressDelaySupplier,
BooleanSupplier shouldCallButtonEndOngoingCallSupplier,
InputCaptureClientController captureController,
VoiceInteractionSessionShowCallbackHelper showCallback) {
super(); // 调用父类构造函数
mContext = context; // 保存上下文
mCaptureController = captureController; // 初始化输入捕获控制器
mInputHalService = inputHalService; // 初始化输入 HAL 服务
mUserService = userService; // 初始化用户服务
mCarOccupantZoneService = occupantZoneService; // 初始化座位区域服务
mCarBluetoothService = bluetoothService; // 初始化蓝牙服务
mCarPowerService = carPowerService; // 初始化电源管理服务
mSystemInterface = systemInterface; // 初始化系统接口
mTelecomManager = telecomManager; // 初始化通信管理服务
mDefaultKeyHandler = defaultKeyHandler; // 初始化默认按键事件处理器
mDefaultMotionHandler = defaultMotionHandler; // 初始化默认触摸事件处理器
mLastCalledNumberSupplier = lastCalledNumberSupplier; // 初始化最后拨打号码提供者
mLongPressDelaySupplier = longPressDelaySupplier; // 初始化长按延迟时间提供者
mShowCallback = showCallback; // 初始化语音交互显示回调
// 初始化语音按键计时器,用于处理语音助手的长按事件
mVoiceKeyTimer = new KeyPressTimer(
handler, longPressDelaySupplier, this::handleVoiceAssistLongPress);
// 初始化呼叫按键计时器,用于处理呼叫按钮的长按事件
mCallKeyTimer = new KeyPressTimer(handler, longPressDelaySupplier,
this::handleCallLongPress);
// 从资源中获取旋钮服务组件的名称
mRotaryServiceComponentName = mContext.getString(R.string.rotaryService);
// 初始化是否按下呼叫按钮结束通话的提供者
mShouldCallButtonEndOngoingCallSupplier = shouldCallButtonEndOngoingCallSupplier;
// 注册默认的特殊按键事件监听器,监听 HOME 和 POWER 按键
registerKeyEventListener(mDefaultSpecialKeyHandler,
Arrays.asList(KeyEvent.KEYCODE_HOME, KeyEvent.KEYCODE_POWER));
}
}
mVoiceKeyTimer
:处理语音助手事件。mCallKeyTimer
:处理呼叫按钮的长按事件。mDefaultKeyHandler
:处理默认的按键事件事件。mDefaultMotionHandler
:处理默认的触摸事件。- 使用
registerKeyEventListener
注册了特殊按键的监听器(例如 HOME 和 POWER 按键)。
CarInputService.init()¶
// packages/services/Car/service/src/com/android/car/CarInputService.java
public class CarInputService extends ICarInput.Stub
implements CarServiceBase, InputHalService.InputListener {
/**
* 初始化方法,用于设置输入服务和用户生命周期监听器。
* 该方法在服务启动时调用,确保输入事件的正确处理。
*/
@Override
public void init() {
// 检查输入 HAL 服务是否支持按键输入
if (!mInputHalService.isKeyInputSupported()) {
// 如果不支持,记录警告日志并返回
Slogf.w(TAG, "Hal does not support key input.");
return;
}
// 如果支持,记录调试日志
Slogf.d(TAG, "Hal supports key input.");
// 设置当前对象为输入 HAL 服务的监听器,以便接收输入事件
mInputHalService.setInputListener(this);
// 创建用户生命周期事件过滤器,监听用户切换事件
UserLifecycleEventFilter userSwitchingEventFilter = new UserLifecycleEventFilter.Builder()
.addEventType(USER_LIFECYCLE_EVENT_TYPE_SWITCHING).build();
// 将用户生命周期监听器添加到用户服务中,以便在用户切换时执行相应操作
mUserService.addUserLifecycleListener(userSwitchingEventFilter, mUserLifecycleListener);
// 获取驾驶员座位信息
mDriverSeat = mCarOccupantZoneService.getDriverSeat();
// 判断是否有驾驶员座位
mHasDriver = (mDriverSeat != VehicleAreaSeat.SEAT_UNKNOWN);
}
}
调用mInputHalService.setInputListener(this)
,将当前对象设置为输入事件的监听器,以便接收来自HAL的输入事件。这里不详细分析,后续在回调章节中再详细分析。
CarInputService.onInitComplete()¶
这里的 CarInputService 不重 CarSystemService.onInitComplete() 方法而直接使用默认实现。
InputHalService.InputListener¶
// packages/services/Car/service/src/com/android/car/hal/InputHalService.java
public class InputHalService extends HalServiceBase {
/**
* 用于处理来自 HAL(硬件抽象层)的输入事件的接口。
* 该接口定义了一组回调方法,用于处理不同类型的输入事件。
* 通过实现该接口,服务可以接收并处理来自车辆硬件的输入事件。
*/
public interface InputListener {
/**
* 当收到按键事件时调用。
*
* @param event 按键事件对象,包含按键的详细信息(例如按下、释放等)。
* @param targetDisplay 目标显示屏的标识符,表示该事件的目标显示屏。
*/
void onKeyEvent(KeyEvent event, int targetDisplay);
/**
* 当收到按键事件(针对特定座位)时调用。
*
* @param event 按键事件对象,包含按键的详细信息。
* @param targetDisplay 目标显示屏的标识符。
* @param seat 座位区域标识符,表示该事件的来源座位。
*/
void onKeyEvent(KeyEvent event, int targetDisplay, int seat);
/**
* 当收到触摸事件(针对特定座位)时调用。
*
* @param event 触摸事件对象,包含触摸的详细信息(例如坐标、动作类型等)。
* @param targetDisplay 目标显示屏的标识符。
* @param seat 座位区域标识符,表示该事件的来源座位。
*/
void onMotionEvent(MotionEvent event, int targetDisplay, int seat);
/**
* 当收到旋钮事件时调用。
*
* @param event 旋钮事件对象,包含旋钮的详细信息(例如旋转方向、旋转量等)。
* @param targetDisplay 目标显示屏的标识符。
*/
void onRotaryEvent(RotaryEvent event, int targetDisplay);
/**
* 当收到 OEM(原始设备制造商)自定义输入事件时调用。
*
* @param event 自定义输入事件对象,包含事件的详细信息。
*/
void onCustomInputEvent(CustomInputEvent event);
}
}
onKeyEvent(KeyEvent event, int targetDisplay)
:用于处理按键事件,targetDisplay
参数用于指定该事件的目标显示屏(例如主显示屏或副显示屏)。onKeyEvent(KeyEvent event, int targetDisplay, int seat)
:用于处理按键事件,但针对特定的座位区域。seat
参数表示事件的来源座位(例如驾驶员座位或乘客座位)。onMotionEvent(MotionEvent event, int targetDisplay, int seat)
:用于处理触摸事件(例如屏幕触摸、滑动等),seat
参数表示事件的来源座位。onRotaryEvent(RotaryEvent event, int targetDisplay)
:用于处理旋钮事件,旋钮事件通常用于控制音量、菜单导航等功能。RotaryEvent
对象包含旋钮的详细信息,例如旋转方向和旋转量。onCustomInputEvent(CustomInputEvent event)
:用于处理OEM自定义输入事件。
CarInputService 监听输入事件¶
// packages/services/Car/service/src/com/android/car/CarInputService.java
public class CarInputService extends ICarInput.Stub
implements CarServiceBase, InputHalService.InputListener {
// 用于与车辆硬件抽象层(HAL)交互的输入服务
private final InputHalService mInputHalService;
@Override
public void init() {
// 将当前对象(CarInputService)设置为输入服务的监听器
// 这样,CarInputService 就可以接收并处理来自 InputHalService 的输入事件
mInputHalService.setInputListener(this);
}
}
InputHalService.setInputListener()¶
// packages/services/Car/service/src/com/android/car/hal/InputHalService.java
public class InputHalService extends HalServiceBase {
// 用于与车辆硬件抽象层(HAL)交互的对象
private final VehicleHal mHal;
// 输入事件监听器,受mLock保护以确保线程安全
@GuardedBy("mLock")
private InputListener mListener;
/**
* 设置输入事件监听器。
* 该方法用于为服务注册一个输入事件监听器,以便处理来自硬件抽象层(HAL)的各种输入事件。
*
* @param listener 实现了 InputListener 接口的监听器对象。
*/
public void setInputListener(InputListener listener) {
// 定义支持的输入类型的标志变量
boolean keyInputSupported;
boolean keyInputV2Supported;
boolean motionInputSupported;
boolean rotaryInputSupported;
boolean customInputSupported;
// 同步块,用于线程安全地访问共享资源
synchronized (mLock) {
// 检查是否支持按键输入、旋钮输入和自定义输入
if (!mKeyInputSupported && !mRotaryInputSupported && !mCustomInputSupported) {
// 如果都不支持,记录警告日志并返回
Slogf.w(TAG, "input listener set while rotary and key input not supported");
return;
}
// 设置监听器
mListener = listener;
// 记录当前支持的输入类型
keyInputSupported = mKeyInputSupported;
keyInputV2Supported = mKeyInputV2Supported;
motionInputSupported = mMotionInputSupported;
rotaryInputSupported = mRotaryInputSupported;
customInputSupported = mCustomInputSupported;
}
// 根据支持的输入类型,订阅相应的硬件属性
if (keyInputSupported) {
mHal.subscribePropertySafe(this, HW_KEY_INPUT);
}
if (keyInputV2Supported) {
mHal.subscribePropertySafe(this, HW_KEY_INPUT_V2);
}
if (motionInputSupported) {
mHal.subscribePropertySafe(this, HW_MOTION_INPUT);
}
if (rotaryInputSupported) {
mHal.subscribePropertySafe(this, HW_ROTARY_INPUT);
}
if (customInputSupported) {
mHal.subscribePropertySafe(this, HW_CUSTOM_INPUT);
}
}
}
通过检查支持的输入类型,并根据支持的类型订阅相应的硬件属性,确保服务能够正确处理来自HAL的输入事件。
mHal
即为 VehicleHal
,因此调用了 VehicleHal.subscribePropertySafe()
方法来订阅属性。关于这一点,在前面的 Android 15 CarService源码04-CarService与Vehicle HAL交互 中已有详细分析,并提到其最终会回调到 onHalEvents()
方法,这里不再重复说明。
InputHalService.onHalEvents()¶
InputHalService.setInputListener()
方法中调用 VehicleHal.subscribePropertySafe()
方法来订阅属性后,事件回调到 InputHalService.onHalEvents()
方法中。
// packages/services/Car/service/src/com/android/car/hal/InputHalService.java
public class InputHalService extends HalServiceBase {
/**
* 处理来自硬件抽象层(HAL)的事件。
* 该方法接收一个包含多个属性值的列表,并根据属性ID将事件分发给相应的处理方法。
*
* @param values 包含多个HalPropValue对象的列表,每个对象代表一个输入事件。
*/
@Override
public void onHalEvents(List<HalPropValue> values) {
InputListener listener;
// 同步块,确保线程安全地访问监听器
synchronized (mLock) {
listener = mListener;
}
// 如果监听器为空,记录警告日志并返回
if (listener == null) {
Slogf.w(TAG, "Input event while listener is null");
return;
}
// 遍历事件列表,处理每个事件
for (int i = 0; i < values.size(); i++) {
HalPropValue value = values.get(i);
// 根据属性ID分发事件
switch (value.getPropId()) {
case HW_KEY_INPUT:
// 处理按键输入事件
dispatchKeyInput(listener, value);
break;
case HW_KEY_INPUT_V2:
// 处理按键输入V2事件
dispatchKeyInputV2(listener, value);
break;
case HW_MOTION_INPUT:
// 处理触摸输入事件
dispatchMotionInput(listener, value);
break;
case HW_ROTARY_INPUT:
// 处理旋钮输入事件
dispatchRotaryInput(listener, value);
break;
case HW_CUSTOM_INPUT:
// 处理自定义输入事件
dispatchCustomInput(listener, value);
break;
default:
// 如果属性ID不匹配任何已知类型,记录错误日志
Slogf.e(TAG, "Wrong event dispatched, prop:0x%x", value.getPropId());
break;
}
}
}
}
mListener
是通过之前调用InputHalService.setInputListener()
方法设置的。onHalEvents
方法遍历输入事件列表,并根据每个事件的属性ID(propId
),将事件分发给相应的处理方法。接下来,我们将逐一分析这些处理方法。
InputHalService.dispatchKeyInput()¶
// packages/services/Car/service/src/com/android/car/hal/InputHalService.java
public class InputHalService extends HalServiceBase {
/**
* 处理按键输入事件的方法。
* 该方法从`HalPropValue`对象中提取按键事件的详细信息,并将其分发给监听器。
*
* @param listener 用于接收和处理事件的输入监听器
* @param value 包含按键事件数据的`HalPropValue`对象
*/
private void dispatchKeyInput(InputListener listener, HalPropValue value) {
int action; // 按键动作(按下或抬起)
int code; // 按键代码
int vehicleDisplay; // 车辆显示器标识
int indentsCount; // 按键缩进计数
try {
// 从`HalPropValue`中提取按键动作
action = (value.getInt32Value(0) == VehicleHwKeyInputAction.ACTION_DOWN)
? KeyEvent.ACTION_DOWN
: KeyEvent.ACTION_UP;
// 提取按键代码
code = value.getInt32Value(1);
// 提取车辆显示器标识
vehicleDisplay = value.getInt32Value(2);
// 提取按键缩进计数,如果未提供则默认为1
indentsCount = value.getInt32ValuesSize() < 4 ? 1 : value.getInt32Value(3);
// 记录调试日志,显示事件的详细信息
Slogf.d(TAG, "hal event code: %d, action: %d, display: %d, number of indents: %d",
code, action, vehicleDisplay, indentsCount);
} catch (Exception e) {
// 如果提取过程中发生异常,记录错误日志并返回
Slogf.e(TAG, "Invalid hal key input event received, int32Values: "
+ value.dumpInt32Values(), e);
return;
}
// 根据缩进计数分发按键事件
while (indentsCount > 0) {
indentsCount--;
// 调用`dispatchKeyEvent`方法,将事件分发给监听器
dispatchKeyEvent(listener, action, code, convertDisplayType(vehicleDisplay));
}
}
}
从 HalPropValue
对象中提取事件的详细信息,循环调用dispatchKeyEvent
方法,将事件分发给监听器。
InputHalService.dispatchKeyEvent()¶
// packages/services/Car/service/src/com/android/car/hal/InputHalService.java
public class InputHalService extends HalServiceBase {
/**
* 使用{@link #mUptimeSupplier}提供的事件时间分发一个KeyEvent。
*
* @param listener 用于接收事件的监听器
* @param action KeyEvent的动作(按下或抬起)
* @param code KeyEvent的按键代码
* @param display 事件关联的目标显示器
*/
private void dispatchKeyEvent(InputListener listener, int action, int code,
@DisplayTypeEnum int display) {
// 调用重载方法,使用当前系统时间作为事件时间
dispatchKeyEvent(listener, action, code, display, mUptimeSupplier.getAsLong());
}
/**
* 分发一个KeyEvent。
*
* @param listener 用于接收事件的监听器
* @param action KeyEvent的动作(按下或抬起)
* @param code KeyEvent的按键代码
* @param display 事件关联的目标显示器
* @param eventTime 事件发生时的系统运行时间(毫秒)
*/
private void dispatchKeyEvent(InputListener listener, int action, int code,
@DisplayTypeEnum int display, long eventTime) {
long downTime; // 按键按下的时间
int repeat; // 按键重复计数
// 同步块,确保线程安全地访问按键状态
synchronized (mKeyStates) {
// 获取按键状态,如果不存在则创建新的状态
KeyState state = mKeyStates.get(code);
if (state == null) {
state = new KeyState();
mKeyStates.put(code, state);
}
// 处理按键按下事件
if (action == KeyEvent.ACTION_DOWN) {
downTime = eventTime; // 设置按下时间为事件时间
repeat = state.mRepeatCount++; // 增加重复计数
state.mLastKeyDownTimestamp = eventTime; // 更新最后按下时间戳
} else {
// 处理按键抬起事件
// 如果没有匹配的按下事件,将按下时间设置为事件时间
// 这在实际中不应该发生,但可以防止HAL的异常情况
downTime =
(state.mLastKeyDownTimestamp == -1)
? eventTime
: state.mLastKeyDownTimestamp;
repeat = 0; // 重置重复计数
state.mRepeatCount = 0; // 重置按键状态的重复计数
}
}
// 创建KeyEvent对象,包含按键事件的详细信息
KeyEvent event = new KeyEvent(
downTime,
eventTime,
action,
code,
repeat,
0 /* deviceId */,
0 /* scancode */,
0 /* flags */,
InputDevice.SOURCE_CLASS_BUTTON);
// 事件的displayId将在CarInputService#onKeyEvent中设置
listener.onKeyEvent(event, display);
}
}
创建 KeyEvent
对象,包含事件的详细信息。调用监听器的 onKeyEvent()
方法,将事件传递给监听器进行处理。
所以最终会回调到 CarInputService.onKeyEvent()
,后面再分析 CarInputService.onKeyEvent()
。
InputHalService.dispatchKeyInputV2()¶
// packages/services/Car/service/src/com/android/car/hal/InputHalService.java
public class InputHalService extends HalServiceBase {
/**
* 处理按键输入事件(版本2)的方法。
* 该方法从`HalPropValue`对象中提取按键事件的详细信息,并将其分发给监听器。
*
* @param listener 用于接收和处理事件的输入监听器
* @param value 包含按键事件数据的`HalPropValue`对象
*/
private void dispatchKeyInputV2(InputListener listener, HalPropValue value) {
final int int32ValuesSize = 4; // 期望的int32数组大小
final int int64ValuesSize = 1; // 期望的int64数组大小
int seat; // 座位ID
int vehicleDisplay; // 车辆显示器标识
int keyCode; // 按键代码
int action; // 按键动作
int repeatCount; // 按键重复计数
long elapsedDownTimeNanos; // 按键按下的时间(纳秒)
long elapsedEventTimeNanos; // 事件发生的时间(纳秒)
int convertedAction; // 转换后的按键动作
int convertedVehicleDisplay; // 转换后的车辆显示器标识
try {
// 获取座位ID
seat = value.getAreaId();
// 检查int32数组大小是否符合预期
if (value.getInt32ValuesSize() < int32ValuesSize) {
Slogf.e(TAG, "Wrong int32 array size for key input v2 from vhal: %d",
value.getInt32ValuesSize());
return;
}
// 提取按键事件的详细信息
vehicleDisplay = value.getInt32Value(0);
keyCode = value.getInt32Value(1);
action = value.getInt32Value(2);
repeatCount = value.getInt32Value(3);
// 检查int64数组大小是否符合预期
if (value.getInt64ValuesSize() < int64ValuesSize) {
Slogf.e(TAG, "Wrong int64 array size for key input v2 from vhal: %d",
value.getInt64ValuesSize());
return;
}
// 提取按键按下的时间
elapsedDownTimeNanos = value.getInt64Value(0);
// 如果日志级别为DEBUG,记录调试日志
if (Slogf.isLoggable(TAG, Log.DEBUG)) {
Slogf.d(TAG, "hal event keyCode: %d, action: %d, display: %d, repeatCount: %d"
+ ", elapsedDownTimeNanos: %d", keyCode, action, vehicleDisplay,
repeatCount, elapsedDownTimeNanos);
}
// 转换按键动作和车辆显示器标识
convertedAction = convertToKeyEventAction(action);
convertedVehicleDisplay = convertDisplayType(vehicleDisplay);
} catch (Exception e) {
// 如果提取过程中发生异常,记录错误日志并返回
Slogf.e(TAG, "Invalid hal key input event received, int32Values: "
+ value.dumpInt32Values() + ", int64Values: " + value.dumpInt64Values(), e);
return;
}
// 确定事件发生的时间
if (action == VehicleHwKeyInputAction.ACTION_DOWN) {
// 对于按下动作,确保事件时间和按下时间相同,以保持KeyEvent.java中定义的不变性
elapsedEventTimeNanos = elapsedDownTimeNanos;
} else {
// 对于其他动作,使用事件的时间戳
elapsedEventTimeNanos = value.getTimestamp();
}
// 分发按键事件(版本2)
dispatchKeyEventV2(listener, convertedAction, keyCode, convertedVehicleDisplay,
toUpTimeMillis(elapsedEventTimeNanos), toUpTimeMillis(elapsedDownTimeNanos),
repeatCount, seat);
}
}
从 HalPropValue
对象中提取事件的详细信息,并将其分发给输入监听器。
我们稍微看下 convertDisplayType()
方法。
/**
* 将车辆显示类型(如 {@link VehicleDisplay#MAIN} 和 {@link VehicleDisplay#INSTRUMENT_CLUSTER})
* 转换为 {@link CarOccupantZoneManager} 中对应的显示类型
* (如 {@link CarOccupantZoneManager#DISPLAY_TYPE_MAIN} 和
* {@link CarOccupantZoneManager#DISPLAY_TYPE_INSTRUMENT_CLUSTER})。
*
* @param vehicleDisplayType 车辆显示类型
* @return 对应的显示类型(定义在 {@link CarOccupantZoneManager} 中),
* 如果传入的参数值不对应于驾驶员的显示类型,则返回
* {@link CarOccupantZoneManager#DISPLAY_TYPE_UNKNOWN}
* @hide
*/
@DisplayTypeEnum
public static int convertDisplayType(int vehicleDisplayType) {
switch (vehicleDisplayType) {
case VehicleDisplay.MAIN:
// 如果车辆显示类型是主显示器,返回主显示器类型
return CarOccupantZoneManager.DISPLAY_TYPE_MAIN;
case VehicleDisplay.INSTRUMENT_CLUSTER:
// 如果车辆显示类型是仪表盘,返回仪表盘类型
return CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER;
case VehicleDisplay.HUD:
// 如果车辆显示类型是抬头显示器,返回抬头显示器类型
return CarOccupantZoneManager.DISPLAY_TYPE_HUD;
case VehicleDisplay.INPUT:
// 如果车辆显示类型是输入显示器,返回输入显示器类型
return CarOccupantZoneManager.DISPLAY_TYPE_INPUT;
case VehicleDisplay.AUXILIARY:
// 如果车辆显示类型是辅助显示器,返回辅助显示器类型
return CarOccupantZoneManager.DISPLAY_TYPE_AUXILIARY;
default:
// 如果传入的显示类型不匹配任何已知类型,返回未知显示类型
return CarOccupantZoneManager.DISPLAY_TYPE_UNKNOWN;
}
}
CarOccupantZoneManager
类提供了车辆中显示器的相关信息:
DISPLAY_TYPE_MAIN
:主显示器,用户主要与其交互,默认UI会启动到此显示器。DISPLAY_TYPE_INSTRUMENT_CLUSTER
:仪表盘显示器,通常仅存在于驾驶员区域。DISPLAY_TYPE_HUD
:抬头显示器(HUD),通常仅存在于驾驶员区域。DISPLAY_TYPE_INPUT
:专用于显示输入法(IME)的显示器。DISPLAY_TYPE_AUXILIARY
:辅助显示器,为主显示器提供额外的屏幕。DISPLAY_TYPE_AUXILIARY_2
~DISPLAY_TYPE_AUXILIARY_5
:其他辅助显示器类型,隐藏常量(@hide
),可能用于内部扩展。DISPLAY_TYPE_DISPLAY_COMPATIBILITY
:专用于显示兼容性应用程序的显示器,隐藏常量(@hide
)。
InputHalService.dispatchKeyInputV2()¶
// packages/services/Car/service/src/com/android/car/hal/InputHalService.java
public class InputHalService extends HalServiceBase {
/**
* 将一个{@link KeyEvent}分发到指定的座位区域。
*
* @param listener 用于接收事件的监听器
* @param action 按键事件的动作(按下或抬起)
* @param code 按键事件的按键代码
* @param display 事件关联的目标显示器
* @param eventTime 事件发生时的系统运行时间(毫秒)
* @param downTime 按键按下时的时间(毫秒)
* @param repeat 按键按下事件的重复计数。对于按键按下事件,这是从0开始的重复计数。
* 对于按键抬起事件,该值始终为0。
* @param seat 事件发生的区域ID
*/
private void dispatchKeyEventV2(InputListener listener, int action, int code,
@DisplayTypeEnum int display, long eventTime, long downTime, int repeat, int seat) {
// 创建一个KeyEvent对象,包含按键事件的详细信息
KeyEvent event = new KeyEvent(
downTime, // 按键按下的时间
eventTime, // 事件发生的时间
action, // 按键动作
code, // 按键代码
repeat, // 重复计数
0 /* metaState */, // 元状态,默认为0
0 /* deviceId */, // 设备ID,默认为0
0 /* scancode */, // 扫描码,默认为0
0 /* flags */, // 标志,默认为0
InputDevice.SOURCE_CLASS_BUTTON); // 输入设备源类型
// event.displayId将在CarInputService#onKeyEvent中设置
listener.onKeyEvent(event, display, seat); // 将事件分发给监听器
saveV2KeyInputEventInHistory(event); // 将事件保存到历史记录中
}
}
创建一个 KeyEvent
对象,并将其传递给输入监听器进行处理。
所以最终会回调到 CarInputService.onKeyEvent()
,后面再分析 CarInputService.onKeyEvent()
。
InputHalService.dispatchMotionInput()¶
// packages/services/Car/service/src/com/android/car/hal/InputHalService.java
public class InputHalService extends HalServiceBase {
/**
* 处理并分发触摸输入事件。
*
* 该方法从`HalPropValue`对象中提取触摸事件的详细信息,并将其转换为`MotionEvent`对象,
* 然后分发给监听器。
*
* @param listener 用于接收和处理事件的输入监听器
* @param value 包含触摸事件数据的`HalPropValue`对象
*/
private void dispatchMotionInput(InputListener listener, HalPropValue value) {
// 定义常量,用于解析数据
final int firstInt32ArrayOffset = 5; // int32数组的起始偏移量
final int int64ValuesSize = 1; // 期望的int64数组大小
final int numInt32Arrays = 2; // 每个指针的int32数组数量
final int numFloatArrays = 4; // 每个指针的float数组数量
// 定义变量,用于存储解析后的数据
int seat; // 座位区域ID
int vehicleDisplay; // 车辆显示器标识
int inputSource; // 输入源类型
int action; // 动作类型
int buttonStateFlag; // 按钮状态标志
int pointerCount; // 指针数量
int[] pointerIds; // 指针ID数组
int[] toolTypes; // 工具类型数组
float[] xData; // X坐标数组
float[] yData; // Y坐标数组
float[] pressureData; // 压力数据数组
float[] sizeData; // 大小数据数组
long elapsedDownTimeNanos; // 按下时间(纳秒)
PointerProperties[] pointerProperties; // 指针属性数组
PointerCoords[] pointerCoords; // 指针坐标数组
int convertedInputSource; // 转换后的输入源类型
int convertedAction; // 转换后的动作类型
int convertedButtonStateFlag; // 转换后的按钮状态标志
try {
// 提取座位区域ID
seat = value.getAreaId();
// 检查int32数组的大小是否足够
if (value.getInt32ValuesSize() < firstInt32ArrayOffset) {
Slogf.e(TAG, "int32数组大小不足,来自VHAL的触摸输入事件: %d", value.getInt32ValuesSize());
return;
}
// 提取触摸事件的基本信息
vehicleDisplay = value.getInt32Value(0);
inputSource = value.getInt32Value(1);
action = value.getInt32Value(2);
buttonStateFlag = value.getInt32Value(3);
pointerCount = value.getInt32Value(4);
// 检查指针数量是否有效
if (pointerCount < 1) {
Slogf.e(TAG, "指针数量无效,来自VHAL的触摸输入事件: %d", pointerCount);
return;
}
// 初始化指针相关数组
pointerIds = new int[pointerCount];
toolTypes = new int[pointerCount];
xData = new float[pointerCount];
yData = new float[pointerCount];
pressureData = new float[pointerCount];
sizeData = new float[pointerCount];
// 检查int32数组和float数组的大小是否足够
if (value.getInt32ValuesSize() < firstInt32ArrayOffset + pointerCount * numInt32Arrays) {
Slogf.e(TAG, "int32数组大小不足,来自VHAL的触摸输入事件: %d", value.getInt32ValuesSize());
return;
}
if (value.getFloatValuesSize() < pointerCount * numFloatArrays) {
Slogf.e(TAG, "float数组大小不足,来自VHAL的触摸输入事件: %d", value.getFloatValuesSize());
return;
}
// 提取每个指针的详细信息
for (int i = 0; i < pointerCount; i++) {
pointerIds[i] = value.getInt32Value(firstInt32ArrayOffset + i);
toolTypes[i] = value.getInt32Value(firstInt32ArrayOffset + pointerCount + i);
xData[i] = value.getFloatValue(i);
yData[i] = value.getFloatValue(pointerCount + i);
pressureData[i] = value.getFloatValue(2 * pointerCount + i);
sizeData[i] = value.getFloatValue(3 * pointerCount + i);
}
// 检查int64数组的大小是否足够
if (value.getInt64ValuesSize() < int64ValuesSize) {
Slogf.e(TAG, "int64数组大小不足,来自VHAL的触摸输入事件: %d", value.getInt64ValuesSize());
return;
}
// 提取按下时间
elapsedDownTimeNanos = value.getInt64Value(0);
// 如果日志级别为DEBUG,记录调试日志
if (Slogf.isLoggable(TAG, Log.DEBUG)) {
Slogf.d(TAG, "触摸事件信息: 输入源: %d, 动作: %d, 显示器: %d, 按钮状态: %d, 指针数量: %d, 按下时间: %d",
inputSource, action, vehicleDisplay, buttonStateFlag, pointerCount, elapsedDownTimeNanos);
}
// 创建指针属性和坐标数组
pointerProperties = createPointerPropertiesArray(pointerCount);
pointerCoords = createPointerCoordsArray(pointerCount);
// 填充指针属性和坐标数组
for (int i = 0; i < pointerCount; i++) {
pointerProperties[i].id = pointerIds[i];
pointerProperties[i].toolType = convertToolType(toolTypes[i]);
pointerCoords[i].x = xData[i];
pointerCoords[i].y = yData[i];
pointerCoords[i].pressure = pressureData[i];
pointerCoords[i].size = sizeData[i];
}
// 转换动作类型、按钮状态和输入源
convertedAction = convertMotionAction(action);
convertedButtonStateFlag = convertButtonStateFlag(buttonStateFlag);
convertedInputSource = convertInputSource(inputSource);
} catch (Exception e) {
// 如果解析过程中发生异常,记录错误日志并返回
Slogf.e(TAG, "无效的触摸输入事件,int32Values: " + value.dumpInt32Values()
+ ", floatValues: " + value.dumpFloatValues()
+ ", int64Values: " + value.dumpInt64Values(), e);
return;
}
// 创建MotionEvent对象
MotionEvent event = MotionEvent.obtain(
toUpTimeMillis(elapsedDownTimeNanos), // 按下时间
toUpTimeMillis(value.getTimestamp()), // 事件发生时间
convertedAction, // 转换后的动作类型
pointerCount, // 指针数量
pointerProperties, // 指针属性数组
pointerCoords, // 指针坐标数组
0 /* metaState */, // 元状态
convertedButtonStateFlag, // 转换后的按钮状态
0f /* xPrecision */, // X轴精度
0f /* yPrecision */, // Y轴精度
0 /* deviceId */, // 设备ID
0 /* edgeFlags */, // 边缘标志
convertedInputSource, // 转换后的输入源
0 /* flags */); // 标志
// 将事件分发给监听器
listener.onMotionEvent(event, convertDisplayType(vehicleDisplay), seat);
// 将事件保存到历史记录中
saveMotionEventInHistory(event);
}
}
从 HalPropValue
对象中提取触摸事件的详细信息,并将其转换为MotionEvent
对象,然后分发给监听器。
所以最终会回调到 CarInputService.onMotionEvent()
,后面再分析 CarInputService.onMotionEvent()
。
InputHalService.dispatchRotaryInput()¶
// packages/services/Car/service/src/com/android/car/hal/InputHalService.java
public class InputHalService extends HalServiceBase {
/**
* 处理并分发旋钮输入事件。
*
* 该方法从`HalPropValue`对象中提取旋钮事件的详细信息,并将其转换为`RotaryEvent`对象,
* 然后分发给监听器。
*
* @param listener 用于接收和处理事件的输入监听器
* @param value 包含旋钮事件数据的`HalPropValue`对象
*/
private void dispatchRotaryInput(InputListener listener, HalPropValue value) {
int timeValuesIndex = 3; // 剩余的值是时间增量(纳秒)
// 检查int32数组的大小是否足够
if (value.getInt32ValuesSize() < timeValuesIndex) {
Slogf.e(TAG, "来自VHAL的旋钮输入int32数组大小错误: %d", value.getInt32ValuesSize());
return;
}
// 提取旋钮事件的基本信息
int rotaryInputType = value.getInt32Value(0);
int detentCount = value.getInt32Value(1);
int vehicleDisplay = value.getInt32Value(2);
long timestamp = value.getTimestamp(); // 第一个刻度的时间戳(纳秒)
// 记录调试日志
Slogf.d(TAG, "hal旋钮输入类型: %d, 刻度数量: %d, 显示器: %d", rotaryInputType, detentCount, vehicleDisplay);
// 判断旋转方向
boolean clockwise = detentCount > 0;
detentCount = Math.abs(detentCount);
// 检查刻度数量是否有效
if (detentCount == 0) {
Slogf.e(TAG, "来自VHAL的刻度数量为零,忽略事件");
return;
}
// 检查刻度数量是否在有效范围内
if (detentCount < 0 || detentCount > Integer.MAX_VALUE - detentCount + 1) {
Slogf.e(TAG, "来自VHAL的无效刻度数量: %d,忽略事件", detentCount);
}
// 检查显示器类型是否有效
if (vehicleDisplay != VehicleDisplay.MAIN && vehicleDisplay != VehicleDisplay.INSTRUMENT_CLUSTER) {
Slogf.e(TAG, "来自VHAL的旋钮输入显示器类型错误: %d", vehicleDisplay);
return;
}
// 检查int32数组的大小是否与刻度数量匹配
if (value.getInt32ValuesSize() != (timeValuesIndex + detentCount - 1)) {
Slogf.e(TAG, "来自VHAL的旋钮输入int32数组大小错误: %d", value.getInt32ValuesSize());
return;
}
// 确定旋钮输入类型
int carInputManagerType;
switch (rotaryInputType) {
case ROTARY_INPUT_TYPE_SYSTEM_NAVIGATION:
carInputManagerType = CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION;
break;
case ROTARY_INPUT_TYPE_AUDIO_VOLUME:
carInputManagerType = CarInputManager.INPUT_TYPE_ROTARY_VOLUME;
break;
default:
Slogf.e(TAG, "未知的旋钮输入类型: %d", rotaryInputType);
return;
}
// 计算每个刻度的时间戳
long[] timestamps = new long[detentCount];
long uptimeToElapsedTimeDelta = CarServiceUtils.getUptimeToElapsedTimeDeltaInMillis();
long startUptime = TimeUnit.NANOSECONDS.toMillis(timestamp) - uptimeToElapsedTimeDelta;
timestamps[0] = startUptime;
for (int i = 0; i < timestamps.length - 1; i++) {
timestamps[i + 1] = timestamps[i] + TimeUnit.NANOSECONDS.toMillis(
value.getInt32Value(timeValuesIndex + i));
}
// 创建RotaryEvent对象
RotaryEvent event = new RotaryEvent(carInputManagerType, clockwise, timestamps);
// 将事件分发给监听器
listener.onRotaryEvent(event, convertDisplayType(vehicleDisplay));
}
}
从 HalPropValue
对象中提取旋钮事件的详细信息,并将其转换为RotaryEvent
对象,然后分发给监听器,然后分发给监听器。
所以最终会回调到 CarInputService.onRotaryEvent()
,后面再分析 CarInputService.onRotaryEvent()
。
InputHalService.dispatchCustomInput()¶
// packages/services/Car/service/src/com/android/car/hal/InputHalService.java
public class InputHalService extends HalServiceBase {
/**
* 处理并分发自定义输入事件。
*
* 该方法从`HalPropValue`对象中提取自定义输入事件的详细信息,并将其转换为`CustomInputEvent`对象,
* 然后分发给监听器。
*
* @param listener 用于接收和处理事件的输入监听器
* @param value 包含自定义输入事件数据的`HalPropValue`对象
*/
private void dispatchCustomInput(InputListener listener, HalPropValue value) {
// 记录调试日志,显示监听器和事件值的信息
Slogf.d(TAG, "正在分发自定义输入事件,监听器: %s,值: %s", listener, value);
int inputCode; // 输入代码
int targetDisplayType; // 目标显示器类型
int repeatCounter; // 重复计数器
try {
// 从`HalPropValue`中提取输入事件的详细信息
inputCode = value.getInt32Value(0); // 获取输入代码
targetDisplayType = convertDisplayType(value.getInt32Value(1)); // 转换显示器类型
repeatCounter = value.getInt32Value(2); // 获取重复计数器
} catch (Exception e) {
// 如果提取过程中发生异常,记录错误日志并返回
Slogf.e(TAG, "接收到无效的HAL自定义输入事件", e);
return;
}
// 创建CustomInputEvent对象,包含自定义输入事件的详细信息
CustomInputEvent event = new CustomInputEvent(inputCode, targetDisplayType, repeatCounter);
// 将事件分发给监听器
listener.onCustomInputEvent(event);
}
}
从 HalPropValue
对象中提取自定义输入事件的详细信息,并将其转换为CustomInputEvent
对象,然后分发给监听器。
CarInputManager.requestInputEventCapture()¶
// packages/services/Car/car-lib/src/android/car/input/CarInputManager.java
/**
* 该API允许捕获选定的输入事件。
*/
public final class CarInputManager extends CarManagerBase {
/**
* 请求为指定的显示器捕获所有请求的输入类型的输入事件。
*
* <p>如果有高优先级的客户端持有该请求,则请求可能会失败。客户端可以在{@code requestFlags}中设置
* {@link #CAPTURE_REQ_FLAGS_ALLOW_DELAYED_GRANT}以等待当前高优先级客户端释放它。
*
* <p>如果只有部分指定的输入类型可用,请求将:
* <ul>
* <li>失败,返回{@link #INPUT_CAPTURE_RESPONSE_FAILED},或
* <li>被延迟,返回{@link #INPUT_CAPTURE_RESPONSE_DELAYED},如果使用了
* {@link #CAPTURE_REQ_FLAGS_ALLOW_DELAYED_GRANT}标志。
* </ul>
*
* <p>在返回{@link #INPUT_CAPTURE_RESPONSE_DELAYED}后,直到客户端收到带有有效输入类型的
* {@link CarInputCaptureCallback#onCaptureStateChanged(int, int[])}调用之前,不会捕获任何输入类型。
*
* <p>参数targetDisplayType必须仅包含驾驶员显示类型(即
* {@link CarOccupantZoneManager#DISPLAY_TYPE_MAIN}和
* {@link CarOccupantZoneManager#DISPLAY_TYPE_INSTRUMENT_CLUSTER})。
*
* <p>回调按显示类型分组和堆叠。只有最近注册的回调会接收关联显示器和输入类型的传入事件。
* 例如,如果在同一个{@link CarInputManager}实例中针对同一显示类型注册了两个回调,
* 则只有最后注册的回调会接收事件,即使它们是为不同的输入事件类型注册的。
*
* @throws SecurityException 如果调用者没有授予以下权限之一:
* {@code android.car.permission.CAR_MONITOR_INPUT}或
* {@code android.Manifest.permission.MONITOR_INPUT}
* @throws IllegalArgumentException 如果targetDisplayType参数对应于不支持的显示类型
* @throws IllegalArgumentException 如果inputTypes参数包含无效或不支持的值
* @param targetDisplayType 要注册回调的显示类型
* @param inputTypes 要注册回调的输入类型
* @param requestFlags 捕获请求标志
* @param callback 用于接收输入事件的回调
* @return 输入捕获响应,指示注册是成功、失败还是延迟
*
* @hide
*/
@SystemApi
@RequiresPermission(anyOf = {PERMISSION_FRAMEWORK_MONITOR_INPUT,
Car.PERMISSION_CAR_MONITOR_INPUT})
@InputCaptureResponseEnum
public int requestInputEventCapture(@DisplayTypeEnum int targetDisplayType,
@NonNull @InputTypeEnum int[] inputTypes,
@CaptureRequestFlags int requestFlags,
@NonNull CarInputCaptureCallback callback) {
Handler handler = getEventHandler();
return requestInputEventCapture(targetDisplayType, inputTypes, requestFlags, handler::post,
callback);
}
/**
* 类似于{@link CarInputManager#requestInputEventCapture(int, int[], int,
* CarInputCaptureCallback)},但回调使用作为参数传递的执行器调用。
*
* @throws SecurityException 如果调用者没有授予以下权限之一:
* {@code android.car.permission.CAR_MONITOR_INPUT}或
* {@code android.Manifest.permission.MONITOR_INPUT}
* @param targetDisplayType 要注册回调的显示类型
* @param inputTypes 要注册回调的输入类型
* @param requestFlags 捕获请求标志
* @param executor 处理回调的{@link Executor}
* @param callback 用于接收输入事件的回调
* @return 输入捕获响应,指示注册是成功、失败还是延迟
* @see CarInputManager#requestInputEventCapture(int, int[], int, CarInputCaptureCallback)
*
* @hide
*/
@SystemApi
@RequiresPermission(anyOf = {PERMISSION_FRAMEWORK_MONITOR_INPUT,
Car.PERMISSION_CAR_MONITOR_INPUT})
@InputCaptureResponseEnum
public int requestInputEventCapture(@DisplayTypeEnum int targetDisplayType,
@NonNull @InputTypeEnum int[] inputTypes,
@CaptureRequestFlags int requestFlags,
@NonNull @CallbackExecutor Executor executor,
@NonNull CarInputCaptureCallback callback) {
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
synchronized (mLock) {
mCarInputCaptureCallbacks.put(targetDisplayType,
new CallbackHolder(callback, executor));
}
try {
return mService.requestInputEventCapture(mServiceCallback, targetDisplayType,
inputTypes, requestFlags);
} catch (RemoteException e) {
return handleRemoteExceptionFromCarService(e, INPUT_CAPTURE_RESPONSE_FAILED);
}
}
}
requestInputEventCapture()
方法用于请求输入事件,并通过 CarInputCaptureCallback
接收回调事件。其最终通过 IPC 调用到 CarInputService.requestInputEventCapture()
CarInputService.requestInputEventCapture()¶
// packages/services/Car/service/src/com/android/car/CarInputService.java
public class CarInputService extends ICarInput.Stub
implements CarServiceBase, InputHalService.InputListener {
// 输入捕获客户端控制器,用于管理输入事件捕获请求
private final InputCaptureClientController mCaptureController;
/**
* 请求捕获输入事件。
*
* @param callback 用于接收输入事件的回调接口
* @param targetDisplayType 要捕获输入事件的目标显示器类型
* @param inputTypes 要捕获的输入事件类型数组
* @param requestFlags 捕获请求标志
* @return 输入捕获响应,指示注册是成功、失败还是延迟
*/
@Override
public int requestInputEventCapture(ICarInputCallback callback,
@DisplayTypeEnum int targetDisplayType,
int[] inputTypes, int requestFlags) {
// 调用mCaptureController的requestInputEventCapture方法来处理请求
return mCaptureController.requestInputEventCapture(callback, targetDisplayType, inputTypes,
requestFlags);
}
}
这里的 mCaptureController
指的是 InputCaptureClientController
,它是在 CarInputService
的构造函数中通过 mCaptureController = new InputCaptureClientController(context)
进行初始化的。
InputCaptureClientController.requestInputEventCapture()¶
在分析 requestInputEventCapture()
之前,我们先了解 ClientInfoForDisplay
和 ClientsToDispatch
的数据结构。
// packages/services/Car/service/src/com/android/car/InputCaptureClientController.java
public class InputCaptureClientController {
/**
* 表示与特定显示器相关的客户端信息,实现了IBinder.DeathRecipient接口。
* 当绑定的客户端进程死亡时,能够接收到通知。
*/
private final class ClientInfoForDisplay implements IBinder.DeathRecipient {
private final int mUid; // 客户端的用户ID
private final int mPid; // 客户端的进程ID
private final ICarInputCallback mCallback; // 用于接收输入事件的回调接口
private final int mTargetDisplayType; // 目标显示器类型
private final int[] mInputTypes; // 请求的输入类型数组
private final int mFlags; // 请求标志
private final ArrayList<Integer> mGrantedTypes; // 已授予的输入类型列表
/**
* 构造函数,初始化客户端信息。
*
* @param uid 客户端的用户ID
* @param pid 客户端的进程ID
* @param callback 用于接收输入事件的回调接口
* @param targetDisplayType 目标显示器类型
* @param inputTypes 请求的输入类型数组
* @param flags 请求标志
*/
private ClientInfoForDisplay(int uid, int pid, @NonNull ICarInputCallback callback,
int targetDisplayType, int[] inputTypes, int flags) {
mUid = uid;
mPid = pid;
mCallback = callback;
mTargetDisplayType = targetDisplayType;
mInputTypes = inputTypes;
mFlags = flags;
mGrantedTypes = new ArrayList<>(inputTypes.length);
}
/**
* 链接到客户端的死亡通知。
*
* @throws RemoteException 如果链接失败
*/
private void linkToDeath() throws RemoteException {
mCallback.asBinder().linkToDeath(this, 0);
}
/**
* 解除与客户端的死亡通知链接。
*/
private void unlinkToDeath() {
mCallback.asBinder().unlinkToDeath(this, 0);
}
/**
* 当客户端进程死亡时调用。
*/
@Override
public void binderDied() {
onClientDeath(this);
}
}
/**
* 用于存储需要分发的客户端信息。
* 同一个客户端可以多次添加,仅保留最后一次添加的信息。
*/
private static final class ClientsToDispatch {
private final ArrayMap<ICarInputCallback, int[]> mClientsToDispatch = new ArrayMap<>();
private final int mDisplayType; // 显示器类型
/**
* 构造函数,初始化显示器类型。
*
* @param displayType 显示器类型
*/
private ClientsToDispatch(int displayType) {
mDisplayType = displayType;
}
/**
* 添加客户端信息到分发列表。
*
* @param client 客户端信息
*/
private void add(ClientInfoForDisplay client) {
int[] inputTypesToDispatch;
if (client.mGrantedTypes.isEmpty()) {
inputTypesToDispatch = EMPTY_INPUT_TYPES; // 如果没有授予的类型,使用空数组
} else {
inputTypesToDispatch = CarServiceUtils.toIntArray(client.mGrantedTypes); // 转换为数组
}
mClientsToDispatch.put(client.mCallback, inputTypesToDispatch);
}
}
}
ClientInfoForDisplay
和ClientsToDispatch
是 InputCaptureClientController
类中的两个内部类,用于管理客户端信息和分发输入事件。
ClientInfoForDisplay
类:- 用于存储与特定显示器相关的客户端信息。
- 实现了
IBinder.DeathRecipient
接口,以便在客户端进程死亡时接收到通知。 - 包含客户端的用户ID、进程ID、回调接口、目标显示器类型、请求的输入类型、请求标志和已授予的输入类型列表。
- 提供了方法来链接和解除与客户端的死亡通知。
ClientsToDispatch
类:- 用于存储需要分发的客户端信息。
- 使用
ArrayMap
存储客户端的回调接口和对应的输入类型数组。 - 提供了方法来添加客户端信息到分发列表中。
// packages/services/Car/service/src/com/android/car/InputCaptureClientController.java
public class InputCaptureClientController {
/**
* 参见
* {@link CarInputManager#requestInputEventCapture(CarInputManager.CarInputCaptureCallback,
* int, int[], int)}. */ public int requestInputEventCapture(ICarInputCallback callback,
@DisplayTypeEnum int targetDisplayType,
int[] inputTypes, int requestFlags) {
// 确保调用者具有必要的权限
CarServiceUtils.assertAnyPermission(mContext,
Car.PERMISSION_CAR_MONITOR_INPUT, PermissionHelper.MONITOR_INPUT);
// 检查显示器类型是否受支持
Preconditions.checkArgument(SUPPORTED_DISPLAY_TYPES.contains(targetDisplayType),
"Display not supported yet:" + targetDisplayType);
// 检查是否请求捕获所有事件
boolean isRequestingAllEvents =
(requestFlags & CarInputManager.CAPTURE_REQ_FLAGS_TAKE_ALL_EVENTS_FOR_DISPLAY) != 0;
if (isRequestingAllEvents) {
// 对于非仪表盘显示器,确保调用来自系统进程或自身
if (targetDisplayType != CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER) {
CarServiceUtils.assertCallingFromSystemProcessOrSelf();
} else { // 对于DISPLAY_TYPE_INSTRUMENT_CLUSTER
if (!CarServiceUtils.isCallingFromSystemProcessOrSelf()) {
CarServiceUtils.checkCalledByPackage(mContext, mClusterHomePackage);
}
}
// 确保输入类型为INPUT_TYPE_ALL_INPUTS
if (inputTypes.length != 1 || inputTypes[0] != CarInputManager.INPUT_TYPE_ALL_INPUTS) {
throw new IllegalArgumentException("Input type should be INPUT_TYPE_ALL_INPUTS"
+ " for CAPTURE_REQ_FLAGS_TAKE_ALL_EVENTS_FOR_DISPLAY");
}
}
// 验证目标显示器类型是否为已识别的类型
if (targetDisplayType != CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER
&& targetDisplayType != CarOccupantZoneManager.DISPLAY_TYPE_MAIN) {
throw new IllegalArgumentException("Unrecognized display type:" + targetDisplayType);
}
// 确保输入类型数组不为空
if (inputTypes == null) {
throw new IllegalArgumentException("inputTypes cannot be null");
}
// 验证输入类型的有效性
assertInputTypeValid(inputTypes);
// 对输入类型数组进行排序
Arrays.sort(inputTypes);
// 获取客户端的IBinder对象
IBinder clientBinder = callback.asBinder();
// 检查是否允许延迟授予
boolean allowsDelayedGrant =
(requestFlags & CarInputManager.CAPTURE_REQ_FLAGS_ALLOW_DELAYED_GRANT) != 0;
int ret = CarInputManager.INPUT_CAPTURE_RESPONSE_SUCCEEDED;
// 如果启用了调试日志,则记录请求信息
if (DBG_CALLS) {
Slogf.i(TAG,
"requestInputEventCapture callback:" + callback
+ ", display:" + targetDisplayType
+ ", inputTypes:" + Arrays.toString(inputTypes)
+ ", flags:" + requestFlags);
}
// 创建一个用于分发的客户端集合
ClientsToDispatch clientsToDispatch = new ClientsToDispatch(targetDisplayType);
synchronized (mLock) {
// 获取目标显示器的所有客户端信息
HashMap<IBinder, ClientInfoForDisplay> allClientsForDisplay = mAllClients.get(
targetDisplayType);
if (allClientsForDisplay == null) {
allClientsForDisplay = new HashMap<IBinder, ClientInfoForDisplay>();
mAllClients.put(targetDisplayType, allClientsForDisplay);
}
// 移除旧的客户端信息
ClientInfoForDisplay oldClientInfo = allClientsForDisplay.remove(clientBinder);
// 获取全捕获客户端的堆栈
LinkedList<ClientInfoForDisplay> fullCapturersStack = mFullDisplayEventCapturers.get(
targetDisplayType);
if (fullCapturersStack == null) {
fullCapturersStack = new LinkedList<ClientInfoForDisplay>();
mFullDisplayEventCapturers.put(targetDisplayType, fullCapturersStack);
}
// 如果当前有全捕获活动且不允许延迟授予,则返回失败
if (!isRequestingAllEvents && fullCapturersStack.size() > 0
&& fullCapturersStack.getFirst() != oldClientInfo && !allowsDelayedGrant) {
return CarInputManager.INPUT_CAPTURE_RESPONSE_FAILED;
}
// 注册新客户端,并进行死亡监控
ClientInfoForDisplay newClient = new ClientInfoForDisplay(Binder.getCallingUid(),
Binder.getCallingPid(), callback, targetDisplayType,
inputTypes, requestFlags);
try {
newClient.linkToDeath();
} catch (RemoteException e) {
// 客户端死亡
Slogf.i(TAG, "requestInputEventCapture, cannot linkToDeath to client, pid:"
+ Binder.getCallingUid());
return CarInputManager.INPUT_CAPTURE_RESPONSE_FAILED;
}
// 获取每个输入类型的捕获者堆栈
SparseArray<LinkedList<ClientInfoForDisplay>> perInputStacks =
mPerInputTypeCapturers.get(targetDisplayType);
if (perInputStacks == null) {
perInputStacks = new SparseArray<LinkedList<ClientInfoForDisplay>>();
mPerInputTypeCapturers.put(targetDisplayType, perInputStacks);
}
if (isRequestingAllEvents) {
// 如果请求捕获所有事件
if (!fullCapturersStack.isEmpty()) {
ClientInfoForDisplay oldCapturer = fullCapturersStack.getFirst();
if (oldCapturer != oldClientInfo) {
oldCapturer.mGrantedTypes.clear();
clientsToDispatch.add(oldCapturer);
}
fullCapturersStack.remove(oldClientInfo);
} else {
// 通知每个输入类型的顶部堆栈客户端
for (int i = 0; i < perInputStacks.size(); i++) {
LinkedList<ClientInfoForDisplay> perTypeStack = perInputStacks.valueAt(i);
if (!perTypeStack.isEmpty()) {
ClientInfoForDisplay topClient = perTypeStack.getFirst();
if (topClient != oldClientInfo) {
topClient.mGrantedTypes.clear();
clientsToDispatch.add(topClient);
}
perTypeStack.remove(oldClientInfo);
}
}
}
fullCapturersStack.addFirst(newClient);
} else {
// 处理非全捕获请求
boolean hadFullCapture = false;
boolean fullCaptureActive = false;
if (fullCapturersStack.size() > 0) {
if (fullCapturersStack.getFirst() == oldClientInfo) {
fullCapturersStack.remove(oldClientInfo);
if (fullCapturersStack.size() > 0) {
fullCaptureActive = true;
ret = CarInputManager.INPUT_CAPTURE_RESPONSE_DELAYED;
ClientInfoForDisplay topClient = fullCapturersStack.getFirst();
topClient.mGrantedTypes.clear();
topClient.mGrantedTypes.add(CarInputManager.INPUT_TYPE_ALL_INPUTS);
clientsToDispatch.add(topClient);
} else {
hadFullCapture = true;
}
} else {
fullCaptureActive = true;
ret = CarInputManager.INPUT_CAPTURE_RESPONSE_DELAYED;
}
}
for (int i = 0; i < perInputStacks.size(); i++) {
LinkedList<ClientInfoForDisplay> perInputStack = perInputStacks.valueAt(i);
perInputStack.remove(oldClientInfo);
}
// 遍历每个输入类型的堆栈
for (int inputType : inputTypes) {
LinkedList<ClientInfoForDisplay> perInputStack = perInputStacks.get(
inputType);
if (perInputStack == null) {
perInputStack = new LinkedList<ClientInfoForDisplay>();
perInputStacks.put(inputType, perInputStack);
}
if (perInputStack.size() > 0) {
ClientInfoForDisplay oldTopClient = perInputStack.getFirst();
if (oldTopClient.mGrantedTypes.remove(Integer.valueOf(inputType))) {
clientsToDispatch.add(oldTopClient);
}
}
if (!fullCaptureActive) {
newClient.mGrantedTypes.add(inputType);
}
perInputStack.addFirst(newClient);
}
if (!fullCaptureActive && hadFullCapture) {
for (int i = 0; i < perInputStacks.size(); i++) {
int inputType = perInputStacks.keyAt(i);
LinkedList<ClientInfoForDisplay> perInputStack = perInputStacks.valueAt(
i);
if (perInputStack.size() > 0) {
ClientInfoForDisplay topStackClient = perInputStack.getFirst();
if (topStackClient == newClient) {
continue;
}
if (!topStackClient.mGrantedTypes.contains(inputType)) {
topStackClient.mGrantedTypes.add(inputType);
clientsToDispatch.add(topStackClient);
}
}
}
}
}
// 将新客户端信息存储到所有客户端信息中
allClientsForDisplay.put(clientBinder, newClient);
// 分发客户端回调
dispatchClientCallbackLocked(clientsToDispatch);
}
return ret;
}
}
这个函数的代码非常长,前面主要是检查一些权限,后面是把客户端信息存储起来。
我们现在来分析 mPerInputTypeCapturers
和 mFullDisplayEventCapturers
。
public class InputCaptureClientController {
/**
* 用于存储全显示器事件捕获者的结构。
* 键:显示器类型,用于快速查找客户端。
* LinkedList用于实现堆栈结构。第一个条目是堆栈的顶部。
*/
@GuardedBy("mLock")
private final SparseArray<LinkedList<ClientInfoForDisplay>> mFullDisplayEventCapturers =
new SparseArray<>(2);
// 获取目标显示器类型的全捕获者堆栈
LinkedList<ClientInfoForDisplay> fullCapturersStack = mFullDisplayEventCapturers.get(
targetDisplayType);
if (fullCapturersStack == null) {
// 如果堆栈不存在,则创建一个新的LinkedList并放入SparseArray中
fullCapturersStack = new LinkedList<ClientInfoForDisplay>();
mFullDisplayEventCapturers.put(targetDisplayType, fullCapturersStack);
}
}
mFullDisplayEventCapturers
是一个 SparseArray<LinkedList<ClientInfoForDisplay>>
,用于存储每个显示器类型的全事件捕获者。
public class InputCaptureClientController {
/**
* 用于存储每个输入类型的捕获者的结构。
* 键:显示器类型 -> 输入类型,用于快速查找客户端。
* LinkedList用于实现堆栈结构。第一个条目是堆栈的顶部。
*/
@GuardedBy("mLock")
private final SparseArray<SparseArray<LinkedList<ClientInfoForDisplay>>>
mPerInputTypeCapturers = new SparseArray<>(2);
}
// 获取目标显示器类型的输入类型捕获者堆栈
SparseArray<LinkedList<ClientInfoForDisplay>> perInputStacks =
mPerInputTypeCapturers.get(targetDisplayType);
if (perInputStacks == null) {
// 如果堆栈不存在,则创建一个新的SparseArray并放入mPerInputTypeCapturers中
perInputStacks = new SparseArray<LinkedList<ClientInfoForDisplay>>();
mPerInputTypeCapturers.put(targetDisplayType, perInputStacks);
}
}
mPerInputTypeCapturers
是一个嵌套的 SparseArray
结构,用于存储每个显示器类型下的每个输入类型的捕获者。
InputCaptureClientController.getClientForInputTypeLocked()¶
// packages/services/Car/service/src/com/android/car/InputCaptureClientController.java
public class InputCaptureClientController {
/**
* 在锁定的情况下获取特定显示器类型和输入类型的客户端回调。
*
* @param targetDisplayType 目标显示器类型
* @param inputType 输入类型
* @return 对应的ICarInputCallback,如果没有找到则返回null
*/
@GuardedBy("mLock")
ICarInputCallback getClientForInputTypeLocked(int targetDisplayType, int inputType) {
// 获取目标显示器类型的全捕获者堆栈
LinkedList<ClientInfoForDisplay> fullCapturersStack = mFullDisplayEventCapturers.get(
targetDisplayType);
// 如果全捕获者堆栈不为空,返回堆栈顶部的客户端回调
if (fullCapturersStack != null && fullCapturersStack.size() > 0) {
return fullCapturersStack.getFirst().mCallback;
}
// 获取目标显示器类型的输入类型捕获者堆栈
SparseArray<LinkedList<ClientInfoForDisplay>> perInputStacks =
mPerInputTypeCapturers.get(targetDisplayType);
// 如果输入类型堆栈不存在,返回null
if (perInputStacks == null) {
return null;
}
// 获取特定输入类型的捕获者堆栈
LinkedList<ClientInfoForDisplay> perInputStack = perInputStacks.get(inputType);
// 如果捕获者堆栈不为空,返回堆栈顶部的客户端回调
if (perInputStack != null && perInputStack.size() > 0) {
return perInputStack.getFirst().mCallback;
}
// 如果没有找到合适的客户端回调,返回null
return null;
}
}
获取特定显示器类型和输入类型的客户端回调。
- 首先从
mFullDisplayEventCapturers
中获取目标显示器类型的全事件捕获者堆栈。 - 如果全事件捕获者堆栈不为空,返回堆栈顶部的客户端回调。
- 如果全事件捕获者堆栈为空,从
mPerInputTypeCapturers
中获取目标显示器类型的输入类型捕获者堆栈。 - 如果输入类型捕获者堆栈不为空,返回堆栈顶部的客户端回调。
- 如果都没有找到合适的客户端回调,返回
null
。
处理事件¶
InputHalService.onHalEvents()
方法会遍历输入事件列表,并根据每个事件的属性ID,将事件分发至对应的处理方法。接下来,我们将从 CarInputService
入手,逐一分析其在接收到事件后的处理流程。
在这之前我们先看下事件是如何注册的。
预备知识¶
CarInputService.registerKeyEventListener()¶
// packages/services/Car/service/src/com/android/car/CarInputService.java
public class CarInputService extends ICarInput.Stub
implements CarServiceBase, InputHalService.InputListener {
// 用于同步的锁对象
private final Object mLock = new Object();
// 存储按键事件监听器的数组,使用按键代码作为键
@GuardedBy("mLock")
private final SparseArray<KeyEventListener> mListeners = new SparseArray<>();
/**
* 注册一个按键事件监听器,用于监听其感兴趣的按键事件。
*
* @param listener 要注册的监听器
* @param keyCodesOfInterest 监听器感兴趣的按键事件代码列表
* @throws IllegalArgumentException 当某个事件已经注册到另一个监听器时抛出
*/
public void registerKeyEventListener(KeyEventListener listener,
List<Integer> keyCodesOfInterest) {
// 检查监听器是否为空
requireNonNull(listener, "按键事件监听器不能为空");
// 检查感兴趣的按键事件列表是否为空
requireNonNull(keyCodesOfInterest, "感兴趣的按键事件不能为空");
// 检查感兴趣的按键事件列表是否为空
checkArgument(!keyCodesOfInterest.isEmpty(), "感兴趣的按键事件不能为空");
synchronized (mLock) {
// 检查是否有无效的按键代码
for (int i = 0; i < keyCodesOfInterest.size(); i++) {
int keyCode = keyCodesOfInterest.get(i);
if (mListeners.contains(keyCode)
&& mListeners.get(keyCode) != mDefaultSpecialKeyHandler) {
throw new IllegalArgumentException("事件 "
+ KeyEvent.keyCodeToString(keyCode)
+ " 已经注册到另一个监听器");
}
}
// 将监听器注册到感兴趣的按键事件代码中
for (int i = 0; i < keyCodesOfInterest.size(); i++) {
mListeners.put(keyCodesOfInterest.get(i), listener);
}
}
}
}
注册一个按键事件监听器,用于监听其感兴趣的按键事件。如果某个按键代码已经注册到其他监听器,抛出异常。
KeyEventListener代码如下:
// packages/services/Car/service/src/com/android/car/CarInputService.java
public class CarInputService extends ICarInput.Stub
implements CarServiceBase, InputHalService.InputListener {
/**
* 接口用于接收{@link KeyEvent}事件。
*/
public interface KeyEventListener {
/**
* 当按键事件发生时调用。
*
* @deprecated 该方法不再需要,请使用带有座位信息的onKeyEvent方法。
*/
// TODO(b/247170915): 该方法不再需要,请移除并使用
// onKeyEvent(KeyEvent event, int displayType, int seat)
default void onKeyEvent(KeyEvent event) {
// 默认实现为空操作
}
/**
* 当按键事件发生时调用,并包含座位信息。
*
* @param event 发生的按键事件
* @param displayType 事件关联的目标显示器类型,应为以下之一:
* {@link CarOccupantZoneManager#DISPLAY_TYPE_MAIN},
* {@link CarOccupantZoneManager#DISPLAY_TYPE_INSTRUMENT_CLUSTER},
* {@link CarOccupantZoneManager#DISPLAY_TYPE_HUD},
* {@link CarOccupantZoneManager#DISPLAY_TYPE_INPUT},
* {@link CarOccupantZoneManager#DISPLAY_TYPE_AUXILIARY}
* @param seat 事件发生的区域ID
*/
default void onKeyEvent(KeyEvent event, @DisplayTypeEnum int displayType,
@VehicleAreaSeat.Enum int seat) {
// 默认实现为空操作
}
}
}
CarInputService.setInstrumentClusterKeyListener()¶
// packages/services/Car/service/src/com/android/car/CarInputService.java
public class CarInputService extends ICarInput.Stub
implements CarServiceBase, InputHalService.InputListener {
// 用于同步的锁对象
private final Object mLock = new Object();
// 存储仪表盘按键事件监听器
@GuardedBy("mLock")
private KeyEventListener mInstrumentClusterKeyListener;
/**
* 设置仪表盘按键事件监听器。
*
* @param listener 要设置的按键事件监听器
*/
public void setInstrumentClusterKeyListener(KeyEventListener listener) {
// 使用同步块确保对监听器的访问是线程安全的
synchronized (mLock) {
mInstrumentClusterKeyListener = listener;
}
}
}
全局搜索代码可以知道是在 InstrumentClusterService.init()
中设置仪表盘按键事件监听器。这有机会再分析。
CarInputService.setProjectionKeyEventHandler()¶
// packages/services/Car/service/src/com/android/car/CarInputService.java
public class CarInputService extends ICarInput.Stub
implements CarServiceBase, InputHalService.InputListener {
// 用于同步的锁对象
private final Object mLock = new Object();
// 存储CarProjectionService按键事件处理器
@GuardedBy("mLock")
private CarProjectionManager.ProjectionKeyEventHandler mProjectionKeyEventHandler;
/**
* 设置CarProjectionService按键事件监听器。如果传入null,则取消注册监听器。
*
* @param listener 要设置的CarProjectionService按键事件监听器
* @param events 要订阅的按键事件集合
*/
public void setProjectionKeyEventHandler(
@Nullable CarProjectionManager.ProjectionKeyEventHandler listener,
@Nullable BitSet events) {
// 使用同步块确保对监听器和事件集合的访问是线程安全的
synchronized (mLock) {
// 设置CarProjectionService按键事件处理器
mProjectionKeyEventHandler = listener;
// 清除当前订阅的按键事件集合
mProjectionKeyEventsSubscribed.clear();
// 如果传入的事件集合不为空,则更新订阅的事件集合
if (events != null) {
mProjectionKeyEventsSubscribed.or(events);
}
}
}
}
全局搜索代码可以知道是在 CarProjectionService.registerKeyEventHandler()
中设置按键事件监听器。这有机会再分析。
按键事件¶
CarInputService.onKeyEvent()¶
// packages/services/Car/service/src/com/android/car/CarInputService.java
public class CarInputService extends ICarInput.Stub
implements CarServiceBase, InputHalService.InputListener {
/**
* 处理按键事件的方法,使用默认的驾驶员座位。
*
* @param event 按键事件
* @param targetDisplayType 目标显示器类型
*/
@Override
public void onKeyEvent(KeyEvent event, @DisplayTypeEnum int targetDisplayType) {
// 调用带有座位信息的onKeyEvent方法,使用默认的驾驶员座位
onKeyEvent(event, targetDisplayType, mDriverSeat);
}
/**
* 处理按键事件的方法。
*
* @param event 按键事件
* @param targetDisplayType 目标显示器类型
* @param seat 事件发生的座位区域ID
* @throws IllegalArgumentException 如果传入的座位是未知座位且驾驶员座位不是未知座位,则抛出异常
*/
@Override
public void onKeyEvent(KeyEvent event, @DisplayTypeEnum int targetDisplayType,
@VehicleAreaSeat.Enum int seat) {
// 检查座位信息的有效性
if (mHasDriver && seat == VehicleAreaSeat.SEAT_UNKNOWN) {
// 为了支持onKeyEvent(KeyEvent, int),需要检查驾驶员是否存在。
// 例如,对于仅有乘客的系统,驾驶员座位可能是SEAT_UNKNOWN。
// 在这种情况下,不应抛出异常。
throw new IllegalArgumentException("未知座位");
}
// 更新用户活动信息到汽车电源管理服务
notifyUserActivity(event, targetDisplayType, seat);
// 驾驶员的按键事件与硬件按键输入相同处理
if (seat == mDriverSeat) {
// 分发驾驶员的按键事件
dispatchKeyEventForDriver(event, targetDisplayType);
return;
}
// 通知监听器按键事件
notifyKeyEventListener(event, targetDisplayType, seat);
}
}
InputHalService.dispatchKeyInput()
事件被分发到onKeyEvent(KeyEvent event, @DisplayTypeEnum int targetDisplayType)
方法,而InputHalService.dispatchKeyInputV2()
事件则被分发到onKeyEvent(KeyEvent event, @DisplayTypeEnum int targetDisplayType, @VehicleAreaSeat.Enum int seat)
方法。
- 在有驾驶员的系统中,确保座位信息的有效性。
- 将用户活动信息同步到
CarPowerManagementService
服务。 - 如果事件发生在驾驶员座位,调用
dispatchKeyEventForDriver
方法进行处理。 - 否则,调用
notifyKeyEventListener
方法通知监听器处理按键事件。
CarInputService.dispatchKeyEventForDriver()¶
// packages/services/Car/service/src/com/android/car/CarInputService.java
public class CarInputService extends ICarInput.Stub
implements CarServiceBase, InputHalService.InputListener {
/**
* 为驾驶员处理按键事件。
*
* @param event 按键事件
* @param targetDisplayType 目标显示器类型
*/
private void dispatchKeyEventForDriver(KeyEvent event, @DisplayTypeEnum int targetDisplayType) {
// 针对汽车的特殊“长按”处理的特殊按键代码
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_VOICE_ASSIST:
// TODO: b/288107028 - 当乘客显示器支持语音辅助键时,将目标显示类型传递给handleVoiceAssistKey()
handleVoiceAssistKey(event, targetDisplayType);
return;
case KeyEvent.KEYCODE_CALL:
handleCallKey(event);
return;
default:
break;
}
// 为事件分配显示器ID
assignDisplayId(event, targetDisplayType);
// 允许特定目标的按键被路由到仪表盘
if (targetDisplayType == CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER
&& handleInstrumentClusterKey(event)) {
return;
}
// 如果CaptureController处理了事件,则返回
if (mCaptureController.onKeyEvent(targetDisplayType, event)) {
return;
}
// 使用默认键处理器处理事件
mDefaultKeyHandler.onKeyEvent(event, targetDisplayType, mDriverSeat);
}
}
驾驶员处理按键事件。
- 语音按键,调用
handleVoiceAssistKey
方法进行处理。 - 通话按键,调用
handleCallKey
方法进行处理。 - 如果目标显示器类型是仪表盘,并且
handleInstrumentClusterKey()
方法处理了事件,则返回。 - 如果InputCaptureClientController处理了事件,则返回。
- 如果上述处理均未处理事件,使用
mDefaultKeyHandler
进行默认处理。
CarInputService.handleVoiceAssistKey()¶
// packages/services/Car/service/src/com/android/car/CarInputService.java
public class CarInputService extends ICarInput.Stub
implements CarServiceBase, InputHalService.InputListener {
/**
* 处理语音辅助键的按键事件。
*
* @param event 按键事件
* @param targetDisplayType 目标显示器类型
*/
private void handleVoiceAssistKey(KeyEvent event, @DisplayTypeEnum int targetDisplayType) {
int action = event.getAction(); // 获取按键动作
// 如果按键动作是按下,并且重复计数为0(即首次按下)
if (action == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
mVoiceKeyTimer.keyDown(); // 记录按键按下的时间
dispatchProjectionKeyEvent(CarProjectionManager.KEY_EVENT_VOICE_SEARCH_KEY_DOWN); // 分发按键按下事件
} else if (action == KeyEvent.ACTION_UP) { // 如果按键动作是抬起
if (mVoiceKeyTimer.keyUp()) {
// 长按已经由handleVoiceAssistLongPress()处理,无需再做处理。
// 如果CarProjectionService感兴趣,则将事件交给CarProjectionService处理,否则处理结束。
dispatchProjectionKeyEvent(
CarProjectionManager.KEY_EVENT_VOICE_SEARCH_LONG_PRESS_KEY_UP);
return;
}
// 如果短按事件被投影处理,则返回
if (dispatchProjectionKeyEvent(
CarProjectionManager.KEY_EVENT_VOICE_SEARCH_SHORT_PRESS_KEY_UP)) {
return;
}
// TODO: b/288107028 - 当乘客显示器支持语音辅助键时,将实际目标显示类型传递给onKeyEvent
if (mCaptureController.onKeyEvent(targetDisplayType, event)) {
return;
}
// 启动默认的语音助手处理程序
launchDefaultVoiceAssistantHandler();
}
}
}
语音按键事件处理流程:
- 按键按下:
- 如果按键动作是按下,并且重复计数为0(即首次按下),记录按键按下的时间,并分发按键按下事件。
- 按键抬起:
- 如果按键动作是抬起,首先检查是否为长按事件。
- 如果是长按事件,分发长按抬起事件给 CarProjectionService 处理。
- 如果是短按事件,检查是否被 CarProjectionService 处理,如果是则返回。
- 如果上述处理均未处理事件,调用
mCaptureController
处理事件。 - 如果仍未处理事件,启动默认的语音助手处理程序。
CarInputService.dispatchProjectionKeyEvent()¶
// packages/services/Car/service/src/com/android/car/CarInputService.java
public class CarInputService extends ICarInput.Stub
implements CarServiceBase, InputHalService.InputListener {
/**
* 分发投影按键事件。
*
* @param event 要分发的按键事件编号
* @return 如果事件成功分发给事件处理器,返回true;否则返回false
*/
private boolean dispatchProjectionKeyEvent(@CarProjectionManager.KeyEventNum int event) {
CarProjectionManager.ProjectionKeyEventHandler projectionKeyEventHandler;
// 使用同步块确保对事件处理器的访问是线程安全的
synchronized (mLock) {
// 获取当前的投影按键事件处理器
projectionKeyEventHandler = mProjectionKeyEventHandler;
// 如果没有事件处理器,或者事件处理器不订阅该事件,则返回false
if (projectionKeyEventHandler == null || !mProjectionKeyEventsSubscribed.get(event)) {
// 没有事件处理器,或者事件处理器不需要该事件——处理结束。
return false;
}
}
// 调用事件处理器的onKeyEvent方法处理事件
projectionKeyEventHandler.onKeyEvent(event);
return true;
}
}
我们在 CarInputService.setProjectionKeyEventHandler() 提到 CarProjectionService.registerKeyEventHandler()
中设置按键事件监听器。所以事件都交由 CarProjectionService 来出来。
这种情况我们后续讲到 CarProjectionService 再详细分析。
InputCaptureClientController.onKeyEvent()¶
我们回到 CarInputService.handleVoiceAssistKey() 方法,讨论mCaptureController.onKeyEvent()
处理的情形。
// packages/services/Car/service/src/com/android/car/CarInputService.java
public class CarInputService extends ICarInput.Stub
implements CarServiceBase, InputHalService.InputListener {
private final InputCaptureClientController mCaptureController;
private void handleVoiceAssistKey(KeyEvent event, @DisplayTypeEnum int targetDisplayType) {
int action = event.getAction();
if (action == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
...
} else if (action == KeyEvent.ACTION_UP) {
...
// TODO: b/288107028 - Pass the actual target display type to onKeyEvent
// when passenger displays support voice assist keys if (mCaptureController.onKeyEvent(targetDisplayType, event)) {
return;
}
...
}
}
}
这里的 mCaptureController
指的是 InputCaptureClientController
,它是在 CarInputService
的构造函数中通过 mCaptureController = new InputCaptureClientController(context)
进行初始化的。
// packages/services/Car/service/src/com/android/car/InputCaptureClientController.java
/**
* 管理来自客户端的输入捕获请求
*/
public class InputCaptureClientController {
/**
* 将给定的{@code KeyEvent}分发给捕获的客户端(如果存在)。
*
* @param displayType 显示器类型,由{@code CarInputManager}定义,例如
* {@link CarOccupantZoneManager#DISPLAY_TYPE_MAIN}
* @param event 要处理的按键事件
* @return 如果事件被消费,返回true。
*/
public boolean onKeyEvent(@DisplayTypeEnum int displayType, KeyEvent event) {
// 检查显示器类型是否受支持
if (!SUPPORTED_DISPLAY_TYPES.contains(displayType)) {
return false; // 如果不支持,返回false
}
// 根据按键代码获取输入类型
Integer inputType = KEY_EVENT_TO_INPUT_TYPE.get(event.getKeyCode());
if (inputType == null) { // 如果按键不受支持
inputType = CarInputManager.INPUT_TYPE_ALL_INPUTS; // 设置为所有输入类型
}
ICarInputCallback callback;
synchronized (mLock) {
// 获取与输入类型对应的客户端回调
callback = getClientForInputTypeLocked(displayType, inputType);
if (callback == null) {
return false; // 如果没有对应的客户端,返回false
}
mNumKeyEventsDispatched++; // 增加已分发的按键事件计数
}
// 分发按键事件给客户端回调
dispatchKeyEvent(displayType, event, callback);
return true; // 返回true表示事件被消费
}
}
根据 CarInputManager.requestInputEventCapture() 的分析,这里拿到的 callback
就是客户端注册时传进来的回调。
InputCaptureClientController.dispatchKeyEvent()¶
// packages/services/Car/service/src/com/android/car/CarInputService.java
public class InputCaptureClientController {
/**
* 分发按键事件到指定的客户端回调。
*
* @param targetDisplayType 目标显示器类型
* @param event 要分发的按键事件
* @param callback 用于接收按键事件的客户端回调接口
*/
private void dispatchKeyEvent(int targetDisplayType, KeyEvent event,
ICarInputCallback callback) {
// 在通用线程上运行事件分发逻辑
CarServiceUtils.runOnCommon(() -> {
// 清空临时列表以准备新的事件
mKeyEventDispatchScratchList.clear();
// 将事件添加到临时列表中
mKeyEventDispatchScratchList.add(event);
try {
// 调用客户端回调的onKeyEvents方法,将事件分发给客户端
callback.onKeyEvents(targetDisplayType, mKeyEventDispatchScratchList);
} catch (RemoteException e) {
// 如果分发失败且启用了调试日志,记录错误信息
if (DBG_DISPATCH) {
Slogf.e(TAG, "Failed to dispatch KeyEvent " + event, e);
}
}
});
}
}
将按键事件分发到指定的客户端回调。 我们继续回到 CarInputService.handleVoiceAssistKey() 中,如果仍未处理事件,启动默认的语音助手处理程序。
CarInputService.launchDefaultVoiceAssistantHandler()¶
// packages/services/Car/service/src/com/android/car/CarInputService.java
public class CarInputService extends ICarInput.Stub
implements CarServiceBase, InputHalService.InputListener {
/**
* 启动默认的语音助手处理程序。
*/
private void launchDefaultVoiceAssistantHandler() {
// 记录日志,指示语音键被触发并调用AssistUtilsHelper
Slogf.d(TAG, "voice key, invoke AssistUtilsHelper");
// 调用AssistUtilsHelper的showPushToTalkSessionForActiveService方法
// 如果无法检索当前用户的辅助组件,记录警告日志
if (!AssistUtilsHelper.showPushToTalkSessionForActiveService(mContext, mShowCallback)) {
Slogf.w(TAG, "Unable to retrieve assist component for current user");
}
}
}
通过 IPC 启动默认的语音助手处理程序。
总结¶
语音按键事件首先由CarProjectionService
服务进行处理;如果CarProjectionService
未能成功处理,则交由客户端进程处理;若客户端进程也未能处理,则启动默认的语音助手。
CarInputService.handleCallKey()¶
回到 CarInputService.dispatchKeyEventForDriver() 开始处理 handleCallKey()。
// packages/services/Car/service/src/com/android/car/CarInputService.java
public class CarInputService extends ICarInput.Stub
implements CarServiceBase, InputHalService.InputListener {
/**
* 处理电话按键事件。
*
* @param event 按键事件
*/
private void handleCallKey(KeyEvent event) {
int action = event.getAction(); // 获取按键动作
// 如果按键动作是按下,并且重复计数为0(即首次按下)
if (action == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
mCallKeyTimer.keyDown(); // 记录按键按下的时间
dispatchProjectionKeyEvent(CarProjectionManager.KEY_EVENT_CALL_KEY_DOWN); // 分发按键按下事件
} else if (action == KeyEvent.ACTION_UP) { // 如果按键动作是抬起
if (mCallKeyTimer.keyUp()) {
// 长按已经由handleCallLongPress()处理,无需再做处理。
// 如果投影感兴趣,则将事件交给投影处理,否则处理结束。
dispatchProjectionKeyEvent(CarProjectionManager.KEY_EVENT_CALL_LONG_PRESS_KEY_UP);
return;
}
// 如果电话正在响铃,接听电话
if (acceptCallIfRinging()) {
// 已接听响铃电话,无需再做处理。
return;
}
// 如果正在进行通话且应结束通话,则结束通话
if (mShouldCallButtonEndOngoingCallSupplier.getAsBoolean() && endCall()) {
// 已结束正在进行的通话,无需再做处理。
return;
}
// 如果短按事件被投影处理,则返回
if (dispatchProjectionKeyEvent(
CarProjectionManager.KEY_EVENT_CALL_SHORT_PRESS_KEY_UP)) {
return;
}
// 启动拨号程序处理程序
launchDialerHandler();
}
}
}
电话按键事件处理流程:
- 按键按下:
- 如果按键动作是按下,并且重复计数为0(即首次按下),记录按键按下的时间,并分发按键按下事件。
- 按键抬起:
- 如果按键动作是抬起,首先检查是否为长按事件。
- 如果是长按事件,分发长按抬起事件给 CarProjectionService 处理。
- 如果电话正在响铃,调用
acceptCallIfRinging
方法接听电话。 - 如果正在进行通话且应结束通话,调用
endCall
方法结束通话。 - 如果上述处理均未处理事件,调用
dispatchProjectionKeyEvent
方法处理短按事件。 - 如果仍未处理事件,启动拨号程序处理程序。
在这个流程中,CarProjectionService
的处理方式与语音按键事件类似,这里不再重复说明。
CarInputService.acceptCallIfRinging()¶
// packages/services/Car/service/src/com/android/car/CarInputService.java
public class CarInputService extends ICarInput.Stub
implements CarServiceBase, InputHalService.InputListener {
/**
* 如果电话正在响铃,则接听电话。
*
* @return 如果电话正在响铃并成功接听,返回true;否则返回false。
*/
private boolean acceptCallIfRinging() {
// 检查mTelecomManager是否不为空,并且电话是否正在响铃
if (mTelecomManager != null && mTelecomManager.isRinging()) {
// 记录日志,指示在电话响铃时按下了接听键,并接听电话
Slogf.i(TAG, "call key while ringing. Answer the call!");
// 接听响铃电话
mTelecomManager.acceptRingingCall();
return true; // 返回true表示电话已被接听
}
return false; // 返回false表示电话未响铃或未接听
}
}
电话响铃时接听电话。
CarInputService.endCall()¶
// packages/services/Car/service/src/com/android/car/CarInputService.java
public class CarInputService extends ICarInput.Stub
implements CarServiceBase, InputHalService.InputListener {
/**
* 结束当前通话。
*
* @return 如果成功结束通话,返回true;否则返回false。
*/
private boolean endCall() {
// 检查mTelecomManager是否不为空,并且当前是否正在通话
if (mTelecomManager != null && mTelecomManager.isInCall()) {
// 记录日志,指示结束通话
Slogf.i(TAG, "End the call!");
// 调用mTelecomManager的endCall方法结束通话
mTelecomManager.endCall();
return true; // 返回true表示通话已结束
}
return false; // 返回false表示没有正在进行的通话或无法结束通话
}
}
在通话过程中结束通话。
CarInputService.launchDialerHandler()¶
// packages/services/Car/service/src/com/android/car/CarInputService.java
public class CarInputService extends ICarInput.Stub
implements CarServiceBase, InputHalService.InputListener {
private void launchDialerHandler() {
Slogf.i(TAG, "call key, launch dialer intent");
Intent dialerIntent = new Intent(Intent.ACTION_DIAL);
mContext.startActivityAsUser(dialerIntent, UserHandle.CURRENT);
}
}
启动拨号界面。
CarInputService.handleInstrumentClusterKey()¶
回到 CarInputService.dispatchKeyEventForDriver() 开始处理 handleInstrumentClusterKey()。
// packages/services/Car/service/src/com/android/car/CarInputService.java
public class CarInputService extends ICarInput.Stub
implements CarServiceBase, InputHalService.InputListener {
/**
* 处理仪表盘按键事件。
*
* @param event 按键事件
* @return 如果按键事件没有被消费(因为没有InstrumentClusterKeyListener),返回false。
*/
private boolean handleInstrumentClusterKey(KeyEvent event) {
KeyEventListener listener;
// 在同步块中获取当前的仪表盘按键事件监听器
synchronized (mLock) {
listener = mInstrumentClusterKeyListener;
}
// 如果没有监听器,返回false,表示事件未被消费
if (listener == null) {
return false;
}
// 调用监听器的onKeyEvent方法处理事件
listener.onKeyEvent(event);
return true; // 返回true,表示事件已被消费
}
}
如果有注册仪表盘按键事件监听器,则将事件传递给监听器进行处理。参考 CarInputService.setInstrumentClusterKeyListener() 。
InputCaptureClientController.onKeyEvent()¶
回到 CarInputService.dispatchKeyEventForDriver() 开始处理 InputCaptureClientController.onKeyEvent()。
// packages/services/Car/service/src/com/android/car/InputCaptureClientController.java
public class InputCaptureClientController {
/**
* 将给定的{@code KeyEvent}分发给捕获的客户端(如果存在)。
*
* @param displayType 显示器类型,由{@code CarInputManager}定义,例如
* {@link CarOccupantZoneManager#DISPLAY_TYPE_MAIN}
* @param event 要处理的按键事件
* @return 如果事件被消费,返回true。
*/
public boolean onKeyEvent(@DisplayTypeEnum int displayType, KeyEvent event) {
// 检查显示器类型是否受支持
if (!SUPPORTED_DISPLAY_TYPES.contains(displayType)) {
return false; // 如果不支持,返回false
}
// 根据按键代码获取输入类型
Integer inputType = KEY_EVENT_TO_INPUT_TYPE.get(event.getKeyCode());
if (inputType == null) { // 如果按键不受支持
inputType = CarInputManager.INPUT_TYPE_ALL_INPUTS; // 设置为所有输入类型
}
ICarInputCallback callback;
synchronized (mLock) {
// 获取与输入类型对应的客户端回调
callback = getClientForInputTypeLocked(displayType, inputType);
if (callback == null) {
return false; // 如果没有对应的客户端,返回false
}
mNumKeyEventsDispatched++; // 增加已分发的按键事件计数
}
// 分发按键事件给客户端回调
dispatchKeyEvent(displayType, event, callback);
return true; // 返回true表示事件被消费
}
}
在前面的章节 CarInputManager.requestInputEventCapture() 我们提到了如何获取 callback
InputCaptureClientController.getClientForInputTypeLocked() 。
这里获取到的 callback
就是客户端进程注册进来的回调。
// packages/services/Car/service/src/com/android/car/InputCaptureClientController.java
public class InputCaptureClientController {
/**
* 将按键事件分发到指定的客户端回调。
*
* @param targetDisplayType 目标显示器类型
* @param event 要分发的按键事件
* @param callback 用于接收按键事件的客户端回调接口
*/
private void dispatchKeyEvent(int targetDisplayType, KeyEvent event,
ICarInputCallback callback) {
// 使用CarServiceUtils在通用线程池上运行事件分发逻辑,确保异步执行
CarServiceUtils.runOnCommon(() -> {
// 清空临时列表以准备新的事件
mKeyEventDispatchScratchList.clear();
// 将事件添加到临时列表中
mKeyEventDispatchScratchList.add(event);
try {
// 调用客户端回调的onKeyEvents方法,将事件和目标显示器类型传递给客户端
callback.onKeyEvents(targetDisplayType, mKeyEventDispatchScratchList);
} catch (RemoteException e) {
// 如果在调用回调时发生RemoteException,并且启用了调试日志,则记录错误信息
if (DBG_DISPATCH) {
Slogf.e(TAG, "Failed to dispatch KeyEvent " + event, e);
}
}
});
}
}
将按键事件分发到指定的客户端回调。
mDefaultKeyHandler.onKeyEvent()¶
回到 CarInputService.dispatchKeyEventForDriver() 开始处理 InputCaptureClientController.onKeyEvent()。
其代码如:
// packages/services/Car/service/src/com/android/car/CarInputService.java
public class CarInputService extends ICarInput.Stub
implements CarServiceBase, InputHalService.InputListener {
private void dispatchKeyEventForDriver(KeyEvent event, @DisplayTypeEnum int targetDisplayType) {
...
mDefaultKeyHandler.onKeyEvent(event, targetDisplayType, mDriverSeat);
}
}
这里 mDefaultKeyHandler
就是 KeyEventListener
其初始化是在 CarInputService
构造函数里,其主要的功能是见到按键事件后通过 InputManagerHelper.injectInputEvent(context.getSystemService(InputManager.class), event)
把事件发给 system_server
进程的 Input模块
。
// packages/services/Car/service/src/com/android/car/CarInputService.java
public class CarInputService extends ICarInput.Stub
implements CarServiceBase, InputHalService.InputListener {
// 默认的主显示器按键事件处理器。默认情况下,通过InputManager将事件注入输入队列,
// 但可以被覆盖以用于测试。
private final KeyEventListener mDefaultKeyHandler;
// CarInputService构造函数,初始化服务
public CarInputService(Context context, InputHalService inputHalService,
CarUserService userService, CarOccupantZoneService occupantZoneService,
CarBluetoothService bluetoothService, CarPowerManagementService carPowerService,
SystemInterface systemInterface) {
this(...,
new KeyEventListener() {
@Override
public void onKeyEvent(KeyEvent event, @DisplayTypeEnum int displayType,
@VehicleAreaSeat.Enum int seat) {
// 使用InputManager将事件注入输入队列
InputManagerHelper.injectInputEvent(
context.getSystemService(InputManager.class), event);
}
},
...);
}
@VisibleForTesting
CarInputService(...,
KeyEventListener defaultKeyHandler,
...) {
super();
// 初始化默认按键处理器
mDefaultKeyHandler = defaultKeyHandler;
}
}
在 dispatchKeyEventForDriver()
方法中,经过前面的层层判断后,如果某个按键事件没有被特殊处理器拦截并处理,最终将交由mDefaultKeyHandler
进行处理。这意味着该事件会被直接注入到system_server
进程的Input模块
,从而按照手机的标准流程进行处理。
这种处理方式可能忽略了车载系统中特殊按键的独特需求。
我的疑问是:
在非驾驶员座位发出的按键都要调用 notifyKeyEventListener()
通知到监听方。也就是在 mDefaultKeyHandler.onKeyEvent()
处理的之后加了一些特殊按键的处理,如:
- POWER、HOME按键
- VOLUME按键
- MEDIA按键
那么如果驾驶员按下了 VOLUME按键 ,这个事件是 dispatchKeyEventForDriver()
方法中通过 mDefaultKeyHandler.onKeyEvent()
最后注入到system_server
进程的Input模块
,那为什么非驾驶员要经过 CarService 处理这些特殊按钮而驾驶员不需要呢?
特殊按键¶
在前面的 CarInputService.registerKeyEventListener() 分析到,这个方法的主要功能是注册一个按键事件监听器,用于监听其感兴趣的按键事件。如果某个按键代码已经注册到其他监听器,抛出异常。
那其派发是在哪里的呢?我们回到 CarInputService.onKeyEvent() 中,在非驾驶员座位发出的按键都要调用 notifyKeyEventListener()
通知到监听方。
// packages/services/Car/service/src/com/android/car/CarInputService.java
public class CarInputService extends ICarInput.Stub
implements CarServiceBase, InputHalService.InputListener {
/**
* 通知按键事件监听器处理按键事件。
*
* @param event 按键事件对象,包含按键操作的详细信息。
* @param targetDisplay 目标显示器类型,指示事件发生在哪个显示器上。
* @param seat 座位区域,指示事件与哪个座位相关。
*/
private void notifyKeyEventListener(KeyEvent event, int targetDisplay, int seat) {
KeyEventListener keyEventListener;
// 使用同步块以确保线程安全地访问监听器集合
synchronized (mLock) {
// 从监听器集合中获取与按键代码对应的监听器
keyEventListener = mListeners.get(event.getKeyCode());
}
// 如果没有找到对应的按键事件监听器
if (keyEventListener == null) {
// 如果调试模式开启,记录调试日志
if (DBG) {
Slogf.d(TAG, "Key event listener not found for event %s",
KeyEvent.keyCodeToString(event.getKeyCode()));
}
// 如果没有监听器,使用默认的按键处理器
keyEventListener = mDefaultKeyHandler;
}
// 为座位分配显示ID,确保事件被正确路由到相应的显示器
assignDisplayIdForSeat(event, targetDisplay, seat);
// 调用监听器的onKeyEvent方法处理按键事件
keyEventListener.onKeyEvent(event, targetDisplay, seat);
}
}
POWER、HOME按键¶
// packages/services/Car/service/src/com/android/car/CarInputService.java
public class CarInputService extends ICarInput.Stub
implements CarServiceBase, InputHalService.InputListener {
// 默认的特殊按键处理器。在此服务中实现按键的行为。可以通过registerKeyEventListener方法覆盖。
private final KeyEventListener mDefaultSpecialKeyHandler = new KeyEventListener() {
@Override
public void onKeyEvent(KeyEvent event, @DisplayTypeEnum int displayType,
@VehicleAreaSeat.Enum int seat) {
// 根据按键的键码进行处理
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_HOME:
// 处理Home键事件
handleHomeKey(event, displayType, seat);
break;
case KeyEvent.KEYCODE_POWER:
// 处理电源键事件
handlePowerKey(event, displayType, seat);
break;
default:
// 如果按键不被特殊按键处理器支持,记录错误日志
Slogf.e(TAG, "Key event %s is not supported by special key handler",
KeyEvent.keyCodeToString(event.getKeyCode()));
break;
}
}
};
@VisibleForTesting
CarInputService(...) {
super();
// 注册特殊按键事件监听器,监听Home键和电源键
registerKeyEventListener(mDefaultSpecialKeyHandler,
Arrays.asList(KeyEvent.KEYCODE_HOME, KeyEvent.KEYCODE_POWER));
}
}
Home键的处理流程相对简单,它的主要任务是根据userId
和displayId
启动相应显示器的主页界面。
相比之下,Power键的处理流程则复杂得多,它涉及到CarPowerManagementService
、CarOccupantZoneService
等多个服务的协调与交互。由于其复杂性,我们将在后续对CarPowerManagementService
的分析中深入探讨这一流程。
VOLUME按键¶
// packages/services/Car/service/src/com/android/car/audio/CarAudioService.java
public final class CarAudioService extends ICarAudio.Stub implements CarServiceBase {
private static final List<Integer> KEYCODES_OF_INTEREST = List.of(
KEYCODE_VOLUME_DOWN,
KEYCODE_VOLUME_UP,
KEYCODE_VOLUME_MUTE
);
mCarInputService.registerKeyEventListener(mCarKeyEventListener,
KEYCODES_OF_INTEREST);
}
CarAudioService 服务注册监听音量按键,这里也不再深入分析。
MEDIA按键¶
// packages/services/Car/service/src/com/android/car/CarMediaService.java
public final class CarMediaService extends ICarMedia.Stub implements CarServiceBase {
private void setKeyEventListener() {
int maxKeyCode = KeyEvent.getMaxKeyCode();
ArrayList<Integer> mediaKeyCodes = new ArrayList<>(15);
for (int key = 1; key <= maxKeyCode; key++) {
if (KeyEvent.isMediaSessionKey(key)) {
mediaKeyCodes.add(key);
}
}
CarLocalServices.getService(CarInputService.class)
.registerKeyEventListener(new MediaKeyEventListener(), mediaKeyCodes);
}
}
继续看 KeyEvent.isMediaSessionKey()
// frameworks/base/core/java/android/view/KeyEvent.java
public class KeyEvent extends InputEvent implements Parcelable {
public static final boolean isMediaSessionKey(int keyCode) {
switch (keyCode) {
case KeyEvent.KEYCODE_MEDIA_PLAY:
case KeyEvent.KEYCODE_MEDIA_PAUSE:
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
case KeyEvent.KEYCODE_HEADSETHOOK:
case KeyEvent.KEYCODE_MEDIA_STOP:
case KeyEvent.KEYCODE_MEDIA_NEXT:
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
case KeyEvent.KEYCODE_MEDIA_REWIND:
case KeyEvent.KEYCODE_MEDIA_RECORD:
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
return true;
}
return false;
}
}
CarMediaService 服务注册监听多媒体按键,这里也不再深入分析。
触摸事件¶
CarInputService.onMotionEvent()¶
// packages/services/Car/service/src/com/android/car/CarInputService.java
public class CarInputService extends ICarInput.Stub
implements CarServiceBase, InputHalService.InputListener {
/**
* 处理运动事件的方法。
* @param event 运动事件对象,包含了触摸事件的详细信息。
* @param targetDisplayType 目标显示类型,指示事件发生在哪个显示器上。
* @param seat 座位区域,指示事件与哪个座位相关。
*/
@Override
public void onMotionEvent(MotionEvent event, @DisplayTypeEnum int targetDisplayType,
@VehicleAreaSeat.Enum int seat) {
// 如果座位信息未知,抛出非法参数异常
if (seat == VehicleAreaSeat.SEAT_UNKNOWN) {
throw new IllegalArgumentException("Unknown seat");
}
// 通知用户活动,可能用于唤醒系统或重置屏幕超时计时器
notifyUserActivity(event, targetDisplayType, seat);
// 为座位分配显示ID,确保事件被正确路由到相应的显示器
assignDisplayIdForSeat(event, targetDisplayType, seat);
// 使用默认的运动事件处理器处理事件
mDefaultMotionHandler.onMotionEvent(event);
}
}
首先,通过 notifyUserActivity()
方法通知 CarPowerManagementService
服务更新 DisplayPowerInfo
中的 mLastUserActivityTime
,即设置屏幕的最后触摸时间。这一时间用于重置屏幕超时计时器,确保在用户交互时屏幕保持唤醒状态。
其次,调用 assignDisplayIdForSeat()
方法为座位分配正确的 displayId
,确保事件被准确地路由到相应的显示器上。
最后,调用 mDefaultMotionHandler.onMotionEvent(event)
来处理运动事件。
在 mDefaultMotionHandler
就是 MotionEventListener
其初始化是在 CarInputService
构造函数里,其主要的功能是接到触摸事件后通过 InputManagerHelper.injectInputEvent(context.getSystemService(InputManager.class), event)
把事件发给 system_server
进程的 Input模块
。
// packages/services/Car/service/src/com/android/car/CarInputService.java
public class CarInputService extends ICarInput.Stub
implements CarServiceBase, InputHalService.InputListener {
// The default handler for main-display motion events. By default, injects the events into
// the input queue via InputManager, but can be overridden for testing. private final MotionEventListener mDefaultMotionHandler;
public CarInputService(Context context, InputHalService inputHalService,
CarUserService userService, CarOccupantZoneService occupantZoneService,
CarBluetoothService bluetoothService, CarPowerManagementService carPowerService,
SystemInterface systemInterface) {
this(...,
/* defaultMotionHandler= */ event -> InputManagerHelper.injectInputEvent(
context.getSystemService(InputManager.class), event),
...);
}
@VisibleForTesting
CarInputService(...,
MotionEventListener defaultMotionHandler,
...) {
super();
mDefaultMotionHandler = defaultMotionHandler;
}
/** An interface to receive {@link MotionEvent}s as they occur. */
public interface MotionEventListener {
/** Called when a motion event occurs. */
void onMotionEvent(MotionEvent event);
}
}
这里发现触摸事件的处理比按键事件简单很多。
旋钮事件¶
CarInputService.onRotaryEvent()¶
// packages/services/Car/service/src/com/android/car/CarInputService.java
public class CarInputService extends ICarInput.Stub
implements CarServiceBase, InputHalService.InputListener {
/**
* 处理旋钮事件的方法。
* @param event 旋钮事件对象,包含了旋钮操作的详细信息。
* @param targetDisplay 目标显示类型,指示事件发生在哪个显示器上。
*/
@Override
public void onRotaryEvent(RotaryEvent event, @DisplayTypeEnum int targetDisplay) {
// 首先尝试通过捕获控制器处理旋钮事件
if (!mCaptureController.onRotaryEvent(targetDisplay, event)) {
// 如果捕获控制器未处理事件,将旋钮事件转换为按键事件列表
List<KeyEvent> keyEvents = rotaryEventToKeyEvents(event);
// 遍历转换后的按键事件列表
for (KeyEvent keyEvent : keyEvents) {
// 调用按键事件处理方法处理每个按键事件
onKeyEvent(keyEvent, targetDisplay);
}
}
}
}
- 首先,尝试通过
mCaptureController
处理旋钮事件。如果mCaptureController.onRotaryEvent(targetDisplay, event)
返回false
,表示事件未被处理。 - 如果事件未被捕获控制器处理,则调用
rotaryEventToKeyEvents(event)
方法将旋钮事件转换为按键事件列表。这是因为某些旋钮操作可以映射为一系列按键操作。 - 遍历转换后的按键事件列表,并调用
onKeyEvent(keyEvent, targetDisplay)
方法逐个处理每个按键事件。这确保了即使旋钮事件未被直接处理,也能通过按键事件的形式间接处理。
InputCaptureClientController.onRotaryEvent()¶
这里的 mCaptureController
指的是 InputCaptureClientController
,它是在 CarInputService
的构造函数中通过 mCaptureController = new InputCaptureClientController(context)
进行初始化的。
// packages/services/Car/service/src/com/android/car/InputCaptureClientController.java
public class InputCaptureClientController {
/**
* 将给定的{@code RotaryEvent}分发给捕获的客户端(如果存在)。
*
* @param displayType 显示类型,在{@code CarInputManager}中定义,例如
* {@link CarOccupantZoneManager#DISPLAY_TYPE_MAIN}
* @param event 要处理的旋钮事件
* @return 如果事件被消费则返回true。
*/
public boolean onRotaryEvent(@DisplayTypeEnum int displayType, RotaryEvent event) {
// 检查显示类型是否支持
if (!SUPPORTED_DISPLAY_TYPES.contains(displayType)) {
// 如果不支持,记录警告日志并返回false
Slogf.w(TAG, "onRotaryEvent for not supported display:" + displayType);
return false;
}
// 获取旋钮事件的输入类型
int inputType = event.getInputType();
// 检查输入类型是否有效
if (!VALID_ROTARY_TYPES.contains(inputType)) {
// 如果无效,记录警告日志并返回false
Slogf.w(TAG, "onRotaryEvent for not supported input type:" + inputType);
return false;
}
ICarInputCallback callback;
// 同步锁定以确保线程安全
synchronized (mLock) {
// 获取与输入类型对应的客户端回调
callback = getClientForInputTypeLocked(displayType, inputType);
if (callback == null) {
// 如果没有找到对应的客户端回调,记录信息日志(如果调试模式开启)并返回false
if (DBG_DISPATCH) {
Slogf.i(TAG, "onRotaryEvent no client for input type:" + inputType);
}
return false;
}
// 记录已分发的旋钮事件数量
mNumRotaryEventsDispatched++;
}
// 分发旋钮事件给客户端回调
dispatchRotaryEvent(displayType, event, callback);
// 返回true表示事件已被消费
return true;
}
}
获取客户端可以参考 InputCaptureClientController.getClientForInputTypeLocked() ,获取到客户端后就分发事件给客户端。
// packages/services/Car/service/src/com/android/car/InputCaptureClientController.java
public class InputCaptureClientController {
/**
* 将旋钮事件分发给指定的客户端回调。
*
* @param targetDisplayType 目标显示器类型,指示事件发生在哪个显示器上。
* @param event 要分发的旋钮事件。
* @param callback 客户端回调接口,用于接收事件。
*/
private void dispatchRotaryEvent(int targetDisplayType, RotaryEvent event,
ICarInputCallback callback) {
// 如果调试模式开启,记录分发的旋钮事件日志
if (DBG_DISPATCH) {
Slogf.i(TAG, "dispatchRotaryEvent:" + event);
}
// TODO(b/159623196): 使用HandlerThread来分发事件,而不是依赖主线程。
// 需要在此处和其他分发方法中进行修改。
CarServiceUtils.runOnCommon(() -> {
// 清空临时事件列表,确保不会携带旧数据
mRotaryEventDispatchScratchList.clear();
// 将当前事件添加到临时事件列表中
mRotaryEventDispatchScratchList.add(event);
try {
// 调用客户端回调方法,将事件分发给客户端
callback.onRotaryEvents(targetDisplayType, mRotaryEventDispatchScratchList);
} catch (RemoteException e) {
// 如果分发失败且调试模式开启,记录错误日志
if (DBG_DISPATCH) {
Slogf.e(TAG, "Failed to dispatch RotaryEvent " + event, e);
}
}
});
}
}
CarInputService.rotaryEventToKeyEvents()¶
回到 CarInputService.onRotaryEvent() 如果旋钮事件没有客户端处理,则转换成按键事件进行处理。
// packages/services/Car/service/src/com/android/car/CarInputService.java
public class CarInputService extends ICarInput.Stub
implements CarServiceBase, InputHalService.InputListener {
/**
* 将旋钮事件转换为按键事件列表。
*
* @param event 旋钮事件对象,包含旋钮操作的详细信息。
* @return 转换后的按键事件列表。
*/
private static List<KeyEvent> rotaryEventToKeyEvents(RotaryEvent event) {
// 获取旋钮事件中的点击次数
int numClicks = event.getNumberOfClicks();
// 每次点击会产生一个按下和一个抬起事件,因此事件数量是点击次数的两倍
int numEvents = numClicks * 2;
// 判断旋钮旋转方向是否为顺时针
boolean clockwise = event.isClockwise();
int keyCode;
// 根据输入类型确定对应的按键代码
switch (event.getInputType()) {
case CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION:
// 导航类型的旋钮事件,顺时针为下一个,逆时针为上一个
keyCode = clockwise
? KeyEvent.KEYCODE_NAVIGATE_NEXT
: KeyEvent.KEYCODE_NAVIGATE_PREVIOUS;
break;
case CarInputManager.INPUT_TYPE_ROTARY_VOLUME:
// 音量类型的旋钮事件,顺时针为音量增加,逆时针为音量减少
keyCode = clockwise
? KeyEvent.KEYCODE_VOLUME_UP
: KeyEvent.KEYCODE_VOLUME_DOWN;
break;
default:
// 未知的旋钮输入类型,记录错误日志并返回空列表
Slogf.e(TAG, "Unknown rotary input type: %d", event.getInputType());
return Collections.EMPTY_LIST;
}
// 创建用于存储按键事件的列表
ArrayList<KeyEvent> keyEvents = new ArrayList<>(numEvents);
// 遍历每次点击,生成对应的按下和抬起事件
for (int i = 0; i < numClicks; i++) {
// 获取每次点击的时间戳
long uptime = event.getUptimeMillisForClick(i);
// 创建按下事件
KeyEvent downEvent = createKeyEvent(/* down= */ true, uptime, uptime, keyCode);
// 创建抬起事件
KeyEvent upEvent = createKeyEvent(/* down= */ false, uptime, uptime, keyCode);
// 将事件添加到列表中
keyEvents.add(downEvent);
keyEvents.add(upEvent);
}
// 返回生成的按键事件列表
return keyEvents;
}
/**
* 创建按键事件。
*
* @param down 指示是按下事件还是抬起事件。
* @param downTime 按下事件的时间戳。
* @param eventTime 事件的时间戳。
* @param keyCode 按键代码,表示哪个按键被按下或抬起。
* @return 创建的按键事件对象。
*/
private static KeyEvent createKeyEvent(boolean down, long downTime, long eventTime,
int keyCode) {
return new KeyEvent(
downTime, // 按下时间
eventTime, // 事件时间
/* action= */ down ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP, // 事件类型:按下或抬起
keyCode, // 按键代码
/* repeat= */ 0, // 重复次数
/* metaState= */ 0, // 元状态
/* deviceId= */ 0, // 设备ID
/* scancode= */ 0, // 扫描码
/* flags= */ 0, // 标志
InputDevice.SOURCE_CLASS_BUTTON // 输入设备源类型
);
}
}
根据旋钮事件的输入类型(如导航或音量),确定对应的按键代码。
- 导航类型:顺时针旋转对应
KEYCODE_NAVIGATE_NEXT
,逆时针对应KEYCODE_NAVIGATE_PREVIOUS
。 - 音量类型:顺时针旋转对应
KEYCODE_VOLUME_UP
,逆时针对应KEYCODE_VOLUME_DOWN
。
定义输入事件¶
CarInputService.onCustomInputEvent()¶
// packages/services/Car/service/src/com/android/car/CarInputService.java
public class CarInputService extends ICarInput.Stub
implements CarServiceBase, InputHalService.InputListener {
/**
* 处理自定义输入事件的方法。
*
* @param event 自定义输入事件对象,包含事件的详细信息。
*/
@Override
public void onCustomInputEvent(CustomInputEvent event) {
// 尝试通过捕获控制器处理自定义输入事件
if (!mCaptureController.onCustomInputEvent(event)) {
// 如果捕获控制器未成功处理事件,记录警告日志并返回
Slogf.w(TAG, "Failed to propagate (%s)", event);
return;
}
// 如果事件成功处理,记录调试日志
Slogf.d(TAG, "Succeed injecting (%s)", event);
}
}
这里的 mCaptureController
指的是 InputCaptureClientController
,它是在 CarInputService
的构造函数中通过 mCaptureController = new InputCaptureClientController(context)
进行初始化的。
处理自定义输入事件处理流程很简单,就是通过 InputCaptureClientController
把事件回调给客户端。
InputCaptureClientController.onRotaryEvent()¶
这里的 mCaptureController
指的是 InputCaptureClientController
,它是在 CarInputService
的构造函数中通过 mCaptureController = new InputCaptureClientController(context)
进行初始化的。
// packages/services/Car/service/src/com/android/car/InputCaptureClientController.java
public class InputCaptureClientController {
/**
* 将给定的{@link CustomInputEvent}分发给捕获的客户端(如果存在)。
* 如果没有为传入事件注册回调,则不会发生任何操作。在这种情况下,该方法将返回{@code false}。
* <p>
* 如果有多个客户端为此事件注册,则只有第一个客户端会收到通知。
*
* @param event 要分发的{@link CustomInputEvent}
* @return 如果事件被消费则返回{@code true}。
*/
public boolean onCustomInputEvent(CustomInputEvent event) {
// 获取事件的目标显示类型
int displayType = event.getTargetDisplayType();
// 检查显示类型是否在支持的显示类型列表中
if (!SUPPORTED_DISPLAY_TYPES.contains(displayType)) {
// 如果不支持,记录警告日志并返回false
Slogf.w(TAG, "onCustomInputEvent for not supported display:" + displayType);
return false;
}
ICarInputCallback callback;
// 使用同步块以确保线程安全
synchronized (mLock) {
// 获取与输入类型对应的客户端回调
callback = getClientForInputTypeLocked(displayType,
CarInputManager.INPUT_TYPE_CUSTOM_INPUT_EVENT);
if (callback == null) {
// 如果没有找到对应的客户端回调,记录警告日志并返回false
Slogf.w(TAG, "No client for input: " + CarInputManager.INPUT_TYPE_CUSTOM_INPUT_EVENT
+ " and display: " + displayType);
return false;
}
}
// 分发自定义输入事件给客户端回调
dispatchCustomInputEvent(displayType, event, callback);
// 返回true表示事件已被消费
return true;
}
}
获取客户端可以参考 InputCaptureClientController.getClientForInputTypeLocked() ,获取到客户端后就分发事件给客户端。
// packages/services/Car/service/src/com/android/car/InputCaptureClientController.java
public class InputCaptureClientController {
/**
* 将自定义输入事件分发给指定的客户端回调。
*
* @param targetDisplayType 目标显示器类型,指示事件发生在哪个显示器上。
* @param event 要分发的自定义输入事件。
* @param callback 客户端回调接口,用于接收事件。
*/
private void dispatchCustomInputEvent(@DisplayTypeEnum int targetDisplayType,
CustomInputEvent event,
ICarInputCallback callback) {
// 如果调试模式开启,记录分发的自定义输入事件日志
if (DBG_DISPATCH) {
Slogf.d(TAG, "dispatchCustomInputEvent:" + event);
}
// 使用CarServiceUtils.runOnCommon方法将分发逻辑封装为异步任务
CarServiceUtils.runOnCommon(() -> {
// 清空临时事件列表,确保不会携带旧数据
mCustomInputEventDispatchScratchList.clear();
// 将当前事件添加到临时事件列表中
mCustomInputEventDispatchScratchList.add(event);
try {
// 调用客户端回调方法,将事件分发给客户端
callback.onCustomInputEvents(targetDisplayType,
mCustomInputEventDispatchScratchList);
} catch (RemoteException e) {
// 如果分发失败且调试模式开启,记录错误日志
if (DBG_DISPATCH) {
Slogf.e(TAG, "Failed to dispatch CustomInputEvent " + event, e);
}
}
});
}
}