• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2019 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 android.media.tv.tuner;
18 
19 import android.annotation.BytesLong;
20 import android.annotation.CallbackExecutor;
21 import android.annotation.IntDef;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.annotation.RequiresPermission;
25 import android.annotation.SuppressLint;
26 import android.annotation.SystemApi;
27 import android.content.Context;
28 import android.hardware.tv.tuner.V1_0.Constants;
29 import android.media.tv.TvInputService;
30 import android.media.tv.tuner.dvr.DvrPlayback;
31 import android.media.tv.tuner.dvr.DvrRecorder;
32 import android.media.tv.tuner.dvr.OnPlaybackStatusChangedListener;
33 import android.media.tv.tuner.dvr.OnRecordStatusChangedListener;
34 import android.media.tv.tuner.filter.Filter;
35 import android.media.tv.tuner.filter.Filter.Subtype;
36 import android.media.tv.tuner.filter.Filter.Type;
37 import android.media.tv.tuner.filter.FilterCallback;
38 import android.media.tv.tuner.filter.TimeFilter;
39 import android.media.tv.tuner.frontend.Atsc3PlpInfo;
40 import android.media.tv.tuner.frontend.FrontendInfo;
41 import android.media.tv.tuner.frontend.FrontendSettings;
42 import android.media.tv.tuner.frontend.FrontendStatus;
43 import android.media.tv.tuner.frontend.FrontendStatus.FrontendStatusType;
44 import android.media.tv.tuner.frontend.OnTuneEventListener;
45 import android.media.tv.tuner.frontend.ScanCallback;
46 import android.media.tv.tunerresourcemanager.ResourceClientProfile;
47 import android.media.tv.tunerresourcemanager.TunerCiCamRequest;
48 import android.media.tv.tunerresourcemanager.TunerDemuxRequest;
49 import android.media.tv.tunerresourcemanager.TunerDescramblerRequest;
50 import android.media.tv.tunerresourcemanager.TunerFrontendRequest;
51 import android.media.tv.tunerresourcemanager.TunerLnbRequest;
52 import android.media.tv.tunerresourcemanager.TunerResourceManager;
53 import android.os.Handler;
54 import android.os.HandlerExecutor;
55 import android.os.Looper;
56 import android.os.Message;
57 import android.os.Process;
58 import android.util.Log;
59 
60 import com.android.internal.util.FrameworkStatsLog;
61 
62 import java.lang.annotation.Retention;
63 import java.lang.annotation.RetentionPolicy;
64 import java.lang.ref.WeakReference;
65 import java.util.ArrayList;
66 import java.util.Arrays;
67 import java.util.HashMap;
68 import java.util.Iterator;
69 import java.util.List;
70 import java.util.Map;
71 import java.util.Objects;
72 import java.util.concurrent.Executor;
73 
74 /**
75  * This class is used to interact with hardware tuners devices.
76  *
77  * <p> Each TvInputService Session should create one instance of this class.
78  *
79  * <p> This class controls the TIS interaction with Tuner HAL.
80  *
81  * @hide
82  */
83 @SystemApi
84 public class Tuner implements AutoCloseable  {
85     /**
86      * Invalid TS packet ID.
87      */
88     public static final int INVALID_TS_PID = Constants.Constant.INVALID_TS_PID;
89     /**
90      * Invalid stream ID.
91      */
92     public static final int INVALID_STREAM_ID = Constants.Constant.INVALID_STREAM_ID;
93     /**
94      * Invalid filter ID.
95      */
96     public static final int INVALID_FILTER_ID = Constants.Constant.INVALID_FILTER_ID;
97     /**
98      * Invalid AV Sync ID.
99      */
100     public static final int INVALID_AV_SYNC_ID = Constants.Constant.INVALID_AV_SYNC_ID;
101     /**
102      * Invalid timestamp.
103      *
104      * <p>Returned by {@link android.media.tv.tuner.filter.TimeFilter#getSourceTime()},
105      * {@link android.media.tv.tuner.filter.TimeFilter#getTimeStamp()},
106      * {@link Tuner#getAvSyncTime(int)} or {@link TsRecordEvent#getPts()} and
107      * {@link MmtpRecordEvent#getPts()} when the requested timestamp is not available.
108      *
109      * @see android.media.tv.tuner.filter.TimeFilter#getSourceTime()
110      * @see android.media.tv.tuner.filter.TimeFilter#getTimeStamp()
111      * @see Tuner#getAvSyncTime(int)
112      * @see android.media.tv.tuner.filter.TsRecordEvent#getPts()
113      * @see android.media.tv.tuner.filter.MmtpRecordEvent#getPts()
114      */
115     public static final long INVALID_TIMESTAMP =
116             android.hardware.tv.tuner.V1_1.Constants.Constant64Bit.INVALID_PRESENTATION_TIME_STAMP;
117     /**
118      * Invalid mpu sequence number in MmtpRecordEvent.
119      *
120      * <p>Returned by {@link MmtpRecordEvent#getMpuSequenceNumber()} when the requested sequence
121      * number is not available.
122      *
123      * @see android.media.tv.tuner.filter.MmtpRecordEvent#getMpuSequenceNumber()
124      */
125     public static final int INVALID_MMTP_RECORD_EVENT_MPT_SEQUENCE_NUM =
126             android.hardware.tv.tuner.V1_1.Constants.Constant
127                     .INVALID_MMTP_RECORD_EVENT_MPT_SEQUENCE_NUM;
128     /**
129      * Invalid first macroblock address in MmtpRecordEvent and TsRecordEvent.
130      *
131      * <p>Returned by {@link MmtpRecordEvent#getMbInSlice()} and
132      * {@link TsRecordEvent#getMbInSlice()} when the requested sequence number is not available.
133      *
134      * @see android.media.tv.tuner.filter.MmtpRecordEvent#getMbInSlice()
135      * @see android.media.tv.tuner.filter.TsRecordEvent#getMbInSlice()
136      */
137     public static final int INVALID_FIRST_MACROBLOCK_IN_SLICE =
138             android.hardware.tv.tuner.V1_1.Constants.Constant.INVALID_FIRST_MACROBLOCK_IN_SLICE;
139     /**
140      * Invalid local transport stream id.
141      *
142      * <p>Returned by {@link #linkFrontendToCiCam(int)} when the requested failed
143      * or the hal implementation does not support the operation.
144      *
145      * @see #linkFrontendToCiCam(int)
146      */
147     public static final int INVALID_LTS_ID =
148             android.hardware.tv.tuner.V1_1.Constants.Constant.INVALID_LTS_ID;
149     /**
150      * Invalid 64-bit filter ID.
151      */
152     public static final long INVALID_FILTER_ID_LONG =
153             android.hardware.tv.tuner.V1_1.Constants.Constant64Bit.INVALID_FILTER_ID_64BIT;
154     /**
155      * Invalid frequency that is used as the default frontend frequency setting.
156      */
157     public static final int INVALID_FRONTEND_SETTING_FREQUENCY =
158             android.hardware.tv.tuner.V1_1.Constants.Constant.INVALID_FRONTEND_SETTING_FREQUENCY;
159     /**
160      * Invalid frontend id.
161      */
162     public static final int INVALID_FRONTEND_ID =
163             android.hardware.tv.tuner.V1_1.Constants.Constant.INVALID_FRONTEND_ID;
164     /**
165      * Invalid LNB id.
166      *
167      * @hide
168      */
169     public static final int INVALID_LNB_ID =
170             android.hardware.tv.tuner.V1_1.Constants.Constant.INVALID_LNB_ID;
171     /**
172      * A void key token. It is used to remove the current key from descrambler.
173      *
174      * <p>If the current keyToken comes from a MediaCas session, App is recommended to
175      * to use this constant to remove current key before closing MediaCas session.
176      */
177     @NonNull
178     public static final byte[] VOID_KEYTOKEN =
179             {android.hardware.tv.tuner.V1_1.Constants.Constant.INVALID_KEYTOKEN};
180 
181     /** @hide */
182     @IntDef(prefix = "SCAN_TYPE_", value = {SCAN_TYPE_UNDEFINED, SCAN_TYPE_AUTO, SCAN_TYPE_BLIND})
183     @Retention(RetentionPolicy.SOURCE)
184     public @interface ScanType {}
185     /**
186      * Scan type undefined.
187      */
188     public static final int SCAN_TYPE_UNDEFINED = Constants.FrontendScanType.SCAN_UNDEFINED;
189     /**
190      * Scan type auto.
191      *
192      * <p> Tuner will send {@link android.media.tv.tuner.frontend.ScanCallback#onLocked}
193      */
194     public static final int SCAN_TYPE_AUTO = Constants.FrontendScanType.SCAN_AUTO;
195     /**
196      * Blind scan.
197      *
198      * <p>Frequency range is not specified. The {@link android.media.tv.tuner.Tuner} will scan an
199      * implementation specific range.
200      */
201     public static final int SCAN_TYPE_BLIND = Constants.FrontendScanType.SCAN_BLIND;
202 
203 
204     /** @hide */
205     @IntDef({RESULT_SUCCESS, RESULT_UNAVAILABLE, RESULT_NOT_INITIALIZED, RESULT_INVALID_STATE,
206             RESULT_INVALID_ARGUMENT, RESULT_OUT_OF_MEMORY, RESULT_UNKNOWN_ERROR})
207     @Retention(RetentionPolicy.SOURCE)
208     public @interface Result {}
209 
210     /**
211      * Operation succeeded.
212      */
213     public static final int RESULT_SUCCESS = Constants.Result.SUCCESS;
214     /**
215      * Operation failed because the corresponding resources are not available.
216      */
217     public static final int RESULT_UNAVAILABLE = Constants.Result.UNAVAILABLE;
218     /**
219      * Operation failed because the corresponding resources are not initialized.
220      */
221     public static final int RESULT_NOT_INITIALIZED = Constants.Result.NOT_INITIALIZED;
222     /**
223      * Operation failed because it's not in a valid state.
224      */
225     public static final int RESULT_INVALID_STATE = Constants.Result.INVALID_STATE;
226     /**
227      * Operation failed because there are invalid arguments.
228      */
229     public static final int RESULT_INVALID_ARGUMENT = Constants.Result.INVALID_ARGUMENT;
230     /**
231      * Memory allocation failed.
232      */
233     public static final int RESULT_OUT_OF_MEMORY = Constants.Result.OUT_OF_MEMORY;
234     /**
235      * Operation failed due to unknown errors.
236      */
237     public static final int RESULT_UNKNOWN_ERROR = Constants.Result.UNKNOWN_ERROR;
238 
239 
240 
241     private static final String TAG = "MediaTvTuner";
242     private static final boolean DEBUG = false;
243 
244     private static final int MSG_RESOURCE_LOST = 1;
245     private static final int MSG_ON_FILTER_EVENT = 2;
246     private static final int MSG_ON_FILTER_STATUS = 3;
247     private static final int MSG_ON_LNB_EVENT = 4;
248 
249     private static final int FILTER_CLEANUP_THRESHOLD = 256;
250 
251     /** @hide */
252     @IntDef(prefix = "DVR_TYPE_", value = {DVR_TYPE_RECORD, DVR_TYPE_PLAYBACK})
253     @Retention(RetentionPolicy.SOURCE)
254     public @interface DvrType {}
255 
256     /**
257      * DVR for recording.
258      * @hide
259      */
260     public static final int DVR_TYPE_RECORD = Constants.DvrType.RECORD;
261     /**
262      * DVR for playback of recorded programs.
263      * @hide
264      */
265     public static final int DVR_TYPE_PLAYBACK = Constants.DvrType.PLAYBACK;
266 
267     static {
268         try {
269             System.loadLibrary("media_tv_tuner");
nativeInit()270             nativeInit();
271         } catch (UnsatisfiedLinkError e) {
272             Log.d(TAG, "tuner JNI library not found!");
273         }
274     }
275 
276     private final Context mContext;
277     private final TunerResourceManager mTunerResourceManager;
278     private final int mClientId;
279     private static int sTunerVersion = TunerVersionChecker.TUNER_VERSION_UNKNOWN;
280 
281     private Frontend mFrontend;
282     private EventHandler mHandler;
283     @Nullable
284     private FrontendInfo mFrontendInfo;
285     private Integer mFrontendHandle;
286     private Boolean mIsSharedFrontend = false;
287     private int mFrontendType = FrontendSettings.TYPE_UNDEFINED;
288     private int mUserId;
289     private Lnb mLnb;
290     private Integer mLnbHandle;
291     @Nullable
292     private OnTuneEventListener mOnTuneEventListener;
293     @Nullable
294     private Executor mOnTuneEventExecutor;
295     @Nullable
296     private ScanCallback mScanCallback;
297     @Nullable
298     private Executor mScanCallbackExecutor;
299     @Nullable
300     private OnResourceLostListener mOnResourceLostListener;
301     @Nullable
302     private Executor mOnResourceLostListenerExecutor;
303 
304     private final Object mOnTuneEventLock = new Object();
305     private final Object mScanCallbackLock = new Object();
306     private final Object mOnResourceLostListenerLock = new Object();
307 
308     private Integer mDemuxHandle;
309     private Integer mFrontendCiCamHandle;
310     private Integer mFrontendCiCamId;
311     private Map<Integer, WeakReference<Descrambler>> mDescramblers = new HashMap<>();
312     private List<WeakReference<Filter>> mFilters = new ArrayList<WeakReference<Filter>>();
313 
314     private final TunerResourceManager.ResourcesReclaimListener mResourceListener =
315             new TunerResourceManager.ResourcesReclaimListener() {
316                 @Override
317                 public void onReclaimResources() {
318                     if (mFrontend != null) {
319                         FrameworkStatsLog
320                                 .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
321                                     FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__UNKNOWN);
322                     }
323                     releaseAll();
324                     mHandler.sendMessage(mHandler.obtainMessage(MSG_RESOURCE_LOST));
325                 }
326             };
327 
328     /**
329      * Constructs a Tuner instance.
330      *
331      * @param context the context of the caller.
332      * @param tvInputSessionId the session ID of the TV input.
333      * @param useCase the use case of this Tuner instance.
334      */
335     @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
Tuner(@onNull Context context, @Nullable String tvInputSessionId, @TvInputService.PriorityHintUseCaseType int useCase)336     public Tuner(@NonNull Context context, @Nullable String tvInputSessionId,
337             @TvInputService.PriorityHintUseCaseType int useCase) {
338         nativeSetup();
339         sTunerVersion = nativeGetTunerVersion();
340         if (sTunerVersion == TunerVersionChecker.TUNER_VERSION_UNKNOWN) {
341             Log.e(TAG, "Unknown Tuner version!");
342         } else {
343             Log.d(TAG, "Current Tuner version is "
344                     + TunerVersionChecker.getMajorVersion(sTunerVersion) + "."
345                     + TunerVersionChecker.getMinorVersion(sTunerVersion) + ".");
346         }
347         mContext = context;
348         mTunerResourceManager = (TunerResourceManager)
349                 context.getSystemService(Context.TV_TUNER_RESOURCE_MGR_SERVICE);
350         if (mHandler == null) {
351             mHandler = createEventHandler();
352         }
353 
354         int[] clientId = new int[1];
355         ResourceClientProfile profile = new ResourceClientProfile();
356         profile.tvInputSessionId = tvInputSessionId;
357         profile.useCase = useCase;
358         mTunerResourceManager.registerClientProfile(
359                 profile, new HandlerExecutor(mHandler), mResourceListener, clientId);
360         mClientId = clientId[0];
361 
362         mUserId = Process.myUid();
363     }
364 
365     /**
366      * Get frontend info list from native and build them into a {@link FrontendInfo} list. Any
367      * {@code null} FrontendInfo element would be removed.
368      */
getFrontendInfoListInternal()369     private FrontendInfo[] getFrontendInfoListInternal() {
370         List<Integer> ids = getFrontendIds();
371         if (ids == null) {
372             return null;
373         }
374         FrontendInfo[] infos = new FrontendInfo[ids.size()];
375         for (int i = 0; i < ids.size(); i++) {
376             int id = ids.get(i);
377             FrontendInfo frontendInfo = getFrontendInfoById(id);
378             if (frontendInfo == null) {
379                 Log.e(TAG, "Failed to get a FrontendInfo on frontend id:" + id + "!");
380                 continue;
381             }
382             infos[i] = frontendInfo;
383         }
384         return Arrays.stream(infos).filter(Objects::nonNull).toArray(FrontendInfo[]::new);
385     }
386 
387     /** @hide */
getTunerVersion()388     public static int getTunerVersion() {
389         return sTunerVersion;
390     }
391 
392     /** @hide */
getFrontendIds()393     public List<Integer> getFrontendIds() {
394         return nativeGetFrontendIds();
395     }
396 
397     /**
398      * Sets the listener for resource lost.
399      *
400      * @param executor the executor on which the listener should be invoked.
401      * @param listener the listener that will be run.
402      */
setResourceLostListener(@onNull @allbackExecutor Executor executor, @NonNull OnResourceLostListener listener)403     public void setResourceLostListener(@NonNull @CallbackExecutor Executor executor,
404             @NonNull OnResourceLostListener listener) {
405         synchronized (mOnResourceLostListenerLock) {
406             Objects.requireNonNull(executor, "OnResourceLostListener must not be null");
407             Objects.requireNonNull(listener, "executor must not be null");
408             mOnResourceLostListener = listener;
409             mOnResourceLostListenerExecutor = executor;
410         }
411     }
412 
413     /**
414      * Removes the listener for resource lost.
415      */
clearResourceLostListener()416     public void clearResourceLostListener() {
417         synchronized (mOnResourceLostListenerLock) {
418             mOnResourceLostListener = null;
419             mOnResourceLostListenerExecutor = null;
420         }
421     }
422 
423     /**
424      * Shares the frontend resource with another Tuner instance
425      *
426      * @param tuner the Tuner instance to share frontend resource with.
427      */
shareFrontendFromTuner(@onNull Tuner tuner)428     public void shareFrontendFromTuner(@NonNull Tuner tuner) {
429         mTunerResourceManager.shareFrontend(mClientId, tuner.mClientId);
430         synchronized (mIsSharedFrontend) {
431             mFrontendHandle = tuner.mFrontendHandle;
432             mFrontend = tuner.mFrontend;
433             mIsSharedFrontend = true;
434         }
435     }
436 
437     /**
438      * Updates client priority with an arbitrary value along with a nice value.
439      *
440      * <p>Tuner resource manager (TRM) uses the client priority value to decide whether it is able
441      * to reclaim insufficient resources from another client.
442      *
443      * <p>The nice value represents how much the client intends to give up the resource when an
444      * insufficient resource situation happens.
445      *
446      * @param priority the new priority. Any negative value would cause no-op on priority setting
447      *                 and the API would only process nice value setting in that case.
448      * @param niceValue the nice value.
449      */
updateResourcePriority(int priority, int niceValue)450     public void updateResourcePriority(int priority, int niceValue) {
451         mTunerResourceManager.updateClientPriority(mClientId, priority, niceValue);
452     }
453 
454     private long mNativeContext; // used by native jMediaTuner
455 
456     /**
457      * Releases the Tuner instance.
458      */
459     @Override
close()460     public void close() {
461         releaseAll();
462         TunerUtils.throwExceptionForResult(nativeClose(), "failed to close tuner");
463     }
464 
releaseAll()465     private void releaseAll() {
466         if (mFrontendHandle != null) {
467             synchronized (mIsSharedFrontend) {
468                 if (!mIsSharedFrontend) {
469                     int res = nativeCloseFrontend(mFrontendHandle);
470                     if (res != Tuner.RESULT_SUCCESS) {
471                         TunerUtils.throwExceptionForResult(res, "failed to close frontend");
472                     }
473                 }
474                 mIsSharedFrontend = false;
475             }
476             mTunerResourceManager.releaseFrontend(mFrontendHandle, mClientId);
477             FrameworkStatsLog
478                     .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
479                     FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__UNKNOWN);
480             mFrontendHandle = null;
481             mFrontend = null;
482         }
483         if (mLnb != null) {
484             mLnb.close();
485         }
486         if (mFrontendCiCamHandle != null) {
487             int result = nativeUnlinkCiCam(mFrontendCiCamId);
488             if (result == RESULT_SUCCESS) {
489                 mTunerResourceManager.releaseCiCam(mFrontendCiCamHandle, mClientId);
490                 mFrontendCiCamId = null;
491                 mFrontendCiCamHandle = null;
492             }
493         }
494         synchronized (mDescramblers) {
495             if (!mDescramblers.isEmpty()) {
496                 for (Map.Entry<Integer, WeakReference<Descrambler>> d : mDescramblers.entrySet()) {
497                     Descrambler descrambler = d.getValue().get();
498                     if (descrambler != null) {
499                         descrambler.close();
500                     }
501                     mTunerResourceManager.releaseDescrambler(d.getKey(), mClientId);
502                 }
503                 mDescramblers.clear();
504             }
505         }
506         synchronized (mFilters) {
507             if (!mFilters.isEmpty()) {
508                 for (WeakReference<Filter> weakFilter : mFilters) {
509                     Filter filter = weakFilter.get();
510                     if (filter != null) {
511                         filter.close();
512                     }
513                 }
514                 mFilters.clear();
515             }
516         }
517         if (mDemuxHandle != null) {
518             int res = nativeCloseDemux(mDemuxHandle);
519             if (res != Tuner.RESULT_SUCCESS) {
520                 TunerUtils.throwExceptionForResult(res, "failed to close demux");
521             }
522             mTunerResourceManager.releaseDemux(mDemuxHandle, mClientId);
523             mDemuxHandle = null;
524         }
525 
526         mTunerResourceManager.unregisterClientProfile(mClientId);
527 
528     }
529 
530     /**
531      * Native Initialization.
532      */
nativeInit()533     private static native void nativeInit();
534 
535     /**
536      * Native setup.
537      */
nativeSetup()538     private native void nativeSetup();
539 
540     /**
541      * Native method to get all frontend IDs.
542      */
nativeGetTunerVersion()543     private native int nativeGetTunerVersion();
544 
545     /**
546      * Native method to get all frontend IDs.
547      */
nativeGetFrontendIds()548     private native List<Integer> nativeGetFrontendIds();
549 
550     /**
551      * Native method to open frontend of the given ID.
552      */
nativeOpenFrontendByHandle(int handle)553     private native Frontend nativeOpenFrontendByHandle(int handle);
554     @Result
nativeTune(int type, FrontendSettings settings)555     private native int nativeTune(int type, FrontendSettings settings);
nativeStopTune()556     private native int nativeStopTune();
nativeScan(int settingsType, FrontendSettings settings, int scanType)557     private native int nativeScan(int settingsType, FrontendSettings settings, int scanType);
nativeStopScan()558     private native int nativeStopScan();
nativeSetLnb(Lnb lnb)559     private native int nativeSetLnb(Lnb lnb);
nativeSetLna(boolean enable)560     private native int nativeSetLna(boolean enable);
nativeGetFrontendStatus(int[] statusTypes)561     private native FrontendStatus nativeGetFrontendStatus(int[] statusTypes);
nativeGetAvSyncHwId(Filter filter)562     private native Integer nativeGetAvSyncHwId(Filter filter);
nativeGetAvSyncTime(int avSyncId)563     private native Long nativeGetAvSyncTime(int avSyncId);
nativeConnectCiCam(int ciCamId)564     private native int nativeConnectCiCam(int ciCamId);
nativeLinkCiCam(int ciCamId)565     private native int nativeLinkCiCam(int ciCamId);
nativeDisconnectCiCam()566     private native int nativeDisconnectCiCam();
nativeUnlinkCiCam(int ciCamId)567     private native int nativeUnlinkCiCam(int ciCamId);
nativeGetFrontendInfo(int id)568     private native FrontendInfo nativeGetFrontendInfo(int id);
nativeOpenFilter(int type, int subType, long bufferSize)569     private native Filter nativeOpenFilter(int type, int subType, long bufferSize);
nativeOpenTimeFilter()570     private native TimeFilter nativeOpenTimeFilter();
571 
nativeOpenLnbByHandle(int handle)572     private native Lnb nativeOpenLnbByHandle(int handle);
nativeOpenLnbByName(String name)573     private native Lnb nativeOpenLnbByName(String name);
574 
nativeOpenDescramblerByHandle(int handle)575     private native Descrambler nativeOpenDescramblerByHandle(int handle);
nativeOpenDemuxByhandle(int handle)576     private native int nativeOpenDemuxByhandle(int handle);
577 
nativeOpenDvrRecorder(long bufferSize)578     private native DvrRecorder nativeOpenDvrRecorder(long bufferSize);
nativeOpenDvrPlayback(long bufferSize)579     private native DvrPlayback nativeOpenDvrPlayback(long bufferSize);
580 
nativeGetDemuxCapabilities()581     private native DemuxCapabilities nativeGetDemuxCapabilities();
582 
nativeCloseDemux(int handle)583     private native int nativeCloseDemux(int handle);
nativeCloseFrontend(int handle)584     private native int nativeCloseFrontend(int handle);
nativeClose()585     private native int nativeClose();
586 
587 
588     /**
589      * Listener for resource lost.
590      *
591      * <p>Insufficient resources are reclaimed by higher priority clients.
592      */
593     public interface OnResourceLostListener {
594         /**
595          * Invoked when resource lost.
596          *
597          * @param tuner the tuner instance whose resource is being reclaimed.
598          */
onResourceLost(@onNull Tuner tuner)599         void onResourceLost(@NonNull Tuner tuner);
600     }
601 
602     @Nullable
createEventHandler()603     private EventHandler createEventHandler() {
604         Looper looper;
605         if ((looper = Looper.myLooper()) != null) {
606             return new EventHandler(looper);
607         } else if ((looper = Looper.getMainLooper()) != null) {
608             return new EventHandler(looper);
609         }
610         return null;
611     }
612 
613     private class EventHandler extends Handler {
EventHandler(Looper looper)614         private EventHandler(Looper looper) {
615             super(looper);
616         }
617 
618         @Override
handleMessage(Message msg)619         public void handleMessage(Message msg) {
620             switch (msg.what) {
621                 case MSG_ON_FILTER_STATUS: {
622                     Filter filter = (Filter) msg.obj;
623                     if (filter.getCallback() != null) {
624                         filter.getCallback().onFilterStatusChanged(filter, msg.arg1);
625                     }
626                     break;
627                 }
628                 case MSG_RESOURCE_LOST: {
629                     synchronized (mOnResourceLostListenerLock) {
630                         if (mOnResourceLostListener != null
631                                 && mOnResourceLostListenerExecutor != null) {
632                             mOnResourceLostListenerExecutor.execute(
633                                     () -> mOnResourceLostListener.onResourceLost(Tuner.this));
634                         }
635                     }
636                     break;
637                 }
638                 default:
639                     // fall through
640             }
641         }
642     }
643 
644     private class Frontend {
645         private int mId;
646 
Frontend(int id)647         private Frontend(int id) {
648             mId = id;
649         }
650     }
651 
652     /**
653      * Listens for tune events.
654      *
655      * <p>
656      * Tuner events are started when {@link #tune(FrontendSettings)} is called and end when {@link
657      * #cancelTuning()} is called.
658      *
659      * @param eventListener receives tune events.
660      * @throws SecurityException if the caller does not have appropriate permissions.
661      * @see #tune(FrontendSettings)
662      */
setOnTuneEventListener(@onNull @allbackExecutor Executor executor, @NonNull OnTuneEventListener eventListener)663     public void setOnTuneEventListener(@NonNull @CallbackExecutor Executor executor,
664             @NonNull OnTuneEventListener eventListener) {
665         synchronized (mOnTuneEventLock) {
666             mOnTuneEventListener = eventListener;
667             mOnTuneEventExecutor = executor;
668         }
669     }
670 
671     /**
672      * Clears the {@link OnTuneEventListener} and its associated {@link Executor}.
673      *
674      * @throws SecurityException if the caller does not have appropriate permissions.
675      * @see #setOnTuneEventListener(Executor, OnTuneEventListener)
676      */
clearOnTuneEventListener()677     public void clearOnTuneEventListener() {
678         synchronized (mOnTuneEventLock) {
679             mOnTuneEventListener = null;
680             mOnTuneEventExecutor = null;
681         }
682     }
683 
684     /**
685      * Tunes the frontend to using the settings given.
686      *
687      * <p>Tuner resource manager (TRM) uses the client priority value to decide whether it is able
688      * to get frontend resource. If the client can't get the resource, this call returns {@link
689      * #RESULT_UNAVAILABLE}.
690      *
691      * <p>
692      * This locks the frontend to a frequency by providing signal
693      * delivery information. If previous tuning isn't completed, this stop the previous tuning, and
694      * start a new tuning.
695      *
696      * <p>
697      * Tune is an async call, with {@link OnTuneEventListener#SIGNAL_LOCKED} and {@link
698      * OnTuneEventListener#SIGNAL_NO_SIGNAL} events sent to the {@link OnTuneEventListener}
699      * specified in {@link #setOnTuneEventListener(Executor, OnTuneEventListener)}.
700      *
701      * <p>Tuning with {@link android.media.tv.tuner.frontend.DtmbFrontendSettings} is only
702      * supported in Tuner 1.1 or higher version. Unsupported version will cause no-op. Use {@link
703      * TunerVersionChecker#getTunerVersion()} to get the version information.
704      *
705      * @param settings Signal delivery information the frontend uses to
706      *                 search and lock the signal.
707      * @return result status of tune operation.
708      * @throws SecurityException if the caller does not have appropriate permissions.
709      * @see #setOnTuneEventListener(Executor, OnTuneEventListener)
710      */
711     @Result
tune(@onNull FrontendSettings settings)712     public int tune(@NonNull FrontendSettings settings) {
713         Log.d(TAG, "Tune to " + settings.getFrequency());
714         mFrontendType = settings.getType();
715         if (mFrontendType == FrontendSettings.TYPE_DTMB) {
716             if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
717                     TunerVersionChecker.TUNER_VERSION_1_1, "Tuner with DTMB Frontend")) {
718                 return RESULT_UNAVAILABLE;
719             }
720         }
721         if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
722             mFrontendInfo = null;
723             Log.d(TAG, "Write Stats Log for tuning.");
724             FrameworkStatsLog
725                     .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
726                         FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__TUNING);
727             return nativeTune(settings.getType(), settings);
728         }
729         return RESULT_UNAVAILABLE;
730     }
731 
732     /**
733      * Stops a previous tuning.
734      *
735      * <p>If the method completes successfully, the frontend is no longer tuned and no data
736      * will be sent to attached filters.
737      *
738      * @return result status of the operation.
739      */
740     @Result
cancelTuning()741     public int cancelTuning() {
742         return nativeStopTune();
743     }
744 
745     /**
746      * Scan for channels.
747      *
748      * <p>Details for channels found are returned via {@link ScanCallback}.
749      *
750      * <p>Scanning with {@link android.media.tv.tuner.frontend.DtmbFrontendSettings} is only
751      * supported in Tuner 1.1 or higher version. Unsupported version will cause no-op. Use {@link
752      * TunerVersionChecker#getTunerVersion()} to get the version information.
753      *
754      * @param settings A {@link FrontendSettings} to configure the frontend.
755      * @param scanType The scan type.
756      * @throws SecurityException     if the caller does not have appropriate permissions.
757      * @throws IllegalStateException if {@code scan} is called again before
758      *                               {@link #cancelScanning()} is called.
759      */
760     @Result
scan(@onNull FrontendSettings settings, @ScanType int scanType, @NonNull @CallbackExecutor Executor executor, @NonNull ScanCallback scanCallback)761     public int scan(@NonNull FrontendSettings settings, @ScanType int scanType,
762             @NonNull @CallbackExecutor Executor executor, @NonNull ScanCallback scanCallback) {
763         synchronized (mScanCallbackLock) {
764             // Scan can be called again for blink scan if scanCallback and executor are same as
765             //before.
766             if (((mScanCallback != null) && (mScanCallback != scanCallback))
767                 || ((mScanCallbackExecutor != null) && (mScanCallbackExecutor != executor))) {
768                 throw new IllegalStateException(
769                     "Different Scan session already in progress.  stopScan must be called "
770                         + "before a new scan session can be " + "started.");
771             }
772             mFrontendType = settings.getType();
773             if (mFrontendType == FrontendSettings.TYPE_DTMB) {
774                 if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
775                         TunerVersionChecker.TUNER_VERSION_1_1,
776                         "Scan with DTMB Frontend")) {
777                     return RESULT_UNAVAILABLE;
778                 }
779             }
780             if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
781                 mScanCallback = scanCallback;
782                 mScanCallbackExecutor = executor;
783                 mFrontendInfo = null;
784                 FrameworkStatsLog
785                     .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
786                         FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__SCANNING);
787                 return nativeScan(settings.getType(), settings, scanType);
788             }
789             return RESULT_UNAVAILABLE;
790         }
791     }
792 
793     /**
794      * Stops a previous scanning.
795      *
796      * <p>
797      * The {@link ScanCallback} and it's {@link Executor} will be removed.
798      *
799      * <p>
800      * If the method completes successfully, the frontend stopped previous scanning.
801      *
802      * @throws SecurityException if the caller does not have appropriate permissions.
803      */
804     @Result
cancelScanning()805     public int cancelScanning() {
806         synchronized (mScanCallbackLock) {
807             FrameworkStatsLog.write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
808                     FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__SCAN_STOPPED);
809 
810             int retVal = nativeStopScan();
811             mScanCallback = null;
812             mScanCallbackExecutor = null;
813             return retVal;
814         }
815     }
816 
requestFrontend()817     private boolean requestFrontend() {
818         int[] feHandle = new int[1];
819         TunerFrontendRequest request = new TunerFrontendRequest();
820         request.clientId = mClientId;
821         request.frontendType = mFrontendType;
822         boolean granted = mTunerResourceManager.requestFrontend(request, feHandle);
823         if (granted) {
824             mFrontendHandle = feHandle[0];
825             mFrontend = nativeOpenFrontendByHandle(mFrontendHandle);
826         }
827         return granted;
828     }
829 
830     /**
831      * Sets Low-Noise Block downconverter (LNB) for satellite frontend.
832      *
833      * <p>This assigns a hardware LNB resource to the satellite tuner. It can be
834      * called multiple times to update LNB assignment.
835      *
836      * @param lnb the LNB instance.
837      *
838      * @return result status of the operation.
839      */
840     @Result
setLnb(@onNull Lnb lnb)841     private int setLnb(@NonNull Lnb lnb) {
842         return nativeSetLnb(lnb);
843     }
844 
845     /**
846      * Enable or Disable Low Noise Amplifier (LNA).
847      *
848      * @param enable {@code true} to activate LNA module; {@code false} to deactivate LNA.
849      *
850      * @return result status of the operation.
851      */
852     @Result
setLnaEnabled(boolean enable)853     public int setLnaEnabled(boolean enable) {
854         return nativeSetLna(enable);
855     }
856 
857     /**
858      * Gets the statuses of the frontend.
859      *
860      * <p>This retrieve the statuses of the frontend for given status types.
861      *
862      * @param statusTypes an array of status types which the caller requests. Any types that are not
863      *        in {@link FrontendInfo#getStatusCapabilities()} would be ignored.
864      * @return statuses which response the caller's requests. {@code null} if the operation failed.
865      */
866     @Nullable
getFrontendStatus(@onNull @rontendStatusType int[] statusTypes)867     public FrontendStatus getFrontendStatus(@NonNull @FrontendStatusType int[] statusTypes) {
868         if (mFrontend == null) {
869             throw new IllegalStateException("frontend is not initialized");
870         }
871         return nativeGetFrontendStatus(statusTypes);
872     }
873 
874     /**
875      * Gets hardware sync ID for audio and video.
876      *
877      * @param filter the filter instance for the hardware sync ID.
878      * @return the id of hardware A/V sync.
879      */
getAvSyncHwId(@onNull Filter filter)880     public int getAvSyncHwId(@NonNull Filter filter) {
881         if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
882             return INVALID_AV_SYNC_ID;
883         }
884         Integer id = nativeGetAvSyncHwId(filter);
885         return id == null ? INVALID_AV_SYNC_ID : id;
886     }
887 
888     /**
889      * Gets the current timestamp for Audio/Video sync
890      *
891      * <p>The timestamp is maintained by hardware. The timestamp based on 90KHz, and it's format is
892      * the same as PTS (Presentation Time Stamp).
893      *
894      * @param avSyncHwId the hardware id of A/V sync.
895      * @return the current timestamp of hardware A/V sync.
896      */
getAvSyncTime(int avSyncHwId)897     public long getAvSyncTime(int avSyncHwId) {
898         if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
899             return INVALID_TIMESTAMP;
900         }
901         Long time = nativeGetAvSyncTime(avSyncHwId);
902         return time == null ? INVALID_TIMESTAMP : time;
903     }
904 
905     /**
906      * Connects Conditional Access Modules (CAM) through Common Interface (CI).
907      *
908      * <p>The demux uses the output from the frontend as the input by default, and must change to
909      * use the output from CI-CAM as the input after this call.
910      *
911      * <p> Note that this API is used to connect the CI-CAM to the Demux module while
912      * {@link #connectFrontendToCiCam(int)} is used to connect CI-CAM to the Frontend module.
913      *
914      * @param ciCamId specify CI-CAM Id to connect.
915      * @return result status of the operation.
916      */
917     @Result
connectCiCam(int ciCamId)918     public int connectCiCam(int ciCamId) {
919         if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
920             return nativeConnectCiCam(ciCamId);
921         }
922         return RESULT_UNAVAILABLE;
923     }
924 
925     /**
926      * Connect Conditional Access Modules (CAM) Frontend to support Common Interface (CI)
927      * by-pass mode.
928      *
929      * <p>It is used by the client to link CI-CAM to a Frontend. CI by-pass mode requires that
930      * the CICAM also receives the TS concurrently from the frontend when the Demux is receiving
931      * the TS directly from the frontend.
932      *
933      * <p> Note that this API is used to connect the CI-CAM to the Frontend module while
934      * {@link #connectCiCam(int)} is used to connect CI-CAM to the Demux module.
935      *
936      * <p>Use {@link #disconnectFrontendToCiCam(int)} to disconnect.
937      *
938      * <p>This API is only supported by Tuner HAL 1.1 or higher. Unsupported version would cause
939      * no-op and return {@link #INVALID_LTS_ID}. Use {@link TunerVersionChecker#getTunerVersion()}
940      * to check the version.
941      *
942      * @param ciCamId specify CI-CAM Id, which is the id of the Conditional Access Modules (CAM)
943      *                Common Interface (CI), to link.
944      * @return Local transport stream id when connection is successfully established. Failed
945      *         operation returns {@link #INVALID_LTS_ID} while unsupported version also returns
946      *         {@link #INVALID_LTS_ID}. Check the current HAL version using
947      *         {@link TunerVersionChecker#getTunerVersion()}.
948      */
connectFrontendToCiCam(int ciCamId)949     public int connectFrontendToCiCam(int ciCamId) {
950         if (TunerVersionChecker.checkHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1,
951                 "linkFrontendToCiCam")) {
952             if (checkCiCamResource(ciCamId)
953                     && checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
954                 return nativeLinkCiCam(ciCamId);
955             }
956         }
957         return INVALID_LTS_ID;
958     }
959 
960     /**
961      * Disconnects Conditional Access Modules (CAM).
962      *
963      * <p>The demux will use the output from the frontend as the input after this call.
964      *
965      * <p> Note that this API is used to disconnect the CI-CAM to the Demux module while
966      * {@link #disconnectFrontendToCiCam(int)} is used to disconnect CI-CAM to the Frontend module.
967      *
968      * @return result status of the operation.
969      */
970     @Result
disconnectCiCam()971     public int disconnectCiCam() {
972         if (mDemuxHandle != null) {
973             return nativeDisconnectCiCam();
974         }
975         return RESULT_UNAVAILABLE;
976     }
977 
978     /**
979      * Disconnect Conditional Access Modules (CAM) Frontend.
980      *
981      * <p>It is used by the client to unlink CI-CAM to a Frontend.
982      *
983      * <p> Note that this API is used to disconnect the CI-CAM to the Demux module while
984      * {@link #disconnectCiCam(int)} is used to disconnect CI-CAM to the Frontend module.
985      *
986      * <p>This API is only supported by Tuner HAL 1.1 or higher. Unsupported version would cause
987      * no-op. Use {@link TunerVersionChecker#getTunerVersion()} to check the version.
988      *
989      * @param ciCamId specify CI-CAM Id, which is the id of the Conditional Access Modules (CAM)
990      *                Common Interface (CI), to disconnect.
991      * @return result status of the operation. Unsupported version would return
992      *         {@link #RESULT_UNAVAILABLE}
993      */
994     @Result
disconnectFrontendToCiCam(int ciCamId)995     public int disconnectFrontendToCiCam(int ciCamId) {
996         if (TunerVersionChecker.checkHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1,
997                 "unlinkFrontendToCiCam")) {
998             if (mFrontendCiCamHandle != null && mFrontendCiCamId != null
999                     && mFrontendCiCamId == ciCamId) {
1000                 int result = nativeUnlinkCiCam(ciCamId);
1001                 if (result == RESULT_SUCCESS) {
1002                     mTunerResourceManager.releaseCiCam(mFrontendCiCamHandle, mClientId);
1003                     mFrontendCiCamId = null;
1004                     mFrontendCiCamHandle = null;
1005                 }
1006                 return result;
1007             }
1008         }
1009         return RESULT_UNAVAILABLE;
1010     }
1011 
1012     /**
1013      * Gets the currently initialized and activated frontend information. To get all the available
1014      * frontend info on the device, use {@link getAvailableFrontendInfos()}.
1015      *
1016      * @return The active frontend information. {@code null} if the operation failed.
1017      * @throws IllegalStateException if there is no active frontend currently.
1018      */
1019     @Nullable
getFrontendInfo()1020     public FrontendInfo getFrontendInfo() {
1021         if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
1022             return null;
1023         }
1024         if (mFrontend == null) {
1025             throw new IllegalStateException("frontend is not initialized");
1026         }
1027         if (mFrontendInfo == null) {
1028             mFrontendInfo = getFrontendInfoById(mFrontend.mId);
1029         }
1030         return mFrontendInfo;
1031     }
1032 
1033     /**
1034      * Gets a list of all the available frontend information on the device. To get the information
1035      * of the currently active frontend, use {@link getFrontendInfo()}. The active frontend
1036      * information is also included in the list of the available frontend information.
1037      *
1038      * @return The list of all the available frontend information. {@code null} if the operation
1039      * failed.
1040      */
1041     @Nullable
1042     @SuppressLint("NullableCollection")
getAvailableFrontendInfos()1043     public List<FrontendInfo> getAvailableFrontendInfos() {
1044         FrontendInfo[] feInfoList = getFrontendInfoListInternal();
1045         if (feInfoList == null) {
1046             return null;
1047         }
1048         return Arrays.asList(feInfoList);
1049     }
1050 
1051     /** @hide */
getFrontendInfoById(int id)1052     public FrontendInfo getFrontendInfoById(int id) {
1053         return nativeGetFrontendInfo(id);
1054     }
1055 
1056     /**
1057      * Gets Demux capabilities.
1058      *
1059      * @return A {@link DemuxCapabilities} instance that represents the demux capabilities.
1060      *         {@code null} if the operation failed.
1061      */
1062     @Nullable
getDemuxCapabilities()1063     public DemuxCapabilities getDemuxCapabilities() {
1064         return nativeGetDemuxCapabilities();
1065     }
1066 
onFrontendEvent(int eventType)1067     private void onFrontendEvent(int eventType) {
1068         Log.d(TAG, "Got event from tuning. Event type: " + eventType);
1069         synchronized (mOnTuneEventLock) {
1070             if (mOnTuneEventExecutor != null && mOnTuneEventListener != null) {
1071                 mOnTuneEventExecutor.execute(() -> mOnTuneEventListener.onTuneEvent(eventType));
1072             }
1073         }
1074 
1075         Log.d(TAG, "Wrote Stats Log for the events from tuning.");
1076         if (eventType == OnTuneEventListener.SIGNAL_LOCKED) {
1077             FrameworkStatsLog
1078                     .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
1079                         FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__LOCKED);
1080         } else if (eventType == OnTuneEventListener.SIGNAL_NO_SIGNAL) {
1081             FrameworkStatsLog
1082                     .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
1083                         FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__NOT_LOCKED);
1084         } else if (eventType == OnTuneEventListener.SIGNAL_LOST_LOCK) {
1085             FrameworkStatsLog
1086                     .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
1087                         FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__SIGNAL_LOST);
1088         }
1089     }
1090 
onLocked()1091     private void onLocked() {
1092         Log.d(TAG, "Wrote Stats Log for locked event from scanning.");
1093         FrameworkStatsLog.write(
1094                 FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
1095                 FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__LOCKED);
1096 
1097         synchronized (mScanCallbackLock) {
1098             if (mScanCallbackExecutor != null && mScanCallback != null) {
1099                 mScanCallbackExecutor.execute(() -> mScanCallback.onLocked());
1100             }
1101         }
1102     }
1103 
onScanStopped()1104     private void onScanStopped() {
1105         synchronized (mScanCallbackLock) {
1106             if (mScanCallbackExecutor != null && mScanCallback != null) {
1107                 mScanCallbackExecutor.execute(() -> mScanCallback.onScanStopped());
1108             }
1109         }
1110     }
1111 
onProgress(int percent)1112     private void onProgress(int percent) {
1113         synchronized (mScanCallbackLock) {
1114             if (mScanCallbackExecutor != null && mScanCallback != null) {
1115                 mScanCallbackExecutor.execute(() -> mScanCallback.onProgress(percent));
1116             }
1117         }
1118     }
1119 
onFrequenciesReport(int[] frequency)1120     private void onFrequenciesReport(int[] frequency) {
1121         synchronized (mScanCallbackLock) {
1122             if (mScanCallbackExecutor != null && mScanCallback != null) {
1123                 mScanCallbackExecutor.execute(() -> mScanCallback.onFrequenciesReported(frequency));
1124             }
1125         }
1126     }
1127 
onSymbolRates(int[] rate)1128     private void onSymbolRates(int[] rate) {
1129         synchronized (mScanCallbackLock) {
1130             if (mScanCallbackExecutor != null && mScanCallback != null) {
1131                 mScanCallbackExecutor.execute(() -> mScanCallback.onSymbolRatesReported(rate));
1132             }
1133         }
1134     }
1135 
onHierarchy(int hierarchy)1136     private void onHierarchy(int hierarchy) {
1137         synchronized (mScanCallbackLock) {
1138             if (mScanCallbackExecutor != null && mScanCallback != null) {
1139                 mScanCallbackExecutor.execute(() -> mScanCallback.onHierarchyReported(hierarchy));
1140             }
1141         }
1142     }
1143 
onSignalType(int signalType)1144     private void onSignalType(int signalType) {
1145         synchronized (mScanCallbackLock) {
1146             if (mScanCallbackExecutor != null && mScanCallback != null) {
1147                 mScanCallbackExecutor.execute(() -> mScanCallback.onSignalTypeReported(signalType));
1148             }
1149         }
1150     }
1151 
onPlpIds(int[] plpIds)1152     private void onPlpIds(int[] plpIds) {
1153         synchronized (mScanCallbackLock) {
1154             if (mScanCallbackExecutor != null && mScanCallback != null) {
1155                 mScanCallbackExecutor.execute(() -> mScanCallback.onPlpIdsReported(plpIds));
1156             }
1157         }
1158     }
1159 
onGroupIds(int[] groupIds)1160     private void onGroupIds(int[] groupIds) {
1161         synchronized (mScanCallbackLock) {
1162             if (mScanCallbackExecutor != null && mScanCallback != null) {
1163                 mScanCallbackExecutor.execute(() -> mScanCallback.onGroupIdsReported(groupIds));
1164             }
1165         }
1166     }
1167 
onInputStreamIds(int[] inputStreamIds)1168     private void onInputStreamIds(int[] inputStreamIds) {
1169         synchronized (mScanCallbackLock) {
1170             if (mScanCallbackExecutor != null && mScanCallback != null) {
1171                 mScanCallbackExecutor.execute(
1172                         () -> mScanCallback.onInputStreamIdsReported(inputStreamIds));
1173             }
1174         }
1175     }
1176 
onDvbsStandard(int dvbsStandandard)1177     private void onDvbsStandard(int dvbsStandandard) {
1178         synchronized (mScanCallbackLock) {
1179             if (mScanCallbackExecutor != null && mScanCallback != null) {
1180                 mScanCallbackExecutor.execute(
1181                         () -> mScanCallback.onDvbsStandardReported(dvbsStandandard));
1182             }
1183         }
1184     }
1185 
onDvbtStandard(int dvbtStandard)1186     private void onDvbtStandard(int dvbtStandard) {
1187         synchronized (mScanCallbackLock) {
1188             if (mScanCallbackExecutor != null && mScanCallback != null) {
1189                 mScanCallbackExecutor.execute(
1190                         () -> mScanCallback.onDvbtStandardReported(dvbtStandard));
1191             }
1192         }
1193     }
1194 
onAnalogSifStandard(int sif)1195     private void onAnalogSifStandard(int sif) {
1196         synchronized (mScanCallbackLock) {
1197             if (mScanCallbackExecutor != null && mScanCallback != null) {
1198                 mScanCallbackExecutor.execute(() -> mScanCallback.onAnalogSifStandardReported(sif));
1199             }
1200         }
1201     }
1202 
onAtsc3PlpInfos(Atsc3PlpInfo[] atsc3PlpInfos)1203     private void onAtsc3PlpInfos(Atsc3PlpInfo[] atsc3PlpInfos) {
1204         synchronized (mScanCallbackLock) {
1205             if (mScanCallbackExecutor != null && mScanCallback != null) {
1206                 mScanCallbackExecutor.execute(
1207                         () -> mScanCallback.onAtsc3PlpInfosReported(atsc3PlpInfos));
1208             }
1209         }
1210     }
1211 
onModulationReported(int modulation)1212     private void onModulationReported(int modulation) {
1213         synchronized (mScanCallbackLock) {
1214             if (mScanCallbackExecutor != null && mScanCallback != null) {
1215                 mScanCallbackExecutor.execute(
1216                         () -> mScanCallback.onModulationReported(modulation));
1217             }
1218         }
1219     }
1220 
onPriorityReported(boolean isHighPriority)1221     private void onPriorityReported(boolean isHighPriority) {
1222         synchronized (mScanCallbackLock) {
1223             if (mScanCallbackExecutor != null && mScanCallback != null) {
1224                 mScanCallbackExecutor.execute(
1225                         () -> mScanCallback.onPriorityReported(isHighPriority));
1226             }
1227         }
1228     }
1229 
onDvbcAnnexReported(int dvbcAnnex)1230     private void onDvbcAnnexReported(int dvbcAnnex) {
1231         synchronized (mScanCallbackLock) {
1232             if (mScanCallbackExecutor != null && mScanCallback != null) {
1233                 mScanCallbackExecutor.execute(
1234                         () -> mScanCallback.onDvbcAnnexReported(dvbcAnnex));
1235             }
1236         }
1237     }
1238 
1239     /**
1240      * Opens a filter object based on the given types and buffer size.
1241      *
1242      * @param mainType the main type of the filter.
1243      * @param subType the subtype of the filter.
1244      * @param bufferSize the buffer size of the filter to be opened in bytes. The buffer holds the
1245      * data output from the filter.
1246      * @param executor the executor on which callback will be invoked. The default event handler
1247      * executor is used if it's {@code null}.
1248      * @param cb the callback to receive notifications from filter.
1249      * @return the opened filter. {@code null} if the operation failed.
1250      */
1251     @Nullable
openFilter(@ype int mainType, @Subtype int subType, @BytesLong long bufferSize, @CallbackExecutor @Nullable Executor executor, @Nullable FilterCallback cb)1252     public Filter openFilter(@Type int mainType, @Subtype int subType,
1253             @BytesLong long bufferSize, @CallbackExecutor @Nullable Executor executor,
1254             @Nullable FilterCallback cb) {
1255         if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
1256             return null;
1257         }
1258         Filter filter = nativeOpenFilter(
1259                 mainType, TunerUtils.getFilterSubtype(mainType, subType), bufferSize);
1260         if (filter != null) {
1261             filter.setType(mainType, subType);
1262             filter.setCallback(cb, executor);
1263             if (mHandler == null) {
1264                 mHandler = createEventHandler();
1265             }
1266             synchronized (mFilters) {
1267                 WeakReference<Filter> weakFilter = new WeakReference<Filter>(filter);
1268                 mFilters.add(weakFilter);
1269                 if (mFilters.size() > FILTER_CLEANUP_THRESHOLD) {
1270                     Iterator<WeakReference<Filter>> iterator = mFilters.iterator();
1271                     while (iterator.hasNext()) {
1272                         WeakReference<Filter> wFilter = iterator.next();
1273                         if (wFilter.get() == null) {
1274                             iterator.remove();
1275                         }
1276                     }
1277                 }
1278             }
1279         }
1280         return filter;
1281     }
1282 
1283     /**
1284      * Opens an LNB (low-noise block downconverter) object.
1285      *
1286      * <p>If there is an existing Lnb object, it will be replace by the newly opened one.
1287      *
1288      * @param executor the executor on which callback will be invoked. The default event handler
1289      * executor is used if it's {@code null}.
1290      * @param cb the callback to receive notifications from LNB.
1291      * @return the opened LNB object. {@code null} if the operation failed.
1292      */
1293     @Nullable
openLnb(@allbackExecutor @onNull Executor executor, @NonNull LnbCallback cb)1294     public Lnb openLnb(@CallbackExecutor @NonNull Executor executor, @NonNull LnbCallback cb) {
1295         Objects.requireNonNull(executor, "executor must not be null");
1296         Objects.requireNonNull(cb, "LnbCallback must not be null");
1297         if (mLnb != null) {
1298             mLnb.setCallback(executor, cb, this);
1299             return mLnb;
1300         }
1301         if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_LNB) && mLnb != null) {
1302             mLnb.setCallback(executor, cb, this);
1303             setLnb(mLnb);
1304             return mLnb;
1305         }
1306         return null;
1307     }
1308 
1309     /**
1310      * Opens an LNB (low-noise block downconverter) object specified by the give name.
1311      *
1312      * @param name the LNB name.
1313      * @param executor the executor on which callback will be invoked. The default event handler
1314      * executor is used if it's {@code null}.
1315      * @param cb the callback to receive notifications from LNB.
1316      * @return the opened LNB object. {@code null} if the operation failed.
1317      */
1318     @Nullable
openLnbByName(@onNull String name, @CallbackExecutor @NonNull Executor executor, @NonNull LnbCallback cb)1319     public Lnb openLnbByName(@NonNull String name, @CallbackExecutor @NonNull Executor executor,
1320             @NonNull LnbCallback cb) {
1321         Objects.requireNonNull(name, "LNB name must not be null");
1322         Objects.requireNonNull(executor, "executor must not be null");
1323         Objects.requireNonNull(cb, "LnbCallback must not be null");
1324         Lnb newLnb = nativeOpenLnbByName(name);
1325         if (newLnb != null) {
1326             if (mLnb != null) {
1327                 mLnb.close();
1328                 mLnbHandle = null;
1329             }
1330             mLnb = newLnb;
1331             mLnb.setCallback(executor, cb, this);
1332             setLnb(mLnb);
1333         }
1334         return mLnb;
1335     }
1336 
requestLnb()1337     private boolean requestLnb() {
1338         int[] lnbHandle = new int[1];
1339         TunerLnbRequest request = new TunerLnbRequest();
1340         request.clientId = mClientId;
1341         boolean granted = mTunerResourceManager.requestLnb(request, lnbHandle);
1342         if (granted) {
1343             mLnbHandle = lnbHandle[0];
1344             mLnb = nativeOpenLnbByHandle(mLnbHandle);
1345         }
1346         return granted;
1347     }
1348 
1349     /**
1350      * Open a time filter object.
1351      *
1352      * @return the opened time filter object. {@code null} if the operation failed.
1353      */
1354     @Nullable
openTimeFilter()1355     public TimeFilter openTimeFilter() {
1356         if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
1357             return null;
1358         }
1359         return nativeOpenTimeFilter();
1360     }
1361 
1362     /**
1363      * Opens a Descrambler in tuner.
1364      *
1365      * @return a {@link Descrambler} object.
1366      */
1367     @RequiresPermission(android.Manifest.permission.ACCESS_TV_DESCRAMBLER)
1368     @Nullable
openDescrambler()1369     public Descrambler openDescrambler() {
1370         if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
1371             return null;
1372         }
1373         return requestDescrambler();
1374     }
1375 
1376     /**
1377      * Open a DVR (Digital Video Record) recorder instance.
1378      *
1379      * @param bufferSize the buffer size of the output in bytes. It's used to hold output data of
1380      * the attached filters.
1381      * @param executor the executor on which callback will be invoked. The default event handler
1382      * executor is used if it's {@code null}.
1383      * @param l the listener to receive notifications from DVR recorder.
1384      * @return the opened DVR recorder object. {@code null} if the operation failed.
1385      */
1386     @Nullable
openDvrRecorder( @ytesLong long bufferSize, @CallbackExecutor @NonNull Executor executor, @NonNull OnRecordStatusChangedListener l)1387     public DvrRecorder openDvrRecorder(
1388             @BytesLong long bufferSize,
1389             @CallbackExecutor @NonNull Executor executor,
1390             @NonNull OnRecordStatusChangedListener l) {
1391         Objects.requireNonNull(executor, "executor must not be null");
1392         Objects.requireNonNull(l, "OnRecordStatusChangedListener must not be null");
1393         if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
1394             return null;
1395         }
1396         DvrRecorder dvr = nativeOpenDvrRecorder(bufferSize);
1397         dvr.setListener(executor, l);
1398         return dvr;
1399     }
1400 
1401     /**
1402      * Open a DVR (Digital Video Record) playback instance.
1403      *
1404      * @param bufferSize the buffer size of the output in bytes. It's used to hold output data of
1405      * the attached filters.
1406      * @param executor the executor on which callback will be invoked. The default event handler
1407      * executor is used if it's {@code null}.
1408      * @param l the listener to receive notifications from DVR recorder.
1409      * @return the opened DVR playback object. {@code null} if the operation failed.
1410      */
1411     @Nullable
openDvrPlayback( @ytesLong long bufferSize, @CallbackExecutor @NonNull Executor executor, @NonNull OnPlaybackStatusChangedListener l)1412     public DvrPlayback openDvrPlayback(
1413             @BytesLong long bufferSize,
1414             @CallbackExecutor @NonNull Executor executor,
1415             @NonNull OnPlaybackStatusChangedListener l) {
1416         Objects.requireNonNull(executor, "executor must not be null");
1417         Objects.requireNonNull(l, "OnPlaybackStatusChangedListener must not be null");
1418         if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
1419             return null;
1420         }
1421         DvrPlayback dvr = nativeOpenDvrPlayback(bufferSize);
1422         dvr.setListener(executor, l);
1423         return dvr;
1424     }
1425 
requestDemux()1426     private boolean requestDemux() {
1427         int[] demuxHandle = new int[1];
1428         TunerDemuxRequest request = new TunerDemuxRequest();
1429         request.clientId = mClientId;
1430         boolean granted = mTunerResourceManager.requestDemux(request, demuxHandle);
1431         if (granted) {
1432             mDemuxHandle = demuxHandle[0];
1433             nativeOpenDemuxByhandle(mDemuxHandle);
1434         }
1435         return granted;
1436     }
1437 
requestDescrambler()1438     private Descrambler requestDescrambler() {
1439         int[] descramblerHandle = new int[1];
1440         TunerDescramblerRequest request = new TunerDescramblerRequest();
1441         request.clientId = mClientId;
1442         boolean granted = mTunerResourceManager.requestDescrambler(request, descramblerHandle);
1443         if (!granted) {
1444             return null;
1445         }
1446         int handle = descramblerHandle[0];
1447         Descrambler descrambler = nativeOpenDescramblerByHandle(handle);
1448         if (descrambler != null) {
1449             synchronized (mDescramblers) {
1450                 WeakReference weakDescrambler = new WeakReference<Descrambler>(descrambler);
1451                 mDescramblers.put(handle, weakDescrambler);
1452             }
1453         } else {
1454             mTunerResourceManager.releaseDescrambler(handle, mClientId);
1455         }
1456         return descrambler;
1457     }
1458 
requestFrontendCiCam(int ciCamId)1459     private boolean requestFrontendCiCam(int ciCamId) {
1460         int[] ciCamHandle = new int[1];
1461         TunerCiCamRequest request = new TunerCiCamRequest();
1462         request.clientId = mClientId;
1463         request.ciCamId = ciCamId;
1464         boolean granted = mTunerResourceManager.requestCiCam(request, ciCamHandle);
1465         if (granted) {
1466             mFrontendCiCamHandle = ciCamHandle[0];
1467             mFrontendCiCamId = ciCamId;
1468         }
1469         return granted;
1470     }
1471 
checkResource(int resourceType)1472     private boolean checkResource(int resourceType)  {
1473         switch (resourceType) {
1474             case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND: {
1475                 if (mFrontendHandle == null && !requestFrontend()) {
1476                     return false;
1477                 }
1478                 break;
1479             }
1480             case TunerResourceManager.TUNER_RESOURCE_TYPE_LNB: {
1481                 if (mLnb == null && !requestLnb()) {
1482                     return false;
1483                 }
1484                 break;
1485             }
1486             case TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX: {
1487                 if (mDemuxHandle == null && !requestDemux()) {
1488                     return false;
1489                 }
1490                 break;
1491             }
1492             default:
1493                 return false;
1494         }
1495         return true;
1496     }
1497 
checkCiCamResource(int ciCamId)1498     private boolean checkCiCamResource(int ciCamId) {
1499         if (mFrontendCiCamHandle == null && !requestFrontendCiCam(ciCamId)) {
1500             return false;
1501         }
1502         return true;
1503     }
1504 
releaseLnb()1505     /* package */ void releaseLnb() {
1506         if (mLnbHandle != null) {
1507             // LNB handle can be null if it's opened by name.
1508             mTunerResourceManager.releaseLnb(mLnbHandle, mClientId);
1509             mLnbHandle = null;
1510         }
1511         mLnb = null;
1512     }
1513 }
1514