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