• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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