1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.pandora
18 
19 import android.content.Intent
20 import android.media.MediaPlayer
21 import android.os.Bundle
22 import android.support.v4.media.*
23 import android.support.v4.media.MediaBrowserCompat.MediaItem
24 import android.support.v4.media.MediaMetadataCompat
25 import android.support.v4.media.session.*
26 import android.support.v4.media.session.MediaSessionCompat
27 import android.support.v4.media.session.PlaybackStateCompat
28 import android.support.v4.media.session.PlaybackStateCompat.SHUFFLE_MODE_ALL
29 import android.support.v4.media.session.PlaybackStateCompat.SHUFFLE_MODE_GROUP
30 import android.support.v4.media.session.PlaybackStateCompat.SHUFFLE_MODE_NONE
31 import android.util.Log
32 import androidx.media.MediaBrowserServiceCompat
33 import androidx.media.MediaBrowserServiceCompat.BrowserRoot
34 
35 /* MediaBrowserService to handle MediaButton and Browsing */
36 class MediaPlayerBrowserService : MediaBrowserServiceCompat() {
37     private val TAG = "PandoraMediaPlayerBrowserService"
38 
39     private lateinit var mediaSession: MediaSessionCompat
40     private lateinit var playbackStateBuilder: PlaybackStateCompat.Builder
41     private var mMediaPlayer: MediaPlayer? = null
42     private val mediaIdToChildren = mutableMapOf<String, MutableList<MediaItem>>()
43     private var metadataItems = mutableMapOf<String, MediaMetadataCompat>()
44     private var queue = mutableListOf<MediaSessionCompat.QueueItem>()
45     private var currentTrack = -1
46 
onCreatenull47     override fun onCreate() {
48         super.onCreate()
49         initBrowseFolderList()
50         setupMediaSession()
51         instance = this
52     }
53 
setupMediaSessionnull54     private fun setupMediaSession() {
55         mediaSession = MediaSessionCompat(this, "MediaSession")
56         mediaSession.setCallback(mSessionCallback)
57         initQueue()
58         mediaSession.setQueue(queue)
59         playbackStateBuilder =
60             PlaybackStateCompat.Builder()
61                 .setState(PlaybackStateCompat.STATE_NONE, 0, 1.0f)
62                 .setActions(getAvailableActions())
63                 .setActiveQueueItemId(QUEUE_START_INDEX.toLong())
64         mediaSession.setPlaybackState(playbackStateBuilder.build())
65         mediaSession.setMetadata(null)
66         mediaSession.setQueueTitle(NOW_PLAYING_PREFIX)
67         mediaSession.setActive(true)
68         setSessionToken(mediaSession.getSessionToken())
69     }
70 
getAvailableActionsnull71     private fun getAvailableActions(): Long =
72         PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS or
73             PlaybackStateCompat.ACTION_SKIP_TO_NEXT or
74             PlaybackStateCompat.ACTION_FAST_FORWARD or
75             PlaybackStateCompat.ACTION_REWIND or
76             PlaybackStateCompat.ACTION_PLAY or
77             PlaybackStateCompat.ACTION_STOP or
78             PlaybackStateCompat.ACTION_PAUSE or
79             PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE
80 
81     private fun setPlaybackState(state: Int) {
82         playbackStateBuilder.setState(state, 0, 1.0f).setActiveQueueItemId(currentTrack.toLong())
83         mediaSession.setPlaybackState(playbackStateBuilder.build())
84     }
85 
startTestPlaybacknull86     fun startTestPlayback() {
87         if (mMediaPlayer == null) {
88             // File copied from: development/samples/ApiDemos/res/raw/test_cbr.mp3
89             // to: packages/modules/Bluetooth/android/pandora/server/res/raw/test_cbr.mp3
90             val resourceId: Int = getResources().getIdentifier("test_cbr", "raw", getPackageName())
91             mMediaPlayer = MediaPlayer.create(this, resourceId)
92             if (mMediaPlayer == null) {
93                 Log.e(TAG, "Failed to create MediaPlayer.")
94                 return
95             }
96         }
97 
98         mMediaPlayer?.setOnCompletionListener { stopTestPlayback() }
99 
100         mMediaPlayer?.start()
101     }
102 
stopTestPlaybacknull103     fun stopTestPlayback() {
104         mMediaPlayer?.stop()
105         mMediaPlayer?.setOnCompletionListener(null)
106         mMediaPlayer?.release()
107         mMediaPlayer = null
108     }
109 
playnull110     fun play() {
111         if (currentTrack == -1 || currentTrack == QUEUE_SIZE) currentTrack = QUEUE_START_INDEX
112         else currentTrack += 1
113         setPlaybackState(PlaybackStateCompat.STATE_PLAYING)
114         mediaSession.setMetadata(metadataItems.get("" + currentTrack))
115     }
116 
stopnull117     fun stop() {
118         setPlaybackState(PlaybackStateCompat.STATE_STOPPED)
119         mediaSession.setMetadata(null)
120     }
121 
pausenull122     fun pause() {
123         setPlaybackState(PlaybackStateCompat.STATE_PAUSED)
124     }
125 
rewindnull126     fun rewind() {
127         setPlaybackState(PlaybackStateCompat.STATE_REWINDING)
128     }
129 
fastForwardnull130     fun fastForward() {
131         setPlaybackState(PlaybackStateCompat.STATE_FAST_FORWARDING)
132     }
133 
forwardnull134     fun forward() {
135         if (currentTrack == QUEUE_SIZE || currentTrack == -1) currentTrack = QUEUE_START_INDEX
136         else currentTrack += 1
137         setPlaybackState(PlaybackStateCompat.STATE_SKIPPING_TO_NEXT)
138         mediaSession.setMetadata(metadataItems.get("" + currentTrack))
139         setPlaybackState(PlaybackStateCompat.STATE_PLAYING)
140     }
141 
backwardnull142     fun backward() {
143         if (currentTrack == QUEUE_START_INDEX || currentTrack == -1) currentTrack = QUEUE_SIZE
144         else currentTrack -= 1
145         setPlaybackState(PlaybackStateCompat.STATE_SKIPPING_TO_PREVIOUS)
146         mediaSession.setMetadata(metadataItems.get("" + currentTrack))
147         setPlaybackState(PlaybackStateCompat.STATE_PLAYING)
148     }
149 
setLargeMetadatanull150     fun setLargeMetadata() {
151         currentTrack = QUEUE_SIZE
152         mediaSession.setMetadata(metadataItems.get("" + currentTrack))
153         setPlaybackState(PlaybackStateCompat.STATE_PLAYING)
154     }
155 
updateQueuenull156     fun updateQueue() {
157         val metaData: MediaMetadataCompat =
158             MediaMetadataCompat.Builder()
159                 .putString(
160                     MediaMetadataCompat.METADATA_KEY_MEDIA_ID,
161                     NOW_PLAYING_PREFIX + NEW_QUEUE_ITEM_INDEX
162                 )
163                 .putString(MediaMetadataCompat.METADATA_KEY_TITLE, "Title" + NEW_QUEUE_ITEM_INDEX)
164                 .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, "Artist" + NEW_QUEUE_ITEM_INDEX)
165                 .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, "Album" + NEW_QUEUE_ITEM_INDEX)
166                 .putLong(
167                     MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER,
168                     NEW_QUEUE_ITEM_INDEX.toLong()
169                 )
170                 .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, NEW_QUEUE_ITEM_INDEX.toLong())
171                 .build()
172         val mediaItem = MediaItem(metaData.description, MediaItem.FLAG_PLAYABLE)
173         queue.add(
174             MediaSessionCompat.QueueItem(mediaItem.description, NEW_QUEUE_ITEM_INDEX.toLong())
175         )
176         mediaSession.setQueue(queue)
177     }
178 
getShuffleModenull179     fun getShuffleMode(): Int {
180         val controller = mediaSession.getController()
181         return controller.getShuffleMode()
182     }
183 
setShuffleModenull184     fun setShuffleMode(shuffleMode: Int) {
185         val controller = mediaSession.getController()
186         val transportControls = controller.getTransportControls()
187         when (shuffleMode) {
188             SHUFFLE_MODE_NONE,
189             SHUFFLE_MODE_ALL,
190             SHUFFLE_MODE_GROUP -> transportControls.setShuffleMode(shuffleMode)
191             else -> transportControls.setShuffleMode(SHUFFLE_MODE_NONE)
192         }
193     }
194 
195     private val mSessionCallback: MediaSessionCompat.Callback =
196         object : MediaSessionCompat.Callback() {
onPlaynull197             override fun onPlay() {
198                 Log.i(TAG, "onPlay")
199                 play()
200             }
201 
onPausenull202             override fun onPause() {
203                 Log.i(TAG, "onPause")
204                 pause()
205             }
206 
onSkipToPreviousnull207             override fun onSkipToPrevious() {
208                 Log.i(TAG, "onSkipToPrevious")
209                 // TODO : Need to handle to play previous audio in the list
210             }
211 
onSkipToNextnull212             override fun onSkipToNext() {
213                 Log.i(TAG, "onSkipToNext")
214                 // TODO : Need to handle to play next audio in the list
215             }
216 
onMediaButtonEventnull217             override fun onMediaButtonEvent(mediaButtonEvent: Intent): Boolean {
218                 Log.i(TAG, "MediaSessionCallback——》onMediaButtonEvent $mediaButtonEvent")
219                 return super.onMediaButtonEvent(mediaButtonEvent)
220             }
221 
onSetShuffleModenull222             override fun onSetShuffleMode(shuffleMode: Int) {
223                 Log.i(TAG, "MediaSessionCallback——》onSetShuffleMode $shuffleMode")
224                 mediaSession.setShuffleMode(shuffleMode)
225             }
226         }
227 
onGetRootnull228     override fun onGetRoot(p0: String, clientUid: Int, rootHints: Bundle?): BrowserRoot? {
229         Log.i(TAG, "onGetRoot")
230         return BrowserRoot(ROOT, null)
231     }
232 
onLoadChildrennull233     override fun onLoadChildren(parentId: String, result: Result<MutableList<MediaItem>>) {
234         Log.i(TAG, "onLoadChildren")
235         if (parentId == ROOT) {
236             val map = mediaIdToChildren[ROOT]
237             Log.i(TAG, "onloadchildren $map")
238             result.sendResult(map)
239         } else if (parentId == NOW_PLAYING_PREFIX) {
240             result.sendResult(mediaIdToChildren[NOW_PLAYING_PREFIX])
241         } else {
242             Log.i(TAG, "onloadchildren inside else")
243             result.sendResult(null)
244         }
245     }
246 
initMediaItemsnull247     private fun initMediaItems() {
248         var mediaItems = mutableListOf<MediaItem>()
249         for (item in QUEUE_START_INDEX..QUEUE_SIZE) {
250             val metaData: MediaMetadataCompat =
251                 MediaMetadataCompat.Builder()
252                     .putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, NOW_PLAYING_PREFIX + item)
253                     .putString(MediaMetadataCompat.METADATA_KEY_TITLE, "Title$item")
254                     .putString(
255                         MediaMetadataCompat.METADATA_KEY_ARTIST,
256                         if (item != QUEUE_SIZE) "Artist$item" else generateAlphanumericString(512)
257                     )
258                     .putString(
259                         MediaMetadataCompat.METADATA_KEY_ALBUM,
260                         if (item != QUEUE_SIZE) "Album$item" else generateAlphanumericString(512)
261                     )
262                     .putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, item.toLong())
263                     .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, QUEUE_SIZE.toLong())
264                     .build()
265             val mediaItem = MediaItem(metaData.description, MediaItem.FLAG_PLAYABLE)
266             mediaItems.add(mediaItem)
267             metadataItems.put("" + item, metaData)
268         }
269         mediaIdToChildren[NOW_PLAYING_PREFIX] = mediaItems
270     }
271 
initQueuenull272     private fun initQueue() {
273         for ((key, value) in metadataItems.entries) {
274             val mediaItem = MediaItem(value.description, MediaItem.FLAG_PLAYABLE)
275             queue.add(MediaSessionCompat.QueueItem(mediaItem.description, key.toLong()))
276         }
277     }
278 
initBrowseFolderListnull279     private fun initBrowseFolderList() {
280         var rootList = mediaIdToChildren[ROOT] ?: mutableListOf()
281 
282         val emptyFolderMetaData =
283             MediaMetadataCompat.Builder()
284                 .putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, EMPTY_FOLDER)
285                 .putString(MediaMetadataCompat.METADATA_KEY_TITLE, EMPTY_FOLDER)
286                 .putLong(
287                     MediaMetadataCompat.METADATA_KEY_BT_FOLDER_TYPE,
288                     MediaDescriptionCompat.BT_FOLDER_TYPE_PLAYLISTS
289                 )
290                 .build()
291         val emptyFolderMediaItem =
292             MediaItem(emptyFolderMetaData.description, MediaItem.FLAG_BROWSABLE)
293 
294         val playlistMetaData =
295             MediaMetadataCompat.Builder()
296                 .apply {
297                     putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, NOW_PLAYING_PREFIX)
298                     putString(MediaMetadataCompat.METADATA_KEY_TITLE, NOW_PLAYING_PREFIX)
299                     putLong(
300                         MediaMetadataCompat.METADATA_KEY_BT_FOLDER_TYPE,
301                         MediaDescriptionCompat.BT_FOLDER_TYPE_PLAYLISTS
302                     )
303                 }
304                 .build()
305 
306         val playlistsMediaItem = MediaItem(playlistMetaData.description, MediaItem.FLAG_BROWSABLE)
307 
308         rootList += emptyFolderMediaItem
309         rootList += playlistsMediaItem
310         mediaIdToChildren[ROOT] = rootList
311         initMediaItems()
312     }
313 
314     companion object {
315         lateinit var instance: MediaPlayerBrowserService
316         const val ROOT = "__ROOT__"
317         const val EMPTY_FOLDER = "@empty@"
318         const val NOW_PLAYING_PREFIX = "NowPlayingId"
319         const val QUEUE_START_INDEX = 1
320         const val QUEUE_SIZE = 6
321         const val NEW_QUEUE_ITEM_INDEX = 7
322 
isInitializednull323         fun isInitialized(): Boolean = this::instance.isInitialized
324     }
325 }
326