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.car.radio.platform; 18 19 import android.hardware.radio.ProgramSelector; 20 import android.hardware.radio.RadioManager; 21 import android.hardware.radio.RadioMetadata; 22 import android.hardware.radio.RadioTuner; 23 import android.os.Handler; 24 import android.os.Looper; 25 26 import androidx.annotation.Nullable; 27 28 import com.android.car.radio.util.Log; 29 import com.android.internal.annotations.GuardedBy; 30 31 import java.util.Map; 32 import java.util.Objects; 33 import java.util.concurrent.atomic.AtomicReference; 34 35 /** 36 * Proposed extensions to android.hardware.radio.TunerCallbackAdapter. 37 * 38 * This class is not compatible with the original at all (because they operate 39 * on different callback types), it just represents a proposed feature 40 * extensions. 41 * 42 * They might eventually get pushed to the framework. 43 */ 44 final class TunerCallbackAdapterExt extends RadioTuner.Callback { 45 private static final String TAG = "BcRadioApp.tunerext"; 46 private static final int INIT_TIMEOUT_MS = 10000; // 10s 47 48 private final Object mInitLock = new Object(); 49 private boolean mIsInitialized = false; 50 51 private final RadioTuner.Callback mCallback; 52 private final Handler mHandler; 53 54 private final AtomicReference<TuneFailedCallback> mTuneFailedCallback = new AtomicReference<>(); 55 private final Object mProgramInfoLock = new Object(); 56 @GuardedBy("mProgramInfoLock") 57 private ProgramInfoCallback mProgramInfoCallback; 58 @GuardedBy("mProgramInfoLock") 59 private RadioManager.ProgramInfo mCachedProgramInfo; 60 61 interface TuneFailedCallback { onTuneFailed(int result, @Nullable ProgramSelector selector)62 void onTuneFailed(int result, @Nullable ProgramSelector selector); 63 } 64 65 interface ProgramInfoCallback { onProgramInfoChanged(RadioManager.ProgramInfo info)66 void onProgramInfoChanged(RadioManager.ProgramInfo info); 67 } 68 TunerCallbackAdapterExt(RadioTuner.Callback callback, @Nullable Handler handler)69 TunerCallbackAdapterExt(RadioTuner.Callback callback, @Nullable Handler handler) { 70 mCallback = Objects.requireNonNull(callback, "Callback cannot be null"); 71 if (handler == null) { 72 mHandler = new Handler(Looper.getMainLooper()); 73 } else { 74 mHandler = handler; 75 } 76 } 77 waitForInitialization()78 public boolean waitForInitialization() { 79 synchronized (mInitLock) { 80 if (mIsInitialized) return true; 81 try { 82 mInitLock.wait(INIT_TIMEOUT_MS); 83 } catch (InterruptedException ex) { 84 // ignore the exception, as we check mIsInitialized anyway 85 } 86 return mIsInitialized; 87 } 88 } 89 setTuneFailedCallback(TuneFailedCallback cb)90 void setTuneFailedCallback(TuneFailedCallback cb) { 91 mTuneFailedCallback.set(cb); 92 } 93 setProgramInfoCallback(ProgramInfoCallback cb)94 void setProgramInfoCallback(ProgramInfoCallback cb) { 95 synchronized (mProgramInfoLock) { 96 mProgramInfoCallback = cb; 97 if (mProgramInfoCallback != null && mCachedProgramInfo != null) { 98 Log.d(TAG, "Invoking callback with cached ProgramInfo"); 99 mProgramInfoCallback.onProgramInfoChanged(mCachedProgramInfo); 100 mCachedProgramInfo = null; 101 } 102 } 103 } 104 105 @Override onError(int status)106 public void onError(int status) { 107 mHandler.post(() -> mCallback.onError(status)); 108 } 109 110 @Override onTuneFailed(int result, @Nullable ProgramSelector selector)111 public void onTuneFailed(int result, @Nullable ProgramSelector selector) { 112 TuneFailedCallback cb = mTuneFailedCallback.get(); 113 if (cb != null) { 114 cb.onTuneFailed(result, selector); 115 } 116 mHandler.post(() -> mCallback.onTuneFailed(result, selector)); 117 } 118 119 @Override onConfigurationChanged(RadioManager.BandConfig config)120 public void onConfigurationChanged(RadioManager.BandConfig config) { 121 mHandler.post(() -> mCallback.onConfigurationChanged(config)); 122 if (mIsInitialized) return; 123 synchronized (mInitLock) { 124 mIsInitialized = true; 125 mInitLock.notifyAll(); 126 } 127 } 128 onProgramInfoChanged(RadioManager.ProgramInfo info)129 public void onProgramInfoChanged(RadioManager.ProgramInfo info) { 130 synchronized (mProgramInfoLock) { 131 if (mProgramInfoCallback == null) { 132 // Cache the ProgramInfo until the callback is set. This workaround is needed 133 // because a TunerCallbackAdapterExt needed to call RadioManager.openTuner(), but 134 // the return of that function is needed to create a RadioManagerExt, which calls 135 // sets the callback through setProgramInfoCallback(). 136 Log.d(TAG, "ProgramInfo callback is not set yet; caching ProgramInfo"); 137 mCachedProgramInfo = info; 138 } else { 139 mProgramInfoCallback.onProgramInfoChanged(info); 140 } 141 } 142 mHandler.post(() -> mCallback.onProgramInfoChanged(info)); 143 } 144 onMetadataChanged(RadioMetadata metadata)145 public void onMetadataChanged(RadioMetadata metadata) { 146 mHandler.post(() -> mCallback.onMetadataChanged(metadata)); 147 } 148 onTrafficAnnouncement(boolean active)149 public void onTrafficAnnouncement(boolean active) { 150 mHandler.post(() -> mCallback.onTrafficAnnouncement(active)); 151 } 152 onEmergencyAnnouncement(boolean active)153 public void onEmergencyAnnouncement(boolean active) { 154 mHandler.post(() -> mCallback.onEmergencyAnnouncement(active)); 155 } 156 onAntennaState(boolean connected)157 public void onAntennaState(boolean connected) { 158 mHandler.post(() -> mCallback.onAntennaState(connected)); 159 } 160 onControlChanged(boolean control)161 public void onControlChanged(boolean control) { 162 mHandler.post(() -> mCallback.onControlChanged(control)); 163 } 164 onBackgroundScanAvailabilityChange(boolean isAvailable)165 public void onBackgroundScanAvailabilityChange(boolean isAvailable) { 166 mHandler.post(() -> mCallback.onBackgroundScanAvailabilityChange(isAvailable)); 167 } 168 onBackgroundScanComplete()169 public void onBackgroundScanComplete() { 170 mHandler.post(() -> mCallback.onBackgroundScanComplete()); 171 } 172 onProgramListChanged()173 public void onProgramListChanged() { 174 mHandler.post(() -> mCallback.onProgramListChanged()); 175 } 176 onParametersUpdated(Map<String, String> parameters)177 public void onParametersUpdated(Map<String, String> parameters) { 178 mHandler.post(() -> mCallback.onParametersUpdated(parameters)); 179 } 180 } 181