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 17package com.android.car.radio.platform; 18 19import android.annotation.NonNull; 20import android.annotation.Nullable; 21import android.content.Context; 22import android.graphics.Bitmap; 23import android.hardware.radio.RadioManager; 24import android.hardware.radio.RadioManager.BandDescriptor; 25import android.hardware.radio.RadioTuner; 26import android.os.Handler; 27import android.os.HandlerThread; 28import android.util.Log; 29 30import com.android.car.broadcastradio.support.platform.RadioMetadataExt; 31 32import java.util.ArrayList; 33import java.util.Arrays; 34import java.util.HashMap; 35import java.util.List; 36import java.util.Map; 37import java.util.Objects; 38import 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 */ 45public 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