• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.MediaMetadata;
22 import android.media.browse.MediaBrowser.MediaItem;
23 import android.media.session.MediaSession;
24 import android.os.SystemProperties;
25 import android.util.Log;
26 
27 import androidx.annotation.VisibleForTesting;
28 
29 import java.util.ArrayList;
30 import java.util.List;
31 
32 class Util {
33     public static String TAG = "audio_util.Util";
34 
35     private static final String VFS_COVER_ART_ENABLED_PROPERTY =
36             "bluetooth.profile.avrcp.target.vfs_coverart.enabled";
37 
38     private static final String MULTIPLE_PLAYERS_SUPPORT_ENABLED_PROPERTY =
39             "bluetooth.profile.avrcp.target.multiple_players.enabled";
40 
41     @VisibleForTesting
42     static Boolean sUriImagesSupport =
43             SystemProperties.getBoolean(VFS_COVER_ART_ENABLED_PROPERTY, false);
44 
45     @VisibleForTesting
46     static Boolean sMultiPlayersSupport =
47             SystemProperties.getBoolean(MULTIPLE_PLAYERS_SUPPORT_ENABLED_PROPERTY, false);
48 
49     // TODO (apanicke): Remove this prefix later, for now it makes debugging easier.
50     public static final String NOW_PLAYING_PREFIX = "NowPlayingId";
51 
52     /** Get an empty set of Metadata */
empty_data()53     public static final Metadata empty_data() {
54         Metadata.Builder builder = new Metadata.Builder();
55         return builder.useDefaults().build();
56     }
57 
58     /** Determine if a set of Metadata is "empty" as defined by audio_util. */
isEmptyData(Metadata data)59     public static final boolean isEmptyData(Metadata data) {
60         if (data == null) return true;
61         // Note: We need both equals() and an explicit media id check because equals() does
62         // not check for the media ID.
63         return (empty_data().equals(data) && data.mediaId.equals(Metadata.EMPTY_MEDIA_ID));
64     }
65 
66     /**
67      * Get whether or not Bluetooth is configured to support URI images.
68      *
69      * <p>Note that creating URI images will dramatically increase memory usage.
70      */
areUriImagesSupported()71     public static boolean areUriImagesSupported() {
72         return sUriImagesSupport.booleanValue();
73     }
74 
75     /**
76      * Get whether or not Bluetooth is configured to advertise multiple media players.
77      *
78      * <p>This is disabled by default as some car head units will stop working if multiple media
79      * players are present. Addressed Player and Browsing commands should always display only one
80      * media player to the remote device by default.
81      */
areMultiplePlayersSupported()82     public static boolean areMultiplePlayersSupported() {
83         return sMultiPlayersSupport.booleanValue();
84     }
85 
86     /** Translate a MediaItem to audio_util's Metadata */
toMetadata(Context context, MediaItem item)87     public static Metadata toMetadata(Context context, MediaItem item) {
88         Metadata.Builder builder = new Metadata.Builder();
89         try {
90             return builder.useContext(context).useDefaults().fromMediaItem(item).build();
91         } catch (Exception e) {
92             Log.e(TAG, "Failed to build Metadata from MediaItem, returning empty data", e);
93             return empty_data();
94         }
95     }
96 
97     /** Translate a MediaSession.QueueItem to audio_util's Metadata */
toMetadata(Context context, MediaSession.QueueItem item)98     public static Metadata toMetadata(Context context, MediaSession.QueueItem item) {
99         Metadata.Builder builder = new Metadata.Builder();
100 
101         try {
102             builder.useDefaults().fromQueueItem(item);
103         } catch (Exception e) {
104             Log.e(TAG, "Failed to build Metadata from QueueItem, returning empty data", e);
105             return empty_data();
106         }
107 
108         // For Queue Items, the Media Id will always be just its Queue ID
109         // We don't need to use its actual ID since we don't promise UIDS being valid
110         // between a file system and it's now playing list.
111         if (item != null) builder.setMediaId(NOW_PLAYING_PREFIX + item.getQueueId());
112         return builder.build();
113     }
114 
115     /** Translate a MediaMetadata to audio_util's Metadata */
toMetadata(Context context, MediaMetadata data)116     public static Metadata toMetadata(Context context, MediaMetadata data) {
117         Metadata.Builder builder = new Metadata.Builder();
118         // This will always be currsong. The AVRCP service will overwrite the mediaId if it needs to
119         // TODO (apanicke): Remove when the service is ready, right now it makes debugging much more
120         // convenient
121         try {
122             return builder.useContext(context)
123                     .useDefaults()
124                     .fromMediaMetadata(data)
125                     .setMediaId("currsong")
126                     .build();
127         } catch (Exception e) {
128             Log.e(TAG, "Failed to build Metadata from MediaMetadata, returning empty data", e);
129             return empty_data();
130         }
131     }
132 
133     /** Translate a list of MediaSession.QueueItem to a list of audio_util's Metadata */
toMetadataList( Context context, List<MediaSession.QueueItem> items)134     public static List<Metadata> toMetadataList(
135             Context context, List<MediaSession.QueueItem> items) {
136         ArrayList<Metadata> list = new ArrayList<>();
137 
138         if (items == null) return list;
139 
140         for (int i = 0; i < items.size(); i++) {
141             Metadata data = toMetadata(context, items.get(i));
142             if (isEmptyData(data)) {
143                 Log.e(TAG, "Received an empty Metadata item in list. Returning an empty queue");
144                 return new ArrayList<>();
145             }
146             data.trackNum = "" + (i + 1);
147             data.numTracks = "" + items.size();
148             list.add(data);
149         }
150 
151         return list;
152     }
153 
154     // Helper method to close a list of ListItems so that if the callee wants
155     // to mutate the list they can do it without affecting any internally cached info
cloneList(List<ListItem> list)156     public static List<ListItem> cloneList(List<ListItem> list) {
157         List<ListItem> clone = new ArrayList<ListItem>(list.size());
158         for (ListItem item : list) clone.add(item.clone());
159         return clone;
160     }
161 
getDisplayName(Context context, String packageName)162     public static String getDisplayName(Context context, String packageName) {
163         try {
164             PackageManager manager = context.getPackageManager();
165             return manager.getApplicationLabel(manager.getApplicationInfo(packageName, 0))
166                     .toString();
167         } catch (Exception e) {
168             Log.w(TAG, "Name Not Found using package name: " + packageName);
169             return packageName;
170         }
171     }
172 }
173