1 /*
2  * Copyright (C) 2018 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.avrcp;
18 
19 import android.content.Context;
20 import android.content.pm.PackageManager;
21 import android.media.MediaDescription;
22 import android.media.MediaMetadata;
23 import android.media.browse.MediaBrowser.MediaItem;
24 import android.media.session.MediaSession;
25 import android.os.Bundle;
26 import android.util.Log;
27 
28 import java.util.ArrayList;
29 import java.util.List;
30 
31 class Util {
32     public static String TAG = "AvrcpUtil";
33     public static boolean DEBUG = false;
34 
35     private static final String GPM_KEY = "com.google.android.music.mediasession.music_metadata";
36 
37     // TODO (apanicke): Remove this prefix later, for now it makes debugging easier.
38     public static final String NOW_PLAYING_PREFIX = "NowPlayingId";
39 
empty_data()40     public static final Metadata empty_data() {
41         Metadata ret = new Metadata();
42         ret.mediaId = "Not Provided";
43         ret.title = "Not Provided";
44         ret.artist = "";
45         ret.album = "";
46         ret.genre = "";
47         ret.trackNum = "1";
48         ret.numTracks = "1";
49         ret.duration = "0";
50         return ret;
51     }
52 
bundleToMetadata(Bundle bundle)53     public static Metadata bundleToMetadata(Bundle bundle) {
54         if (bundle == null) return empty_data();
55 
56         Metadata temp = new Metadata();
57         temp.title = bundle.getString(MediaMetadata.METADATA_KEY_TITLE, "Not Provided");
58         temp.artist = bundle.getString(MediaMetadata.METADATA_KEY_ARTIST, "");
59         temp.album = bundle.getString(MediaMetadata.METADATA_KEY_ALBUM, "");
60         temp.trackNum = "" + bundle.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER, 1);
61         temp.numTracks = "" + bundle.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS, 1);
62         temp.genre = bundle.getString(MediaMetadata.METADATA_KEY_GENRE, "");
63         temp.duration = "" + bundle.getLong(MediaMetadata.METADATA_KEY_DURATION, 0);
64         return temp;
65     }
66 
descriptionToBundle(MediaDescription desc)67     public static Bundle descriptionToBundle(MediaDescription desc) {
68         Bundle ret = new Bundle();
69         if (desc == null) return ret;
70 
71         if (desc.getTitle() != null) {
72             ret.putString(MediaMetadata.METADATA_KEY_TITLE, desc.getTitle().toString());
73         }
74 
75         if (desc.getSubtitle() != null) {
76             ret.putString(MediaMetadata.METADATA_KEY_ARTIST, desc.getSubtitle().toString());
77         }
78 
79         if (desc.getDescription() != null) {
80             ret.putString(MediaMetadata.METADATA_KEY_ALBUM, desc.getDescription().toString());
81         }
82 
83         // If the bundle has title or artist use those over the description title or subtitle.
84         if (desc.getExtras() != null) ret.putAll(desc.getExtras());
85 
86         if (ret.containsKey(GPM_KEY)) {
87             if (DEBUG) Log.d(TAG, "MediaDescription contains GPM data");
88             ret.putAll(mediaMetadataToBundle((MediaMetadata) ret.get(GPM_KEY)));
89         }
90 
91         return ret;
92     }
93 
mediaMetadataToBundle(MediaMetadata data)94     public static Bundle mediaMetadataToBundle(MediaMetadata data) {
95         Bundle bundle = new Bundle();
96         if (data == null) return bundle;
97 
98         if (data.containsKey(MediaMetadata.METADATA_KEY_TITLE)) {
99             bundle.putString(MediaMetadata.METADATA_KEY_TITLE,
100                     data.getString(MediaMetadata.METADATA_KEY_TITLE));
101         }
102 
103         if (data.containsKey(MediaMetadata.METADATA_KEY_ARTIST)) {
104             bundle.putString(MediaMetadata.METADATA_KEY_ARTIST,
105                     data.getString(MediaMetadata.METADATA_KEY_ARTIST));
106         }
107 
108         if (data.containsKey(MediaMetadata.METADATA_KEY_ALBUM)) {
109             bundle.putString(MediaMetadata.METADATA_KEY_ALBUM,
110                     data.getString(MediaMetadata.METADATA_KEY_ALBUM));
111         }
112 
113         if (data.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER)) {
114             bundle.putLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER,
115                     data.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER));
116         }
117 
118         if (data.containsKey(MediaMetadata.METADATA_KEY_NUM_TRACKS)) {
119             bundle.putLong(MediaMetadata.METADATA_KEY_NUM_TRACKS,
120                     data.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS));
121         }
122 
123         if (data.containsKey(MediaMetadata.METADATA_KEY_GENRE)) {
124             bundle.putString(MediaMetadata.METADATA_KEY_GENRE,
125                     data.getString(MediaMetadata.METADATA_KEY_GENRE));
126         }
127 
128         if (data.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
129             bundle.putLong(MediaMetadata.METADATA_KEY_DURATION,
130                     data.getLong(MediaMetadata.METADATA_KEY_DURATION));
131         }
132 
133         return bundle;
134     }
135 
toMetadata(MediaSession.QueueItem item)136     public static Metadata toMetadata(MediaSession.QueueItem item) {
137         if (item == null) {
138             return empty_data();
139         }
140 
141         Bundle bundle = descriptionToBundle(item.getDescription());
142 
143         if (DEBUG) {
144             for (String key : bundle.keySet()) {
145                 Log.d(TAG, "toMetadata: QueueItem: ContainsKey: " + key);
146             }
147         }
148 
149         Metadata ret = bundleToMetadata(bundle);
150 
151         // For Queue Items, the Media Id will always be just its Queue ID
152         // We don't need to use its actual ID since we don't promise UIDS being valid
153         // between a file system and it's now playing list.
154         ret.mediaId = NOW_PLAYING_PREFIX + item.getQueueId();
155 
156         return ret;
157     }
158 
toMetadata(MediaMetadata data)159     public static Metadata toMetadata(MediaMetadata data) {
160         if (data == null) {
161             return empty_data();
162         }
163 
164         MediaDescription desc = data.getDescription();
165 
166         Bundle dataBundle = mediaMetadataToBundle(data);
167         Bundle bundle = descriptionToBundle(data.getDescription());
168 
169         // Prioritize the media metadata over the media description
170         bundle.putAll(dataBundle);
171 
172         if (DEBUG) {
173             for (String key : bundle.keySet()) {
174                 Log.d(TAG, "toMetadata: MediaMetadata: ContainsKey: " + key);
175             }
176         }
177 
178         Metadata ret = bundleToMetadata(bundle);
179 
180         // This will always be currsong. The AVRCP service will overwrite the mediaId if it needs to
181         // TODO (apanicke): Remove when the service is ready, right now it makes debugging much more
182         // convenient
183         ret.mediaId = "currsong";
184 
185         return ret;
186     }
187 
toMetadata(MediaItem item)188     public static Metadata toMetadata(MediaItem item) {
189         if (item == null) {
190             return empty_data();
191         }
192 
193         Bundle bundle = descriptionToBundle(item.getDescription());
194 
195         if (DEBUG) {
196             for (String key : bundle.keySet()) {
197                 Log.d(TAG, "toMetadata: MediaItem: ContainsKey: " + key);
198             }
199         }
200 
201         Metadata ret = bundleToMetadata(bundle);
202         ret.mediaId = item.getMediaId();
203 
204         return ret;
205     }
206 
toMetadataList(List<MediaSession.QueueItem> items)207     public static List<Metadata> toMetadataList(List<MediaSession.QueueItem> items) {
208         ArrayList<Metadata> list = new ArrayList<Metadata>();
209 
210         if (items == null) return list;
211 
212         for (int i = 0; i < items.size(); i++) {
213             Metadata data = toMetadata(items.get(i));
214             data.trackNum = "" + (i + 1);
215             data.numTracks = "" + items.size();
216             list.add(data);
217         }
218 
219         return list;
220     }
221 
222     // Helper method to close a list of ListItems so that if the callee wants
223     // to mutate the list they can do it without affecting any internally cached info
cloneList(List<ListItem> list)224     public static List<ListItem> cloneList(List<ListItem> list) {
225         List<ListItem> clone = new ArrayList<ListItem>(list.size());
226         for (ListItem item : list) clone.add(item.clone());
227         return clone;
228     }
229 
getDisplayName(Context context, String packageName)230     public static String getDisplayName(Context context, String packageName) {
231         try {
232             PackageManager manager = context.getPackageManager();
233             return manager.getApplicationLabel(manager.getApplicationInfo(packageName, 0))
234                     .toString();
235         } catch (Exception e) {
236             Log.w(TAG, "Name Not Found using package name: " + packageName);
237             return packageName;
238         }
239     }
240 }
241