blob: 774f953490b3399e246a80326454cc78fd4459e9 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.bluetooth.avrcpcontroller;
import android.media.MediaMetadata;
import android.media.browse.MediaBrowser.MediaItem;
import android.os.Bundle;
import android.support.v4.media.MediaBrowserCompat;
import android.support.v4.media.MediaDescriptionCompat;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.util.Log;
import androidx.media.MediaBrowserServiceCompat;
import com.android.bluetooth.R;
import java.util.ArrayList;
import java.util.List;
/**
* Implements the MediaBrowserService interface to AVRCP and A2DP
*
* This service provides a means for external applications to access A2DP and AVRCP.
* The applications are expected to use MediaBrowser (see API) and all the music
* browsing/playback/metadata can be controlled via MediaBrowser and MediaController.
*
* The current behavior of MediaSessionCompat exposed by this service is as follows:
* 1. MediaSessionCompat is active (i.e. SystemUI and other overview UIs can see updates) when
* device is connected and first starts playing. Before it starts playing we do not activate the
* session.
* 1.1 The session is active throughout the duration of connection.
* 2. The session is de-activated when the device disconnects. It will be connected again when (1)
* happens.
*/
public class BluetoothMediaBrowserService extends MediaBrowserServiceCompat {
private static final String TAG = "BluetoothMediaBrowserService";
private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
private static BluetoothMediaBrowserService sBluetoothMediaBrowserService;
private MediaSessionCompat mSession;
// Browsing related structures.
private List<MediaSessionCompat.QueueItem> mMediaQueue = new ArrayList<>();
/**
* Initialize this BluetoothMediaBrowserService, creating our MediaSessionCompat, MediaPlayer
* and MediaMetaData, and setting up mechanisms to talk with the AvrcpControllerService.
*/
@Override
public void onCreate() {
if (DBG) Log.d(TAG, "onCreate");
super.onCreate();
// Create and configure the MediaSessionCompat
mSession = new MediaSessionCompat(this, TAG);
setSessionToken(mSession.getSessionToken());
mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS
| MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
mSession.setQueueTitle(getString(R.string.bluetooth_a2dp_sink_queue_name));
mSession.setQueue(mMediaQueue);
PlaybackStateCompat.Builder playbackStateBuilder = new PlaybackStateCompat.Builder();
playbackStateBuilder.setState(PlaybackStateCompat.STATE_ERROR,
PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 1.0f).setActions(0);
playbackStateBuilder.setErrorMessage(getString(R.string.bluetooth_disconnected));
mSession.setPlaybackState(playbackStateBuilder.build());
sBluetoothMediaBrowserService = this;
}
List<MediaItem> getContents(final String parentMediaId) {
AvrcpControllerService avrcpControllerService =
AvrcpControllerService.getAvrcpControllerService();
if (avrcpControllerService == null) {
return new ArrayList(0);
} else {
return avrcpControllerService.getContents(parentMediaId);
}
}
@Override
public synchronized void onLoadChildren(final String parentMediaId,
final Result<List<MediaBrowserCompat.MediaItem>> result) {
if (DBG) Log.d(TAG, "onLoadChildren parentMediaId=" + parentMediaId);
List<MediaBrowserCompat.MediaItem> contents =
MediaBrowserCompat.MediaItem.fromMediaItemList(getContents(parentMediaId));
if (contents == null) {
result.detach();
} else {
result.sendResult(contents);
}
}
@Override
public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
if (DBG) Log.d(TAG, "onGetRoot");
return new BrowserRoot(BrowseTree.ROOT, null);
}
private void updateNowPlayingQueue(BrowseTree.BrowseNode node) {
List<MediaItem> songList = node.getContents();
mMediaQueue.clear();
if (songList != null) {
for (MediaItem song : songList) {
mMediaQueue.add(new MediaSessionCompat.QueueItem(
MediaDescriptionCompat.fromMediaDescription(song.getDescription()),
mMediaQueue.size()));
}
}
mSession.setQueue(mMediaQueue);
}
static synchronized void notifyChanged(BrowseTree.BrowseNode node) {
if (sBluetoothMediaBrowserService != null) {
if (node.getScope() == AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING) {
sBluetoothMediaBrowserService.updateNowPlayingQueue(node);
} else {
sBluetoothMediaBrowserService.notifyChildrenChanged(node.getID());
}
}
}
static synchronized void addressedPlayerChanged(MediaSessionCompat.Callback callback) {
if (sBluetoothMediaBrowserService != null) {
sBluetoothMediaBrowserService.mSession.setCallback(callback);
} else {
Log.w(TAG, "addressedPlayerChanged Unavailable");
}
}
static synchronized void trackChanged(MediaMetadata mediaMetadata) {
if (sBluetoothMediaBrowserService != null) {
sBluetoothMediaBrowserService.mSession.setMetadata(
MediaMetadataCompat.fromMediaMetadata(mediaMetadata));
} else {
Log.w(TAG, "trackChanged Unavailable");
}
}
static synchronized void notifyChanged(PlaybackStateCompat playbackState) {
Log.d(TAG, "notifyChanged PlaybackState" + playbackState);
if (sBluetoothMediaBrowserService != null) {
sBluetoothMediaBrowserService.mSession.setPlaybackState(playbackState);
} else {
Log.w(TAG, "notifyChanged Unavailable");
}
}
/**
* Send AVRCP Play command
*/
public static synchronized void play() {
if (sBluetoothMediaBrowserService != null) {
sBluetoothMediaBrowserService.mSession.getController().getTransportControls().play();
} else {
Log.w(TAG, "play Unavailable");
}
}
/**
* Send AVRCP Pause command
*/
public static synchronized void pause() {
if (sBluetoothMediaBrowserService != null) {
sBluetoothMediaBrowserService.mSession.getController().getTransportControls().pause();
} else {
Log.w(TAG, "pause Unavailable");
}
}
/**
* Get object for controlling playback
*/
public static synchronized MediaControllerCompat.TransportControls getTransportControls() {
if (sBluetoothMediaBrowserService != null) {
return sBluetoothMediaBrowserService.mSession.getController().getTransportControls();
} else {
Log.w(TAG, "transportControls Unavailable");
return null;
}
}
/**
* Set Media session active whenever we have Focus of any kind
*/
public static synchronized void setActive(boolean active) {
if (sBluetoothMediaBrowserService != null) {
sBluetoothMediaBrowserService.mSession.setActive(active);
} else {
Log.w(TAG, "setActive Unavailable");
}
}
/**
* Get Media session for updating state
*/
public static synchronized MediaSessionCompat getSession() {
if (sBluetoothMediaBrowserService != null) {
return sBluetoothMediaBrowserService.mSession;
} else {
Log.w(TAG, "getSession Unavailable");
return null;
}
}
}