1 /* 2 * Copyright (C) 2015 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.bluetooth.avrcpcontroller; 18 19 import android.media.MediaMetadata; 20 import android.media.browse.MediaBrowser.MediaItem; 21 import android.media.session.MediaController; 22 import android.media.session.MediaSession; 23 import android.media.session.PlaybackState; 24 import android.os.Bundle; 25 import android.service.media.MediaBrowserService; 26 import android.util.Log; 27 28 import com.android.bluetooth.R; 29 30 import java.util.ArrayList; 31 import java.util.List; 32 33 /** 34 * Implements the MediaBrowserService interface to AVRCP and A2DP 35 * 36 * This service provides a means for external applications to access A2DP and AVRCP. 37 * The applications are expected to use MediaBrowser (see API) and all the music 38 * browsing/playback/metadata can be controlled via MediaBrowser and MediaController. 39 * 40 * The current behavior of MediaSession exposed by this service is as follows: 41 * 1. MediaSession is active (i.e. SystemUI and other overview UIs can see updates) when device is 42 * connected and first starts playing. Before it starts playing we do not active the session. 43 * 1.1 The session is active throughout the duration of connection. 44 * 2. The session is de-activated when the device disconnects. It will be connected again when (1) 45 * happens. 46 */ 47 public class BluetoothMediaBrowserService extends MediaBrowserService { 48 private static final String TAG = "BluetoothMediaBrowserService"; 49 private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); 50 51 private static BluetoothMediaBrowserService sBluetoothMediaBrowserService; 52 53 private MediaSession mSession; 54 55 // Browsing related structures. 56 private List<MediaSession.QueueItem> mMediaQueue = new ArrayList<>(); 57 58 /** 59 * Initialize this BluetoothMediaBrowserService, creating our MediaSession, MediaPlayer and 60 * MediaMetaData, and setting up mechanisms to talk with the AvrcpControllerService. 61 */ 62 @Override onCreate()63 public void onCreate() { 64 if (DBG) Log.d(TAG, "onCreate"); 65 super.onCreate(); 66 67 // Create and configure the MediaSession 68 mSession = new MediaSession(this, TAG); 69 setSessionToken(mSession.getSessionToken()); 70 mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS 71 | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); 72 mSession.setQueueTitle(getString(R.string.bluetooth_a2dp_sink_queue_name)); 73 mSession.setQueue(mMediaQueue); 74 PlaybackState.Builder playbackStateBuilder = new PlaybackState.Builder(); 75 playbackStateBuilder.setState(PlaybackState.STATE_ERROR, 76 PlaybackState.PLAYBACK_POSITION_UNKNOWN, 1.0f).setActions(0); 77 playbackStateBuilder.setErrorMessage(getString(R.string.bluetooth_disconnected)); 78 mSession.setPlaybackState(playbackStateBuilder.build()); 79 sBluetoothMediaBrowserService = this; 80 } 81 getContents(final String parentMediaId)82 List<MediaItem> getContents(final String parentMediaId) { 83 AvrcpControllerService avrcpControllerService = 84 AvrcpControllerService.getAvrcpControllerService(); 85 if (avrcpControllerService == null) { 86 return new ArrayList(0); 87 } else { 88 return avrcpControllerService.getContents(parentMediaId); 89 } 90 } 91 92 @Override onLoadChildren(final String parentMediaId, final Result<List<MediaItem>> result)93 public synchronized void onLoadChildren(final String parentMediaId, 94 final Result<List<MediaItem>> result) { 95 if (DBG) Log.d(TAG, "onLoadChildren parentMediaId=" + parentMediaId); 96 List<MediaItem> contents = getContents(parentMediaId); 97 if (contents == null) { 98 result.detach(); 99 } else { 100 result.sendResult(contents); 101 } 102 } 103 104 @Override onGetRoot(String clientPackageName, int clientUid, Bundle rootHints)105 public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) { 106 if (DBG) Log.d(TAG, "onGetRoot"); 107 return new BrowserRoot(BrowseTree.ROOT, null); 108 } 109 updateNowPlayingQueue(BrowseTree.BrowseNode node)110 private void updateNowPlayingQueue(BrowseTree.BrowseNode node) { 111 List<MediaItem> songList = node.getContents(); 112 mMediaQueue.clear(); 113 if (songList != null) { 114 for (MediaItem song : songList) { 115 mMediaQueue.add(new MediaSession.QueueItem(song.getDescription(), 116 mMediaQueue.size())); 117 } 118 } 119 mSession.setQueue(mMediaQueue); 120 } 121 notifyChanged(BrowseTree.BrowseNode node)122 static synchronized void notifyChanged(BrowseTree.BrowseNode node) { 123 if (sBluetoothMediaBrowserService != null) { 124 if (node.getScope() == AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING) { 125 sBluetoothMediaBrowserService.updateNowPlayingQueue(node); 126 } else { 127 sBluetoothMediaBrowserService.notifyChildrenChanged(node.getID()); 128 } 129 } 130 } 131 addressedPlayerChanged(MediaSession.Callback callback)132 static synchronized void addressedPlayerChanged(MediaSession.Callback callback) { 133 if (sBluetoothMediaBrowserService != null) { 134 sBluetoothMediaBrowserService.mSession.setCallback(callback); 135 } else { 136 Log.w(TAG, "addressedPlayerChanged Unavailable"); 137 } 138 } 139 trackChanged(MediaMetadata mediaMetadata)140 static synchronized void trackChanged(MediaMetadata mediaMetadata) { 141 if (sBluetoothMediaBrowserService != null) { 142 sBluetoothMediaBrowserService.mSession.setMetadata(mediaMetadata); 143 } else { 144 Log.w(TAG, "trackChanged Unavailable"); 145 } 146 } 147 notifyChanged(PlaybackState playbackState)148 static synchronized void notifyChanged(PlaybackState playbackState) { 149 Log.d(TAG, "notifyChanged PlaybackState" + playbackState); 150 if (sBluetoothMediaBrowserService != null) { 151 sBluetoothMediaBrowserService.mSession.setPlaybackState(playbackState); 152 } else { 153 Log.w(TAG, "notifyChanged Unavailable"); 154 } 155 } 156 157 /** 158 * Send AVRCP Play command 159 */ play()160 public static synchronized void play() { 161 if (sBluetoothMediaBrowserService != null) { 162 sBluetoothMediaBrowserService.mSession.getController().getTransportControls().play(); 163 } else { 164 Log.w(TAG, "play Unavailable"); 165 } 166 } 167 168 /** 169 * Send AVRCP Pause command 170 */ pause()171 public static synchronized void pause() { 172 if (sBluetoothMediaBrowserService != null) { 173 sBluetoothMediaBrowserService.mSession.getController().getTransportControls().pause(); 174 } else { 175 Log.w(TAG, "pause Unavailable"); 176 } 177 } 178 179 /** 180 * Get object for controlling playback 181 */ getTransportControls()182 public static synchronized MediaController.TransportControls getTransportControls() { 183 if (sBluetoothMediaBrowserService != null) { 184 return sBluetoothMediaBrowserService.mSession.getController().getTransportControls(); 185 } else { 186 Log.w(TAG, "transportControls Unavailable"); 187 return null; 188 } 189 } 190 191 /** 192 * Set Media session active whenever we have Focus of any kind 193 */ setActive(boolean active)194 public static synchronized void setActive(boolean active) { 195 if (sBluetoothMediaBrowserService != null) { 196 sBluetoothMediaBrowserService.mSession.setActive(active); 197 } else { 198 Log.w(TAG, "setActive Unavailable"); 199 } 200 } 201 } 202