blob: 4c26dffe8fb93d5106c50f56bafcc36c42e04978 [file] [log] [blame]
/*
* Copyright (C) 2016 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.browse.MediaBrowser;
import android.media.browse.MediaBrowser.MediaItem;
import android.media.MediaDescription;
import android.os.Bundle;
import android.os.ResultReceiver;
import android.service.media.MediaBrowserService.Result;
import android.util.Log;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Stack;
// Browsing hierarchy.
// Root:
// Player1:
// Now_Playing:
// MediaItem1
// MediaItem2
// Folder1
// Folder2
// ....
// Player2
// ....
public class BrowseTree {
private static final String TAG = "BrowseTree";
private static final boolean DBG = true;
public static final int DIRECTION_DOWN = 0;
public static final int DIRECTION_UP = 1;
public static final int DIRECTION_SAME = 2;
public static final int DIRECTION_UNKNOWN = -1;
public static final String ROOT = "__ROOT__";
public static final String NOW_PLAYING_PREFIX = "NOW_PLAYING";
public static final String PLAYER_PREFIX = "PLAYER";
// Static instance of Folder ID <-> Folder Instance (for navigation purposes)
private final HashMap<String, BrowseNode> mBrowseMap = new HashMap<String, BrowseNode>();
private BrowseNode mCurrentBrowseNode;
private BrowseNode mCurrentBrowsedPlayer;
private BrowseNode mCurrentAddressedPlayer;
BrowseTree() {
}
public void init() {
MediaDescription.Builder mdb = new MediaDescription.Builder();
mdb.setMediaId(ROOT);
mdb.setTitle(ROOT);
Bundle mdBundle = new Bundle();
mdBundle.putString(AvrcpControllerService.MEDIA_ITEM_UID_KEY, ROOT);
mdb.setExtras(mdBundle);
mBrowseMap.put(ROOT, new BrowseNode(new MediaItem(mdb.build(), MediaItem.FLAG_BROWSABLE)));
mCurrentBrowseNode = mBrowseMap.get(ROOT);
}
public void clear() {
// Clearing the map should garbage collect everything.
mBrowseMap.clear();
}
// Each node of the tree is represented by Folder ID, Folder Name and the children.
class BrowseNode {
// MediaItem to store the media related details.
MediaItem mItem;
// Type of this browse node.
// Since Media APIs do not define the player separately we define that
// distinction here.
boolean mIsPlayer = false;
// If this folder is currently cached, can be useful to return the contents
// without doing another fetch.
boolean mCached = false;
// Result object if this node is not loaded yet. This result object will be used
// once loading is finished.
Result<List<MediaItem>> mResult = null;
// List of children.
final List<BrowseNode> mChildren = new ArrayList<BrowseNode>();
BrowseNode(MediaItem item) {
mItem = item;
}
BrowseNode(AvrcpPlayer player) {
mIsPlayer = true;
// Transform the player into a item.
MediaDescription.Builder mdb = new MediaDescription.Builder();
Bundle mdExtra = new Bundle();
String playerKey = PLAYER_PREFIX + player.getId();
mdExtra.putString(AvrcpControllerService.MEDIA_ITEM_UID_KEY, playerKey);
mdb.setExtras(mdExtra);
mdb.setMediaId(playerKey);
mdb.setTitle(player.getName());
mItem = new MediaBrowser.MediaItem(mdb.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE);
}
synchronized List<BrowseNode> getChildren() {
return mChildren;
}
synchronized boolean isChild(BrowseNode node) {
for (BrowseNode bn : mChildren) {
if (bn.equals(node)) {
return true;
}
}
return false;
}
synchronized boolean isCached() {
return mCached;
}
synchronized void setCached(boolean cached) {
mCached = cached;
}
// Fetch the Unique UID for this item, this is unique across all elements in the tree.
synchronized String getID() {
return mItem.getDescription().getMediaId();
}
// Get the BT Player ID associated with this node.
synchronized int getPlayerID() {
return Integer.parseInt(getID().replace(PLAYER_PREFIX, ""));
}
// Fetch the Folder UID that can be used to fetch folder listing via bluetooth.
// This may not be unique hence this combined with direction will define the
// browsing here.
synchronized String getFolderUID() {
return mItem.getDescription().getExtras().getString(
AvrcpControllerService.MEDIA_ITEM_UID_KEY);
}
synchronized MediaItem getMediaItem() {
return mItem;
}
synchronized boolean isPlayer() {
return mIsPlayer;
}
synchronized boolean isNowPlaying() {
return getID().startsWith(NOW_PLAYING_PREFIX);
}
@Override
public boolean equals(Object other) {
if (!(other instanceof BrowseNode)) {
return false;
}
BrowseNode otherNode = (BrowseNode) other;
return getID().equals(otherNode.getID());
}
@Override
public String toString() {
return "ID: " + getID() + " desc: " + mItem;
}
}
synchronized <E> void refreshChildren(String parentID, List<E> children) {
BrowseNode parent = findFolderByIDLocked(parentID);
if (parent == null) {
Log.w(TAG, "parent not found for parentID " + parentID);
return;
}
refreshChildren(parent, children);
}
synchronized <E> void refreshChildren(BrowseNode parent, List<E> children) {
if (children == null) {
Log.e(TAG, "children cannot be null ");
return;
}
List<BrowseNode> bnList = new ArrayList<BrowseNode>();
for (E child : children) {
if (child instanceof MediaItem) {
bnList.add(new BrowseNode((MediaItem) child));
} else if (child instanceof AvrcpPlayer) {
bnList.add(new BrowseNode((AvrcpPlayer) child));
}
}
String parentID = parent.getID();
// Make sure that the child list is clean.
if (DBG) {
Log.d(TAG, "parent " + parentID + " child list " + parent.getChildren());
}
addChildrenLocked(parent, bnList);
List<MediaItem> childrenList = new ArrayList<MediaItem>();
for (BrowseNode bn : parent.getChildren()) {
childrenList.add(bn.getMediaItem());
}
parent.setCached(true);
}
synchronized BrowseNode findBrowseNodeByID(String parentID) {
BrowseNode bn = mBrowseMap.get(parentID);
if (bn == null) {
Log.e(TAG, "folder " + parentID + " not found!");
return null;
}
if (DBG) {
Log.d(TAG, "Browse map: " + mBrowseMap);
}
return bn;
}
BrowseNode findFolderByIDLocked(String parentID) {
return mBrowseMap.get(parentID);
}
void addChildrenLocked(BrowseNode parent, List<BrowseNode> items) {
// Remove existing children and then add the new children.
for (BrowseNode c : parent.getChildren()) {
mBrowseMap.remove(c.getID());
}
parent.getChildren().clear();
for (BrowseNode bn : items) {
parent.getChildren().add(bn);
mBrowseMap.put(bn.getID(), bn);
}
}
synchronized int getDirection(String toUID) {
BrowseNode fromFolder = mCurrentBrowseNode;
BrowseNode toFolder = findFolderByIDLocked(toUID);
if (fromFolder == null || toFolder == null) {
Log.e(TAG, "from folder " + mCurrentBrowseNode + " or to folder " + toUID + " null!");
}
// Check the relationship.
if (fromFolder.isChild(toFolder)) {
return DIRECTION_DOWN;
} else if (toFolder.isChild(fromFolder)) {
return DIRECTION_UP;
} else if (fromFolder.equals(toFolder)) {
return DIRECTION_SAME;
} else {
Log.w(TAG, "from folder " + mCurrentBrowseNode + " children " +
fromFolder.getChildren() + "to folder " + toUID + " children " +
toFolder.getChildren());
return DIRECTION_UNKNOWN;
}
}
synchronized boolean setCurrentBrowsedFolder(String uid) {
BrowseNode bn = findFolderByIDLocked(uid);
if (bn == null) {
Log.e(TAG, "Setting an unknown browsed folder, ignoring bn " + uid);
return false;
}
// Set the previous folder as not cached so that we fetch the contents again.
if (!bn.equals(mCurrentBrowseNode)) {
Log.d(TAG, "Set cache false " + bn + " curr " + mCurrentBrowseNode);
mCurrentBrowseNode.setCached(false);
}
mCurrentBrowseNode = bn;
return true;
}
synchronized BrowseNode getCurrentBrowsedFolder() {
return mCurrentBrowseNode;
}
synchronized boolean setCurrentBrowsedPlayer(String uid) {
BrowseNode bn = findFolderByIDLocked(uid);
if (bn == null) {
Log.e(TAG, "Setting an unknown browsed player, ignoring bn " + uid);
return false;
}
mCurrentBrowsedPlayer = bn;
return true;
}
synchronized BrowseNode getCurrentBrowsedPlayer() {
return mCurrentBrowsedPlayer;
}
synchronized boolean setCurrentAddressedPlayer(String uid) {
BrowseNode bn = findFolderByIDLocked(uid);
if (bn == null) {
Log.e(TAG, "Setting an unknown addressed player, ignoring bn " + uid);
return false;
}
mCurrentAddressedPlayer = bn;
return true;
}
synchronized BrowseNode getCurrentAddressedPlayer() {
return mCurrentAddressedPlayer;
}
@Override
public String toString() {
return mBrowseMap.toString();
}
}