blob: ce3d01459d2e3c327af89932958d2cbed39031c0 [file] [log] [blame]
/**
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.broadcastradio.support.platform;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Bitmap;
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager.ProgramInfo;
import android.hardware.radio.RadioMetadata;
import android.media.MediaMetadata;
import android.media.Rating;
import android.util.Log;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Proposed extensions to android.hardware.radio.RadioManager.ProgramInfo.
*
* They might eventually get pushed to the framework.
*/
public class ProgramInfoExt {
private static final String TAG = "BcRadioApp.pinfoext";
/**
* If there is no suitable program name, return null instead of doing
* a fallback to channel display name.
*/
public static final int NAME_NO_CHANNEL_FALLBACK = 1 << 16;
/**
* Flags to control how to fetch program name with {@link #getProgramName}.
*
* Lower 16 bits are reserved for {@link ProgramSelectorExt#NameFlag}.
*/
@IntDef(prefix = { "NAME_" }, flag = true, value = {
ProgramSelectorExt.NAME_NO_MODULATION,
ProgramSelectorExt.NAME_MODULATION_ONLY,
NAME_NO_CHANNEL_FALLBACK,
})
@Retention(RetentionPolicy.SOURCE)
public @interface NameFlag {}
private static final char EN_DASH = '\u2013';
private static final String TITLE_SEPARATOR = " " + EN_DASH + " ";
private static final String[] PROGRAM_NAME_ORDER = new String[] {
RadioMetadata.METADATA_KEY_PROGRAM_NAME,
RadioMetadata.METADATA_KEY_DAB_COMPONENT_NAME,
RadioMetadata.METADATA_KEY_DAB_SERVICE_NAME,
RadioMetadata.METADATA_KEY_DAB_ENSEMBLE_NAME,
RadioMetadata.METADATA_KEY_RDS_PS,
};
/**
* Returns program name suitable to display.
*
* If there is no program name, it falls back to channel name. Flags related to
* the channel name display will be forwarded to the channel name generation method.
*/
public static @NonNull String getProgramName(@NonNull ProgramInfo info, @NameFlag int flags) {
RadioMetadata meta = info.getMetadata();
if (meta != null) {
for (String key : PROGRAM_NAME_ORDER) {
String value = meta.getString(key);
if (value != null) return value;
}
}
if ((flags & NAME_NO_CHANNEL_FALLBACK) != 0) return "";
ProgramSelector sel = info.getSelector();
// if it's AM/FM program, prefer to display currently used AF frequency
if (ProgramSelectorExt.isAmFmProgram(sel)) {
ProgramSelector.Identifier phy = info.getPhysicallyTunedTo();
if (phy != null && phy.getType() == ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY) {
String chName = ProgramSelectorExt.formatAmFmFrequency(phy.getValue(), flags);
if (chName != null) return chName;
}
}
String selName = ProgramSelectorExt.getDisplayName(sel, flags);
if (selName != null) return selName;
Log.w(TAG, "ProgramInfo without a name nor channel name");
return "";
}
/**
* Proposed reimplementation of {@link RadioManager#ProgramInfo#getMetadata}.
*
* As opposed to the original implementation, it never returns null.
*/
public static @NonNull RadioMetadata getMetadata(@NonNull ProgramInfo info) {
RadioMetadata meta = info.getMetadata();
if (meta != null) return meta;
/* Creating new Metadata object on each get won't be necessary after we
* push this code to the framework. */
return (new RadioMetadata.Builder()).build();
}
/**
* Converts {@ProgramInfo} to {@MediaMetadata}.
*
* This method is meant to be used for currently playing station in {@link MediaSession}.
*
* @param info {@link ProgramInfo} to convert
* @param isFavorite true, if a given program is a favorite
* @param imageResolver metadata images resolver/cache
* @return {@link MediaMetadata} object
*/
public static @NonNull MediaMetadata toMediaMetadata(@NonNull ProgramInfo info,
boolean isFavorite, @Nullable ImageResolver imageResolver) {
MediaMetadata.Builder bld = new MediaMetadata.Builder();
bld.putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, getProgramName(info, 0));
RadioMetadata meta = info.getMetadata();
if (meta != null) {
String title = meta.getString(RadioMetadata.METADATA_KEY_TITLE);
if (title != null) {
bld.putString(MediaMetadata.METADATA_KEY_TITLE, title);
}
String artist = meta.getString(RadioMetadata.METADATA_KEY_ARTIST);
if (artist != null) {
bld.putString(MediaMetadata.METADATA_KEY_ARTIST, artist);
}
String album = meta.getString(RadioMetadata.METADATA_KEY_ALBUM);
if (album != null) {
bld.putString(MediaMetadata.METADATA_KEY_ALBUM, album);
}
if (title != null || artist != null) {
String subtitle;
if (title == null) {
subtitle = artist;
} else if (artist == null) {
subtitle = title;
} else {
subtitle = title + TITLE_SEPARATOR + artist;
}
bld.putString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE, subtitle);
}
long albumArtId = RadioMetadataExt.getGlobalBitmapId(meta,
RadioMetadata.METADATA_KEY_ART);
if (albumArtId != 0 && imageResolver != null) {
Bitmap bm = imageResolver.resolve(albumArtId);
if (bm != null) bld.putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, bm);
}
}
bld.putRating(MediaMetadata.METADATA_KEY_USER_RATING, Rating.newHeartRating(isFavorite));
return bld.build();
}
}