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