1861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk/** 2861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk * Copyright (C) 2018 The Android Open Source Project 3861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk * 4861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk * Licensed under the Apache License, Version 2.0 (the "License"); 5861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk * you may not use this file except in compliance with the License. 6861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk * You may obtain a copy of the License at 7861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk * 8861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk * http://www.apache.org/licenses/LICENSE-2.0 9861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk * 10861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk * Unless required by applicable law or agreed to in writing, software 11861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk * distributed under the License is distributed on an "AS IS" BASIS, 12861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk * See the License for the specific language governing permissions and 14861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk * limitations under the License. 15861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk */ 16861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk 17861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczykpackage com.android.car.broadcastradio.support.platform; 18861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk 19861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczykimport android.annotation.IntDef; 20861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczykimport android.annotation.NonNull; 21861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczykimport android.annotation.Nullable; 22861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczykimport android.graphics.Bitmap; 23861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczykimport android.hardware.radio.ProgramSelector; 24861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczykimport android.hardware.radio.RadioManager.ProgramInfo; 25861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczykimport android.hardware.radio.RadioMetadata; 26861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczykimport android.media.MediaMetadata; 27861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczykimport android.media.Rating; 28861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczykimport android.util.Log; 29861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk 30861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczykimport java.lang.annotation.Retention; 31861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczykimport java.lang.annotation.RetentionPolicy; 32861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk 33861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk/** 34861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk * Proposed extensions to android.hardware.radio.RadioManager.ProgramInfo. 35861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk * 36861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk * They might eventually get pushed to the framework. 37861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk */ 38861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczykpublic class ProgramInfoExt { 39861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk private static final String TAG = "BcRadioApp.pinfoext"; 40861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk 41861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk /** 42861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk * If there is no suitable program name, return null instead of doing 43861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk * a fallback to channel display name. 44861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk */ 45861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk public static final int NAME_NO_CHANNEL_FALLBACK = 1 << 16; 46861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk 47861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk /** 48861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk * Flags to control how to fetch program name with {@link #getProgramName}. 49861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk * 50861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk * Lower 16 bits are reserved for {@link ProgramSelectorExt#NameFlag}. 51861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk */ 52861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk @IntDef(prefix = { "NAME_" }, flag = true, value = { 53861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk ProgramSelectorExt.NAME_NO_MODULATION, 54861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk ProgramSelectorExt.NAME_MODULATION_ONLY, 55861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk NAME_NO_CHANNEL_FALLBACK, 56861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk }) 57861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk @Retention(RetentionPolicy.SOURCE) 58861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk public @interface NameFlag {} 59861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk 60861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk private static final char EN_DASH = '\u2013'; 61861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk private static final String TITLE_SEPARATOR = " " + EN_DASH + " "; 62861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk 63861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk private static final String[] PROGRAM_NAME_ORDER = new String[] { 64861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk RadioMetadata.METADATA_KEY_PROGRAM_NAME, 65861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk RadioMetadata.METADATA_KEY_DAB_COMPONENT_NAME, 66861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk RadioMetadata.METADATA_KEY_DAB_SERVICE_NAME, 67861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk RadioMetadata.METADATA_KEY_DAB_ENSEMBLE_NAME, 68861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk RadioMetadata.METADATA_KEY_RDS_PS, 69861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk }; 70861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk 71861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk /** 72861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk * Returns program name suitable to display. 73861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk * 74861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk * If there is no program name, it falls back to channel name. Flags related to 75861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk * the channel name display will be forwarded to the channel name generation method. 76861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk */ 77861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk public static @NonNull String getProgramName(@NonNull ProgramInfo info, @NameFlag int flags) { 78861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk RadioMetadata meta = info.getMetadata(); 79861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk if (meta != null) { 80861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk for (String key : PROGRAM_NAME_ORDER) { 81861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk String value = meta.getString(key); 82861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk if (value != null) return value; 83861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk } 84861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk } 85861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk 86861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk if ((flags & NAME_NO_CHANNEL_FALLBACK) != 0) return ""; 87861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk 88861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk ProgramSelector sel = info.getSelector(); 89861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk 90861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk // if it's AM/FM program, prefer to display currently used AF frequency 91861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk if (ProgramSelectorExt.isAmFmProgram(sel)) { 92861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk ProgramSelector.Identifier phy = info.getPhysicallyTunedTo(); 93861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk if (phy != null && phy.getType() == ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY) { 94861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk String chName = ProgramSelectorExt.formatAmFmFrequency(phy.getValue(), flags); 95861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk if (chName != null) return chName; 96861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk } 97861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk } 98861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk 99861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk String selName = ProgramSelectorExt.getDisplayName(sel, flags); 100861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk if (selName != null) return selName; 101861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk 102861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk Log.w(TAG, "ProgramInfo without a name nor channel name"); 103861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk return ""; 104861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk } 105861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk 106861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk /** 107861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk * Proposed reimplementation of {@link RadioManager#ProgramInfo#getMetadata}. 108861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk * 109861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk * As opposed to the original implementation, it never returns null. 110861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk */ 111861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk public static @NonNull RadioMetadata getMetadata(@NonNull ProgramInfo info) { 112861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk RadioMetadata meta = info.getMetadata(); 113861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk if (meta != null) return meta; 114861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk 115861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk /* Creating new Metadata object on each get won't be necessary after we 116861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk * push this code to the framework. */ 117861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk return (new RadioMetadata.Builder()).build(); 118861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk } 119861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk 120861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk /** 121861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk * Converts {@ProgramInfo} to {@MediaMetadata}. 122861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk * 123861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk * This method is meant to be used for currently playing station in {@link MediaSession}. 124861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk * 125861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk * @param info {@link ProgramInfo} to convert 126861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk * @param isFavorite true, if a given program is a favorite 127861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk * @param imageResolver metadata images resolver/cache 128861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk * @return {@link MediaMetadata} object 129861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk */ 130861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk public static @NonNull MediaMetadata toMediaMetadata(@NonNull ProgramInfo info, 131861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk boolean isFavorite, @Nullable ImageResolver imageResolver) { 132861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk MediaMetadata.Builder bld = new MediaMetadata.Builder(); 133861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk 134861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk bld.putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, getProgramName(info, 0)); 135861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk 136861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk RadioMetadata meta = info.getMetadata(); 137861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk if (meta != null) { 138861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk String title = meta.getString(RadioMetadata.METADATA_KEY_TITLE); 139861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk if (title != null) { 140861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk bld.putString(MediaMetadata.METADATA_KEY_TITLE, title); 141861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk } 142861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk String artist = meta.getString(RadioMetadata.METADATA_KEY_ARTIST); 143861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk if (artist != null) { 144861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk bld.putString(MediaMetadata.METADATA_KEY_ARTIST, artist); 145861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk } 146861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk String album = meta.getString(RadioMetadata.METADATA_KEY_ALBUM); 147861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk if (album != null) { 148861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk bld.putString(MediaMetadata.METADATA_KEY_ALBUM, album); 149861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk } 150861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk if (title != null || artist != null) { 151861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk String subtitle; 152861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk if (title == null) { 153861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk subtitle = artist; 154861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk } else if (artist == null) { 155861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk subtitle = title; 156861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk } else { 157861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk subtitle = title + TITLE_SEPARATOR + artist; 158861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk } 159861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk bld.putString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE, subtitle); 160861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk } 161861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk long albumArtId = RadioMetadataExt.getGlobalBitmapId(meta, 162861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk RadioMetadata.METADATA_KEY_ART); 163861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk if (albumArtId != 0 && imageResolver != null) { 164861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk Bitmap bm = imageResolver.resolve(albumArtId); 165861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk if (bm != null) bld.putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, bm); 166861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk } 167861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk } 168861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk 169861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk bld.putRating(MediaMetadata.METADATA_KEY_USER_RATING, Rating.newHeartRating(isFavorite)); 170861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk 171861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk return bld.build(); 172861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk } 173861c2cc701eefbe939b76bae7528b2c1bcb7fc7bTomasz Wasilczyk} 174