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.audio_util;
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 com.android.bluetooth.R;
29 
30 import java.util.ArrayList;
31 import java.util.List;
32 
33 class Util {
34     public static String TAG = "audio_util.Util";
35     public static boolean DEBUG = false;
36 
37     private static final String GPM_KEY = "com.google.android.music.mediasession.music_metadata";
38 
39     // TODO (apanicke): Remove this prefix later, for now it makes debugging easier.
40     public static final String NOW_PLAYING_PREFIX = "NowPlayingId";
41 
42     /**
43      * Get an empty set of Metadata
44      */
empty_data()45     public static final Metadata empty_data() {
46         Metadata.Builder builder = new Metadata.Builder();
47         return builder.useDefaults().build();
48     }
49 
50     /**
51      * Determine if a set of Metadata is "empty" as defined by audio_util.
52      */
isEmptyData(Metadata data)53     public static final boolean isEmptyData(Metadata data) {
54         if (data == null) return true;
55         return (empty_data().equals(data) && data.mediaId.equals("Not Provided"));
56     }
57 
58     /**
59      * Get whether or not Bluetooth is configured to support URI images or not.
60      *
61      * Note that creating URI images will dramatically increase memory usage.
62      */
areUriImagesSupported(Context context)63     public static boolean areUriImagesSupported(Context context) {
64         if (context == null) return false;
65         return context.getResources().getBoolean(R.bool.avrcp_target_cover_art_uri_images);
66     }
67 
68     /**
69      * Translate a bundle of MediaMetadata keys to audio_util's Metadata
70      */
toMetadata(Context context, Bundle bundle)71     public static Metadata toMetadata(Context context, Bundle bundle) {
72         Metadata.Builder builder = new Metadata.Builder();
73         try {
74             return builder.useContext(context).useDefaults().fromBundle(bundle).build();
75         } catch (Exception e) {
76             Log.e(TAG, "Failed to build Metadata from Bundle, returning empty data", e);
77             return empty_data();
78         }
79     }
80 
81     /**
82      * Translate a MediaDescription to audio_util's Metadata
83      */
toMetadata(Context context, MediaDescription desc)84     public static Metadata toMetadata(Context context, MediaDescription desc) {
85         // Find GPM_KEY data if it exists
86         MediaMetadata data = null;
87         Bundle extras = (desc != null ? desc.getExtras() : null);
88         if (extras != null && extras.containsKey(GPM_KEY)) {
89             data = (MediaMetadata) extras.get(GPM_KEY);
90         }
91 
92         Metadata.Builder builder = new Metadata.Builder();
93         try {
94             return builder.useContext(context).useDefaults().fromMediaDescription(desc)
95                     .fromMediaMetadata(data).build();
96         } catch (Exception e) {
97             Log.e(TAG, "Failed to build Metadata from MediaDescription, returning empty data", e);
98             return empty_data();
99         }
100     }
101 
102     /**
103      * Translate a MediaItem to audio_util's Metadata
104      */
toMetadata(Context context, MediaItem item)105     public static Metadata toMetadata(Context context, MediaItem item) {
106         Metadata.Builder builder = new Metadata.Builder();
107         try {
108             return builder.useContext(context).useDefaults().fromMediaItem(item).build();
109         } catch (Exception e) {
110             Log.e(TAG, "Failed to build Metadata from MediaItem, returning empty data", e);
111             return empty_data();
112         }
113     }
114 
115     /**
116      * Translate a MediaSession.QueueItem to audio_util's Metadata
117      */
toMetadata(Context context, MediaSession.QueueItem item)118     public static Metadata toMetadata(Context context, MediaSession.QueueItem item) {
119         Metadata.Builder builder = new Metadata.Builder();
120 
121         try {
122             builder.useDefaults().fromQueueItem(item);
123         } catch (Exception e) {
124             Log.e(TAG, "Failed to build Metadata from QueueItem, returning empty data", e);
125             return empty_data();
126         }
127 
128         // For Queue Items, the Media Id will always be just its Queue ID
129         // We don't need to use its actual ID since we don't promise UIDS being valid
130         // between a file system and it's now playing list.
131         if (item != null) builder.setMediaId(NOW_PLAYING_PREFIX + item.getQueueId());
132         return builder.build();
133     }
134 
135     /**
136      * Translate a MediaMetadata to audio_util's Metadata
137      */
toMetadata(Context context, MediaMetadata data)138     public static Metadata toMetadata(Context context, MediaMetadata data) {
139         Metadata.Builder builder = new Metadata.Builder();
140         // This will always be currsong. The AVRCP service will overwrite the mediaId if it needs to
141         // TODO (apanicke): Remove when the service is ready, right now it makes debugging much more
142         // convenient
143         try {
144             return builder.useContext(context).useDefaults().fromMediaMetadata(data)
145                     .setMediaId("currsong").build();
146         } catch (Exception e) {
147             Log.e(TAG, "Failed to build Metadata from MediaMetadata, returning empty data", e);
148             return empty_data();
149         }
150     }
151 
152     /**
153      * Translate a list of MediaSession.QueueItem to a list of audio_util's Metadata
154      */
toMetadataList(Context context, List<MediaSession.QueueItem> items)155     public static List<Metadata> toMetadataList(Context context,
156             List<MediaSession.QueueItem> items) {
157         ArrayList<Metadata> list = new ArrayList<Metadata>();
158 
159         if (items == null) return list;
160 
161         for (int i = 0; i < items.size(); i++) {
162             Metadata data = toMetadata(context, items.get(i));
163             if (isEmptyData(data)) {
164                 Log.e(TAG, "Received an empty Metadata item in list. Returning an empty queue");
165                 return new ArrayList<Metadata>();
166             }
167             data.trackNum = "" + (i + 1);
168             data.numTracks = "" + items.size();
169             list.add(data);
170         }
171 
172         return list;
173     }
174 
175     // Helper method to close a list of ListItems so that if the callee wants
176     // to mutate the list they can do it without affecting any internally cached info
cloneList(List<ListItem> list)177     public static List<ListItem> cloneList(List<ListItem> list) {
178         List<ListItem> clone = new ArrayList<ListItem>(list.size());
179         for (ListItem item : list) clone.add(item.clone());
180         return clone;
181     }
182 
getDisplayName(Context context, String packageName)183     public static String getDisplayName(Context context, String packageName) {
184         try {
185             PackageManager manager = context.getPackageManager();
186             return manager.getApplicationLabel(manager.getApplicationInfo(packageName, 0))
187                     .toString();
188         } catch (Exception e) {
189             Log.w(TAG, "Name Not Found using package name: " + packageName);
190             return packageName;
191         }
192     }
193 }
194