• 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.server.broadcastradio.hal2;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.graphics.Bitmap;
22 import android.hardware.broadcastradio.V2_0.ConfigFlag;
23 import android.hardware.broadcastradio.V2_0.ITunerSession;
24 import android.hardware.broadcastradio.V2_0.Result;
25 import android.hardware.radio.ITuner;
26 import android.hardware.radio.ProgramList;
27 import android.hardware.radio.ProgramSelector;
28 import android.hardware.radio.RadioManager;
29 import android.os.RemoteException;
30 import android.util.IndentingPrintWriter;
31 import android.util.MutableBoolean;
32 import android.util.MutableInt;
33 import android.util.Slog;
34 
35 import java.util.HashSet;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.Objects;
39 
40 class TunerSession extends ITuner.Stub {
41     private static final String TAG = "BcRadio2Srv.session";
42     private static final String kAudioDeviceName = "Radio tuner source";
43     private static final int TUNER_EVENT_LOGGER_QUEUE_SIZE = 25;
44 
45     private final Object mLock;
46     @NonNull private final RadioEventLogger mEventLogger;
47 
48     private final RadioModule mModule;
49     private final ITunerSession mHwSession;
50     final android.hardware.radio.ITunerCallback mCallback;
51     private boolean mIsClosed = false;
52     private boolean mIsMuted = false;
53     private ProgramInfoCache mProgramInfoCache = null;
54 
55     // necessary only for older APIs compatibility
56     private RadioManager.BandConfig mDummyConfig = null;
57 
TunerSession(@onNull RadioModule module, @NonNull ITunerSession hwSession, @NonNull android.hardware.radio.ITunerCallback callback, @NonNull Object lock)58     TunerSession(@NonNull RadioModule module, @NonNull ITunerSession hwSession,
59             @NonNull android.hardware.radio.ITunerCallback callback,
60             @NonNull Object lock) {
61         mModule = Objects.requireNonNull(module);
62         mHwSession = Objects.requireNonNull(hwSession);
63         mCallback = Objects.requireNonNull(callback);
64         mLock = Objects.requireNonNull(lock);
65         mEventLogger = new RadioEventLogger(TAG, TUNER_EVENT_LOGGER_QUEUE_SIZE);
66     }
67 
68     @Override
close()69     public void close() {
70         mEventLogger.logRadioEvent("Close");
71         close(null);
72     }
73 
74     /**
75      * Closes the TunerSession. If error is non-null, the client's onError() callback is invoked
76      * first with the specified error, see {@link
77      * android.hardware.radio.RadioTuner.Callback#onError}.
78      *
79      * @param error Optional error to send to client before session is closed.
80      */
close(@ullable Integer error)81     public void close(@Nullable Integer error) {
82         mEventLogger.logRadioEvent("Close on error %d", error);
83         synchronized (mLock) {
84             if (mIsClosed) return;
85             if (error != null) {
86                 try {
87                     mCallback.onError(error);
88                 } catch (RemoteException ex) {
89                     Slog.w(TAG, "mCallback.onError() failed: ", ex);
90                 }
91             }
92             mIsClosed = true;
93             mModule.onTunerSessionClosed(this);
94         }
95     }
96 
97     @Override
isClosed()98     public boolean isClosed() {
99         return mIsClosed;
100     }
101 
checkNotClosedLocked()102     private void checkNotClosedLocked() {
103         if (mIsClosed) {
104             throw new IllegalStateException("Tuner is closed, no further operations are allowed");
105         }
106     }
107 
108     @Override
setConfiguration(RadioManager.BandConfig config)109     public void setConfiguration(RadioManager.BandConfig config) {
110         synchronized (mLock) {
111             checkNotClosedLocked();
112             mDummyConfig = Objects.requireNonNull(config);
113             Slog.i(TAG, "Ignoring setConfiguration - not applicable for broadcastradio HAL 2.0");
114             mModule.fanoutAidlCallback(cb -> cb.onConfigurationChanged(config));
115         }
116     }
117 
118     @Override
getConfiguration()119     public RadioManager.BandConfig getConfiguration() {
120         synchronized (mLock) {
121             checkNotClosedLocked();
122             return mDummyConfig;
123         }
124     }
125 
126     @Override
setMuted(boolean mute)127     public void setMuted(boolean mute) {
128         synchronized (mLock) {
129             checkNotClosedLocked();
130             if (mIsMuted == mute) return;
131             mIsMuted = mute;
132             Slog.w(TAG, "Mute via RadioService is not implemented - please handle it via app");
133         }
134     }
135 
136     @Override
isMuted()137     public boolean isMuted() {
138         synchronized (mLock) {
139             checkNotClosedLocked();
140             return mIsMuted;
141         }
142     }
143 
144     @Override
step(boolean directionDown, boolean skipSubChannel)145     public void step(boolean directionDown, boolean skipSubChannel) throws RemoteException {
146         mEventLogger.logRadioEvent("Step with direction %s, skipSubChannel?  %s",
147                 directionDown ? "down" : "up", skipSubChannel ? "yes" : "no");
148         synchronized (mLock) {
149             checkNotClosedLocked();
150             int halResult = mHwSession.step(!directionDown);
151             Convert.throwOnError("step", halResult);
152         }
153     }
154 
155     @Override
scan(boolean directionDown, boolean skipSubChannel)156     public void scan(boolean directionDown, boolean skipSubChannel) throws RemoteException {
157         mEventLogger.logRadioEvent("Scan with direction %s, skipSubChannel? %s",
158                 directionDown ? "down" : "up", skipSubChannel ? "yes" : "no");
159         synchronized (mLock) {
160             checkNotClosedLocked();
161             int halResult = mHwSession.scan(!directionDown, skipSubChannel);
162             Convert.throwOnError("step", halResult);
163         }
164     }
165 
166     @Override
tune(ProgramSelector selector)167     public void tune(ProgramSelector selector) throws RemoteException {
168         mEventLogger.logRadioEvent("Tune with selector %s", selector);
169         synchronized (mLock) {
170             checkNotClosedLocked();
171             int halResult = mHwSession.tune(Convert.programSelectorToHal(selector));
172             Convert.throwOnError("tune", halResult);
173         }
174     }
175 
176     @Override
cancel()177     public void cancel() {
178         Slog.i(TAG, "Cancel");
179         synchronized (mLock) {
180             checkNotClosedLocked();
181             Utils.maybeRethrow(mHwSession::cancel);
182         }
183     }
184 
185     @Override
cancelAnnouncement()186     public void cancelAnnouncement() {
187         Slog.i(TAG, "Announcements control doesn't involve cancelling at the HAL level in HAL 2.0");
188     }
189 
190     @Override
getImage(int id)191     public Bitmap getImage(int id) {
192         mEventLogger.logRadioEvent("Get image for %d", id);
193         return mModule.getImage(id);
194     }
195 
196     @Override
startBackgroundScan()197     public boolean startBackgroundScan() {
198         Slog.i(TAG, "Explicit background scan trigger is not supported with HAL 2.0");
199         mModule.fanoutAidlCallback(cb -> cb.onBackgroundScanComplete());
200         return true;
201     }
202 
203     @Override
startProgramListUpdates(ProgramList.Filter filter)204     public void startProgramListUpdates(ProgramList.Filter filter) throws RemoteException {
205         mEventLogger.logRadioEvent("start programList updates %s", filter);
206         // If the AIDL client provides a null filter, it wants all updates, so use the most broad
207         // filter.
208         if (filter == null) {
209             filter = new ProgramList.Filter(new HashSet<Integer>(),
210                     new HashSet<android.hardware.radio.ProgramSelector.Identifier>(), true, false);
211         }
212         synchronized (mLock) {
213             checkNotClosedLocked();
214             mProgramInfoCache = new ProgramInfoCache(filter);
215         }
216         // Note: RadioModule.onTunerSessionProgramListFilterChanged() must be called without mLock
217         // held since it can call getProgramListFilter() and onHalProgramInfoUpdated().
218         mModule.onTunerSessionProgramListFilterChanged(this);
219     }
220 
getProgramListFilter()221     ProgramList.Filter getProgramListFilter() {
222         synchronized (mLock) {
223             return mProgramInfoCache == null ? null : mProgramInfoCache.getFilter();
224         }
225     }
226 
onMergedProgramListUpdateFromHal(ProgramList.Chunk mergedChunk)227     void onMergedProgramListUpdateFromHal(ProgramList.Chunk mergedChunk) {
228         List<ProgramList.Chunk> clientUpdateChunks = null;
229         synchronized (mLock) {
230             if (mProgramInfoCache == null) {
231                 return;
232             }
233             clientUpdateChunks = mProgramInfoCache.filterAndApplyChunk(mergedChunk);
234         }
235         dispatchClientUpdateChunks(clientUpdateChunks);
236     }
237 
updateProgramInfoFromHalCache(ProgramInfoCache halCache)238     void updateProgramInfoFromHalCache(ProgramInfoCache halCache) {
239         List<ProgramList.Chunk> clientUpdateChunks = null;
240         synchronized (mLock) {
241             if (mProgramInfoCache == null) {
242                 return;
243             }
244             clientUpdateChunks = mProgramInfoCache.filterAndUpdateFrom(halCache, true);
245         }
246         dispatchClientUpdateChunks(clientUpdateChunks);
247     }
248 
dispatchClientUpdateChunks(@ullable List<ProgramList.Chunk> chunks)249     private void dispatchClientUpdateChunks(@Nullable List<ProgramList.Chunk> chunks) {
250         if (chunks == null) {
251             return;
252         }
253         for (ProgramList.Chunk chunk : chunks) {
254             try {
255                 mCallback.onProgramListUpdated(chunk);
256             } catch (RemoteException ex) {
257                 Slog.w(TAG, "mCallback.onProgramListUpdated() failed: ", ex);
258             }
259         }
260     }
261 
262     @Override
stopProgramListUpdates()263     public void stopProgramListUpdates() throws RemoteException {
264         mEventLogger.logRadioEvent("Stop programList updates");
265         synchronized (mLock) {
266             checkNotClosedLocked();
267             mProgramInfoCache = null;
268         }
269         // Note: RadioModule.onTunerSessionProgramListFilterChanged() must be called without mLock
270         // held since it can call getProgramListFilter() and onHalProgramInfoUpdated().
271         mModule.onTunerSessionProgramListFilterChanged(this);
272     }
273 
274     @Override
isConfigFlagSupported(int flag)275     public boolean isConfigFlagSupported(int flag) {
276         try {
277             isConfigFlagSet(flag);
278             return true;
279         } catch (IllegalStateException ex) {
280             return true;
281         } catch (UnsupportedOperationException ex) {
282             return false;
283         }
284     }
285 
286     @Override
isConfigFlagSet(int flag)287     public boolean isConfigFlagSet(int flag) {
288         mEventLogger.logRadioEvent("Is ConfigFlagSet for %s", ConfigFlag.toString(flag));
289         synchronized (mLock) {
290             checkNotClosedLocked();
291 
292             MutableInt halResult = new MutableInt(Result.UNKNOWN_ERROR);
293             MutableBoolean flagState = new MutableBoolean(false);
294             try {
295                 mHwSession.isConfigFlagSet(flag, (int result, boolean value) -> {
296                     halResult.value = result;
297                     flagState.value = value;
298                 });
299             } catch (RemoteException ex) {
300                 throw new RuntimeException("Failed to check flag " + ConfigFlag.toString(flag), ex);
301             }
302             Convert.throwOnError("isConfigFlagSet", halResult.value);
303 
304             return flagState.value;
305         }
306     }
307 
308     @Override
setConfigFlag(int flag, boolean value)309     public void setConfigFlag(int flag, boolean value) throws RemoteException {
310         mEventLogger.logRadioEvent("Set ConfigFlag  %s = %b", ConfigFlag.toString(flag), value);
311         synchronized (mLock) {
312             checkNotClosedLocked();
313             int halResult = mHwSession.setConfigFlag(flag, value);
314             Convert.throwOnError("setConfigFlag", halResult);
315         }
316     }
317 
318     @Override
setParameters(Map<String, String> parameters)319     public Map<String, String> setParameters(Map<String, String> parameters) {
320         synchronized (mLock) {
321             checkNotClosedLocked();
322             return Convert.vendorInfoFromHal(Utils.maybeRethrow(
323                     () -> mHwSession.setParameters(Convert.vendorInfoToHal(parameters))));
324         }
325     }
326 
327     @Override
getParameters(List<String> keys)328     public Map<String, String> getParameters(List<String> keys) {
329         synchronized (mLock) {
330             checkNotClosedLocked();
331             return Convert.vendorInfoFromHal(Utils.maybeRethrow(
332                     () -> mHwSession.getParameters(Convert.listToArrayList(keys))));
333         }
334     }
335 
dumpInfo(IndentingPrintWriter pw)336     void dumpInfo(IndentingPrintWriter pw) {
337         pw.printf("TunerSession\n");
338         pw.increaseIndent();
339         synchronized (mLock) {
340             pw.printf("HIDL HAL Session: %s\n", mHwSession);
341             pw.printf("Is session closed? %s\n", mIsClosed ? "Yes" : "No");
342             pw.printf("Is muted? %s\n", mIsMuted ? "Yes" : "No");
343             pw.printf("ProgramInfoCache: %s\n", mProgramInfoCache);
344             pw.printf("Config: %s\n", mDummyConfig);
345         }
346         pw.printf("Tuner session events:\n");
347         pw.increaseIndent();
348         mEventLogger.dump(pw);
349         pw.decreaseIndent();
350         pw.decreaseIndent();
351     }
352 }
353