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.SystemApi;
26 import android.app.ActivityManager;
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.TunerDemuxRequest;
48 import android.media.tv.tunerresourcemanager.TunerDescramblerRequest;
49 import android.media.tv.tunerresourcemanager.TunerFrontendInfo;
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.util.Log;
58 
59 import com.android.internal.util.FrameworkStatsLog;
60 
61 import java.lang.annotation.Retention;
62 import java.lang.annotation.RetentionPolicy;
63 import java.util.ArrayList;
64 import java.util.HashMap;
65 import java.util.List;
66 import java.util.Map;
67 import java.util.Objects;
68 import java.util.concurrent.Executor;
69 
70 /**
71  * This class is used to interact with hardware tuners devices.
72  *
73  * <p> Each TvInputService Session should create one instance of this class.
74  *
75  * <p> This class controls the TIS interaction with Tuner HAL.
76  *
77  * @hide
78  */
79 @SystemApi
80 public class Tuner implements AutoCloseable  {
81     /**
82      * Invalid TS packet ID.
83      */
84     public static final int INVALID_TS_PID = Constants.Constant.INVALID_TS_PID;
85     /**
86      * Invalid stream ID.
87      */
88     public static final int INVALID_STREAM_ID = Constants.Constant.INVALID_STREAM_ID;
89     /**
90      * Invalid filter ID.
91      */
92     public static final int INVALID_FILTER_ID = Constants.Constant.INVALID_FILTER_ID;
93     /**
94      * Invalid AV Sync ID.
95      */
96     public static final int INVALID_AV_SYNC_ID = Constants.Constant.INVALID_AV_SYNC_ID;
97     /**
98      * Invalid timestamp.
99      *
100      * <p>Returned by {@link android.media.tv.tuner.filter.TimeFilter#getSourceTime()},
101      * {@link android.media.tv.tuner.filter.TimeFilter#getTimeStamp()}, or
102      * {@link Tuner#getAvSyncTime(int)} when the requested timestamp is not available.
103      *
104      * @see android.media.tv.tuner.filter.TimeFilter#getSourceTime()
105      * @see android.media.tv.tuner.filter.TimeFilter#getTimeStamp()
106      * @see Tuner#getAvSyncTime(int)
107      */
108     public static final long INVALID_TIMESTAMP = -1L;
109 
110 
111     /** @hide */
112     @IntDef(prefix = "SCAN_TYPE_", value = {SCAN_TYPE_UNDEFINED, SCAN_TYPE_AUTO, SCAN_TYPE_BLIND})
113     @Retention(RetentionPolicy.SOURCE)
114     public @interface ScanType {}
115     /**
116      * Scan type undefined.
117      */
118     public static final int SCAN_TYPE_UNDEFINED = Constants.FrontendScanType.SCAN_UNDEFINED;
119     /**
120      * Scan type auto.
121      *
122      * <p> Tuner will send {@link android.media.tv.tuner.frontend.ScanCallback#onLocked}
123      */
124     public static final int SCAN_TYPE_AUTO = Constants.FrontendScanType.SCAN_AUTO;
125     /**
126      * Blind scan.
127      *
128      * <p>Frequency range is not specified. The {@link android.media.tv.tuner.Tuner} will scan an
129      * implementation specific range.
130      */
131     public static final int SCAN_TYPE_BLIND = Constants.FrontendScanType.SCAN_BLIND;
132 
133 
134     /** @hide */
135     @IntDef({RESULT_SUCCESS, RESULT_UNAVAILABLE, RESULT_NOT_INITIALIZED, RESULT_INVALID_STATE,
136             RESULT_INVALID_ARGUMENT, RESULT_OUT_OF_MEMORY, RESULT_UNKNOWN_ERROR})
137     @Retention(RetentionPolicy.SOURCE)
138     public @interface Result {}
139 
140     /**
141      * Operation succeeded.
142      */
143     public static final int RESULT_SUCCESS = Constants.Result.SUCCESS;
144     /**
145      * Operation failed because the corresponding resources are not available.
146      */
147     public static final int RESULT_UNAVAILABLE = Constants.Result.UNAVAILABLE;
148     /**
149      * Operation failed because the corresponding resources are not initialized.
150      */
151     public static final int RESULT_NOT_INITIALIZED = Constants.Result.NOT_INITIALIZED;
152     /**
153      * Operation failed because it's not in a valid state.
154      */
155     public static final int RESULT_INVALID_STATE = Constants.Result.INVALID_STATE;
156     /**
157      * Operation failed because there are invalid arguments.
158      */
159     public static final int RESULT_INVALID_ARGUMENT = Constants.Result.INVALID_ARGUMENT;
160     /**
161      * Memory allocation failed.
162      */
163     public static final int RESULT_OUT_OF_MEMORY = Constants.Result.OUT_OF_MEMORY;
164     /**
165      * Operation failed due to unknown errors.
166      */
167     public static final int RESULT_UNKNOWN_ERROR = Constants.Result.UNKNOWN_ERROR;
168 
169 
170 
171     private static final String TAG = "MediaTvTuner";
172     private static final boolean DEBUG = false;
173 
174     private static final int MSG_RESOURCE_LOST = 1;
175     private static final int MSG_ON_FILTER_EVENT = 2;
176     private static final int MSG_ON_FILTER_STATUS = 3;
177     private static final int MSG_ON_LNB_EVENT = 4;
178 
179     /** @hide */
180     @IntDef(prefix = "DVR_TYPE_", value = {DVR_TYPE_RECORD, DVR_TYPE_PLAYBACK})
181     @Retention(RetentionPolicy.SOURCE)
182     public @interface DvrType {}
183 
184     /**
185      * DVR for recording.
186      * @hide
187      */
188     public static final int DVR_TYPE_RECORD = Constants.DvrType.RECORD;
189     /**
190      * DVR for playback of recorded programs.
191      * @hide
192      */
193     public static final int DVR_TYPE_PLAYBACK = Constants.DvrType.PLAYBACK;
194 
195     static {
196         try {
197             System.loadLibrary("media_tv_tuner");
nativeInit()198             nativeInit();
199         } catch (UnsatisfiedLinkError e) {
200             Log.d(TAG, "tuner JNI library not found!");
201         }
202     }
203 
204     private final Context mContext;
205     private final TunerResourceManager mTunerResourceManager;
206     private final int mClientId;
207 
208     private Frontend mFrontend;
209     private EventHandler mHandler;
210     @Nullable
211     private FrontendInfo mFrontendInfo;
212     private Integer mFrontendHandle;
213     private int mFrontendType = FrontendSettings.TYPE_UNDEFINED;
214     private int mUserId;
215     private Lnb mLnb;
216     private Integer mLnbHandle;
217     @Nullable
218     private OnTuneEventListener mOnTuneEventListener;
219     @Nullable
220     private Executor mOnTunerEventExecutor;
221     @Nullable
222     private ScanCallback mScanCallback;
223     @Nullable
224     private Executor mScanCallbackExecutor;
225     @Nullable
226     private OnResourceLostListener mOnResourceLostListener;
227     @Nullable
228     private Executor mOnResourceLostListenerExecutor;
229 
230     private Integer mDemuxHandle;
231     private Map<Integer, Descrambler> mDescramblers = new HashMap<>();
232     private List<Filter> mFilters = new ArrayList<>();
233 
234     private final TunerResourceManager.ResourcesReclaimListener mResourceListener =
235             new TunerResourceManager.ResourcesReclaimListener() {
236                 @Override
237                 public void onReclaimResources() {
238                     if (mFrontend != null) {
239                         FrameworkStatsLog
240                                 .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
241                                     FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__UNKNOWN);
242                     }
243                     mHandler.sendMessage(mHandler.obtainMessage(MSG_RESOURCE_LOST));
244                 }
245             };
246 
247     /**
248      * Constructs a Tuner instance.
249      *
250      * @param context the context of the caller.
251      * @param tvInputSessionId the session ID of the TV input.
252      * @param useCase the use case of this Tuner instance.
253      */
254     @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
Tuner(@onNull Context context, @Nullable String tvInputSessionId, @TvInputService.PriorityHintUseCaseType int useCase)255     public Tuner(@NonNull Context context, @Nullable String tvInputSessionId,
256             @TvInputService.PriorityHintUseCaseType int useCase) {
257         nativeSetup();
258         mContext = context;
259         mTunerResourceManager = (TunerResourceManager)
260                 context.getSystemService(Context.TV_TUNER_RESOURCE_MGR_SERVICE);
261         if (mHandler == null) {
262             mHandler = createEventHandler();
263         }
264 
265         mHandler = createEventHandler();
266         int[] clientId = new int[1];
267         ResourceClientProfile profile = new ResourceClientProfile(tvInputSessionId, useCase);
268         mTunerResourceManager.registerClientProfile(
269                 profile, new HandlerExecutor(mHandler), mResourceListener, clientId);
270         mClientId = clientId[0];
271 
272         mUserId = ActivityManager.getCurrentUser();
273 
274         setFrontendInfoList();
275         setLnbIds();
276     }
277 
setFrontendInfoList()278     private void setFrontendInfoList() {
279         List<Integer> ids = getFrontendIds();
280         if (ids == null) {
281             return;
282         }
283         TunerFrontendInfo[] infos = new TunerFrontendInfo[ids.size()];
284         for (int i = 0; i < ids.size(); i++) {
285             int id = ids.get(i);
286             FrontendInfo frontendInfo = getFrontendInfoById(id);
287             if (frontendInfo == null) {
288                 continue;
289             }
290             TunerFrontendInfo tunerFrontendInfo = new TunerFrontendInfo(
291                     id, frontendInfo.getType(), frontendInfo.getExclusiveGroupId());
292             infos[i] = tunerFrontendInfo;
293         }
294         mTunerResourceManager.setFrontendInfoList(infos);
295     }
296 
297     /** @hide */
getFrontendIds()298     public List<Integer> getFrontendIds() {
299         return nativeGetFrontendIds();
300     }
301 
setLnbIds()302     private void setLnbIds() {
303         int[] ids = nativeGetLnbIds();
304         if (ids == null) {
305             return;
306         }
307         mTunerResourceManager.setLnbInfoList(ids);
308     }
309 
310     /**
311      * Sets the listener for resource lost.
312      *
313      * @param executor the executor on which the listener should be invoked.
314      * @param listener the listener that will be run.
315      */
setResourceLostListener(@onNull @allbackExecutor Executor executor, @NonNull OnResourceLostListener listener)316     public void setResourceLostListener(@NonNull @CallbackExecutor Executor executor,
317             @NonNull OnResourceLostListener listener) {
318         Objects.requireNonNull(executor, "OnResourceLostListener must not be null");
319         Objects.requireNonNull(listener, "executor must not be null");
320         mOnResourceLostListener = listener;
321         mOnResourceLostListenerExecutor = executor;
322     }
323 
324     /**
325      * Removes the listener for resource lost.
326      */
clearResourceLostListener()327     public void clearResourceLostListener() {
328         mOnResourceLostListener = null;
329         mOnResourceLostListenerExecutor = null;
330     }
331 
332     /**
333      * Shares the frontend resource with another Tuner instance
334      *
335      * @param tuner the Tuner instance to share frontend resource with.
336      */
shareFrontendFromTuner(@onNull Tuner tuner)337     public void shareFrontendFromTuner(@NonNull Tuner tuner) {
338         mTunerResourceManager.shareFrontend(mClientId, tuner.mClientId);
339         mFrontendHandle = tuner.mFrontendHandle;
340         mFrontend = nativeOpenFrontendByHandle(mFrontendHandle);
341     }
342 
343     /**
344      * Updates client priority with an arbitrary value along with a nice value.
345      *
346      * <p>Tuner resource manager (TRM) uses the client priority value to decide whether it is able
347      * to reclaim insufficient resources from another client.
348      * <p>The nice value represents how much the client intends to give up the resource when an
349      * insufficient resource situation happens.
350      *
351      * @param priority the new priority.
352      * @param niceValue the nice value.
353      */
updateResourcePriority(int priority, int niceValue)354     public void updateResourcePriority(int priority, int niceValue) {
355         mTunerResourceManager.updateClientPriority(mClientId, priority, niceValue);
356     }
357 
358     private long mNativeContext; // used by native jMediaTuner
359 
360     /**
361      * Releases the Tuner instance.
362      */
363     @Override
close()364     public void close() {
365         if (mFrontendHandle != null) {
366             int res = nativeCloseFrontend(mFrontendHandle);
367             if (res != Tuner.RESULT_SUCCESS) {
368                 TunerUtils.throwExceptionForResult(res, "failed to close frontend");
369             }
370             mTunerResourceManager.releaseFrontend(mFrontendHandle, mClientId);
371             FrameworkStatsLog
372                     .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
373                         FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__UNKNOWN);
374             mFrontendHandle = null;
375             mFrontend = null;
376         }
377         if (mLnb != null) {
378             mLnb.close();
379         }
380         if (!mDescramblers.isEmpty()) {
381             for (Map.Entry<Integer, Descrambler> d : mDescramblers.entrySet()) {
382                 d.getValue().close();
383                 mTunerResourceManager.releaseDescrambler(d.getKey(), mClientId);
384             }
385             mDescramblers.clear();
386         }
387         if (!mFilters.isEmpty()) {
388             for (Filter f : mFilters) {
389                 f.close();
390             }
391             mFilters.clear();
392         }
393         if (mDemuxHandle != null) {
394             int res = nativeCloseDemux(mDemuxHandle);
395             if (res != Tuner.RESULT_SUCCESS) {
396                 TunerUtils.throwExceptionForResult(res, "failed to close demux");
397             }
398             mTunerResourceManager.releaseDemux(mDemuxHandle, mClientId);
399             mFrontendHandle = null;
400         }
401         TunerUtils.throwExceptionForResult(nativeClose(), "failed to close tuner");
402     }
403 
404     /**
405      * Native Initialization.
406      */
nativeInit()407     private static native void nativeInit();
408 
409     /**
410      * Native setup.
411      */
nativeSetup()412     private native void nativeSetup();
413 
414     /**
415      * Native method to get all frontend IDs.
416      */
nativeGetFrontendIds()417     private native List<Integer> nativeGetFrontendIds();
418 
419     /**
420      * Native method to open frontend of the given ID.
421      */
nativeOpenFrontendByHandle(int handle)422     private native Frontend nativeOpenFrontendByHandle(int handle);
423     @Result
nativeCloseFrontendByHandle(int handle)424     private native int nativeCloseFrontendByHandle(int handle);
nativeTune(int type, FrontendSettings settings)425     private native int nativeTune(int type, FrontendSettings settings);
nativeStopTune()426     private native int nativeStopTune();
nativeScan(int settingsType, FrontendSettings settings, int scanType)427     private native int nativeScan(int settingsType, FrontendSettings settings, int scanType);
nativeStopScan()428     private native int nativeStopScan();
nativeSetLnb(int lnbId)429     private native int nativeSetLnb(int lnbId);
nativeSetLna(boolean enable)430     private native int nativeSetLna(boolean enable);
nativeGetFrontendStatus(int[] statusTypes)431     private native FrontendStatus nativeGetFrontendStatus(int[] statusTypes);
nativeGetAvSyncHwId(Filter filter)432     private native Integer nativeGetAvSyncHwId(Filter filter);
nativeGetAvSyncTime(int avSyncId)433     private native Long nativeGetAvSyncTime(int avSyncId);
nativeConnectCiCam(int ciCamId)434     private native int nativeConnectCiCam(int ciCamId);
nativeDisconnectCiCam()435     private native int nativeDisconnectCiCam();
nativeGetFrontendInfo(int id)436     private native FrontendInfo nativeGetFrontendInfo(int id);
nativeOpenFilter(int type, int subType, long bufferSize)437     private native Filter nativeOpenFilter(int type, int subType, long bufferSize);
nativeOpenTimeFilter()438     private native TimeFilter nativeOpenTimeFilter();
439 
nativeGetLnbIds()440     private native int[] nativeGetLnbIds();
nativeOpenLnbByHandle(int handle)441     private native Lnb nativeOpenLnbByHandle(int handle);
nativeOpenLnbByName(String name)442     private native Lnb nativeOpenLnbByName(String name);
443 
nativeOpenDescramblerByHandle(int handle)444     private native Descrambler nativeOpenDescramblerByHandle(int handle);
nativeOpenDemuxByhandle(int handle)445     private native int nativeOpenDemuxByhandle(int handle);
446 
nativeOpenDvrRecorder(long bufferSize)447     private native DvrRecorder nativeOpenDvrRecorder(long bufferSize);
nativeOpenDvrPlayback(long bufferSize)448     private native DvrPlayback nativeOpenDvrPlayback(long bufferSize);
449 
nativeGetDemuxCapabilities()450     private native DemuxCapabilities nativeGetDemuxCapabilities();
451 
nativeCloseDemux(int handle)452     private native int nativeCloseDemux(int handle);
nativeCloseFrontend(int handle)453     private native int nativeCloseFrontend(int handle);
nativeClose()454     private native int nativeClose();
455 
456 
457     /**
458      * Listener for resource lost.
459      *
460      * <p>Insufficient resources are reclaimed by higher priority clients.
461      */
462     public interface OnResourceLostListener {
463         /**
464          * Invoked when resource lost.
465          *
466          * @param tuner the tuner instance whose resource is being reclaimed.
467          */
onResourceLost(@onNull Tuner tuner)468         void onResourceLost(@NonNull Tuner tuner);
469     }
470 
471     @Nullable
createEventHandler()472     private EventHandler createEventHandler() {
473         Looper looper;
474         if ((looper = Looper.myLooper()) != null) {
475             return new EventHandler(looper);
476         } else if ((looper = Looper.getMainLooper()) != null) {
477             return new EventHandler(looper);
478         }
479         return null;
480     }
481 
482     private class EventHandler extends Handler {
EventHandler(Looper looper)483         private EventHandler(Looper looper) {
484             super(looper);
485         }
486 
487         @Override
handleMessage(Message msg)488         public void handleMessage(Message msg) {
489             switch (msg.what) {
490                 case MSG_ON_FILTER_STATUS: {
491                     Filter filter = (Filter) msg.obj;
492                     if (filter.getCallback() != null) {
493                         filter.getCallback().onFilterStatusChanged(filter, msg.arg1);
494                     }
495                     break;
496                 }
497                 case MSG_RESOURCE_LOST: {
498                     if (mOnResourceLostListener != null
499                                 && mOnResourceLostListenerExecutor != null) {
500                         mOnResourceLostListenerExecutor.execute(
501                                 () -> mOnResourceLostListener.onResourceLost(Tuner.this));
502                     }
503                     break;
504                 }
505                 default:
506                     // fall through
507             }
508         }
509     }
510 
511     private class Frontend {
512         private int mId;
513 
Frontend(int id)514         private Frontend(int id) {
515             mId = id;
516         }
517     }
518 
519     /**
520      * Listens for tune events.
521      *
522      * <p>
523      * Tuner events are started when {@link #tune(FrontendSettings)} is called and end when {@link
524      * #cancelTuning()} is called.
525      *
526      * @param eventListener receives tune events.
527      * @throws SecurityException if the caller does not have appropriate permissions.
528      * @see #tune(FrontendSettings)
529      */
setOnTuneEventListener(@onNull @allbackExecutor Executor executor, @NonNull OnTuneEventListener eventListener)530     public void setOnTuneEventListener(@NonNull @CallbackExecutor Executor executor,
531             @NonNull OnTuneEventListener eventListener) {
532         mOnTuneEventListener = eventListener;
533         mOnTunerEventExecutor = executor;
534     }
535 
536     /**
537      * Clears the {@link OnTuneEventListener} and its associated {@link Executor}.
538      *
539      * @throws SecurityException if the caller does not have appropriate permissions.
540      * @see #setOnTuneEventListener(Executor, OnTuneEventListener)
541      */
clearOnTuneEventListener()542     public void clearOnTuneEventListener() {
543         mOnTuneEventListener = null;
544         mOnTunerEventExecutor = null;
545 
546     }
547 
548     /**
549      * Tunes the frontend to using the settings given.
550      *
551      * <p>Tuner resource manager (TRM) uses the client priority value to decide whether it is able
552      * to get frontend resource. If the client can't get the resource, this call returns {@link
553      * Result#RESULT_UNAVAILABLE}.
554      *
555      * <p>
556      * This locks the frontend to a frequency by providing signal
557      * delivery information. If previous tuning isn't completed, this stop the previous tuning, and
558      * start a new tuning.
559      *
560      * <p>
561      * Tune is an async call, with {@link OnTuneEventListener#SIGNAL_LOCKED} and {@link
562      * OnTuneEventListener#SIGNAL_NO_SIGNAL} events sent to the {@link OnTuneEventListener}
563      * specified in {@link #setOnTuneEventListener(Executor, OnTuneEventListener)}.
564      *
565      * @param settings Signal delivery information the frontend uses to
566      *                 search and lock the signal.
567      * @return result status of tune operation.
568      * @throws SecurityException if the caller does not have appropriate permissions.
569      * @see #setOnTuneEventListener(Executor, OnTuneEventListener)
570      */
571     @Result
tune(@onNull FrontendSettings settings)572     public int tune(@NonNull FrontendSettings settings) {
573         Log.d(TAG, "Tune to " + settings.getFrequency());
574         mFrontendType = settings.getType();
575         if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
576             mFrontendInfo = null;
577             Log.d(TAG, "Write Stats Log for tuning.");
578             FrameworkStatsLog
579                     .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
580                         FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__TUNING);
581             return nativeTune(settings.getType(), settings);
582         }
583         return RESULT_UNAVAILABLE;
584     }
585 
586     /**
587      * Stops a previous tuning.
588      *
589      * <p>If the method completes successfully, the frontend is no longer tuned and no data
590      * will be sent to attached filters.
591      *
592      * @return result status of the operation.
593      */
594     @Result
cancelTuning()595     public int cancelTuning() {
596         return nativeStopTune();
597     }
598 
599     /**
600      * Scan for channels.
601      *
602      * <p>Details for channels found are returned via {@link ScanCallback}.
603      *
604      * @param settings A {@link FrontendSettings} to configure the frontend.
605      * @param scanType The scan type.
606      * @throws SecurityException     if the caller does not have appropriate permissions.
607      * @throws IllegalStateException if {@code scan} is called again before
608      *                               {@link #cancelScanning()} is called.
609      */
610     @Result
scan(@onNull FrontendSettings settings, @ScanType int scanType, @NonNull @CallbackExecutor Executor executor, @NonNull ScanCallback scanCallback)611     public int scan(@NonNull FrontendSettings settings, @ScanType int scanType,
612             @NonNull @CallbackExecutor Executor executor, @NonNull ScanCallback scanCallback) {
613         if (mScanCallback != null || mScanCallbackExecutor != null) {
614             throw new IllegalStateException(
615                     "Scan already in progress.  stopScan must be called before a new scan can be "
616                             + "started.");
617         }
618         mFrontendType = settings.getType();
619         if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
620             mScanCallback = scanCallback;
621             mScanCallbackExecutor = executor;
622             mFrontendInfo = null;
623             FrameworkStatsLog
624                     .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
625                         FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__SCANNING);
626             return nativeScan(settings.getType(), settings, scanType);
627         }
628         return RESULT_UNAVAILABLE;
629     }
630 
631     /**
632      * Stops a previous scanning.
633      *
634      * <p>
635      * The {@link ScanCallback} and it's {@link Executor} will be removed.
636      *
637      * <p>
638      * If the method completes successfully, the frontend stopped previous scanning.
639      *
640      * @throws SecurityException if the caller does not have appropriate permissions.
641      */
642     @Result
cancelScanning()643     public int cancelScanning() {
644         FrameworkStatsLog
645                 .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
646                     FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__SCAN_STOPPED);
647 
648         int retVal = nativeStopScan();
649         mScanCallback = null;
650         mScanCallbackExecutor = null;
651         return retVal;
652     }
653 
requestFrontend()654     private boolean requestFrontend() {
655         int[] feHandle = new int[1];
656         TunerFrontendRequest request = new TunerFrontendRequest(mClientId, mFrontendType);
657         boolean granted = mTunerResourceManager.requestFrontend(request, feHandle);
658         if (granted) {
659             mFrontendHandle = feHandle[0];
660             mFrontend = nativeOpenFrontendByHandle(mFrontendHandle);
661         }
662         return granted;
663     }
664 
665     /**
666      * Sets Low-Noise Block downconverter (LNB) for satellite frontend.
667      *
668      * <p>This assigns a hardware LNB resource to the satellite tuner. It can be
669      * called multiple times to update LNB assignment.
670      *
671      * @param lnb the LNB instance.
672      *
673      * @return result status of the operation.
674      */
675     @Result
setLnb(@onNull Lnb lnb)676     private int setLnb(@NonNull Lnb lnb) {
677         return nativeSetLnb(lnb.mId);
678     }
679 
680     /**
681      * Enable or Disable Low Noise Amplifier (LNA).
682      *
683      * @param enable {@code true} to activate LNA module; {@code false} to deactivate LNA.
684      *
685      * @return result status of the operation.
686      */
687     @Result
setLnaEnabled(boolean enable)688     public int setLnaEnabled(boolean enable) {
689         return nativeSetLna(enable);
690     }
691 
692     /**
693      * Gets the statuses of the frontend.
694      *
695      * <p>This retrieve the statuses of the frontend for given status types.
696      *
697      * @param statusTypes an array of status types which the caller requests.
698      * @return statuses which response the caller's requests. {@code null} if the operation failed.
699      */
700     @Nullable
getFrontendStatus(@onNull @rontendStatusType int[] statusTypes)701     public FrontendStatus getFrontendStatus(@NonNull @FrontendStatusType int[] statusTypes) {
702         if (mFrontend == null) {
703             throw new IllegalStateException("frontend is not initialized");
704         }
705         return nativeGetFrontendStatus(statusTypes);
706     }
707 
708     /**
709      * Gets hardware sync ID for audio and video.
710      *
711      * @param filter the filter instance for the hardware sync ID.
712      * @return the id of hardware A/V sync.
713      */
getAvSyncHwId(@onNull Filter filter)714     public int getAvSyncHwId(@NonNull Filter filter) {
715         if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
716             return INVALID_AV_SYNC_ID;
717         }
718         Integer id = nativeGetAvSyncHwId(filter);
719         return id == null ? INVALID_AV_SYNC_ID : id;
720     }
721 
722     /**
723      * Gets the current timestamp for Audio/Video sync
724      *
725      * <p>The timestamp is maintained by hardware. The timestamp based on 90KHz, and it's format is
726      * the same as PTS (Presentation Time Stamp).
727      *
728      * @param avSyncHwId the hardware id of A/V sync.
729      * @return the current timestamp of hardware A/V sync.
730      */
getAvSyncTime(int avSyncHwId)731     public long getAvSyncTime(int avSyncHwId) {
732         if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
733             return INVALID_TIMESTAMP;
734         }
735         Long time = nativeGetAvSyncTime(avSyncHwId);
736         return time == null ? INVALID_TIMESTAMP : time;
737     }
738 
739     /**
740      * Connects Conditional Access Modules (CAM) through Common Interface (CI)
741      *
742      * <p>The demux uses the output from the frontend as the input by default, and must change to
743      * use the output from CI-CAM as the input after this call.
744      *
745      * @param ciCamId specify CI-CAM Id to connect.
746      * @return result status of the operation.
747      */
748     @Result
connectCiCam(int ciCamId)749     public int connectCiCam(int ciCamId) {
750         if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
751             return nativeConnectCiCam(ciCamId);
752         }
753         return RESULT_UNAVAILABLE;
754     }
755 
756     /**
757      * Disconnects Conditional Access Modules (CAM)
758      *
759      * <p>The demux will use the output from the frontend as the input after this call.
760      *
761      * @return result status of the operation.
762      */
763     @Result
disconnectCiCam()764     public int disconnectCiCam() {
765         if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
766             return nativeDisconnectCiCam();
767         }
768         return RESULT_UNAVAILABLE;
769     }
770 
771     /**
772      * Gets the frontend information.
773      *
774      * @return The frontend information. {@code null} if the operation failed.
775      */
776     @Nullable
getFrontendInfo()777     public FrontendInfo getFrontendInfo() {
778         if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
779             return null;
780         }
781         if (mFrontend == null) {
782             throw new IllegalStateException("frontend is not initialized");
783         }
784         if (mFrontendInfo == null) {
785             mFrontendInfo = getFrontendInfoById(mFrontend.mId);
786         }
787         return mFrontendInfo;
788     }
789 
790     /** @hide */
getFrontendInfoById(int id)791     public FrontendInfo getFrontendInfoById(int id) {
792         return nativeGetFrontendInfo(id);
793     }
794 
795     /**
796      * Gets Demux capabilities.
797      *
798      * @return A {@link DemuxCapabilities} instance that represents the demux capabilities.
799      *         {@code null} if the operation failed.
800      */
801     @Nullable
getDemuxCapabilities()802     public DemuxCapabilities getDemuxCapabilities() {
803         return nativeGetDemuxCapabilities();
804     }
805 
onFrontendEvent(int eventType)806     private void onFrontendEvent(int eventType) {
807         Log.d(TAG, "Got event from tuning. Event type: " + eventType);
808         if (mOnTunerEventExecutor != null && mOnTuneEventListener != null) {
809             mOnTunerEventExecutor.execute(() -> mOnTuneEventListener.onTuneEvent(eventType));
810         }
811 
812         Log.d(TAG, "Wrote Stats Log for the events from tuning.");
813         if (eventType == OnTuneEventListener.SIGNAL_LOCKED) {
814             FrameworkStatsLog
815                     .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
816                         FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__LOCKED);
817         } else if (eventType == OnTuneEventListener.SIGNAL_NO_SIGNAL) {
818             FrameworkStatsLog
819                     .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
820                         FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__NOT_LOCKED);
821         } else if (eventType == OnTuneEventListener.SIGNAL_LOST_LOCK) {
822             FrameworkStatsLog
823                     .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
824                         FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__SIGNAL_LOST);
825         }
826     }
827 
onLocked()828     private void onLocked() {
829         Log.d(TAG, "Wrote Stats Log for locked event from scanning.");
830         FrameworkStatsLog
831                 .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
832                     FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__LOCKED);
833 
834         if (mScanCallbackExecutor != null && mScanCallback != null) {
835             mScanCallbackExecutor.execute(() -> mScanCallback.onLocked());
836         }
837     }
838 
onScanStopped()839     private void onScanStopped() {
840         if (mScanCallbackExecutor != null && mScanCallback != null) {
841             mScanCallbackExecutor.execute(() -> mScanCallback.onScanStopped());
842         }
843     }
844 
onProgress(int percent)845     private void onProgress(int percent) {
846         if (mScanCallbackExecutor != null && mScanCallback != null) {
847             mScanCallbackExecutor.execute(() -> mScanCallback.onProgress(percent));
848         }
849     }
850 
onFrequenciesReport(int[] frequency)851     private void onFrequenciesReport(int[] frequency) {
852         if (mScanCallbackExecutor != null && mScanCallback != null) {
853             mScanCallbackExecutor.execute(() -> mScanCallback.onFrequenciesReported(frequency));
854         }
855     }
856 
onSymbolRates(int[] rate)857     private void onSymbolRates(int[] rate) {
858         if (mScanCallbackExecutor != null && mScanCallback != null) {
859             mScanCallbackExecutor.execute(() -> mScanCallback.onSymbolRatesReported(rate));
860         }
861     }
862 
onHierarchy(int hierarchy)863     private void onHierarchy(int hierarchy) {
864         if (mScanCallbackExecutor != null && mScanCallback != null) {
865             mScanCallbackExecutor.execute(() -> mScanCallback.onHierarchyReported(hierarchy));
866         }
867     }
868 
onSignalType(int signalType)869     private void onSignalType(int signalType) {
870         if (mScanCallbackExecutor != null && mScanCallback != null) {
871             mScanCallbackExecutor.execute(() -> mScanCallback.onSignalTypeReported(signalType));
872         }
873     }
874 
onPlpIds(int[] plpIds)875     private void onPlpIds(int[] plpIds) {
876         if (mScanCallbackExecutor != null && mScanCallback != null) {
877             mScanCallbackExecutor.execute(() -> mScanCallback.onPlpIdsReported(plpIds));
878         }
879     }
880 
onGroupIds(int[] groupIds)881     private void onGroupIds(int[] groupIds) {
882         if (mScanCallbackExecutor != null && mScanCallback != null) {
883             mScanCallbackExecutor.execute(() -> mScanCallback.onGroupIdsReported(groupIds));
884         }
885     }
886 
onInputStreamIds(int[] inputStreamIds)887     private void onInputStreamIds(int[] inputStreamIds) {
888         if (mScanCallbackExecutor != null && mScanCallback != null) {
889             mScanCallbackExecutor.execute(
890                     () -> mScanCallback.onInputStreamIdsReported(inputStreamIds));
891         }
892     }
893 
onDvbsStandard(int dvbsStandandard)894     private void onDvbsStandard(int dvbsStandandard) {
895         if (mScanCallbackExecutor != null && mScanCallback != null) {
896             mScanCallbackExecutor.execute(
897                     () -> mScanCallback.onDvbsStandardReported(dvbsStandandard));
898         }
899     }
900 
onDvbtStandard(int dvbtStandard)901     private void onDvbtStandard(int dvbtStandard) {
902         if (mScanCallbackExecutor != null && mScanCallback != null) {
903             mScanCallbackExecutor.execute(() -> mScanCallback.onDvbtStandardReported(dvbtStandard));
904         }
905     }
906 
onAnalogSifStandard(int sif)907     private void onAnalogSifStandard(int sif) {
908         if (mScanCallbackExecutor != null && mScanCallback != null) {
909             mScanCallbackExecutor.execute(() -> mScanCallback.onAnalogSifStandardReported(sif));
910         }
911     }
912 
onAtsc3PlpInfos(Atsc3PlpInfo[] atsc3PlpInfos)913     private void onAtsc3PlpInfos(Atsc3PlpInfo[] atsc3PlpInfos) {
914         if (mScanCallbackExecutor != null && mScanCallback != null) {
915             mScanCallbackExecutor.execute(
916                     () -> mScanCallback.onAtsc3PlpInfosReported(atsc3PlpInfos));
917         }
918     }
919 
920     /**
921      * Opens a filter object based on the given types and buffer size.
922      *
923      * @param mainType the main type of the filter.
924      * @param subType the subtype of the filter.
925      * @param bufferSize the buffer size of the filter to be opened in bytes. The buffer holds the
926      * data output from the filter.
927      * @param executor the executor on which callback will be invoked. The default event handler
928      * executor is used if it's {@code null}.
929      * @param cb the callback to receive notifications from filter.
930      * @return the opened filter. {@code null} if the operation failed.
931      */
932     @Nullable
openFilter(@ype int mainType, @Subtype int subType, @BytesLong long bufferSize, @CallbackExecutor @Nullable Executor executor, @Nullable FilterCallback cb)933     public Filter openFilter(@Type int mainType, @Subtype int subType,
934             @BytesLong long bufferSize, @CallbackExecutor @Nullable Executor executor,
935             @Nullable FilterCallback cb) {
936         if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
937             return null;
938         }
939         Filter filter = nativeOpenFilter(
940                 mainType, TunerUtils.getFilterSubtype(mainType, subType), bufferSize);
941         if (filter != null) {
942             filter.setType(mainType, subType);
943             filter.setCallback(cb, executor);
944             if (mHandler == null) {
945                 mHandler = createEventHandler();
946             }
947             mFilters.add(filter);
948         }
949         return filter;
950     }
951 
952     /**
953      * Opens an LNB (low-noise block downconverter) object.
954      *
955      * <p>If there is an existing Lnb object, it will be replace by the newly opened one.
956      *
957      * @param executor the executor on which callback will be invoked. The default event handler
958      * executor is used if it's {@code null}.
959      * @param cb the callback to receive notifications from LNB.
960      * @return the opened LNB object. {@code null} if the operation failed.
961      */
962     @Nullable
openLnb(@allbackExecutor @onNull Executor executor, @NonNull LnbCallback cb)963     public Lnb openLnb(@CallbackExecutor @NonNull Executor executor, @NonNull LnbCallback cb) {
964         Objects.requireNonNull(executor, "executor must not be null");
965         Objects.requireNonNull(cb, "LnbCallback must not be null");
966         if (mLnb != null) {
967             mLnb.setCallback(executor, cb, this);
968             return mLnb;
969         }
970         if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_LNB) && mLnb != null) {
971             mLnb.setCallback(executor, cb, this);
972             setLnb(mLnb);
973             return mLnb;
974         }
975         return null;
976     }
977 
978     /**
979      * Opens an LNB (low-noise block downconverter) object specified by the give name.
980      *
981      * @param name the LNB name.
982      * @param executor the executor on which callback will be invoked. The default event handler
983      * executor is used if it's {@code null}.
984      * @param cb the callback to receive notifications from LNB.
985      * @return the opened LNB object. {@code null} if the operation failed.
986      */
987     @Nullable
openLnbByName(@onNull String name, @CallbackExecutor @NonNull Executor executor, @NonNull LnbCallback cb)988     public Lnb openLnbByName(@NonNull String name, @CallbackExecutor @NonNull Executor executor,
989             @NonNull LnbCallback cb) {
990         Objects.requireNonNull(name, "LNB name must not be null");
991         Objects.requireNonNull(executor, "executor must not be null");
992         Objects.requireNonNull(cb, "LnbCallback must not be null");
993         Lnb newLnb = nativeOpenLnbByName(name);
994         if (newLnb != null) {
995             if (mLnb != null) {
996                 mLnb.close();
997                 mLnbHandle = null;
998             }
999             mLnb = newLnb;
1000             mLnb.setCallback(executor, cb, this);
1001             setLnb(mLnb);
1002         }
1003         return mLnb;
1004     }
1005 
requestLnb()1006     private boolean requestLnb() {
1007         int[] lnbHandle = new int[1];
1008         TunerLnbRequest request = new TunerLnbRequest(mClientId);
1009         boolean granted = mTunerResourceManager.requestLnb(request, lnbHandle);
1010         if (granted) {
1011             mLnbHandle = lnbHandle[0];
1012             mLnb = nativeOpenLnbByHandle(mLnbHandle);
1013         }
1014         return granted;
1015     }
1016 
1017     /**
1018      * Open a time filter object.
1019      *
1020      * @return the opened time filter object. {@code null} if the operation failed.
1021      */
1022     @Nullable
openTimeFilter()1023     public TimeFilter openTimeFilter() {
1024         if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
1025             return null;
1026         }
1027         return nativeOpenTimeFilter();
1028     }
1029 
1030     /**
1031      * Opens a Descrambler in tuner.
1032      *
1033      * @return  a {@link Descrambler} object.
1034      */
1035     @RequiresPermission(android.Manifest.permission.ACCESS_TV_DESCRAMBLER)
1036     @Nullable
openDescrambler()1037     public Descrambler openDescrambler() {
1038         if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
1039             return null;
1040         }
1041         return requestDescrambler();
1042     }
1043 
1044     /**
1045      * Open a DVR (Digital Video Record) recorder instance.
1046      *
1047      * @param bufferSize the buffer size of the output in bytes. It's used to hold output data of
1048      * the attached filters.
1049      * @param executor the executor on which callback will be invoked. The default event handler
1050      * executor is used if it's {@code null}.
1051      * @param l the listener to receive notifications from DVR recorder.
1052      * @return the opened DVR recorder object. {@code null} if the operation failed.
1053      */
1054     @Nullable
openDvrRecorder( @ytesLong long bufferSize, @CallbackExecutor @NonNull Executor executor, @NonNull OnRecordStatusChangedListener l)1055     public DvrRecorder openDvrRecorder(
1056             @BytesLong long bufferSize,
1057             @CallbackExecutor @NonNull Executor executor,
1058             @NonNull OnRecordStatusChangedListener l) {
1059         Objects.requireNonNull(executor, "executor must not be null");
1060         Objects.requireNonNull(l, "OnRecordStatusChangedListener must not be null");
1061         if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
1062             return null;
1063         }
1064         DvrRecorder dvr = nativeOpenDvrRecorder(bufferSize);
1065         dvr.setListener(executor, l);
1066         return dvr;
1067     }
1068 
1069     /**
1070      * Open a DVR (Digital Video Record) playback instance.
1071      *
1072      * @param bufferSize the buffer size of the output in bytes. It's used to hold output data of
1073      * the attached filters.
1074      * @param executor the executor on which callback will be invoked. The default event handler
1075      * executor is used if it's {@code null}.
1076      * @param l the listener to receive notifications from DVR recorder.
1077      * @return the opened DVR playback object. {@code null} if the operation failed.
1078      */
1079     @Nullable
openDvrPlayback( @ytesLong long bufferSize, @CallbackExecutor @NonNull Executor executor, @NonNull OnPlaybackStatusChangedListener l)1080     public DvrPlayback openDvrPlayback(
1081             @BytesLong long bufferSize,
1082             @CallbackExecutor @NonNull Executor executor,
1083             @NonNull OnPlaybackStatusChangedListener l) {
1084         Objects.requireNonNull(executor, "executor must not be null");
1085         Objects.requireNonNull(l, "OnPlaybackStatusChangedListener must not be null");
1086         if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
1087             return null;
1088         }
1089         DvrPlayback dvr = nativeOpenDvrPlayback(bufferSize);
1090         dvr.setListener(executor, l);
1091         return dvr;
1092     }
1093 
requestDemux()1094     private boolean requestDemux() {
1095         int[] demuxHandle = new int[1];
1096         TunerDemuxRequest request = new TunerDemuxRequest(mClientId);
1097         boolean granted = mTunerResourceManager.requestDemux(request, demuxHandle);
1098         if (granted) {
1099             mDemuxHandle = demuxHandle[0];
1100             nativeOpenDemuxByhandle(mDemuxHandle);
1101         }
1102         return granted;
1103     }
1104 
requestDescrambler()1105     private Descrambler requestDescrambler() {
1106         int[] descramblerHandle = new int[1];
1107         TunerDescramblerRequest request = new TunerDescramblerRequest(mClientId);
1108         boolean granted = mTunerResourceManager.requestDescrambler(request, descramblerHandle);
1109         if (!granted) {
1110             return null;
1111         }
1112         int handle = descramblerHandle[0];
1113         Descrambler descrambler = nativeOpenDescramblerByHandle(handle);
1114         if (descrambler != null) {
1115             mDescramblers.put(handle, descrambler);
1116         } else {
1117             mTunerResourceManager.releaseDescrambler(handle, mClientId);
1118         }
1119         return descrambler;
1120     }
1121 
checkResource(int resourceType)1122     private boolean checkResource(int resourceType)  {
1123         switch (resourceType) {
1124             case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND: {
1125                 if (mFrontendHandle == null && !requestFrontend()) {
1126                     return false;
1127                 }
1128                 break;
1129             }
1130             case TunerResourceManager.TUNER_RESOURCE_TYPE_LNB: {
1131                 if (mLnb == null && !requestLnb()) {
1132                     return false;
1133                 }
1134                 break;
1135             }
1136             case TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX: {
1137                 if (mDemuxHandle == null && !requestDemux()) {
1138                     return false;
1139                 }
1140                 break;
1141             }
1142             default:
1143                 return false;
1144         }
1145         return true;
1146     }
1147 
releaseLnb()1148     /* package */ void releaseLnb() {
1149         if (mLnbHandle != null) {
1150             // LNB handle can be null if it's opened by name.
1151             mTunerResourceManager.releaseLnb(mLnbHandle, mClientId);
1152             mLnbHandle = null;
1153         }
1154         mLnb = null;
1155     }
1156 }
1157