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