Convert.java revision eab3e5509689cf4dd066f0de5e060967626b3a2a
1/** 2 * Copyright (C) 2017 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 17package com.android.server.broadcastradio.hal2; 18 19import android.annotation.NonNull; 20import android.annotation.Nullable; 21import android.hardware.broadcastradio.V2_0.AmFmBandRange; 22import android.hardware.broadcastradio.V2_0.AmFmRegionConfig; 23import android.hardware.broadcastradio.V2_0.Announcement; 24import android.hardware.broadcastradio.V2_0.IdentifierType; 25import android.hardware.broadcastradio.V2_0.ProgramFilter; 26import android.hardware.broadcastradio.V2_0.ProgramIdentifier; 27import android.hardware.broadcastradio.V2_0.ProgramInfo; 28import android.hardware.broadcastradio.V2_0.ProgramInfoFlags; 29import android.hardware.broadcastradio.V2_0.ProgramListChunk; 30import android.hardware.broadcastradio.V2_0.Properties; 31import android.hardware.broadcastradio.V2_0.Result; 32import android.hardware.broadcastradio.V2_0.VendorKeyValue; 33import android.hardware.radio.ProgramList; 34import android.hardware.radio.ProgramSelector; 35import android.hardware.radio.RadioManager; 36import android.os.ParcelableException; 37import android.util.Slog; 38 39import java.util.ArrayList; 40import java.util.Arrays; 41import java.util.Collection; 42import java.util.Collections; 43import java.util.HashMap; 44import java.util.HashSet; 45import java.util.List; 46import java.util.Map; 47import java.util.Objects; 48import java.util.Set; 49import java.util.stream.Collectors; 50 51class Convert { 52 private static final String TAG = "BcRadio2Srv.convert"; 53 54 static void throwOnError(String action, int result) { 55 switch (result) { 56 case Result.OK: 57 return; 58 case Result.UNKNOWN_ERROR: 59 throw new ParcelableException(new RuntimeException(action + ": UNKNOWN_ERROR")); 60 case Result.INTERNAL_ERROR: 61 throw new ParcelableException(new RuntimeException(action + ": INTERNAL_ERROR")); 62 case Result.INVALID_ARGUMENTS: 63 throw new IllegalArgumentException(action + ": INVALID_ARGUMENTS"); 64 case Result.INVALID_STATE: 65 throw new IllegalStateException(action + ": INVALID_STATE"); 66 case Result.NOT_SUPPORTED: 67 throw new UnsupportedOperationException(action + ": NOT_SUPPORTED"); 68 case Result.TIMEOUT: 69 throw new ParcelableException(new RuntimeException(action + ": TIMEOUT")); 70 default: 71 throw new ParcelableException(new RuntimeException( 72 action + ": unknown error (" + result + ")")); 73 } 74 } 75 76 private static @NonNull Map<String, String> 77 vendorInfoFromHal(@Nullable List<VendorKeyValue> info) { 78 if (info == null) return Collections.emptyMap(); 79 80 Map<String, String> map = new HashMap<>(); 81 for (VendorKeyValue kvp : info) { 82 if (kvp.key == null || kvp.value == null) { 83 Slog.w(TAG, "VendorKeyValue contains null pointers"); 84 continue; 85 } 86 map.put(kvp.key, kvp.value); 87 } 88 89 return map; 90 } 91 92 private static @ProgramSelector.ProgramType int identifierTypeToProgramType( 93 @ProgramSelector.IdentifierType int idType) { 94 switch (idType) { 95 case ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY: 96 case ProgramSelector.IDENTIFIER_TYPE_RDS_PI: 97 // TODO(b/69958423): verify AM/FM with frequency range 98 return ProgramSelector.PROGRAM_TYPE_FM; 99 case ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT: 100 // TODO(b/69958423): verify AM/FM with frequency range 101 return ProgramSelector.PROGRAM_TYPE_FM_HD; 102 case ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC: 103 case ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE: 104 case ProgramSelector.IDENTIFIER_TYPE_DAB_SCID: 105 case ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY: 106 return ProgramSelector.PROGRAM_TYPE_DAB; 107 case ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID: 108 case ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY: 109 return ProgramSelector.PROGRAM_TYPE_DRMO; 110 case ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID: 111 case ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL: 112 return ProgramSelector.PROGRAM_TYPE_SXM; 113 } 114 if (idType >= ProgramSelector.IDENTIFIER_TYPE_VENDOR_PRIMARY_START 115 && idType <= ProgramSelector.IDENTIFIER_TYPE_VENDOR_PRIMARY_END) { 116 return idType; 117 } 118 return ProgramSelector.PROGRAM_TYPE_INVALID; 119 } 120 121 private static @NonNull int[] 122 identifierTypesToProgramTypes(@NonNull int[] idTypes) { 123 Set<Integer> pTypes = new HashSet<>(); 124 125 for (int idType : idTypes) { 126 int pType = identifierTypeToProgramType(idType); 127 128 if (pType == ProgramSelector.PROGRAM_TYPE_INVALID) continue; 129 130 pTypes.add(pType); 131 if (pType == ProgramSelector.PROGRAM_TYPE_FM) { 132 // TODO(b/69958423): verify AM/FM with region info 133 pTypes.add(ProgramSelector.PROGRAM_TYPE_AM); 134 } 135 if (pType == ProgramSelector.PROGRAM_TYPE_FM_HD) { 136 // TODO(b/69958423): verify AM/FM with region info 137 pTypes.add(ProgramSelector.PROGRAM_TYPE_AM_HD); 138 } 139 } 140 141 return pTypes.stream().mapToInt(Integer::intValue).toArray(); 142 } 143 144 private static @NonNull RadioManager.BandDescriptor[] 145 amfmConfigToBands(@Nullable AmFmRegionConfig config) { 146 if (config == null) return new RadioManager.BandDescriptor[0]; 147 148 int len = config.ranges.size(); 149 List<RadioManager.BandDescriptor> bands = new ArrayList<>(len); 150 151 // Just a dummy value. 152 int region = RadioManager.REGION_ITU_1; 153 154 for (AmFmBandRange range : config.ranges) { 155 FrequencyBand bandType = Utils.getBand(range.lowerBound); 156 if (bandType == FrequencyBand.UNKNOWN) { 157 Slog.e(TAG, "Unknown frequency band at " + range.lowerBound + "kHz"); 158 continue; 159 } 160 if (bandType == FrequencyBand.FM) { 161 bands.add(new RadioManager.FmBandDescriptor(region, RadioManager.BAND_FM, 162 range.lowerBound, range.upperBound, range.spacing, 163 164 // TODO(b/69958777): stereo, rds, ta, af, ea 165 true, true, true, true, true 166 )); 167 } else { // AM 168 bands.add(new RadioManager.AmBandDescriptor(region, RadioManager.BAND_AM, 169 range.lowerBound, range.upperBound, range.spacing, 170 171 // TODO(b/69958777): stereo 172 true 173 )); 174 } 175 } 176 177 return bands.toArray(new RadioManager.BandDescriptor[bands.size()]); 178 } 179 180 static @NonNull RadioManager.ModuleProperties 181 propertiesFromHal(int id, @NonNull String serviceName, @NonNull Properties prop, 182 @Nullable AmFmRegionConfig amfmConfig) { 183 Objects.requireNonNull(serviceName); 184 Objects.requireNonNull(prop); 185 186 int[] supportedIdentifierTypes = prop.supportedIdentifierTypes.stream(). 187 mapToInt(Integer::intValue).toArray(); 188 int[] supportedProgramTypes = identifierTypesToProgramTypes(supportedIdentifierTypes); 189 190 return new RadioManager.ModuleProperties( 191 id, 192 serviceName, 193 194 // There is no Class concept in HAL 2.0. 195 RadioManager.CLASS_AM_FM, 196 197 prop.maker, 198 prop.product, 199 prop.version, 200 prop.serial, 201 202 /* HAL 2.0 only supports single tuner and audio source per 203 * HAL implementation instance. */ 204 1, // numTuners 205 1, // numAudioSources 206 false, // isCaptureSupported 207 208 amfmConfigToBands(amfmConfig), 209 false, // isBgScanSupported is deprecated 210 supportedProgramTypes, 211 supportedIdentifierTypes, 212 vendorInfoFromHal(prop.vendorInfo) 213 ); 214 } 215 216 static @NonNull ProgramIdentifier programIdentifierToHal( 217 @NonNull ProgramSelector.Identifier id) { 218 ProgramIdentifier hwId = new ProgramIdentifier(); 219 hwId.type = id.getType(); 220 hwId.value = id.getValue(); 221 return hwId; 222 } 223 224 static @Nullable ProgramSelector.Identifier programIdentifierFromHal( 225 @NonNull ProgramIdentifier id) { 226 if (id.type == IdentifierType.INVALID) return null; 227 return new ProgramSelector.Identifier(id.type, id.value); 228 } 229 230 static @NonNull ProgramSelector programSelectorFromHal( 231 @NonNull android.hardware.broadcastradio.V2_0.ProgramSelector sel) { 232 ProgramSelector.Identifier[] secondaryIds = sel.secondaryIds.stream(). 233 map(id -> Objects.requireNonNull(programIdentifierFromHal(id))). 234 toArray(ProgramSelector.Identifier[]::new); 235 236 return new ProgramSelector( 237 identifierTypeToProgramType(sel.primaryId.type), 238 Objects.requireNonNull(programIdentifierFromHal(sel.primaryId)), 239 secondaryIds, null); 240 } 241 242 static @NonNull RadioManager.ProgramInfo programInfoFromHal(@NonNull ProgramInfo info) { 243 Collection<ProgramSelector.Identifier> relatedContent = info.relatedContent.stream(). 244 map(id -> Objects.requireNonNull(programIdentifierFromHal(id))). 245 collect(Collectors.toList()); 246 247 return new RadioManager.ProgramInfo( 248 programSelectorFromHal(info.selector), 249 programIdentifierFromHal(info.logicallyTunedTo), 250 programIdentifierFromHal(info.physicallyTunedTo), 251 relatedContent, 252 info.infoFlags, 253 info.signalQuality, 254 null, // TODO(b/69860743): metadata 255 vendorInfoFromHal(info.vendorInfo) 256 ); 257 } 258 259 static @NonNull ProgramFilter programFilterToHal(@NonNull ProgramList.Filter filter) { 260 ProgramFilter hwFilter = new ProgramFilter(); 261 262 filter.getIdentifierTypes().stream().forEachOrdered(hwFilter.identifierTypes::add); 263 filter.getIdentifiers().stream().forEachOrdered( 264 id -> hwFilter.identifiers.add(programIdentifierToHal(id))); 265 hwFilter.includeCategories = filter.areCategoriesIncluded(); 266 hwFilter.excludeModifications = filter.areModificationsExcluded(); 267 268 return hwFilter; 269 } 270 271 static @NonNull ProgramList.Chunk programListChunkFromHal(@NonNull ProgramListChunk chunk) { 272 Set<RadioManager.ProgramInfo> modified = chunk.modified.stream(). 273 map(info -> programInfoFromHal(info)).collect(Collectors.toSet()); 274 Set<ProgramSelector.Identifier> removed = chunk.removed.stream(). 275 map(id -> Objects.requireNonNull(programIdentifierFromHal(id))). 276 collect(Collectors.toSet()); 277 278 return new ProgramList.Chunk(chunk.purge, chunk.complete, modified, removed); 279 } 280 281 public static @NonNull android.hardware.radio.Announcement announcementFromHal( 282 @NonNull Announcement hwAnnouncement) { 283 return new android.hardware.radio.Announcement( 284 programSelectorFromHal(hwAnnouncement.selector), 285 hwAnnouncement.type, 286 vendorInfoFromHal(hwAnnouncement.vendorInfo) 287 ); 288 } 289} 290