Google은 흑인 공동체를 위한 인종 간 평등을 진전시키기 위해 노력하고 있습니다. Google에서 어떤 노력을 하고 있는지 확인하세요.

차량용 미디어 앱 빌드

Android Auto와 Android Automotive OS를 사용하면 미디어 앱 콘텐츠를 차에 탄 사용자에게 제공할 수 있습니다 차량용 미디어 앱에서는 Android Auto와 Android Automotive OS(또는 미디어 브라우저가 있는 다른 앱)가 콘텐츠를 검색하고 표시할 수 있도록 미디어 브라우저 서비스를 제공해야 합니다.

이 가이드에서는 개발자에게 이미 휴대전화에서 오디오를 재생하는 미디어 앱이 있고 미디어 앱이 Android 미디어 앱 아키텍처를 준수한다고 가정합니다.

이 가이드에서는 앱이 Android Auto 또는 Android Automotive OS에서 작동하는 데 필요한 MediaBrowserServiceMediaSession의 필수 구성요소를 설명합니다. 핵심 미디어 인프라를 완료하면 Android Auto 지원Android Automotive OS 지원을 미디어 앱에 추가할 수 있습니다.

시작하기 전에

  1. Android 미디어 API 문서를 검토합니다.
  2. Android Automotive OS 앱 디자인 가이드라인Android Auto 앱 디자인 가이드라인을 검토합니다.
  3. 이 섹션에 나열된 주요 용어와 개념을 검토합니다.

주요 용어 및 개념

미디어 브라우저 서비스
MediaBrowserServiceCompat API를 준수하는 미디어 앱에서 구현한 Android 서비스입니다. 앱에서 이 서비스를 사용하여 콘텐츠를 노출합니다.
미디어 브라우저
미디어 앱에서 미디어 브라우저 서비스를 검색하고 콘텐츠를 표시하는 데 사용하는 API입니다. Android Auto 및 Android Automotive OS에서는 미디어 브라우저를 사용하여 앱의 미디어 브라우저 서비스를 찾습니다.
미디어 항목

미디어 브라우저는 MediaItem 객체 트리에 콘텐츠를 구성합니다. 미디어 항목에는 다음 두 플래그 중 하나 또는 둘 다가 있을 수 있습니다.

  • 재생 가능: 이 플래그는 항목이 콘텐츠 트리의 리프인 것을 나타냅니다. 항목은 앨범의 노래, 오디오북의 장 또는 팟캐스트의 에피소드와 같은 단일 사운드 스트림을 나타냅니다.
  • 탐색 가능: 이 플래그는 항목이 콘텐츠 트리의 노드이고 하위 요소가 있다는 것을 나타냅니다. 예를 들어 항목은 앨범을 나타내고 하위 요소는 앨범의 노래입니다.

탐색 가능하고 재생 가능한 미디어 항목은 재생목록 역할을 합니다. 항목 자체를 선택하여 모든 하위 요소를 재생하거나 하위 요소를 탐색할 수 있습니다.

차량에 최적화

Android Automotive OS 디자인 가이드라인을 준수하는 Android Automotive OS 앱을 위한 활동입니다. 이러한 활동의 인터페이스는 Android Automotive OS에서 작성하지 않으므로 앱이 디자인 가이드라인을 준수하는지 확인해야 합니다. 일반적으로 여기에는 더 큰 탭 타겟 및 글꼴 크기, 주야간 모드 지원, 더 높은 대비율이 포함됩니다.

차량에 최적화된 사용자 인터페이스는 사용자의 주의 또는 상호작용이 더 오래 필요할 수 있기 때문에 자동차 사용자 환경 제한(CUXR)이 적용되지 않는 경우에만 표시가 허용됩니다. CUXR은 차량이 정차 또는 주차 중일 때는 적용되지 않지만 차량이 움직이면 항상 적용됩니다.

Android Auto는 미디어 브라우저 서비스의 정보를 사용하여 차량에 최적화된 자체 인터페이스를 작성하므로 Android Auto용 활동을 디자인할 필요가 없습니다.

앱의 매니페스트 파일 구성

미디어 브라우저 서비스를 만들려면 먼저 앱의 매니페스트 파일을 구성해야 합니다.

미디어 브라우저 서비스 선언

Android Auto와 Android Automotive OS는 모두 미디어 브라우저 서비스를 통해 앱에 연결되어 미디어 항목을 탐색합니다. 매니페스트에서 미디어 탐색 서비스를 선언하면 Android Auto와 Android Automotive OS에서 서비스를 검색하고 앱에 연결할 수 있습니다.

다음 코드 스니펫은 매니페스트에서 미디어 브라우저 서비스를 선언하는 방법을 보여줍니다. 이 코드를 Android Automotive OS 모듈의 매니페스트 파일과 전화 앱의 매니페스트 파일에 포함해야 합니다.

<application>
        ...
        <service android:name=".MyMediaBrowserService"
                 android:exported="true">
            <intent-filter>
                <action android:name="android.media.browse.MediaBrowserService"/>
            </intent-filter>
        </service>
        ...
    <application>
    

앱 아이콘 지정

Android Auto 및 Android Automotive OS가 시스템 UI에서 앱을 나타내는 데 사용할 수 있는 앱 아이콘을 지정해야 합니다.

다음 매니페스트 선언을 통해 앱을 나타내는 데 사용할 아이콘을 지정할 수 있습니다.

<!--The android:icon attribute is used by Android Automotive OS-->
    <application
        ...
        android:icon="@mipmap/ic_launcher">
        ...
        <!--Used by Android Auto-->
        <meta-data android:name="com.google.android.gms.car.notification.SmallIcon"
                   android:resource="@drawable/ic_auto_icon" />
        ...
    <application>
    

미디어 브라우저 서비스 만들기

MediaBrowserServiceCompat 클래스를 확장하여 미디어 브라우저 서비스를 만듭니다. 그러면 Android Auto와 Android Automotive OS에서 서비스를 사용하여 다음 작업을 할 수 있습니다.

  • 사용자에게 메뉴를 표시하기 위해 앱의 콘텐츠 계층 구조 탐색
  • 오디오 재생을 제어하기 위해 앱에 있는 MediaSessionCompat 객체의 토큰 가져오기

미디어 브라우저 서비스 워크플로

이 섹션에서는 일반적인 사용자 워크플로가 진행되는 과정에서 Android Automotive OS 및 Android Auto가 미디어 브라우저 서비스와 어떻게 상호작용하는지 설명합니다.

  1. 사용자가 Android Automotive OS 또는 Android Auto에서 앱을 실행합니다.
  2. Android Automotive OS 또는 Android Auto에서 onCreate() 메서드를 사용하여 앱의 미디어 브라우저 서비스에 연결합니다. onCreate() 메서드를 구현할 때 MediaSessionCompat 객체와 이 객체의 콜백 객체를 만들고 등록해야 합니다.
  3. Android Automotive OS 또는 Android Auto에서는 서비스의 onGetRoot() 메서드를 호출하여 콘텐츠 계층 구조의 루트 미디어 항목을 가져옵니다. 루트 미디어 항목은 표시되지 않지만 앱에서 더 많은 콘텐츠를 검색하는 데 사용됩니다.
  4. Android Automotive OS 또는 Android Auto에서는 서비스의 onLoadChildren() 메서드를 호출하여 루트 미디어 항목의 하위 요소를 가져옵니다. Android Automotive OS 및 Android Auto에서는 이러한 미디어 항목을 최상위 수준의 콘텐츠 항목으로 표시합니다. 최상위 수준의 콘텐츠 항목은 탐색 가능해야 합니다.
  5. 사용자가 탐색 가능한 미디어 항목을 선택하면 서비스의 onLoadChildren() 메서드가 다시 호출되어 선택한 메뉴 항목의 하위 요소를 검색합니다.
  6. 사용자가 재생 가능한 미디어 항목을 선택하면 Android Automotive OS 또는 Android Auto에서 적절한 미디어 세션 콜백 메서드를 호출하여 이 작업을 실행합니다.
  7. 앱에서 지원하는 경우 사용자는 콘텐츠를 검색할 수도 있습니다. 이 경우 Android Automotive OS 또는 Android Auto에서는 서비스의 onSearch() 메서드를 호출합니다.

콘텐츠 계층 구조 빌드

Android Auto 및 Android Automotive OS는 앱의 미디어 브라우저 서비스를 호출하여 사용 가능한 콘텐츠를 찾습니다. 이 작업을 지원하려면 미디어 브라우저 서비스에서 onGetRoot()onLoadChildren()이라는 두 가지 메서드를 구현해야 합니다.

onGetRoot 구현

서비스의 onGetRoot() 메서드에서는 콘텐츠 계층 구조의 루트 노드에 관한 정보를 반환합니다. Android Auto 및 Android Automotive OS에서는 이 루트 노드를 사용하여 onLoadChildren() 메서드로 나머지 콘텐츠를 요청합니다.

다음 코드 스니펫은 onGetRoot() 메서드의 간단한 구현을 보여줍니다.

Kotlin

    override fun onGetRoot(
        clientPackageName: String,
        clientUid: Int,
        rootHints: Bundle?
    ): BrowserRoot? =
        // Verify that the specified package is allowed to access your
        // content! You'll need to write your own logic to do this.
        if (!isValid(clientPackageName, clientUid)) {
            // If the request comes from an untrusted package, return null.
            // No further calls will be made to other media browsing methods.

            null
        } else MediaBrowserServiceCompat.BrowserRoot(MY_MEDIA_ROOT_ID, null)
    

자바

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

        // Verify that the specified package is allowed to access your
        // content! You'll need to write your own logic to do this.
        if (!isValid(clientPackageName, clientUid)) {
            // If the request comes from an untrusted package, return null.
            // No further calls will be made to other media browsing methods.

            return null;
        }

        return new MediaBrowserServiceCompat.BrowserRoot(MY_MEDIA_ROOT_ID, null);
    }
    

이 메서드의 자세한 예는 GitHub의 범용 Android 뮤직 플레이어 샘플 앱에서 onGetRoot() 메서드를 참조하세요.

onGetRoot()에 패키지 유효성 검사 추가

서비스의 onGetRoot() 메서드가 호출되면 호출 패키지는 서비스에 식별 정보를 전달합니다. 서비스에서는 이 정보를 사용하여 패키지가 콘텐츠에 액세스할 수 있는지 확인할 수 있습니다. 예를 들어 clientPackageName을 허용 목록과 비교하고 패키지의 APK에 서명하는 데 사용되는 인증서를 확인하여 앱 콘텐츠 액세스를 승인된 패키지 목록으로 제한할 수 있습니다. 패키지를 확인할 수 없는 경우 null을 반환하여 콘텐츠 액세스를 거부합니다.

시스템 앱(예: Android Auto, Android Automotive OS)에 콘텐츠 액세스 권한을 제공하려면 서비스에서는 이러한 시스템 앱이 onGetRoot() 메서드를 호출할 때 항상 null이 아닌 BrowserRoot를 반환해야 합니다.

다음 코드 스니펫은 호출 패키지가 시스템 앱인지 여부를 서비스에서 검사하는 방법을 보여줍니다.

fun isKnownCaller(
        callingPackage: String,
        callingUid: Int
    ): Boolean {
        ...
        val isCallerKnown = when {
           // If the system is making the call, allow it.
           callingUid == Process.SYSTEM_UID -> true
           // If the app was signed by the same certificate as the platform
           // itself, also allow it.
           callerSignature == platformSignature -> true
           // ... more cases
        }
        return isCallerKnown
    }
    

이 코드 스니펫은 GitHub의 범용 Android 뮤직 플레이어 샘플 앱에 있는 PackageValidator 클래스에서 발췌한 것입니다. 서비스의 onGetRoot() 메서드의 패키지 유효성 검사를 구현하는 방법에 관한 자세한 예는 이 클래스를 참조하세요.

onLoadChildren() 구현

루트 노드 객체를 수신한 후 Android Auto와 Android Automotive OS는 루트 노드 객체에서 onLoadChildren()을 호출하여 하위 요소를 가져오는 방식으로 최상위 메뉴를 빌드합니다. 클라이언트 앱에서는 하위 노드 객체를 사용해 이와 동일한 메서드를 호출하여 하위 메뉴를 빌드합니다.

콘텐츠 계층 구조의 각 노드는 MediaBrowserCompat.MediaItem 객체로 표현됩니다. 이러한 미디어 항목은 각기 고유 ID 문자열로 식별됩니다. 클라이언트 앱에서는 이러한 ID 문자열을 불투명 토큰으로 취급합니다. 클라이언트 앱은 하위 메뉴를 탐색하거나 미디어 항목을 재생하려 할 때 이 토큰을 전달합니다. 앱은 이 토큰을 적절한 미디어 항목과 연결하는 일을 담당합니다.

참고: Android Auto 및 Android Automotive OS는 메뉴의 각 수준에 표시할 수 있는 미디어 항목 수를 엄격히 제한합니다. 이러한 제한은 운전자의 주의를 분산하는 요소를 최소화하고 음성 명령으로 앱을 작동하는 데 도움이 됩니다. 자세한 내용은 콘텐츠 세부정보 탐색Android Auto 앱 검색 창을 참조하세요.

다음 코드 스니펫은 onLoadChildren() 메서드의 간단한 구현을 보여줍니다.

Kotlin

    override fun onLoadChildren(
        parentMediaId: String,
        result: Result<List<MediaBrowserCompat.MediaItem>>
    ) {
        // Assume for example that the music catalog is already loaded/cached.

        val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = mutableListOf()

        // Check if this is the root menu:
        if (MY_MEDIA_ROOT_ID == parentMediaId) {

            // build the MediaItem objects for the top level,
            // and put them in the mediaItems list
        } else {

            // examine the passed parentMediaId to see which submenu we're at,
            // and put the children of that menu in the mediaItems list
        }
        result.sendResult(mediaItems)
    }
    

자바

    @Override
    public void onLoadChildren(final String parentMediaId,
        final Result<List<MediaBrowserCompat.MediaItem>> result) {

        // Assume for example that the music catalog is already loaded/cached.

        List<MediaBrowserCompat.MediaItem> mediaItems = new ArrayList<>();

        // Check if this is the root menu:
        if (MY_MEDIA_ROOT_ID.equals(parentMediaId)) {

            // build the MediaItem objects for the top level,
            // and put them in the mediaItems list
        } else {

            // examine the passed parentMediaId to see which submenu we're at,
            // and put the children of that menu in the mediaItems list
        }
        result.sendResult(mediaItems);
    }
    

이 메서드의 전체 예는 GitHub의 범용 Android 뮤직 플레이어 샘플 앱에서 onLoadChildren() 메서드를 참조하세요.

콘텐츠 스타일 적용

탐색 가능 또는 재생 가능 항목을 사용하여 콘텐츠 계층 구조를 빌드한 후에는 그러한 항목이 차량에 표시되는 방식을 결정하는 콘텐츠 스타일을 적용할 수 있습니다.

다음과 같은 콘텐츠 스타일을 사용할 수 있습니다.

목록 항목

이 콘텐츠 스타일에서는 이미지보다 제목 및 메타데이터의 우선순위가 더 높습니다.

그리드 항목

이 콘텐츠 스타일에서는 제목 및 메타데이터보다 이미지의 우선순위가 더 높습니다.

기본 콘텐츠 스타일 설정

서비스의 onGetRoot() 메서드의 BrowserRoot 추가 번들에 특정 상수를 포함하여 미디어 항목이 표시되는 방식에 전역 기본값을 설정할 수 있습니다. Android Auto와 Android Automotive OS는 탐색 트리의 각 항목과 연결된 추가 번들을 읽고 이러한 상수를 찾아 적절한 스타일을 결정합니다.

앱에서 이 상수를 선언하려면 다음 코드를 사용하세요.

Kotlin

    /** Declares that ContentStyle is supported */
    val CONTENT_STYLE_SUPPORTED = "android.media.browse.CONTENT_STYLE_SUPPORTED"

    /**
    * Bundle extra indicating the presentation hint for playable media items.
    */
    val CONTENT_STYLE_PLAYABLE_HINT = "android.media.browse.CONTENT_STYLE_PLAYABLE_HINT"

    /**
    * Bundle extra indicating the presentation hint for browsable media items.
    */
    val CONTENT_STYLE_BROWSABLE_HINT = "android.media.browse.CONTENT_STYLE_BROWSABLE_HINT"

    /**
    * Specifies the corresponding items should be presented as lists.
    */
    val CONTENT_STYLE_LIST_ITEM_HINT_VALUE = 1

    /**
    * Specifies that the corresponding items should be presented as grids.
    */
    val CONTENT_STYLE_GRID_ITEM_HINT_VALUE = 2
    

자바

    /** Declares that ContentStyle is supported */
    public static final String CONTENT_STYLE_SUPPORTED =;
       "android.media.browse.CONTENT_STYLE_SUPPORTED";

    /**
    * Bundle extra indicating the presentation hint for playable media items.
    */
    public static final String CONTENT_STYLE_PLAYABLE_HINT =
       "android.media.browse.CONTENT_STYLE_PLAYABLE_HINT";

    /**
    * Bundle extra indicating the presentation hint for browsable media items.
    */
    public static final String CONTENT_STYLE_BROWSABLE_HINT =
       "android.media.browse.CONTENT_STYLE_BROWSABLE_HINT";

    /**
    * Specifies the corresponding items should be presented as lists.
    */
    public static final int CONTENT_STYLE_LIST_ITEM_HINT_VALUE = 1;

    /**
    * Specifies that the corresponding items should be presented as grids.
    */
    public static final int CONTENT_STYLE_GRID_ITEM_HINT_VALUE = 2;
    

이러한 상수를 선언한 후 서비스에 있는 onGetRoot() 메서드의 추가 번들에 포함하여 기본 콘텐츠 스타일을 설정합니다. 다음 코드 스니펫은 탐색 가능한 항목의 기본 콘텐츠 스타일을 그리드로 설정하고 재생 가능한 항목을 목록으로 설정하는 방법을 보여줍니다.

Kotlin

    @Nullable
    override fun onGetRoot(
        @NonNull clientPackageName: String,
        clientUid: Int,
        @Nullable rootHints: Bundle
    ): BrowserRoot {
        val extras = Bundle()
        extras.putBoolean(CONTENT_STYLE_SUPPORTED, true)
        extras.putInt(CONTENT_STYLE_BROWSABLE_HINT, CONTENT_STYLE_GRID_ITEM_HINT_VALUE)
        extras.putInt(CONTENT_STYLE_PLAYABLE_HINT, CONTENT_STYLE_LIST_ITEM_HINT_VALUE)
        return BrowserRoot(ROOT_ID, extras)
    }
    

자바

    @Nullable
    @Override
    public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid,
       @Nullable Bundle rootHints) {
       Bundle extras = new Bundle();
       extras.putBoolean(CONTENT_STYLE_SUPPORTED, true);
       extras.putInt(CONTENT_STYLE_BROWSABLE_HINT, CONTENT_STYLE_GRID_ITEM_HINT_VALUE);
       extras.putInt(CONTENT_STYLE_PLAYABLE_HINT, CONTENT_STYLE_LIST_ITEM_HINT_VALUE);
       return new BrowserRoot(ROOT_ID, extras);
    }
    

항목별 콘텐츠 스타일 설정

Content Style API를 사용하면 탐색 가능한 미디어 항목의 하위 요소에 기본 콘텐츠 스타일을 재정의할 수 있습니다. 기본값을 재정의하려면 미디어 항목의 MediaDescription에 추가 번들을 만드세요.

다음 코드 스니펫은 기본 콘텐츠 스타일을 재정의하는 탐색 가능한 MediaItem을 만드는 방법을 보여줍니다.

Kotlin

    private fun createBrowsableMediaItem(
        mediaId: String,
        folderName: String,
        iconUri: Uri
    ): MediaBrowser.MediaItem {
        val mediaDescriptionBuilder = MediaDescription.Builder()
        mediaDescriptionBuilder.setMediaId(mediaId)
        mediaDescriptionBuilder.setTitle(folderName)
        mediaDescriptionBuilder.setIconUri(iconUri)
        val extras = Bundle()
        extras.putInt(CONTENT_STYLE_BROWSABLE_HINT, CONTENT_STYLE_LIST_ITEM_HINT_VALUE)
        extras.putInt(CONTENT_STYLE_PLAYABLE_HINT, CONTENT_STYLE_GRID_ITEM_HINT_VALUE)
        mediaDescriptionBuilder.setExtras(extras)
        return MediaBrowser.MediaItem(
            mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE)
    }
    

자바

    private MediaBrowser.MediaItem createBrowsableMediaItem(String mediaId,
       String folderName, Uri iconUri) {
       MediaDescription.Builder mediaDescriptionBuilder = new MediaDescription.Builder();
       mediaDescriptionBuilder.setMediaId(mediaId);
       mediaDescriptionBuilder.setTitle(folderName);
       mediaDescriptionBuilder.setIconUri(iconUri);
       Bundle extras = new Bundle();
       extras.putInt(CONTENT_STYLE_BROWSABLE_HINT, CONTENT_STYLE_LIST_ITEM_HINT_VALUE);
       extras.putInt(CONTENT_STYLE_PLAYABLE_HINT, CONTENT_STYLE_GRID_ITEM_HINT_VALUE);
       mediaDescriptionBuilder.setExtras(extras);
       return new MediaBrowser.MediaItem(
           mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE);
    }
    

제목 힌트를 사용하여 항목 그룹화

관련 미디어 항목을 함께 그룹화하려면 항목별 힌트를 사용하세요. 그룹의 모든 미디어 항목은 MediaDescription에서 동일한 문자열을 사용하는 추가 번들을 선언해야 합니다. 이 문자열은 그룹의 제목으로 사용되며 현지화할 수 있습니다.

다음 코드 스니펫은 하위 그룹 제목이 "Songs"MediaItem을 만드는 방법을 보여줍니다.

Kotlin

    val EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT = "android.media.browse.CONTENT_STYLE_GROUP_TITLE_HINT"

    private fun createMediaItem(
        mediaId: String,
        folderName: String,
        iconUri: Uri
    ): MediaBrowser.MediaItem {
        val mediaDescriptionBuilder = MediaDescription.Builder()
        mediaDescriptionBuilder.setMediaId(mediaId)
        mediaDescriptionBuilder.setTitle(folderName)
        mediaDescriptionBuilder.setIconUri(iconUri)
        val extras = Bundle()
        extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs")
        mediaDescriptionBuilder.setExtras(extras)
        return MediaBrowser.MediaItem(
            mediaDescriptionBuilder.build(), /* playable or browsable flag*/)
    }
    

자바

    public static final String EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT =
      "android.media.browse.CONTENT_STYLE_GROUP_TITLE_HINT";

    private MediaBrowser.MediaItem createMediaItem(String mediaId, String folderName, Uri iconUri) {
       MediaDescription.Builder mediaDescriptionBuilder = new MediaDescription.Builder();
       mediaDescriptionBuilder.setMediaId(mediaId);
       mediaDescriptionBuilder.setTitle(folderName);
       mediaDescriptionBuilder.setIconUri(iconUri);
       Bundle extras = new Bundle();
       extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs");
       mediaDescriptionBuilder.setExtras(extras);
       return new MediaBrowser.MediaItem(
           mediaDescriptionBuilder.build(), /* playable or browsable flag*/);
    }
    

앱은 함께 그룹화하려는 모든 미디어 항목을 연속된 블록으로 전달해야 합니다. 예를 들어 '노래'와 '앨범'이라는 두 미디어 항목 그룹을 같은 순서로 표시하려고 하며 앱이 다음 순서로 다섯 가지 미디어 항목을 전달했다고 가정해보겠습니다.

  1. extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs")이 있는 미디어 항목 A
  2. extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Albums")이 있는 미디어 항목 B
  3. extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs")이 있는 미디어 항목 C
  4. extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs")이 있는 미디어 항목 D
  5. extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Albums")이 있는 미디어 항목 E

'노래' 그룹과 '앨범' 그룹의 미디어 항목이 연속된 블록으로 함께 보관되지 않으므로 Android Auto 및 Android Automotive OS는 대신 다음 4개 그룹으로 이를 해석합니다.

  • 미디어 항목 A가 포함된 '노래' 그룹 1
  • 미디어 항목 B가 포함된 '앨범' 그룹 2
  • 미디어 항목 C와 D가 포함된 '노래' 그룹 3
  • 미디어 항목 E가 포함된 '앨범' 그룹 4

이러한 항목을 두 그룹으로 표시하려면 앱에서는 대신 다음 순서로 앱을 전달합니다.

  1. extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs")이 있는 미디어 항목 A
  2. extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs")이 있는 미디어 항목 C
  3. extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Songs")이 있는 미디어 항목 D
  4. extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Albums")이 있는 미디어 항목 B
  5. extras.putString(EXTRA_CONTENT_STYLE_GROUP_TITLE_HINT, "Albums")이 있는 미디어 항목 E

추가 메타데이터 표시기 표시

그림 3. 노래와 아티스트를 식별하는 메타데이터가 포함된 재생 뷰

추가 메타데이터 표시기를 포함하여 미디어 브라우저 트리의 콘텐츠와 재생 중 콘텐츠에 관한 한눈에 보는 정보를 제공할 수 있습니다. 탐색 트리 내에서 Android Auto 및 Android Automotive OS는 항목과 연결된 추가 정보를 읽고 특정 상수를 찾아 어떤 표시기를 표시할지 판단합니다. 미디어를 재생하는 동안 Android Auto 및 Android Automotive OS는 미디어 세션의 메타데이터를 읽고 특정 상수를 찾아 어떤 표시기를 표시할지 판단합니다.

다음 코드를 사용하여 앱에서 메타데이터 표시기 상수를 선언하세요.

Kotlin

    // Bundle extra indicating that a song contains explicit content.
    var EXTRA_IS_EXPLICIT = "android.media.IS_EXPLICIT"

    /**
    * Bundle extra indicating that a media item is available offline.
    * Same as MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS.
    */
    var EXTRA_IS_DOWNLOADED = "android.media.extra.DOWNLOAD_STATUS"

    /**
    * Bundle extra value indicating that an item should show the corresponding
    * metadata.
    */
    var EXTRA_METADATA_ENABLED_VALUE:Long = 1

    /**
    * Bundle extra indicating the played state of long-form content (such as podcast
    * episodes or audiobooks).
    */
    var EXTRA_PLAY_COMPLETION_STATE = "android.media.extra.PLAYBACK_STATUS"

    /**
    * Value for EXTRA_PLAY_COMPLETION_STATE that indicates the media item has
    * not been played at all.
    */
    var STATUS_NOT_PLAYED = 0

    /**
    * Value for EXTRA_PLAY_COMPLETION_STATE that indicates the media item has
    * been partially played (i.e. the current position is somewhere in the middle).
    */
    var STATUS_PARTIALLY_PLAYED = 1

    /**
    * Value for EXTRA_PLAY_COMPLETION_STATE that indicates the media item has
    * been completed.
    */
    var STATUS_FULLY_PLAYED = 2
    

자바

    // Bundle extra indicating that a song contains explicit content.
    String EXTRA_IS_EXPLICIT = "android.media.IS_EXPLICIT";

    /**
     * Bundle extra indicating that a media item is available offline.
     * Same as MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS.
     */
    String EXTRA_IS_DOWNLOADED = "android.media.extra.DOWNLOAD_STATUS";

    /**
     * Bundle extra value indicating that an item should show the corresponding
     * metadata.
     */
    long EXTRA_METADATA_ENABLED_VALUE = 1;

    /**
     * Bundle extra indicating the played state of long-form content (such as podcast
     * episodes or audiobooks).
     */
    String EXTRA_PLAY_COMPLETION_STATE = "android.media.extra.PLAYBACK_STATUS";

    /**
     * Value for EXTRA_PLAY_COMPLETION_STATE that indicates the media item has
     * not been played at all.
     */
    int STATUS_NOT_PLAYED = 0;

    /**
     * Value for EXTRA_PLAY_COMPLETION_STATE that indicates the media item has
     * been partially played (i.e. the current position is somewhere in the middle).
     */
    int STATUS_PARTIALLY_PLAYED = 1;

    /**
     * Value for EXTRA_PLAY_COMPLETION_STATE that indicates the media item has
     * been completed.
     */
    int STATUS_FULLY_PLAYED = 2;
    

이 상수를 선언한 후에는 이 상수를 사용하여 메타데이터 표시기를 표시할 수 있습니다. 사용자가 미디어 브라우저 트리를 탐색하는 동안 표시기를 나타나게 하려면 이러한 상수 중 하나 이상을 포함하는 추가 번들을 만들고 이 번들을 MediaDescription.Builder.setExtras() 메서드에 전달합니다.

다음 코드 스니펫은 가사가 음란한 부분 재생 미디어 항목의 표시기를 표시하는 방법을 보여줍니다.

Kotlin

    val extras = Bundle()
    extras.putLong(EXTRA_IS_EXPLICIT, 1)
    extras.putInt(EXTRA_PLAY_COMPLETION_STATE, STATUS_PARTIALLY_PLAYED)
    val description = MediaDescriptionCompat.Builder()
    .setMediaId(/*...*/)
    .setTitle(resources.getString(/*...*/))
    .setExtras(extras)
    .build()
    return MediaBrowserCompat.MediaItem(description, /* flags */)
    

자바

    Bundle extras = new Bundle();
    extras.putLong(EXTRA_IS_EXPLICIT, 1);
    extras.putInt(EXTRA_PLAY_COMPLETION_STATE, STATUS_PARTIALLY_PLAYED);

    MediaDescriptionCompat description = new MediaDescriptionCompat.Builder()
      .setMediaId(/*...*/)
      .setTitle(resources.getString(/*...*/))
      .setExtras(extras)
      .build();
    return new MediaBrowserCompat.MediaItem(description, /* flags */);
    

현재 재생되고 있는 미디어 항목의 표시기를 표시하려면 mediaSessionMediaMetadata.Builder() 메서드에서 EXTRA_IS_EXPLICIT 또는 EXTRA_IS_DOWNLOADED의 Long 값을 선언하면 됩니다. 재생 뷰에는 EXTRA_PLAY_COMPLETION_STATE 표시기를 표시할 수 없습니다.

다음 코드 스니펫은 재생 뷰에 있는 현재 노래의 가사가 음란하고 이 노래가 다운로드된 상태임을 나타내는 방법을 보여줍니다.

Kotlin

    mediaSession.setMetadata(
      MediaMetadata.Builder()
      .putString(
        MediaMetadata.METADATA_KEY_DISPLAY_TITLE, "Song Name")
      .putString(
        MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE, "Artist name")
      .putString(MediaMetadata.METADATA_KEY_ALBUM_ART_URI, albumArtUri.toString())
      .putLong(
        EXTRA_IS_EXPLICIT, EXTRA_METADATA_ENABLED_VALUE)
      .putLong(
        EXTRA_IS_DOWNLOADED, EXTRA_METADATA_ENABLED_VALUE)
      .build())
    

자바

    mediaSession.setMetadata(
        new MediaMetadata.Builder()
            .putString(
                MediaMetadata.METADATA_KEY_DISPLAY_TITLE, "Song Name")
            .putString(
                MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE, "Artist name")
            .putString(MediaMetadata.METADATA_KEY_ALBUM_ART_URI, albumArtUri.toString())
            .putLong(
                EXTRA_IS_EXPLICIT, EXTRA_METADATA_ENABLED_VALUE)
            .putLong(
                EXTRA_IS_DOWNLOADED, EXTRA_METADATA_ENABLED_VALUE)
            .build());
    

사용자가 콘텐츠를 탐색할 수 있도록 앱에서는 사용자가 음성 검색을 할 때마다 검색어와 관련된 검색결과 그룹을 탐색하는 것을 허용할 수 있습니다. Android Auto 및 Android Automotive OS는 이러한 결과를 인터페이스에서 '결과 더보기' 막대로 표시합니다.

그림 4. 관련 검색결과를 볼 수 있는 '결과 더보기' 옵션

탐색 가능한 검색결과를 표시하려면 상수를 만들고 서비스에 있는 onGetRoot() 메서드의 추가 번들에 이 상수를 포함해야 합니다.

다음 코드 스니펫은 onGetRoot() 메서드에서 지원을 사용 설정하는 방법을 보여줍니다.

Kotlin

    // Bundle extra indicating that onSearch() is supported
    val EXTRA_MEDIA_SEARCH_SUPPORTED = "android.media.browse.SEARCH_SUPPORTED"

    @Nullable
    fun onGetRoot(
        @NonNull clientPackageName: String,
        clientUid: Int,
        @Nullable rootHints: Bundle
    ): BrowserRoot {
        val extras = Bundle()
        extras.putBoolean(EXTRA_MEDIA_SEARCH_SUPPORTED, true)
        return BrowserRoot(ROOT_ID, extras)
    }
    

자바

    public static final String EXTRA_MEDIA_SEARCH_SUPPORTED =
       "android.media.browse.SEARCH_SUPPORTED";

    @Nullable
    @Override
    public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid,
       @Nullable Bundle rootHints) {
       Bundle extras = new Bundle();
       extras.putBoolean(EXTRA_MEDIA_SEARCH_SUPPORTED, true);
       return new BrowserRoot(ROOT_ID, extras);
    }
    

검색결과를 제공하려면 미디어 브라우저 서비스에서 onSearch() 메서드를 재정의하세요. Android Auto 및 Android Automotive OS는 사용자가 '결과 더보기' 어포던스를 호출할 때마다 사용자의 검색어를 이 메서드로 전달합니다. 제목 항목을 사용해 서비스의 onSearch() 메서드에서 검색결과를 구성하면 더 쉽게 탐색할 수 있습니다. 예를 들어 앱에서 음악을 재생하는 경우 '앨범', '아티스트' 및 '노래'를 기준으로 검색결과를 구성할 수 있습니다.

다음 코드 스니펫은 onSearch() 메서드의 간단한 구현을 보여줍니다.

Kotlin

    fun onSearch(query: String, extras: Bundle) {
      // Detach from results to unblock the caller (if a search is expensive)
      result.detach()
      object:AsyncTask() {
        internal var searchResponse:ArrayList
        internal var succeeded = false
        protected fun doInBackground(vararg params:Void):Void {
          searchResponse = ArrayList()
          if (doSearch(query, extras, searchResponse))
          {
            succeeded = true
          }
          return null
        }
        protected fun onPostExecute(param:Void) {
          if (succeeded)
          {
            // Sending an empty List informs the caller that there were no results.
            result.sendResult(searchResponse)
          }
          else
          {
            // This invokes onError() on the search callback
            result.sendResult(null)
          }
          return null
        }
      }.execute()
    }
    // Populates resultsToFill with search results. Returns true on success or false on error
    private fun doSearch(
        query: String,
        extras: Bundle,
        resultsToFill: ArrayList
    ): Boolean {
      // Implement this method
    }
    

자바

    @Override
    public void onSearch(final String query, final Bundle extras,
                            Result<ArrayList<MediaItem>> result) {

      // Detach from results to unblock the caller (if a search is expensive)
      result.detach();

      new AsyncTask<Void, Void, Void>() {
        ArrayList<MediaItem> searchResponse;
        boolean succeeded = false;
        @Override
        protected Void doInBackground(Void... params) {
          searchResponse = new ArrayList<MediaItem>();
          if (doSearch(query, extras, searchResponse)) {
            succeeded = true;
          }
          return null;
        }

        @Override
        protected void onPostExecute(Void param) {
          if (succeeded) {
           // Sending an empty List informs the caller that there were no results.
           result.sendResult(searchResponse);
          } else {
            // This invokes onError() on the search callback
            result.sendResult(null);
          }
          return null;
        }
      }.execute()
    }

    /** Populates resultsToFill with search results. Returns true on success or false on error */
    private boolean doSearch(String query, Bundle extras, ArrayList<MediaItem> resultsToFill) {
        // Implement this method
    }
    

재생 컨트롤 사용 설정

Android Auto 및 Android Automotive OS는 서비스의 MediaSessionCompat를 통해 재생 컨트롤 명령어를 전송합니다. 세션을 등록하고 연결된 콜백 메서드를 구현해야 합니다.

미디어 세션 등록

미디어 브라우저 서비스의 onCreate() 메서드에서 MediaSessionCompat를 만들고 setSessionToken()을 호출하여 미디어 세션을 등록하세요.

다음 코드 스니펫은 미디어 세션을 만들고 등록하는 방법을 보여줍니다.

Kotlin

    override fun onCreate() {
        super.onCreate()

        ...
        // Start a new MediaSession
        val session = MediaSessionCompat(this, "session tag").apply {
            // Set a callback object to handle play control requests, which
            // implements MediaSession.Callback
            setCallback(MyMediaSessionCallback())
        }
        sessionToken = session.sessionToken

        ...
    }
    

자바

    public void onCreate() {
        super.onCreate();

        ...
        // Start a new MediaSession
        MediaSessionCompat session = new MediaSessionCompat(this, "session tag");
        setSessionToken(session.getSessionToken());

        // Set a callback object to handle play control requests, which
        // implements MediaSession.Callback
        session.setCallback(new MyMediaSessionCallback());

        ...
    }
    

미디어 세션 객체를 만들 때 재생 컨트롤 요청을 처리하는 데 사용되는 콜백 객체를 설정합니다. 이 콜백 객체는 앱의 MediaSessionCompat.Callback 클래스 구현을 제공하여 만듭니다. 다음 섹션에서는 이 객체를 구현하는 방법을 설명합니다.

재생 명령어 구현

사용자가 앱에서 미디어 항목의 재생을 요청하면 Android Automotive OS와 Android Auto는 앱의 미디어 브라우저 서비스에서 가져온 MediaSessionCompat 객체의 MediaSessionCompat.Callback 클래스를 사용합니다. 사용자가 재생을 일시중지하거나 다음 트랙으로 건너뛰는 등 콘텐츠 재생을 제어하려는 경우 Android Auto 및 Android Automotive OS는 콜백 객체의 메서드 중 하나를 호출합니다.

콘텐츠 재생을 처리하려면 추상 MediaSessionCompat.Callback 클래스를 확장하고 앱에서 지원하는 메서드를 구현해야 합니다.

앱에서 제공하는 콘텐츠 유형에 타당한 다음 콜백 메서드를 모두 구현해야 합니다.

onPrepare()
미디어 소스가 변경되면 호출됩니다. Android Automotive OS에서도 부팅 직후에 이 메소드를 호출합니다. 미디어 앱에서는 이 메서드를 구현해야 합니다.
onPlay()
사용자가 특정 항목을 선택하지 않고 재생을 선택하면 호출됩니다. 앱에서는 기본 콘텐츠를 재생해야 합니다. onPause()로 재생이 일시중지된 경우 앱에서 재생을 다시 시작해야 합니다.

참고: Android Automotive OS 또는 Android Auto가 미디어 브라우저 서비스에 연결될 때 앱에서 자동으로 음악 재생을 시작해서는 안 됩니다. 자세한 내용은 초기 재생 상태 설정을 참조하세요.

onPlayFromMediaId()
사용자가 특정 항목 재생을 선택하면 호출됩니다. 이 메서드에는 미디어 브라우저 서비스가 콘텐츠 계층 구조의 미디어 항목에 할당한 ID가 전달됩니다.
onPlayFromSearch()
사용자가 검색어에서 재생을 선택하면 호출됩니다. 앱에서는 전달된 검색 문자열에 근거하여 적절한 선택을 해야 합니다.
onPause()
사용자가 재생 일시중지를 선택하면 호출됩니다.
onSkipToNext()
사용자가 다음 항목으로 건너뛰기를 선택하면 호출됩니다.
onSkipToPrevious()
사용자가 이전 항목으로 건너뛰기를 선택하면 호출됩니다.
onStop()
사용자가 재생 중지를 선택하면 호출됩니다.

원하는 기능을 제공하려면 앱에서 이러한 메서드를 재정의해야 합니다. 앱에서 지원하지 않는 메서드는 구현할 필요가 없습니다. 예를 들어 앱에서 실시간 스트림(예: 스포츠 방송)을 재생하는 경우 onSkipToNext() 메서드는 구현하기에 적합하지 않으며 대신 onSkipToNext()의 기본 구현을 사용할 수 있습니다.

앱에 특별한 로직이 있어야 자동차 스피커를 통해 콘텐츠를 재생할 수 있는 것은 아닙니다. 앱에서 콘텐츠 재생 요청을 수신하면 일반적으로 사용자의 전화 스피커 또는 헤드폰을 통해 콘텐츠를 재생하는 것과 같은 방식으로 오디오를 재생해야 합니다. Android Auto와 Android Automotive OS는 오디오 콘텐츠를 차량 시스템으로 자동 전송하여 차량 스피커를 통해 재생합니다.

오디오 콘텐츠 재생에 관한 자세한 내용은 미디어 재생, 오디오 재생 관리ExoPlayer를 참조하세요.

표준 재생 작업 설정

Android Auto와 Android Automotive OS는 PlaybackStateCompat 객체에서 사용 설정된 작업에 기반하여 재생 컨트롤을 표시합니다.

기본적으로 앱에서는 다음 작업을 지원해야 합니다.

또한 사용자에게 표시될 수 있는 재생 대기열을 만드는 것이 좋을 수 있습니다. 이렇게 하려면 setQueue()setQueueTitle() 메서드를 호출하고 ACTION_SKIP_TO_QUEUE_ITEM 작업을 사용 설정하고 콜백 onSkipToQueueItem()을 정의해야 합니다.

Android Auto 및 Android Automotive OS는 사용 설정된 각 작업의 버튼을 표시할 뿐 아니라 재생 대기열을 만들기로 한 경우 재생 대기열도 표시합니다.

사용하지 않는 공간 예약

Android Auto와 Android Automotive OS는 ACTION_SKIP_TO_PREVIOUSACTION_SKIP_TO_NEXT 작업을 위해 UI에 공간을 예약합니다. 또한 Android Auto에서는 재생 대기열의 공간을 예약합니다. 앱에서 이러한 기능 중 하나를 지원하지 않는 경우 Android Auto와 Android Automotive OS에서는 이 공간을 사용하여 개발자가 만드는 맞춤 작업을 표시합니다.

이러한 공간을 맞춤 작업으로 채우지 않으려면 이 공간을 예약하여 앱이 상응하는 기능을 지원하지 않을 때마다 Android Auto 및 Android Automotive OS에서 이 공간을 비워두도록 할 수 있습니다. 이렇게 하려면 예약된 각 함수에 상응하는 상수가 포함된 추가 번들로 setExtras() 메서드를 호출하세요. 공간을 예약하려는 각 상수를 true로 설정합니다.

다음 코드 스니펫은 사용하지 않는 공간을 예약하는 데 사용할 수 있는 상수를 보여줍니다.

Kotlin

    // Use these extras to show the transport control buttons for the corresponding actions,
    // even when they are not enabled in the PlaybackState.
    private const val PLAYBACK_SLOT_RESERVATION_SKIP_TO_NEXT =
            "android.media.playback.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_NEXT"
    private const val PLAYBACK_SLOT_RESERVATION_SKIP_TO_PREV =
            "android.media.playback.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_PREVIOUS"
    private const val PLAYBACK_SLOT_RESERVATION_QUEUE =
            "android.media.playback.ALWAYS_RESERVE_SPACE_FOR.ACTION_QUEUE"
    

자바

    // Use these extras to show the transport control buttons for the corresponding actions,
    // even when they are not enabled in the PlaybackState.
    private static final String PLAYBACK_SLOT_RESERVATION_SKIP_TO_NEXT =
        "android.media.playback.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_NEXT";
    private static final String PLAYBACK_SLOT_RESERVATION_SKIP_TO_PREV =
        "android.media.playback.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_PREVIOUS";
    private static final String PLAYBACK_SLOT_RESERVATION_QUEUE =
        "android.media.playback.ALWAYS_RESERVE_SPACE_FOR.ACTION_QUEUE";
    

초기 PlaybackState 설정

Android Auto와 Android Automotive OS가 미디어 브라우저 서비스와 통신할 때 미디어 세션은 PlaybackState를 사용하여 콘텐츠 재생 상태를 전달합니다. Android Automotive OS 또는 Android Auto가 미디어 브라우저 서비스에 연결될 때 앱에서 자동으로 음악 재생을 시작해서는 안 됩니다. 대신 Android Auto 및 Android Automotive OS를 사용하여 차량 상태 또는 사용자 작업에 기반한 재생을 재개하거나 시작하세요.

이 작업을 실행하려면 미디어 세션의 초기 PlaybackStateSTATE_STOPPED, STATE_PAUSED, STATE_NONE 또는 STATE_ERROR로 설정합니다.

맞춤 재생 작업 추가

사용자설정 재생 작업을 추가하면 미디어 앱에서 지원하는 추가 작업을 표시할 수 있습니다. 공간이 있는 경우(예약은 하지 않음) Android는 전송 컨트롤에 사용자설정 작업을 추가합니다. 그렇지 않은 경우에는 맞춤 작업이 더보기 메뉴에 표시됩니다. 맞춤 작업은 PlaybackState에 추가된 순서대로 표시됩니다.

PlaybackStateCompat.Builder 클래스의 addCustomAction() 메서드를 사용하여 이러한 작업을 추가할 수 있습니다.

다음 코드 스니펫은 맞춤 '무선 채널 시작' 작업을 추가하는 방법을 보여줍니다.

Kotlin

    stateBuilder.addCustomAction(
            PlaybackStateCompat.CustomAction.Builder(
                    CUSTOM_ACTION_START_RADIO_FROM_MEDIA,
                    resources.getString(R.string.start_radio_from_media),
                    startRadioFromMediaIcon
            ).run {
                setExtras(customActionExtras)
                build()
            }
    )
    

자바

    stateBuilder.addCustomAction(new PlaybackStateCompat.CustomAction.Builder(
        CUSTOM_ACTION_START_RADIO_FROM_MEDIA,
        resources.getString(R.string.start_radio_from_media), startRadioFromMediaIcon)
        .setExtras(customActionExtras)
        .build());
    

이 메서드의 자세한 예는 GitHub의 범용 Android 뮤직 플레이어 샘플 앱에서 setCustomAction() 메서드를 참조하세요.

맞춤 작업을 만든 후 미디어 세션은 onCustomAction() 메서드를 재정의하여 작업에 응답할 수 있습니다.

다음 코드 스니펫은 앱이 '무선 채널 시작' 작업에 응답할 수 있는 방법을 보여줍니다.

Kotlin

    override fun onCustomAction(action: String, extras: Bundle?) {
        when(action) {
            CUSTOM_ACTION_START_RADIO_FROM_MEDIA -> {
                ...
            }
        }
    }
    

자바

    @Override
    public void onCustomAction(@NonNull String action, Bundle extras) {
        if (CUSTOM_ACTION_START_RADIO_FROM_MEDIA.equals(action)) {
            ...
        }
    }
    

이 메서드의 자세한 예는 GitHub의 범용 Android 뮤직 플레이어 샘플 앱에서 onCustomAction 메서드를 참조하세요.

맞춤 작업 아이콘

개발자가 만드는 각 맞춤 작업에는 아이콘 리소스가 필요합니다. 자동차의 앱은 다양한 화면 크기와 밀도에서 실행될 수 있으므로 개발자가 제공하는 아이콘은 벡터 드로어블이어야 합니다. 벡터 드로어블을 사용하면 세부정보 손실 없이 자산을 확장할 수 있습니다. 또한 벡터 드로어블을 사용하면 해상도가 낮을 때 가장자리와 모서리를 픽셀 경계에 맞추기도 쉬워집니다.

사용하지 않는 작업에 대체 아이콘 스타일 제공

현재 컨텍스트에서 사용자설정 작업을 사용할 수 없는 경우 사용자설정 작업 아이콘을 작업이 사용 중지 되었음을 나타내는 대체 아이콘으로 바꾸세요.

그림 5. 샘플 오프 스타일 사용자설정 작업 아이콘.

음성 액션 지원

미디어 앱에서는 주의를 분산하는 요소를 최소화하는 안전하고 편리한 환경을 운전자에게 제공할 수 있는 음성 작업을 지원해야 합니다. 예를 들어 앱에서 이미 하나의 미디어 항목을 재생하고 있다면 사용자는 "보헤미안 랩소디 재생"이라고 말하여 차량 디스플레이를 보거나 터치하지 않고도 다른 노래를 재생하라고 앱에 알려줄 수 있습니다.

앱에서 음성 지원 재생 작업을 구현하는 방법에 관한 자세한 예는 Google 어시스턴트 및 미디어 앱을 참조하세요.

주의 분산 방지 수단 구현

Android Auto 사용 중에는 사용자의 전화가 자동차의 스피커에 연결되어 있으므로 운전자의 주의가 분산되지 않도록 추가 예방 조치를 취해야 합니다.

운전 모드 감지

사용자가 의식적으로 재생을 시작하지 않는 한(예: 앱에서 재생을 누름) Android Auto 미디어 앱에서는 차량 스피커를 통한 오디오 재생을 시작해서는 안 됩니다. 미디어 앱에서 사용자가 예약한 경보가 울리더라도 자동차 스피커를 통한 음악 재생을 시작해서는 안 됩니다. 이 요구사항을 충족하려면 앱에서 오디오를 재생하기 전에 전화가 운전 모드에 있는지 확인해야 합니다. 앱에서 getCurrentModeType() 메서드를 호출하여 전화가 운전 모드인지 확인할 수 있습니다.

사용자의 전화가 운전 모드에 있는 경우 경보를 지원하는 미디어 앱에서는 다음 중 한 가지 작업을 해야 합니다.

  • 경보 사용을 중지합니다.
  • STREAM_ALARM을 통해 경보를 재생하고 전화 화면에 경보를 사용 중지할 수 있는 UI를 제공합니다.

다음 코드 스니펫은 앱이 운전 모드에서 실행 중인지 확인하는 방법을 보여줍니다.

Kotlin

    fun isCarUiMode(c: Context): Boolean {
        val uiModeManager = c.getSystemService(Context.UI_MODE_SERVICE) as UiModeManager
        return if (uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_CAR) {
            LogHelper.d(TAG, "Running in Car mode")
            true
        } else {
            LogHelper.d(TAG, "Running in a non-Car mode")
            false
        }
    }
    

자바

     public static boolean isCarUiMode(Context c) {
          UiModeManager uiModeManager = (UiModeManager) c.getSystemService(Context.UI_MODE_SERVICE);
          if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR) {
                LogHelper.d(TAG, "Running in Car mode");
                return true;
          } else {
              LogHelper.d(TAG, "Running in a non-Car mode");
              return false;
            }
      }
    

미디어 광고 처리

기본적으로 Android Auto에서는 오디오 재생 세션 중에 미디어 메타데이터가 변경되면 알림을 표시합니다. 미디어 앱이 음악 재생에서 광고 실행으로 전환할 때 사용자에게 알림을 표시하는 것은 사용자의 주의를 분산하므로 불필요한 일입니다. 이 경우 Android Auto에서 알림을 표시하지 않도록하려면 다음 코드 스니펫과 같이 미디어 메타데이터 키 android.media.metadata.ADVERTISEMENT1로 설정해야 합니다.

Kotlin

    const val EXTRA_METADATA_ADVERTISEMENT = "android.media.metadata.ADVERTISEMENT"
    ...
    override fun onPlayFromMediaId(mediaId: String, extras: Bundle?) {
        MediaMetadataCompat.Builder().apply {
            // ...
            if (isAd(mediaId)) {
                putLong(EXTRA_METADATA_ADVERTISEMENT, 1)
            }
            // ...
            mediaSession.setMetadata(build())
        }
    }
    

자바

    public static final String EXTRA_METADATA_ADVERTISEMENT =
        "android.media.metadata.ADVERTISEMENT";

    @Override
    public void onPlayFromMediaId(String mediaId, Bundle extras) {
        MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder();
        // ...
        if (isAd(mediaId)) {
            builder.putLong(EXTRA_METADATA_ADVERTISEMENT, 1);
        }
        // ...
        mediaSession.setMetadata(builder.build());
    }
    

일반 오류 처리

앱에 오류가 발생하면 재생 상태를 STATE_ERROR로 설정하고 setErrorMessage() 메서드를 사용하여 오류 메시지를 제공해야 합니다. 오류 메시지는 사용자에게 표시되는 것이므로 사용자의 현재 언어에 맞게 현지화해야 합니다. 그러면 Android Auto와 Android Automotive OS에서 사용자에게 오류 메시지를 표시할 수 있습니다.

오류 상태에 관한 자세한 내용은 미디어 세션 작업: 상태 및 오류를 참조하세요.

Android Auto 사용자가 오류를 해결하기 위해 전화 앱을 열어야 하는 경우 메시지에서는 이러한 정보를 사용자에게 제공해야 합니다. 예를 들어 오류 메시지에 '로그인하세요' 대신에 '[앱 이름]에 로그인하세요'라고 표시해야 합니다.

기타 자료