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.car.radio.bands;
18 
19 import android.hardware.radio.ProgramSelector;
20 import android.os.Parcel;
21 import android.os.Parcelable;
22 
23 import androidx.annotation.DrawableRes;
24 import androidx.annotation.IntDef;
25 import androidx.annotation.NonNull;
26 import androidx.annotation.Nullable;
27 import androidx.annotation.StringRes;
28 
29 import com.android.car.broadcastradio.support.platform.ProgramSelectorExt;
30 import com.android.car.radio.platform.RadioTunerExt;
31 import com.android.car.radio.platform.RadioTunerExt.TuneCallback;
32 import com.android.car.radio.util.Log;
33 
34 import java.lang.annotation.Retention;
35 import java.lang.annotation.RetentionPolicy;
36 import java.util.Objects;
37 
38 /**
39  * Representation of program type (band); i.e. AM, FM, DAB.
40  *
41  * It's OK to use == operator between these objects, as a given program type
42  * has only one instance per process.
43  */
44 public abstract class ProgramType implements Parcelable {
45     private static final String TAG = "BcRadioApp.ProgramType";
46 
47     /** {@see #TypeId} */
48     public static final int ID_AM = 1;
49 
50     /** {@see #TypeId} */
51     public static final int ID_FM = 2;
52 
53     /** {@see #TypeId} */
54     public static final int ID_DAB = 3;
55 
56     /**
57      * Numeric identifier of program type, for use with switch statements.
58      */
59     @IntDef(value = {
60         ID_AM,
61         ID_FM,
62         ID_DAB,
63     })
64     @Retention(RetentionPolicy.SOURCE)
65     public @interface TypeId {}
66 
67     /** AM program type */
68     public static final ProgramType AM = new AMProgramType(ID_AM);
69 
70     /** FM program type */
71     public static final ProgramType FM = new FMProgramType(ID_FM);
72 
73     /** DAB program type */
74     public static final ProgramType DAB = new DABProgramType(ID_DAB);
75 
76     /** Identifier of this program type.
77      *
78      * {@see #TypeId}
79      */
80     @TypeId
81     public final int id;
82 
ProgramType(@ypeId int id)83     protected ProgramType(@TypeId int id) {
84         this.id = id;
85     }
86 
87     /**
88      * Retrieves non-localized, english name of this program type.
89      */
90     @NonNull
getEnglishName()91     public abstract String getEnglishName();
92 
93     /**
94      * Retrieves localized name of this program type.
95      */
96     @StringRes
getLocalizedName()97     public abstract int getLocalizedName();
98 
99     /**
100      * Retrieves resourceId of this program type.
101      */
102     @DrawableRes
getResourceId()103     public abstract int getResourceId();
104 
105     /**
106      * Tunes to a default channel from this band.
107      *
108      * @param tuner Tuner to take action on.
109      * @param config Region config (i.e. frequency ranges).
110      * @param result Callback for tune success/failure.
111      */
tuneToDefault(@onNull RadioTunerExt tuner, @NonNull RegionConfig config, @Nullable TuneCallback result)112     public abstract void tuneToDefault(@NonNull RadioTunerExt tuner, @NonNull RegionConfig config,
113             @Nullable TuneCallback result);
114 
115     /**
116      * Returns program type for a given selector.
117      *
118      * @param sel ProgramSelector to check.
119      * @return program type of a given selector
120      */
fromSelector(@ullable ProgramSelector sel)121     public static @Nullable ProgramType fromSelector(@Nullable ProgramSelector sel) {
122         if (sel == null) return null;
123 
124         int priType = sel.getPrimaryId().getType();
125         if (priType == ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT) {
126             return DAB;
127         }
128         if (!ProgramSelectorExt.isAmFmProgram(sel)) return null;
129 
130         // this is an AM/FM program; let's check whether it's AM or FM
131         if (!ProgramSelectorExt.hasId(sel, ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY)) {
132             Log.e(TAG, "AM/FM program selector with missing frequency");
133             return FM;
134         }
135 
136         long freq = sel.getFirstId(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY);
137         if (ProgramSelectorExt.isAmFrequency(freq)) return AM;
138         if (ProgramSelectorExt.isFmFrequency(freq)) return FM;
139 
140         Log.e(TAG, "AM/FM program selector with frequency out of range: " + freq);
141         return FM;
142     }
143 
144     /**
145      * Checks, if the partial channel number is actually complete.
146      *
147      * This takes display format (see {@link #format}) into account, i.e. doesn't require
148      * FM trailing zeros (95.5 MHz, not 95500 kHz).
149      */
isComplete(@onNull RegionConfig config, int leadingDigits)150     public abstract boolean isComplete(@NonNull RegionConfig config, int leadingDigits);
151 
152     /**
153      * Generates full channel selector from its leading digits.
154      *
155      * The argument must be validated with {@link #isComplete} prior.
156      */
157     @NonNull
parseDigits(int leadingDigits)158     public abstract ProgramSelector parseDigits(int leadingDigits);
159 
160     /**
161      * Generates an array stating whether certain digits are append-able to a given channel prefix
162      * (so that it's still possible to type in a valid channel afterwards).
163      *
164      * @param config Regional config.
165      * @param leadingDigits Channel prefix.
166      * @return an array of length 10, where {@code arr[i] == true} states that it's possible to
167      *         append {@code i} to {@code leadingDigits}
168      */
169     @NonNull
getValidAppendices(@onNull RegionConfig config, int leadingDigits)170     public abstract boolean[] getValidAppendices(@NonNull RegionConfig config, int leadingDigits);
171 
172     /**
173      * Format partial channel number.
174      *
175      * This is used by manual tuner dialpad to display channel number entered by the user.
176      */
format(int leadingDigits)177     public String format(int leadingDigits) {
178         if (leadingDigits < 0) throw new IllegalArgumentException();
179         if (leadingDigits == 0) return "";
180         return Integer.toString(leadingDigits);
181     }
182 
183     @Override
toString()184     public String toString() {
185         return getEnglishName();
186     }
187 
188     @Override
hashCode()189     public int hashCode() {
190         return Objects.hash(id);
191     }
192 
193     @Override
equals(Object obj)194     public boolean equals(Object obj) {
195         if (this == obj) return true;
196         if (!(obj instanceof ProgramType)) return false;
197         ProgramType other = (ProgramType) obj;
198         return other.id == id;
199     }
200 
201     @Override
writeToParcel(Parcel dest, int flags)202     public void writeToParcel(Parcel dest, int flags) {
203         dest.writeInt(id);
204     }
205 
206     @Override
describeContents()207     public int describeContents() {
208         return 0;
209     }
210 
211     public static final Parcelable.Creator<ProgramType> CREATOR =
212             new Parcelable.Creator<ProgramType>() {
213         public ProgramType createFromParcel(Parcel in) {
214             int id = in.readInt();
215             switch (id) {
216                 case ID_AM:
217                     return AM;
218                 case ID_FM:
219                     return FM;
220                 case ID_DAB:
221                     return DAB;
222                 default:
223                     Log.w(TAG, "Unknown ProgramType ID: " + id);
224                     return null;
225             }
226         }
227 
228         public ProgramType[] newArray(int size) {
229             return new ProgramType[size];
230         }
231     };
232 }
233