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