Home | History | Annotate | Download | only in avrcpcontroller
      1 /*
      2  * Copyright (C) 2016 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.browse.MediaBrowser;
     20 import android.media.browse.MediaBrowser.MediaItem;
     21 import android.media.MediaDescription;
     22 import android.os.Bundle;
     23 import android.os.ResultReceiver;
     24 import android.service.media.MediaBrowserService.Result;
     25 import android.util.Log;
     26 
     27 import java.util.ArrayList;
     28 import java.util.HashMap;
     29 import java.util.List;
     30 import java.util.Stack;
     31 
     32 // Browsing hierarchy.
     33 // Root:
     34 //      Player1:
     35 //        Now_Playing:
     36 //          MediaItem1
     37 //          MediaItem2
     38 //        Folder1
     39 //        Folder2
     40 //        ....
     41 //      Player2
     42 //      ....
     43 public class BrowseTree {
     44     private static final String TAG = "BrowseTree";
     45     private static final boolean DBG = true;
     46 
     47     public static final int DIRECTION_DOWN = 0;
     48     public static final int DIRECTION_UP = 1;
     49     public static final int DIRECTION_SAME = 2;
     50     public static final int DIRECTION_UNKNOWN = -1;
     51 
     52     public static final String ROOT = "__ROOT__";
     53     public static final String NOW_PLAYING_PREFIX = "NOW_PLAYING";
     54     public static final String PLAYER_PREFIX = "PLAYER";
     55 
     56     // Static instance of Folder ID <-> Folder Instance (for navigation purposes)
     57     private final HashMap<String, BrowseNode> mBrowseMap = new HashMap<String, BrowseNode>();
     58     private BrowseNode mCurrentBrowseNode;
     59     private BrowseNode mCurrentBrowsedPlayer;
     60     private BrowseNode mCurrentAddressedPlayer;
     61 
     62     BrowseTree() {
     63     }
     64 
     65     public void init() {
     66         MediaDescription.Builder mdb = new MediaDescription.Builder();
     67         mdb.setMediaId(ROOT);
     68         mdb.setTitle(ROOT);
     69         Bundle mdBundle = new Bundle();
     70         mdBundle.putString(AvrcpControllerService.MEDIA_ITEM_UID_KEY, ROOT);
     71         mdb.setExtras(mdBundle);
     72         mBrowseMap.put(ROOT, new BrowseNode(new MediaItem(mdb.build(), MediaItem.FLAG_BROWSABLE)));
     73         mCurrentBrowseNode = mBrowseMap.get(ROOT);
     74     }
     75 
     76     public void clear() {
     77         // Clearing the map should garbage collect everything.
     78         mBrowseMap.clear();
     79     }
     80 
     81     // Each node of the tree is represented by Folder ID, Folder Name and the children.
     82     class BrowseNode {
     83         // MediaItem to store the media related details.
     84         MediaItem mItem;
     85 
     86         // Type of this browse node.
     87         // Since Media APIs do not define the player separately we define that
     88         // distinction here.
     89         boolean mIsPlayer = false;
     90 
     91         // If this folder is currently cached, can be useful to return the contents
     92         // without doing another fetch.
     93         boolean mCached = false;
     94 
     95         // Result object if this node is not loaded yet. This result object will be used
     96         // once loading is finished.
     97         Result<List<MediaItem>> mResult = null;
     98 
     99         // List of children.
    100         final List<BrowseNode> mChildren = new ArrayList<BrowseNode>();
    101 
    102         BrowseNode(MediaItem item) {
    103             mItem = item;
    104         }
    105 
    106         BrowseNode(AvrcpPlayer player) {
    107             mIsPlayer = true;
    108 
    109             // Transform the player into a item.
    110             MediaDescription.Builder mdb = new MediaDescription.Builder();
    111             Bundle mdExtra = new Bundle();
    112             String playerKey = PLAYER_PREFIX + player.getId();
    113             mdExtra.putString(AvrcpControllerService.MEDIA_ITEM_UID_KEY, playerKey);
    114             mdb.setExtras(mdExtra);
    115             mdb.setMediaId(playerKey);
    116             mdb.setTitle(player.getName());
    117             mItem = new MediaBrowser.MediaItem(mdb.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE);
    118         }
    119 
    120         synchronized List<BrowseNode> getChildren() {
    121             return mChildren;
    122         }
    123 
    124         synchronized boolean isChild(BrowseNode node) {
    125             for (BrowseNode bn : mChildren) {
    126                 if (bn.equals(node)) {
    127                     return true;
    128                 }
    129             }
    130             return false;
    131         }
    132 
    133         synchronized boolean isCached() {
    134             return mCached;
    135         }
    136 
    137         synchronized void setCached(boolean cached) {
    138             mCached = cached;
    139         }
    140 
    141         // Fetch the Unique UID for this item, this is unique across all elements in the tree.
    142         synchronized String getID() {
    143             return mItem.getDescription().getMediaId();
    144         }
    145 
    146         // Get the BT Player ID associated with this node.
    147         synchronized int getPlayerID() {
    148             return Integer.parseInt(getID().replace(PLAYER_PREFIX, ""));
    149         }
    150 
    151         // Fetch the Folder UID that can be used to fetch folder listing via bluetooth.
    152         // This may not be unique hence this combined with direction will define the
    153         // browsing here.
    154         synchronized String getFolderUID() {
    155             return mItem.getDescription().getExtras().getString(
    156                 AvrcpControllerService.MEDIA_ITEM_UID_KEY);
    157         }
    158 
    159         synchronized MediaItem getMediaItem() {
    160             return mItem;
    161         }
    162 
    163         synchronized boolean isPlayer() {
    164             return mIsPlayer;
    165         }
    166 
    167         synchronized boolean isNowPlaying() {
    168             return getID().startsWith(NOW_PLAYING_PREFIX);
    169         }
    170 
    171         @Override
    172         public boolean equals(Object other) {
    173             if (!(other instanceof BrowseNode)) {
    174                 return false;
    175             }
    176             BrowseNode otherNode = (BrowseNode) other;
    177             return getID().equals(otherNode.getID());
    178         }
    179 
    180         @Override
    181         public String toString() {
    182             return "ID: " + getID() + " desc: " + mItem;
    183         }
    184     }
    185 
    186     synchronized <E> void refreshChildren(String parentID, List<E> children) {
    187         BrowseNode parent = findFolderByIDLocked(parentID);
    188         if (parent == null) {
    189             Log.w(TAG, "parent not found for parentID " + parentID);
    190             return;
    191         }
    192         refreshChildren(parent, children);
    193     }
    194 
    195     synchronized <E> void refreshChildren(BrowseNode parent, List<E> children) {
    196         if (children == null) {
    197             Log.e(TAG, "children cannot be null ");
    198             return;
    199         }
    200 
    201         List<BrowseNode> bnList = new ArrayList<BrowseNode>();
    202         for (E child : children) {
    203             if (child instanceof MediaItem) {
    204                 bnList.add(new BrowseNode((MediaItem) child));
    205             } else if (child instanceof AvrcpPlayer) {
    206                 bnList.add(new BrowseNode((AvrcpPlayer) child));
    207             }
    208         }
    209 
    210         String parentID = parent.getID();
    211         // Make sure that the child list is clean.
    212         if (DBG) {
    213             Log.d(TAG, "parent " + parentID + " child list " + parent.getChildren());
    214         }
    215 
    216         addChildrenLocked(parent, bnList);
    217         List<MediaItem> childrenList = new ArrayList<MediaItem>();
    218         for (BrowseNode bn : parent.getChildren()) {
    219             childrenList.add(bn.getMediaItem());
    220         }
    221 
    222         parent.setCached(true);
    223     }
    224 
    225     synchronized BrowseNode findBrowseNodeByID(String parentID) {
    226         BrowseNode bn = mBrowseMap.get(parentID);
    227         if (bn == null) {
    228             Log.e(TAG, "folder " + parentID + " not found!");
    229             return null;
    230         }
    231         if (DBG) {
    232             Log.d(TAG, "Browse map: " + mBrowseMap);
    233         }
    234         return bn;
    235     }
    236 
    237     BrowseNode findFolderByIDLocked(String parentID) {
    238         return mBrowseMap.get(parentID);
    239     }
    240 
    241     void addChildrenLocked(BrowseNode parent, List<BrowseNode> items) {
    242         // Remove existing children and then add the new children.
    243         for (BrowseNode c : parent.getChildren()) {
    244             mBrowseMap.remove(c.getID());
    245         }
    246         parent.getChildren().clear();
    247 
    248         for (BrowseNode bn : items) {
    249             parent.getChildren().add(bn);
    250             mBrowseMap.put(bn.getID(), bn);
    251         }
    252     }
    253 
    254     synchronized int getDirection(String toUID) {
    255         BrowseNode fromFolder = mCurrentBrowseNode;
    256         BrowseNode toFolder = findFolderByIDLocked(toUID);
    257         if (fromFolder == null || toFolder == null) {
    258             Log.e(TAG, "from folder " + mCurrentBrowseNode + " or to folder " + toUID + " null!");
    259         }
    260 
    261         // Check the relationship.
    262         if (fromFolder.isChild(toFolder)) {
    263             return DIRECTION_DOWN;
    264         } else if (toFolder.isChild(fromFolder)) {
    265             return DIRECTION_UP;
    266         } else if (fromFolder.equals(toFolder)) {
    267             return DIRECTION_SAME;
    268         } else {
    269             Log.w(TAG, "from folder " + mCurrentBrowseNode + " children " +
    270                 fromFolder.getChildren() + "to folder " + toUID + " children " +
    271                 toFolder.getChildren());
    272             return DIRECTION_UNKNOWN;
    273         }
    274     }
    275 
    276     synchronized boolean setCurrentBrowsedFolder(String uid) {
    277         BrowseNode bn = findFolderByIDLocked(uid);
    278         if (bn == null) {
    279             Log.e(TAG, "Setting an unknown browsed folder, ignoring bn " + uid);
    280             return false;
    281         }
    282 
    283         // Set the previous folder as not cached so that we fetch the contents again.
    284         if (!bn.equals(mCurrentBrowseNode)) {
    285             Log.d(TAG, "Set cache false " + bn + " curr " + mCurrentBrowseNode);
    286             mCurrentBrowseNode.setCached(false);
    287         }
    288 
    289         mCurrentBrowseNode = bn;
    290         return true;
    291     }
    292 
    293     synchronized BrowseNode getCurrentBrowsedFolder() {
    294         return mCurrentBrowseNode;
    295     }
    296 
    297     synchronized boolean setCurrentBrowsedPlayer(String uid) {
    298         BrowseNode bn = findFolderByIDLocked(uid);
    299         if (bn == null) {
    300             Log.e(TAG, "Setting an unknown browsed player, ignoring bn " + uid);
    301             return false;
    302         }
    303         mCurrentBrowsedPlayer = bn;
    304         return true;
    305     }
    306 
    307     synchronized BrowseNode getCurrentBrowsedPlayer() {
    308         return mCurrentBrowsedPlayer;
    309     }
    310 
    311     synchronized boolean setCurrentAddressedPlayer(String uid) {
    312         BrowseNode bn = findFolderByIDLocked(uid);
    313         if (bn == null) {
    314             Log.e(TAG, "Setting an unknown addressed player, ignoring bn " + uid);
    315             return false;
    316         }
    317         mCurrentAddressedPlayer = bn;
    318         return true;
    319     }
    320 
    321     synchronized BrowseNode getCurrentAddressedPlayer() {
    322         return mCurrentAddressedPlayer;
    323     }
    324 
    325     @Override
    326     public String toString() {
    327         return mBrowseMap.toString();
    328     }
    329 }
    330