Google 어시스턴트 및 미디어 앱

Google 어시스턴트를 사용하면 음성 명령을 사용하여 다음과 같은 여러 기기를 제어할 수 있습니다. Google Home, 휴대전화 등 Cloud Functions는 미디어 명령어("비욘세 노래 틀어 줘")를 이해하고 미디어 컨트롤 (예: 일시중지, 건너뛰기, 빨리 감기, 좋아요)

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

미디어 세션 사용

모든 오디오 및 동영상 앱은 미디어 세션 어시스턴트가 작동할 수 있도록 재생을 시작하면 전송 컨트롤에 전달됩니다.

어시스턴트는 이 섹션에 나열된 작업만 사용하지만 가장 좋은 방법은 모든 준비 및 재생 API를 구현하여 다른 애플리케이션과의 호환성이 있습니다. 지원하지 않는 작업의 경우 미디어 세션 콜백은 단순히 ERROR_CODE_NOT_SUPPORTED

앱의 MediaSession 객체:

Kotlin

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

자바

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 기반 작업은 회사에서만 작동합니다. 외부 IP 주소가 있습니다 Google에 미디어 콘텐츠를 설명하는 방법 자세히 알아보기 미디어 작업을 참고하세요.

준비 API를 구현하면 음성 명령 후 재생 지연 시간이 줄일 수 있습니다 재생 지연 시간을 개선하려는 미디어 앱은 다음을 사용할 수 있습니다. 콘텐츠 캐싱과 미디어 재생 준비에 시간이 더 많이 걸리기 때문입니다.

검색어 파싱

사용자가 'Play jazz on [앱 이름]' 또는 '[노래 제목] 듣기' 또는 onPrepareFromSearch() 또는 onPlayFromSearch() 콜백 메서드가 쿼리 매개변수 및 추가 번들을 수신합니다.

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

  1. 음성 검색에서 반환된 추가 번들 및 검색어 문자열 사용 결과를 필터링할 수 있습니다
  2. 이 결과에 근거하여 재생 대기열을 빌드합니다.
  3. 결과 중에서 관련성이 가장 높은 미디어 항목을 재생합니다.
를 통해 개인정보처리방침을 정의할 수 있습니다.

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

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

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

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

자바

@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()

일반적으로 잘못 처리되는 사례

다음은 자신이 처리해야 하는 오류 사례의 예입니다. 있습니다.

  • 사용자가 로그인해야 합니다. <ph type="x-smartling-placeholder">
      </ph>
    • PlaybackState 오류 코드를 ERROR_CODE_AUTHENTICATION_EXPIRED로 설정합니다.
    • PlaybackState 오류 메시지를 설정합니다.
    • 재생에 필요한 경우 PlaybackState 상태를 STATE_ERROR로 설정합니다. 나머지는 PlaybackState의 나머지를 그대로 유지합니다.
  • 사용자가 사용할 수 없는 작업을 요청함 <ph type="x-smartling-placeholder">
      </ph>
    • PlaybackState 오류 코드를 적절하게 설정합니다. 예를 들어 PlaybackState 출발 ERROR_CODE_NOT_SUPPORTED 도착 작업이 지원되지 않는 경우 또는 ERROR_CODE_PREMIUM_ACCOUNT_REQUIRED 로그인이 보호되는 작업인 경우
    • PlaybackState 오류 메시지를 설정합니다.
    • 나머지 PlaybackState를 그대로 유지합니다.
  • 사용자가 앱에서 사용할 수 없는 콘텐츠를 요청함 <ph type="x-smartling-placeholder">
      </ph>
    • PlaybackState 오류 코드를 적절하게 설정합니다. 예를 들어 ERROR_CODE_NOT_AVAILABLE_IN_REGION
    • PlaybackState 오류 메시지를 설정합니다.
    • PlaybackSate 상태를 STATE_ERROR로 설정하여 재생을 중단합니다. 나머지 PlaybackState를 그대로 유지합니다.
  • 사용자가 일치검색을 사용할 수 없는 콘텐츠를 요청합니다. 예를 들어 프리미엄 등급 사용자에게만 제공되는 콘텐츠를 요청하는 무료 등급 사용자
    • 오류를 반환하지 말고 대신 뭔가를 찾는 것처럼 말이죠. 어시스턴트가 가장 많이 말하는 내용을 처리합니다. 관련 음성 응답을 수신할 수 있습니다.

인텐트를 사용한 재생

어시스턴트는 오디오 또는 동영상 앱을 실행하고 인텐트를 딥 링크로 설정하여 인텐트를 호출합니다.

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

  • 어시스턴트가 다음 상태인 경우 이제 Google 검색을 사용하여 마크업된 콘텐츠를 검색할 수 있습니다. 시청 작업을 링크와 함께 제공합니다.
  • 어시스턴트가 TV 앱을 시작할 때 앱에 다음이 포함되어야 합니다. TV 검색 제공업체 미디어 콘텐츠의 URI를 노출합니다 어시스턴트는 딥 링크의 URI가 포함된 인텐트를 반환해야 하는 콘텐츠 제공자와 선택 작업을 할 수 있습니다. 쿼리가 인텐트에서 작업을 반환하면 어시스턴트가 해당 작업과 URI를 앱으로 다시 보냅니다. 제공업체가 지정되지 않은 어시스턴트는 인텐트에 ACTION_VIEW를 추가합니다.

어시스턴트가 true 값으로 EXTRA_START_PLAYBACK를 추가합니다. 앱에 전송하는 인텐트에 적용됩니다. 앱이 재생을 시작해야 할 때 EXTRA_START_PLAYBACK가 있는 인텐트를 수신합니다.

활성 상태에서 인텐트 처리

앱이 재생되는 동안 사용자가 어시스턴트에게 콘텐츠를 재생해 달라고 요청할 수 있습니다. 이전 요청의 콘텐츠만 반환합니다. 즉, 앱이 새로운 인텐트를 수신하여 재생 활동이 이미 시작되어 활성화되어 있는 동안 재생을 시작할 수 없습니다.

딥 링크가 있는 인텐트를 지원하는 활동은 onNewIntent() 새로운 요청을 처리합니다

재생을 시작할 때 어시스턴트가 플래그 앱에 전송하는 인텐트에 적용됩니다. 특히 FLAG_ACTIVITY_CLEAR_TOP 또는 FLAG_ACTIVITY_NEW_TASK 또는 둘 다 코드가 Android 시스템이 이 플래그를 처리할 필요가 없으면 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 미디어 브라우저 서비스는 활동을 시작해서는 안 됩니다. 어시스턴트가 정의된 PendingIntent에 따라 활동을 실행합니다. setSessionActivity()로 호출

요청 시 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...
}

자바

@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>