• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
BrowseTree()61     BrowseTree() {
62     }
63 
init()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 
clear()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 
BrowseNode(MediaItem item)101         BrowseNode(MediaItem item) {
102             mItem = item;
103         }
104 
BrowseNode(AvrcpPlayer player)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 
getChildren()119         synchronized List<BrowseNode> getChildren() {
120             return mChildren;
121         }
122 
isChild(BrowseNode node)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 
isCached()132         synchronized boolean isCached() {
133             return mCached;
134         }
135 
setCached(boolean cached)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.
getID()141         synchronized String getID() {
142             return mItem.getDescription().getMediaId();
143         }
144 
145         // Get the BT Player ID associated with this node.
getPlayerID()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.
getFolderUID()153         synchronized String getFolderUID() {
154             return mItem.getDescription()
155                     .getExtras()
156                     .getString(AvrcpControllerService.MEDIA_ITEM_UID_KEY);
157         }
158 
getMediaItem()159         synchronized MediaItem getMediaItem() {
160             return mItem;
161         }
162 
isPlayer()163         synchronized boolean isPlayer() {
164             return mIsPlayer;
165         }
166 
isNowPlaying()167         synchronized boolean isNowPlaying() {
168             return getID().startsWith(NOW_PLAYING_PREFIX);
169         }
170 
171         @Override
equals(Object other)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
toString()181         public String toString() {
182             if (VDBG) {
183                 return "ID: " + getID() + " desc: " + mItem;
184             } else {
185                 return "ID: " + getID();
186             }
187         }
188     }
189 
refreshChildren(String parentID, List<E> children)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 
refreshChildren(BrowseNode parent, List<E> children)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 
findBrowseNodeByID(String parentID)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 
findFolderByIDLocked(String parentID)241     BrowseNode findFolderByIDLocked(String parentID) {
242         return mBrowseMap.get(parentID);
243     }
244 
addChildrenLocked(BrowseNode parent, List<BrowseNode> items)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 
getDirection(String toUID)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 
setCurrentBrowsedFolder(String uid)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 
getCurrentBrowsedFolder()295     synchronized BrowseNode getCurrentBrowsedFolder() {
296         return mCurrentBrowseNode;
297     }
298 
setCurrentBrowsedPlayer(String uid)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 
getCurrentBrowsedPlayer()309     synchronized BrowseNode getCurrentBrowsedPlayer() {
310         return mCurrentBrowsedPlayer;
311     }
312 
setCurrentAddressedPlayer(String uid)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 
getCurrentAddressedPlayer()323     synchronized BrowseNode getCurrentAddressedPlayer() {
324         return mCurrentAddressedPlayer;
325     }
326 
327     @Override
toString()328     public String toString() {
329         return mBrowseMap.toString();
330     }
331 }
332