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