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