แอปของคุณต้องประกาศ MediaBrowserService พร้อมตัวกรอง Intent ในไฟล์ Manifest และคุณสามารถเลือกชื่อบริการของคุณเองได้ ในตัวอย่างต่อไปนี้ ชื่อบริการที่เลือกคือ MediaPlaybackService
<service android:name=".MediaPlaybackService">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>
เริ่มต้นเซสชันสื่อ
เมื่อบริการได้รับเมธอด Lifecycle Callback onCreate() บริการควรทำตามขั้นตอนต่อไปนี้
- สร้างและเริ่มต้นเซสชันสื่อ
- ตั้งค่า Callback ของเซสชันสื่อ
- ตั้งค่าโทเค็นของเซสชันสื่อ
โค้ด onCreate() ต่อไปนี้แสดงขั้นตอนเหล่านี้
Kotlin
private const val MY_MEDIA_ROOT_ID = "media_root_id" private const val MY_EMPTY_MEDIA_ROOT_ID = "empty_root_id" class MediaPlaybackService : MediaBrowserServiceCompat() { private var mediaSession: MediaSessionCompat? = null private lateinit var stateBuilder: PlaybackStateCompat.Builder override fun onCreate() { super.onCreate() // Create a MediaSessionCompat mediaSession = MediaSessionCompat(baseContext, LOG_TAG).apply { // Enable callbacks from MediaButtons and TransportControls setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS ) // Set an initial PlaybackState with ACTION_PLAY, so media buttons can start the player stateBuilder = PlaybackStateCompat.Builder() .setActions(PlaybackStateCompat.ACTION_PLAY or PlaybackStateCompat.ACTION_PLAY_PAUSE ) setPlaybackState(stateBuilder.build()) // MySessionCallback() has methods that handle callbacks from a media controller setCallback(MySessionCallback()) // Set the session's token so that client activities can communicate with it. setSessionToken(sessionToken) } } }
Java
public class MediaPlaybackService extends MediaBrowserServiceCompat { private static final String MY_MEDIA_ROOT_ID = "media_root_id"; private static final String MY_EMPTY_MEDIA_ROOT_ID = "empty_root_id"; private MediaSessionCompat mediaSession; private PlaybackStateCompat.Builder stateBuilder; @Override public void onCreate() { super.onCreate(); // Create a MediaSessionCompat mediaSession = new MediaSessionCompat(context, LOG_TAG); // Enable callbacks from MediaButtons and TransportControls mediaSession.setFlags( MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); // Set an initial PlaybackState with ACTION_PLAY, so media buttons can start the player stateBuilder = new PlaybackStateCompat.Builder() .setActions( PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PLAY_PAUSE); mediaSession.setPlaybackState(stateBuilder.build()); // MySessionCallback() has methods that handle callbacks from a media controller mediaSession.setCallback(new MySessionCallback()); // Set the session's token so that client activities can communicate with it. setSessionToken(mediaSession.getSessionToken()); } }
จัดการการเชื่อมต่อไคลเอ็นต์
MediaBrowserService มี 2 เมธอดที่จัดการการเชื่อมต่อไคลเอ็นต์ ได้แก่ onGetRoot() ควบคุมการเข้าถึงบริการ และ onLoadChildren() ช่วยให้ไคลเอ็นต์สร้างและแสดงเมนูของลำดับชั้นเนื้อหาของ MediaBrowserService ได้
การควบคุมการเชื่อมต่อไคลเอ็นต์ด้วย onGetRoot()
เมธอด onGetRoot() จะแสดงผลโหนดรากของลำดับชั้นเนื้อหา หากเมธอดแสดงผลเป็น Null ระบบจะปฏิเสธการเชื่อมต่อ
หากต้องการอนุญาตให้ไคลเอ็นต์เชื่อมต่อกับบริการและเรียกดูเนื้อหาสื่อ onGetRoot() ต้องแสดงผล BrowserRoot ที่ไม่ใช่ Null ซึ่งเป็นรหัสรากที่แสดงถึงลำดับชั้นเนื้อหา
หากต้องการอนุญาตให้ไคลเอ็นต์เชื่อมต่อกับ MediaSession โดยไม่ต้องเรียกดู onGetRoot() จะต้องแสดงผล BrowserRoot ที่ไม่ใช่ Null แต่รหัสรูทควรแสดงถึงลำดับชั้นเนื้อหาที่ว่างเปล่า
การใช้งาน onGetRoot() โดยทั่วไปอาจมีลักษณะดังนี้
Kotlin
override fun onGetRoot( clientPackageName: String, clientUid: Int, rootHints: Bundle? ): MediaBrowserServiceCompat.BrowserRoot { // (Optional) Control the level of access for the specified package name. // You'll need to write your own logic to do this. return if (allowBrowsing(clientPackageName, clientUid)) { // Returns a root ID that clients can use with onLoadChildren() to retrieve // the content hierarchy. MediaBrowserServiceCompat.BrowserRoot(MY_MEDIA_ROOT_ID, null) } else { // Clients can connect, but this BrowserRoot is an empty hierarchy // so onLoadChildren returns nothing. This disables the ability to browse for content. MediaBrowserServiceCompat.BrowserRoot(MY_EMPTY_MEDIA_ROOT_ID, null) } }
Java
@Override public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) { // (Optional) Control the level of access for the specified package name. // You'll need to write your own logic to do this. if (allowBrowsing(clientPackageName, clientUid)) { // Returns a root ID that clients can use with onLoadChildren() to retrieve // the content hierarchy. return new BrowserRoot(MY_MEDIA_ROOT_ID, null); } else { // Clients can connect, but this BrowserRoot is an empty hierarchy // so onLoadChildren returns nothing. This disables the ability to browse for content. return new BrowserRoot(MY_EMPTY_MEDIA_ROOT_ID, null); } }
ในบางกรณี คุณอาจต้องการควบคุมผู้ที่สามารถเชื่อมต่อกับ MediaBrowserService ได้ วิธีหนึ่งคือใช้รายการควบคุมการเข้าถึง (ACL) ที่ระบุการเชื่อมต่อที่ได้รับอนุญาต หรืออีกวิธีหนึ่งคือระบุการเชื่อมต่อที่ควรถูกห้าม ดูตัวอย่างวิธีใช้ ACL ที่อนุญาตการเชื่อมต่อที่เฉพาะเจาะจงได้ที่คลาส PackageValidator ในแอปตัวอย่าง Universal Android Music Player
คุณควรพิจารณาแสดงลำดับชั้นเนื้อหาที่แตกต่างกันไปตามประเภทของไคลเอ็นต์ที่ทำการค้นหา โดยเฉพาะอย่างยิ่ง Android Auto จะจำกัดวิธีที่ผู้ใช้โต้ตอบกับแอปเสียง ดูข้อมูลเพิ่มเติมได้ที่ การเล่นเสียงสำหรับ
Auto คุณสามารถดู clientPackageName ในเวลาที่เชื่อมต่อเพื่อกำหนดประเภทไคลเอ็นต์ และแสดงผล BrowserRoot ที่แตกต่างกันไปตามไคลเอ็นต์ (หรือ rootHints หากมี)
การสื่อสารเนื้อหาด้วย onLoadChildren()
หลังจากที่ไคลเอ็นต์เชื่อมต่อแล้ว ไคลเอ็นต์จะข้ามผ่านลำดับชั้นเนื้อหาได้โดยการเรียก MediaBrowserCompat.subscribe() ซ้ำๆ เพื่อสร้างการแสดง UI ในเครื่อง เมธอด subscribe() จะส่ง Callback
onLoadChildren() ไปยังบริการ ซึ่งจะแสดงผลรายการออบเจ็กต์ MediaBrowser.MediaItem
MediaItem แต่ละรายการจะมีสตริงรหัสที่ไม่ซ้ำกัน ซึ่งเป็นโทเค็นทึบแสง เมื่อไคลเอ็นต์ต้องการเปิดเมนูย่อยหรือเล่นรายการ ไคลเอ็นต์จะส่งรหัส บริการของคุณมีหน้าที่รับผิดชอบในการเชื่อมโยงรหัสกับโหนดเมนูหรือรายการเนื้อหาที่เหมาะสม
การใช้งาน onLoadChildren() อย่างง่ายอาจมีลักษณะดังนี้
Kotlin
override fun onLoadChildren( parentMediaId: String, result: MediaBrowserServiceCompat.Result<List<MediaBrowserCompat.MediaItem>> ) { // Browsing not allowed if (MY_EMPTY_MEDIA_ROOT_ID == parentMediaId) { result.sendResult(null) return } // Assume for example that the music catalog is already loaded/cached. val mediaItems = emptyList<MediaBrowserCompat.MediaItem>() // Check if this is the root menu: if (MY_MEDIA_ROOT_ID == parentMediaId) { // Build the MediaItem objects for the top level, // and put them in the mediaItems list... } else { // Examine the passed parentMediaId to see which submenu we're at, // and put the children of that menu in the mediaItems list... } result.sendResult(mediaItems) }
Java
@Override public void onLoadChildren(final String parentMediaId, final Result<List<MediaItem>> result) { // Browsing not allowed if (TextUtils.equals(MY_EMPTY_MEDIA_ROOT_ID, parentMediaId)) { result.sendResult(null); return; } // Assume for example that the music catalog is already loaded/cached. List<MediaItem> mediaItems = new ArrayList<>(); // Check if this is the root menu: if (MY_MEDIA_ROOT_ID.equals(parentMediaId)) { // Build the MediaItem objects for the top level, // and put them in the mediaItems list... } else { // Examine the passed parentMediaId to see which submenu we're at, // and put the children of that menu in the mediaItems list... } result.sendResult(mediaItems); }
หมายเหตุ: MediaItem ออบเจ็กต์ที่ MediaBrowserService ส่งมา
ไม่ควรมีบิตแมปไอคอน ให้ใช้ Uri แทนโดยเรียก
setIconUri()
เมื่อสร้าง MediaDescription สำหรับแต่ละรายการ
ดูตัวอย่างวิธีใช้งาน onLoadChildren() ได้ที่แอปตัวอย่าง Universal
Android Music Player
วงจรชีวิตของบริการ Media Browser
ลักษณะการทำงานของบริการ Android จะขึ้นอยู่กับว่าบริการนั้นเริ่มต้น หรือ ผูก กับไคลเอ็นต์อย่างน้อย 1 ราย หลังจากสร้างบริการแล้ว คุณจะเริ่มต้น ผูก หรือทั้ง 2 อย่างก็ได้ ในทุกสถานะเหล่านี้ บริการจะทำงานได้อย่างสมบูรณ์และสามารถทำงานที่ออกแบบไว้ได้ ความแตกต่างคือระยะเวลาที่บริการจะยังคงอยู่ บริการที่มีผลผูกพันจะไม่ถูกทำลายจนกว่าไคลเอ็นต์ที่ผูกไว้ทั้งหมดจะยกเลิกการเชื่อมโยง คุณสามารถหยุดและทำลายบริการที่เริ่มต้นไว้อย่างชัดเจน (โดยสมมติว่าบริการนั้นไม่ได้ผูกกับไคลเอ็นต์อีกต่อไป)
เมื่อ MediaBrowser ที่ทำงานในกิจกรรมอื่นเชื่อมต่อกับ
MediaBrowserService ระบบจะผูกกิจกรรมกับบริการ ทำให้บริการ
ผูกไว้ (แต่ไม่ได้เริ่มต้น) ลักษณะการทำงานเริ่มต้นนี้มีอยู่ในคลาส MediaBrowserServiceCompat
ระบบจะทำลายบริการที่ผูกไว้เท่านั้น (และไม่ได้เริ่มต้น) เมื่อไคลเอ็นต์ทั้งหมดของบริการยกเลิกการผูก หากกิจกรรม UI ของคุณยกเลิกการเชื่อมต่อในจุดนี้ ระบบจะทำลายบริการ ซึ่งจะไม่เป็นปัญหาหากคุณยังไม่ได้เล่นเพลง อย่างไรก็ตาม เมื่อการเล่นเริ่มขึ้น ผู้ใช้มักจะคาดหวังว่าจะได้ฟังต่อไปแม้หลังจากเปลี่ยนแอปแล้ว คุณไม่ต้องการทำลายเพลเยอร์เมื่อยกเลิกการผูก UI เพื่อทำงานกับแอปอื่น
ด้วยเหตุนี้ คุณจึงต้องตรวจสอบว่าบริการเริ่มต้นขึ้นเมื่อเริ่มเล่นโดยเรียก startService() บริการที่เริ่มต้นไว้จะต้องหยุดอย่างชัดเจน ไม่ว่าจะผูกไว้หรือไม่ก็ตาม ซึ่งจะช่วยให้เพลเยอร์ทำงานต่อไปได้แม้ว่ากิจกรรม UI ที่ควบคุมจะยกเลิกการผูก
หากต้องการหยุดบริการที่เริ่มต้นไว้ ให้เรียก Context.stopService() หรือ stopSelf() ระบบจะหยุดและทำลายบริการโดยเร็วที่สุด อย่างไรก็ตาม หากไคลเอ็นต์อย่างน้อย 1 รายยังคงผูกกับบริการอยู่ ระบบจะเลื่อนการเรียกให้หยุดบริการออกไปจนกว่าไคลเอ็นต์ทั้งหมดจะยกเลิกการผูก
วงจรชีวิตของ MediaBrowserService จะถูกควบคุมโดยวิธีที่สร้าง จำนวนไคลเอ็นต์ที่ผูกไว้ และการเรียกที่ได้รับจาก Callback ของเซสชันสื่อ สรุปได้ดังนี้
- ระบบจะสร้างบริการเมื่อเริ่มต้นเพื่อตอบสนองต่อปุ่มสื่อ หรือเมื่อกิจกรรมผูกกับบริการ (หลังจากเชื่อมต่อผ่าน
MediaBrowserของบริการ) - Callback
onPlay()ของเซสชันสื่อควรมีโค้ดที่เรียกstartService()ซึ่งจะช่วยให้บริการเริ่มต้นและทำงานต่อไปได้ แม้ว่ากิจกรรมMediaBrowserUI ทั้งหมดที่ผูกไว้จะยกเลิกการผูก - Callback
onStop()ควรเรียกstopSelf()หากบริการเริ่มต้นไว้ การดำเนินการนี้จะหยุดบริการ นอกจากนี้ ระบบจะทำลายบริการหากไม่มีกิจกรรมใดผูกกับบริการ มิฉะนั้น บริการจะยังคงผูกไว้จนกว่ากิจกรรมทั้งหมดจะยกเลิกการผูก (หากได้รับstartService()ที่ตามมาอีกก่อนที่ระบบจะทำลายบริการ ระบบจะยกเลิกการหยุดที่รอดำเนินการ)
โฟลว์ชาร์ตต่อไปนี้แสดงวิธีจัดการวงจรชีวิตของบริการ ตัวแปร Counter จะติดตามจำนวนไคลเอ็นต์ที่ผูกไว้

การใช้การแจ้งเตือน MediaStyle กับบริการที่ทำงานอยู่เบื้องหน้า
เมื่อบริการกำลังเล่น บริการควรทำงานในเบื้องหน้า ซึ่งจะช่วยให้ระบบทราบว่าบริการกำลังทำหน้าที่ที่เป็นประโยชน์และไม่ควรหยุดบริการหากระบบมีหน่วยความจำเหลือน้อย บริการที่ทำงานอยู่เบื้องหน้าต้องแสดงการแจ้งเตือนเพื่อให้ผู้ใช้ทราบและสามารถควบคุมบริการได้หากต้องการ Callback onPlay() ควรทำให้บริการทำงานในเบื้องหน้า (โปรดทราบว่านี่เป็นความหมายพิเศษของคำว่า "เบื้องหน้า" แม้ว่า Android จะพิจารณาว่าบริการทำงานในเบื้องหน้าเพื่อวัตถุประสงค์ในการจัดการกระบวนการ แต่สำหรับผู้ใช้แล้ว เพลเยอร์จะเล่นในเบื้องหลังขณะที่แอปอื่นปรากฏใน "เบื้องหน้า" บนหน้าจอ)
เมื่อบริการทำงานในเบื้องหน้า บริการจะต้องแสดงการแจ้งเตือน โดยควรมีการควบคุมการขนส่งอย่างน้อย 1 รายการ นอกจากนี้ การแจ้งเตือนควรมีข้อมูลที่เป็นประโยชน์จากข้อมูลเมตาของเซสชันด้วย
สร้างและแสดงการแจ้งเตือนเมื่อผู้เล่นเริ่มเล่น ตำแหน่งที่ดีที่สุดในการดำเนินการนี้คือภายในเมธอด MediaSessionCompat.Callback.onPlay()
ตัวอย่างด้านล่างใช้ NotificationCompat.MediaStyle ซึ่งออกแบบมาสำหรับแอปสื่อ โดยแสดงวิธีสร้างการแจ้งเตือนที่แสดงข้อมูลเมตาและการควบคุมการขนส่ง เมธอดความสะดวก getController() ช่วยให้คุณสร้างตัวควบคุมสื่อจากเซสชันสื่อได้โดยตรง
Kotlin
// Given a media session and its context (usually the component containing the session) // Create a NotificationCompat.Builder // Get the session's metadata val controller = mediaSession.controller val mediaMetadata = controller.metadata val description = mediaMetadata.description val builder = NotificationCompat.Builder(context, channelId).apply { // Add the metadata for the currently playing track setContentTitle(description.title) setContentText(description.subtitle) setSubText(description.description) setLargeIcon(description.iconBitmap) // Enable launching the player by clicking the notification setContentIntent(controller.sessionActivity) // Stop the service when the notification is swiped away setDeleteIntent( MediaButtonReceiver.buildMediaButtonPendingIntent( context, PlaybackStateCompat.ACTION_STOP ) ) // Make the transport controls visible on the lockscreen setVisibility(NotificationCompat.VISIBILITY_PUBLIC) // Add an app icon and set its accent color // Be careful about the color setSmallIcon(R.drawable.notification_icon) color = ContextCompat.getColor(context, R.color.primaryDark) // Add a pause button addAction( NotificationCompat.Action( R.drawable.pause, getString(R.string.pause), MediaButtonReceiver.buildMediaButtonPendingIntent( context, PlaybackStateCompat.ACTION_PLAY_PAUSE ) ) ) // Take advantage of MediaStyle features setStyle(android.support.v4.media.app.NotificationCompat.MediaStyle() .setMediaSession(mediaSession.sessionToken) .setShowActionsInCompactView(0) // Add a cancel button .setShowCancelButton(true) .setCancelButtonIntent( MediaButtonReceiver.buildMediaButtonPendingIntent( context, PlaybackStateCompat.ACTION_STOP ) ) ) } // Display the notification and place the service in the foreground startForeground(id, builder.build())
Java
// Given a media session and its context (usually the component containing the session) // Create a NotificationCompat.Builder // Get the session's metadata MediaControllerCompat controller = mediaSession.getController(); MediaMetadataCompat mediaMetadata = controller.getMetadata(); MediaDescriptionCompat description = mediaMetadata.getDescription(); NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId); builder // Add the metadata for the currently playing track .setContentTitle(description.getTitle()) .setContentText(description.getSubtitle()) .setSubText(description.getDescription()) .setLargeIcon(description.getIconBitmap()) // Enable launching the player by clicking the notification .setContentIntent(controller.getSessionActivity()) // Stop the service when the notification is swiped away .setDeleteIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(context, PlaybackStateCompat.ACTION_STOP)) // Make the transport controls visible on the lockscreen .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) // Add an app icon and set its accent color // Be careful about the color .setSmallIcon(R.drawable.notification_icon) .setColor(ContextCompat.getColor(context, R.color.primaryDark)) // Add a pause button .addAction(new NotificationCompat.Action( R.drawable.pause, getString(R.string.pause), MediaButtonReceiver.buildMediaButtonPendingIntent(context, PlaybackStateCompat.ACTION_PLAY_PAUSE))) // Take advantage of MediaStyle features .setStyle(new MediaStyle() .setMediaSession(mediaSession.getSessionToken()) .setShowActionsInCompactView(0) // Add a cancel button .setShowCancelButton(true) .setCancelButtonIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(context, PlaybackStateCompat.ACTION_STOP))); // Display the notification and place the service in the foreground startForeground(id, builder.build());
เมื่อใช้การแจ้งเตือน MediaStyle โปรดทราบลักษณะการทำงานของการตั้งค่า NotificationCompat ต่อไปนี้
- เมื่อคุณใช้
setContentIntent()บริการจะเริ่มต้นโดยอัตโนมัติเมื่อมีการคลิกการแจ้งเตือน ซึ่งเป็นฟีเจอร์ที่มีประโยชน์ - ในสถานการณ์ "ไม่น่าเชื่อถือ" เช่น หน้าจอล็อก ค่าเริ่มต้นของการมองเห็นเนื้อหาการแจ้งเตือนคือ
VISIBILITY_PRIVATEคุณอาจต้องการดูการควบคุมการขนส่งบนหน้าจอล็อก ดังนั้นVISIBILITY_PUBLICจึงเป็นตัวเลือกที่เหมาะสม - โปรดระมัดระวังเมื่อตั้งค่าสีพื้นหลัง ในการแจ้งเตือนทั่วไปใน Android เวอร์ชัน 5.0 ขึ้นไป ระบบจะใช้สีกับพื้นหลังของไอคอนแอปขนาดเล็กเท่านั้น แต่สำหรับการแจ้งเตือน MediaStyle ใน Android เวอร์ชันก่อน 7.0 ระบบจะใช้สีกับพื้นหลังของการแจ้งเตือนทั้งหมด โปรดทดสอบสีพื้นหลัง เลือกสีที่สบายตาและหลีกเลี่ยงสีที่สว่างหรือเรืองแสงมากเกินไป
การตั้งค่าเหล่านี้จะใช้ได้เฉพาะเมื่อคุณใช้ NotificationCompat.MediaStyle เท่านั้น
- ใช้
setMediaSession()เพื่อเชื่อมโยงการแจ้งเตือนกับเซสชัน ซึ่งจะช่วยให้แอปของบุคคลที่สามและอุปกรณ์คู่หูเข้าถึงและควบคุมเซสชันได้ - ใช้
setShowActionsInCompactView()เพื่อเพิ่มการดำเนินการได้สูงสุด 3 รายการที่จะแสดงใน contentView ขนาดมาตรฐานของการแจ้งเตือน (ในที่นี้มีการระบุปุ่มหยุดชั่วคราว) - ใน Android 5.0 (ระดับ API 21) ขึ้นไป คุณสามารถปัดการแจ้งเตือนออกเพื่อหยุดเพลเยอร์เมื่อบริการไม่ได้ทำงานในเบื้องหน้าอีกต่อไป แต่จะทำเช่นนี้ในเวอร์ชันก่อนหน้าไม่ได้ หากต้องการอนุญาตให้ผู้ใช้นำการแจ้งเตือนออกและหยุดการเล่นก่อน Android 5.0 (ระดับ API 21) คุณสามารถเพิ่มปุ่มยกเลิกที่มุมขวาบนของการแจ้งเตือนได้โดยเรียก
setShowCancelButton(true)และsetCancelButtonIntent()
เมื่อเพิ่มปุ่มหยุดชั่วคราวและปุ่มยกเลิก คุณจะต้องมี PendingIntent เพื่อแนบกับการดำเนินการเล่น เมธอด MediaButtonReceiver.buildMediaButtonPendingIntent() จะทำหน้าที่แปลงการดำเนินการ PlaybackState เป็น PendingIntent
เปิดใช้การเรียกดูสื่อ AVRCP
นอกเหนือจากแอปที่กำหนดเอง เช่น Android Auto แล้ว เลเยอร์ Bluetooth ของระบบยังทำหน้าที่เป็นไคลเอ็นต์ของ MediaBrowserService เพื่อช่วยในการเรียกดูแคตตาล็อกระยะไกลแบบไร้สาย (AVRCP) ด้วย
ใน Android 16 และ Android 17 แพลตฟอร์มกำหนดให้แอปที่ไม่ได้ใช้ Media3 ต้องแสดงกิจกรรมที่เฉพาะเจาะจงพร้อมตัวกรอง Intent เพื่อให้ระบบตรวจสอบการเรียกดู
เพิ่มตัวกรอง Intent ที่เฉพาะเจาะจงนี้ลงในกิจกรรมที่ส่งออกใน AndroidManifest.xml โปรดทราบว่าเราจงใจละเว้น CATEGORY_DEFAULT เพื่อป้องกันไม่ให้แอปของคุณปรากฏในเมนู "เปิดด้วย" ทั่วไปสำหรับไฟล์เสียงในเครื่อง
<activity
android:name=".BluetoothValidationActivity"
android:exported="true"
android:theme="@android:style/Theme.NoDisplay"
android:excludeFromRecents="true"
android:noHistory="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="content" />
<data android:host="media" />
<!-- Specific path check used by Bluetooth stack for validation -->
<data android:pathPrefix="/internal/audio/media/" />
<data android:mimeType="audio/*" />
</intent-filter>
</activity>