애플리케이션 개발

음성 상호작용 애플리케이션(VIA)을 구현하려면 다음 단계를 완료하세요.

  1. VIA 스켈레톤을 생성합니다.
  2. (선택사항) 설정/로그인 흐름을 구현합니다.
  3. (선택사항) 설정 화면을 구현합니다.
  4. 매니페스트 파일에서 필수 권한을 선언합니다.
  5. 음성 플레이트 UI를 구현합니다.
  6. 음성 인식을 구현합니다(RecognitionService API 구현을 포함해야 함).
  7. 발화를 구현합니다(선택사항으로, TextToSpeech API를 구현할 수 있음).
  8. 명령어 처리를 구현합니다. 명령어 처리에서 이 콘텐츠를 참고하세요.

다음 섹션에서는 위에서 언급한 각 단계를 완료하는 방법을 설명합니다.

VIA 스켈레톤 생성

매니페스트

다음 코드가 매니페스트에 포함된 경우 애플리케이션은 음성 상호작용이 있는 애플리케이션으로 감지됩니다.

AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myvoicecontrol">
    ...

  <application ... >
    <service android:name=".MyInteractionService"
        android:label="@string/app_name"
        android:permission="android.permission.BIND_VOICE_INTERACTION"
        android:process=":interactor">
      <meta-data
          android:name="android.voice_interaction"
          android:resource="@xml/interaction_service" />
      <intent-filter>
        <action android:name=
          "android.service.voice.VoiceInteractionService" />
      </intent-filter>
    </service>
  </application>
</manifest>

이 예에서

  • VIA는 VoiceInteractionService.SERVICE_INTERFACE ("android.service.voice.VoiceInteractionService") 작업의 인텐트 필터와 함께 VoiceInteractionService를 확장하는 서비스를 노출해야 합니다.
  • 이 서비스는 BIND_VOICE_INTERACTION 시스템 서명 권한을 보유해야 합니다.
  • 이 서비스에는 다음을 포함하는 android.voice_interaction 메타데이터 파일이 포함되어야 합니다.

    res/xml/interaction_service.xml

    <voice-interaction-service
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:sessionService=
          "com.example.MyInteractionSessionService"
        android:recognitionService=
          "com.example.MyRecognitionService"
        android:settingsActivity=
          "com.example.MySettingsActivity"
        android:supportsAssist="true"
        android:supportsLaunchVoiceAssistFromKeyguard="true"
        android:supportsLocalInteraction="true" />
    

각 필드에 관한 자세한 내용은 R.styleable#VoiceInteractionService를 참고하세요. 모든 VIA는 음성 인식기 서비스이기도 하므로 매니페스트에 다음을 포함해야 합니다.

AndroidManifest.xml

<manifest ...>
  <uses-permission android:name="android.permission.RECORD_AUDIO"/>
  <application ...>
    ...
    <service android:name=".RecognitionService" ...>
      <intent-filter>
        <action android:name="android.speech.RecognitionService" />
        <category android:name="android.intent.category.DEFAULT" />
      </intent-filter>
      <meta-data
        android:name="android.speech"
        android:resource="@xml/recognition_service" />
    </service>
  </application>
</manifest>

또한 음성 인식 서비스에는 다음과 같은 메타데이터도 필요합니다.

res/xml/recognition_service.xml

<recognition-service
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:settingsActivity="com.example.MyRecognizerSettingsActivity" />

VoiceInteractionService, VoiceInteractionSessionServiceVoiceInteractionSession

다음 다이어그램은 이러한 각 항목의 수명 주기를 보여줍니다.

수명 주기

그림 1. 수명 주기

앞에서 언급했듯이 VoiceInteractionService는 VIA의 진입점입니다. 이 서비스는 주로 다음을 담당합니다.

  • 이 VIA가 활성 상태인 동안은 계속 실행되어야 하는 프로세스를 초기화합니다. 예를 들어 핫워드 감지를 초기화합니다.
  • 지원되는 음성 작업을 보고합니다(음성 어시스턴트 탭하여 읽기 참고).
  • 잠금 화면에서 음성 상호작용 세션을 시작합니다(키가드).

가장 간단한 형태의 VoiceInteractionService 구현은 다음과 유사합니다.

public class MyVoiceInteractionService extends VoiceInteractionService {
    private static final List<String> SUPPORTED_VOICE_ACTIONS =
        Arrays.asList(
            CarVoiceInteractionSession.VOICE_ACTION_READ_NOTIFICATION,
            CarVoiceInteractionSession.VOICE_ACTION_REPLY_NOTIFICATION,
            CarVoiceInteractionSession.VOICE_ACTION_HANDLE_EXCEPTION
    );

    @Override
    public void onReady() {
        super.onReady();
        // TODO: Setup hotword detector
    }

    @NonNull
    @Override
    public Set<String> onGetSupportedVoiceActions(
            @NonNull Set<String> voiceActions) {
        Set<String> result = new HashSet<>(voiceActions);
        result.retainAll(SUPPORTED_VOICE_ACTIONS);
        return result;
    }
    ...
}

음성 어시스턴트 탭하여 읽기를 처리하려면 VoiceInteractionService#onGetSupportedVoiceActions() 구현이 필요합니다. VoiceInteractionSessionService는 시스템에서 VoiceInteractionSession을 만들고 이 세션과 상호작용하는 데 사용됩니다. 이 서비스는 요청 시 새 세션을 시작하는 것만 하면 됩니다.

public class MyVoiceInteractionSessionService extends VoiceInteractionSessionService {
    @Override
    public VoiceInteractionSession onNewSession(Bundle args) {
        return new MyVoiceInteractionSession(this);
    }
}

마지막으로, VoiceInteractionSession에서는 대부분의 작업이 실행됩니다. 단일 세션 인스턴스를 재사용하여 여러 사용자 상호작용을 완료할 수 있습니다. AAOS에는 CarVoiceInteractionSession 도우미가 있으며, 이 도우미는 자동차의 고유한 기능 일부를 구현하도록 지원합니다.

public class MyVoiceInteractionSession extends CarVoiceInteractionSession {

    public InteractionSession(Context context) {
        super(context);
    }

    @Override
    protected void onShow(String action, Bundle args, int showFlags) {
        closeSystemDialogs();
        // TODO: Unhide UI and update UI state
        // TODO: Start processing audio input
    }
    ...
}

VoiceInteractionSession에는 다음 섹션에서 설명할 다양한 콜백 메서드 세트가 있습니다. 전체 목록은 VoiceInterationSession에 관한 문서를 참고하세요.

설정/로그인 흐름 구현

다음 경우에 설정 및 로그인이 발생할 수 있습니다.

  • 기기 온보딩 중(설정 마법사)
  • 음성 상호작용 서비스 전환 중(설정)
  • 애플리케이션이 선택되어 처음 실행될 때

권장 사용자 환경 및 시각적 안내에 관한 자세한 내용은 미리 로드된 어시스턴트: UX 안내를 참고하세요.

음성 서비스 전환 중 설정

사용자가 적절하게 구성되지 않은 VIA를 선택할 가능성은 항상 있습니다. 이는 다음과 같은 이유로 발생할 수 있습니다.

  • 사용자가 설정 마법사를 완전히 건너뛰었거나 음성 상호작용 구성 단계를 건너뛰었습니다.
  • 사용자가 기기 온보딩 중에 구성된 VIA와 다른 VIA를 선택했습니다.

어떠한 경우든 VoiceInteractionService에는 사용자가 설정을 완료하도록 유도하는 몇 가지 방법이 있습니다.

  • 알림을 표시합니다.
  • 사용자가 사용하려고 할 때 자동 음성 응답을 실행합니다.

참고: 명시적인 사용자 요청이 없다면 VIA 설정 흐름을 제공하지 않는 것이 좋습니다. 즉, VIA가 기기 부팅 중에 또는 사용자 전환이나 잠금 해제의 결과로 HU에 콘텐츠를 자동으로 표시하는 일이 없도록 해야 합니다.

1. 알림 표시

알림 표시는 설정의 필요성을 알려주고 어시스턴트 설정 흐름으로 이동할 수 있는 어포던스를 사용자에게 제공하는 비간섭적 방법입니다.

알림 표시

그림 2. 알림 표시

이 흐름이 작동하는 방식은 다음과 같습니다.

알림 표시 흐름

그림 3. 알림 표시 흐름

Note: UX 제한 규칙(운전자 주의 분산 행동 가이드라인 참고)에 따라 운전 중에는 설정 흐름이 허용되지 않을 수 있습니다. 사용자가 알림을 클릭했을 때 설정이 허용되지 않는 경우 애플리케이션은 즉시 차단될 UI를 표시하려고 시도하지 않고 사용자에게 상황을 설명하는 발화를 사용해야 합니다.

2. 음성 응답

이는 구현하기에 가장 간단한 흐름으로, VoiceInteractionSession#onShow() 콜백에서 발화를 시작하고 사용자에게 실행해야 할 작업을 설명한 후 (UX 제한 상태에서 설정이 허용되는 경우) 설정 흐름을 시작할 것인지 묻습니다. 이때 설정이 불가능하다면 이 상황도 설명합니다.

처음 사용 시 설정

사용자가 적절하게 구성되지 않은 VIA를 트리거할 가능성은 항상 있습니다. 그런 경우에는 다음과 같이 합니다.

  1. 사용자에게 이 상황에 관해 구두로 알립니다(예: "올바르게 작동하려면 몇 가지 단계를 완료하셔야 합니다…").
  2. UX 제한 엔진이 허용하는 경우(UX_RESTRICTIONS_NO_SETUP 참고) 사용자에게 설정 프로세스를 시작할 것인지 물어본 후 VIA의 설정 화면을 엽니다.
  3. 그 외의 경우(예: 사용자가 운전 중인 경우)에는 안전할 때 사용자가 옵션을 클릭하도록 알림을 남깁니다.

음성 상호작용 설정 화면 빌드

설정 및 로그인 화면은 일반 활동으로 개발해야 합니다. 미리 로드된 어시스턴트: UX 안내에서 UI 개발에 관한 UX 및 시각적 가이드라인을 참고하세요.

일반 가이드라인:

  • VIA에서는 사용자가 언제든지 설정을 중단하고 재개할 수 있도록 해야 합니다.
  • UX_RESTRICTIONS_NO_SETUP 제한이 적용되는 경우 설정을 허용해서는 안 됩니다. 자세한 내용은 운전자 주의 분산 행동 가이드라인을 참고하세요.
  • 설정 화면은 각 차량의 디자인 시스템과 일치해야 합니다. 일반 화면 레이아웃, 아이콘, 색상 및 기타 측면은 나머지 UI와 일관되어야 합니다. 자세한 내용은 맞춤설정을 참고하세요.

설정 화면 구현

설정 통합

그림 4. 설정 통합

설정 화면은 일반 Android 활동입니다. 설정 화면이 구현된 경우 진입점은 VIA 매니페스트의 일부로 res/xml/interaction_service.xml에 선언되어야 합니다(매니페스트 참고). 설정 섹션은 설정 및 로그인을 계속하거나(사용자가 완료하지 않은 경우) 필요한 경우 로그아웃 또는 사용자 전환 옵션을 제공하기에 좋은 위치입니다. 위에서 설명한 설정 화면과 유사하게 이러한 화면은 다음과 같아야 합니다.

  • 화면 스택의 이전 화면으로(예: 자동차 설정으로) 돌아가는 옵션을 제공해야 합니다.
  • 운전 중에는 설정이 허용되지 않아야 합니다. 자세한 내용은 운전자 주의 분산 행동 가이드라인을 참고하세요.
  • 각 차량의 디자인 시스템과 일치해야 합니다. 자세한 내용은 맞춤설정을 참고하세요.

매니페스트 파일에서 필수 권한 선언

VIA에 필요한 권한은 다음과 같은 세 가지 카테고리로 나눌 수 있습니다.

  • 시스템 서명 권한: 사전 설치된 시스템 서명 APK에만 부여되는 권한입니다. 사용자는 이 권한을 부여할 수 없으며 OEM만 시스템 이미지를 빌드할 때 이 권한을 부여할 수 있습니다. 서명 권한을 얻는 방법에 관한 자세한 내용은 시스템 독점 권한 부여를 참고하세요.
  • 위험한 권한: 사용자가 PermissionsController 대화상자를 사용해 부여해야 하는 권한입니다. OEM은 이러한 권한 중 일부를 기본 VoiceInteractionService에 미리 부여할 수 있습니다. 그러나 이 기본값이 기기마다 변경될 수 있다는 점을 고려한다면 애플리케이션은 필요할 때 이러한 권한을 요청할 수 있어야 합니다.
  • 기타 권한: 사용자 개입이 필요하지 않은 기타 모든 권한입니다. 이러한 권한은 시스템에 의해 자동으로 부여됩니다.

위에서 설명한 내용을 기반으로 다음 섹션에서는 위험한 권한 요청만 중점적으로 다룹니다. 사용자가 로그인 또는 설정 화면에 있는 동안에만 권한을 요청해야 합니다.

애플리케이션에 작동하는 데 필요한 권한이 없는 경우 권장되는 흐름은 음성 발화를 사용하여 사용자에게 상황을 설명하고 알림을 사용하여 사용자가 VIA 설정 화면으로 다시 이동하는 데 사용할 수 있는 어포던스를 제공하는 것입니다. 자세한 내용은 1. 알림 표시를 참고하세요.

설정 화면의 일부로 권한 요청

일반 ActivityCompat#requestPermission() 메서드(또는 이와 동등한 메서드)를 사용하여 위험한 권한을 요청합니다. 권한을 요청하는 방법에 관한 자세한 내용은 앱 권한 요청을 참고하세요.

권한 요청

그림 5. 권한 요청

알림 리스너 권한

TTR 흐름을 구현하려면 VIA를 알림 리스너로 지정해야 합니다. 이는 권한 자체를 부여하는 것이 아니라 시스템에서 등록된 리스너에 알림을 보낼 수 있도록 구성하는 것입니다. VIA에 이 정보에 관한 액세스 권한이 부여되었는지 알아보기 위해 애플리케이션은 다음을 할 수 있습니다.

이 액세스 권한이 미리 부여되지 않았다면 VIA는 발화 및 알림 조합을 사용하여 사용자를 자동차 설정의 알림 액세스 섹션으로 안내해야 합니다. 다음 코드를 사용하면 설정 애플리케이션의 해당 섹션을 열 수 있습니다.

private void requestNotificationListenerAccess() {
    Intent intent = new Intent(Settings
        .ACTION_NOTIFICATION_LISTENER_SETTINGS);
    intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
    startActivity(intent);
}

음성 플레이트 UI 구현

VoiceInteractionSessiononShow() 콜백을 수신할 때 음성 플레이트 UI를 표시할 수 있습니다. 음성 플레이트 구현에 관한 UX 및 시각적 가이드라인은 미리 로드된 어시스턴트: UX 안내를 참고하세요.

음성 플레이트 표시

그림 6. 음성 플레이트 표시

이 UI를 구현하는 방법에는 두 가지 옵션이 있습니다.

  • VoiceInteractionSession#onCreateContentView() 재정의
  • VoiceInteractionSession#startAssistantActivity()를 사용하여 활동 시작

onCreateContentView() 사용

음성 플레이트를 표시하는 기본 방법입니다. VoiceInteractionSession 기본 클래스는 음성 세션이 활성화되어 있는 동안 창을 생성하고 수명 주기를 관리합니다. 애플리케이션은 세션이 생성되는 즉시 VoiceInteractionSession#onCreateContentView()를 재정의하고 이 창에 연결될 뷰를 반환해야 합니다. 처음에는 이 뷰가 표시되지 않습니다. 음성 상호작용이 시작되면 이 뷰가 VoiceInteractionSession#onShow()에 표시되며 VoiceInteractionSession#onHide()에는 다시 표시되지 않습니다.

public class MyVoiceInteractionSession extends CarVoiceInteractionSession {
    private View mVoicePlate;
    …

    @Override
    public View onCreateContentView() {
        mVoicePlate = inflater.inflate(R.layout.voice_plate, null);
        …
   }

    @Override
    protected void onShow(String action, Bundle args, int showFlags) {
        // TODO: Update UI state to "listening"
        mVoicePlate.setVisibility(View.VISIBLE);
    }

    @Override
    public void onHide() {
        mVoicePlate.setVisibility(View.GONE);
    }
    …
}

이 메서드 사용 시 VoiceInteractionSession#onComputeInsets()를 조정하여 UI의 가려진 영역을 처리할 수 있습니다.

startAssistantActivity() 사용

이 경우 VoiceInteractionSession은 음성 플레이트 UI 처리를 일반 활동에 위임합니다. 이 옵션을 사용할 때 VoiceInteractionSession 구현은 onPrepareShow() 콜백에서 기본 콘텐츠 창 생성(onCreateContentView() 사용 참고)을 중지해야 합니다. VoiceInteractionSession#onShow()에서 세션은 VoiceInteractionSession#startAssistantActivity()를 사용하여 음성 플레이트 활동을 시작합니다. 이 메서드는 적절한 창 설정 및 활동 플래그를 통해 UI를 시작합니다.

public class MyVoiceInteractionSession extends CarVoiceInteractionSession {
    …

    @Override
    public void onPrepareShow(Bundle args, int showFlags) {
        super.onPrepareShow(args, showFlags);
        setUiEnabled(false);
    }

    @Override
    protected void onShow(String action, Bundle args, int showFlags) {
        closeSystemDialogs();
        Intent intent = new Intent(getContext(), VoicePlateActivity.class);
        intent.putExtra(VoicePlateActivity.EXTRA_ACTION, action);
        intent.putExtra(VoicePlateActivity.EXTRA_ARGS, args);
        startAssistantActivity(intent);
    }

    …
}

이 활동과 VoiceInteractionSession 간의 통신을 유지하려면 내부 인텐트 또는 서비스 결합 세트가 필요할 수 있습니다. 예를 들어 VoiceInteractionSession#onHide()가 호출될 때 세션은 이 요청을 활동에 전달할 수 있어야 합니다.

중요: Automotive에서는 특별히 주석이 지정된 활동 또는 UXR '허용 목록'에 나열된 활동만 운전 중에 표시할 수 있습니다. 이는 VoiceInteractionSession#startAssistantActivity()로 시작된 활동에도 적용됩니다. <meta-data android:name="distractionOptimized" android:value="true"/>로 활동에 주석을 지정하거나 /packages/services/Car/service/res/values/config.xml 파일의 systemActivityWhitelist 키에 이 활동을 포함해야 합니다. 자세한 내용은 운전자 주의 분산 행동 가이드라인을 참고하세요.

음성 인식 구현

이 섹션에서는 핫워드 감지 및 인식을 통해 음성 인식을 구현하는 방법을 알아봅니다. 핫워드는 음성으로 새 쿼리 또는 작업을 시작하는 데 사용되는 트리거 단어입니다. 핫워드의 예로는 "OK Google" 또는 "Hey Google"이 있습니다.

DSP 핫워드 감지

Android는 AlwaysOnHotwordDetector 클래스를 통해 DSP 수준에서 상시 사용 설정 핫워드 감지기에 관한 액세스를 제공합니다. 이를 통해 저사양 CPU로 핫워드 감지를 편리하게 구현할 수 있습니다. 이 기능의 사용은 다음과 같이 두 부분으로 나뉩니다.

VoiceInteractionService 구현은 VoiceInteractionService#createAlwaysOnHotwordDetector()를 사용하여 핫워드 감지기를 생성하고 감지에 사용할 주요 구문 및 언어를 전달할 수 있습니다. 결과적으로 애플리케이션은 다음과 같은 값 중 하나와 함께 onAvailabilityChanged() 콜백을 수신합니다.

  • STATE_HARDWARE_UNAVAILABLE. 기기에서 DSP 기능을 사용할 수 없습니다. 이 경우 소프트웨어 핫워드 감지가 사용됩니다.
  • STATE_HARDWARE_UNSUPPORTED. 일반적으로 DSP 지원을 사용할 수 없지만, DSP는 특정 주요 구문과 언어 조합을 지원하지 않습니다. 애플리케이션은 소프트웨어 핫워드 감지를 사용하도록 선택할 수 있습니다.
  • STATE_HARDWARE_ENROLLED. 핫워드 감지가 준비되었으며 startRecognition() 메서드를 호출하여 시작할 수 있습니다.
  • STATE_HARDWARE_UNENROLLED. 요청된 주요 구문에 관한 알림음 모델을 사용할 수 없지만 등록할 수는 있습니다.

IVoiceInteractionManagerService#updateKeyphraseSoundModel()을 사용하여 핫워드 감지 알림음 모델을 등록할 수 있습니다. 특정 시점에 여러 모델을 시스템에 등록할 수 있지만 하나의 모델만 AlwaysOnHotwordDetector와 연결됩니다. 일부 기기에서는 DSP 핫워드 감지를 사용하지 못할 수도 있습니다. VIA 개발자는 getDspModuleProperties() 메서드를 사용하여 하드웨어 기능을 확인해야 합니다. 알림음 모델을 등록하는 방법을 보여주는 샘플 코드는 VoiceEnrollment/src/com/android/test/voiceenrollment/EnrollmentUtil.java를 참고하세요. 동시 핫워드 인식에 관해서는 동시 캡처를 참고하세요.

소프트웨어 핫워드 감지

위에서 언급한 것처럼 일부 기기에서는 DSP 핫워드 감지를 사용하지 못할 수도 있습니다(예: Android Emulator는 DSP 에뮬레이션을 제공하지 않음). 이 경우 소프트웨어 음성 인식이 유일한 대안입니다. 마이크에 액세스해야 할 수 있는 다른 애플리케이션에 방해가 되지 않도록 VIA는 다음과 같이 오디오 입력에 액세스해야 합니다.

이러한 상수는 둘 다 @hide이며 번들 애플리케이션에서만 사용할 수 있습니다.

오디오 입력 및 음성 인식 관리

오디오 입력은 MediaRecorder API를 사용하여 구현됩니다. 이 API를 사용하는 방법에 관한 자세한 내용은 MediaRecorder 개요를 참고하세요. 음성 상호작용 서비스도 RecognitionService 구현이어야 합니다. 음성 인식이 필요한 시스템의 애플리케이션은 SpeechRecognizer API를 사용하여 이 기능에 액세스합니다. 음성 인식을 실행하고 마이크에 액세스할 수 있으려면 VIA가 android.permission.RECORD_AUDIO 권한을 보유해야 합니다. RecognitionService 구현에 액세스하는 애플리케이션도 이 권한을 보유해야 합니다.

Android 10 이전에는 한 번에 하나의 애플리케이션에만 마이크 액세스 권한이 부여되었습니다(핫워드 감지는 예외, 위 참고). Android 10부터 마이크 액세스를 공유할 수 있습니다. 자세한 내용은 오디오 입력 공유를 참고하세요.

오디오 출력 액세스

VIA가 음성 응답을 제공할 준비가 되었을 때 다음 가이드라인을 따르는 것이 중요합니다.