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