Home | History | Annotate | Download | only in hal2
      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 
     17 package com.android.server.broadcastradio.hal2;
     18 
     19 import android.annotation.NonNull;
     20 import android.annotation.Nullable;
     21 import android.hardware.broadcastradio.V2_0.AmFmBandRange;
     22 import android.hardware.broadcastradio.V2_0.AmFmRegionConfig;
     23 import android.hardware.broadcastradio.V2_0.Announcement;
     24 import android.hardware.broadcastradio.V2_0.DabTableEntry;
     25 import android.hardware.broadcastradio.V2_0.IdentifierType;
     26 import android.hardware.broadcastradio.V2_0.Metadata;
     27 import android.hardware.broadcastradio.V2_0.MetadataKey;
     28 import android.hardware.broadcastradio.V2_0.ProgramFilter;
     29 import android.hardware.broadcastradio.V2_0.ProgramIdentifier;
     30 import android.hardware.broadcastradio.V2_0.ProgramInfo;
     31 import android.hardware.broadcastradio.V2_0.ProgramInfoFlags;
     32 import android.hardware.broadcastradio.V2_0.ProgramListChunk;
     33 import android.hardware.broadcastradio.V2_0.Properties;
     34 import android.hardware.broadcastradio.V2_0.Result;
     35 import android.hardware.broadcastradio.V2_0.VendorKeyValue;
     36 import android.hardware.radio.ProgramList;
     37 import android.hardware.radio.ProgramSelector;
     38 import android.hardware.radio.RadioManager;
     39 import android.hardware.radio.RadioMetadata;
     40 import android.os.ParcelableException;
     41 import android.util.Slog;
     42 
     43 import java.util.ArrayList;
     44 import java.util.Arrays;
     45 import java.util.Collection;
     46 import java.util.Collections;
     47 import java.util.HashMap;
     48 import java.util.HashSet;
     49 import java.util.List;
     50 import java.util.Map;
     51 import java.util.Objects;
     52 import java.util.Set;
     53 import java.util.stream.Collectors;
     54 
     55 class Convert {
     56     private static final String TAG = "BcRadio2Srv.convert";
     57 
     58     static void throwOnError(String action, int result) {
     59         switch (result) {
     60             case Result.OK:
     61                 return;
     62             case Result.UNKNOWN_ERROR:
     63                 throw new ParcelableException(new RuntimeException(action + ": UNKNOWN_ERROR"));
     64             case Result.INTERNAL_ERROR:
     65                 throw new ParcelableException(new RuntimeException(action + ": INTERNAL_ERROR"));
     66             case Result.INVALID_ARGUMENTS:
     67                 throw new IllegalArgumentException(action + ": INVALID_ARGUMENTS");
     68             case Result.INVALID_STATE:
     69                 throw new IllegalStateException(action + ": INVALID_STATE");
     70             case Result.NOT_SUPPORTED:
     71                 throw new UnsupportedOperationException(action + ": NOT_SUPPORTED");
     72             case Result.TIMEOUT:
     73                 throw new ParcelableException(new RuntimeException(action + ": TIMEOUT"));
     74             default:
     75                 throw new ParcelableException(new RuntimeException(
     76                         action + ": unknown error (" + result + ")"));
     77         }
     78     }
     79 
     80     static @NonNull ArrayList<VendorKeyValue>
     81     vendorInfoToHal(@Nullable Map<String, String> info) {
     82         if (info == null) return new ArrayList<>();
     83 
     84         ArrayList<VendorKeyValue> list = new ArrayList<>();
     85         for (Map.Entry<String, String> entry : info.entrySet()) {
     86             VendorKeyValue elem = new VendorKeyValue();
     87             elem.key = entry.getKey();
     88             elem.value = entry.getValue();
     89             if (elem.key == null || elem.value == null) {
     90                 Slog.w(TAG, "VendorKeyValue contains null pointers");
     91                 continue;
     92             }
     93             list.add(elem);
     94         }
     95 
     96         return list;
     97     }
     98 
     99     static @NonNull Map<String, String>
    100     vendorInfoFromHal(@Nullable List<VendorKeyValue> info) {
    101         if (info == null) return Collections.emptyMap();
    102 
    103         Map<String, String> map = new HashMap<>();
    104         for (VendorKeyValue kvp : info) {
    105             if (kvp.key == null || kvp.value == null) {
    106                 Slog.w(TAG, "VendorKeyValue contains null pointers");
    107                 continue;
    108             }
    109             map.put(kvp.key, kvp.value);
    110         }
    111 
    112         return map;
    113     }
    114 
    115     private static @ProgramSelector.ProgramType int identifierTypeToProgramType(
    116             @ProgramSelector.IdentifierType int idType) {
    117         switch (idType) {
    118             case ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY:
    119             case ProgramSelector.IDENTIFIER_TYPE_RDS_PI:
    120                 // TODO(b/69958423): verify AM/FM with frequency range
    121                 return ProgramSelector.PROGRAM_TYPE_FM;
    122             case ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT:
    123                 // TODO(b/69958423): verify AM/FM with frequency range
    124                 return ProgramSelector.PROGRAM_TYPE_FM_HD;
    125             case ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC:
    126             case ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE:
    127             case ProgramSelector.IDENTIFIER_TYPE_DAB_SCID:
    128             case ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY:
    129                 return ProgramSelector.PROGRAM_TYPE_DAB;
    130             case ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID:
    131             case ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY:
    132                 return ProgramSelector.PROGRAM_TYPE_DRMO;
    133             case ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID:
    134             case ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL:
    135                 return ProgramSelector.PROGRAM_TYPE_SXM;
    136         }
    137         if (idType >= ProgramSelector.IDENTIFIER_TYPE_VENDOR_PRIMARY_START
    138                 && idType <= ProgramSelector.IDENTIFIER_TYPE_VENDOR_PRIMARY_END) {
    139             return idType;
    140         }
    141         return ProgramSelector.PROGRAM_TYPE_INVALID;
    142     }
    143 
    144     private static @NonNull int[]
    145     identifierTypesToProgramTypes(@NonNull int[] idTypes) {
    146         Set<Integer> pTypes = new HashSet<>();
    147 
    148         for (int idType : idTypes) {
    149             int pType = identifierTypeToProgramType(idType);
    150 
    151             if (pType == ProgramSelector.PROGRAM_TYPE_INVALID) continue;
    152 
    153             pTypes.add(pType);
    154             if (pType == ProgramSelector.PROGRAM_TYPE_FM) {
    155                 // TODO(b/69958423): verify AM/FM with region info
    156                 pTypes.add(ProgramSelector.PROGRAM_TYPE_AM);
    157             }
    158             if (pType == ProgramSelector.PROGRAM_TYPE_FM_HD) {
    159                 // TODO(b/69958423): verify AM/FM with region info
    160                 pTypes.add(ProgramSelector.PROGRAM_TYPE_AM_HD);
    161             }
    162         }
    163 
    164         return pTypes.stream().mapToInt(Integer::intValue).toArray();
    165     }
    166 
    167     private static @NonNull RadioManager.BandDescriptor[]
    168     amfmConfigToBands(@Nullable AmFmRegionConfig config) {
    169         if (config == null) return new RadioManager.BandDescriptor[0];
    170 
    171         int len = config.ranges.size();
    172         List<RadioManager.BandDescriptor> bands = new ArrayList<>(len);
    173 
    174         // Just a dummy value.
    175         int region = RadioManager.REGION_ITU_1;
    176 
    177         for (AmFmBandRange range : config.ranges) {
    178             FrequencyBand bandType = Utils.getBand(range.lowerBound);
    179             if (bandType == FrequencyBand.UNKNOWN) {
    180                 Slog.e(TAG, "Unknown frequency band at " + range.lowerBound + "kHz");
    181                 continue;
    182             }
    183             if (bandType == FrequencyBand.FM) {
    184                 bands.add(new RadioManager.FmBandDescriptor(region, RadioManager.BAND_FM,
    185                     range.lowerBound, range.upperBound, range.spacing,
    186 
    187                     // TODO(b/69958777): stereo, rds, ta, af, ea
    188                     true, true, true, true, true
    189                 ));
    190             } else {  // AM
    191                 bands.add(new RadioManager.AmBandDescriptor(region, RadioManager.BAND_AM,
    192                     range.lowerBound, range.upperBound, range.spacing,
    193 
    194                     // TODO(b/69958777): stereo
    195                     true
    196                 ));
    197             }
    198         }
    199 
    200         return bands.toArray(new RadioManager.BandDescriptor[bands.size()]);
    201     }
    202 
    203     private static @Nullable Map<String, Integer> dabConfigFromHal(
    204             @Nullable List<DabTableEntry> config) {
    205         if (config == null) return null;
    206         return config.stream().collect(Collectors.toMap(e -> e.label, e -> e.frequency));
    207     }
    208 
    209     static @NonNull RadioManager.ModuleProperties
    210     propertiesFromHal(int id, @NonNull String serviceName, @NonNull Properties prop,
    211             @Nullable AmFmRegionConfig amfmConfig, @Nullable List<DabTableEntry> dabConfig) {
    212         Objects.requireNonNull(serviceName);
    213         Objects.requireNonNull(prop);
    214 
    215         int[] supportedIdentifierTypes = prop.supportedIdentifierTypes.stream().
    216                 mapToInt(Integer::intValue).toArray();
    217         int[] supportedProgramTypes = identifierTypesToProgramTypes(supportedIdentifierTypes);
    218 
    219         return new RadioManager.ModuleProperties(
    220                 id,
    221                 serviceName,
    222 
    223                 // There is no Class concept in HAL 2.0.
    224                 RadioManager.CLASS_AM_FM,
    225 
    226                 prop.maker,
    227                 prop.product,
    228                 prop.version,
    229                 prop.serial,
    230 
    231                 /* HAL 2.0 only supports single tuner and audio source per
    232                  * HAL implementation instance. */
    233                 1,      // numTuners
    234                 1,      // numAudioSources
    235                 false,  // isInitializationRequired
    236                 false,  // isCaptureSupported
    237 
    238                 amfmConfigToBands(amfmConfig),
    239                 true,  // isBgScanSupported is deprecated
    240                 supportedProgramTypes,
    241                 supportedIdentifierTypes,
    242                 dabConfigFromHal(dabConfig),
    243                 vendorInfoFromHal(prop.vendorInfo)
    244         );
    245     }
    246 
    247     static void programIdentifierToHal(@NonNull ProgramIdentifier hwId,
    248             @NonNull ProgramSelector.Identifier id) {
    249         hwId.type = id.getType();
    250         hwId.value = id.getValue();
    251     }
    252 
    253     static @NonNull ProgramIdentifier programIdentifierToHal(
    254             @NonNull ProgramSelector.Identifier id) {
    255         ProgramIdentifier hwId = new ProgramIdentifier();
    256         programIdentifierToHal(hwId, id);
    257         return hwId;
    258     }
    259 
    260     static @Nullable ProgramSelector.Identifier programIdentifierFromHal(
    261             @NonNull ProgramIdentifier id) {
    262         if (id.type == IdentifierType.INVALID) return null;
    263         return new ProgramSelector.Identifier(id.type, id.value);
    264     }
    265 
    266     static @NonNull android.hardware.broadcastradio.V2_0.ProgramSelector programSelectorToHal(
    267             @NonNull ProgramSelector sel) {
    268         android.hardware.broadcastradio.V2_0.ProgramSelector hwSel =
    269             new android.hardware.broadcastradio.V2_0.ProgramSelector();
    270 
    271         programIdentifierToHal(hwSel.primaryId, sel.getPrimaryId());
    272         Arrays.stream(sel.getSecondaryIds()).map(Convert::programIdentifierToHal).
    273                 forEachOrdered(hwSel.secondaryIds::add);
    274 
    275         return hwSel;
    276     }
    277 
    278     static @NonNull ProgramSelector programSelectorFromHal(
    279             @NonNull android.hardware.broadcastradio.V2_0.ProgramSelector sel) {
    280         ProgramSelector.Identifier[] secondaryIds = sel.secondaryIds.stream().
    281                 map(Convert::programIdentifierFromHal).map(Objects::requireNonNull).
    282                 toArray(ProgramSelector.Identifier[]::new);
    283 
    284         return new ProgramSelector(
    285                 identifierTypeToProgramType(sel.primaryId.type),
    286                 Objects.requireNonNull(programIdentifierFromHal(sel.primaryId)),
    287                 secondaryIds, null);
    288     }
    289 
    290     private enum MetadataType {
    291         INT, STRING
    292     }
    293 
    294     private static class MetadataDef {
    295         private MetadataType type;
    296         private String key;
    297         private MetadataDef(MetadataType type, String key) {
    298             this.type = type;
    299             this.key = key;
    300         }
    301     }
    302 
    303     private static final Map<Integer, MetadataDef> metadataKeys;
    304     static {
    305         metadataKeys = new HashMap<>();
    306         metadataKeys.put(MetadataKey.RDS_PS, new MetadataDef(
    307                 MetadataType.STRING, RadioMetadata.METADATA_KEY_RDS_PS));
    308         metadataKeys.put(MetadataKey.RDS_PTY, new MetadataDef(
    309                 MetadataType.INT, RadioMetadata.METADATA_KEY_RDS_PTY));
    310         metadataKeys.put(MetadataKey.RBDS_PTY, new MetadataDef(
    311                 MetadataType.INT, RadioMetadata.METADATA_KEY_RBDS_PTY));
    312         metadataKeys.put(MetadataKey.RDS_RT, new MetadataDef(
    313                 MetadataType.STRING, RadioMetadata.METADATA_KEY_RDS_RT));
    314         metadataKeys.put(MetadataKey.SONG_TITLE, new MetadataDef(
    315                 MetadataType.STRING, RadioMetadata.METADATA_KEY_TITLE));
    316         metadataKeys.put(MetadataKey.SONG_ARTIST, new MetadataDef(
    317                 MetadataType.STRING, RadioMetadata.METADATA_KEY_ARTIST));
    318         metadataKeys.put(MetadataKey.SONG_ALBUM, new MetadataDef(
    319                 MetadataType.STRING, RadioMetadata.METADATA_KEY_ALBUM));
    320         metadataKeys.put(MetadataKey.STATION_ICON, new MetadataDef(
    321                 MetadataType.INT, RadioMetadata.METADATA_KEY_ICON));
    322         metadataKeys.put(MetadataKey.ALBUM_ART, new MetadataDef(
    323                 MetadataType.INT, RadioMetadata.METADATA_KEY_ART));
    324         metadataKeys.put(MetadataKey.PROGRAM_NAME, new MetadataDef(
    325                 MetadataType.STRING, RadioMetadata.METADATA_KEY_PROGRAM_NAME));
    326         metadataKeys.put(MetadataKey.DAB_ENSEMBLE_NAME, new MetadataDef(
    327                 MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_ENSEMBLE_NAME));
    328         metadataKeys.put(MetadataKey.DAB_ENSEMBLE_NAME_SHORT, new MetadataDef(
    329                 MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_ENSEMBLE_NAME_SHORT));
    330         metadataKeys.put(MetadataKey.DAB_SERVICE_NAME, new MetadataDef(
    331                 MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_SERVICE_NAME));
    332         metadataKeys.put(MetadataKey.DAB_SERVICE_NAME_SHORT, new MetadataDef(
    333                 MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_SERVICE_NAME_SHORT));
    334         metadataKeys.put(MetadataKey.DAB_COMPONENT_NAME, new MetadataDef(
    335                 MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_COMPONENT_NAME));
    336         metadataKeys.put(MetadataKey.DAB_COMPONENT_NAME_SHORT, new MetadataDef(
    337                 MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_COMPONENT_NAME_SHORT));
    338     }
    339 
    340     private static @NonNull RadioMetadata metadataFromHal(@NonNull ArrayList<Metadata> meta) {
    341         RadioMetadata.Builder builder = new RadioMetadata.Builder();
    342 
    343         for (Metadata entry : meta) {
    344             MetadataDef keyDef = metadataKeys.get(entry.key);
    345             if (keyDef == null) {
    346                 Slog.i(TAG, "Ignored unknown metadata entry: " + MetadataKey.toString(entry.key));
    347                 continue;
    348             }
    349             if (keyDef.type == MetadataType.STRING) {
    350                 builder.putString(keyDef.key, entry.stringValue);
    351             } else {  // MetadataType.INT
    352                 /* Current java API use 32-bit values for int metadata,
    353                  * but we might change it in the future */
    354                 builder.putInt(keyDef.key, (int)entry.intValue);
    355             }
    356         }
    357 
    358         return builder.build();
    359     }
    360 
    361     static @NonNull RadioManager.ProgramInfo programInfoFromHal(@NonNull ProgramInfo info) {
    362         Collection<ProgramSelector.Identifier> relatedContent = info.relatedContent.stream().
    363                 map(id -> Objects.requireNonNull(programIdentifierFromHal(id))).
    364                 collect(Collectors.toList());
    365 
    366         return new RadioManager.ProgramInfo(
    367                 programSelectorFromHal(info.selector),
    368                 programIdentifierFromHal(info.logicallyTunedTo),
    369                 programIdentifierFromHal(info.physicallyTunedTo),
    370                 relatedContent,
    371                 info.infoFlags,
    372                 info.signalQuality,
    373                 metadataFromHal(info.metadata),
    374                 vendorInfoFromHal(info.vendorInfo)
    375         );
    376     }
    377 
    378     static @NonNull ProgramFilter programFilterToHal(@Nullable ProgramList.Filter filter) {
    379         if (filter == null) filter = new ProgramList.Filter();
    380 
    381         ProgramFilter hwFilter = new ProgramFilter();
    382 
    383         filter.getIdentifierTypes().stream().forEachOrdered(hwFilter.identifierTypes::add);
    384         filter.getIdentifiers().stream().forEachOrdered(
    385             id -> hwFilter.identifiers.add(programIdentifierToHal(id)));
    386         hwFilter.includeCategories = filter.areCategoriesIncluded();
    387         hwFilter.excludeModifications = filter.areModificationsExcluded();
    388 
    389         return hwFilter;
    390     }
    391 
    392     static @NonNull ProgramList.Chunk programListChunkFromHal(@NonNull ProgramListChunk chunk) {
    393         Set<RadioManager.ProgramInfo> modified = chunk.modified.stream().
    394                 map(info -> programInfoFromHal(info)).collect(Collectors.toSet());
    395         Set<ProgramSelector.Identifier> removed = chunk.removed.stream().
    396                 map(id -> Objects.requireNonNull(programIdentifierFromHal(id))).
    397                 collect(Collectors.toSet());
    398 
    399         return new ProgramList.Chunk(chunk.purge, chunk.complete, modified, removed);
    400     }
    401 
    402     public static @NonNull android.hardware.radio.Announcement announcementFromHal(
    403             @NonNull Announcement hwAnnouncement) {
    404         return new android.hardware.radio.Announcement(
    405             programSelectorFromHal(hwAnnouncement.selector),
    406             hwAnnouncement.type,
    407             vendorInfoFromHal(hwAnnouncement.vendorInfo)
    408         );
    409     }
    410 
    411     static <T> @Nullable ArrayList<T> listToArrayList(@Nullable List<T> list) {
    412         if (list == null) return null;
    413         if (list instanceof ArrayList) return (ArrayList) list;
    414         return new ArrayList<>(list);
    415     }
    416 }
    417