跳转至

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
    • 释放之前请求的输入事件捕获。
    • 接受一个回调接口 ICarInputCallbacktargetDisplayType,用于指定在哪个显示设备上释放事件捕获。
  • 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 实例。
    • 然后,调用 HalServicetakeProperties() 方法。
    • 接着,调用 HalServiceinit() 方法。
  • Java服务的初始化

    • 构建每个 CarSystemService 实例。
    • 调用 CarSystemServiceinit() 方法。
    • 最后,调用 CarSystemServiceonInitComplete() 方法。

接下来,我们将按照这个思路,从 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() 之前,我们先了解 ClientInfoForDisplayClientsToDispatch 的数据结构。

// 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);
        }
    }
}

ClientInfoForDisplayClientsToDispatchInputCaptureClientController 类中的两个内部类,用于管理客户端信息和分发输入事件。

  • 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;  
    }  

}

这个函数的代码非常长,前面主要是检查一些权限,后面是把客户端信息存储起来。 我们现在来分析 mPerInputTypeCapturersmFullDisplayEventCapturers

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键的处理流程相对简单,它的主要任务是根据userIddisplayId启动相应显示器的主页界面。

相比之下,Power键的处理流程则复杂得多,它涉及到CarPowerManagementServiceCarOccupantZoneService等多个服务的协调与交互。由于其复杂性,我们将在后续对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);
                }
            }
        });
    }
}

评论