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