Google 어시스턴트 및 미디어 앱

Google 어시스턴트를 사용하면 음성 명령으로 Google Home, 휴대전화 등 다양한 기기를 제어할 수 있습니다. 미디어 명령어('Beyoncé의 항목 재생')를 이해하는 기능이 내장되어 있으며 일시중지, 건너뛰기, 빨리 감기, 좋아요 등의 미디어 컨트롤을 지원합니다.

어시스턴트는 미디어 세션을 사용하여 Android 미디어 앱과 통신합니다. 인텐트 또는 서비스를 사용하여 앱을 실행하고 재생을 시작할 수 있습니다. 최상의 결과를 얻으려면 앱에서 이 페이지에 설명된 모든 기능을 구현해야 합니다.

미디어 세션 사용

모든 오디오 및 동영상 앱은 재생이 시작되면 어시스턴트가 전송 컨트롤을 작동할 수 있도록 미디어 세션을 구현해야 합니다.

어시스턴트는 이 섹션에 나열된 작업만 사용하지만 다른 애플리케이션과의 호환성을 보장하려면 모든 준비 및 재생 API를 구현하는 것이 가장 좋습니다. 지원하지 않는 작업의 경우 미디어 세션 콜백이 ERROR_CODE_NOT_SUPPORTED를 사용하여 오류를 반환할 수 있습니다.

앱의 MediaSession 객체에서 다음 플래그를 설정하여 미디어 및 전송 컨트롤을 사용 설정합니다.

Kotlin

session.setFlags(
        MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or
        MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS
)

Java

session.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
    MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);

앱의 미디어 세션은 지원하는 작업을 선언하고 상응하는 미디어 세션 콜백을 구현해야 합니다. setActions()에서 지원되는 작업을 선언합니다.

범용 Android 뮤직 플레이어 샘플 프로젝트는 미디어 세션을 설정하는 방법을 보여주는 좋은 예입니다.

재생 작업

서비스에서 재생을 시작하려면 미디어 세션에 다음과 같은 PLAY 작업 및 콜백이 필요합니다.

작업 콜백
ACTION_PLAY onPlay()
ACTION_PLAY_FROM_SEARCH onPlayFromSearch()
ACTION_PLAY_FROM_URI(*) onPlayFromUri()

또한 세션에서 다음과 같은 PREPARE 작업 및 콜백을 구현해야 합니다.

작업 콜백
ACTION_PREPARE onPrepare()
ACTION_PREPARE_FROM_SEARCH onPrepareFromSearch()
ACTION_PREPARE_FROM_URI(*) onPrepareFromUri()

(*) Google 어시스턴트 URI 기반 작업은 Google에 URI를 제공하는 회사에서만 가능합니다. Google에 미디어 콘텐츠를 설명하는 방법에 관한 자세한 내용은 미디어 작업을 참고하세요.

준비 API를 구현하면 음성 명령 이후의 재생 지연 시간을 줄일 수 있습니다. 재생 지연 시간을 개선하려는 미디어 앱은 추가 시간을 사용하여 콘텐츠 캐싱 및 미디어 재생 준비를 시작할 수 있습니다.

검색어 파싱

사용자가 '[앱 이름]에서 재즈 재생해 줘' 또는 '[노래 제목] 듣기'와 같은 특정 미디어 항목을 검색하면 onPrepareFromSearch() 또는 onPlayFromSearch() 콜백 메서드가 쿼리 매개변수와 추가 번들을 수신합니다.

앱은 다음 단계에 따라 음성 검색어를 파싱하고 재생을 시작해야 합니다.

  1. 음성 검색에서 반환된 추가 번들 및 검색어 문자열을 사용하여 결과를 필터링합니다.
  2. 이 결과에 근거하여 재생 대기열을 빌드합니다.
  3. 결과 중에서 관련성이 가장 높은 미디어 항목을 재생합니다.

onPlayFromSearch() 메서드는 음성 검색에서 더 자세한 정보가 포함된 추가 매개변수를 사용합니다. 이러한 추가 매개변수는 앱에서 재생할 오디오 콘텐츠를 찾는 데 도움이 됩니다. 검색결과에서 이 데이터를 제공할 수 없는 경우 원시 검색어를 파싱하고 쿼리에 따라 적절한 트랙을 재생하는 로직을 구현할 수 있습니다.

Android Automotive OS와 Android Auto에서는 다음과 같은 추가 매개변수를 지원합니다.

다음 코드 스니펫은 MediaSession.Callback 구현에서 onPlayFromSearch() 메서드를 재정의하여 음성 검색어를 파싱하고 재생을 시작하는 방법을 보여줍니다.

Kotlin

override fun onPlayFromSearch(query: String?, extras: Bundle?) {
    if (query.isNullOrEmpty()) {
        // The user provided generic string e.g. 'Play music'
        // Build appropriate playlist queue
    } else {
        // Build a queue based on songs that match "query" or "extras" param
        val mediaFocus: String? = extras?.getString(MediaStore.EXTRA_MEDIA_FOCUS)
        if (mediaFocus == MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE) {
            isArtistFocus = true
            artist = extras.getString(MediaStore.EXTRA_MEDIA_ARTIST)
        } else if (mediaFocus == MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE) {
            isAlbumFocus = true
            album = extras.getString(MediaStore.EXTRA_MEDIA_ALBUM)
        }

        // Implement additional "extras" param filtering
    }

    // Implement your logic to retrieve the queue
    var result: String? = when {
        isArtistFocus -> artist?.also {
            searchMusicByArtist(it)
        }
        isAlbumFocus -> album?.also {
            searchMusicByAlbum(it)
        }
        else -> null
    }
    result = result ?: run {
        // No focus found, search by query for song title
        query?.also {
            searchMusicBySongTitle(it)
        }
    }

    if (result?.isNotEmpty() == true) {
        // Immediately start playing from the beginning of the search results
        // Implement your logic to start playing music
        playMusic(result)
    } else {
        // Handle no queue found. Stop playing if the app
        // is currently playing a song
    }
}

Java

@Override
public void onPlayFromSearch(String query, Bundle extras) {
    if (TextUtils.isEmpty(query)) {
        // The user provided generic string e.g. 'Play music'
        // Build appropriate playlist queue
    } else {
        // Build a queue based on songs that match "query" or "extras" param
        String mediaFocus = extras.getString(MediaStore.EXTRA_MEDIA_FOCUS);
        if (TextUtils.equals(mediaFocus,
                MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE)) {
            isArtistFocus = true;
            artist = extras.getString(MediaStore.EXTRA_MEDIA_ARTIST);
        } else if (TextUtils.equals(mediaFocus,
                MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE)) {
            isAlbumFocus = true;
            album = extras.getString(MediaStore.EXTRA_MEDIA_ALBUM);
        }

        // Implement additional "extras" param filtering
    }

    // Implement your logic to retrieve the queue
    if (isArtistFocus) {
        result = searchMusicByArtist(artist);
    } else if (isAlbumFocus) {
        result = searchMusicByAlbum(album);
    }

    if (result == null) {
        // No focus found, search by query for song title
        result = searchMusicBySongTitle(query);
    }

    if (result != null && !result.isEmpty()) {
        // Immediately start playing from the beginning of the search results
        // Implement your logic to start playing music
        playMusic(result);
    } else {
        // Handle no queue found. Stop playing if the app
        // is currently playing a song
    }
}

앱에서 오디오 콘텐츠를 재생하기 위해 음성 검색을 구현하는 방법에 관한 자세한 예는 범용 Android 뮤직 플레이어 샘플을 참고하세요.

빈 쿼리 처리

검색어 없이 onPrepare(), onPlay(), onPrepareFromSearch() 또는 onPlayFromSearch()가 호출되면 미디어 앱은 '현재' 미디어를 재생해야 합니다. 현재 미디어가 없는 경우 앱은 가장 최근 재생목록의 노래 또는 무작위 대기열의 노래와 같은 항목을 재생하려고 시도해야 합니다. 어시스턴트는 사용자가 추가 정보 없이 "[앱 이름]에서 음악 재생해 줘"라고 요청하면 이 API를 사용합니다.

사용자가 "[앱 이름]에서 음악 재생해 줘"라고 말하면 Android Automotive OS 또는 Android Auto는 앱을 실행하고 앱의 onPlayFromSearch() 메서드를 호출하여 오디오를 재생하려고 합니다. 그러나 사용자가 미디어 항목의 이름을 말하지 않았으므로 onPlayFromSearch() 메서드는 빈 쿼리 매개변수를 수신합니다. 이 경우 앱은 가장 최근 재생목록의 노래나 무작위 대기열의 노래와 같은 오디오를 즉시 재생하여 응답해야 합니다.

음성 작업의 레거시 지원 선언

대부분의 경우 위에서 설명한 재생 작업을 처리하면 앱에 필요한 모든 재생 기능이 제공됩니다. 그러나 일부 시스템에서는 검색용 인텐트 필터를 앱에 포함해야 합니다. 앱의 매니페스트 파일에서 이 인텐트 필터에 관한 지원을 선언해야 합니다.

전화 앱의 매니페스트 파일에 다음 코드를 포함합니다.

<activity>
    <intent-filter>
        <action android:name=
             "android.media.action.MEDIA_PLAY_FROM_SEARCH" />
        <category android:name=
             "android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

전송 컨트롤

앱의 미디어 세션이 활성화되면 어시스턴트가 음성 명령을 실행하여 재생을 제어하고 미디어 메타데이터를 업데이트할 수 있습니다. 이렇게 하려면 코드에서 다음 작업을 사용 설정하고 상응하는 콜백을 구현해야 합니다.

작업 콜백 설명
ACTION_SKIP_TO_NEXT onSkipToNext() 다음 동영상
ACTION_SKIP_TO_PREVIOUS onSkipToPrevious() 이전 곡
ACTION_PAUSE, ACTION_PLAY_PAUSE onPause() 일시중지
ACTION_STOP onStop() 중지
ACTION_PLAY onPlay() 다시 시작하기
ACTION_SEEK_TO onSeekTo() 30초 되감기
ACTION_SET_RATING onSetRating(android.support.v4.media.RatingCompat) 좋아요/싫어요
ACTION_SET_CAPTIONING_ENABLED onSetCaptioningEnabled(boolean) 자막 켜기/끄기

다음을 참고하세요.

  • seek 명령어가 작동하려면 PlaybackStatestate, position, playback speed, and update time이 최신 상태여야 합니다. 상태가 변경되면 앱에서 setPlaybackState()를 호출해야 합니다.
  • 또한 미디어 앱은 미디어 세션 메타데이터를 최신 상태로 유지해야 합니다. "재생 중인 곡이 무엇인가요?"와 같은 질문을 지원합니다. 트랙 제목, 아티스트, 이름 등 관련 필드가 변경되면 앱에서 setMetadata()를 호출해야 합니다.
  • 앱이 지원하는 평점의 유형을 나타내도록 MediaSession.setRatingType()을 설정해야 하며 앱은 onSetRating()을 구현해야 합니다. 앱이 평점을 지원하지 않는 경우 평점 유형을 RATING_NONE으로 설정해야 합니다.

지원하는 음성 작업은 콘텐츠 유형에 따라 다를 수 있습니다.

콘텐츠 유형 필요한 작업
음악

지원해야 하는 기능: 재생, 일시중지, 중지, 다음으로 건너뛰기, 이전으로 건너뛰기

지원 적극 권장: 탐색 대상

팟캐스트

지원해야 하는 기능: 재생, 일시중지, 중지, 탐색

다음 지원 추천: 다음으로 건너뛰기 및 이전으로 건너뛰기

오디오북 지원해야 하는 기능: 재생, 일시중지, 중지, 탐색
라디오 지원해야 하는 기능: 재생, 일시중지, 중지
뉴스 지원해야 하는 기능: 재생, 일시중지, 중지, 다음으로 건너뛰기, 이전으로 건너뛰기
동영상

지원해야 하는 기능: 재생, 일시중지, 중지, 찾기, 되감기, 빨리 감기

다음 지원 적극 권장: 다음으로 건너뛰기 및 이전으로 건너뛰기

위에 나열된 작업을 제품에서 허용하는 만큼 지원해야 하지만 다른 작업에는 적절하게 대응해야 합니다. 예를 들어 프리미엄 사용자만 이전 항목으로 돌아갈 수 있는 경우 무료 등급 사용자가 어시스턴트에게 이전 항목으로 돌아가도록 요청하면 오류가 발생할 수 있습니다. 자세한 내용은 오류 처리 섹션을 참고하세요.

사용해 볼 만한 음성 쿼리 샘플

다음 표에는 구현을 테스트할 때 사용해야 하는 몇 가지 샘플 쿼리가 요약되어 있습니다.

MediaSession 콜백 사용할 "Hey Google" 구문
onPlay()

“재생해 줘.”

"다시 시작해 줘."

onPlayFromSearch()
onPlayFromUri()
음악

"(앱 이름)에서 음악이나 노래 재생해 줘." 이 쿼리는 비어 있습니다.

"(앱 이름)에서 (노래 | 아티스트 | 앨범 | 장르 | 재생목록) 재생해 줘."

라디오 "(앱 이름)에서 (주파수 | 채널) 재생해 줘."
오디오북

"(앱 이름)에서 오디오북 읽어 줘."

"(앱 이름)에서(오디오북) 읽어 줘."

팟캐스트 "(앱 이름)에서(팟캐스트) 재생해 줘."
onPause() “일시중지해 줘.”
onStop() “중지해 줘.”
onSkipToNext() "다음(노래 | 에피소드 | 트랙))"
onSkipToPrevious() "이전(노래 | 에피소드 | 트랙))"
onSeekTo()

“다시 시작해 줘.”

"##초 앞으로 건너뜁니다."

"##분 전으로 돌아가세요."

해당 사항 없음 (MediaMetadata을 최신 상태로 유지) “지금 나오는 뉴스 뭐야?”

오류

어시스턴트는 미디어 세션의 오류가 발생하면 이를 처리하고 사용자에게 보고합니다. 미디어 세션 작업에 설명된 대로 미디어 세션이 PlaybackState의 전송 상태와 오류 코드를 올바르게 업데이트해야 합니다. 어시스턴트는 getErrorCode()에서 반환된 모든 오류 코드를 인식합니다.

일반적으로 잘못 처리되는 케이스

다음은 올바르게 처리해야 하는 오류 사례의 예입니다.

  • 사용자가 로그인해야 합니다.
    • PlaybackState 오류 코드를 ERROR_CODE_AUTHENTICATION_EXPIRED로 설정합니다.
    • PlaybackState 오류 메시지를 설정합니다.
    • 재생에 필요한 경우 PlaybackState 상태를 STATE_ERROR로 설정하고, 그렇지 않으면 PlaybackState의 나머지 부분을 그대로 유지합니다.
  • 사용자가 사용할 수 없는 작업을 요청함
    • PlaybackState 오류 코드를 적절하게 설정합니다. 예를 들어 작업이 지원되지 않으면 PlaybackStateERROR_CODE_NOT_SUPPORTED로 설정하고 로그인 보호 작업인 경우 ERROR_CODE_PREMIUM_ACCOUNT_REQUIRED로 설정합니다.
    • PlaybackState 오류 메시지를 설정합니다.
    • PlaybackState의 나머지 부분은 그대로 유지합니다.
  • 사용자가 앱에서 사용할 수 없는 콘텐츠를 요청합니다.
    • PlaybackState 오류 코드를 적절하게 설정합니다. 예를 들어 ERROR_CODE_NOT_AVAILABLE_IN_REGION를 사용합니다.
    • PlaybackState 오류 메시지를 설정합니다.
    • PlaybackSate 상태를 STATE_ERROR로 설정하여 재생을 중단하고 나머지 PlaybackState를 그대로 유지합니다.
  • 사용자가 일치검색이 불가능한 콘텐츠를 요청합니다. 예를 들어 프리미엄 등급 사용자에게만 제공되는 콘텐츠를 요청하는 무료 등급 사용자가 있습니다.
    • 오류를 반환하지 않고 대신 플레이와 비슷한 것을 찾는 것에 우선순위를 두는 것이 좋습니다. 어시스턴트는 재생이 시작되기 전에 가장 관련성 높은 음성 응답을 말합니다.

인텐트를 사용한 재생

어시스턴트는 오디오 또는 동영상 앱을 실행하고 딥 링크와 함께 인텐트를 전송하여 재생을 시작할 수 있습니다.

인텐트와 딥 링크는 다음과 같이 다양한 소스에서 가져올 수 있습니다.

  • 어시스턴트는 모바일 앱을 시작할 때 Google 검색을 사용하여 링크와 함께 시청 작업을 제공하는 마크업된 콘텐츠를 검색할 수 있습니다.
  • 어시스턴트가 TV 앱을 시작할 때 미디어 콘텐츠의 URI를 노출하는 TV 검색 제공자를 앱에 포함해야 합니다. 어시스턴트는 딥 링크의 URI와 선택적 작업이 포함된 인텐트를 반환해야 하는 쿼리를 콘텐츠 제공업체에 전송합니다. 쿼리가 인텐트에 작업을 반환하면 어시스턴트는 그 작업과 URI를 앱에 다시 전송합니다. 제공자가 작업을 지정하지 않으면 어시스턴트는 ACTION_VIEW를 인텐트에 추가합니다.

어시스턴트는 앱에 전송하는 인텐트에 값이 true인 추가 EXTRA_START_PLAYBACK를 추가합니다. 앱은 EXTRA_START_PLAYBACK로 인텐트를 수신하면 재생을 시작해야 합니다.

활성 상태에서 인텐트 처리

사용자는 앱이 이전 요청의 콘텐츠를 재생하는 동안 어시스턴트에 무언가를 재생하도록 요청할 수 있습니다. 즉, 재생 활동이 이미 시작되어 활성 상태인 동안 앱이 새 인텐트를 수신하여 재생을 시작할 수 있습니다.

딥 링크가 있는 인텐트를 지원하는 활동은 onNewIntent()를 재정의하여 새 요청을 처리해야 합니다.

재생을 시작할 때 어시스턴트는 앱에 전송하는 인텐트에 플래그를 추가할 수 있습니다. 특히 FLAG_ACTIVITY_CLEAR_TOPFLAG_ACTIVITY_NEW_TASK 또는 둘 다를 추가할 수 있습니다. 코드가 이러한 플래그를 처리할 필요는 없지만 Android 시스템은 이에 응답합니다. 이는 이전 URI가 계속 재생되는 동안 새 URI가 포함된 두 번째 재생 요청이 도착하면 앱 동작에 영향을 줄 수 있습니다. 이 경우 앱이 어떻게 반응하는지를 테스트하는 것이 좋습니다. adb 명령줄 도구를 사용하여 상황을 시뮬레이션할 수 있습니다 (상수 0x14000000는 두 플래그의 불리언 비트 OR입니다).

adb shell 'am start -a android.intent.action.VIEW --ez android.intent.extra.START_PLAYBACK true -d "<first_uri>"' -f 0x14000000
adb shell 'am start -a android.intent.action.VIEW --ez android.intent.extra.START_PLAYBACK true -d "<second_uri>"' -f 0x14000000

서비스에서 재생

앱에 어시스턴트로부터의 연결을 허용하는 media browser service가 있다면 어시스턴트는 서비스의 media session와 통신하여 앱을 시작할 수 있습니다. 미디어 브라우저 서비스는 활동을 시작해서는 안 됩니다. 어시스턴트는 setSessionActivity()로 정의한 PendingIntent에 따라 활동을 실행합니다.

미디어 브라우저 서비스를 초기화할 때 MediaSession.Token을 설정해야 합니다. 초기화 도중을 포함하여 항상 지원되는 재생 작업을 설정해야 합니다. 어시스턴트는 어시스턴트가 첫 번째 재생 명령어를 전송하기 전에 미디어 앱에서 재생 작업을 설정할 것으로 예상합니다.

서비스에서 재생을 시작하기 위해 어시스턴트는 미디어 브라우저 클라이언트 API를 구현합니다. 앱의 미디어 세션에서 PLAY 작업 콜백을 트리거하는 TransportControls를 호출합니다.

다음 다이어그램은 어시스턴트 및 이에 상응하는 미디어 세션 콜백에서 생성한 호출의 순서를 보여줍니다. 준비 콜백은 앱에서 지원하는 경우에만 전송됩니다. 모든 호출은 비동기입니다. 어시스턴트는 앱의 응답을 기다리지 않습니다.

미디어 세션과 함께 재생 시작

사용자가 재생을 위한 음성 명령을 실행하면 어시스턴트가 짧은 알림으로 응답합니다. 알림이 완료되는 즉시 어시스턴트가 PLAY 작업을 실행합니다. 특정 재생 상태를 기다리지 않습니다.

앱에서 ACTION_PREPARE_* 작업을 지원하는 경우 어시스턴트는 알림을 시작하기 전에 PREPARE 작업을 호출합니다.

MediaBrowserService에 연결

서비스를 사용하여 앱을 시작하려면 어시스턴트가 앱의 MediaBrowserService에 연결하여 MediaSession.Token을 검색할 수 있어야 합니다. 연결 요청은 서비스의 onGetRoot() 메서드에서 처리됩니다. 요청을 처리하는 방법에는 두 가지가 있습니다.

  • 모든 연결 요청 수락
  • 어시스턴트 앱에서만 연결 요청 수락

모든 연결 요청 수락

어시스턴트가 미디어 세션으로 명령어를 보낼 수 있도록 하려면 BrowserRoot를 반환해야 합니다. 가장 쉬운 방법은 모든 MediaBrowser 앱이 MediaBrowserService에 연결되도록 허용하는 것입니다. Null이 아닌 BrowserRoot를 반환해야 합니다. 다음은 유니버설 음악 플레이어의 관련 코드입니다.

Kotlin

override fun onGetRoot(
        clientPackageName: String,
        clientUid: Int,
        rootHints: Bundle?
): BrowserRoot? {

    // To ensure you are not allowing any arbitrary app to browse your app's contents, you
    // need to check the origin:
    if (!packageValidator.isCallerAllowed(this, clientPackageName, clientUid)) {
        // If the request comes from an untrusted package, return an empty browser root.
        // If you return null, then the media browser will not be able to connect and
        // no further calls will be made to other media browsing methods.
        Log.i(TAG, "OnGetRoot: Browsing NOT ALLOWED for unknown caller. Returning empty "
                + "browser root so all apps can use MediaController. $clientPackageName")
        return MediaBrowserServiceCompat.BrowserRoot(MEDIA_ID_EMPTY_ROOT, null)
    }

    // Return browser roots for browsing...
}

Java

@Override
public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid,
                             Bundle rootHints) {

    // To ensure you are not allowing any arbitrary app to browse your app's contents, you
    // need to check the origin:
    if (!packageValidator.isCallerAllowed(this, clientPackageName, clientUid)) {
        // If the request comes from an untrusted package, return an empty browser root.
        // If you return null, then the media browser will not be able to connect and
        // no further calls will be made to other media browsing methods.
        LogHelper.i(TAG, "OnGetRoot: Browsing NOT ALLOWED for unknown caller. "
                + "Returning empty browser root so all apps can use MediaController."
                + clientPackageName);
        return new MediaBrowserServiceCompat.BrowserRoot(MEDIA_ID_EMPTY_ROOT, null);
    }

    // Return browser roots for browsing...
}

어시스턴트 앱 패키지 및 서명 수락

패키지 이름과 서명을 확인하여 어시스턴트가 미디어 브라우저 서비스에 연결하도록 명시적으로 허용할 수 있습니다. 앱은 MediaBrowserService의 onGetRoot 메서드에서 패키지 이름을 수신합니다. 어시스턴트가 미디어 세션으로 명령어를 보낼 수 있도록 하려면 BrowserRoot를 반환해야 합니다. 범용 음악 플레이어 샘플은 알려진 패키지 이름 및 서명 목록을 유지 관리합니다. 다음은 Google 어시스턴트에서 사용되는 패키지 이름 및 서명입니다.

<signature name="Google" package="com.google.android.googlequicksearchbox">
    <key release="false">19:75:b2:f1:71:77:bc:89:a5:df:f3:1f:9e:64:a6:ca:e2:81:a5:3d:c1:d1:d5:9b:1d:14:7f:e1:c8:2a:fa:00</key>
    <key release="true">f0:fd:6c:5b:41:0f:25:cb:25:c3:b5:33:46:c8:97:2f:ae:30:f8:ee:74:11:df:91:04:80:ad:6b:2d:60:db:83</key>
</signature>

<signature name="Google Assistant on Android Automotive OS" package="com.google.android.carassistant">
    <key release="false">17:E2:81:11:06:2F:97:A8:60:79:7A:83:70:5B:F8:2C:7C:C0:29:35:56:6D:46:22:BC:4E:CF:EE:1B:EB:F8:15</key>
    <key release="true">74:B6:FB:F7:10:E8:D9:0D:44:D3:40:12:58:89:B4:23:06:A6:2C:43:79:D0:E5:A6:62:20:E3:A6:8A:BF:90:E2</key>
</signature>