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