Android Auto و Android Automotive OS به شما کمک میکنند محتوای برنامه رسانهای خود را به کاربرانی که در خودرویشان هستند ارائه دهید. یک برنامه رسانه برای خودروها باید یک سرویس مرورگر رسانه ارائه دهد تا Android Auto و Android Automotive OS یا برنامه دیگری با مرورگر رسانه بتواند محتوای شما را کشف و نمایش دهد.
این راهنما فرض میکند که شما قبلاً یک برنامه رسانه دارید که صدا را روی تلفن پخش میکند و برنامه رسانه شما با معماری برنامه رسانه Android مطابقت دارد.
این راهنما اجزای مورد نیاز MediaBrowserService
و MediaSession
را که برنامه شما برای کار بر روی Android Auto یا Android Automotive OS به آن نیاز دارد، توضیح میدهد. پس از تکمیل زیرساخت رسانه اصلی، میتوانید پشتیبانی از Android Auto را اضافه کنید و پشتیبانی از Android Automotive OS را به برنامه رسانه خود اضافه کنید .
قبل از شروع
- مستندات Android Media API را مرور کنید.
- بررسی ایجاد برنامه های رسانه ای برای راهنمایی طراحی.
- اصطلاحات و مفاهیم کلیدی فهرست شده در این بخش را مرور کنید.
اصطلاحات و مفاهیم کلیدی
- سرویس مرورگر رسانه
- یک سرویس Android اجرا شده توسط برنامه رسانه شما که با
MediaBrowserServiceCompat
API مطابقت دارد. برنامه شما از این سرویس برای افشای محتوای خود استفاده می کند. - مرورگر رسانه
- یک API که توسط برنامه های رسانه برای کشف سرویس های مرورگر رسانه و نمایش محتوای آنها استفاده می شود. Android Auto و Android Automotive OS از یک مرورگر رسانه برای یافتن سرویس مرورگر رسانه برنامه شما استفاده می کنند.
- آیتم رسانه ای
مرورگر رسانه محتوای خود را در درختی از اشیاء
MediaItem
سازماندهی می کند. یک آیتم رسانه میتواند یکی یا هر دو پرچمهای زیر را داشته باشد:-
FLAG_PLAYABLE
: نشان می دهد که مورد یک برگ روی درخت محتوا است. این آیتم یک جریان صدا را نشان می دهد، مانند یک آهنگ در یک آلبوم، یک فصل از یک کتاب صوتی، یا یک قسمت از یک پادکست. -
FLAG_BROWSABLE
: نشان می دهد که مورد یک گره در درخت محتوا است و دارای فرزندان است. به عنوان مثال، آیتم نشان دهنده یک آلبوم است و فرزندان آن آهنگ های موجود در آلبوم هستند.
یک آیتم رسانه ای که هم قابل مرور و هم قابل پخش است مانند یک لیست پخش عمل می کند. میتوانید خود مورد را برای پخش همه فرزندانش انتخاب کنید، یا میتوانید فرزندان آن را مرور کنید.
-
- خودرو بهینه شده است
فعالیتی برای یک برنامه سیستم عامل Android Automotive که از دستورالعمل های طراحی سیستم عامل Android Automotive پیروی می کند. رابط این فعالیت ها توسط سیستم عامل Android Automotive ترسیم نشده است، بنابراین باید مطمئن شوید که برنامه شما از دستورالعمل های طراحی پیروی می کند. به طور معمول، این شامل اهداف ضربه ای بزرگتر و اندازه فونت، پشتیبانی از حالت های روز و شب، و نسبت کنتراست بالاتر است.
رابطهای کاربری بهینهسازی شده برای خودرو فقط زمانی مجاز به نمایش هستند که محدودیتهای تجربه کاربر خودرو (CUXR) در کار نباشد، زیرا این رابطها میتوانند به توجه یا تعامل گستردهای از جانب کاربر نیاز داشته باشند. CUXR ها زمانی که خودرو متوقف یا پارک شده است کارایی ندارند اما زمانی که خودرو در حال حرکت است همیشه فعال هستند.
نیازی به طراحی فعالیتها برای Android Auto ندارید، زیرا Android Auto با استفاده از اطلاعات سرویس مرورگر رسانه شما، رابط بهینهسازی شده برای وسیله نقلیه خود را طراحی میکند.
فایل های مانیفست برنامه خود را پیکربندی کنید
قبل از اینکه بتوانید سرویس مرورگر رسانه خود را ایجاد کنید، باید فایل های مانیفست برنامه خود را پیکربندی کنید.
سرویس مرورگر رسانه خود را اعلام کنید
Android Auto و Android Automotive OS از طریق سرویس مرورگر رسانه شما برای مرور موارد رسانه به برنامه شما متصل می شوند. سرویس مرورگر رسانه خود را در مانیفست خود اعلام کنید تا به Android Auto و Android Automotive OS این سرویس را کشف کرده و به برنامه شما متصل شوند.
قطعه کد زیر نحوه اعلام سرویس مرورگر رسانه خود را در مانیفست نشان می دهد. این کد را در فایل مانیفست ماژول سیستم عامل Android Automotive خود و در فایل مانیفست برنامه تلفن خود قرار دهید.
<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 می توانند برای نمایش برنامه شما در رابط کاربری سیستم استفاده کنند. دو نوع نماد مورد نیاز است:
- نماد راهانداز
- نماد اسناد
نماد راهانداز
نماد راهانداز برنامه شما را در رابط کاربری سیستم، مانند راهانداز و سینی نمادها نشان میدهد. میتوانید مشخص کنید که میخواهید از نماد برنامه تلفن همراه خود برای نشان دادن برنامه رسانه خودرو خود با استفاده از مانیفست زیر استفاده کنید:
<application
...
android:icon="@mipmap/ic_launcher"
...
/>
برای استفاده از نمادی متفاوت از نماد برنامه تلفن همراه خود، ویژگی android:icon
در عنصر <service>
سرویس مرورگر رسانه خود در مانیفست تنظیم کنید:
<application>
...
<service
...
android:icon="@mipmap/auto_launcher"
...
/>
</application>
نماد اسناد
نماد انتساب در مکان هایی استفاده می شود که محتوای رسانه اولویت دارد، مانند کارت های رسانه. استفاده مجدد از نماد کوچک مورد استفاده برای اعلان ها را در نظر بگیرید. این نماد باید تک رنگ باشد. میتوانید نمادی را که برای نمایش برنامهتان استفاده میشود، با استفاده از اعلان مانیفست زیر مشخص کنید:
<application>
...
<meta-data
android:name="androidx.car.app.TintableAttributionIcon"
android:resource="@drawable/ic_status_icon" />
...
</application>
سرویس مرورگر رسانه خود را ایجاد کنید
شما با گسترش کلاس MediaBrowserServiceCompat
یک سرویس مرورگر رسانه ایجاد می کنید. هر دو سیستم عامل Android Auto و Android Automotive می توانند از خدمات شما برای انجام کارهای زیر استفاده کنند:
- سلسله مراتب محتوای برنامه خود را برای ارائه منو به کاربر مرور کنید.
- برای کنترل پخش صدا، توکن شی
MediaSessionCompat
برنامه خود را دریافت کنید.
همچنین میتوانید از سرویس مرورگر رسانه خود استفاده کنید تا به سایر مشتریان اجازه دهید به محتوای رسانه برنامه شما دسترسی داشته باشند. این کلاینتهای رسانه ممکن است برنامههای دیگری در تلفن کاربر باشند یا میتوانند مشتریان راه دور دیگری باشند.
گردش کار سرویس مرورگر رسانه
این بخش نحوه تعامل سیستم عامل Android Automotive و Android Auto با سرویس مرورگر رسانه شما را در طول یک گردش کار معمولی کاربر توضیح می دهد.
- کاربر برنامه شما را در سیستم عامل Android Automotive یا Android Auto راه اندازی می کند.
- سیستم عامل Android Automotive یا Android Auto با استفاده از روش
onCreate()
با سرویس مرورگر رسانه برنامه شما تماس می گیرد. در پیاده سازی متدonCreate()
باید یک شیMediaSessionCompat
و آبجکت برگشت آن را ایجاد و ثبت کنید. - سیستم عامل Android Automotive یا Android Auto روش
onGetRoot()
سرویس شما را فراخوانی می کند تا آیتم رسانه ریشه در سلسله مراتب محتوای شما را دریافت کند. مورد رسانه ریشه نمایش داده نمی شود. در عوض، برای بازیابی محتوای بیشتر از برنامه شما استفاده می شود. - سیستم عامل Android Automotive یا Android Auto روش
onLoadChildren()
سرویس شما را فراخوانی می کند تا فرزندان آیتم رسانه ریشه را دریافت کند. سیستمعامل Android Automotive و Android Auto این موارد رسانهای را به عنوان سطح بالای آیتمهای محتوا نمایش میدهند. برای اطلاعات بیشتر در مورد آنچه سیستم در این سطح انتظار دارد، به ساختار منوی ریشه در این صفحه مراجعه کنید. - اگر کاربر یک مورد رسانه قابل مرور را انتخاب کند، متد
onLoadChildren()
سرویس شما دوباره فراخوانی می شود تا فرزندان آیتم منوی انتخاب شده را بازیابی کند. - اگر کاربر یک مورد رسانه قابل پخش را انتخاب کند، سیستم عامل Android Automotive یا Android Auto روش بازگشت تماس جلسه رسانه مناسب را برای انجام آن عمل فراخوانی می کند.
- اگر توسط برنامه شما پشتیبانی می شود، کاربر می تواند محتوای شما را نیز جستجو کند. در این حالت، سیستم عامل Android Automotive یا Android Auto روش
onSearch()
سرویس شما را فراخوانی می کند.
سلسله مراتب محتوای خود را بسازید
Android Auto و Android Automotive OS با سرویس مرورگر رسانه برنامه شما تماس می گیرند تا بفهمند چه محتوایی در دسترس است. برای پشتیبانی از آن باید دو روش را در سرویس مرورگر رسانه خود پیاده سازی کنید: onGetRoot()
و onLoadChildren()
پیاده سازی onGetRoot
متد onGetRoot()
سرویس شما اطلاعات مربوط به گره ریشه سلسله مراتب محتوای شما را برمی گرداند. Android Auto و Android Automotive OS از این گره ریشه برای درخواست بقیه محتوای شما با استفاده از روش onLoadChildren()
استفاده می کنند.
قطعه کد زیر یک پیاده سازی ساده از متد onGetRoot()
را نشان می دهد:
کاتلین
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); }
برای مثال دقیقتر از این روش، متد onGetRoot()
را در برنامه نمونه Universal Android Music Player در GitHub ببینید.
افزودن اعتبار سنجی بسته برای onGetRoot()
هنگامی که با متد onGetRoot()
سرویس شما تماس برقرار می شود، بسته فراخوانی اطلاعات شناسایی را به سرویس شما ارسال می کند. سرویس شما می تواند از این اطلاعات برای تصمیم گیری در مورد اینکه آیا آن بسته می تواند به محتوای شما دسترسی داشته باشد استفاده کند. برای مثال، میتوانید با مقایسه clientPackageName
با لیست مجاز خود و تأیید گواهی استفاده شده برای امضای APK بسته، دسترسی به محتوای برنامه خود را به فهرستی از بستههای تأییدشده محدود کنید. اگر بسته قابل تأیید نیست، برای جلوگیری از دسترسی به محتوای خود null
را برگردانید.
برای ارائه برنامههای سیستمی، مانند Android Auto و Android Automotive OS، با دسترسی به محتوای شما، سرویس شما باید همیشه یک BrowserRoot
غیر تهی را زمانی که این برنامههای سیستم متد onGetRoot()
را فراخوانی میکنند، برگرداند. امضای برنامه سیستم سیستم عامل Android Automotive بسته به نوع و مدل خودرو می تواند متفاوت باشد، بنابراین باید به اتصالات همه برنامه های سیستم اجازه دهید تا از سیستم عامل Android Automotive به شدت پشتیبانی کنند.
قطعه کد زیر نشان می دهد که چگونه سرویس شما می تواند تأیید کند که بسته تماس یک برنامه سیستمی است:
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
}
این قطعه کد گزیده ای از کلاس PackageValidator
در برنامه نمونه برنامه پخش موسیقی جهانی اندروید در GitHub است. برای مثال دقیق تر از نحوه پیاده سازی اعتبار سنجی بسته برای متد onGetRoot()
سرویس خود، آن کلاس را ببینید.
علاوه بر اجازه دادن به برنامههای سیستم، باید به دستیار Google اجازه دهید به MediaBrowserService
شما متصل شود. توجه داشته باشید که Google Assistant دارای نامهای بسته جداگانه برای تلفن است که شامل Android Auto و سیستم عامل Android Automotive است.
پیاده سازی onLoadChildren()
پس از دریافت شی root node، Android Auto و Android Automotive OS با فراخوانی onLoadChildren()
روی شی گره ریشه، یک منوی سطح بالایی ایجاد می کنند تا فرزندان آن را دریافت کنند. برنامه های سرویس گیرنده با فراخوانی همین روش با استفاده از اشیاء گره فرزند، زیر منوها را می سازند.
هر گره در سلسله مراتب محتوای شما توسط یک شی MediaBrowserCompat.MediaItem
نشان داده می شود. هر یک از این موارد رسانه ای با یک رشته ID منحصر به فرد شناسایی می شوند. برنامه های سرویس گیرنده با این رشته های شناسه به عنوان توکن های غیر شفاف برخورد می کنند. هنگامی که یک برنامه مشتری می خواهد به یک منوی فرعی مرور کند یا یک آیتم رسانه را پخش کند، رمز را ارسال می کند. برنامه شما مسئول مرتبط کردن توکن با آیتم رسانه ای مناسب است.
قطعه کد زیر یک پیاده سازی ساده از متد onLoadChildren()
را نشان می دهد:
کاتلین
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 whether 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 whether 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); }
برای مثال کاملی از این روش، متد onLoadChildren()
را در برنامه نمونه Universal Android Music Player در GitHub ببینید.
ساختار منوی ریشه
Android Auto و Android Automotive OS محدودیت های خاصی در مورد ساختار منوی ریشه دارند. اینها از طریق راهنمایی ریشه به MediaBrowserService
منتقل می شوند، که می توانند از طریق آرگومان Bundle
که به onGetRoot()
ارسال می شود، خوانده شوند. پیروی از این نکات به سیستم اجازه می دهد تا محتوای ریشه را به صورت بهینه به عنوان برگه های ناوبری نمایش دهد. اگر این نکات را دنبال نکنید، ممکن است برخی از محتوای ریشه حذف شود یا توسط سیستم کمتر قابل شناسایی باشد. دو نکته ارسال می شود:
- محدودیت در تعداد فرزندان ریشه : برای اکثر موارد، می توانید انتظار داشته باشید که این تعداد چهار باشد. این بدان معنی است که بیش از چهار برگه را نمی توان نشان داد.
- پرچمهای پشتیبانی شده در فرزندان ریشه : میتوانید انتظار داشته باشید که این مقدار
MediaItem#FLAG_BROWSABLE
باشد. این بدان معنی است که فقط موارد قابل مرور - نه موارد قابل پخش - می توانند به عنوان برگه نشان داده شوند.
از کد زیر برای خواندن نکات ریشه مربوطه استفاده کنید:
کاتلین
import androidx.media.utils.MediaConstants // Later, in your MediaBrowserServiceCompat. override fun onGetRoot( clientPackageName: String, clientUid: Int, rootHints: Bundle ): BrowserRoot { val maximumRootChildLimit = rootHints.getInt( MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_LIMIT, /* defaultValue= */ 4) val supportedRootChildFlags = rootHints.getInt( MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_SUPPORTED_FLAGS, /* defaultValue= */ MediaItem.FLAG_BROWSABLE) // Rest of method... }
جاوا
import androidx.media.utils.MediaConstants; // Later, in your MediaBrowserServiceCompat. @Override public BrowserRoot onGetRoot( String clientPackageName, int clientUid, Bundle rootHints) { int maximumRootChildLimit = rootHints.getInt( MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_LIMIT, /* defaultValue= */ 4); int supportedRootChildFlags = rootHints.getInt( MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_SUPPORTED_FLAGS, /* defaultValue= */ MediaItem.FLAG_BROWSABLE); // Rest of method... }
میتوانید منطق ساختار سلسله مراتب محتوای خود را بر اساس مقادیر این نکات منشعب کنید، به خصوص اگر سلسله مراتب شما در میان ادغامهای MediaBrowser
خارج از Android Auto و Android Automotive OS متفاوت باشد. به عنوان مثال، اگر به طور معمول یک آیتم قابل پخش ریشه را نشان می دهید، ممکن است بخواهید به جای آن، به دلیل ارزش اشاره پرچم های پشتیبانی شده، آن را زیر یک آیتم قابل مرور ریشه قرار دهید.
جدا از نکات ریشه، چند دستورالعمل اضافی وجود دارد که باید رعایت کنید تا اطمینان حاصل شود که برگه ها بهینه ارائه می شوند:
- برای هر مورد برگه، نمادهای تک رنگ، ترجیحاً سفید را ارائه دهید.
- برای هر مورد برگه برچسب های کوتاه اما معنی دار ارائه کنید. کوتاه نگه داشتن برچسب ها احتمال کوتاه شدن رشته ها را کاهش می دهد.
نمایش آثار هنری رسانه ای
آثار هنری برای موارد رسانه باید به عنوان یک URI محلی با استفاده از ContentResolver.SCHEME_CONTENT
یا ContentResolver.SCHEME_ANDROID_RESOURCE
ارسال شوند. این URI محلی باید به یک بیت مپ یا یک بردار قابل ترسیم در منابع برنامه حل شود. برای اشیاء MediaDescriptionCompat
که مواردی را در سلسله مراتب محتوا نشان میدهند، URI را از طریق setIconUri()
عبور دهید. برای اشیاء MediaMetadataCompat
که آیتم در حال پخش را نشان میدهند، با استفاده از هر یک از کلیدهای زیر، URI را از طریق putString()
عبور دهید:
-
MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI
-
MediaMetadataCompat.METADATA_KEY_ART_URI
-
MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI
مراحل زیر نحوه بارگیری هنر از یک URI وب و نمایش آن از طریق یک URI محلی را توضیح می دهد. برای مثال کاملتر، پیادهسازی openFile()
و متدهای اطراف آن را در برنامه نمونه برنامه پخش موسیقی جهانی اندروید ببینید.
یک
content://
URI مطابق با URI وب بسازید. سرویس مرورگر رسانه و جلسه رسانه این URI محتوا را به Android Auto و Android Automotive OS منتقل می کند.کاتلین
fun Uri.asAlbumArtContentURI(): Uri { return Uri.Builder() .scheme(ContentResolver.SCHEME_CONTENT) .authority(CONTENT_PROVIDER_AUTHORITY) .appendPath(this.getPath()) // Make sure you trust the URI .build() }
جاوا
public static Uri asAlbumArtContentURI(Uri webUri) { return new Uri.Builder() .scheme(ContentResolver.SCHEME_CONTENT) .authority(CONTENT_PROVIDER_AUTHORITY) .appendPath(webUri.getPath()) // Make sure you trust the URI! .build(); }
در اجرای
ContentProvider.openFile()
، بررسی کنید که آیا فایلی برای URI مربوطه وجود دارد یا خیر. اگر نه، فایل تصویر را دانلود و کش کنید. قطعه کد زیر از Glide استفاده می کند.کاتلین
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? { val context = this.context ?: return null val file = File(context.cacheDir, uri.path) if (!file.exists()) { val remoteUri = Uri.Builder() .scheme("https") .authority("my-image-site") .appendPath(uri.path) .build() val cacheFile = Glide.with(context) .asFile() .load(remoteUri) .submit() .get(DOWNLOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS) cacheFile.renameTo(file) file = cacheFile } return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY) }
جاوا
@Nullable @Override public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException { Context context = this.getContext(); File file = new File(context.getCacheDir(), uri.getPath()); if (!file.exists()) { Uri remoteUri = new Uri.Builder() .scheme("https") .authority("my-image-site") .appendPath(uri.getPath()) .build(); File cacheFile = Glide.with(context) .asFile() .load(remoteUri) .submit() .get(DOWNLOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS); cacheFile.renameTo(file); file = cacheFile; } return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); }
برای جزئیات بیشتر در مورد ارائه دهندگان محتوا، به ایجاد یک ارائه دهنده محتوا مراجعه کنید.
اعمال سبک های محتوا
پس از ساختن سلسله مراتب محتوای خود با استفاده از آیتم های قابل مرور یا قابل پخش، می توانید سبک های محتوایی را اعمال کنید که نحوه نمایش آن موارد را در خودرو تعیین می کند.
می توانید از سبک های محتوای زیر استفاده کنید:
- فهرست موارد
این سبک محتوا عناوین و ابرداده ها را بر تصاویر اولویت می دهد.
- آیتم های شبکه
این سبک محتوا، تصاویر را بر عناوین و ابرداده ها در اولویت قرار می دهد.
سبک های محتوای پیش فرض را تنظیم کنید
میتوانید پیشفرضهای جهانی را برای نحوه نمایش آیتمهای رسانه خود با گنجاندن ثابتهای خاصی در بسته اضافی BrowserRoot
متد onGetRoot()
سرویس خود تنظیم کنید. Android Auto و Android Automotive OS این بسته را مطالعه کرده و برای تعیین سبک مناسب به دنبال آن ثابتها بگردید.
موارد اضافی زیر را می توان به عنوان کلید در بسته استفاده کرد:
-
DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE
: یک راهنمایی ارائه برای همه موارد قابل مرور در درخت مرور را نشان می دهد. -
DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE
: یک راهنمایی ارائه برای همه موارد قابل پخش در درخت مرور را نشان می دهد.
کلیدها می توانند به مقادیر ثابت اعداد صحیح زیر نگاشت شوند تا بر نمایش آن موارد تأثیر بگذارند:
-
DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM
: موارد مربوطه به عنوان موارد فهرست ارائه میشوند. -
DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM
: موارد مربوطه به عنوان موارد شبکه ارائه می شوند. -
DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM
: موارد مربوطه به عنوان موارد فهرست "رده" ارائه می شوند. این موارد مانند آیتم های لیست معمولی هستند با این تفاوت که حاشیه ها در اطراف نمادهای آیتم ها اعمال می شوند، زیرا نمادها وقتی کوچک هستند بهتر به نظر می رسند. نمادها باید بردار قابل ترسیم باشند. انتظار می رود این راهنمایی فقط برای موارد قابل مرور ارائه شود. -
DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_GRID_ITEM
: موارد مربوطه به عنوان آیتمهای شبکه «رده» ارائه میشوند. این موارد مانند آیتمهای شبکهای معمولی هستند، با این تفاوت که حاشیهها در اطراف نمادهای آیتمها اعمال میشوند، زیرا آیکونها وقتی کوچک هستند بهتر به نظر میرسند. نمادها باید بردار قابل ترسیم باشند. انتظار می رود این راهنمایی فقط برای موارد قابل مرور ارائه شود.
قطعه کد زیر نحوه تنظیم سبک محتوای پیشفرض برای موارد قابل مرور به شبکه و موارد قابل پخش در لیست را نشان میدهد:
کاتلین
import androidx.media.utils.MediaConstants @Nullable override fun onGetRoot( @NonNull clientPackageName: String, clientUid: Int, @Nullable rootHints: Bundle ): BrowserRoot { val extras = Bundle() extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM) return BrowserRoot(ROOT_ID, extras) }
جاوا
import androidx.media.utils.MediaConstants; @Nullable @Override public BrowserRoot onGetRoot( @NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) { Bundle extras = new Bundle(); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM); return new BrowserRoot(ROOT_ID, extras); }
سبک محتوای هر مورد را تنظیم کنید
Content Style API به شما امکان میدهد سبک محتوای پیشفرض را برای فرزندان هر آیتم رسانه قابل مرور و همچنین هر مورد رسانهای لغو کنید.
برای لغو پیشفرض برای فرزندان یک مورد رسانه قابل مرور، یک بسته اضافی در MediaDescription
مورد رسانه ایجاد کنید و همان نکات ذکر شده قبلی را اضافه کنید. DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE
برای کودکان قابل بازی آن مورد اعمال می شود، در حالی که DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE
برای کودکان قابل مرور آن مورد اعمال می شود.
برای لغو پیشفرض برای خود یک مورد رسانه خاص، نه فرزندان آن، یک بسته اضافی در MediaDescription
مورد رسانه ایجاد کنید و یک راهنمایی با کلید DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM
اضافه کنید. از همان مقادیری که قبلا توضیح داده شد برای مشخص کردن ارائه آن مورد استفاده کنید.
قطعه کد زیر نحوه ایجاد یک MediaItem
قابل مرور را نشان می دهد که سبک محتوای پیش فرض را هم برای خودش و هم برای فرزندانش لغو می کند. خود را بهعنوان آیتم فهرست دستهبندی، فرزندان قابل مرور آن به عنوان آیتمهای فهرست، و کودکان قابل پخش آن به عنوان آیتمهای شبکهای استایل میکند:
کاتلین
import androidx.media.utils.MediaConstants 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( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM) mediaDescriptionBuilder.setExtras(extras) return MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE) }
جاوا
import androidx.media.utils.MediaConstants; 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( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM); mediaDescriptionBuilder.setExtras(extras); return new MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE); }
موارد را با استفاده از نکات عنوان گروه بندی کنید
برای گروه بندی آیتم های رسانه ای مرتبط با هم، از یک اشاره برای هر مورد استفاده می کنید. هر مورد رسانه ای در یک گروه باید یک بسته اضافی را در MediaDescription
خود اعلام کند که شامل یک نقشه با کلید DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE
و یک مقدار رشته یکسان است. این رشته را که به عنوان عنوان گروه استفاده می شود، محلی کنید.
قطعه کد زیر نحوه ایجاد یک MediaItem
با عنوان زیرگروه "Songs"
را نشان می دهد:
کاتلین
import androidx.media.utils.MediaConstants 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( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs") mediaDescriptionBuilder.setExtras(extras) return MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), /* playable or browsable flag*/) }
جاوا
import androidx.media.utils.MediaConstants; 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( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs"); mediaDescriptionBuilder.setExtras(extras); return new MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), /* playable or browsable flag*/); }
برنامه شما باید همه آیتم های رسانه ای را که می خواهید با هم به عنوان یک بلوک پیوسته گروه بندی کنید، ارسال کند. برای مثال، فرض کنید که میخواهید دو گروه از آیتمهای رسانه، «آهنگها» و «آلبومها» را به ترتیب نمایش دهید و برنامه شما در پنج آیتم رسانه به ترتیب زیر ارسال شود:
- مورد رسانه A با
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- مورد رسانه B با
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )
- مورد رسانه C با
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- مورد رسانه D با
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- مورد رسانه E با
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )
از آنجایی که آیتمهای رسانه گروه «آهنگها» و گروه «آلبومها» با هم در بلوکهای پیوسته نگهداری نمیشوند، Android Auto و Android Automotive OS این را به چهار گروه زیر تعبیر میکنند:
- گروه 1 به نام "ترانه ها" حاوی آیتم رسانه ای A
- گروه 2 به نام "آلبوم" حاوی آیتم رسانه ای B
- گروه 3 به نام "آهنگ ها" حاوی آیتم های رسانه ای C و D
- گروه 4 به نام "آلبوم" حاوی آیتم رسانه ای E
برای نمایش این موارد در دو گروه، برنامه شما باید موارد رسانه را به ترتیب زیر ارسال کند:
- مورد رسانه A با
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- مورد رسانه C با
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- مورد رسانه D با
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- مورد رسانه B با
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )
- مورد رسانه E با
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )
نشانگرهای فراداده اضافی را نمایش دهید
می توانید شاخص های فراداده اضافی را برای ارائه اطلاعات در یک نگاه برای محتوا در درخت مرورگر رسانه و در حین پخش اضافه کنید. در درخت مرور، Android Auto و Android Automotive OS موارد اضافی مرتبط با یک مورد را میخوانند و به دنبال ثابتهای خاصی میگردند تا مشخص کنند کدام نشانگرها باید نمایش داده شوند. در حین پخش رسانه، Android Auto و Android Automotive OS فرادادههای جلسه رسانه را میخوانند و به دنبال ثابتهای خاصی میگردند تا نشانگرهایی را برای نمایش تعیین کنند.
ثابتهای زیر را میتوان هم در توضیحات اضافی MediaItem
و هم در موارد اضافی MediaMetadata
استفاده کرد:
-
EXTRA_DOWNLOAD_STATUS
: وضعیت دانلود یک مورد را نشان می دهد. از این ثابت به عنوان کلید استفاده کنید. ثابت های طولانی زیر مقادیر ممکن هستند:-
STATUS_DOWNLOADED
: مورد به طور کامل دانلود شده است. -
STATUS_DOWNLOADING
: مورد در حال دانلود است. -
STATUS_NOT_DOWNLOADED
: مورد دانلود نشده است.
-
-
METADATA_KEY_IS_EXPLICIT
: نشان می دهد که آیا مورد حاوی محتوای صریح است یا خیر. برای نشان دادن صریح بودن یک مورد، از این ثابت به عنوان کلید وMETADATA_VALUE_ATTRIBUTE_PRESENT
طولانی به عنوان مقدار استفاده کنید.
ثابت های زیر را فقط می توان در توضیحات اضافی MediaItem
استفاده کرد:
-
DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS
: وضعیت تکمیل محتوای طولانی، مانند قسمت های پادکست یا کتاب های صوتی را نشان می دهد. از این ثابت به عنوان کلید استفاده کنید. ثابت های عدد صحیح زیر مقادیر ممکن هستند:-
DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED
: مورد اصلا پخش نشده است. -
DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED
: مورد تا حدی پخش شده است و موقعیت فعلی جایی در وسط است. -
DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED
: مورد تکمیل شده است.
-
-
DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
: میزان پیشرفت تکمیل در محتوای طولانی را به صورت دو برابری بین 0.0 و 1.0، شامل میشود. این موارد اضافی اطلاعات بیشتری در مورد وضعیتPARTIALLY_PLAYING
ارائه میکند تا Android Auto یا Android Automotive OS نشانگر پیشرفت معنیداری مانند نوار پیشرفت را نمایش دهند. اگر از این موارد اضافی استفاده میکنید، به بخش بهروزرسانی نوار پیشرفت در نمای مرور هنگام پخش محتوا در این راهنما مراجعه کنید تا یاد بگیرید چگونه این شاخص را پس از نمایش اولیه بهروز نگه دارید.
برای نمایش نشانگرهایی که در هنگام مرور درخت مرور رسانه ظاهر می شوند، یک بسته اضافی ایجاد کنید که شامل یک یا چند مورد از این ثابت ها باشد و آن بسته را به متد MediaDescription.Builder.setExtras()
منتقل کنید.
قطعه کد زیر نحوه نمایش نشانگرها را برای یک مورد رسانه صریح که 70٪ کامل است نشان می دهد:
کاتلین
import androidx.media.utils.MediaConstants val extras = Bundle() extras.putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS, MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED) extras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.7) val description = MediaDescriptionCompat.Builder() .setMediaId(/*...*/) .setTitle(resources.getString(/*...*/)) .setExtras(extras) .build() return MediaBrowserCompat.MediaItem(description, /* flags */)
جاوا
import androidx.media.utils.MediaConstants; Bundle extras = new Bundle(); extras.putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS, MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED); extras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.7); MediaDescriptionCompat description = new MediaDescriptionCompat.Builder() .setMediaId(/*...*/) .setTitle(resources.getString(/*...*/)) .setExtras(extras) .build(); return new MediaBrowserCompat.MediaItem(description, /* flags */);
برای نمایش نشانگرهای مورد رسانه ای که در حال پخش است، می توانید مقادیر Long
را برای METADATA_KEY_IS_EXPLICIT
یا EXTRA_DOWNLOAD_STATUS
در MediaMetadataCompat
mediaSession
خود اعلام کنید. شما نمی توانید نشانگرهای DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS
یا DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
را در نمای پخش نمایش دهید.
قطعه کد زیر نشان می دهد که چگونه می توان نشان داد که آهنگ فعلی در نمای پخش صریح و دانلود شده است:
کاتلین
import androidx.media.utils.MediaConstants mediaSession.setMetadata( MediaMetadataCompat.Builder() .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, "Song Name") .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "Artist name") .putString( MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, albumArtUri.toString()) .putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) .putLong( MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS, MediaDescriptionCompat.STATUS_DOWNLOADED) .build())
جاوا
import androidx.media.utils.MediaConstants; mediaSession.setMetadata( new MediaMetadataCompat.Builder() .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, "Song Name") .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "Artist name") .putString( MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, albumArtUri.toString()) .putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) .putLong( MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS, MediaDescriptionCompat.STATUS_DOWNLOADED) .build());
هنگام پخش محتوا، نوار پیشرفت را در نمای مرور به روز کنید
همانطور که قبلا ذکر شد، میتوانید از DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
اضافی برای نمایش نوار پیشرفت برای محتوای نیمهبازیشده در نمای مرور استفاده کنید. با این حال، اگر کاربر به پخش محتوای نیمهبازیشده از Android Auto یا Android Automotive OS ادامه دهد، با گذشت زمان این نشانگر نادرست میشود.
برای اینکه Android Auto و Android Automotive OS نوار پیشرفت را به روز نگه دارند، می توانید اطلاعات بیشتری را در MediaMetadataCompat
و PlaybackStateCompat
ارائه دهید تا محتوای در حال انجام را به موارد رسانه در نمای مرور پیوند دهید. برای اینکه آیتم رسانه نوار پیشرفت بهروزرسانی خودکار داشته باشد، باید شرایط زیر رعایت شود:
- هنگامی که
MediaItem
ایجاد شد، بایدDESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
را با مقداری بین 0.0 و 1.0، شامل DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE ارسال کند. -
MediaMetadataCompat
بایدMETADATA_KEY_MEDIA_ID
با مقدار رشته ای برابر با شناسه رسانه ارسال شده بهMediaItem
ارسال کند. -
PlaybackStateCompat
باید شامل یک کلید اضافی با کلیدPLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID
باشد که به مقدار رشته ای برابر با شناسه رسانه ارسال شده بهMediaItem
نگاشت می شود.
قطعه کد زیر نشان می دهد که چگونه می توان نشان داد که آیتم در حال پخش به یک آیتم در نمای مرور مرتبط است:
کاتلین
import androidx.media.utils.MediaConstants // When the MediaItem is constructed to show in the browse view. // Suppose the item was 25% complete when the user launched the browse view. val mediaItemExtras = Bundle() mediaItemExtras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.25) val description = MediaDescriptionCompat.Builder() .setMediaId("my-media-id") .setExtras(mediaItemExtras) // ...and any other setters. .build() return MediaBrowserCompat.MediaItem(description, /* flags */) // Elsewhere, when the user has selected MediaItem for playback. mediaSession.setMetadata( MediaMetadataCompat.Builder() .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, "my-media-id") // ...and any other setters. .build()) val playbackStateExtras = Bundle() playbackStateExtras.putString( MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID, "my-media-id") mediaSession.setPlaybackState( PlaybackStateCompat.Builder() .setExtras(playbackStateExtras) // ...and any other setters. .build())
جاوا
import androidx.media.utils.MediaConstants; // When the MediaItem is constructed to show in the browse view. // Suppose the item was 25% complete when the user launched the browse view. Bundle mediaItemExtras = new Bundle(); mediaItemExtras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.25); MediaDescriptionCompat description = new MediaDescriptionCompat.Builder() .setMediaId("my-media-id") .setExtras(mediaItemExtras) // ...and any other setters. .build(); return MediaBrowserCompat.MediaItem(description, /* flags */); // Elsewhere, when the user has selected MediaItem for playback. mediaSession.setMetadata( new MediaMetadataCompat.Builder() .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, "my-media-id") // ...and any other setters. .build()); Bundle playbackStateExtras = new Bundle(); playbackStateExtras.putString( MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID, "my-media-id"); mediaSession.setPlaybackState( new PlaybackStateCompat.Builder() .setExtras(playbackStateExtras) // ...and any other setters. .build());
نمایش نتایج جستجوی قابل مرور
برنامه شما میتواند نتایج جستجوی متنی را ارائه کند که هنگام شروع یک عبارت جستجو برای کاربران نمایش داده شود. Android Auto و Android Automotive OS این نتایج را از طریق رابطهای درخواست جستجو یا از طریق مقرون به صرفههایی که بر روی عبارتهای قبلی در جلسه انجام شده است، نشان میدهند. برای کسب اطلاعات بیشتر، به بخش پشتیبانی از اقدامات صوتی در این راهنما مراجعه کنید.
برای نمایش نتایج جستجوی قابل مرور، کلید ثابت BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED
را در بسته اضافی متد onGetRoot()
سرویس خود قرار دهید، با نگاشت true
بولی.
قطعه کد زیر نحوه فعال کردن پشتیبانی را در متد onGetRoot()
نشان می دهد:
کاتلین
import androidx.media.utils.MediaConstants @Nullable fun onGetRoot( @NonNull clientPackageName: String, clientUid: Int, @Nullable rootHints: Bundle ): BrowserRoot { val extras = Bundle() extras.putBoolean( MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true) return BrowserRoot(ROOT_ID, extras) }
جاوا
import androidx.media.utils.MediaConstants; @Nullable @Override public BrowserRoot onGetRoot( @NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) { Bundle extras = new Bundle(); extras.putBoolean( MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true); return new BrowserRoot(ROOT_ID, extras); }
برای شروع ارائه نتایج جستجو، روش onSearch()
در سرویس مرورگر رسانه خود لغو کنید. Android Auto و Android Automotive OS هر زمان که کاربر یک رابط جستجو یا "نتایج جستجو" را فراخوانی کند، عبارات جستجوی کاربر را به این روش ارسال می کند.
می توانید نتایج جستجو را از روش onSearch()
سرویس خود با استفاده از آیتم های عنوان سازماندهی کنید تا آنها را قابل مرور کنید. به عنوان مثال، اگر برنامه شما موسیقی پخش می کند، ممکن است نتایج جستجو را بر اساس آلبوم، هنرمند و آهنگ ها سازماندهی کنید.
قطعه کد زیر پیاده سازی ساده ای از متد onSearch()
را نشان می دهد:
کاتلین
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<List<MediaItem>> result) { // Detach from results to unblock the caller (if a search is expensive). result.detach(); new AsyncTask<Void, Void, Void>() { List<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); } } }.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. }
اقدامات مرور سفارشی
اعمال مرور سفارشی به شما این امکان را می دهد که نمادها و برچسب های سفارشی را به اشیاء MediaItem
برنامه خود در برنامه رسانه خودرو اضافه کنید و تعاملات کاربر را با این کنش ها مدیریت کنید. این به شما امکان میدهد عملکرد برنامه رسانه را به روشهای مختلفی گسترش دهید، مانند افزودن عملکردهای «دانلود»، «افزودن به صف»، «رادیو پخش»، «مورد علاقه» یا «حذف».
اگر اقدامات سفارشی بیشتر از آنچه OEM اجازه نمایش داده است وجود داشته باشد، یک منوی سرریز به کاربر ارائه می شود.
چگونه کار می کنند؟
هر اقدام مرور سفارشی با موارد زیر تعریف می شود:
- شناسه اقدام (شناسه رشته منحصر به فرد)
- یک برچسب اقدام (متن نمایش داده شده به کاربر)
- URI یک Action Icon (یک بردار قابل ترسیم که می تواند رنگ آمیزی شود)
شما فهرستی از اقدامات مرور سفارشی را در سطح جهانی به عنوان بخشی از BrowseRoot
خود تعریف می کنید. سپس می توانید زیرمجموعه ای از این اقدامات را به MediaItem.
هنگامی که یک کاربر با یک اقدام مرور سفارشی تعامل می کند، برنامه شما در onCustomAction()
یک تماس پاسخ دریافت می کند. سپس میتوانید این عمل را انجام دهید و در صورت لزوم فهرست اقدامات MediaItem
را بهروزرسانی کنید. این برای اقدامات حالتی مانند "مورد علاقه" و "دانلود" مفید است. برای کنشهایی که نیازی به بهروزرسانی ندارند، مانند «Play Radio»، نیازی نیست فهرست کنشها را بهروزرسانی کنید.
همچنین میتوانید اقدامهای مرور سفارشی را به ریشه گره مرور متصل کنید. این اقدامات در یک نوار ابزار ثانویه در زیر نوار ابزار اصلی نمایش داده می شود.
نحوه پیاده سازی اقدامات مرور سفارشی
در اینجا مراحل افزودن اعمال مرور سفارشی به پروژه شما آمده است:
- دو روش را در اجرای
MediaBrowserServiceCompat
خود لغو کنید: - محدودیت های عمل را در زمان اجرا تجزیه کنید:
- در
onGetRoot()
با استفاده از کلیدBROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT
درrootHints
Bundle
حداکثر تعداد اعمال مجاز را برای هرMediaItem
دریافت کنید. محدودیت 0 نشان می دهد که این ویژگی توسط سیستم پشتیبانی نمی شود.
- در
- فهرست جهانی اقدامات مرور سفارشی را بسازید:
- برای هر کنش، یک شی
Bundle
با کلیدهای زیر ایجاد کنید: *EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID
: شناسه کنش *EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL
: برچسب کنش *EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI
Bundle
به لیست اضافه کنید.
- برای هر کنش، یک شی
- لیست جهانی را به
BrowseRoot
خود اضافه کنید:- در
BrowseRoot
Bundle
با استفاده از کلیدBROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST
، فهرستی از اقدامات را بهعنوانArraylist
Parcelable
اضافه کنید.
- در
- اعمالی را به اشیاء
MediaItem
خود اضافه کنید:- میتوانید با استفاده از کلید
DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST
، کنشها را با گنجاندن فهرست شناسههای کنش در موارد اضافیMediaDescriptionCompat
به اشیاء جداگانهMediaItem
اضافه کنید. این لیست باید زیرمجموعه ای از فهرست جهانی اقداماتی باشد که درBrowseRoot
تعریف کرده اید.
- میتوانید با استفاده از کلید
- مدیریت اقدامات و بازگشت پیشرفت یا نتایج:
- در
onCustomAction
، عملکرد را بر اساس شناسه اقدام و هر داده دیگری که نیاز دارید مدیریت کنید. با استفاده از کلیدEXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID
میتوانید شناسهMediaItem
را که این عمل را آغاز کرده است، از موارد اضافی دریافت کنید. . - با قرار دادن کلید
EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
در بسته پیشرفت یا نتیجه، میتوانید فهرست اقدامات یکMediaItem
را بهروزرسانی کنید.
- در
در اینجا تغییراتی وجود دارد که می توانید در BrowserServiceCompat
خود ایجاد کنید تا با اعمال مرور سفارشی شروع کنید.
نادیده گرفتن BrowserServiceCompat
شما باید روشهای زیر را در MediaBrowserServiceCompat
نادیده بگیرید.
public void onLoadItem(String itemId, @NonNull Result<MediaBrowserCompat.MediaItem> result)
public void onCustomAction(@NonNull String action, Bundle extras, @NonNull Result<Bundle> result)
محدود کردن اقدامات تجزیه
شما باید بررسی کنید که چند اقدامات مرور سفارشی پشتیبانی می شود.
public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, Bundle rootHints) { rootHints.getInt( MediaConstants.BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT, 0) }
یک اقدام به مرور سفارشی بسازید
هر عمل باید در یک Bundle
جداگانه بسته بندی شود.
- شناسه عمل
bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID, "<ACTION_ID>")
- برچسب عمل
bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL, "<ACTION_LABEL>")
- نماد عمل URI
bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI, "<ACTION_ICON_URI>")
اقدامات مرور سفارشی را به ArrayList
Parceable
اضافه کنید
همه اشیاء Bundle
نرم افزاری Action Custom را در یک ArrayList
اضافه کنید.
private ArrayList<Bundle> createCustomActionsList( CustomBrowseAction browseActions) { ArrayList<Bundle> browseActionsBundle = new ArrayList<>(); for (CustomBrowseAction browseAction : browseActions) { Bundle action = new Bundle(); action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID, browseAction.mId); action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL, getString(browseAction.mLabelResId)); action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI, browseAction.mIcon); browseActionsBundle.add(action); } return browseActionsBundle; }
لیست اقدامات سفارشی را به مرور ریشه اضافه کنید
public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, Bundle rootHints) { Bundle browserRootExtras = new Bundle(); browserRootExtras.putParcelableArrayList( BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST, createCustomActionsList())); mRoot = new BrowserRoot(ROOT_ID, browserRootExtras); return mRoot; }
اقدامات را به یک MediaItem
اضافه کنید
MediaDescriptionCompat buildDescription (long id, String title, String subtitle, String description, Uri iconUri, Uri mediaUri, ArrayList<String> browseActionIds) { MediaDescriptionCompat.Builder bob = new MediaDescriptionCompat.Builder(); bob.setMediaId(id); bob.setTitle(title); bob.setSubtitle(subtitle); bob.setDescription(description); bob.setIconUri(iconUri); bob.setMediaUri(mediaUri); Bundle extras = new Bundle(); extras.putStringArrayList( DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST, browseActionIds); bob.setExtras(extras); return bob.build(); } MediaItem mediaItem = new MediaItem(buildDescription(...), flags);
نتیجه onCustomAction
را ایجاد کنید
- تجزیه مدیائید از
Bundle extras
:@Override public void onCustomAction( @NonNull String action, Bundle extras, @NonNull Result<Bundle> result){ String mediaId = extras.getString(MediaConstans.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID); }
- برای نتایج ناهمزمان نتیجه جدا.
result.detach()
- ساخت بسته نتیجه
- پیام به کاربر
mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE, mContext.getString(stringRes))
- مورد را به روز کنید (برای به روزرسانی اقدامات در یک مورد استفاده کنید)
mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM, mediaId);
- نمای پخش باز
//Shows user the PBV without changing the playback state mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM, null);
- به روزرسانی گره را مرور کنید
//Change current browse node to mediaId mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE, mediaId);
- پیام به کاربر
- در صورت خطا ، نتیجه تماس
result.sendError(resultBundle).
- در صورت بروزرسانی پیشرفت ،
result.sendProgressUpdate(resultBundle)
فراخوانی کنید. - با فراخوانی
result.sendResult(resultBundle)
پایان دهید.
حالت عمل را به روز کنید
با استفاده از روش result.sendProgressUpdate(resultBundle)
با کلید EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
، می توانید MediaItem
را به روز کنید تا وضعیت جدید عمل را منعکس کند. این به شما امکان می دهد تا در مورد پیشرفت و نتیجه عملکرد آنها ، بازخورد در زمان واقعی را به کاربر ارائه دهید.
مثال: اکشن بارگیری
در اینجا نمونه ای از چگونگی استفاده از این ویژگی برای اجرای یک عمل بارگیری با سه ایالت آورده شده است:
- بارگیری: این حالت اولیه عمل است. هنگامی که کاربر این عمل را انتخاب می کند ، می توانید آن را با "بارگیری" مبادله کرده و برای به روزرسانی UI با
sendProgressUpdate
تماس بگیرید. - بارگیری: این حالت نشان می دهد که بارگیری در حال انجام است. می توانید از این حالت برای نشان دادن نوار پیشرفت یا شاخص دیگری برای کاربر استفاده کنید.
- بارگیری: این حالت نشان می دهد که بارگیری کامل است. هنگامی که بارگیری به پایان رسید ، می توانید "بارگیری" را با "بارگیری" تعویض کنید و با
EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
باsendResult
تماس بگیرید تا نشان دهید که این مورد باید تازه شود. علاوه بر این ، می توانید از کلیدEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE
برای نمایش یک پیام موفقیت به کاربر استفاده کنید.
این رویکرد به شما امکان می دهد تا در مورد فرآیند بارگیری و وضعیت فعلی آن بازخورد روشنی را به کاربر ارائه دهید. می توانید جزئیات بیشتری را با نمادها اضافه کنید تا 25 ٪ ، 50 ٪ ، 75 ٪ بیان کنید.
مثال: عمل مورد علاقه
مثال دیگر یک عمل مورد علاقه با دو ایالت است:
- مورد علاقه: این عمل برای مواردی که در لیست موارد دلخواه کاربر نیستند نمایش داده می شود. هنگامی که کاربر این عمل را انتخاب می کند ، می توانید آن را با "مورد علاقه" مبادله کرده و با
sendResult
باEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
تماس بگیرید تا UI را به روز کنید. - مورد علاقه: این اقدام برای مواردی که در لیست موارد دلخواه کاربر قرار دارند نمایش داده می شود. هنگامی که کاربر این عمل را انتخاب می کند ، می توانید آن را با "مورد علاقه" مبادله کرده و با کلید
EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
sendResult
بگیرید تا UI را به روز کنید.
این رویکرد روشی واضح و مداوم را برای کاربران برای مدیریت موارد مورد علاقه خود فراهم می کند.
این مثالها انعطاف پذیری اقدامات مرور سفارشی و چگونگی استفاده از آنها را برای اجرای انواع کارکردهای با بازخورد در زمان واقعی برای یک تجربه کاربر پیشرفته در برنامه رسانه خودرو نشان می دهد.
برای اجرای کامل این ویژگی ، می توانید به پروژه TestMediaApp
مراجعه کنید.
کنترل پخش را فعال کنید
سیستم عامل Android Auto و Android Automobile OS دستورات کنترل پخش را از طریق MediaSessionCompat
سرویس خود ارسال می کند. شما باید یک جلسه را ثبت کنید و روش های پاسخ به تماس را پیاده سازی کنید.
یک جلسه رسانه ای ثبت کنید
در روش onCreate()
سرویس مرورگر رسانه خود ، یک MediaSessionCompat
ایجاد کنید ، سپس جلسه رسانه را با فراخوانی setSessionToken()
ثبت کنید.
قطعه کد زیر نحوه ایجاد و ثبت یک جلسه رسانه ای را نشان می دهد:
کاتلین
override fun onCreate() { super.onCreate() ... // Start a new MediaSession. val session = MediaSessionCompat(this, "session tag").apply { // Set a callback object that implements MediaSession.Callback // to handle play control requests. 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 that implements MediaSession.Callback // to handle play control requests. session.setCallback(new MyMediaSessionCallback()); ... }
هنگامی که شیء Media Session را ایجاد می کنید ، یک شیء پاسخ به تماس را تنظیم می کنید که برای رسیدگی به درخواست های کنترل پخش استفاده می شود. شما با ارائه کلاس MediaSessionCompat.Callback
برای برنامه خود ، این شیء پاسخ به تماس را ایجاد می کنید. در بخش بعدی نحوه اجرای این شی مورد بحث قرار می گیرد.
اجرای دستورات بازی
هنگامی که کاربر درخواست پخش یک مورد رسانه ای را از برنامه شما ، سیستم عامل Android Automobile و Android Auto از کلاس MediaSessionCompat.Callback
از شیء MediaSessionCompat
برنامه خود که از سرویس مرورگر رسانه برنامه شما بدست آورده اند ، استفاده می کنند. هنگامی که یک کاربر می خواهد پخش محتوا را کنترل کند ، مانند مکث پخش یا پرش به مسیر بعدی ، Android Auto و Android Automobile Os یکی از روشهای شیء پاسخ به تماس را فراخوانی می کند.
برای رسیدگی به پخش محتوا ، برنامه شما باید کلاس انتزاعی MediaSessionCompat.Callback
را گسترش داده و روشهایی را که برنامه شما پشتیبانی می کند ، پیاده سازی کنید.
تمام روشهای پاسخ به تماس زیر را که برای نوع محتوایی که برنامه شما ارائه می دهد معقول کند:
-
onPrepare()
- هنگام تغییر منبع رسانه فراخوانی می شود. سیستم عامل Android Automobile نیز بلافاصله پس از بوت شدن از این روش استفاده می کند. برنامه رسانه ای شما باید این روش را پیاده سازی کند.
-
onPlay()
- اگر کاربر بدون انتخاب یک مورد خاص ، بازی را انتخاب کند ، فراخوانی می شود. برنامه شما باید محتوای پیش فرض خود را بازی کند یا اگر پخش با
onPause()
متوقف شد ، برنامه شما پخش را از سر می گیرد.توجه: برنامه شما نباید وقتی Android Automobile OS یا Android Auto به سرویس مرورگر رسانه شما متصل شود ، به طور خودکار پخش موسیقی را شروع کند. برای اطلاعات بیشتر ، به بخش تنظیم وضعیت پخش اولیه مراجعه کنید.
-
onPlayFromMediaId()
- وقتی کاربر تصمیم به پخش یک مورد خاص گرفت ، فراخوانی شد. این روش شناسه ای را که سرویس مرورگر رسانه شما در سلسله مراتب محتوای شما به مورد رسانه اختصاص داده است ، منتقل می کند.
-
onPlayFromSearch()
- وقتی کاربر تصمیم به بازی از یک پرس و جو جستجو گرفت ، فراخوانی شد. برنامه باید بر اساس رشته جستجو که در آن منتقل شده است ، یک انتخاب مناسب را انجام دهد.
-
onPause()
- وقتی کاربر تصمیم به مکث پخش می کند ، فراخوانی می شود.
-
onSkipToNext()
- هنگامی که کاربر تصمیم به جستجوی مورد بعدی گرفت ، فراخوانی شد.
-
onSkipToPrevious()
- هنگامی که کاربر تصمیم به جستجوی مورد قبلی گرفت ، فراخوانی شد.
-
onStop()
- وقتی کاربر تصمیم به متوقف کردن پخش می کند ، فراخوانی می شود.
برای ارائه هرگونه قابلیت مورد نظر ، این روش ها را در برنامه خود نادیده بگیرید. اگر عملکرد آن توسط برنامه شما پشتیبانی نمی شود ، نیازی به اجرای یک روش ندارید. به عنوان مثال ، اگر برنامه شما یک جریان زنده مانند پخش ورزشی بازی می کند ، نیازی به اجرای روش onSkipToNext()
نیست. به جای آن می توانید از اجرای پیش فرض onSkipToNext()
استفاده کنید.
برنامه شما برای پخش محتوا از طریق بلندگوهای خودرو نیازی به منطق خاصی ندارد. هنگامی که برنامه شما درخواست پخش محتوا را دریافت می کند ، می تواند صوتی را به همان روشی پخش کند که از طریق بلندگوهای تلفن یا هدفون کاربر محتوا را پخش کند. سیستم عامل Android Auto و Android Automobile OS به طور خودکار محتوای صوتی را به سیستم خودرو ارسال می کند تا از روی بلندگوهای خودرو پخش شود.
برای کسب اطلاعات بیشتر در مورد پخش محتوای صوتی ، به نمای کلی MediaPlayer ، نمای کلی برنامه صوتی و نمای کلی سیارات سیارات مراجعه کنید.
اقدامات پخش استاندارد را تنظیم کنید
Android Auto و Android Automobile OS نمایشگر کنترل پخش بر اساس اقداماتی که در Object PlaybackStateCompat
فعال شده است.
به طور پیش فرض ، برنامه شما باید از اقدامات زیر پشتیبانی کند:
برنامه شما در صورت مرتبط بودن با محتوای برنامه می تواند از اقدامات زیر پشتیبانی کند:
علاوه بر این ، شما گزینه ایجاد یک صف بازی را دارید که می تواند برای کاربر نمایش داده شود ، اما لازم نیست. برای انجام این کار ، با روش های setQueue()
و setQueueTitle()
تماس بگیرید ، عملکرد ACTION_SKIP_TO_QUEUE_ITEM
را فعال کنید و پاسخ به تماس onSkipToQueueItem()
را تعریف کنید.
همچنین ، پشتیبانی از نماد بازی در حال حاضر ، که نشانگر آنچه در حال حاضر بازی می کند ، اضافه کنید. برای انجام این کار ، با روش setActiveQueueItemId()
تماس بگیرید و شناسه مورد موجود در حال حاضر را در صف منتقل کنید. شما باید هر زمان که یک تغییر صف وجود داشته باشد ، setActiveQueueItemId()
را به روز کنید.
دکمه های نمایش سیستم عامل Android Auto و Android Automobile OS برای هر عمل فعال و همچنین صف پخش. هنگامی که دکمه ها کلیک می شود ، سیستم از پاسخ به تماس متناظر خود از MediaSessionCompat.Callback
فراخوانی می کند.
فضای بلااستفاده را رزرو کنید
Android Auto و Android Automobile OS فضای ذخیره در UI برای اقدامات ACTION_SKIP_TO_PREVIOUS
و ACTION_SKIP_TO_NEXT
. اگر برنامه شما از یکی از این توابع پشتیبانی نمی کند ، Android Auto و Android Automobile Os از فضا برای نمایش هرگونه اقدامات سفارشی که ایجاد می کنید استفاده کنید.
اگر نمی خواهید آن فضاها را با اقدامات سفارشی پر کنید ، می توانید آنها را رزرو کنید تا Android Auto و Android Automobile Os هر زمان که برنامه شما از عملکرد مربوطه پشتیبانی نکند ، فضای خالی را خالی می کند. برای انجام این کار ، با یک بسته نرم افزاری setExtras()
تماس بگیرید که حاوی ثابت هایی است که مطابق با توابع رزرو شده است. SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT
با ACTION_SKIP_TO_NEXT
مطابقت دارد ، و SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV
مطابق با ACTION_SKIP_TO_PREVIOUS
است. از این ثابت ها به عنوان کلیدهای موجود در بسته نرم افزاری استفاده کنید و از Boolean true
برای مقادیر آنها استفاده کنید.
پخش اولیه را تنظیم کنید
همانطور که Android Auto و Android Automobile Os با سرویس مرورگر رسانه خود ارتباط برقرار می کنند ، جلسه رسانه ای شما با استفاده از PlaybackStateCompat
وضعیت پخش محتوا را ارتباط می دهد. برنامه شما نباید وقتی Android Automobile OS یا Android Auto به سرویس مرورگر رسانه شما متصل شود ، به طور خودکار پخش موسیقی را شروع کند. در عوض ، به سیستم عامل Android Auto و Android Automobile Os برای از سرگیری یا شروع پخش بر اساس وضعیت خودرو یا اقدامات کاربر اعتماد کنید.
برای تحقق این هدف ، PlaybackStateCompat
اولیه جلسه رسانه خود را بر روی STATE_STOPPED
، STATE_PAUSED
، STATE_NONE
یا STATE_ERROR
قرار دهید.
جلسات رسانه ای در سیستم عامل Android Auto و Android Automobile Os فقط برای مدت زمان درایو دوام می آورد ، بنابراین کاربران مرتباً این جلسات را شروع و متوقف می کنند. برای ارتقاء یک تجربه یکپارچه بین درایوها ، وضعیت جلسه قبلی کاربر را پیگیری کنید ، به طوری که وقتی برنامه رسانه درخواست رزومه ای را دریافت می کند ، کاربر می تواند به طور خودکار جایی را که ترک کرده است انتخاب کند - برای مثال ، آخرین مورد رسانه ای بازی شده ، PlaybackStateCompat
، و صف
اقدامات پخش سفارشی را اضافه کنید
برای نمایش اقدامات اضافی که برنامه رسانه ای شما از آن پشتیبانی می کند می توانید اقدامات پخش سفارشی اضافه کنید. اگر فضا اجازه می دهد (و محفوظ نیست) ، Android اقدامات سفارشی را به کنترل حمل و نقل اضافه می کند. در غیر این صورت ، اقدامات سفارشی در منوی Overflow نمایش داده می شود. نمایش اقدامات سفارشی به ترتیب آنها به PlaybackStateCompat
اضافه می شود.
از اقدامات سفارشی برای ارائه رفتار متمایز از اقدامات استاندارد استفاده کنید. از آنها برای جایگزینی یا کپی کردن اقدامات استاندارد استفاده نکنید.
می توانید اقدامات سفارشی را با استفاده از روش addCustomAction()
در کلاس PlaybackStateCompat.Builder
اضافه کنید.
قطعه کد زیر نحوه اضافه کردن یک عمل "شروع یک کانال رادیویی" را نشان می دهد:
کاتلین
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());
برای مثال بیشتر از این روش ، به روش setCustomAction()
در برنامه نمونه پخش کننده موسیقی جهانی Android در GitHub مراجعه کنید.
پس از ایجاد اقدامات سفارشی ، جلسه رسانه ای شما می تواند با غلبه بر روش onCustomAction()
به عمل پاسخ دهد.
قطعه کد زیر نشان می دهد که چگونه برنامه شما ممکن است به یک عمل "شروع کانال رادیویی" پاسخ دهد:
کاتلین
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)) { ... } }
برای مثال بیشتر از این روش ، به روش onCustomAction
در برنامه نمونه پخش کننده موسیقی Android Universal در GitHub مراجعه کنید.
نمادها برای اقدامات سفارشی
هر اقدام سفارشی که ایجاد می کنید به یک منبع نماد نیاز دارد. برنامه های موجود در اتومبیل ها می توانند در بسیاری از اندازه ها و تراکم های صفحه نمایش مختلف اجرا شوند ، بنابراین نمادهایی که شما ارائه می دهید باید بردار وکتور باشند. یک بردار قابل ترسیم به شما امکان می دهد بدون از دست دادن جزئیات دارایی را مقیاس کنید. یک بردار قابل ترسیم همچنین باعث می شود لبه ها و گوشه ها با مرزهای پیکسل با وضوح کوچکتر تراز شوند.
اگر یک عمل سفارشی مطبوع باشد - به عنوان مثال ، یک تنظیم پخش را روشن یا خاموش می کند - نمادهای مختلف را برای حالت های مختلف ارائه می دهد ، بنابراین کاربران می توانند هنگام انتخاب عمل ، تغییری را مشاهده کنند.
برای اقدامات معلولیت سبک های جایگزین را ارائه دهید
هنگامی که یک اقدام سفارشی برای زمینه فعلی در دسترس نیست ، نماد عمل سفارشی را با یک نماد جایگزین مبادله کنید که نشان می دهد این عمل غیرفعال است.
قالب صوتی را نشان دهید
برای نشان دادن اینکه در حال حاضر در حال پخش رسانه از یک فرمت صوتی ویژه استفاده می کند ، می توانید نمادهایی را که در اتومبیل هایی ارائه می شوند که از این ویژگی پشتیبانی می کنند ، مشخص کنید. شما می توانید KEY_CONTENT_FORMAT_TINTABLE_LARGE_ICON_URI
و KEY_CONTENT_FORMAT_TINTABLE_SMALL_ICON_URI
را در بسته اضافی مورد رسانه ای در حال حاضر در حال حاضر (منتقل شده به MediaSession.setMetadata()
) تنظیم کنید. حتماً هر دو مورد اضافی را تنظیم کنید تا چیدمان های مختلف را در خود جای دهید.
علاوه بر این ، شما می توانید KEY_IMMERSIVE_AUDIO
Extra را تنظیم کنید تا به OEM های اتومبیل بگویید که این صدای همهجانبه است ، و آنها باید هنگام تصمیم گیری در مورد استفاده از جلوه های صوتی که ممکن است در محتوای همهجانبه تداخل داشته باشد ، بسیار مراقب باشند.
پیوندهایی را از مورد بازی در حال حاضر اضافه کنید
شما می توانید مورد رسانه ای در حال حاضر بازی را پیکربندی کنید تا زیرنویس ، توضیحات یا هر دو آن پیوندی به سایر موارد رسانه ای باشد. این اجازه می دهد تا کاربر به سرعت به موارد مرتبط پرش کند. به عنوان مثال ، آنها ممکن است توسط همان هنرمند ، قسمت های دیگر آن پادکست و غیره به آهنگ های دیگر پرش کنند.
برای افزودن پیوندها ، ابرداده KEY_SUBTITLE_LINK_MEDIA_ID
(برای پیوند از زیرنویس) یا KEY_DESCRIPTION_LINK_MEDIA_ID
(برای پیوند از توضیحات) پیکربندی کنید. برای جزئیات بیشتر ، به مستندات مرجع آن زمینه های ابرداده مراجعه کنید.
از اقدامات صوتی پشتیبانی کنید
برنامه رسانه ای شما باید از اقدامات صوتی پشتیبانی کند تا به رانندگان یک تجربه ایمن و راحت کمک کند که حواس پرتی ها را به حداقل برساند. به عنوان مثال ، اگر برنامه شما در حال پخش یک مورد رسانه ای است ، کاربر می تواند " [عنوان آهنگ]" را پخش کند تا به برنامه خود بگوید بدون اینکه به نمایش یا لمس کردن صفحه نمایش خودرو آهنگ دیگری را پخش کند. روی فرمان آنها یا صحبت کردن با کلمات داغ " خوب Google ".
هنگامی که Android Auto یا Android Automobile Os یک عملکرد صوتی را تشخیص داده و تفسیر می کند ، این عملکرد صوتی از طریق onPlayFromSearch()
به برنامه تحویل داده می شود. با دریافت این پاسخ به تماس ، برنامه محتوا را مطابق با رشته query
می کند و پخش را شروع می کند.
کاربران می توانند دسته های مختلفی از اصطلاحات را در پرس و جو خود مشخص کنند: ژانر ، هنرمند ، آلبوم ، نام آهنگ ، ایستگاه رادیویی یا لیست پخش. هنگام ایجاد پشتیبانی از جستجو ، تمام دسته هایی را که برای برنامه شما معنی دارد ، حساب کنید. اگر Android Auto یا Android Automobile Os تشخیص دهد که یک پرس و جو خاص در دسته های خاصی قرار می گیرد ، در پارامتر extras
اضافی را اضافه می کند. موارد اضافی زیر را می توان ارسال کرد:
اگر کاربر شرایط جستجو را مشخص نکند ، می توانید یک رشته query
خالی را که می توانید توسط سیستم عامل Android Auto یا Android Automobile ارسال کنید ، حساب کنید. به عنوان مثال ، اگر کاربر می گوید " موسیقی پخش کنید ". در این حالت ، برنامه شما ممکن است یک آهنگ اخیراً بازی شده یا تازه پیشنهادی را شروع کند.
اگر جستجو به سرعت قابل پردازش نیست ، در onPlayFromSearch()
مسدود نکنید. در عوض ، حالت پخش را روی STATE_CONNECTING
تنظیم کرده و جستجو را روی یک موضوع async انجام دهید.
پس از شروع پخش ، جمع کردن صف جلسه رسانه با محتوای مرتبط را در نظر بگیرید. به عنوان مثال ، اگر کاربر درخواست آلبومی را برای پخش کند ، برنامه شما ممکن است صف را با لیست آهنگ آلبوم پر کند. همچنین پشتیبانی از نتایج جستجوی قابل مرور را در نظر بگیرید تا کاربر بتواند آهنگ متفاوتی را انتخاب کند که مطابق با پرس و جو آنها باشد.
علاوه بر " نمایش " نمایش داده شد ، Android Auto و Android Automobile Os ، نمایش داده های صوتی را برای کنترل پخش مانند " مکث موسیقی " و " آهنگ بعدی " و مطابقت با این دستورات با تماس های جلسه مناسب رسانه ها ، مانند onPause()
و onSkipToNext()
تشخیص می دهند.
برای مثال مفصل در مورد نحوه اجرای اقدامات پخش با قابلیت پخش در برنامه خود ، به Google Assistant و برنامه های رسانه ای مراجعه کنید.
حفاظت از حواس پرتی را اجرا کنید
از آنجا که تلفن کاربر هنگام استفاده از Android Auto به بلندگوهای خودرو خود متصل است ، برای جلوگیری از حواس پرتی راننده باید اقدامات احتیاطی دیگری را انجام دهید.
هشدارها را در ماشین سرکوب کنید
برنامه های رسانه ای Android Auto نباید از طریق بلندگوهای خودرو پخش صدا را شروع کنند ، مگر اینکه کاربر با فشار دادن یک دکمه بازی ، پخش را شروع کند. حتی یک زنگ هشدار برنامه ریزی شده توسط برنامه رسانه ای شما نباید پخش موسیقی را از طریق بلندگوهای اتومبیل شروع کند.
برای تحقق این نیاز ، برنامه شما می تواند قبل از پخش هرگونه صدا ، از CarConnection
به عنوان سیگنال استفاده کند. برنامه شما می تواند بررسی کند که آیا تلفن با مشاهده LiveData
برای نوع اتصال خودرو به صفحه نمایش اتومبیل در حال نمایش است و بررسی می کند که آیا برابر با CONNECTION_TYPE_PROJECTION
است یا خیر.
اگر تلفن کاربر در حال طرح ریزی است ، برنامه های رسانه ای که از هشدارها پشتیبانی می کنند باید یکی از موارد زیر را انجام دهند:
- زنگ هشدار را غیرفعال کنید.
- زنگ هشدار را از طریق
STREAM_ALARM
پخش کنید و یک UI را در صفحه تلفن فراهم کنید تا زنگ خطر را غیرفعال کنید.
انجام تبلیغات رسانه ای
به طور پیش فرض ، Android Auto هنگامی که ابرداده رسانه در یک جلسه پخش صوتی تغییر می کند ، اعلان را نشان می دهد. هنگامی که یک برنامه رسانه ای از پخش موسیقی به اجرای تبلیغات تغییر می کند ، نمایش یک اعلان به کاربر حواس پرتی است. برای جلوگیری از نمایش خودکار اندرویدی در این حالت ، باید Media Metadata Key METADATA_KEY_IS_ADVERTISEMENT
را به METADATA_VALUE_ATTRIBUTE_PRESENT
تنظیم کنید ، همانطور که در قطعه کد زیر نشان داده شده است:
کاتلین
import androidx.media.utils.MediaConstants override fun onPlayFromMediaId(mediaId: String, extras: Bundle?) { MediaMetadataCompat.Builder().apply { if (isAd(mediaId)) { putLong( MediaConstants.METADATA_KEY_IS_ADVERTISEMENT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) } // ...add any other properties you normally would. mediaSession.setMetadata(build()) } }
جاوا
import androidx.media.utils.MediaConstants; @Override public void onPlayFromMediaId(String mediaId, Bundle extras) { MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder(); if (isAd(mediaId)) { builder.putLong( MediaConstants.METADATA_KEY_IS_ADVERTISEMENT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT); } // ...add any other properties you normally would. mediaSession.setMetadata(builder.build()); }
رسیدگی به خطاهای عمومی
هنگامی که برنامه خطایی را تجربه می کند ، حالت پخش را روی STATE_ERROR
تنظیم کرده و با استفاده از روش setErrorMessage()
پیام خطا ارائه دهید. برای لیستی از کدهای خطا که می توانید هنگام تنظیم پیام خطا از آن استفاده کنید ، به PlaybackStateCompat
مراجعه کنید. پیام های خطا باید با محلی فعلی کاربر کاربر کاربر شوند و بومی سازی شوند. سیستم عامل Android Auto و Android Automobile می تواند پیام خطا را به کاربر نمایش دهد.
به عنوان مثال ، اگر محتوا در منطقه فعلی کاربر در دسترس نیست ، می توانید هنگام تنظیم پیام خطا ، از کد ERROR_CODE_NOT_AVAILABLE_IN_REGION
استفاده کنید.
کاتلین
mediaSession.setPlaybackState( PlaybackStateCompat.Builder() .setState(PlaybackStateCompat.STATE_ERROR) .setErrorMessage(PlaybackStateCompat.ERROR_CODE_NOT_AVAILABLE_IN_REGION, getString(R.string.error_unsupported_region)) // ...and any other setters. .build())
جاوا
mediaSession.setPlaybackState( new PlaybackStateCompat.Builder() .setState(PlaybackStateCompat.STATE_ERROR) .setErrorMessage(PlaybackStateCompat.ERROR_CODE_NOT_AVAILABLE_IN_REGION, getString(R.string.error_unsupported_region)) // ...and any other setters. .build());
برای کسب اطلاعات بیشتر در مورد حالت های خطا ، به استفاده از یک جلسه رسانه ای مراجعه کنید: حالت ها و خطاها .
اگر یک کاربر خودکار Android برای حل خطا نیاز به باز کردن برنامه تلفن شما دارد ، آن اطلاعات را در پیام خود به کاربر ارائه دهید. به عنوان مثال ، پیام خطای شما ممکن است به جای "لطفاً وارد سیستم شوید" وارد [نام برنامه خود شوید].
منابع دیگر
،سیستم عامل Android Auto و Android Automobile Os به شما کمک می کند تا محتوای برنامه رسانه خود را به کاربران در اتومبیل خود بیاورید. یک برنامه رسانه ای برای اتومبیل ها باید یک سرویس مرورگر رسانه ای را ارائه دهد تا Android Auto و Android Automobile Os یا یک برنامه دیگر با یک مرورگر رسانه ای بتوانند محتوای شما را کشف و نمایش دهند.
این راهنما فرض می کند که شما قبلاً یک برنامه رسانه ای دارید که صدا را از طریق تلفن پخش می کند و برنامه رسانه شما با معماری برنامه Android Media مطابقت دارد.
این راهنما مؤلفه های مورد نیاز یک MediaBrowserService
و MediaSession
را که برنامه شما به منظور کار در سیستم عامل Android Auto یا Android Automobile Astone نیاز دارد ، توصیف می کند. پس از اتمام زیرساخت های اصلی رسانه ، می توانید پشتیبانی از Android Auto را اضافه کرده و پشتیبانی از سیستم عامل Android Automobile را به برنامه رسانه خود اضافه کنید .
قبل از شروع
- مستندات API Android Media را مرور کنید.
- بررسی برنامه های رسانه ای را برای راهنمایی طراحی ایجاد کنید .
- اصطلاحات و مفاهیم کلیدی ذکر شده در این بخش را مرور کنید.
اصطلاحات و مفاهیم کلیدی
- سرویس مرورگر رسانه ای
- یک سرویس Android که توسط برنامه رسانه ای شما اجرا شده است و مطابق با API
MediaBrowserServiceCompat
است. برنامه شما از این سرویس برای افشای محتوای خود استفاده می کند. - مرورگر رسانه
- API که توسط برنامه های رسانه ای برای کشف خدمات مرورگر رسانه و نمایش محتوای آنها استفاده می شود. Android Auto و Android Automobile Os از یک مرورگر رسانه برای یافتن سرویس مرورگر رسانه برنامه خود استفاده کنید.
- آیتم رسانه ای
مرورگر رسانه محتوای خود را در درختی از اشیاء
MediaItem
سازماندهی می کند. یک مورد رسانه ای می تواند یا هر دو پرچم زیر را داشته باشد:-
FLAG_PLAYABLE
: نشان می دهد که این مورد برگ بر روی درخت محتوا است. این مورد یک جریان صدا را نشان می دهد ، مانند یک آهنگ در یک آلبوم ، یک فصل در یک کتاب صوتی یا یک قسمت از پادکست. -
FLAG_BROWSABLE
: نشان می دهد که این مورد گره ای در درخت محتوا است و دارای کودکان است. به عنوان مثال ، این مورد یک آلبوم را نشان می دهد ، و فرزندان آن آهنگ های موجود در این آلبوم هستند.
یک مورد رسانه ای که هم مانند یک لیست پخش قابل کشت و قابل پخش است. شما می توانید خود را برای بازی در همه فرزندان خود انتخاب کنید ، یا می توانید فرزندان آن را مرور کنید.
-
- بهینه شده وسیله نقلیه
فعالیتی برای یک برنامه سیستم عامل Android Automobile Os که به دستورالعمل های طراحی سیستم عامل Android Automobile Adroid پایبند است. رابط کاربری این فعالیت ها توسط سیستم عامل Android Automobile ترسیم نمی شود ، بنابراین باید اطمینان حاصل کنید که برنامه شما به دستورالعمل های طراحی پایبند است. به طور معمول ، این شامل اهداف شیر بزرگ و اندازه قلم ، پشتیبانی از حالت های روز و شب و نسبت های کنتراست بالاتر است.
رابط های کاربر بهینه شده وسیله نقلیه فقط در صورتی که محدودیت های تجربه کاربر خودرو (CUXRS) به کار نمی روند ، مجاز به نمایش داده می شوند ، زیرا این رابط ها می توانند نیاز به توجه یا تعامل گسترده از کاربر داشته باشند. هنگامی که ماشین متوقف یا پارک می شود ، CuxR ها عملی نیستند اما هنگام حرکت خودرو همیشه در حال کار هستند.
شما نیازی به طراحی فعالیت برای Android Auto ندارید ، زیرا Android Auto با استفاده از اطلاعات سرویس مرورگر رسانه خود ، رابط بهینه شده وسیله نقلیه خود را ترسیم می کند.
پرونده های مانیفست برنامه خود را پیکربندی کنید
قبل از اینکه بتوانید سرویس مرورگر رسانه خود را ایجاد کنید ، باید پرونده های مانیفست برنامه خود را پیکربندی کنید.
سرویس مرورگر رسانه خود را اعلام کنید
هر دو سیستم عامل Android Auto و Android Automobile Os از طریق سرویس مرورگر رسانه خود برای مرور موارد رسانه ای به برنامه خود متصل می شوند. سرویس مرورگر رسانه خود را در مانیفست خود اعلام کنید تا اجازه دهید Android Auto و Android Automobile Os سرویس را کشف کرده و به برنامه خود متصل شود.
قطعه کد زیر نحوه اعلام سرویس مرورگر رسانه خود را در مانیفست خود نشان می دهد. این کد را در پرونده مانیفست برای ماژول سیستم عامل Android Automobile 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 Automobile Os می توانند از آن استفاده کنند تا برنامه شما را در UI سیستم نشان دهند. دو نوع نماد مورد نیاز است:
- نماد پرتابگر
- نماد انتساب
نماد پرتابگر
نماد پرتابگر برنامه شما در UI سیستم ، مانند پرتابگر و سینی آیکون ها را نشان می دهد. می توانید مشخص کنید که می خواهید از برنامه تلفن همراه خود از نماد خود استفاده کنید تا برنامه رسانه اتومبیل خود را با استفاده از اعلامیه مانیفست زیر نشان دهید:
<application
...
android:icon="@mipmap/ic_launcher"
...
/>
برای استفاده از یک نماد متفاوت از برنامه تلفن همراه خود ، ویژگی android:icon
در عنصر <service>
سرویس مرورگر رسانه خود در مانیفست تنظیم کنید:
<application>
...
<service
...
android:icon="@mipmap/auto_launcher"
...
/>
</application>
نماد انتساب
نماد انتساب در مکانهایی که محتوای رسانه ای از آن استفاده می کند ، مانند کارت های رسانه ای استفاده می شود. در نظر بگیرید که از نماد کوچک استفاده شده برای اعلان ها استفاده کنید. این نماد باید تک رنگ باشد. می توانید نمادی را که برای نمایش برنامه خود با استفاده از اعلامیه مانیفست زیر استفاده می شود ، مشخص کنید:
<application>
...
<meta-data
android:name="androidx.car.app.TintableAttributionIcon"
android:resource="@drawable/ic_status_icon" />
...
</application>
سرویس مرورگر رسانه خود را ایجاد کنید
شما با گسترش کلاس MediaBrowserServiceCompat
یک سرویس مرورگر رسانه ای ایجاد می کنید. هر دو سیستم عامل Android Auto و Android Automobile می توانند از خدمات شما برای انجام موارد زیر استفاده کنند:
- برای ارائه منوی به کاربر ، سلسله مراتب محتوای برنامه خود را مرور کنید.
- برای کنترل پخش صوتی ، Token for
MediaSessionCompat
Object خود را دریافت کنید.
همچنین می توانید از سرویس مرورگر رسانه خود استفاده کنید تا سایر مشتریان از برنامه خود به محتوای رسانه دسترسی پیدا کنند. این مشتری های رسانه ای ممکن است برنامه های دیگری در تلفن کاربر باشند ، یا می توانند سایر مشتری های از راه دور باشند.
گردش کار خدمات مرورگر رسانه
در این بخش نحوه تعامل سیستم عامل Android Automobile Os و Android با سرویس مرورگر رسانه شما در طی یک گردش کار کاربر معمولی توضیح داده شده است.
- کاربر برنامه شما را در سیستم عامل Android Automobile یا Android Auto راه اندازی می کند.
- Android Automobile Os یا Android Auto با استفاده از روش
onCreate()
با سرویس مرورگر رسانه برنامه شما تماس می گیرد. در اجرای روشonCreate()
، باید یک شیءMediaSessionCompat
و شیء پاسخ به آن را ایجاد و ثبت کنید. - Android Automobile Os یا Android Auto با روش
onGetRoot()
سرویس شما تماس می گیرد تا مورد اصلی رسانه را در سلسله مراتب محتوای خود بدست آورید. مورد رسانه اصلی نمایش داده نمی شود. در عوض ، از آن برای بازیابی مطالب بیشتر از برنامه خود استفاده می شود. - Android Automobile Os یا Android Auto روش
onLoadChildren()
خدمات شما را برای گرفتن کالاهای رسانه ای ریشه فراخوانی می کند. سیستم عامل Android Automobile و Android Auto این موارد رسانه ای را به عنوان سطح برتر موارد محتوا نمایش می دهند. برای اطلاعات بیشتر در مورد آنچه سیستم در این سطح انتظار دارد ، به منوی ریشه در این صفحه مراجعه کنید. - اگر کاربر یک مورد رسانه ای قابل مرور را انتخاب کند ، روش
onLoadChildren()
سرویس شما دوباره برای بازیابی فرزندان مورد منوی انتخاب شده فراخوانی می شود. - اگر کاربر یک مورد رسانه ای قابل پخش را انتخاب کند ، سیستم عامل Android Automobile یا Android Auto برای انجام این عمل ، روش پاسخگویی به جلسه رسانه مناسب را فراخوانی می کند.
- اگر توسط برنامه شما پشتیبانی شود ، کاربر نیز می تواند محتوای شما را جستجو کند. در این حالت ، سیستم عامل Android Automobile یا Android Auto با روش
onSearch()
خدمات خود تماس بگیرید.
سلسله مراتب محتوای خود را بسازید
Android Auto و Android Automobile OS با سرویس مرورگر رسانه برنامه خود تماس بگیرید تا دریابید که چه محتوا در دسترس است. برای پشتیبانی از این: onGetRoot()
و onLoadChildren()
باید دو روش را در سرویس مرورگر رسانه خود پیاده سازی کنید.
اجرای OngetRoot
روش onGetRoot()
سرویس شما اطلاعات مربوط به گره ریشه سلسله مراتب محتوای شما را برمی گرداند. Android Auto و Android Automobile OS از این گره ریشه استفاده کنید تا بقیه محتوای خود را با استفاده از روش onLoadChildren()
درخواست کنید.
قطعه کد زیر اجرای ساده ای از روش onGetRoot()
را نشان می دهد:
کاتلین
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); }
برای مثال بیشتر از این روش ، به روش onGetRoot()
در برنامه نمونه پخش کننده موسیقی Android Universal در GitHub مراجعه کنید.
افزودن اعتبار سنجی بسته برای OngetRoot ()
هنگامی که یک تماس با روش onGetRoot()
سرویس شما برقرار می شود ، بسته تماس با شناسایی اطلاعات به خدمات شما منتقل می شود. سرویس شما می تواند از این اطلاعات استفاده کند تا تصمیم بگیرد که آیا این بسته می تواند به محتوای شما دسترسی پیدا کند. به عنوان مثال ، می توانید با مقایسه clientPackageName
با لیست اجازه و تأیید گواهی مورد استفاده برای امضای APK بسته ، دسترسی به محتوای برنامه خود را به لیستی از بسته های تأیید شده محدود کنید. اگر بسته بندی قابل تأیید نیست ، برای انکار دسترسی به محتوای خود null
را برگردانید.
برای ارائه برنامه های سیستم ، مانند Android Auto و Android Automobile Os ، با دسترسی به محتوای شما ، سرویس شما همیشه باید هنگام تماس با این برنامه های سیستم با روش onGetRoot()
یک BrowserRoot
غیر تهی برگرداند. امضای برنامه سیستم سیستم عامل Android Automobile OS بسته به ساخت و مدل خودرو می تواند متفاوت باشد ، بنابراین شما باید برای پشتیبانی از سیستم عامل Android Automobile Android ، اتصالات را از همه برنامه های سیستم اجازه دهید.
قطعه کد زیر نشان می دهد که چگونه سرویس شما می تواند اعتبار دهد که بسته تماس یک برنامه سیستم است:
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
}
این قطعه کد گزیده ای از کلاس PackageValidator
در برنامه نمونه پخش کننده موسیقی Android Universal در GitHub است. برای مثال دقیق تر نحوه اجرای اعتبار سنجی بسته برای روش onGetRoot()
سرویس خود ، آن کلاس را مشاهده کنید.
علاوه بر اجازه برنامه های سیستم ، باید به دستیار Google اجازه دهید به MediaBrowserService
خود متصل شود. توجه داشته باشید که دستیار Google دارای نام بسته های جداگانه ای برای تلفن است که شامل Android Auto و برای سیستم عامل Android Automobile است.
اجرای بلورهای بلوک ()
پس از دریافت شیء گره ریشه خود ، Android Auto و Android Automobile Os یک منوی سطح بالا را با فراخوانی onLoadChildren()
در شیء گره ریشه برای به دست آوردن فرزندان خود بسازید. برنامه های مشتری با فراخوانی همین روش با استفاده از اشیاء گره کودک ، زیرمنوس می سازند.
هر گره در سلسله مراتب محتوای شما توسط یک شیء MediaBrowserCompat.MediaItem
نشان داده شده است. هر یک از این موارد رسانه ای توسط یک رشته شناسه منحصر به فرد مشخص می شود. برنامه های مشتری این رشته های شناسه را به عنوان نشانه های مات رفتار می کنند. هنگامی که یک برنامه مشتری می خواهد به یک زیرمنو مرور کند ، یا یک مورد رسانه ای بازی کند ، این نشانه را منتقل می کند. برنامه شما وظیفه ارتباط این نشانه را با مورد رسانه مناسب دارد.
قطعه کد زیر یک روش ساده از روش onLoadChildren()
را نشان می دهد:
کاتلین
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 whether 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 whether 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); }
برای مثال کامل این روش ، به روش onLoadChildren()
در برنامه نمونه پخش کننده موسیقی جهانی اندرویدی در GitHub مراجعه کنید.
منوی ریشه را ساختار دهید
Android Auto و Android Automobile Os محدودیت های خاصی در مورد ساختار منوی ریشه دارند. اینها از طریق نکات ریشه ای به MediaBrowserService
ابلاغ می شوند ، که می توان از طریق استدلال Bundle
که به onGetRoot()
بخوانید. به دنبال این نکات به سیستم اجازه می دهد تا محتوای ریشه را به صورت بهینه به عنوان زبانه های ناوبری نمایش دهد. اگر این نکات را رعایت نکنید ، ممکن است برخی از محتوای ریشه کاهش یابد یا توسط سیستم کمتر کشف شود. دو نکته ارسال می شود:
- محدودیت در تعداد کودکان ریشه : برای اکثر موارد ، می توانید انتظار داشته باشید که این تعداد چهار نفر باشد. این بدان معنی است که بیش از چهار برگه نمی توان نشان داد.
- پرچم های پشتیبانی شده در مورد کودکان ریشه : شما می توانید انتظار داشته باشید که این مقدار
MediaItem#FLAG_BROWSABLE
باشد. این بدان معنی است که فقط موارد قابل مرور - موارد قابل پخش - می توانند به صورت زبانه نشان داده شوند.
برای خواندن نکات مربوط به ریشه از کد زیر استفاده کنید:
کاتلین
import androidx.media.utils.MediaConstants // Later, in your MediaBrowserServiceCompat. override fun onGetRoot( clientPackageName: String, clientUid: Int, rootHints: Bundle ): BrowserRoot { val maximumRootChildLimit = rootHints.getInt( MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_LIMIT, /* defaultValue= */ 4) val supportedRootChildFlags = rootHints.getInt( MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_SUPPORTED_FLAGS, /* defaultValue= */ MediaItem.FLAG_BROWSABLE) // Rest of method... }
جاوا
import androidx.media.utils.MediaConstants; // Later, in your MediaBrowserServiceCompat. @Override public BrowserRoot onGetRoot( String clientPackageName, int clientUid, Bundle rootHints) { int maximumRootChildLimit = rootHints.getInt( MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_LIMIT, /* defaultValue= */ 4); int supportedRootChildFlags = rootHints.getInt( MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_SUPPORTED_FLAGS, /* defaultValue= */ MediaItem.FLAG_BROWSABLE); // Rest of method... }
You can choose to branch the logic for the structure of your content hierarchy based on the values of these hints, particularly if your hierarchy varies among MediaBrowser
integrations outside of Android Auto and Android Automotive OS. For example, if you normally show a root playable item, you might want to nest it under a root browsable item instead due to the value of the supported flags hint.
Apart from the root hints, there are a couple additional guidelines to follow to help ensure that tabs render optimally:
- Supply monochrome, preferably white, icons for each tab item.
- Supply short but meaningful labels for each tab item. Keeping labels short reduces the chance of the strings being truncated.
Display media artwork
Artwork for media items must be passed as a local URI using either ContentResolver.SCHEME_CONTENT
or ContentResolver.SCHEME_ANDROID_RESOURCE
. This local URI must resolve to either a bitmap or a vector drawable in the application's resources. For MediaDescriptionCompat
objects representing items in the content hierarchy, pass the URI through setIconUri()
. For MediaMetadataCompat
objects representing the currently playing item, pass the URI through putString()
, using any of the following keys:
-
MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI
-
MediaMetadataCompat.METADATA_KEY_ART_URI
-
MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI
The following steps describe how to download art from a web URI and expose it through a local URI. For a more complete example, see the implementation of openFile()
and the surrounding methods in the Universal Android Music Player sample app.
Build a
content://
URI corresponding to the web URI. The media browser service and media session pass this content URI to Android Auto and Android Automotive OS.کاتلین
fun Uri.asAlbumArtContentURI(): Uri { return Uri.Builder() .scheme(ContentResolver.SCHEME_CONTENT) .authority(CONTENT_PROVIDER_AUTHORITY) .appendPath(this.getPath()) // Make sure you trust the URI .build() }
جاوا
public static Uri asAlbumArtContentURI(Uri webUri) { return new Uri.Builder() .scheme(ContentResolver.SCHEME_CONTENT) .authority(CONTENT_PROVIDER_AUTHORITY) .appendPath(webUri.getPath()) // Make sure you trust the URI! .build(); }
In your implementation of
ContentProvider.openFile()
, check whether a file exists for the corresponding URI. If not, download and cache the image file. The following code snippet uses Glide .کاتلین
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? { val context = this.context ?: return null val file = File(context.cacheDir, uri.path) if (!file.exists()) { val remoteUri = Uri.Builder() .scheme("https") .authority("my-image-site") .appendPath(uri.path) .build() val cacheFile = Glide.with(context) .asFile() .load(remoteUri) .submit() .get(DOWNLOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS) cacheFile.renameTo(file) file = cacheFile } return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY) }
جاوا
@Nullable @Override public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException { Context context = this.getContext(); File file = new File(context.getCacheDir(), uri.getPath()); if (!file.exists()) { Uri remoteUri = new Uri.Builder() .scheme("https") .authority("my-image-site") .appendPath(uri.getPath()) .build(); File cacheFile = Glide.with(context) .asFile() .load(remoteUri) .submit() .get(DOWNLOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS); cacheFile.renameTo(file); file = cacheFile; } return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); }
For more details about content providers, refer to Creating a content provider .
Apply content styles
After building your content hierarchy using browsable or playable items, you can apply content styles that determine how those items display in the car.
You can use the following content styles:
- List items
This content style prioritizes titles and metadata over images.
- Grid items
This content style prioritizes images over titles and metadata.
Set default content styles
You can set global defaults for how your media items are displayed by including certain constants in the BrowserRoot
extras bundle of your service's onGetRoot()
method. Android Auto and Android Automotive OS read this bundle and look for those constants to determine the appropriate style.
The following extras can be used as keys in the bundle:
-
DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE
: indicates a presentation hint for all browsable items within the browse tree. -
DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE
: indicates a presentation hint for all playable items within the browse tree.
The keys can map to the following integer constant values to influence the presentation of those items:
-
DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM
: the corresponding items are presented as list items. -
DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM
: the corresponding items are presented as grid items. -
DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM
: the corresponding items are presented as "category" list items. These are the same as ordinary list items except that margins are applied around the items' icons, since the icons look better when they are small. The icons must be tintable vector drawables. This hint is expected to only be provided for browsable items. -
DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_GRID_ITEM
: the corresponding items are presented as "category" grid items. These are the same as ordinary grid items, except that margins are applied around the items' icons, since the icons look better when they are small. The icons must be tintable vector drawables. This hint is expected to only be provided for browsable items.
The following code snippet shows how to set the default content style for browsable items to grids and playable items to lists:
کاتلین
import androidx.media.utils.MediaConstants @Nullable override fun onGetRoot( @NonNull clientPackageName: String, clientUid: Int, @Nullable rootHints: Bundle ): BrowserRoot { val extras = Bundle() extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM) return BrowserRoot(ROOT_ID, extras) }
جاوا
import androidx.media.utils.MediaConstants; @Nullable @Override public BrowserRoot onGetRoot( @NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) { Bundle extras = new Bundle(); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM); return new BrowserRoot(ROOT_ID, extras); }
Set per-item content styles
The Content Style API lets you override the default content style for any browsable media item's children, as well as any media item itself.
To override the default for a browsable media item's children , create an extras bundle in the MediaDescription
of the media item and add the same previously mentioned hints. DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE
applies to that item's playable children, while DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE
applies to that item's browsable children.
To override the default for a particular media item itself , not its children, create an extras bundle in the MediaDescription
of the media item and add a hint with the key DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM
. Use the same values described previously to specify that item's presentation.
The following code snippet shows how to create a browsable MediaItem
that overrides the default content style for both itself and its children. It styles itself as a category list item, its browsable children as list items, and its playable children as grid items:
کاتلین
import androidx.media.utils.MediaConstants 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( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM) mediaDescriptionBuilder.setExtras(extras) return MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE) }
جاوا
import androidx.media.utils.MediaConstants; 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( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM); mediaDescriptionBuilder.setExtras(extras); return new MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE); }
Group items using title hints
To group related media items together, you use a per-item hint. Every media item in a group needs to declare an extras bundle in their MediaDescription
that includes a mapping with the key DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE
and an identical string value. Localize this string, which is used as the title of the group.
The following code snippet shows how to create a MediaItem
with a subgroup heading of "Songs"
:
کاتلین
import androidx.media.utils.MediaConstants 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( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs") mediaDescriptionBuilder.setExtras(extras) return MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), /* playable or browsable flag*/) }
جاوا
import androidx.media.utils.MediaConstants; 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( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs"); mediaDescriptionBuilder.setExtras(extras); return new MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), /* playable or browsable flag*/); }
Your app must pass all the media items that you want to group together as a contiguous block. For example, suppose that you want to display two groups of media items, "Songs" and "Albums," in that order, and your app passes in five media items in the following order:
- Media item A with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Media item B with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )
- Media item C with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Media item D with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Media item E with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )
Because the media items for the "Songs" group and "Albums" group are not kept together in contiguous blocks, Android Auto and Android Automotive OS interprets this as the following four groups:
- Group 1 called "Songs" containing media item A
- Group 2 called "Albums" containing media item B
- Group 3 called "Songs" containing media items C and D
- Group 4 called "Albums" containing media item E
To display these items in two groups, your app must pass the media items in the following order instead:
- Media item A with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Media item C with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Media item D with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Media item B with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )
- Media item E with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )
Display additional metadata indicators
You can include additional metadata indicators to provide at-a-glance information for content in the media browser tree and during playback. Within the browse tree, Android Auto and Android Automotive OS read the extras associated with an item and look for certain constants to determine which indicators to display. During media playback, Android Auto and Android Automotive OS read the metadata for the media session and look for certain constants to determine indicators to display.
The following constants can be used in both MediaItem
description extras and MediaMetadata
extras:
-
EXTRA_DOWNLOAD_STATUS
: indicates the download status of an item. Use this constant as the key; the following long constants are the possible values:-
STATUS_DOWNLOADED
: the item is completely downloaded. -
STATUS_DOWNLOADING
: the item is being downloaded. -
STATUS_NOT_DOWNLOADED
: the item is not downloaded.
-
-
METADATA_KEY_IS_EXPLICIT
: indicates whether the item contains explicit content. To indicate an item is explicit, use this constant as the key and the longMETADATA_VALUE_ATTRIBUTE_PRESENT
as the value.
The following constants can only be used in MediaItem
description extras:
-
DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS
: indicates the completion state of long-form content, such as podcast episodes or audiobooks. Use this constant as the key; the following integer constants are the possible values:-
DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED
: the item has not been played at all. -
DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED
: the item has been partially played, and the current position is somewhere in the middle. -
DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED
: the item has been completed.
-
-
DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
: indicates the amount of completion progress on long-form content as a double between 0.0 and 1.0, inclusive. This extra provides more information about thePARTIALLY_PLAYING
state so that Android Auto or Android Automotive OS displays a more meaningful progress indicator, such as a progress bar. If you use this extra, see the section about updating the progress bar in the browse view as content is playing in this guide to learn how to keep this indicator up to date after its initial impression.
To display indicators that appear while the user is browsing the media browse tree, create an extras bundle that includes one or more of these constants and pass that bundle to the MediaDescription.Builder.setExtras()
method.
The following code snippet shows how to display indicators for an explicit media item that is 70% complete:
کاتلین
import androidx.media.utils.MediaConstants val extras = Bundle() extras.putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS, MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED) extras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.7) val description = MediaDescriptionCompat.Builder() .setMediaId(/*...*/) .setTitle(resources.getString(/*...*/)) .setExtras(extras) .build() return MediaBrowserCompat.MediaItem(description, /* flags */)
جاوا
import androidx.media.utils.MediaConstants; Bundle extras = new Bundle(); extras.putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS, MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED); extras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.7); MediaDescriptionCompat description = new MediaDescriptionCompat.Builder() .setMediaId(/*...*/) .setTitle(resources.getString(/*...*/)) .setExtras(extras) .build(); return new MediaBrowserCompat.MediaItem(description, /* flags */);
To display indicators for a media item that is currently being played, you can declare Long
values for METADATA_KEY_IS_EXPLICIT
or EXTRA_DOWNLOAD_STATUS
in the MediaMetadataCompat
of your mediaSession
. You can't display the DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS
or DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
indicators on the playback view.
The following code snippet shows how to indicate that the current song in the playback view is explicit and downloaded:
کاتلین
import androidx.media.utils.MediaConstants mediaSession.setMetadata( MediaMetadataCompat.Builder() .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, "Song Name") .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "Artist name") .putString( MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, albumArtUri.toString()) .putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) .putLong( MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS, MediaDescriptionCompat.STATUS_DOWNLOADED) .build())
جاوا
import androidx.media.utils.MediaConstants; mediaSession.setMetadata( new MediaMetadataCompat.Builder() .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, "Song Name") .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "Artist name") .putString( MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, albumArtUri.toString()) .putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) .putLong( MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS, MediaDescriptionCompat.STATUS_DOWNLOADED) .build());
Update the progress bar in the browse view as content is playing
As previously mentioned, you can use the DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
extra to show a progress bar for partially played content in the browse view. However, if a user continues playing the partially played content from Android Auto or Android Automotive OS, that indicator becomes inaccurate as time passes.
For Android Auto and Android Automotive OS to keep the progress bar up to date, you can supply additional information in MediaMetadataCompat
and PlaybackStateCompat
to link ongoing content to media items in the browse view. The following requirements must be met for the media item to have an automatically updating progress bar:
- When it is created, the
MediaItem
must sendDESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
in its extras with a value between 0.0 and 1.0, inclusive. - The
MediaMetadataCompat
must sendMETADATA_KEY_MEDIA_ID
with a string value equal to the media ID passed to theMediaItem
. - The
PlaybackStateCompat
must include an extra with the keyPLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID
that maps to a string value equal to the media ID passed to theMediaItem
.
The following code snippet shows how to indicate that the currently playing item is linked to an item in the browse view:
کاتلین
import androidx.media.utils.MediaConstants // When the MediaItem is constructed to show in the browse view. // Suppose the item was 25% complete when the user launched the browse view. val mediaItemExtras = Bundle() mediaItemExtras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.25) val description = MediaDescriptionCompat.Builder() .setMediaId("my-media-id") .setExtras(mediaItemExtras) // ...and any other setters. .build() return MediaBrowserCompat.MediaItem(description, /* flags */) // Elsewhere, when the user has selected MediaItem for playback. mediaSession.setMetadata( MediaMetadataCompat.Builder() .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, "my-media-id") // ...and any other setters. .build()) val playbackStateExtras = Bundle() playbackStateExtras.putString( MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID, "my-media-id") mediaSession.setPlaybackState( PlaybackStateCompat.Builder() .setExtras(playbackStateExtras) // ...and any other setters. .build())
جاوا
import androidx.media.utils.MediaConstants; // When the MediaItem is constructed to show in the browse view. // Suppose the item was 25% complete when the user launched the browse view. Bundle mediaItemExtras = new Bundle(); mediaItemExtras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.25); MediaDescriptionCompat description = new MediaDescriptionCompat.Builder() .setMediaId("my-media-id") .setExtras(mediaItemExtras) // ...and any other setters. .build(); return MediaBrowserCompat.MediaItem(description, /* flags */); // Elsewhere, when the user has selected MediaItem for playback. mediaSession.setMetadata( new MediaMetadataCompat.Builder() .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, "my-media-id") // ...and any other setters. .build()); Bundle playbackStateExtras = new Bundle(); playbackStateExtras.putString( MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID, "my-media-id"); mediaSession.setPlaybackState( new PlaybackStateCompat.Builder() .setExtras(playbackStateExtras) // ...and any other setters. .build());
Display browsable search results
Your app can provide contextual search results that display to users when they initiate a search query. Android Auto and Android Automotive OS show these results through search query interfaces or through affordances that pivot on queries made earlier in the session. To learn more, see the Support voice actions section in this guide.
To display browsable search results, include the constant key BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED
in the extras bundle of your service's onGetRoot()
method, mapping to the boolean true
.
The following code snippet shows how to enable support in the onGetRoot()
method:
کاتلین
import androidx.media.utils.MediaConstants @Nullable fun onGetRoot( @NonNull clientPackageName: String, clientUid: Int, @Nullable rootHints: Bundle ): BrowserRoot { val extras = Bundle() extras.putBoolean( MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true) return BrowserRoot(ROOT_ID, extras) }
جاوا
import androidx.media.utils.MediaConstants; @Nullable @Override public BrowserRoot onGetRoot( @NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) { Bundle extras = new Bundle(); extras.putBoolean( MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true); return new BrowserRoot(ROOT_ID, extras); }
To start providing search results, override the onSearch()
method in your media browser service. Android Auto and Android Automotive OS forward the user's search terms to this method whenever a user invokes a search query interface or “Search results” affordance.
You can organize the search results from your service's onSearch()
method using title items to make them more browsable. For example, if your app plays music, you might organize search results by album, artist, and songs.
The following code snippet shows a simple implementation of the onSearch()
method:
کاتلین
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<List<MediaItem>> result) { // Detach from results to unblock the caller (if a search is expensive). result.detach(); new AsyncTask<Void, Void, Void>() { List<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); } } }.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. }
Custom Browse Actions
Custom Browse Actions allow you to add custom icons and labels to your app's MediaItem
objects in the car's media app, and handle user interactions with these actions. This lets you extend the functionality of the Media App in a variety of ways, like adding "Download", "Add to Queue", "Play Radio", "Favorite", or "Remove" actions.
If there are more custom actions than the OEM allows to be displayed, an overflow menu will be presented to the user.
How do they work?
Each Custom Browse Action is defined with:
- An Action ID (a unique string identifier)
- An Action Label (the text displayed to the user)
- An Action Icon URI (a vector drawable that can be tinted)
You define a list of Custom Browse Actions globally as part of your BrowseRoot
. Then you can attach a subset of these actions to individual MediaItem.
When a user interacts with a Custom Browse Action, your app receives a callback in onCustomAction()
. You can then handle the action and update the list of actions for the MediaItem
if necessary. This is useful for stateful actions like "Favorite" and "Download". For actions that don't need updating, like "Play Radio", you don't need to update the list of actions.
You can also attach Custom Browse Actions to a browse node root. These actions will be displayed in a secondary toolbar under the main toolbar.
How to implement Custom Browse Actions
Here are the steps to add Custom Browse Actions to your project:
- Override two methods in your
MediaBrowserServiceCompat
implementation: - Parse the action limits at runtime:
- In
onGetRoot()
, get the maximum number of actions allowed for eachMediaItem
using the keyBROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT
in therootHints
Bundle
. A limit of 0 indicates that the feature is not supported by the system.
- In
- Build the global list of Custom Browse Actions:
- For each action, create a
Bundle
object with the following keys: *EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID
: The action ID *EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL
: The action label *EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI
: The action icon URI * Add all the actionBundle
objects to a list.
- For each action, create a
- Add the global list to your
BrowseRoot
:- In the
BrowseRoot
extrasBundle
, add the list of actions as aParcelable
Arraylist
using the keyBROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST
.
- In the
- Add actions to your
MediaItem
objects:- You can add actions to individual
MediaItem
objects by including the list of action IDs in theMediaDescriptionCompat
extras using the keyDESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST
. This list must be a subset of the global list of actions you defined in theBrowseRoot
.
- You can add actions to individual
- Handle actions and return progress or results:
- In
onCustomAction
, handle the action based on the action ID and any other data you need. You can get the ID of theMediaItem
that triggered the action from the extras using the keyEXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID
. . - You can update the list of actions for a
MediaItem
by including the keyEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
in the progress or result bundle.
- In
Here are some changes you can make in your BrowserServiceCompat
to get started with Custom Browse Actions.
Override BrowserServiceCompat
You need to override the following methods in MediaBrowserServiceCompat
.
public void onLoadItem(String itemId, @NonNull Result<MediaBrowserCompat.MediaItem> result)
public void onCustomAction(@NonNull String action, Bundle extras, @NonNull Result<Bundle> result)
Parse actions limit
You should check to see how many Custom Browse Actions are supported.
public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, Bundle rootHints) { rootHints.getInt( MediaConstants.BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT, 0) }
Build a Custom Browse Action
Each action needs to be packed into a separate Bundle
.
- Action ID
bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID, "<ACTION_ID>")
- Action Label
bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL, "<ACTION_LABEL>")
- Action Icon URI
bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI, "<ACTION_ICON_URI>")
Add Custom Browse Actions to Parceable
ArrayList
Add all Custom Browse Action Bundle
objects into an ArrayList
.
private ArrayList<Bundle> createCustomActionsList( CustomBrowseAction browseActions) { ArrayList<Bundle> browseActionsBundle = new ArrayList<>(); for (CustomBrowseAction browseAction : browseActions) { Bundle action = new Bundle(); action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID, browseAction.mId); action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL, getString(browseAction.mLabelResId)); action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI, browseAction.mIcon); browseActionsBundle.add(action); } return browseActionsBundle; }
Add Custom Browse Action list to the browse root
public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, Bundle rootHints) { Bundle browserRootExtras = new Bundle(); browserRootExtras.putParcelableArrayList( BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST, createCustomActionsList())); mRoot = new BrowserRoot(ROOT_ID, browserRootExtras); return mRoot; }
Add actions to a MediaItem
MediaDescriptionCompat buildDescription (long id, String title, String subtitle, String description, Uri iconUri, Uri mediaUri, ArrayList<String> browseActionIds) { MediaDescriptionCompat.Builder bob = new MediaDescriptionCompat.Builder(); bob.setMediaId(id); bob.setTitle(title); bob.setSubtitle(subtitle); bob.setDescription(description); bob.setIconUri(iconUri); bob.setMediaUri(mediaUri); Bundle extras = new Bundle(); extras.putStringArrayList( DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST, browseActionIds); bob.setExtras(extras); return bob.build(); } MediaItem mediaItem = new MediaItem(buildDescription(...), flags);
Build onCustomAction
result
- Parse mediaId from
Bundle extras
:@Override public void onCustomAction( @NonNull String action, Bundle extras, @NonNull Result<Bundle> result){ String mediaId = extras.getString(MediaConstans.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID); }
- For asynchronous results detach result.
result.detach()
- Build result bundle
- Message to user
mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE, mContext.getString(stringRes))
- Update item(use to update actions in an item)
mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM, mediaId);
- Open Playback view
//Shows user the PBV without changing the playback state mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM, null);
- Update Browse Node
//Change current browse node to mediaId mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE, mediaId);
- Message to user
- If an error, call
result.sendError(resultBundle).
- If progress update, call
result.sendProgressUpdate(resultBundle)
. - Finish by calling
result.sendResult(resultBundle)
.
Update Action State
By using the result.sendProgressUpdate(resultBundle)
method with the EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
key, you can update the MediaItem
to reflect the new state of the action. This lets you provide real-time feedback to the user about the progress and result of their action.
Example: Download Action
Here's an example of how you can use this feature to implement a download action with three states:
- Download: This is the initial state of the action. When the user selects this action, you can swap it with "Downloading" and call
sendProgressUpdate
to update the UI. - Downloading: This state indicates that the download is in progress. You can use this state to show a progress bar or another indicator to the user.
- Downloaded: This state indicates that the download is complete. When the download finishes, you can swap "Downloading" with "Downloaded" and call
sendResult
with theEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
key to indicate that the item should be refreshed. Additionally, you can use theEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE
key to display a success message to the user.
This approach lets you provide clear feedback to the user about the download process and its current state. You can add even more detail with icons to show 25%, 50%, 75% download states.
Example: Favorite Action
Another example is a favorite action with two states:
- Favorite: This action is displayed for items that are not in the user's favorites list. When the user selects this action, you can swap it with "Favorited" and call
sendResult
with theEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
key to update the UI. - Favorited: This action is displayed for items that are in the user's favorites list. When the user selects this action, you can swap it with "Favorite" and call
sendResult
with theEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
key to update the UI.
This approach provides a clear and consistent way for users to manage their favorite items.
These examples showcase the flexibility of Custom Browse Actions and how you can use them to implement a variety of functionalities with real-time feedback for an enhanced user experience in the car's media app.
For a complete example implementation of this feature, you can refer to the TestMediaApp
project.
Enable playback control
Android Auto and Android Automotive OS send playback control commands through your service's MediaSessionCompat
. You must register a session and implement the associated callback methods.
Register a media session
In your media browser service's onCreate()
method, create a MediaSessionCompat
, then register the media session by calling setSessionToken()
.
The following code snippet shows how to create and register a media session:
کاتلین
override fun onCreate() { super.onCreate() ... // Start a new MediaSession. val session = MediaSessionCompat(this, "session tag").apply { // Set a callback object that implements MediaSession.Callback // to handle play control requests. 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 that implements MediaSession.Callback // to handle play control requests. session.setCallback(new MyMediaSessionCallback()); ... }
When you create the media session object, you set a callback object that is used to handle playback control requests. You create this callback object by providing an implementation of the MediaSessionCompat.Callback
class for your app. The next section discusses how to implement this object.
Implement play commands
When a user requests playback for a media item from your app, Android Automotive OS and Android Auto use the MediaSessionCompat.Callback
class from your app's MediaSessionCompat
object that they obtained from your app's media browser service. When a user wants to control content playback, such as pausing playback or skipping to the next track, Android Auto and Android Automotive OS invoke one of the callback object's methods.
To handle content playback, your app must extend the abstract MediaSessionCompat.Callback
class and implement the methods that your app supports.
Implement all the following callback methods that make sense for the type of content that your app offers:
-
onPrepare()
- Invoked when the media source is changed. Android Automotive OS also invokes this method immediately after booting. Your media app must implement this method.
-
onPlay()
- Invoked if the user chooses play without choosing a specific item. Your app must play its default content or, if playback was paused with
onPause()
, your app resumes playback.Note: Your app should not automatically start playing music when Android Automotive OS or Android Auto connect to your media browser service. For more information, see the section about setting the initial playback state .
-
onPlayFromMediaId()
- Invoked when the user chooses to play a specific item. The method is passed the ID that your media browser service assigned to the media item in your content hierarchy.
-
onPlayFromSearch()
- Invoked when the user chooses to play from a search query. The app must make an appropriate choice based on the search string that was passed in.
-
onPause()
- Invoked when the user chooses to pause playback.
-
onSkipToNext()
- Invoked when the user chooses to skip to the next item.
-
onSkipToPrevious()
- Invoked when the user chooses to skip to the previous item.
-
onStop()
- Invoked when the user chooses to stop playback.
Override these methods in your app to provide any desired functionality. You don't need to implement a method if its functionality isn't supported by your app. For example, if your app plays a live stream, such as a sports broadcast, you don't need to implement the onSkipToNext()
method. You can use the default implementation of onSkipToNext()
instead.
Your app doesn't need any special logic to play content through the car's speakers. When your app receives a request to play content, it can play audio the same way that it plays content through a user's phone speakers or headphones. Android Auto and Android Automotive OS automatically send the audio content to the car's system to play over the car's speakers.
For more information about playing audio content, see MediaPlayer overview , Audio app overview , and the ExoPlayer overview .
Set standard playback actions
Android Auto and Android Automotive OS display playback controls based on the actions that are enabled in the PlaybackStateCompat
object.
By default, your app must support the following actions:
Your app can additionally support the following actions if they are relevant to the app's content:
In addition, you have the option to create a play queue that can be displayed for the user, but it is not required. To do this, call the setQueue()
and setQueueTitle()
methods, enable the ACTION_SKIP_TO_QUEUE_ITEM
action, and define the callback onSkipToQueueItem()
.
Also, add support for the Now playing icon, which is an indicator for what is currently playing. To do this, call the setActiveQueueItemId()
method and pass the ID of the currently playing item in the queue. You need to update setActiveQueueItemId()
whenever there is a queue change.
Android Auto and Android Automotive OS display buttons for each enabled action as well as the playback queue. When the buttons are clicked, the system invokes their corresponding callback from MediaSessionCompat.Callback
.
Reserve unused space
Android Auto and Android Automotive OS reserve space in the UI for the ACTION_SKIP_TO_PREVIOUS
and ACTION_SKIP_TO_NEXT
actions. If your app does not support one of these functions, Android Auto and Android Automotive OS use the space to display any custom actions you create.
If you don't want to fill those spaces with custom actions, you can reserve them so that Android Auto and Android Automotive OS leave the space blank whenever your app does not support the corresponding function. To do this, call the setExtras()
method with an extras bundle that contains constants that correspond to the reserved functions. SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT
corresponds to ACTION_SKIP_TO_NEXT
, and SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV
corresponds to ACTION_SKIP_TO_PREVIOUS
. Use these constants as keys in the bundle, and use the boolean true
for their values.
Set initial PlaybackState
As Android Auto and Android Automotive OS communicate with your media browser service, your media session communicates the status of content playback using the PlaybackStateCompat
. Your app should not automatically start playing music when Android Automotive OS or Android Auto connect to your media browser service. Instead, rely on Android Auto and Android Automotive OS to resume or start playback based on the car's state or user actions.
To accomplish this, set the initial PlaybackStateCompat
of your media session to STATE_STOPPED
, STATE_PAUSED
, STATE_NONE
, or STATE_ERROR
.
Media sessions within Android Auto and Android Automotive OS only last for the duration of the drive, so users start and stop these sessions frequently. To promote a seamless experience between drives, keep track of the user's previous session state, so that when the media app receives a resume request, the user can automatically pick up where they left off—for example, the last played media item, the PlaybackStateCompat
, and the queue.
Add custom playback actions
You can add custom playback actions to display additional actions that your media app supports. If space permits (and is not reserved) , Android adds the custom actions to the transport controls. Otherwise, the custom actions display in the overflow menu. Custom actions display in the order they are added to the PlaybackStateCompat
.
Use custom actions to provide behavior distinct from standard actions . Don't use them to replace or duplicate standard actions.
You can add custom actions using the addCustomAction()
method in the PlaybackStateCompat.Builder
class.
The following code snippet shows how to add a custom “Start a radio channel” action:
کاتلین
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());
For a more detailed example of this method, see the setCustomAction()
method in the Universal Android Music Player sample app on GitHub.
After creating your custom action, your media session can respond to the action by overriding the onCustomAction()
method.
The following code snippet shows how your app might respond to a “Start a radio channel” action:
کاتلین
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)) { ... } }
For a more detailed example of this method, see the onCustomAction
method in the Universal Android Music Player sample app on GitHub.
Icons for custom actions
Each custom action that you create requires an icon resource. Apps in cars can run on many different screen sizes and densities, so icons that you provide must be vector drawables . A vector drawable allows you to scale assets without losing the detail. A vector drawable also makes it easy to align edges and corners to pixel boundaries at smaller resolutions.
If a custom action is stateful—for example, it toggles a playback setting on or off—provide different icons for the different states, so users can see a change when they select the action.
Provide alternative icon styles for disabled actions
When a custom action is unavailable for the current context, swap the custom action icon with an alternative icon that shows that the action is disabled.
Indicate audio format
To indicate that currently playing media uses a special audio format, you can specify icons that are rendered in cars that support this feature. You can set the KEY_CONTENT_FORMAT_TINTABLE_LARGE_ICON_URI
and the KEY_CONTENT_FORMAT_TINTABLE_SMALL_ICON_URI
in the extras bundle of the currently playing media item (passed to MediaSession.setMetadata()
). Make sure to set both of those extras, to accommodate different layouts.
In addition, you can set the KEY_IMMERSIVE_AUDIO
extra to tell car OEMs that this is immersive audio, and they should be very careful when deciding whether to apply audio effects that might interfere with the immersive content.
Add links from currently-playing item
You can configure the currently-playing media item so its subtitle, description, or both are links to other media items. That lets the user jump quickly to related items; for example, they might jump to other songs by the same artist, other episodes of that podcast, etc. If the car supports this feature, users can tap the link to browse to that content.
To add links, configure the KEY_SUBTITLE_LINK_MEDIA_ID
metadata (to link from the subtitle) or KEY_DESCRIPTION_LINK_MEDIA_ID
(to link from the description). For details, see the reference documentation for those metadata fields.
Support voice actions
Your media app must support voice actions to help provide drivers with a safe and convenient experience that minimizes distractions. For example, if your app is playing one media item, the user can say “ Play [song title] " to tell your app to play a different song without looking at or touching the car's display. Users can initiate queries by clicking the appropriate buttons on their steering wheel or speaking the hotwords " OK Google ."
When Android Auto or Android Automotive OS detects and interprets a voice action, that voice action is delivered to the app through onPlayFromSearch()
. On receiving this callback, the app finds content matching the query
string and starts playback.
Users can specify different categories of terms in their query: genre, artist, album, song name, radio station, or playlist, among others. When building support for search, account for all the categories that make sense for your app. If Android Auto or Android Automotive OS detects that a given query fits into certain categories, it appends extras in the extras
parameter. The following extras can be sent:
Account for an empty query
string, which can be sent by Android Auto or Android Automotive OS if the user doesn't specify search terms. For example, if the user says " Play some music ." In that case, your app might choose to start a recently played or newly suggested track.
If a search cannot be processed quickly, do not block in onPlayFromSearch()
. Instead, set the playback state to STATE_CONNECTING
and perform the search on an async thread.
Once playback begins, consider populating the media session's queue with related content. For example, if the user requests an album to be played, your app might fill the queue with the album's tracklist. Also consider implementing support for browsable search results so a user can choose a different track that matches their query.
In addition to " play " queries, Android Auto and Android Automotive OS recognize voice queries to control playback like " pause music " and " next song " and match these commands to the appropriate media session callbacks, like onPause()
and onSkipToNext()
.
For a detailed example on how to implement voice-enabled playback actions in your app, see Google Assistant and media apps .
Implement distraction safeguards
Because a user's phone is connected to their car's speakers while using Android Auto, you must take additional precautions to help prevent driver distraction.
Suppress alarms in the car
Android Auto media apps must not start playing audio through the car speakers unless the user starts playback by, for example, pressing a play button. Even a user-scheduled alarm from your media app must not start playing music through the car speakers.
To fulfill this requirement, your app can use CarConnection
as a signal before playing any audio. Your app can check whether the phone is projecting to a car screen by observing the LiveData
for the car connection type and checking whether it is equal to CONNECTION_TYPE_PROJECTION
.
If the user's phone is projecting, media apps that support alarms must do one of the following things:
- Disable the alarm.
- Play the alarm over
STREAM_ALARM
and provide a UI on the phone screen to disable the alarm.
Handle media advertisements
By default, Android Auto displays a notification when the media metadata changes during an audio playback session. When a media app switches from playing music to running an advertisement, it is distracting to display a notification to the user. To prevent Android Auto from displaying a notification in this case, you must set the media metadata key METADATA_KEY_IS_ADVERTISEMENT
to METADATA_VALUE_ATTRIBUTE_PRESENT
, as shown in the following code snippet:
کاتلین
import androidx.media.utils.MediaConstants override fun onPlayFromMediaId(mediaId: String, extras: Bundle?) { MediaMetadataCompat.Builder().apply { if (isAd(mediaId)) { putLong( MediaConstants.METADATA_KEY_IS_ADVERTISEMENT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) } // ...add any other properties you normally would. mediaSession.setMetadata(build()) } }
جاوا
import androidx.media.utils.MediaConstants; @Override public void onPlayFromMediaId(String mediaId, Bundle extras) { MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder(); if (isAd(mediaId)) { builder.putLong( MediaConstants.METADATA_KEY_IS_ADVERTISEMENT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT); } // ...add any other properties you normally would. mediaSession.setMetadata(builder.build()); }
Handle general errors
When the app experiences an error, set the playback state to STATE_ERROR
and provide an error message using the setErrorMessage()
method. See PlaybackStateCompat
for a list of error codes that you can use when setting the error message. Error messages must be user-facing and localized with the user's current locale. Android Auto and Android Automotive OS can then display the error message to the user.
For example, if content is not available in the user's current region, you can use the ERROR_CODE_NOT_AVAILABLE_IN_REGION
error code when setting the error message.
کاتلین
mediaSession.setPlaybackState( PlaybackStateCompat.Builder() .setState(PlaybackStateCompat.STATE_ERROR) .setErrorMessage(PlaybackStateCompat.ERROR_CODE_NOT_AVAILABLE_IN_REGION, getString(R.string.error_unsupported_region)) // ...and any other setters. .build())
جاوا
mediaSession.setPlaybackState( new PlaybackStateCompat.Builder() .setState(PlaybackStateCompat.STATE_ERROR) .setErrorMessage(PlaybackStateCompat.ERROR_CODE_NOT_AVAILABLE_IN_REGION, getString(R.string.error_unsupported_region)) // ...and any other setters. .build());
For more information about error states, see Using a media session: States and errors .
If an Android Auto user needs to open your phone app to resolve an error, provide that information to the user in your message. For example, your error message might say "Sign in to [your app name]" instead of "Please sign in."
منابع دیگر
،Android Auto and Android Automotive OS help you bring your media app content to users in their car. A media app for cars must provide a media browser service so that Android Auto and Android Automotive OS, or another app with a media browser, can discover and display your content.
This guide assumes that you already have a media app that plays audio on a phone and that your media app conforms to the Android media app architecture .
This guide describes the required components of a MediaBrowserService
and MediaSession
that your app needs in order to work on Android Auto or Android Automotive OS. After you have completed the core media infrastructure, you can add support for Android Auto and add support for Android Automotive OS to your media app.
قبل از شروع
- Review the Android media API documentation .
- Review Create media apps for design guidance.
- Review the key terms and concepts listed in this section.
Key terms and concepts
- Media browser service
- An Android service implemented by your media app that complies with the
MediaBrowserServiceCompat
API. Your app uses this service to expose its content. - Media browser
- An API used by media apps to discover media browser services and display their content. Android Auto and Android Automotive OS use a media browser to find your app's media browser service.
- آیتم رسانه ای
The media browser organizes its content in a tree of
MediaItem
objects. A media item can have either or both of the following flags:-
FLAG_PLAYABLE
: indicates that the item is a leaf on the content tree. The item represents a single sound stream, such as a song on an album, a chapter in an audio book, or an episode of a podcast. -
FLAG_BROWSABLE
: indicates that the item is a node on the content tree and it has children. For example, the item represents an album, and its children are the songs on the album.
A media item that is both browsable and playable acts like a playlist. You can select the item itself to play all of its children, or you can browse its children.
-
- Vehicle-optimized
An activity for an Android Automotive OS app that adheres to the Android Automotive OS design guidelines . The interface for these activities is not drawn by Android Automotive OS, so you must ensure that your app adheres to the design guidelines. Typically, this includes larger tap targets and font sizes, support for day and night modes, and higher contrast ratios.
Vehicle-optimized user interfaces are only allowed to be displayed when Car User Experience Restrictions (CUXRs) are not in effect, because these interfaces can require extended attention or interaction from the user. CUXRs are not in effect when the car is stopped or parked but are always in effect when the car is in motion.
You don't need to design activities for Android Auto, because Android Auto draws its own vehicle-optimized interface using the information from your media browser service.
Configure your app's manifest files
Before you can create your media browser service, you need to configure your app's manifest files .
Declare your media browser service
Both Android Auto and Android Automotive OS connect to your app through your media browser service to browse media items. Declare your media browser service in your manifest to let Android Auto and Android Automotive OS discover the service and connect to your app.
The following code snippet shows how to declare your media browser service in your manifest. Include this code in the manifest file for your Android Automotive OS module and in the manifest file for your phone app.
<application>
...
<service android:name=".MyMediaBrowserService"
android:exported="true">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService"/>
</intent-filter>
</service>
...
</application>
Specify app icons
You need to specify app icons that Android Auto and Android Automotive OS can use to represent your app in the system UI. Two icon types are required:
- Launcher icon
- Attribution icon
Launcher icon
The launcher icon represents your app in the system UI, such as on the launcher and in the tray of icons. You can specify that you want to use the icon from your mobile app to represent your car media app using the following manifest declaration:
<application
...
android:icon="@mipmap/ic_launcher"
...
/>
To use a different icon than your mobile app's, set the android:icon
property on your media browser service's <service>
element in the manifest:
<application>
...
<service
...
android:icon="@mipmap/auto_launcher"
...
/>
</application>
Attribution icon
The attribution icon is used in places where media content takes precedence, such as on media cards. Consider reusing the small icon used for notifications. This icon must be monochrome. You can specify an icon that is used to represent your app using the following manifest declaration:
<application>
...
<meta-data
android:name="androidx.car.app.TintableAttributionIcon"
android:resource="@drawable/ic_status_icon" />
...
</application>
Create your media browser service
You create a media browser service by extending the MediaBrowserServiceCompat
class. Both Android Auto and Android Automotive OS can then use your service to do the following:
- Browse your app's content hierarchy to present a menu to the user.
- Get the token for your app's
MediaSessionCompat
object to control audio playback.
You can also use your media browser service to let other clients access media content from your app. These media clients might be other apps on a user's phone, or they can be other remote clients.
Media browser service workflow
This section describes how Android Automotive OS and Android Auto interact with your media browser service during a typical user workflow.
- The user launches your app on Android Automotive OS or Android Auto.
- Android Automotive OS or Android Auto contacts your app's media browser service using the
onCreate()
method. In your implementation of theonCreate()
method, you must create and register aMediaSessionCompat
object and its callback object. - Android Automotive OS or Android Auto calls your service's
onGetRoot()
method to get the root media item in your content hierarchy. The root media item is not displayed; instead, it's used to retrieve more content from your app. - Android Automotive OS or Android Auto calls your service's
onLoadChildren()
method to get the children of the root media item. Android Automotive OS and Android Auto display these media items as the top level of content items. See Structure the root menu on this page for more information on what the system expects at this level. - If the user selects a browsable media item, your service's
onLoadChildren()
method is called again to retrieve the children of the selected menu item. - If the user selects a playable media item, Android Automotive OS or Android Auto calls the appropriate media session callback method to perform that action.
- If supported by your app, the user can also search your content. In this case, Android Automotive OS or Android Auto call your service's
onSearch()
method.
Build your content hierarchy
Android Auto and Android Automotive OS call your app's media browser service to find out what content is available. You need to implement two methods in your media browser service to support this: onGetRoot()
and onLoadChildren()
Implement onGetRoot
Your service's onGetRoot()
method returns information about the root node of your content hierarchy. Android Auto and Android Automotive OS use this root node to request the rest of your content using the onLoadChildren()
method.
The following code snippet shows a simple implementation of the onGetRoot()
method:
کاتلین
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); }
For a more detailed example of this method, see the onGetRoot()
method in the Universal Android Music Player sample app on GitHub.
Add package validation for onGetRoot()
When a call is made to your service's onGetRoot()
method, the calling package passes identifying information to your service. Your service can use this information to decide whether that package can access your content. For example, you can restrict access to your app's content to a list of approved packages by comparing the clientPackageName
to your allowlist and verifying the certificate used to sign the package's APK. If the package can't be verified, return null
to deny access to your content.
To provide system apps, such as Android Auto and Android Automotive OS, with access to your content, your service must always return a non-null BrowserRoot
when these system apps call the onGetRoot()
method. The signature of the Android Automotive OS system app can vary depending on the make and model of the car, so you need to permit connections from all system apps to support Android Automotive OS robustly.
The following code snippet shows how your service can validate that the calling package is a system app:
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
}
This code snippet is an excerpt from the PackageValidator
class in the Universal Android Music Player sample app on GitHub. See that class for a more detailed example of how to implement package validation for your service's onGetRoot()
method.
In addition to allowing system apps, you must let the Google Assistant connect to your MediaBrowserService
. Note that the Google Assistant has separate package names for the phone, which includes Android Auto, and for Android Automotive OS.
Implement onLoadChildren()
After receiving your root node object, Android Auto and Android Automotive OS build a top-level menu by calling onLoadChildren()
on the root node object to get its children. Client apps build submenus by calling this same method using child node objects.
Each node in your content hierarchy is represented by a MediaBrowserCompat.MediaItem
object. Each of these media items is identified by a unique ID string. Client apps treat these ID strings as opaque tokens. When a client app wants to browse to a submenu, or play a media item, it passes the token. Your app is responsible for associating the token with the appropriate media item.
The following code snippet shows a simple implementation of onLoadChildren()
method:
کاتلین
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 whether 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 whether 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); }
For a complete example of this method, see the onLoadChildren()
method in the Universal Android Music Player sample app on GitHub.
Structure the root menu
Android Auto and Android Automotive OS have specific constraints about the structure of the root menu. These are communicated to the MediaBrowserService
through root hints, which can be read through the Bundle
argument passed into onGetRoot()
. Following these hints lets the system display the root content optimally as navigational tabs. If you don't follow these hints, some root content might be dropped or made less discoverable by the system. Two hints are sent:
- A limit on the number of root children : for the majority of cases, you can expect this number to be four. This means that over four tabs cannot be shown.
- Supported flags on the root children : you can expect this value to be
MediaItem#FLAG_BROWSABLE
. This means that only browsable items—not playable items—can be shown as tabs.
Use the following code to read the relevant root hints:
کاتلین
import androidx.media.utils.MediaConstants // Later, in your MediaBrowserServiceCompat. override fun onGetRoot( clientPackageName: String, clientUid: Int, rootHints: Bundle ): BrowserRoot { val maximumRootChildLimit = rootHints.getInt( MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_LIMIT, /* defaultValue= */ 4) val supportedRootChildFlags = rootHints.getInt( MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_SUPPORTED_FLAGS, /* defaultValue= */ MediaItem.FLAG_BROWSABLE) // Rest of method... }
جاوا
import androidx.media.utils.MediaConstants; // Later, in your MediaBrowserServiceCompat. @Override public BrowserRoot onGetRoot( String clientPackageName, int clientUid, Bundle rootHints) { int maximumRootChildLimit = rootHints.getInt( MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_LIMIT, /* defaultValue= */ 4); int supportedRootChildFlags = rootHints.getInt( MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_SUPPORTED_FLAGS, /* defaultValue= */ MediaItem.FLAG_BROWSABLE); // Rest of method... }
You can choose to branch the logic for the structure of your content hierarchy based on the values of these hints, particularly if your hierarchy varies among MediaBrowser
integrations outside of Android Auto and Android Automotive OS. For example, if you normally show a root playable item, you might want to nest it under a root browsable item instead due to the value of the supported flags hint.
Apart from the root hints, there are a couple additional guidelines to follow to help ensure that tabs render optimally:
- Supply monochrome, preferably white, icons for each tab item.
- Supply short but meaningful labels for each tab item. Keeping labels short reduces the chance of the strings being truncated.
Display media artwork
Artwork for media items must be passed as a local URI using either ContentResolver.SCHEME_CONTENT
or ContentResolver.SCHEME_ANDROID_RESOURCE
. This local URI must resolve to either a bitmap or a vector drawable in the application's resources. For MediaDescriptionCompat
objects representing items in the content hierarchy, pass the URI through setIconUri()
. For MediaMetadataCompat
objects representing the currently playing item, pass the URI through putString()
, using any of the following keys:
-
MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI
-
MediaMetadataCompat.METADATA_KEY_ART_URI
-
MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI
The following steps describe how to download art from a web URI and expose it through a local URI. For a more complete example, see the implementation of openFile()
and the surrounding methods in the Universal Android Music Player sample app.
Build a
content://
URI corresponding to the web URI. The media browser service and media session pass this content URI to Android Auto and Android Automotive OS.کاتلین
fun Uri.asAlbumArtContentURI(): Uri { return Uri.Builder() .scheme(ContentResolver.SCHEME_CONTENT) .authority(CONTENT_PROVIDER_AUTHORITY) .appendPath(this.getPath()) // Make sure you trust the URI .build() }
جاوا
public static Uri asAlbumArtContentURI(Uri webUri) { return new Uri.Builder() .scheme(ContentResolver.SCHEME_CONTENT) .authority(CONTENT_PROVIDER_AUTHORITY) .appendPath(webUri.getPath()) // Make sure you trust the URI! .build(); }
In your implementation of
ContentProvider.openFile()
, check whether a file exists for the corresponding URI. If not, download and cache the image file. The following code snippet uses Glide .کاتلین
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? { val context = this.context ?: return null val file = File(context.cacheDir, uri.path) if (!file.exists()) { val remoteUri = Uri.Builder() .scheme("https") .authority("my-image-site") .appendPath(uri.path) .build() val cacheFile = Glide.with(context) .asFile() .load(remoteUri) .submit() .get(DOWNLOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS) cacheFile.renameTo(file) file = cacheFile } return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY) }
جاوا
@Nullable @Override public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException { Context context = this.getContext(); File file = new File(context.getCacheDir(), uri.getPath()); if (!file.exists()) { Uri remoteUri = new Uri.Builder() .scheme("https") .authority("my-image-site") .appendPath(uri.getPath()) .build(); File cacheFile = Glide.with(context) .asFile() .load(remoteUri) .submit() .get(DOWNLOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS); cacheFile.renameTo(file); file = cacheFile; } return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); }
For more details about content providers, refer to Creating a content provider .
Apply content styles
After building your content hierarchy using browsable or playable items, you can apply content styles that determine how those items display in the car.
You can use the following content styles:
- List items
This content style prioritizes titles and metadata over images.
- Grid items
This content style prioritizes images over titles and metadata.
Set default content styles
You can set global defaults for how your media items are displayed by including certain constants in the BrowserRoot
extras bundle of your service's onGetRoot()
method. Android Auto and Android Automotive OS read this bundle and look for those constants to determine the appropriate style.
The following extras can be used as keys in the bundle:
-
DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE
: indicates a presentation hint for all browsable items within the browse tree. -
DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE
: indicates a presentation hint for all playable items within the browse tree.
The keys can map to the following integer constant values to influence the presentation of those items:
-
DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM
: the corresponding items are presented as list items. -
DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM
: the corresponding items are presented as grid items. -
DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM
: the corresponding items are presented as "category" list items. These are the same as ordinary list items except that margins are applied around the items' icons, since the icons look better when they are small. The icons must be tintable vector drawables. This hint is expected to only be provided for browsable items. -
DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_GRID_ITEM
: the corresponding items are presented as "category" grid items. These are the same as ordinary grid items, except that margins are applied around the items' icons, since the icons look better when they are small. The icons must be tintable vector drawables. This hint is expected to only be provided for browsable items.
The following code snippet shows how to set the default content style for browsable items to grids and playable items to lists:
کاتلین
import androidx.media.utils.MediaConstants @Nullable override fun onGetRoot( @NonNull clientPackageName: String, clientUid: Int, @Nullable rootHints: Bundle ): BrowserRoot { val extras = Bundle() extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM) return BrowserRoot(ROOT_ID, extras) }
جاوا
import androidx.media.utils.MediaConstants; @Nullable @Override public BrowserRoot onGetRoot( @NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) { Bundle extras = new Bundle(); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM); return new BrowserRoot(ROOT_ID, extras); }
Set per-item content styles
The Content Style API lets you override the default content style for any browsable media item's children, as well as any media item itself.
To override the default for a browsable media item's children , create an extras bundle in the MediaDescription
of the media item and add the same previously mentioned hints. DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE
applies to that item's playable children, while DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE
applies to that item's browsable children.
To override the default for a particular media item itself , not its children, create an extras bundle in the MediaDescription
of the media item and add a hint with the key DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM
. Use the same values described previously to specify that item's presentation.
The following code snippet shows how to create a browsable MediaItem
that overrides the default content style for both itself and its children. It styles itself as a category list item, its browsable children as list items, and its playable children as grid items:
کاتلین
import androidx.media.utils.MediaConstants 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( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM) mediaDescriptionBuilder.setExtras(extras) return MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE) }
جاوا
import androidx.media.utils.MediaConstants; 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( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM); mediaDescriptionBuilder.setExtras(extras); return new MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE); }
Group items using title hints
To group related media items together, you use a per-item hint. Every media item in a group needs to declare an extras bundle in their MediaDescription
that includes a mapping with the key DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE
and an identical string value. Localize this string, which is used as the title of the group.
The following code snippet shows how to create a MediaItem
with a subgroup heading of "Songs"
:
کاتلین
import androidx.media.utils.MediaConstants 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( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs") mediaDescriptionBuilder.setExtras(extras) return MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), /* playable or browsable flag*/) }
جاوا
import androidx.media.utils.MediaConstants; 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( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs"); mediaDescriptionBuilder.setExtras(extras); return new MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), /* playable or browsable flag*/); }
Your app must pass all the media items that you want to group together as a contiguous block. For example, suppose that you want to display two groups of media items, "Songs" and "Albums," in that order, and your app passes in five media items in the following order:
- Media item A with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Media item B with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )
- Media item C with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Media item D with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Media item E with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )
Because the media items for the "Songs" group and "Albums" group are not kept together in contiguous blocks, Android Auto and Android Automotive OS interprets this as the following four groups:
- Group 1 called "Songs" containing media item A
- Group 2 called "Albums" containing media item B
- Group 3 called "Songs" containing media items C and D
- Group 4 called "Albums" containing media item E
To display these items in two groups, your app must pass the media items in the following order instead:
- Media item A with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Media item C with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Media item D with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Media item B with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )
- Media item E with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )
Display additional metadata indicators
You can include additional metadata indicators to provide at-a-glance information for content in the media browser tree and during playback. Within the browse tree, Android Auto and Android Automotive OS read the extras associated with an item and look for certain constants to determine which indicators to display. During media playback, Android Auto and Android Automotive OS read the metadata for the media session and look for certain constants to determine indicators to display.
The following constants can be used in both MediaItem
description extras and MediaMetadata
extras:
-
EXTRA_DOWNLOAD_STATUS
: indicates the download status of an item. Use this constant as the key; the following long constants are the possible values:-
STATUS_DOWNLOADED
: the item is completely downloaded. -
STATUS_DOWNLOADING
: the item is being downloaded. -
STATUS_NOT_DOWNLOADED
: the item is not downloaded.
-
-
METADATA_KEY_IS_EXPLICIT
: indicates whether the item contains explicit content. To indicate an item is explicit, use this constant as the key and the longMETADATA_VALUE_ATTRIBUTE_PRESENT
as the value.
The following constants can only be used in MediaItem
description extras:
-
DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS
: indicates the completion state of long-form content, such as podcast episodes or audiobooks. Use this constant as the key; the following integer constants are the possible values:-
DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED
: the item has not been played at all. -
DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED
: the item has been partially played, and the current position is somewhere in the middle. -
DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED
: the item has been completed.
-
-
DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
: indicates the amount of completion progress on long-form content as a double between 0.0 and 1.0, inclusive. This extra provides more information about thePARTIALLY_PLAYING
state so that Android Auto or Android Automotive OS displays a more meaningful progress indicator, such as a progress bar. If you use this extra, see the section about updating the progress bar in the browse view as content is playing in this guide to learn how to keep this indicator up to date after its initial impression.
To display indicators that appear while the user is browsing the media browse tree, create an extras bundle that includes one or more of these constants and pass that bundle to the MediaDescription.Builder.setExtras()
method.
The following code snippet shows how to display indicators for an explicit media item that is 70% complete:
کاتلین
import androidx.media.utils.MediaConstants val extras = Bundle() extras.putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS, MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED) extras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.7) val description = MediaDescriptionCompat.Builder() .setMediaId(/*...*/) .setTitle(resources.getString(/*...*/)) .setExtras(extras) .build() return MediaBrowserCompat.MediaItem(description, /* flags */)
جاوا
import androidx.media.utils.MediaConstants; Bundle extras = new Bundle(); extras.putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS, MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED); extras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.7); MediaDescriptionCompat description = new MediaDescriptionCompat.Builder() .setMediaId(/*...*/) .setTitle(resources.getString(/*...*/)) .setExtras(extras) .build(); return new MediaBrowserCompat.MediaItem(description, /* flags */);
To display indicators for a media item that is currently being played, you can declare Long
values for METADATA_KEY_IS_EXPLICIT
or EXTRA_DOWNLOAD_STATUS
in the MediaMetadataCompat
of your mediaSession
. You can't display the DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS
or DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
indicators on the playback view.
The following code snippet shows how to indicate that the current song in the playback view is explicit and downloaded:
کاتلین
import androidx.media.utils.MediaConstants mediaSession.setMetadata( MediaMetadataCompat.Builder() .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, "Song Name") .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "Artist name") .putString( MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, albumArtUri.toString()) .putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) .putLong( MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS, MediaDescriptionCompat.STATUS_DOWNLOADED) .build())
جاوا
import androidx.media.utils.MediaConstants; mediaSession.setMetadata( new MediaMetadataCompat.Builder() .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, "Song Name") .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "Artist name") .putString( MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, albumArtUri.toString()) .putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) .putLong( MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS, MediaDescriptionCompat.STATUS_DOWNLOADED) .build());
Update the progress bar in the browse view as content is playing
As previously mentioned, you can use the DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
extra to show a progress bar for partially played content in the browse view. However, if a user continues playing the partially played content from Android Auto or Android Automotive OS, that indicator becomes inaccurate as time passes.
For Android Auto and Android Automotive OS to keep the progress bar up to date, you can supply additional information in MediaMetadataCompat
and PlaybackStateCompat
to link ongoing content to media items in the browse view. The following requirements must be met for the media item to have an automatically updating progress bar:
- When it is created, the
MediaItem
must sendDESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
in its extras with a value between 0.0 and 1.0, inclusive. - The
MediaMetadataCompat
must sendMETADATA_KEY_MEDIA_ID
with a string value equal to the media ID passed to theMediaItem
. - The
PlaybackStateCompat
must include an extra with the keyPLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID
that maps to a string value equal to the media ID passed to theMediaItem
.
The following code snippet shows how to indicate that the currently playing item is linked to an item in the browse view:
کاتلین
import androidx.media.utils.MediaConstants // When the MediaItem is constructed to show in the browse view. // Suppose the item was 25% complete when the user launched the browse view. val mediaItemExtras = Bundle() mediaItemExtras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.25) val description = MediaDescriptionCompat.Builder() .setMediaId("my-media-id") .setExtras(mediaItemExtras) // ...and any other setters. .build() return MediaBrowserCompat.MediaItem(description, /* flags */) // Elsewhere, when the user has selected MediaItem for playback. mediaSession.setMetadata( MediaMetadataCompat.Builder() .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, "my-media-id") // ...and any other setters. .build()) val playbackStateExtras = Bundle() playbackStateExtras.putString( MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID, "my-media-id") mediaSession.setPlaybackState( PlaybackStateCompat.Builder() .setExtras(playbackStateExtras) // ...and any other setters. .build())
جاوا
import androidx.media.utils.MediaConstants; // When the MediaItem is constructed to show in the browse view. // Suppose the item was 25% complete when the user launched the browse view. Bundle mediaItemExtras = new Bundle(); mediaItemExtras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.25); MediaDescriptionCompat description = new MediaDescriptionCompat.Builder() .setMediaId("my-media-id") .setExtras(mediaItemExtras) // ...and any other setters. .build(); return MediaBrowserCompat.MediaItem(description, /* flags */); // Elsewhere, when the user has selected MediaItem for playback. mediaSession.setMetadata( new MediaMetadataCompat.Builder() .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, "my-media-id") // ...and any other setters. .build()); Bundle playbackStateExtras = new Bundle(); playbackStateExtras.putString( MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID, "my-media-id"); mediaSession.setPlaybackState( new PlaybackStateCompat.Builder() .setExtras(playbackStateExtras) // ...and any other setters. .build());
Display browsable search results
Your app can provide contextual search results that display to users when they initiate a search query. Android Auto and Android Automotive OS show these results through search query interfaces or through affordances that pivot on queries made earlier in the session. To learn more, see the Support voice actions section in this guide.
To display browsable search results, include the constant key BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED
in the extras bundle of your service's onGetRoot()
method, mapping to the boolean true
.
The following code snippet shows how to enable support in the onGetRoot()
method:
کاتلین
import androidx.media.utils.MediaConstants @Nullable fun onGetRoot( @NonNull clientPackageName: String, clientUid: Int, @Nullable rootHints: Bundle ): BrowserRoot { val extras = Bundle() extras.putBoolean( MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true) return BrowserRoot(ROOT_ID, extras) }
جاوا
import androidx.media.utils.MediaConstants; @Nullable @Override public BrowserRoot onGetRoot( @NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) { Bundle extras = new Bundle(); extras.putBoolean( MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true); return new BrowserRoot(ROOT_ID, extras); }
To start providing search results, override the onSearch()
method in your media browser service. Android Auto and Android Automotive OS forward the user's search terms to this method whenever a user invokes a search query interface or “Search results” affordance.
You can organize the search results from your service's onSearch()
method using title items to make them more browsable. For example, if your app plays music, you might organize search results by album, artist, and songs.
The following code snippet shows a simple implementation of the onSearch()
method:
کاتلین
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<List<MediaItem>> result) { // Detach from results to unblock the caller (if a search is expensive). result.detach(); new AsyncTask<Void, Void, Void>() { List<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); } } }.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. }
Custom Browse Actions
Custom Browse Actions allow you to add custom icons and labels to your app's MediaItem
objects in the car's media app, and handle user interactions with these actions. This lets you extend the functionality of the Media App in a variety of ways, like adding "Download", "Add to Queue", "Play Radio", "Favorite", or "Remove" actions.
If there are more custom actions than the OEM allows to be displayed, an overflow menu will be presented to the user.
How do they work?
Each Custom Browse Action is defined with:
- An Action ID (a unique string identifier)
- An Action Label (the text displayed to the user)
- An Action Icon URI (a vector drawable that can be tinted)
You define a list of Custom Browse Actions globally as part of your BrowseRoot
. Then you can attach a subset of these actions to individual MediaItem.
When a user interacts with a Custom Browse Action, your app receives a callback in onCustomAction()
. You can then handle the action and update the list of actions for the MediaItem
if necessary. This is useful for stateful actions like "Favorite" and "Download". For actions that don't need updating, like "Play Radio", you don't need to update the list of actions.
You can also attach Custom Browse Actions to a browse node root. These actions will be displayed in a secondary toolbar under the main toolbar.
How to implement Custom Browse Actions
Here are the steps to add Custom Browse Actions to your project:
- Override two methods in your
MediaBrowserServiceCompat
implementation: - Parse the action limits at runtime:
- In
onGetRoot()
, get the maximum number of actions allowed for eachMediaItem
using the keyBROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT
in therootHints
Bundle
. A limit of 0 indicates that the feature is not supported by the system.
- In
- Build the global list of Custom Browse Actions:
- For each action, create a
Bundle
object with the following keys: *EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID
: The action ID *EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL
: The action label *EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI
: The action icon URI * Add all the actionBundle
objects to a list.
- For each action, create a
- Add the global list to your
BrowseRoot
:- In the
BrowseRoot
extrasBundle
, add the list of actions as aParcelable
Arraylist
using the keyBROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST
.
- In the
- Add actions to your
MediaItem
objects:- You can add actions to individual
MediaItem
objects by including the list of action IDs in theMediaDescriptionCompat
extras using the keyDESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST
. This list must be a subset of the global list of actions you defined in theBrowseRoot
.
- You can add actions to individual
- Handle actions and return progress or results:
- In
onCustomAction
, handle the action based on the action ID and any other data you need. You can get the ID of theMediaItem
that triggered the action from the extras using the keyEXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID
. . - You can update the list of actions for a
MediaItem
by including the keyEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
in the progress or result bundle.
- In
Here are some changes you can make in your BrowserServiceCompat
to get started with Custom Browse Actions.
Override BrowserServiceCompat
You need to override the following methods in MediaBrowserServiceCompat
.
public void onLoadItem(String itemId, @NonNull Result<MediaBrowserCompat.MediaItem> result)
public void onCustomAction(@NonNull String action, Bundle extras, @NonNull Result<Bundle> result)
Parse actions limit
You should check to see how many Custom Browse Actions are supported.
public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, Bundle rootHints) { rootHints.getInt( MediaConstants.BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT, 0) }
Build a Custom Browse Action
Each action needs to be packed into a separate Bundle
.
- Action ID
bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID, "<ACTION_ID>")
- Action Label
bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL, "<ACTION_LABEL>")
- Action Icon URI
bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI, "<ACTION_ICON_URI>")
Add Custom Browse Actions to Parceable
ArrayList
Add all Custom Browse Action Bundle
objects into an ArrayList
.
private ArrayList<Bundle> createCustomActionsList( CustomBrowseAction browseActions) { ArrayList<Bundle> browseActionsBundle = new ArrayList<>(); for (CustomBrowseAction browseAction : browseActions) { Bundle action = new Bundle(); action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID, browseAction.mId); action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL, getString(browseAction.mLabelResId)); action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI, browseAction.mIcon); browseActionsBundle.add(action); } return browseActionsBundle; }
Add Custom Browse Action list to the browse root
public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, Bundle rootHints) { Bundle browserRootExtras = new Bundle(); browserRootExtras.putParcelableArrayList( BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST, createCustomActionsList())); mRoot = new BrowserRoot(ROOT_ID, browserRootExtras); return mRoot; }
Add actions to a MediaItem
MediaDescriptionCompat buildDescription (long id, String title, String subtitle, String description, Uri iconUri, Uri mediaUri, ArrayList<String> browseActionIds) { MediaDescriptionCompat.Builder bob = new MediaDescriptionCompat.Builder(); bob.setMediaId(id); bob.setTitle(title); bob.setSubtitle(subtitle); bob.setDescription(description); bob.setIconUri(iconUri); bob.setMediaUri(mediaUri); Bundle extras = new Bundle(); extras.putStringArrayList( DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST, browseActionIds); bob.setExtras(extras); return bob.build(); } MediaItem mediaItem = new MediaItem(buildDescription(...), flags);
Build onCustomAction
result
- Parse mediaId from
Bundle extras
:@Override public void onCustomAction( @NonNull String action, Bundle extras, @NonNull Result<Bundle> result){ String mediaId = extras.getString(MediaConstans.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID); }
- For asynchronous results detach result.
result.detach()
- Build result bundle
- Message to user
mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE, mContext.getString(stringRes))
- Update item(use to update actions in an item)
mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM, mediaId);
- Open Playback view
//Shows user the PBV without changing the playback state mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM, null);
- Update Browse Node
//Change current browse node to mediaId mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE, mediaId);
- Message to user
- If an error, call
result.sendError(resultBundle).
- If progress update, call
result.sendProgressUpdate(resultBundle)
. - Finish by calling
result.sendResult(resultBundle)
.
Update Action State
By using the result.sendProgressUpdate(resultBundle)
method with the EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
key, you can update the MediaItem
to reflect the new state of the action. This lets you provide real-time feedback to the user about the progress and result of their action.
Example: Download Action
Here's an example of how you can use this feature to implement a download action with three states:
- Download: This is the initial state of the action. When the user selects this action, you can swap it with "Downloading" and call
sendProgressUpdate
to update the UI. - Downloading: This state indicates that the download is in progress. You can use this state to show a progress bar or another indicator to the user.
- Downloaded: This state indicates that the download is complete. When the download finishes, you can swap "Downloading" with "Downloaded" and call
sendResult
with theEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
key to indicate that the item should be refreshed. Additionally, you can use theEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE
key to display a success message to the user.
This approach lets you provide clear feedback to the user about the download process and its current state. You can add even more detail with icons to show 25%, 50%, 75% download states.
Example: Favorite Action
Another example is a favorite action with two states:
- Favorite: This action is displayed for items that are not in the user's favorites list. When the user selects this action, you can swap it with "Favorited" and call
sendResult
with theEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
key to update the UI. - Favorited: This action is displayed for items that are in the user's favorites list. When the user selects this action, you can swap it with "Favorite" and call
sendResult
with theEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
key to update the UI.
This approach provides a clear and consistent way for users to manage their favorite items.
These examples showcase the flexibility of Custom Browse Actions and how you can use them to implement a variety of functionalities with real-time feedback for an enhanced user experience in the car's media app.
For a complete example implementation of this feature, you can refer to the TestMediaApp
project.
Enable playback control
Android Auto and Android Automotive OS send playback control commands through your service's MediaSessionCompat
. You must register a session and implement the associated callback methods.
Register a media session
In your media browser service's onCreate()
method, create a MediaSessionCompat
, then register the media session by calling setSessionToken()
.
The following code snippet shows how to create and register a media session:
کاتلین
override fun onCreate() { super.onCreate() ... // Start a new MediaSession. val session = MediaSessionCompat(this, "session tag").apply { // Set a callback object that implements MediaSession.Callback // to handle play control requests. 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 that implements MediaSession.Callback // to handle play control requests. session.setCallback(new MyMediaSessionCallback()); ... }
When you create the media session object, you set a callback object that is used to handle playback control requests. You create this callback object by providing an implementation of the MediaSessionCompat.Callback
class for your app. The next section discusses how to implement this object.
Implement play commands
When a user requests playback for a media item from your app, Android Automotive OS and Android Auto use the MediaSessionCompat.Callback
class from your app's MediaSessionCompat
object that they obtained from your app's media browser service. When a user wants to control content playback, such as pausing playback or skipping to the next track, Android Auto and Android Automotive OS invoke one of the callback object's methods.
To handle content playback, your app must extend the abstract MediaSessionCompat.Callback
class and implement the methods that your app supports.
Implement all the following callback methods that make sense for the type of content that your app offers:
-
onPrepare()
- Invoked when the media source is changed. Android Automotive OS also invokes this method immediately after booting. Your media app must implement this method.
-
onPlay()
- Invoked if the user chooses play without choosing a specific item. Your app must play its default content or, if playback was paused with
onPause()
, your app resumes playback.Note: Your app should not automatically start playing music when Android Automotive OS or Android Auto connect to your media browser service. For more information, see the section about setting the initial playback state .
-
onPlayFromMediaId()
- Invoked when the user chooses to play a specific item. The method is passed the ID that your media browser service assigned to the media item in your content hierarchy.
-
onPlayFromSearch()
- Invoked when the user chooses to play from a search query. The app must make an appropriate choice based on the search string that was passed in.
-
onPause()
- Invoked when the user chooses to pause playback.
-
onSkipToNext()
- Invoked when the user chooses to skip to the next item.
-
onSkipToPrevious()
- Invoked when the user chooses to skip to the previous item.
-
onStop()
- Invoked when the user chooses to stop playback.
Override these methods in your app to provide any desired functionality. You don't need to implement a method if its functionality isn't supported by your app. For example, if your app plays a live stream, such as a sports broadcast, you don't need to implement the onSkipToNext()
method. You can use the default implementation of onSkipToNext()
instead.
Your app doesn't need any special logic to play content through the car's speakers. When your app receives a request to play content, it can play audio the same way that it plays content through a user's phone speakers or headphones. Android Auto and Android Automotive OS automatically send the audio content to the car's system to play over the car's speakers.
For more information about playing audio content, see MediaPlayer overview , Audio app overview , and the ExoPlayer overview .
Set standard playback actions
Android Auto and Android Automotive OS display playback controls based on the actions that are enabled in the PlaybackStateCompat
object.
By default, your app must support the following actions:
Your app can additionally support the following actions if they are relevant to the app's content:
In addition, you have the option to create a play queue that can be displayed for the user, but it is not required. To do this, call the setQueue()
and setQueueTitle()
methods, enable the ACTION_SKIP_TO_QUEUE_ITEM
action, and define the callback onSkipToQueueItem()
.
Also, add support for the Now playing icon, which is an indicator for what is currently playing. To do this, call the setActiveQueueItemId()
method and pass the ID of the currently playing item in the queue. You need to update setActiveQueueItemId()
whenever there is a queue change.
Android Auto and Android Automotive OS display buttons for each enabled action as well as the playback queue. When the buttons are clicked, the system invokes their corresponding callback from MediaSessionCompat.Callback
.
Reserve unused space
Android Auto and Android Automotive OS reserve space in the UI for the ACTION_SKIP_TO_PREVIOUS
and ACTION_SKIP_TO_NEXT
actions. If your app does not support one of these functions, Android Auto and Android Automotive OS use the space to display any custom actions you create.
If you don't want to fill those spaces with custom actions, you can reserve them so that Android Auto and Android Automotive OS leave the space blank whenever your app does not support the corresponding function. To do this, call the setExtras()
method with an extras bundle that contains constants that correspond to the reserved functions. SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT
corresponds to ACTION_SKIP_TO_NEXT
, and SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV
corresponds to ACTION_SKIP_TO_PREVIOUS
. Use these constants as keys in the bundle, and use the boolean true
for their values.
Set initial PlaybackState
As Android Auto and Android Automotive OS communicate with your media browser service, your media session communicates the status of content playback using the PlaybackStateCompat
. Your app should not automatically start playing music when Android Automotive OS or Android Auto connect to your media browser service. Instead, rely on Android Auto and Android Automotive OS to resume or start playback based on the car's state or user actions.
To accomplish this, set the initial PlaybackStateCompat
of your media session to STATE_STOPPED
, STATE_PAUSED
, STATE_NONE
, or STATE_ERROR
.
Media sessions within Android Auto and Android Automotive OS only last for the duration of the drive, so users start and stop these sessions frequently. To promote a seamless experience between drives, keep track of the user's previous session state, so that when the media app receives a resume request, the user can automatically pick up where they left off—for example, the last played media item, the PlaybackStateCompat
, and the queue.
Add custom playback actions
You can add custom playback actions to display additional actions that your media app supports. If space permits (and is not reserved) , Android adds the custom actions to the transport controls. Otherwise, the custom actions display in the overflow menu. Custom actions display in the order they are added to the PlaybackStateCompat
.
Use custom actions to provide behavior distinct from standard actions . Don't use them to replace or duplicate standard actions.
You can add custom actions using the addCustomAction()
method in the PlaybackStateCompat.Builder
class.
The following code snippet shows how to add a custom “Start a radio channel” action:
کاتلین
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());
For a more detailed example of this method, see the setCustomAction()
method in the Universal Android Music Player sample app on GitHub.
After creating your custom action, your media session can respond to the action by overriding the onCustomAction()
method.
The following code snippet shows how your app might respond to a “Start a radio channel” action:
کاتلین
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)) { ... } }
For a more detailed example of this method, see the onCustomAction
method in the Universal Android Music Player sample app on GitHub.
Icons for custom actions
Each custom action that you create requires an icon resource. Apps in cars can run on many different screen sizes and densities, so icons that you provide must be vector drawables . A vector drawable allows you to scale assets without losing the detail. A vector drawable also makes it easy to align edges and corners to pixel boundaries at smaller resolutions.
If a custom action is stateful—for example, it toggles a playback setting on or off—provide different icons for the different states, so users can see a change when they select the action.
Provide alternative icon styles for disabled actions
When a custom action is unavailable for the current context, swap the custom action icon with an alternative icon that shows that the action is disabled.
Indicate audio format
To indicate that currently playing media uses a special audio format, you can specify icons that are rendered in cars that support this feature. You can set the KEY_CONTENT_FORMAT_TINTABLE_LARGE_ICON_URI
and the KEY_CONTENT_FORMAT_TINTABLE_SMALL_ICON_URI
in the extras bundle of the currently playing media item (passed to MediaSession.setMetadata()
). Make sure to set both of those extras, to accommodate different layouts.
In addition, you can set the KEY_IMMERSIVE_AUDIO
extra to tell car OEMs that this is immersive audio, and they should be very careful when deciding whether to apply audio effects that might interfere with the immersive content.
Add links from currently-playing item
You can configure the currently-playing media item so its subtitle, description, or both are links to other media items. That lets the user jump quickly to related items; for example, they might jump to other songs by the same artist, other episodes of that podcast, etc. If the car supports this feature, users can tap the link to browse to that content.
To add links, configure the KEY_SUBTITLE_LINK_MEDIA_ID
metadata (to link from the subtitle) or KEY_DESCRIPTION_LINK_MEDIA_ID
(to link from the description). For details, see the reference documentation for those metadata fields.
Support voice actions
Your media app must support voice actions to help provide drivers with a safe and convenient experience that minimizes distractions. For example, if your app is playing one media item, the user can say “ Play [song title] " to tell your app to play a different song without looking at or touching the car's display. Users can initiate queries by clicking the appropriate buttons on their steering wheel or speaking the hotwords " OK Google ."
When Android Auto or Android Automotive OS detects and interprets a voice action, that voice action is delivered to the app through onPlayFromSearch()
. On receiving this callback, the app finds content matching the query
string and starts playback.
Users can specify different categories of terms in their query: genre, artist, album, song name, radio station, or playlist, among others. When building support for search, account for all the categories that make sense for your app. If Android Auto or Android Automotive OS detects that a given query fits into certain categories, it appends extras in the extras
parameter. The following extras can be sent:
Account for an empty query
string, which can be sent by Android Auto or Android Automotive OS if the user doesn't specify search terms. For example, if the user says " Play some music ." In that case, your app might choose to start a recently played or newly suggested track.
If a search cannot be processed quickly, do not block in onPlayFromSearch()
. Instead, set the playback state to STATE_CONNECTING
and perform the search on an async thread.
Once playback begins, consider populating the media session's queue with related content. For example, if the user requests an album to be played, your app might fill the queue with the album's tracklist. Also consider implementing support for browsable search results so a user can choose a different track that matches their query.
In addition to " play " queries, Android Auto and Android Automotive OS recognize voice queries to control playback like " pause music " and " next song " and match these commands to the appropriate media session callbacks, like onPause()
and onSkipToNext()
.
For a detailed example on how to implement voice-enabled playback actions in your app, see Google Assistant and media apps .
Implement distraction safeguards
Because a user's phone is connected to their car's speakers while using Android Auto, you must take additional precautions to help prevent driver distraction.
Suppress alarms in the car
Android Auto media apps must not start playing audio through the car speakers unless the user starts playback by, for example, pressing a play button. Even a user-scheduled alarm from your media app must not start playing music through the car speakers.
To fulfill this requirement, your app can use CarConnection
as a signal before playing any audio. Your app can check whether the phone is projecting to a car screen by observing the LiveData
for the car connection type and checking whether it is equal to CONNECTION_TYPE_PROJECTION
.
If the user's phone is projecting, media apps that support alarms must do one of the following things:
- Disable the alarm.
- Play the alarm over
STREAM_ALARM
and provide a UI on the phone screen to disable the alarm.
Handle media advertisements
By default, Android Auto displays a notification when the media metadata changes during an audio playback session. When a media app switches from playing music to running an advertisement, it is distracting to display a notification to the user. To prevent Android Auto from displaying a notification in this case, you must set the media metadata key METADATA_KEY_IS_ADVERTISEMENT
to METADATA_VALUE_ATTRIBUTE_PRESENT
, as shown in the following code snippet:
کاتلین
import androidx.media.utils.MediaConstants override fun onPlayFromMediaId(mediaId: String, extras: Bundle?) { MediaMetadataCompat.Builder().apply { if (isAd(mediaId)) { putLong( MediaConstants.METADATA_KEY_IS_ADVERTISEMENT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) } // ...add any other properties you normally would. mediaSession.setMetadata(build()) } }
جاوا
import androidx.media.utils.MediaConstants; @Override public void onPlayFromMediaId(String mediaId, Bundle extras) { MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder(); if (isAd(mediaId)) { builder.putLong( MediaConstants.METADATA_KEY_IS_ADVERTISEMENT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT); } // ...add any other properties you normally would. mediaSession.setMetadata(builder.build()); }
Handle general errors
When the app experiences an error, set the playback state to STATE_ERROR
and provide an error message using the setErrorMessage()
method. See PlaybackStateCompat
for a list of error codes that you can use when setting the error message. Error messages must be user-facing and localized with the user's current locale. Android Auto and Android Automotive OS can then display the error message to the user.
For example, if content is not available in the user's current region, you can use the ERROR_CODE_NOT_AVAILABLE_IN_REGION
error code when setting the error message.
کاتلین
mediaSession.setPlaybackState( PlaybackStateCompat.Builder() .setState(PlaybackStateCompat.STATE_ERROR) .setErrorMessage(PlaybackStateCompat.ERROR_CODE_NOT_AVAILABLE_IN_REGION, getString(R.string.error_unsupported_region)) // ...and any other setters. .build())
جاوا
mediaSession.setPlaybackState( new PlaybackStateCompat.Builder() .setState(PlaybackStateCompat.STATE_ERROR) .setErrorMessage(PlaybackStateCompat.ERROR_CODE_NOT_AVAILABLE_IN_REGION, getString(R.string.error_unsupported_region)) // ...and any other setters. .build());
For more information about error states, see Using a media session: States and errors .
If an Android Auto user needs to open your phone app to resolve an error, provide that information to the user in your message. For example, your error message might say "Sign in to [your app name]" instead of "Please sign in."
منابع دیگر
،Android Auto and Android Automotive OS help you bring your media app content to users in their car. A media app for cars must provide a media browser service so that Android Auto and Android Automotive OS, or another app with a media browser, can discover and display your content.
This guide assumes that you already have a media app that plays audio on a phone and that your media app conforms to the Android media app architecture .
This guide describes the required components of a MediaBrowserService
and MediaSession
that your app needs in order to work on Android Auto or Android Automotive OS. After you have completed the core media infrastructure, you can add support for Android Auto and add support for Android Automotive OS to your media app.
قبل از شروع
- Review the Android media API documentation .
- Review Create media apps for design guidance.
- Review the key terms and concepts listed in this section.
Key terms and concepts
- Media browser service
- An Android service implemented by your media app that complies with the
MediaBrowserServiceCompat
API. Your app uses this service to expose its content. - Media browser
- An API used by media apps to discover media browser services and display their content. Android Auto and Android Automotive OS use a media browser to find your app's media browser service.
- آیتم رسانه ای
The media browser organizes its content in a tree of
MediaItem
objects. A media item can have either or both of the following flags:-
FLAG_PLAYABLE
: indicates that the item is a leaf on the content tree. The item represents a single sound stream, such as a song on an album, a chapter in an audio book, or an episode of a podcast. -
FLAG_BROWSABLE
: indicates that the item is a node on the content tree and it has children. For example, the item represents an album, and its children are the songs on the album.
A media item that is both browsable and playable acts like a playlist. You can select the item itself to play all of its children, or you can browse its children.
-
- Vehicle-optimized
An activity for an Android Automotive OS app that adheres to the Android Automotive OS design guidelines . The interface for these activities is not drawn by Android Automotive OS, so you must ensure that your app adheres to the design guidelines. Typically, this includes larger tap targets and font sizes, support for day and night modes, and higher contrast ratios.
Vehicle-optimized user interfaces are only allowed to be displayed when Car User Experience Restrictions (CUXRs) are not in effect, because these interfaces can require extended attention or interaction from the user. CUXRs are not in effect when the car is stopped or parked but are always in effect when the car is in motion.
You don't need to design activities for Android Auto, because Android Auto draws its own vehicle-optimized interface using the information from your media browser service.
Configure your app's manifest files
Before you can create your media browser service, you need to configure your app's manifest files .
Declare your media browser service
Both Android Auto and Android Automotive OS connect to your app through your media browser service to browse media items. Declare your media browser service in your manifest to let Android Auto and Android Automotive OS discover the service and connect to your app.
The following code snippet shows how to declare your media browser service in your manifest. Include this code in the manifest file for your Android Automotive OS module and in the manifest file for your phone app.
<application>
...
<service android:name=".MyMediaBrowserService"
android:exported="true">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService"/>
</intent-filter>
</service>
...
</application>
Specify app icons
You need to specify app icons that Android Auto and Android Automotive OS can use to represent your app in the system UI. Two icon types are required:
- Launcher icon
- Attribution icon
Launcher icon
The launcher icon represents your app in the system UI, such as on the launcher and in the tray of icons. You can specify that you want to use the icon from your mobile app to represent your car media app using the following manifest declaration:
<application
...
android:icon="@mipmap/ic_launcher"
...
/>
To use a different icon than your mobile app's, set the android:icon
property on your media browser service's <service>
element in the manifest:
<application>
...
<service
...
android:icon="@mipmap/auto_launcher"
...
/>
</application>
Attribution icon
The attribution icon is used in places where media content takes precedence, such as on media cards. Consider reusing the small icon used for notifications. This icon must be monochrome. You can specify an icon that is used to represent your app using the following manifest declaration:
<application>
...
<meta-data
android:name="androidx.car.app.TintableAttributionIcon"
android:resource="@drawable/ic_status_icon" />
...
</application>
Create your media browser service
You create a media browser service by extending the MediaBrowserServiceCompat
class. Both Android Auto and Android Automotive OS can then use your service to do the following:
- Browse your app's content hierarchy to present a menu to the user.
- Get the token for your app's
MediaSessionCompat
object to control audio playback.
You can also use your media browser service to let other clients access media content from your app. These media clients might be other apps on a user's phone, or they can be other remote clients.
Media browser service workflow
This section describes how Android Automotive OS and Android Auto interact with your media browser service during a typical user workflow.
- The user launches your app on Android Automotive OS or Android Auto.
- Android Automotive OS or Android Auto contacts your app's media browser service using the
onCreate()
method. In your implementation of theonCreate()
method, you must create and register aMediaSessionCompat
object and its callback object. - Android Automotive OS or Android Auto calls your service's
onGetRoot()
method to get the root media item in your content hierarchy. The root media item is not displayed; instead, it's used to retrieve more content from your app. - Android Automotive OS or Android Auto calls your service's
onLoadChildren()
method to get the children of the root media item. Android Automotive OS and Android Auto display these media items as the top level of content items. See Structure the root menu on this page for more information on what the system expects at this level. - If the user selects a browsable media item, your service's
onLoadChildren()
method is called again to retrieve the children of the selected menu item. - If the user selects a playable media item, Android Automotive OS or Android Auto calls the appropriate media session callback method to perform that action.
- If supported by your app, the user can also search your content. In this case, Android Automotive OS or Android Auto call your service's
onSearch()
method.
Build your content hierarchy
Android Auto and Android Automotive OS call your app's media browser service to find out what content is available. You need to implement two methods in your media browser service to support this: onGetRoot()
and onLoadChildren()
Implement onGetRoot
Your service's onGetRoot()
method returns information about the root node of your content hierarchy. Android Auto and Android Automotive OS use this root node to request the rest of your content using the onLoadChildren()
method.
The following code snippet shows a simple implementation of the onGetRoot()
method:
کاتلین
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); }
For a more detailed example of this method, see the onGetRoot()
method in the Universal Android Music Player sample app on GitHub.
Add package validation for onGetRoot()
When a call is made to your service's onGetRoot()
method, the calling package passes identifying information to your service. Your service can use this information to decide whether that package can access your content. For example, you can restrict access to your app's content to a list of approved packages by comparing the clientPackageName
to your allowlist and verifying the certificate used to sign the package's APK. If the package can't be verified, return null
to deny access to your content.
To provide system apps, such as Android Auto and Android Automotive OS, with access to your content, your service must always return a non-null BrowserRoot
when these system apps call the onGetRoot()
method. The signature of the Android Automotive OS system app can vary depending on the make and model of the car, so you need to permit connections from all system apps to support Android Automotive OS robustly.
The following code snippet shows how your service can validate that the calling package is a system app:
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
}
This code snippet is an excerpt from the PackageValidator
class in the Universal Android Music Player sample app on GitHub. See that class for a more detailed example of how to implement package validation for your service's onGetRoot()
method.
In addition to allowing system apps, you must let the Google Assistant connect to your MediaBrowserService
. Note that the Google Assistant has separate package names for the phone, which includes Android Auto, and for Android Automotive OS.
Implement onLoadChildren()
After receiving your root node object, Android Auto and Android Automotive OS build a top-level menu by calling onLoadChildren()
on the root node object to get its children. Client apps build submenus by calling this same method using child node objects.
Each node in your content hierarchy is represented by a MediaBrowserCompat.MediaItem
object. Each of these media items is identified by a unique ID string. Client apps treat these ID strings as opaque tokens. When a client app wants to browse to a submenu, or play a media item, it passes the token. Your app is responsible for associating the token with the appropriate media item.
The following code snippet shows a simple implementation of onLoadChildren()
method:
کاتلین
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 whether 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 whether 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); }
For a complete example of this method, see the onLoadChildren()
method in the Universal Android Music Player sample app on GitHub.
Structure the root menu
Android Auto and Android Automotive OS have specific constraints about the structure of the root menu. These are communicated to the MediaBrowserService
through root hints, which can be read through the Bundle
argument passed into onGetRoot()
. Following these hints lets the system display the root content optimally as navigational tabs. If you don't follow these hints, some root content might be dropped or made less discoverable by the system. Two hints are sent:
- A limit on the number of root children : for the majority of cases, you can expect this number to be four. This means that over four tabs cannot be shown.
- Supported flags on the root children : you can expect this value to be
MediaItem#FLAG_BROWSABLE
. This means that only browsable items—not playable items—can be shown as tabs.
Use the following code to read the relevant root hints:
کاتلین
import androidx.media.utils.MediaConstants // Later, in your MediaBrowserServiceCompat. override fun onGetRoot( clientPackageName: String, clientUid: Int, rootHints: Bundle ): BrowserRoot { val maximumRootChildLimit = rootHints.getInt( MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_LIMIT, /* defaultValue= */ 4) val supportedRootChildFlags = rootHints.getInt( MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_SUPPORTED_FLAGS, /* defaultValue= */ MediaItem.FLAG_BROWSABLE) // Rest of method... }
جاوا
import androidx.media.utils.MediaConstants; // Later, in your MediaBrowserServiceCompat. @Override public BrowserRoot onGetRoot( String clientPackageName, int clientUid, Bundle rootHints) { int maximumRootChildLimit = rootHints.getInt( MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_LIMIT, /* defaultValue= */ 4); int supportedRootChildFlags = rootHints.getInt( MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_SUPPORTED_FLAGS, /* defaultValue= */ MediaItem.FLAG_BROWSABLE); // Rest of method... }
You can choose to branch the logic for the structure of your content hierarchy based on the values of these hints, particularly if your hierarchy varies among MediaBrowser
integrations outside of Android Auto and Android Automotive OS. For example, if you normally show a root playable item, you might want to nest it under a root browsable item instead due to the value of the supported flags hint.
Apart from the root hints, there are a couple additional guidelines to follow to help ensure that tabs render optimally:
- Supply monochrome, preferably white, icons for each tab item.
- Supply short but meaningful labels for each tab item. Keeping labels short reduces the chance of the strings being truncated.
Display media artwork
Artwork for media items must be passed as a local URI using either ContentResolver.SCHEME_CONTENT
or ContentResolver.SCHEME_ANDROID_RESOURCE
. This local URI must resolve to either a bitmap or a vector drawable in the application's resources. For MediaDescriptionCompat
objects representing items in the content hierarchy, pass the URI through setIconUri()
. For MediaMetadataCompat
objects representing the currently playing item, pass the URI through putString()
, using any of the following keys:
-
MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI
-
MediaMetadataCompat.METADATA_KEY_ART_URI
-
MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI
The following steps describe how to download art from a web URI and expose it through a local URI. For a more complete example, see the implementation of openFile()
and the surrounding methods in the Universal Android Music Player sample app.
Build a
content://
URI corresponding to the web URI. The media browser service and media session pass this content URI to Android Auto and Android Automotive OS.کاتلین
fun Uri.asAlbumArtContentURI(): Uri { return Uri.Builder() .scheme(ContentResolver.SCHEME_CONTENT) .authority(CONTENT_PROVIDER_AUTHORITY) .appendPath(this.getPath()) // Make sure you trust the URI .build() }
جاوا
public static Uri asAlbumArtContentURI(Uri webUri) { return new Uri.Builder() .scheme(ContentResolver.SCHEME_CONTENT) .authority(CONTENT_PROVIDER_AUTHORITY) .appendPath(webUri.getPath()) // Make sure you trust the URI! .build(); }
In your implementation of
ContentProvider.openFile()
, check whether a file exists for the corresponding URI. If not, download and cache the image file. The following code snippet uses Glide .کاتلین
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? { val context = this.context ?: return null val file = File(context.cacheDir, uri.path) if (!file.exists()) { val remoteUri = Uri.Builder() .scheme("https") .authority("my-image-site") .appendPath(uri.path) .build() val cacheFile = Glide.with(context) .asFile() .load(remoteUri) .submit() .get(DOWNLOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS) cacheFile.renameTo(file) file = cacheFile } return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY) }
جاوا
@Nullable @Override public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException { Context context = this.getContext(); File file = new File(context.getCacheDir(), uri.getPath()); if (!file.exists()) { Uri remoteUri = new Uri.Builder() .scheme("https") .authority("my-image-site") .appendPath(uri.getPath()) .build(); File cacheFile = Glide.with(context) .asFile() .load(remoteUri) .submit() .get(DOWNLOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS); cacheFile.renameTo(file); file = cacheFile; } return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); }
For more details about content providers, refer to Creating a content provider .
Apply content styles
After building your content hierarchy using browsable or playable items, you can apply content styles that determine how those items display in the car.
You can use the following content styles:
- List items
This content style prioritizes titles and metadata over images.
- Grid items
This content style prioritizes images over titles and metadata.
Set default content styles
You can set global defaults for how your media items are displayed by including certain constants in the BrowserRoot
extras bundle of your service's onGetRoot()
method. Android Auto and Android Automotive OS read this bundle and look for those constants to determine the appropriate style.
The following extras can be used as keys in the bundle:
-
DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE
: indicates a presentation hint for all browsable items within the browse tree. -
DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE
: indicates a presentation hint for all playable items within the browse tree.
The keys can map to the following integer constant values to influence the presentation of those items:
-
DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM
: the corresponding items are presented as list items. -
DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM
: the corresponding items are presented as grid items. -
DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM
: the corresponding items are presented as "category" list items. These are the same as ordinary list items except that margins are applied around the items' icons, since the icons look better when they are small. The icons must be tintable vector drawables. This hint is expected to only be provided for browsable items. -
DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_GRID_ITEM
: the corresponding items are presented as "category" grid items. These are the same as ordinary grid items, except that margins are applied around the items' icons, since the icons look better when they are small. The icons must be tintable vector drawables. This hint is expected to only be provided for browsable items.
The following code snippet shows how to set the default content style for browsable items to grids and playable items to lists:
کاتلین
import androidx.media.utils.MediaConstants @Nullable override fun onGetRoot( @NonNull clientPackageName: String, clientUid: Int, @Nullable rootHints: Bundle ): BrowserRoot { val extras = Bundle() extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM) return BrowserRoot(ROOT_ID, extras) }
جاوا
import androidx.media.utils.MediaConstants; @Nullable @Override public BrowserRoot onGetRoot( @NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) { Bundle extras = new Bundle(); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM); return new BrowserRoot(ROOT_ID, extras); }
Set per-item content styles
The Content Style API lets you override the default content style for any browsable media item's children, as well as any media item itself.
To override the default for a browsable media item's children , create an extras bundle in the MediaDescription
of the media item and add the same previously mentioned hints. DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE
applies to that item's playable children, while DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE
applies to that item's browsable children.
To override the default for a particular media item itself , not its children, create an extras bundle in the MediaDescription
of the media item and add a hint with the key DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM
. Use the same values described previously to specify that item's presentation.
The following code snippet shows how to create a browsable MediaItem
that overrides the default content style for both itself and its children. It styles itself as a category list item, its browsable children as list items, and its playable children as grid items:
کاتلین
import androidx.media.utils.MediaConstants 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( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM) mediaDescriptionBuilder.setExtras(extras) return MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE) }
جاوا
import androidx.media.utils.MediaConstants; 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( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM); mediaDescriptionBuilder.setExtras(extras); return new MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE); }
Group items using title hints
To group related media items together, you use a per-item hint. Every media item in a group needs to declare an extras bundle in their MediaDescription
that includes a mapping with the key DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE
and an identical string value. Localize this string, which is used as the title of the group.
The following code snippet shows how to create a MediaItem
with a subgroup heading of "Songs"
:
کاتلین
import androidx.media.utils.MediaConstants 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( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs") mediaDescriptionBuilder.setExtras(extras) return MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), /* playable or browsable flag*/) }
جاوا
import androidx.media.utils.MediaConstants; 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( MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs"); mediaDescriptionBuilder.setExtras(extras); return new MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), /* playable or browsable flag*/); }
Your app must pass all the media items that you want to group together as a contiguous block. For example, suppose that you want to display two groups of media items, "Songs" and "Albums," in that order, and your app passes in five media items in the following order:
- Media item A with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Media item B with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )
- Media item C with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Media item D with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Media item E with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )
Because the media items for the "Songs" group and "Albums" group are not kept together in contiguous blocks, Android Auto and Android Automotive OS interprets this as the following four groups:
- Group 1 called "Songs" containing media item A
- Group 2 called "Albums" containing media item B
- Group 3 called "Songs" containing media items C and D
- Group 4 called "Albums" containing media item E
To display these items in two groups, your app must pass the media items in the following order instead:
- Media item A with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Media item C with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Media item D with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Songs" )
- Media item B with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )
- Media item E with
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, "Albums" )
Display additional metadata indicators
You can include additional metadata indicators to provide at-a-glance information for content in the media browser tree and during playback. Within the browse tree, Android Auto and Android Automotive OS read the extras associated with an item and look for certain constants to determine which indicators to display. During media playback, Android Auto and Android Automotive OS read the metadata for the media session and look for certain constants to determine indicators to display.
The following constants can be used in both MediaItem
description extras and MediaMetadata
extras:
-
EXTRA_DOWNLOAD_STATUS
: indicates the download status of an item. Use this constant as the key; the following long constants are the possible values:-
STATUS_DOWNLOADED
: the item is completely downloaded. -
STATUS_DOWNLOADING
: the item is being downloaded. -
STATUS_NOT_DOWNLOADED
: the item is not downloaded.
-
-
METADATA_KEY_IS_EXPLICIT
: indicates whether the item contains explicit content. To indicate an item is explicit, use this constant as the key and the longMETADATA_VALUE_ATTRIBUTE_PRESENT
as the value.
The following constants can only be used in MediaItem
description extras:
-
DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS
: indicates the completion state of long-form content, such as podcast episodes or audiobooks. Use this constant as the key; the following integer constants are the possible values:-
DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED
: the item has not been played at all. -
DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED
: the item has been partially played, and the current position is somewhere in the middle. -
DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED
: the item has been completed.
-
-
DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
: indicates the amount of completion progress on long-form content as a double between 0.0 and 1.0, inclusive. This extra provides more information about thePARTIALLY_PLAYING
state so that Android Auto or Android Automotive OS displays a more meaningful progress indicator, such as a progress bar. If you use this extra, see the section about updating the progress bar in the browse view as content is playing in this guide to learn how to keep this indicator up to date after its initial impression.
To display indicators that appear while the user is browsing the media browse tree, create an extras bundle that includes one or more of these constants and pass that bundle to the MediaDescription.Builder.setExtras()
method.
The following code snippet shows how to display indicators for an explicit media item that is 70% complete:
کاتلین
import androidx.media.utils.MediaConstants val extras = Bundle() extras.putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS, MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED) extras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.7) val description = MediaDescriptionCompat.Builder() .setMediaId(/*...*/) .setTitle(resources.getString(/*...*/)) .setExtras(extras) .build() return MediaBrowserCompat.MediaItem(description, /* flags */)
جاوا
import androidx.media.utils.MediaConstants; Bundle extras = new Bundle(); extras.putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT); extras.putInt( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS, MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED); extras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.7); MediaDescriptionCompat description = new MediaDescriptionCompat.Builder() .setMediaId(/*...*/) .setTitle(resources.getString(/*...*/)) .setExtras(extras) .build(); return new MediaBrowserCompat.MediaItem(description, /* flags */);
To display indicators for a media item that is currently being played, you can declare Long
values for METADATA_KEY_IS_EXPLICIT
or EXTRA_DOWNLOAD_STATUS
in the MediaMetadataCompat
of your mediaSession
. You can't display the DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS
or DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
indicators on the playback view.
The following code snippet shows how to indicate that the current song in the playback view is explicit and downloaded:
کاتلین
import androidx.media.utils.MediaConstants mediaSession.setMetadata( MediaMetadataCompat.Builder() .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, "Song Name") .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "Artist name") .putString( MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, albumArtUri.toString()) .putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) .putLong( MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS, MediaDescriptionCompat.STATUS_DOWNLOADED) .build())
جاوا
import androidx.media.utils.MediaConstants; mediaSession.setMetadata( new MediaMetadataCompat.Builder() .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, "Song Name") .putString( MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "Artist name") .putString( MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, albumArtUri.toString()) .putLong( MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) .putLong( MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS, MediaDescriptionCompat.STATUS_DOWNLOADED) .build());
Update the progress bar in the browse view as content is playing
As previously mentioned, you can use the DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
extra to show a progress bar for partially played content in the browse view. However, if a user continues playing the partially played content from Android Auto or Android Automotive OS, that indicator becomes inaccurate as time passes.
For Android Auto and Android Automotive OS to keep the progress bar up to date, you can supply additional information in MediaMetadataCompat
and PlaybackStateCompat
to link ongoing content to media items in the browse view. The following requirements must be met for the media item to have an automatically updating progress bar:
- When it is created, the
MediaItem
must sendDESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE
in its extras with a value between 0.0 and 1.0, inclusive. - The
MediaMetadataCompat
must sendMETADATA_KEY_MEDIA_ID
with a string value equal to the media ID passed to theMediaItem
. - The
PlaybackStateCompat
must include an extra with the keyPLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID
that maps to a string value equal to the media ID passed to theMediaItem
.
The following code snippet shows how to indicate that the currently playing item is linked to an item in the browse view:
کاتلین
import androidx.media.utils.MediaConstants // When the MediaItem is constructed to show in the browse view. // Suppose the item was 25% complete when the user launched the browse view. val mediaItemExtras = Bundle() mediaItemExtras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.25) val description = MediaDescriptionCompat.Builder() .setMediaId("my-media-id") .setExtras(mediaItemExtras) // ...and any other setters. .build() return MediaBrowserCompat.MediaItem(description, /* flags */) // Elsewhere, when the user has selected MediaItem for playback. mediaSession.setMetadata( MediaMetadataCompat.Builder() .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, "my-media-id") // ...and any other setters. .build()) val playbackStateExtras = Bundle() playbackStateExtras.putString( MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID, "my-media-id") mediaSession.setPlaybackState( PlaybackStateCompat.Builder() .setExtras(playbackStateExtras) // ...and any other setters. .build())
جاوا
import androidx.media.utils.MediaConstants; // When the MediaItem is constructed to show in the browse view. // Suppose the item was 25% complete when the user launched the browse view. Bundle mediaItemExtras = new Bundle(); mediaItemExtras.putDouble( MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.25); MediaDescriptionCompat description = new MediaDescriptionCompat.Builder() .setMediaId("my-media-id") .setExtras(mediaItemExtras) // ...and any other setters. .build(); return MediaBrowserCompat.MediaItem(description, /* flags */); // Elsewhere, when the user has selected MediaItem for playback. mediaSession.setMetadata( new MediaMetadataCompat.Builder() .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, "my-media-id") // ...and any other setters. .build()); Bundle playbackStateExtras = new Bundle(); playbackStateExtras.putString( MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID, "my-media-id"); mediaSession.setPlaybackState( new PlaybackStateCompat.Builder() .setExtras(playbackStateExtras) // ...and any other setters. .build());
Display browsable search results
Your app can provide contextual search results that display to users when they initiate a search query. Android Auto and Android Automotive OS show these results through search query interfaces or through affordances that pivot on queries made earlier in the session. To learn more, see the Support voice actions section in this guide.
To display browsable search results, include the constant key BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED
in the extras bundle of your service's onGetRoot()
method, mapping to the boolean true
.
The following code snippet shows how to enable support in the onGetRoot()
method:
کاتلین
import androidx.media.utils.MediaConstants @Nullable fun onGetRoot( @NonNull clientPackageName: String, clientUid: Int, @Nullable rootHints: Bundle ): BrowserRoot { val extras = Bundle() extras.putBoolean( MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true) return BrowserRoot(ROOT_ID, extras) }
جاوا
import androidx.media.utils.MediaConstants; @Nullable @Override public BrowserRoot onGetRoot( @NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) { Bundle extras = new Bundle(); extras.putBoolean( MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true); return new BrowserRoot(ROOT_ID, extras); }
To start providing search results, override the onSearch()
method in your media browser service. Android Auto and Android Automotive OS forward the user's search terms to this method whenever a user invokes a search query interface or “Search results” affordance.
You can organize the search results from your service's onSearch()
method using title items to make them more browsable. For example, if your app plays music, you might organize search results by album, artist, and songs.
The following code snippet shows a simple implementation of the onSearch()
method:
کاتلین
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<List<MediaItem>> result) { // Detach from results to unblock the caller (if a search is expensive). result.detach(); new AsyncTask<Void, Void, Void>() { List<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); } } }.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. }
Custom Browse Actions
Custom Browse Actions allow you to add custom icons and labels to your app's MediaItem
objects in the car's media app, and handle user interactions with these actions. This lets you extend the functionality of the Media App in a variety of ways, like adding "Download", "Add to Queue", "Play Radio", "Favorite", or "Remove" actions.
If there are more custom actions than the OEM allows to be displayed, an overflow menu will be presented to the user.
How do they work?
Each Custom Browse Action is defined with:
- An Action ID (a unique string identifier)
- An Action Label (the text displayed to the user)
- An Action Icon URI (a vector drawable that can be tinted)
You define a list of Custom Browse Actions globally as part of your BrowseRoot
. Then you can attach a subset of these actions to individual MediaItem.
When a user interacts with a Custom Browse Action, your app receives a callback in onCustomAction()
. You can then handle the action and update the list of actions for the MediaItem
if necessary. This is useful for stateful actions like "Favorite" and "Download". For actions that don't need updating, like "Play Radio", you don't need to update the list of actions.
You can also attach Custom Browse Actions to a browse node root. These actions will be displayed in a secondary toolbar under the main toolbar.
How to implement Custom Browse Actions
Here are the steps to add Custom Browse Actions to your project:
- Override two methods in your
MediaBrowserServiceCompat
implementation: - Parse the action limits at runtime:
- In
onGetRoot()
, get the maximum number of actions allowed for eachMediaItem
using the keyBROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT
in therootHints
Bundle
. A limit of 0 indicates that the feature is not supported by the system.
- In
- Build the global list of Custom Browse Actions:
- For each action, create a
Bundle
object with the following keys: *EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID
: The action ID *EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL
: The action label *EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI
: The action icon URI * Add all the actionBundle
objects to a list.
- For each action, create a
- Add the global list to your
BrowseRoot
:- In the
BrowseRoot
extrasBundle
, add the list of actions as aParcelable
Arraylist
using the keyBROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST
.
- In the
- Add actions to your
MediaItem
objects:- You can add actions to individual
MediaItem
objects by including the list of action IDs in theMediaDescriptionCompat
extras using the keyDESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST
. This list must be a subset of the global list of actions you defined in theBrowseRoot
.
- You can add actions to individual
- Handle actions and return progress or results:
- In
onCustomAction
, handle the action based on the action ID and any other data you need. You can get the ID of theMediaItem
that triggered the action from the extras using the keyEXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID
. . - You can update the list of actions for a
MediaItem
by including the keyEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
in the progress or result bundle.
- In
Here are some changes you can make in your BrowserServiceCompat
to get started with Custom Browse Actions.
Override BrowserServiceCompat
You need to override the following methods in MediaBrowserServiceCompat
.
public void onLoadItem(String itemId, @NonNull Result<MediaBrowserCompat.MediaItem> result)
public void onCustomAction(@NonNull String action, Bundle extras, @NonNull Result<Bundle> result)
Parse actions limit
You should check to see how many Custom Browse Actions are supported.
public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, Bundle rootHints) { rootHints.getInt( MediaConstants.BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT, 0) }
Build a Custom Browse Action
Each action needs to be packed into a separate Bundle
.
- Action ID
bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID, "<ACTION_ID>")
- Action Label
bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL, "<ACTION_LABEL>")
- Action Icon URI
bundle.putString(MediaConstants.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI, "<ACTION_ICON_URI>")
Add Custom Browse Actions to Parceable
ArrayList
Add all Custom Browse Action Bundle
objects into an ArrayList
.
private ArrayList<Bundle> createCustomActionsList( CustomBrowseAction browseActions) { ArrayList<Bundle> browseActionsBundle = new ArrayList<>(); for (CustomBrowseAction browseAction : browseActions) { Bundle action = new Bundle(); action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID, browseAction.mId); action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL, getString(browseAction.mLabelResId)); action.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI, browseAction.mIcon); browseActionsBundle.add(action); } return browseActionsBundle; }
Add Custom Browse Action list to the browse root
public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, Bundle rootHints) { Bundle browserRootExtras = new Bundle(); browserRootExtras.putParcelableArrayList( BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST, createCustomActionsList())); mRoot = new BrowserRoot(ROOT_ID, browserRootExtras); return mRoot; }
Add actions to a MediaItem
MediaDescriptionCompat buildDescription (long id, String title, String subtitle, String description, Uri iconUri, Uri mediaUri, ArrayList<String> browseActionIds) { MediaDescriptionCompat.Builder bob = new MediaDescriptionCompat.Builder(); bob.setMediaId(id); bob.setTitle(title); bob.setSubtitle(subtitle); bob.setDescription(description); bob.setIconUri(iconUri); bob.setMediaUri(mediaUri); Bundle extras = new Bundle(); extras.putStringArrayList( DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST, browseActionIds); bob.setExtras(extras); return bob.build(); } MediaItem mediaItem = new MediaItem(buildDescription(...), flags);
Build onCustomAction
result
- Parse mediaId from
Bundle extras
:@Override public void onCustomAction( @NonNull String action, Bundle extras, @NonNull Result<Bundle> result){ String mediaId = extras.getString(MediaConstans.EXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID); }
- For asynchronous results detach result.
result.detach()
- Build result bundle
- Message to user
mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE, mContext.getString(stringRes))
- Update item(use to update actions in an item)
mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM, mediaId);
- Open Playback view
//Shows user the PBV without changing the playback state mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM, null);
- Update Browse Node
//Change current browse node to mediaId mResultBundle.putString(EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE, mediaId);
- Message to user
- If an error, call
result.sendError(resultBundle).
- If progress update, call
result.sendProgressUpdate(resultBundle)
. - Finish by calling
result.sendResult(resultBundle)
.
Update Action State
By using the result.sendProgressUpdate(resultBundle)
method with the EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
key, you can update the MediaItem
to reflect the new state of the action. This lets you provide real-time feedback to the user about the progress and result of their action.
Example: Download Action
Here's an example of how you can use this feature to implement a download action with three states:
- Download: This is the initial state of the action. When the user selects this action, you can swap it with "Downloading" and call
sendProgressUpdate
to update the UI. - Downloading: This state indicates that the download is in progress. You can use this state to show a progress bar or another indicator to the user.
- Downloaded: This state indicates that the download is complete. When the download finishes, you can swap "Downloading" with "Downloaded" and call
sendResult
with theEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
key to indicate that the item should be refreshed. Additionally, you can use theEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE
key to display a success message to the user.
This approach lets you provide clear feedback to the user about the download process and its current state. You can add even more detail with icons to show 25%, 50%, 75% download states.
Example: Favorite Action
Another example is a favorite action with two states:
- Favorite: This action is displayed for items that are not in the user's favorites list. When the user selects this action, you can swap it with "Favorited" and call
sendResult
with theEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
key to update the UI. - Favorited: This action is displayed for items that are in the user's favorites list. When the user selects this action, you can swap it with "Favorite" and call
sendResult
with theEXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
key to update the UI.
This approach provides a clear and consistent way for users to manage their favorite items.
These examples showcase the flexibility of Custom Browse Actions and how you can use them to implement a variety of functionalities with real-time feedback for an enhanced user experience in the car's media app.
For a complete example implementation of this feature, you can refer to the TestMediaApp
project.
Enable playback control
Android Auto and Android Automotive OS send playback control commands through your service's MediaSessionCompat
. You must register a session and implement the associated callback methods.
Register a media session
In your media browser service's onCreate()
method, create a MediaSessionCompat
, then register the media session by calling setSessionToken()
.
The following code snippet shows how to create and register a media session:
کاتلین
override fun onCreate() { super.onCreate() ... // Start a new MediaSession. val session = MediaSessionCompat(this, "session tag").apply { // Set a callback object that implements MediaSession.Callback // to handle play control requests. 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 that implements MediaSession.Callback // to handle play control requests. session.setCallback(new MyMediaSessionCallback()); ... }
When you create the media session object, you set a callback object that is used to handle playback control requests. You create this callback object by providing an implementation of the MediaSessionCompat.Callback
class for your app. The next section discusses how to implement this object.
Implement play commands
When a user requests playback for a media item from your app, Android Automotive OS and Android Auto use the MediaSessionCompat.Callback
class from your app's MediaSessionCompat
object that they obtained from your app's media browser service. When a user wants to control content playback, such as pausing playback or skipping to the next track, Android Auto and Android Automotive OS invoke one of the callback object's methods.
To handle content playback, your app must extend the abstract MediaSessionCompat.Callback
class and implement the methods that your app supports.
Implement all the following callback methods that make sense for the type of content that your app offers:
-
onPrepare()
- Invoked when the media source is changed. Android Automotive OS also invokes this method immediately after booting. Your media app must implement this method.
-
onPlay()
- Invoked if the user chooses play without choosing a specific item. Your app must play its default content or, if playback was paused with
onPause()
, your app resumes playback.Note: Your app should not automatically start playing music when Android Automotive OS or Android Auto connect to your media browser service. For more information, see the section about setting the initial playback state .
-
onPlayFromMediaId()
- Invoked when the user chooses to play a specific item. The method is passed the ID that your media browser service assigned to the media item in your content hierarchy.
-
onPlayFromSearch()
- Invoked when the user chooses to play from a search query. The app must make an appropriate choice based on the search string that was passed in.
-
onPause()
- Invoked when the user chooses to pause playback.
-
onSkipToNext()
- Invoked when the user chooses to skip to the next item.
-
onSkipToPrevious()
- Invoked when the user chooses to skip to the previous item.
-
onStop()
- Invoked when the user chooses to stop playback.
Override these methods in your app to provide any desired functionality. You don't need to implement a method if its functionality isn't supported by your app. For example, if your app plays a live stream, such as a sports broadcast, you don't need to implement the onSkipToNext()
method. You can use the default implementation of onSkipToNext()
instead.
Your app doesn't need any special logic to play content through the car's speakers. When your app receives a request to play content, it can play audio the same way that it plays content through a user's phone speakers or headphones. Android Auto and Android Automotive OS automatically send the audio content to the car's system to play over the car's speakers.
For more information about playing audio content, see MediaPlayer overview , Audio app overview , and the ExoPlayer overview .
Set standard playback actions
Android Auto and Android Automotive OS display playback controls based on the actions that are enabled in the PlaybackStateCompat
object.
By default, your app must support the following actions:
Your app can additionally support the following actions if they are relevant to the app's content:
In addition, you have the option to create a play queue that can be displayed for the user, but it is not required. To do this, call the setQueue()
and setQueueTitle()
methods, enable the ACTION_SKIP_TO_QUEUE_ITEM
action, and define the callback onSkipToQueueItem()
.
Also, add support for the Now playing icon, which is an indicator for what is currently playing. To do this, call the setActiveQueueItemId()
method and pass the ID of the currently playing item in the queue. You need to update setActiveQueueItemId()
whenever there is a queue change.
Android Auto and Android Automotive OS display buttons for each enabled action as well as the playback queue. When the buttons are clicked, the system invokes their corresponding callback from MediaSessionCompat.Callback
.
Reserve unused space
Android Auto and Android Automotive OS reserve space in the UI for the ACTION_SKIP_TO_PREVIOUS
and ACTION_SKIP_TO_NEXT
actions. If your app does not support one of these functions, Android Auto and Android Automotive OS use the space to display any custom actions you create.
If you don't want to fill those spaces with custom actions, you can reserve them so that Android Auto and Android Automotive OS leave the space blank whenever your app does not support the corresponding function. To do this, call the setExtras()
method with an extras bundle that contains constants that correspond to the reserved functions. SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT
corresponds to ACTION_SKIP_TO_NEXT
, and SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV
corresponds to ACTION_SKIP_TO_PREVIOUS
. Use these constants as keys in the bundle, and use the boolean true
for their values.
Set initial PlaybackState
As Android Auto and Android Automotive OS communicate with your media browser service, your media session communicates the status of content playback using the PlaybackStateCompat
. Your app should not automatically start playing music when Android Automotive OS or Android Auto connect to your media browser service. Instead, rely on Android Auto and Android Automotive OS to resume or start playback based on the car's state or user actions.
To accomplish this, set the initial PlaybackStateCompat
of your media session to STATE_STOPPED
, STATE_PAUSED
, STATE_NONE
, or STATE_ERROR
.
Media sessions within Android Auto and Android Automotive OS only last for the duration of the drive, so users start and stop these sessions frequently. To promote a seamless experience between drives, keep track of the user's previous session state, so that when the media app receives a resume request, the user can automatically pick up where they left off—for example, the last played media item, the PlaybackStateCompat
, and the queue.
Add custom playback actions
You can add custom playback actions to display additional actions that your media app supports. If space permits (and is not reserved) , Android adds the custom actions to the transport controls. Otherwise, the custom actions display in the overflow menu. Custom actions display in the order they are added to the PlaybackStateCompat
.
Use custom actions to provide behavior distinct from standard actions . Don't use them to replace or duplicate standard actions.
You can add custom actions using the addCustomAction()
method in the PlaybackStateCompat.Builder
class.
The following code snippet shows how to add a custom “Start a radio channel” action:
کاتلین
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());
For a more detailed example of this method, see the setCustomAction()
method in the Universal Android Music Player sample app on GitHub.
After creating your custom action, your media session can respond to the action by overriding the onCustomAction()
method.
The following code snippet shows how your app might respond to a “Start a radio channel” action:
کاتلین
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)) { ... } }
For a more detailed example of this method, see the onCustomAction
method in the Universal Android Music Player sample app on GitHub.
Icons for custom actions
Each custom action that you create requires an icon resource. Apps in cars can run on many different screen sizes and densities, so icons that you provide must be vector drawables . A vector drawable allows you to scale assets without losing the detail. A vector drawable also makes it easy to align edges and corners to pixel boundaries at smaller resolutions.
If a custom action is stateful—for example, it toggles a playback setting on or off—provide different icons for the different states, so users can see a change when they select the action.
Provide alternative icon styles for disabled actions
When a custom action is unavailable for the current context, swap the custom action icon with an alternative icon that shows that the action is disabled.
Indicate audio format
To indicate that currently playing media uses a special audio format, you can specify icons that are rendered in cars that support this feature. You can set the KEY_CONTENT_FORMAT_TINTABLE_LARGE_ICON_URI
and the KEY_CONTENT_FORMAT_TINTABLE_SMALL_ICON_URI
in the extras bundle of the currently playing media item (passed to MediaSession.setMetadata()
). Make sure to set both of those extras, to accommodate different layouts.
In addition, you can set the KEY_IMMERSIVE_AUDIO
extra to tell car OEMs that this is immersive audio, and they should be very careful when deciding whether to apply audio effects that might interfere with the immersive content.
Add links from currently-playing item
You can configure the currently-playing media item so its subtitle, description, or both are links to other media items. That lets the user jump quickly to related items; for example, they might jump to other songs by the same artist, other episodes of that podcast, etc. If the car supports this feature, users can tap the link to browse to that content.
To add links, configure the KEY_SUBTITLE_LINK_MEDIA_ID
metadata (to link from the subtitle) or KEY_DESCRIPTION_LINK_MEDIA_ID
(to link from the description). For details, see the reference documentation for those metadata fields.
Support voice actions
Your media app must support voice actions to help provide drivers with a safe and convenient experience that minimizes distractions. For example, if your app is playing one media item, the user can say “ Play [song title] " to tell your app to play a different song without looking at or touching the car's display. Users can initiate queries by clicking the appropriate buttons on their steering wheel or speaking the hotwords " OK Google ."
When Android Auto or Android Automotive OS detects and interprets a voice action, that voice action is delivered to the app through onPlayFromSearch()
. On receiving this callback, the app finds content matching the query
string and starts playback.
Users can specify different categories of terms in their query: genre, artist, album, song name, radio station, or playlist, among others. When building support for search, account for all the categories that make sense for your app. If Android Auto or Android Automotive OS detects that a given query fits into certain categories, it appends extras in the extras
parameter. The following extras can be sent:
Account for an empty query
string, which can be sent by Android Auto or Android Automotive OS if the user doesn't specify search terms. For example, if the user says " Play some music ." In that case, your app might choose to start a recently played or newly suggested track.
If a search cannot be processed quickly, do not block in onPlayFromSearch()
. Instead, set the playback state to STATE_CONNECTING
and perform the search on an async thread.
Once playback begins, consider populating the media session's queue with related content. For example, if the user requests an album to be played, your app might fill the queue with the album's tracklist. Also consider implementing support for browsable search results so a user can choose a different track that matches their query.
In addition to " play " queries, Android Auto and Android Automotive OS recognize voice queries to control playback like " pause music " and " next song " and match these commands to the appropriate media session callbacks, like onPause()
and onSkipToNext()
.
For a detailed example on how to implement voice-enabled playback actions in your app, see Google Assistant and media apps .
Implement distraction safeguards
Because a user's phone is connected to their car's speakers while using Android Auto, you must take additional precautions to help prevent driver distraction.
Suppress alarms in the car
Android Auto media apps must not start playing audio through the car speakers unless the user starts playback by, for example, pressing a play button. Even a user-scheduled alarm from your media app must not start playing music through the car speakers.
To fulfill this requirement, your app can use CarConnection
as a signal before playing any audio. Your app can check whether the phone is projecting to a car screen by observing the LiveData
for the car connection type and checking whether it is equal to CONNECTION_TYPE_PROJECTION
.
If the user's phone is projecting, media apps that support alarms must do one of the following things:
- Disable the alarm.
- Play the alarm over
STREAM_ALARM
and provide a UI on the phone screen to disable the alarm.
Handle media advertisements
By default, Android Auto displays a notification when the media metadata changes during an audio playback session. When a media app switches from playing music to running an advertisement, it is distracting to display a notification to the user. To prevent Android Auto from displaying a notification in this case, you must set the media metadata key METADATA_KEY_IS_ADVERTISEMENT
to METADATA_VALUE_ATTRIBUTE_PRESENT
, as shown in the following code snippet:
کاتلین
import androidx.media.utils.MediaConstants override fun onPlayFromMediaId(mediaId: String, extras: Bundle?) { MediaMetadataCompat.Builder().apply { if (isAd(mediaId)) { putLong( MediaConstants.METADATA_KEY_IS_ADVERTISEMENT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT) } // ...add any other properties you normally would. mediaSession.setMetadata(build()) } }
جاوا
import androidx.media.utils.MediaConstants; @Override public void onPlayFromMediaId(String mediaId, Bundle extras) { MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder(); if (isAd(mediaId)) { builder.putLong( MediaConstants.METADATA_KEY_IS_ADVERTISEMENT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT); } // ...add any other properties you normally would. mediaSession.setMetadata(builder.build()); }
Handle general errors
When the app experiences an error, set the playback state to STATE_ERROR
and provide an error message using the setErrorMessage()
method. See PlaybackStateCompat
for a list of error codes that you can use when setting the error message. Error messages must be user-facing and localized with the user's current locale. Android Auto and Android Automotive OS can then display the error message to the user.
For example, if content is not available in the user's current region, you can use the ERROR_CODE_NOT_AVAILABLE_IN_REGION
error code when setting the error message.
کاتلین
mediaSession.setPlaybackState( PlaybackStateCompat.Builder() .setState(PlaybackStateCompat.STATE_ERROR) .setErrorMessage(PlaybackStateCompat.ERROR_CODE_NOT_AVAILABLE_IN_REGION, getString(R.string.error_unsupported_region)) // ...and any other setters. .build())
جاوا
mediaSession.setPlaybackState( new PlaybackStateCompat.Builder() .setState(PlaybackStateCompat.STATE_ERROR) .setErrorMessage(PlaybackStateCompat.ERROR_CODE_NOT_AVAILABLE_IN_REGION, getString(R.string.error_unsupported_region)) // ...and any other setters. .build());
For more information about error states, see Using a media session: States and errors .
If an Android Auto user needs to open your phone app to resolve an error, provide that information to the user in your message. For example, your error message might say "Sign in to [your app name]" instead of "Please sign in."