OEM용 통합 가이드

이 도움말에서는 VHAL에서 로터리 입력을 처리하고 로터리 서비스를 포함하도록 빌드를 구성하는 방법과 모든 앱에서 로터리 환경을 맞춤설정하는 방법을 설명합니다. OEM 제공 런처와 같은 사전 설치된 OEM 앱은 자동차 UI 라이브러리(car-ui-library)를 참고하세요.

VHAL

로터리 컨트롤러는 다음 작업을 지원합니다.

  • 위, 아래, 왼쪽, 오른쪽으로 조금씩 이동
  • 시계 방향과 시계 반대 방향으로 회전
  • 가운데 버튼 누르기
  • 뒤로 버튼 누르기
  • 홈 버튼 누르기
  • 전화, 미디어와 같은 기타 버튼 누르기

시스템 속성과 이에 상응하는 int32Values에 관한 문서는 hardware/interfaces/automotive/vehicle/2.0/types.hal을 참고하세요.

VHAL은 다음 작업을 처리해야 합니다.

조금씩 이동

사용자가 로터리 컨트롤러를 오른쪽으로 밀면 VHAL은 다음 int32Values와 함께 HW_KEY_INPUT 속성을 사용하여 Android에 이벤트를 전송해야 합니다.

  1. ACTION_DOWN
  2. KEYCODE_SYSTEM_NAVIGATION_RIGHT
  3. 타겟 디스플레이

사용자가 로터리 컨트롤러에서 손을 떼면 VHAL은 ACTION_UP과 함께 같은 속성과 키 코드를 사용해야 합니다. 다른 방향으로 조금씩 이동하는 경우 상응하는 키 코드를 사용해야 합니다.

대각선에 적용되는 키 코드는 없지만 하드웨어에서 대각선을 지원하면 VHAL은 수평과 수직 이벤트를 결합하여 대각선을 생성할 수 있습니다. 예를 들어 위로 조금씩 이동과 왼쪽으로 조금씩 이동은 다음과 같이 표시됩니다.

  • HW_KEY_INPUT KEYCODE_SYSTEM_NAVIGATION_LEFT ACTION_DOWN
  • HW_KEY_INPUT KEYCODE_SYSTEM_NAVIGATION_UP ACTION_DOWN

어떤 순서로든(또는 이후에) 로터리 컨트롤러에서 손을 떼면 다음과 같이 표시됩니다.

  • HW_KEY_INPUT KEYCODE_SYSTEM_NAVIGATION_LEFT ACTION_UP
  • HW_KEY_INPUT KEYCODE_SYSTEM_NAVIGATION_UP ACTION_UP

사용자는 로터리 컨트롤러에서 손을 떼기 전에 직각 방향으로 컨트롤러를 밀 수 있습니다. 다음 시나리오를 예로 들어 보겠습니다.

직각 방향
그림 1. 직각 방향

그러면 다음과 같은 이벤트 시퀀스가 생성됩니다.

  1. HW_KEY_INPUT KEYCODE_SYSTEM_NAVIGATION_LEFT ACTION_DOWN
  2. HW_KEY_INPUT KEYCODE_SYSTEM_NAVIGATION_UP ACTION_DOWN
  3. HW_KEY_INPUT KEYCODE_SYSTEM_NAVIGATION_LEFT ACTION_UP
  4. HW_KEY_INPUT KEYCODE_SYSTEM_NAVIGATION_UP ACTION_UP

로터리 컨트롤러가 한 방향으로 유지되는 동안에는 repeat 이벤트가 생성되면 안 됩니다.

회전

사용자가 로터리 컨트롤러를 시계 방향으로 1디텐트(클릭) 회전하면 VHAL은 다음 int32Values와 함께 HW_ROTARY_INPUT 속성을 사용하여 Android에 이벤트를 전송해야 합니다.

  1. ROTARY_INPUT_TYPE_SYSTEM_NAVIGATION
  2. 1디텐트
  3. 타겟 디스플레이

이벤트의 타임스탬프는 경과 시간(나노초)으로 설정해야 합니다.

시계 반대 방향으로 1디텐트 회전하면 같은 이벤트를 생성하지만 디텐트 수는 -1입니다.

빠르게 연속해서 같은 방향으로 여러 디텐트를 회전하면 VHAL은 디텐트를 단일 이벤트로 결합하여 시스템이 이벤트로 과부하되지 않도록 해야 합니다. 이 경우 이벤트의 타임스탬프는 첫 번째 회전 디텐트가 발생한 시점이어야 합니다. int32Values에는 연속적인 회전 디텐트 간의 나노초 수가 포함되어야 합니다.

다음과 같은 회전 시퀀스를 예로 들 수 있습니다.

  • t0 시간에 사용자가 시계 반대 방향으로 1디텐트 회전했습니다.
  • t0 + 5ns 시간에 사용자가 시계 반대 방향으로 1디텐트 회전했습니다.
  • t0 + 8ns 시간에 사용자가 시계 반대 방향으로 1디텐트 회전했습니다.

그러면 다음 이벤트가 생성됩니다.

  • 속성: HW_ROTARY_INPUT
  • 타임스탬프: t0
  • int32Values:
    1. ROTARY_INPUT_TYPE_SYSTEM_NAVIGATION
    2. -3(시계 반대 방향으로 3디텐트)
    3. 타겟 디스플레이
    4. 첫 번째와 두 번째 디텐트 사이에 5ns
    5. 두 번째와 세 번째 디텐트 사이에 3ns

가운데 버튼

사용자가 가운데 버튼을 누르면 VHAL은 다음 int32Values와 함께 HW_KEY_INPUT 속성을 사용하여 Android에 이벤트를 전송해야 합니다.

  1. ACTION_DOWN
  2. KEYCODE_DPAD_CENTER
  3. 타겟 디스플레이

사용자가 로터리 컨트롤러에서 손을 떼면 VHAL은 ACTION_UP과 같은 속성과 키 코드를 사용해야 합니다.

가운데 버튼을 누른 상태에서는 repeat 이벤트를 생성하지 마세요.

뒤로 버튼

사용자가 뒤로 버튼을 누르면 VHAL은 다음 int32Values와 함께 HW_KEY_INPUT 속성을 사용하여 Android에 이벤트를 전송해야 합니다.

  1. ACTION_DOWN
  2. KEYCODE_BACK
  3. 타겟 디스플레이

사용자가 로터리 컨트롤러에서 손을 떼면 VHAL은 ACTION_UP과 같은 속성과 키 코드를 사용해야 합니다.

가운데 버튼을 누르고 있는 동안에는 repeat 이벤트를 생성하면 안 됩니다.

홈 버튼

뒤로 버튼처럼 홈 버튼을 처리하되 KEYCODE_BACK 대신 KEYCODE_HOME을 사용합니다.

기타 버튼

로터리 컨트롤러에 추가 버튼이 포함되어 있는 경우 VHAL은 OEM이 원하는 대로 처리할 수 있습니다. Android의 관점에서는 로터리의 일부로 간주되지 않기 때문입니다. 일반적으로 뒤로 버튼과 홈 버튼처럼 처리되지만 다른 키 코드를 사용합니다. 예를 들면 KEYCODE_CALL 또는 KEYCODE_MUSIC이 있습니다.

빌드 구성

로터리 탐색은 RotaryService라는 접근성 서비스에서 제공합니다. 이 서비스를 기기의 시스템 이미지에 포함하려면 다음 줄을 makefile에 추가하세요.

PRODUCT_PACKAGES += CarRotaryController

디버그 빌드에 다음 패키지를 포함하는 것도 좋습니다.

로터리 서비스는 기기가 부팅될 때와 사용자가 전환될 때 자동으로 사용 설정됩니다. 그러면 사용자가 설정 중에 로터리 컨트롤러를 사용할 수 있습니다.

로터리 컨트롤러가 있는 자동차와 없는 자동차에 같은 빌드를 사용한다면 위와 같이 CarRotaryController를 추가하여 필요한 코드가 빌드에 포함되도록 합니다. 로터리 컨트롤러가 없는 자동차에서 로터리 서비스가 사용 설정되지 않도록 하려면 packages/services/Car/servicerotaryService 문자열 리소스를 빈 문자열로 오버레이하는 고정 RRO를 만듭니다. 로터리 기기와 비 로터리 기기에 같은 빌드를 사용하지만 제품 구성은 별도로 있습니다. 비 로터리 기기에만 오버레이가 포함됩니다.

맞춤설정

OEM은 다음 위치에서 리소스 오버레이를 통해 포커스 찾기 로직, 포커스 하이라이트, 일부 추가 항목을 맞춤설정할 수 있습니다.

  • car-ui-library는 packages/apps/Car/libs/car-ui-lib에 있습니다.
  • RotaryServicepackages/apps/Car/RotaryController에 있습니다.
  • Coreframeworks/base/core에 있습니다.

조금씩 이동 기록

OEM은 두 유형의 조금씩 이동 기록을 각각 사용 설정할지 여부, 그리고 사용 설정된 경우 캐시 크기와 만료 정책을 구성할 수 있습니다. 이 작업은 모두 다양한 car-ui-library 리소스를 재정의하여 실행됩니다.

포커스 기록 캐시

(Android 11 QPR3, Android 11 Car, Android 12)
FocusArea별 캐시는 FocusArea 내에서 가장 최근에 포커스가 맞춰진 뷰를 저장하므로 FocusArea로 다시 조금씩 이동할 때 포커스를 맞출 수 있습니다. 이 캐시는 다음 car-ui-library 리소스를 오버레이하여 구성할 수 있습니다.

  • car_ui_focus_history_cache_type:
    1. 캐시가 사용 중지되었습니다.
    2. 캐시가 일정 시간이 지나면 만료됩니다(아래 참고).
    3. 캐시가 만료되지 않습니다.
  • car_ui_focus_history_expiration_period_ms: 캐시 유형이 2로 설정된 경우 캐시가 만료되기까지의 시간(밀리초 단위)입니다(위 참고).

FocusArea 기록 캐시

(Android 11 QPR3, Android 11 Car, Android 12)
이 캐시는 조금씩 이동 기록을 저장하므로 반대 방향으로 조금씩 이동하면 같은 FocusArea에 포커스를 반환할 수 있습니다. 이 캐시는 다음 car-ui-library 리소스를 오버레이하여 구성할 수 있습니다.

  • car_ui_focus_area_history_cache_type:
    1. 캐시가 사용 중지되었습니다.
    2. 캐시가 일정 시간이 지나면 만료됩니다(아래 참고).
    3. 캐시가 만료되지 않습니다.
  • car_ui_focus_area_history_expiration_period_ms: 캐시 유형이 2로 설정된 경우 캐시가 만료되기까지의 시간(밀리초 단위)입니다(위 참고).
  • car_ui_clear_focus_area_history_when_rotating: 사용자가 컨트롤러를 회전할 때 캐시를 무효화할지 여부입니다.

회전

(Android 11 QPR3, Android 11 Car, Android 12)
OEM은 RotaryService에서 두 정수 리소스를 재정의하여 회전 가속(예: 마우스 가속)이 있는지를 지정할 수 있습니다.

  • rotation_acceleration_3x_ms: Google이 회전 디텐트와 관련해 컨트롤러 회전을 가속화해야 하는지 판단하는 데 사용되는 시간 간격(밀리초)입니다. 이 회전 디텐트와 이전 회전 디텐트 사이의 간격이 이 값보다 작으면 3디텐트 회전으로 간주됩니다. 3배 가속을 사용 중지하려면 2147483647로 설정합니다.
  • rotation_acceleration_2x_ms: rotation_acceleration_3x_ms와 유사하며 2배 가속에 사용됩니다. 2배 가속을 사용 중지하려면 이 값을 2147483647로 설정합니다.

가속은 VHAL에 필요한 대로 각 회전 디텐트에 개별 타임스탬프가 있을 때 가장 잘 작동합니다. 개별 타임스탬프를 사용할 수 없다면 RotaryService는 회전 디텐트의 간격이 균일하다고 간주합니다.

/**
     * Property to feed H/W rotary events to android
     *
     * int32Values[0] : RotaryInputType identifying which rotary knob rotated
     * int32Values[1] : number of detents (clicks), positive for clockwise,
     *                  negative for counterclockwise
     * int32Values[2] : target display defined in VehicleDisplay. Events not
     *                  tied to specific display must be sent to
     *                  VehicleDisplay#MAIN.
     * int32values[3 .. 3 + abs(number of detents) - 2]:
     *                  nanosecond deltas between pairs of consecutive detents,
     *                  if the number of detents is > 1 or < -1
     *
     * VehiclePropValue.timestamp: when the rotation occurred. If the number of
     *                             detents is > 1 or < -1, this is when the
     *                             first detent of rotation occurred.
     *
     * @change_mode VehiclePropertyChangeMode:ON_CHANGE
     * @data_enum RotaryInputType
     * @access VehiclePropertyAccess:READ
     */
    HW_ROTARY_INPUT = (
        0x0A20
        | VehiclePropertyGroup:SYSTEM
        | VehiclePropertyType:INT32_VEC
        | VehicleArea:GLOBAL),

포커스 하이라이트

OEM은 Android 프레임워크의 기본 포커스 하이라이트와 car-ui-library의 여러 포커스 하이라이트 리소스를 재정의할 수 있습니다.

기본 포커스 하이라이트

Android 프레임워크는 selectableItemBackground 속성을 통해 기본 포커스 하이라이트를 제공합니다. Theme.DeviceDefault에서 이 속성은 Coreitem_background.xml을 참조합니다. OEM은 item_background.xml을 오버레이하여 기본 포커스 하이라이트 드로어블을 변경할 수 있습니다.

이 드로어블은 일반적으로 android:state_focused, android:state_pressed 등 다양한 상태 조합에 따라 배경을 조정하는 StateListDrawable이어야 합니다. 사용자가 로터리 컨트롤러를 사용하여 뷰에 포커스를 맞추면 android:state_focusedtrue이지만 android:state_pressedfalse입니다. 그런 다음 사용자가 로터리 컨트롤러의 가운데 버튼을 누르면 사용자가 버튼을 누르고 있는 동안 android:state_focusedandroid:state_pressed는 모두 true가 됩니다. 사용자가 버튼에서 손을 떼면 android:state_focusedtrue로 유지됩니다.

car-ui-library는 Theme.DeviceDefault에서 파생된 테마를 사용합니다. 따라서 이 오버레이는 이 라이브러리를 사용하는 앱과 Theme.DeviceDefault에서 파생된 테마를 사용하는 앱에 영향을 미칩니다. Theme.Material과 같이 관련 없는 테마를 사용하는 앱에는 영향을 미치지 않습니다.

car-ui-library의 포커스 하이라이트 리소스

OEM은 여러 car-ui-library 리소스를 재정의하여 직사각형이 아닌(예: 원형 또는 알약 모양) 포커스 하이라이트가 있는 뷰와 Theme.DeviceDefault에서 파생되지 않은 테마를 사용하는 앱에서 포커스 하이라이트가 표시되는 방식을 제어할 수 있습니다. 포커스 하이라이트가 기본 포커스 하이라이트 드로어블과 일치하도록 이러한 리소스를 오버레이해야 합니다.

(Android 11 QPR3, Android 11 Car, Android 12)
다음 리소스는 뷰에 포커스가 있지만 뷰를 누르지 않은 경우를 나타내는 데 사용됩니다.

  • car_ui_rotary_focus_fill_color: 채우기 색상
  • car_ui_rotary_focus_stroke_color: 윤곽선 색상
  • car_ui_rotary_focus_stroke_width: 윤곽선 두께

(Android 11 QPR3, Android 11 Car, Android 12)
다음 리소스는 뷰에 포커스가 있고 뷰를 누른 경우를 나타내는 데 사용됩니다.

  • car_ui_rotary_focus_pressed_fill_color: 채우기 색상
  • car_ui_rotary_focus_pressed_stroke_color: 윤곽선 색상
  • car_ui_rotary_focus_pressed_stroke_width: 윤곽선 두께

다음 예와 같이 버튼은 배경 색상을 단색으로 하여 사용자의 주의를 끌기도 합니다. 이로 인해 포커스 하이라이트가 잘 표시되지 않을 수 있습니다.

배경이 단색인 버튼
그림 2. 배경이 단색인 버튼

이 경우 개발자는 보조 색상을 사용하여 맞춤 포커스 하이라이트를 지정할 수 있습니다.
  • (Android 11 QPR3, Android 11 Car, Android 12)
    car_ui_rotary_focus_fill_secondary_color
    car_ui_rotary_focus_stroke_secondary_color
  • (Android 12)
    car_ui_rotary_focus_pressed_fill_secondary_color
    car_ui_rotary_focus_pressed_stroke_secondary_color

모든 색상은 투명할 수 있고 예를 들어 채우기만 원하거나 윤곽선만 원할 경우 두 치수 모두 0이 될 수 있습니다.

FocusArea 하이라이트

(Android 11 QPR3, Android 11 Car, Android 12)
FocusArea는 하위 요소 중 하나에 포커스가 있을 때 두 가지 하이라이트 유형을 그릴 수 있습니다. 원한다면 둘 다 함께 사용할 수 있습니다. 이 기능은 AOSP에서 기본적으로 사용 중지되어 있지만 car-ui-library 리소스를 재정의하여 사용 설정할 수 있습니다.

  • car_ui_enable_focus_area_foreground_highlight: FocusArea와 그 하위 요소 위에 하이라이트를 그립니다. AOSP에서 이 드로어블은 FocusArea 주변의 윤곽선입니다. OEM은 car_ui_focus_area_foreground_highlight 드로어블을 재정의할 수 있습니다.
  • car_ui_enable_focus_area_background_highlight: FocusArea 위 그러나 그 하위 요소 뒤에 하이라이트를 그립니다. AOSP에서 이 드로어블은 단색 채우기입니다. OEM은 car_ui_focus_area_background_highlight 드로어블을 재정의할 수 있습니다.

입력 방식 편집기(IME)

입력 방식 편집기(IME)는 입력 방법입니다. 터치 키보드를 예로 들 수 있습니다.

(Android 11 QPR3, Android 11 Car, Android 12)
OEM은 RotaryServicedefault_touch_input_method 문자열 리소스를 오버레이하여 터치 기반 IME의 ComponentName을 지정해야 합니다. 예를 들어 OEM이 Android Automotive와 함께 제공된 IME를 사용하는 경우 com.google.android.apps.automotive.inputmethod/.InputMethodService를 지정해야 합니다.

(Android 11 QPR3, Android 11 Car, Android 12)
OEM이 로터리 전용으로 IME를 만든 경우 rotary_input_method 리소스에서 ComponentName을 지정해야 합니다. 이 리소스가 오버레이되면 사용자가 로터리 컨트롤러의 조금씩 이동, 회전, 가운데 버튼을 통해 헤드 단위와 상호작용할 때마다 지정된 IME가 사용됩니다. 사용자가 화면을 터치하면 이전 IME가 사용됩니다. 뒤로 버튼(및 로터리 컨트롤러의 기타 버튼)은 IME 선택에 영향을 미치지 않습니다. 이 리소스가 오버레이되지 않으면 IME 전환은 발생하지 않습니다. Carboard는 로터리를 지원하지 않으므로 OEM이 로터리 IME를 제공하지 않았다면 사용자는 로터리 컨트롤러를 통해 텍스트를 입력할 수 없습니다.

RotaryIME는 데모 로터리 IME입니다. 기본적이긴 하지만 위에서 설명한 자동 IME 전환을 시도해 보기에 충분합니다. RotaryIME의 소스 코드는 packages/apps/Car/tests/RotaryIME/에서 확인할 수 있습니다.

화면 밖으로 조금씩 이동

기본적으로 사용자가 화면 가장자리에서 조금씩 이동하려고 하면 아무 일도 발생하지 않습니다. OEM은 다음 조합을 지정하여 4방향 각각에 발생해야 하는 작업을 구성할 수 있습니다.

  1. AccessibilityService에서 정의한 전역 작업. 예: GLOBAL_ACTION_BACK
  2. 키 코드(예: KEYCODE_BACK)
  3. URL로 표시되는 활동을 실행할 인텐트

(Android 11 QPR3, Android 11 Car, Android 12)
RotaryService에서 다음 배열 리소스를 오버레이하여 지정됩니다.

  • off_screen_nudge_global_actions: 사용자가 화면 가장자리에서 위, 아래, 왼쪽, 오른쪽으로 조금씩 이동할 때 실행할 전역 작업의 배열입니다. 이 배열의 관련 요소가 -1이면 전역 작업이 실행되지 않습니다.
  • off_screen_nudge_key_codes: 사용자가 화면 가장자리에서 위, 아래, 왼쪽, 오른쪽으로 조금씩 이동할 때 삽입할 클릭 이벤트의 키 코드 배열입니다. 이 배열의 관련 요소가 0(KEYCODE_UNKNOWN)이면 이벤트가 삽입되지 않습니다.
  • off_screen_nudge_intents: 사용자가 화면 가장자리에서 위, 아래, 왼쪽, 오른쪽으로 조금씩 이동할 때 활동을 실행할 인텐트의 배열입니다. 이 배열의 관련 요소가 비어 있으면 활동이 실행되지 않습니다.

기타 구성

다음 RotaryService 리소스를 오버레이해야 합니다.

  • (Android 11 QPR3, Android 11 Car, Android 12)
    config_showHeadsUpNotificationOnBottom: 상단이 아닌 하단에 사전 경고 알림을 표시해야 하는지 나타내는 부울 값입니다. 이 값은 frameworks/base/packages/CarSystemUI/res/values/config.xmlconfig_showHeadsUpNotificationOnBottom 부울 리소스와 같은 값이어야 합니다.
  • (Android 11 QPR3, Android 11 Car, Android 12)
    notification_headsup_card_margin_horizontal: 사전 경고 알림 창의 왼쪽과 오른쪽 여백입니다. packages/apps/Car/Notification/res/values/dimens.xmlnotification_headsup_card_margin_horizontal 치수 리소스와 같은 값이어야 합니다.
  • (Android 12)
    excluded_application_overlay_window_titles: 오버레이 창으로 간주하면 안 되는 창 제목의 배열입니다. 여기에는 TaskViewsTaskDisplayAreas를 나타내는 애플리케이션 창 제목이 포함되어야 합니다. 기본적으로 이 목록에는 '지도'만 포함됩니다.

다음 RotaryService 리소스를 오버레이할 수 있습니다.

  • (Android 11 QPR3, Android 11 Car, Android 12)
    long_press_ms: 길게 누르기를 트리거하기 위해 가운데 버튼을 누르고 있어야 하는 시간(밀리초)을 나타내는 정숫값입니다. 0은 시스템 기본 길게 누르기 제한 시간을 사용해야 함을 나타냅니다. 기본값입니다.