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