Home | History | Annotate | Download | only in platform
      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.platform;
     18 
     19 import android.annotation.NonNull;
     20 import android.annotation.Nullable;
     21 import android.content.Context;
     22 import android.graphics.Bitmap;
     23 import android.hardware.radio.RadioManager;
     24 import android.hardware.radio.RadioManager.BandDescriptor;
     25 import android.hardware.radio.RadioTuner;
     26 import android.os.Handler;
     27 import android.os.HandlerThread;
     28 import android.util.Log;
     29 
     30 import com.android.car.broadcastradio.support.platform.RadioMetadataExt;
     31 
     32 import java.util.ArrayList;
     33 import java.util.Arrays;
     34 import java.util.HashMap;
     35 import java.util.List;
     36 import java.util.Map;
     37 import java.util.Objects;
     38 import java.util.stream.Collectors;
     39 
     40 /**
     41  * Proposed extensions to android.hardware.radio.RadioManager.
     42  *
     43  * They might eventually get pushed to the framework.
     44  */
     45 public class RadioManagerExt {
     46     private static final String TAG = "BcRadioApp.mgrext";
     47 
     48     // For now, we open first radio module only.
     49     private static final int HARDCODED_MODULE_INDEX = 0;
     50 
     51     private final Object mLock = new Object();
     52 
     53     private final HandlerThread mCallbackHandlerThread = new HandlerThread("BcRadioApp.cbhandler");
     54 
     55     private final @NonNull RadioManager mRadioManager;
     56     private final RadioTunerExt mRadioTunerExt;
     57     private List<RadioManager.ModuleProperties> mModules;
     58     private @Nullable List<BandDescriptor> mAmFmRegionConfig;
     59 
     60     public RadioManagerExt(@NonNull Context ctx) {
     61         mRadioManager = (RadioManager)ctx.getSystemService(Context.RADIO_SERVICE);
     62         Objects.requireNonNull(mRadioManager, "RadioManager could not be loaded");
     63         mRadioTunerExt = new RadioTunerExt(ctx);
     64         mCallbackHandlerThread.start();
     65     }
     66 
     67     public RadioTunerExt getRadioTunerExt() {
     68         return mRadioTunerExt;
     69     }
     70 
     71     /* Select only one region. HAL 2.x moves region selection responsibility from the app to the
     72      * Broadcast Radio service, so we won't implement region selection based on bands in the app.
     73      */
     74     private @Nullable List<BandDescriptor> reduceAmFmBands(@Nullable BandDescriptor[] bands) {
     75         if (bands == null || bands.length == 0) return null;
     76         int region = bands[0].getRegion();
     77         Log.d(TAG, "Auto-selecting region " + region);
     78 
     79         return Arrays.stream(bands).filter(band -> band.getRegion() == region).
     80                 collect(Collectors.toList());
     81     }
     82 
     83     private void initModules() {
     84         synchronized (mLock) {
     85             if (mModules != null) return;
     86 
     87             mModules = new ArrayList<>();
     88             int status = mRadioManager.listModules(mModules);
     89             if (status != RadioManager.STATUS_OK) {
     90                 Log.w(TAG, "Couldn't get radio module list: " + status);
     91                 return;
     92             }
     93 
     94             if (mModules.size() == 0) {
     95                 Log.i(TAG, "No radio modules on this device");
     96                 return;
     97             }
     98 
     99             RadioManager.ModuleProperties module = mModules.get(HARDCODED_MODULE_INDEX);
    100             mAmFmRegionConfig = reduceAmFmBands(module.getBands());
    101         }
    102     }
    103 
    104     public @Nullable RadioTuner openSession(RadioTuner.Callback callback, Handler handler) {
    105         Log.i(TAG, "Opening broadcast radio session...");
    106 
    107         initModules();
    108         if (mModules.size() == 0) return null;
    109 
    110         /* We won't need custom default wrapper when we push these proposed extensions to the
    111          * framework; this is solely to avoid deadlock on onConfigurationChanged callback versus
    112          * waitForInitialization.
    113          */
    114         Handler hwHandler = new Handler(mCallbackHandlerThread.getLooper());
    115 
    116         RadioManager.ModuleProperties module = mModules.get(HARDCODED_MODULE_INDEX);
    117         TunerCallbackAdapterExt cbExt = new TunerCallbackAdapterExt(callback, handler);
    118 
    119         RadioTuner tuner = mRadioManager.openTuner(
    120                 module.getId(),
    121                 null,  // BandConfig - let the service automatically select one.
    122                 true,  // withAudio
    123                 cbExt, hwHandler);
    124         mSessions.put(module.getId(), tuner);
    125         if (tuner == null) return null;
    126         RadioMetadataExt.setModuleId(module.getId());
    127 
    128         if (module.isInitializationRequired()) {
    129             if (!cbExt.waitForInitialization()) {
    130                 Log.w(TAG, "Timed out waiting for tuner initialization");
    131                 tuner.close();
    132                 return null;
    133             }
    134         }
    135 
    136         return tuner;
    137     }
    138 
    139     public @Nullable List<BandDescriptor> getAmFmRegionConfig() {
    140         initModules();
    141         return mAmFmRegionConfig;
    142     }
    143 
    144     /* This won't be necessary when we push this code to the framework,
    145      * as we really need only module references. */
    146     private static Map<Integer, RadioTuner> mSessions = new HashMap<>();
    147 
    148     public @Nullable Bitmap getMetadataImage(long globalId) {
    149         if (globalId == 0) return null;
    150 
    151         int moduleId = (int)(globalId >>> 32);
    152         int localId = (int)(globalId & 0xFFFFFFFF);
    153 
    154         RadioTuner tuner = mSessions.get(moduleId);
    155         if (tuner == null) return null;
    156 
    157         return tuner.getMetadataImage(localId);
    158     }
    159 }
    160