blob: 60a927c5695919bd314b74a4db91c8c94f5e1972 [file] [log] [blame]
Tomasz Wasilczykd65b3ca2017-12-13 08:26:25 -08001/**
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;
Tomasz Wasilczykf58305d2017-12-28 14:03:15 -080021import android.hardware.broadcastradio.V2_0.AmFmBandRange;
22import android.hardware.broadcastradio.V2_0.AmFmRegionConfig;
Tomasz Wasilczyk436128f2018-01-08 16:46:09 -080023import android.hardware.broadcastradio.V2_0.ProgramFilter;
24import android.hardware.broadcastradio.V2_0.ProgramIdentifier;
25import android.hardware.broadcastradio.V2_0.ProgramInfo;
26import android.hardware.broadcastradio.V2_0.ProgramInfoFlags;
27import android.hardware.broadcastradio.V2_0.ProgramListChunk;
Tomasz Wasilczykd65b3ca2017-12-13 08:26:25 -080028import android.hardware.broadcastradio.V2_0.Properties;
Tomasz Wasilczykf58305d2017-12-28 14:03:15 -080029import android.hardware.broadcastradio.V2_0.Result;
Tomasz Wasilczykd65b3ca2017-12-13 08:26:25 -080030import android.hardware.broadcastradio.V2_0.VendorKeyValue;
Tomasz Wasilczyk436128f2018-01-08 16:46:09 -080031import android.hardware.radio.ProgramList;
Tomasz Wasilczykd65b3ca2017-12-13 08:26:25 -080032import android.hardware.radio.ProgramSelector;
33import android.hardware.radio.RadioManager;
Tomasz Wasilczykf58305d2017-12-28 14:03:15 -080034import android.os.ParcelableException;
Tomasz Wasilczykd65b3ca2017-12-13 08:26:25 -080035import android.util.Slog;
36
Tomasz Wasilczykf58305d2017-12-28 14:03:15 -080037import java.util.ArrayList;
Tomasz Wasilczyk436128f2018-01-08 16:46:09 -080038import java.util.Arrays;
Tomasz Wasilczykd65b3ca2017-12-13 08:26:25 -080039import java.util.Collections;
40import java.util.HashMap;
41import java.util.HashSet;
42import java.util.List;
43import java.util.Map;
44import java.util.Objects;
45import java.util.Set;
Tomasz Wasilczyk436128f2018-01-08 16:46:09 -080046import java.util.stream.Collectors;
Tomasz Wasilczykd65b3ca2017-12-13 08:26:25 -080047
48class Convert {
49 private static final String TAG = "BcRadio2Srv.convert";
50
Tomasz Wasilczykf58305d2017-12-28 14:03:15 -080051 static void throwOnError(String action, int result) {
52 switch (result) {
53 case Result.OK:
54 return;
55 case Result.UNKNOWN_ERROR:
56 throw new ParcelableException(new RuntimeException(action + ": UNKNOWN_ERROR"));
57 case Result.INTERNAL_ERROR:
58 throw new ParcelableException(new RuntimeException(action + ": INTERNAL_ERROR"));
59 case Result.INVALID_ARGUMENTS:
60 throw new IllegalArgumentException(action + ": INVALID_ARGUMENTS");
61 case Result.INVALID_STATE:
62 throw new IllegalStateException(action + ": INVALID_STATE");
63 case Result.NOT_SUPPORTED:
64 throw new UnsupportedOperationException(action + ": NOT_SUPPORTED");
65 case Result.TIMEOUT:
66 throw new ParcelableException(new RuntimeException(action + ": TIMEOUT"));
67 default:
68 throw new ParcelableException(new RuntimeException(
69 action + ": unknown error (" + result + ")"));
70 }
71 }
72
Tomasz Wasilczykd65b3ca2017-12-13 08:26:25 -080073 private static @NonNull Map<String, String>
74 vendorInfoFromHal(@Nullable List<VendorKeyValue> info) {
75 if (info == null) return Collections.emptyMap();
76
77 Map<String, String> map = new HashMap<>();
78 for (VendorKeyValue kvp : info) {
79 if (kvp.key == null || kvp.value == null) {
80 Slog.w(TAG, "VendorKeyValue contains null pointers");
81 continue;
82 }
83 map.put(kvp.key, kvp.value);
84 }
85
86 return map;
87 }
88
Tomasz Wasilczyk436128f2018-01-08 16:46:09 -080089 private static @ProgramSelector.ProgramType int identifierTypeToProgramType(
90 @ProgramSelector.IdentifierType int idType) {
91 switch (idType) {
92 case ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY:
93 case ProgramSelector.IDENTIFIER_TYPE_RDS_PI:
94 // TODO(b/69958423): verify AM/FM with frequency range
95 return ProgramSelector.PROGRAM_TYPE_FM;
96 case ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT:
97 // TODO(b/69958423): verify AM/FM with frequency range
98 return ProgramSelector.PROGRAM_TYPE_FM_HD;
99 case ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC:
100 case ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE:
101 case ProgramSelector.IDENTIFIER_TYPE_DAB_SCID:
102 case ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY:
103 return ProgramSelector.PROGRAM_TYPE_DAB;
104 case ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID:
105 case ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY:
106 return ProgramSelector.PROGRAM_TYPE_DRMO;
107 case ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID:
108 case ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL:
109 return ProgramSelector.PROGRAM_TYPE_SXM;
110 }
111 if (idType >= ProgramSelector.IDENTIFIER_TYPE_VENDOR_PRIMARY_START
112 && idType <= ProgramSelector.IDENTIFIER_TYPE_VENDOR_PRIMARY_END) {
113 return idType;
114 }
115 return ProgramSelector.PROGRAM_TYPE_INVALID;
116 }
117
Tomasz Wasilczykd65b3ca2017-12-13 08:26:25 -0800118 private static @NonNull int[]
119 identifierTypesToProgramTypes(@NonNull int[] idTypes) {
120 Set<Integer> pTypes = new HashSet<>();
121
122 for (int idType : idTypes) {
Tomasz Wasilczyk436128f2018-01-08 16:46:09 -0800123 int pType = identifierTypeToProgramType(idType);
124
125 if (pType == ProgramSelector.PROGRAM_TYPE_INVALID) continue;
126
127 pTypes.add(pType);
128 if (pType == ProgramSelector.PROGRAM_TYPE_FM) {
129 // TODO(b/69958423): verify AM/FM with region info
130 pTypes.add(ProgramSelector.PROGRAM_TYPE_AM);
Tomasz Wasilczykd65b3ca2017-12-13 08:26:25 -0800131 }
Tomasz Wasilczyk436128f2018-01-08 16:46:09 -0800132 if (pType == ProgramSelector.PROGRAM_TYPE_FM_HD) {
133 // TODO(b/69958423): verify AM/FM with region info
134 pTypes.add(ProgramSelector.PROGRAM_TYPE_AM_HD);
Tomasz Wasilczykd65b3ca2017-12-13 08:26:25 -0800135 }
136 }
137
138 return pTypes.stream().mapToInt(Integer::intValue).toArray();
139 }
140
Tomasz Wasilczykf58305d2017-12-28 14:03:15 -0800141 private static @NonNull RadioManager.BandDescriptor[]
142 amfmConfigToBands(@Nullable AmFmRegionConfig config) {
143 if (config == null) return new RadioManager.BandDescriptor[0];
Tomasz Wasilczykd65b3ca2017-12-13 08:26:25 -0800144
Tomasz Wasilczykf58305d2017-12-28 14:03:15 -0800145 int len = config.ranges.size();
146 List<RadioManager.BandDescriptor> bands = new ArrayList<>(len);
147
148 // Just a dummy value.
149 int region = RadioManager.REGION_ITU_1;
150
151 for (AmFmBandRange range : config.ranges) {
152 FrequencyBand bandType = Utils.getBand(range.lowerBound);
153 if (bandType == FrequencyBand.UNKNOWN) {
154 Slog.e(TAG, "Unknown frequency band at " + range.lowerBound + "kHz");
155 continue;
156 }
157 if (bandType == FrequencyBand.FM) {
158 bands.add(new RadioManager.FmBandDescriptor(region, RadioManager.BAND_FM,
159 range.lowerBound, range.upperBound, range.spacing,
160
161 // TODO(b/69958777): stereo, rds, ta, af, ea
162 true, true, true, true, true
163 ));
164 } else { // AM
165 bands.add(new RadioManager.AmBandDescriptor(region, RadioManager.BAND_AM,
166 range.lowerBound, range.upperBound, range.spacing,
167
168 // TODO(b/69958777): stereo
169 true
170 ));
171 }
172 }
173
174 return bands.toArray(new RadioManager.BandDescriptor[bands.size()]);
175 }
176
177 static @NonNull RadioManager.ModuleProperties
178 propertiesFromHal(int id, @NonNull String serviceName, @NonNull Properties prop,
179 @Nullable AmFmRegionConfig amfmConfig) {
180 Objects.requireNonNull(serviceName);
181 Objects.requireNonNull(prop);
Tomasz Wasilczykd65b3ca2017-12-13 08:26:25 -0800182
183 int[] supportedIdentifierTypes = prop.supportedIdentifierTypes.stream().
184 mapToInt(Integer::intValue).toArray();
185 int[] supportedProgramTypes = identifierTypesToProgramTypes(supportedIdentifierTypes);
186
187 return new RadioManager.ModuleProperties(
188 id,
189 serviceName,
190
191 // There is no Class concept in HAL 2.0.
192 RadioManager.CLASS_AM_FM,
193
194 prop.maker,
195 prop.product,
196 prop.version,
197 prop.serial,
198
199 /* HAL 2.0 only supports single tuner and audio source per
200 * HAL implementation instance. */
201 1, // numTuners
202 1, // numAudioSources
203 false, // isCaptureSupported
204
Tomasz Wasilczykf58305d2017-12-28 14:03:15 -0800205 amfmConfigToBands(amfmConfig),
Tomasz Wasilczykca98cde2018-01-04 12:26:40 -0800206 false, // isBgScanSupported is deprecated
Tomasz Wasilczykd65b3ca2017-12-13 08:26:25 -0800207 supportedProgramTypes,
208 supportedIdentifierTypes,
Tomasz Wasilczyk436128f2018-01-08 16:46:09 -0800209 vendorInfoFromHal(prop.vendorInfo)
210 );
211 }
212
213 static @NonNull ProgramIdentifier programIdentifierToHal(
214 @NonNull ProgramSelector.Identifier id) {
215 ProgramIdentifier hwId = new ProgramIdentifier();
216 hwId.type = id.getType();
217 hwId.value = id.getValue();
218 return hwId;
219 }
220
221 static @NonNull ProgramSelector.Identifier programIdentifierFromHal(@NonNull ProgramIdentifier id) {
222 return new ProgramSelector.Identifier(id.type, id.value);
223 }
224
225 static @NonNull ProgramSelector programSelectorFromHal(
226 @NonNull android.hardware.broadcastradio.V2_0.ProgramSelector sel) {
227 ProgramSelector.Identifier[] secondaryIds = sel.secondaryIds.stream().map(
228 id -> programIdentifierFromHal(id)).toArray(ProgramSelector.Identifier[]::new);
229
230 return new ProgramSelector(
231 identifierTypeToProgramType(sel.primaryId.type),
232 programIdentifierFromHal(sel.primaryId),
233 secondaryIds, null);
234 }
235
236 static @NonNull RadioManager.ProgramInfo programInfoFromHal(@NonNull ProgramInfo info) {
237 return new RadioManager.ProgramInfo(
238 programSelectorFromHal(info.selector),
239 (info.infoFlags & ProgramInfoFlags.TUNED) != 0,
240 (info.infoFlags & ProgramInfoFlags.STEREO) != 0,
241 false, // TODO(b/69860743): digital
242 info.signalQuality,
243 null, // TODO(b/69860743): metadata
244 info.infoFlags,
245 vendorInfoFromHal(info.vendorInfo)
246 );
247 }
248
249 static @NonNull ProgramFilter programFilterToHal(@NonNull ProgramList.Filter filter) {
250 ProgramFilter hwFilter = new ProgramFilter();
251
252 filter.getIdentifierTypes().stream().forEachOrdered(hwFilter.identifierTypes::add);
253 filter.getIdentifiers().stream().forEachOrdered(
254 id -> hwFilter.identifiers.add(programIdentifierToHal(id)));
255 hwFilter.includeCategories = filter.areCategoriesIncluded();
256 hwFilter.excludeModifications = filter.areModificationsExcluded();
257
258 return hwFilter;
259 }
260
261 static @NonNull ProgramList.Chunk programListChunkFromHal(@NonNull ProgramListChunk chunk) {
262 Set<RadioManager.ProgramInfo> modified = chunk.modified.stream().map(
263 info -> programInfoFromHal(info)).collect(Collectors.toSet());
264 Set<ProgramSelector.Identifier> removed = chunk.removed.stream().map(
265 id -> programIdentifierFromHal(id)).collect(Collectors.toSet());
266
267 return new ProgramList.Chunk(chunk.purge, chunk.complete, modified, removed);
Tomasz Wasilczykd65b3ca2017-12-13 08:26:25 -0800268 }
269}