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.filter;
18 
19 import android.annotation.BytesLong;
20 import android.annotation.IntDef;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.SystemApi;
24 import android.hardware.tv.tuner.DemuxFilterMainType;
25 import android.hardware.tv.tuner.DemuxFilterMonitorEventType;
26 import android.hardware.tv.tuner.DemuxFilterStatus;
27 import android.media.tv.tuner.Tuner;
28 import android.media.tv.tuner.Tuner.Result;
29 import android.media.tv.tuner.TunerUtils;
30 import android.media.tv.tuner.TunerVersionChecker;
31 import android.util.Log;
32 
33 import java.lang.annotation.Retention;
34 import java.lang.annotation.RetentionPolicy;
35 import java.util.concurrent.Executor;
36 
37 /**
38  * Tuner data filter.
39  *
40  * <p>This class is used to filter wanted data according to the filter's configuration.
41  *
42  * @hide
43  */
44 @SystemApi
45 public class Filter implements AutoCloseable {
46     /** @hide */
47     @IntDef(prefix = "TYPE_",
48             value = {TYPE_TS, TYPE_MMTP, TYPE_IP, TYPE_TLV, TYPE_ALP})
49     @Retention(RetentionPolicy.SOURCE)
50     public @interface Type {}
51 
52     /**
53      * Undefined filter type.
54      */
55     public static final int TYPE_UNDEFINED = 0;
56     /**
57      * TS filter type.
58      */
59     public static final int TYPE_TS = DemuxFilterMainType.TS;
60     /**
61      * MMTP filter type.
62      */
63     public static final int TYPE_MMTP = DemuxFilterMainType.MMTP;
64     /**
65      * IP filter type.
66      */
67     public static final int TYPE_IP = DemuxFilterMainType.IP;
68     /**
69      * TLV filter type.
70      */
71     public static final int TYPE_TLV = DemuxFilterMainType.TLV;
72     /**
73      * ALP filter type.
74      */
75     public static final int TYPE_ALP = DemuxFilterMainType.ALP;
76 
77     /** @hide */
78     @IntDef(prefix = "SUBTYPE_",
79             value = {SUBTYPE_UNDEFINED, SUBTYPE_SECTION, SUBTYPE_PES, SUBTYPE_AUDIO, SUBTYPE_VIDEO,
80                     SUBTYPE_DOWNLOAD, SUBTYPE_RECORD, SUBTYPE_TS, SUBTYPE_PCR, SUBTYPE_TEMI,
81                     SUBTYPE_MMTP, SUBTYPE_NTP, SUBTYPE_IP_PAYLOAD, SUBTYPE_IP,
82                     SUBTYPE_PAYLOAD_THROUGH, SUBTYPE_TLV, SUBTYPE_PTP, })
83     @Retention(RetentionPolicy.SOURCE)
84     public @interface Subtype {}
85     /**
86      * Filter subtype undefined.
87      */
88     public static final int SUBTYPE_UNDEFINED = 0;
89     /**
90      * Section filter subtype.
91      */
92     public static final int SUBTYPE_SECTION = 1;
93     /**
94      * PES filter subtype.
95      */
96     public static final int SUBTYPE_PES = 2;
97     /**
98      * Audio filter subtype.
99      */
100     public static final int SUBTYPE_AUDIO = 3;
101     /**
102      * Video filter subtype.
103      */
104     public static final int SUBTYPE_VIDEO = 4;
105     /**
106      * Download filter subtype.
107      */
108     public static final int SUBTYPE_DOWNLOAD = 5;
109     /**
110      * Record filter subtype.
111      */
112     public static final int SUBTYPE_RECORD = 6;
113     /**
114      * TS filter subtype.
115      */
116     public static final int SUBTYPE_TS = 7;
117     /**
118      * PCR filter subtype.
119      */
120     public static final int SUBTYPE_PCR = 8;
121     /**
122      * TEMI filter subtype.
123      */
124     public static final int SUBTYPE_TEMI = 9;
125     /**
126      * MMTP filter subtype.
127      */
128     public static final int SUBTYPE_MMTP = 10;
129     /**
130      * NTP filter subtype.
131      */
132     public static final int SUBTYPE_NTP = 11;
133     /**
134      * Payload filter subtype.
135      */
136     public static final int SUBTYPE_IP_PAYLOAD = 12;
137     /**
138      * IP filter subtype.
139      */
140     public static final int SUBTYPE_IP = 13;
141     /**
142      * Payload through filter subtype.
143      */
144     public static final int SUBTYPE_PAYLOAD_THROUGH = 14;
145     /**
146      * TLV filter subtype.
147      */
148     public static final int SUBTYPE_TLV = 15;
149     /**
150      * PTP filter subtype.
151      */
152     public static final int SUBTYPE_PTP = 16;
153 
154 
155     /** @hide */
156     @IntDef(prefix = "STATUS_",
157             value = {STATUS_DATA_READY, STATUS_LOW_WATER, STATUS_HIGH_WATER, STATUS_OVERFLOW,
158                     STATUS_NO_DATA})
159     @Retention(RetentionPolicy.SOURCE)
160     public @interface Status {}
161 
162     /**
163      * The status of a filter that the data in the filter buffer is ready to be read. It can also be
164      * used to know the STC (System Time Clock) ready status if it's PCR filter.
165      */
166     public static final int STATUS_DATA_READY = DemuxFilterStatus.DATA_READY;
167     /**
168      * The status of a filter that the amount of available data in the filter buffer is at low
169      * level.
170      *
171      * The value is set to 25 percent of the buffer size by default. It can be changed when
172      * configuring the filter.
173      */
174     public static final int STATUS_LOW_WATER = DemuxFilterStatus.LOW_WATER;
175     /**
176      * The status of a filter that the amount of available data in the filter buffer is at high
177      * level.
178      * The value is set to 75 percent of the buffer size by default. It can be changed when
179      * configuring the filter.
180      */
181     public static final int STATUS_HIGH_WATER = DemuxFilterStatus.HIGH_WATER;
182     /**
183      * The status of a filter that the filter buffer is full and newly filtered data is being
184      * discarded.
185      */
186     public static final int STATUS_OVERFLOW = DemuxFilterStatus.OVERFLOW;
187     /**
188      * The status of a filter that the filter buffer is empty and no filtered data is coming.
189      */
190     public static final int STATUS_NO_DATA = DemuxFilterStatus.NO_DATA;
191 
192     /** @hide */
193     @IntDef(prefix = "SCRAMBLING_STATUS_",
194             value = {SCRAMBLING_STATUS_UNKNOWN, SCRAMBLING_STATUS_NOT_SCRAMBLED,
195                     SCRAMBLING_STATUS_SCRAMBLED})
196     @Retention(RetentionPolicy.SOURCE)
197     public @interface ScramblingStatus {}
198 
199     /**
200      * Content’s scrambling status is unknown
201      */
202     public static final int SCRAMBLING_STATUS_UNKNOWN =
203             android.hardware.tv.tuner.ScramblingStatus.UNKNOWN;
204     /**
205      * Content is not scrambled.
206      */
207     public static final int SCRAMBLING_STATUS_NOT_SCRAMBLED =
208             android.hardware.tv.tuner.ScramblingStatus.NOT_SCRAMBLED;
209     /**
210      * Content is scrambled.
211      */
212     public static final int SCRAMBLING_STATUS_SCRAMBLED =
213             android.hardware.tv.tuner.ScramblingStatus.SCRAMBLED;
214 
215     /** @hide */
216     @IntDef(prefix = "MONITOR_EVENT_",
217             value = {MONITOR_EVENT_SCRAMBLING_STATUS, MONITOR_EVENT_IP_CID_CHANGE})
218     @Retention(RetentionPolicy.SOURCE)
219     public @interface MonitorEventMask {}
220 
221     /**
222      * Monitor scrambling status change.
223      */
224     public static final int MONITOR_EVENT_SCRAMBLING_STATUS =
225             DemuxFilterMonitorEventType.SCRAMBLING_STATUS;
226     /**
227      * Monitor ip cid change.
228      */
229     public static final int MONITOR_EVENT_IP_CID_CHANGE = DemuxFilterMonitorEventType.IP_CID_CHANGE;
230 
231     private static final String TAG = "Filter";
232 
233     private long mNativeContext;
234     private FilterCallback mCallback;
235     private Executor mExecutor;
236     private final Object mCallbackLock = new Object();
237     private final long mId;
238     private int mMainType;
239     private int mSubtype;
240     private Filter mSource;
241     private boolean mStarted;
242     private boolean mIsClosed = false;
243     private boolean mIsStarted = false;
244     private boolean mIsShared = false;
245     private final Object mLock = new Object();
246 
nativeConfigureFilter( int type, int subType, FilterConfiguration settings)247     private native int nativeConfigureFilter(
248             int type, int subType, FilterConfiguration settings);
nativeGetId()249     private native int nativeGetId();
nativeGetId64Bit()250     private native long nativeGetId64Bit();
nativeConfigureMonitorEvent(int monitorEventMask)251     private native int nativeConfigureMonitorEvent(int monitorEventMask);
nativeSetDataSource(Filter source)252     private native int nativeSetDataSource(Filter source);
nativeStartFilter()253     private native int nativeStartFilter();
nativeStopFilter()254     private native int nativeStopFilter();
nativeFlushFilter()255     private native int nativeFlushFilter();
nativeRead(byte[] buffer, long offset, long size)256     private native int nativeRead(byte[] buffer, long offset, long size);
nativeClose()257     private native int nativeClose();
nativeAcquireSharedFilterToken()258     private native String nativeAcquireSharedFilterToken();
nativeFreeSharedFilterToken(String token)259     private native void nativeFreeSharedFilterToken(String token);
nativeSetTimeDelayHint(int timeDelayInMs)260     private native int nativeSetTimeDelayHint(int timeDelayInMs);
nativeSetDataSizeDelayHint(int dataSizeDelayInBytes)261     private native int nativeSetDataSizeDelayHint(int dataSizeDelayInBytes);
262 
263     // Called by JNI
Filter(long id)264     private Filter(long id) {
265         mId = id;
266     }
267 
onFilterStatus(int status)268     private void onFilterStatus(int status) {
269         synchronized (mCallbackLock) {
270             if (mCallback != null && mExecutor != null) {
271                 mExecutor.execute(() -> {
272                     FilterCallback callback;
273                     synchronized (mCallbackLock) {
274                         callback = mCallback;
275                     }
276                     if (callback != null) {
277                         try {
278                             callback.onFilterStatusChanged(this, status);
279                         } catch (NullPointerException e) {
280                             Log.d(TAG, "catch exception:" + e);
281                         }
282                     }
283                     if (callback != null) {
284                         callback.onFilterStatusChanged(this, status);
285                     }
286                 });
287             }
288         }
289     }
290 
onFilterEvent(FilterEvent[] events)291     private void onFilterEvent(FilterEvent[] events) {
292         synchronized (mCallbackLock) {
293             if (mCallback != null && mExecutor != null) {
294                 mExecutor.execute(() -> {
295                     FilterCallback callback;
296                     synchronized (mCallbackLock) {
297                         callback = mCallback;
298                     }
299                     if (callback != null) {
300                         try {
301                             callback.onFilterEvent(this, events);
302                         } catch (NullPointerException e) {
303                             Log.d(TAG, "catch exception:" + e);
304                         }
305                     } else {
306                         for (FilterEvent event : events) {
307                             if (event instanceof MediaEvent) {
308                                 ((MediaEvent) event).release();
309                             }
310                         }
311                     }
312                 });
313             } else {
314                 for (FilterEvent event : events) {
315                     if (event instanceof MediaEvent) {
316                         ((MediaEvent) event).release();
317                     }
318                 }
319             }
320         }
321     }
322 
323     /** @hide */
setType(@ype int mainType, @Subtype int subtype)324     public void setType(@Type int mainType, @Subtype int subtype) {
325         mMainType = mainType;
326         mSubtype = TunerUtils.getFilterSubtype(mainType, subtype);
327     }
328 
329     /** @hide */
setCallback(FilterCallback cb, Executor executor)330     public void setCallback(FilterCallback cb, Executor executor) {
331         synchronized (mCallbackLock) {
332             mCallback = cb;
333             mExecutor = executor;
334         }
335     }
336 
337     /** @hide */
getCallback()338     public FilterCallback getCallback() {
339         synchronized (mCallbackLock) {
340             return mCallback;
341         }
342     }
343 
344     /**
345      * Configures the filter.
346      *
347      * <p>Recofiguring must happen after stopping the filter.
348      *
349      * <p>When stopping, reconfiguring and restarting the filter, the client should discard all
350      * coming events until it receives {@link RestartEvent} through {@link FilterCallback} to avoid
351      * using the events from the previous configuration.
352      *
353      * <p>If this filter is shared, do nothing and just return {@link Tuner#RESULT_INVALID_STATE}.
354      *
355      * @param config the configuration of the filter.
356      * @return result status of the operation.
357      */
358     @Result
configure(@onNull FilterConfiguration config)359     public int configure(@NonNull FilterConfiguration config) {
360         synchronized (mLock) {
361             TunerUtils.checkResourceState(TAG, mIsClosed);
362             if (mIsShared) {
363                 return Tuner.RESULT_INVALID_STATE;
364             }
365             Settings s = config.getSettings();
366             int subType = (s == null) ? mSubtype : s.getType();
367             if (mMainType != config.getType() || mSubtype != subType) {
368                 throw new IllegalArgumentException("Invalid filter config. filter main type="
369                         + mMainType + ", filter subtype=" + mSubtype + ". config main type="
370                         + config.getType() + ", config subtype=" + subType);
371             }
372             // Tuner only support VVC after tuner 3.0
373             if (s instanceof RecordSettings
374                     && ((RecordSettings) s).getScIndexType() == RecordSettings.INDEX_TYPE_SC_VVC
375                     && !TunerVersionChecker.isHigherOrEqualVersionTo(
376                             TunerVersionChecker.TUNER_VERSION_3_0)) {
377                 Log.e(TAG, "Tuner version " + TunerVersionChecker.getTunerVersion()
378                         + " does not support VVC");
379                 return Tuner.RESULT_UNAVAILABLE;
380             }
381             return nativeConfigureFilter(config.getType(), subType, config);
382         }
383     }
384 
385     /**
386      * Gets the filter Id in 32-bit. For any Tuner SoC that supports 64-bit filter architecture,
387      * use {@link #getIdLong()}.
388      * @deprecated Use {@link #getIdLong()} for both 32-bit and 64-bit filter architectures.
389      */
getId()390     public int getId() {
391         synchronized (mLock) {
392             TunerUtils.checkResourceState(TAG, mIsClosed);
393             return nativeGetId();
394         }
395     }
396 
397     /**
398      * Gets the filter Id.
399      */
getIdLong()400     public long getIdLong() {
401         synchronized (mLock) {
402             TunerUtils.checkResourceState(TAG, mIsClosed);
403             return nativeGetId64Bit();
404         }
405     }
406 
407     /**
408      * Configure the Filter to monitor scrambling status and ip cid change. Set corresponding bit
409      * to monitor the change. Reset to stop monitoring.
410      *
411      * <p>{@link ScramblingStatusEvent} should be sent at the following two scenarios:
412      * <ul>
413      *   <li>When this method is called with {@link #MONITOR_EVENT_SCRAMBLING_STATUS}, the first
414      *       detected scrambling status should be sent.
415      *   <li>When the Scrambling status transits into different status, event should be sent.
416      *     <ul/>
417      *
418      * <p>{@link IpCidChangeEvent} should be sent at the following two scenarios:
419      * <ul>
420      *   <li>When this method is called with {@link #MONITOR_EVENT_IP_CID_CHANGE}, the first
421      *       detected CID for the IP should be sent.
422      *   <li>When the CID is changed to different value for the IP filter, event should be sent.
423      *     <ul/>
424      *
425      * <p>This configuration is only supported in Tuner 1.1 or higher version. Unsupported version
426      * will cause no-op. Use {@link TunerVersionChecker#getTunerVersion()} to get the version
427      * information.
428      *
429      * <p>If this filter is shared, do nothing and just return {@link Tuner#RESULT_INVALID_STATE}.
430      *
431      * @param monitorEventMask Types of event to be monitored. Set corresponding bit to
432      *                         monitor it. Reset to stop monitoring.
433      * @return result status of the operation.
434      */
435     @Result
setMonitorEventMask(@onitorEventMask int monitorEventMask)436     public int setMonitorEventMask(@MonitorEventMask int monitorEventMask) {
437         synchronized (mLock) {
438             TunerUtils.checkResourceState(TAG, mIsClosed);
439             if (mIsShared) {
440                 return Tuner.RESULT_INVALID_STATE;
441             }
442             if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
443                     TunerVersionChecker.TUNER_VERSION_1_1, "setMonitorEventMask")) {
444                 return Tuner.RESULT_UNAVAILABLE;
445             }
446             return nativeConfigureMonitorEvent(monitorEventMask);
447         }
448     }
449 
450     /**
451      * Sets the filter's data source.
452      *
453      * A filter uses demux as data source by default. If the data was packetized
454      * by multiple protocols, multiple filters may need to work together to
455      * extract all protocols' header. Then a filter's data source can be output
456      * from another filter.
457      *
458      * <p>If this filter is shared, do nothing and just return {@link Tuner#RESULT_INVALID_STATE}.
459      *
460      * @param source the filter instance which provides data input. Switch to
461      * use demux as data source if the filter instance is NULL.
462      * @return result status of the operation.
463      * @throws IllegalStateException if the data source has been set.
464      */
465     @Result
setDataSource(@ullable Filter source)466     public int setDataSource(@Nullable Filter source) {
467         synchronized (mLock) {
468             TunerUtils.checkResourceState(TAG, mIsClosed);
469             if (mIsShared) {
470                 return Tuner.RESULT_INVALID_STATE;
471             }
472             if (mSource != null) {
473                 throw new IllegalStateException("Data source is existing");
474             }
475             int res = nativeSetDataSource(source);
476             if (res == Tuner.RESULT_SUCCESS) {
477                 mSource = source;
478             }
479             return res;
480         }
481     }
482 
483     /**
484      * Starts filtering data.
485      *
486      * <p>Does nothing if the filter is already started.
487      *
488      * <p>When stopping, reconfiguring and restarting the filter, the client should discard all
489      * coming events until it receives {@link RestartEvent} through {@link FilterCallback} to avoid
490      * using the events from the previous configuration.
491      *
492      * <p>If this filter is shared, do nothing and just return {@link Tuner#RESULT_INVALID_STATE}.
493      *
494      * @return result status of the operation.
495      */
496     @Result
start()497     public int start() {
498         synchronized (mLock) {
499             TunerUtils.checkResourceState(TAG, mIsClosed);
500             if (mIsShared) {
501                 return Tuner.RESULT_INVALID_STATE;
502             }
503             int res = nativeStartFilter();
504             if (res == Tuner.RESULT_SUCCESS) {
505                 mIsStarted = true;
506             }
507             return res;
508         }
509     }
510 
511     /**
512      * Stops filtering data.
513      *
514      * <p>Does nothing if the filter is stopped or not started.
515      *
516      * <p>Filter must be stopped to reconfigure.
517      *
518      * <p>When stopping, reconfiguring and restarting the filter, the client should discard all
519      * coming events until it receives {@link RestartEvent} through {@link FilterCallback} to avoid
520      * using the events from the previous configuration.
521      *
522      * <p>If this filter is shared, do nothing and just return {@link Tuner#RESULT_INVALID_STATE}.
523      *
524      * @return result status of the operation.
525      */
526     @Result
stop()527     public int stop() {
528         synchronized (mLock) {
529             TunerUtils.checkResourceState(TAG, mIsClosed);
530             if (mIsShared) {
531                 return Tuner.RESULT_INVALID_STATE;
532             }
533             int res = nativeStopFilter();
534             if (res == Tuner.RESULT_SUCCESS) {
535                 mIsStarted = false;
536             }
537             return res;
538         }
539     }
540 
541     /**
542      * Flushes the filter.
543      *
544      * <p>The data which is already produced by filter but not consumed yet will
545      * be cleared.
546      *
547      * <p>If this filter is shared, do nothing and just return {@link Tuner#RESULT_INVALID_STATE}.
548      *
549      * @return result status of the operation.
550      */
551     @Result
flush()552     public int flush() {
553         synchronized (mLock) {
554             TunerUtils.checkResourceState(TAG, mIsClosed);
555             if (mIsShared) {
556                 return Tuner.RESULT_INVALID_STATE;
557             }
558             return nativeFlushFilter();
559         }
560     }
561 
562     /**
563      * Copies filtered data from filter output to the given byte array.
564      *
565      * <p>If this filter is shared, do nothing and just return {@link Tuner#RESULT_INVALID_STATE}.
566      *
567      * @param buffer the buffer to store the filtered data.
568      * @param offset the index of the first byte in {@code buffer} to write.
569      * @param size the maximum number of bytes to read.
570      * @return the number of bytes read.
571      */
read(@onNull byte[] buffer, @BytesLong long offset, @BytesLong long size)572     public int read(@NonNull byte[] buffer, @BytesLong long offset, @BytesLong long size) {
573         synchronized (mLock) {
574             TunerUtils.checkResourceState(TAG, mIsClosed);
575             if (mIsShared) {
576                 return 0;
577             }
578             size = Math.min(size, buffer.length - offset);
579             return nativeRead(buffer, offset, size);
580         }
581     }
582 
583     /**
584      * Stops filtering data and releases the Filter instance.
585      *
586      * <p>If this filter is shared, this filter will be closed and a
587      * {@link SharedFilterCallback#STATUS_INACCESSIBLE} event will be sent to shared filter before
588      * closing.
589      */
590     @Override
close()591     public void close() {
592         synchronized (mCallbackLock) {
593             mCallback = null;
594             mExecutor = null;
595         }
596 
597         synchronized (mLock) {
598             if (mIsClosed) {
599                 return;
600             }
601             int res = nativeClose();
602             if (res != Tuner.RESULT_SUCCESS) {
603                 TunerUtils.throwExceptionForResult(res, "Failed to close filter.");
604             } else {
605                 mIsStarted = false;
606                 mIsClosed = true;
607             }
608         }
609     }
610 
611     /**
612      * Acquires a shared filter token.
613      *
614      * @return a string shared filter token.
615      */
616     @Nullable
acquireSharedFilterToken()617     public String acquireSharedFilterToken() {
618         synchronized (mLock) {
619             TunerUtils.checkResourceState(TAG, mIsClosed);
620             if (mIsStarted || mIsShared) {
621                 Log.d(TAG, "Acquire shared filter in a wrong state, started: " +
622                      mIsStarted + "shared: " + mIsShared);
623                 return null;
624             }
625             String token = nativeAcquireSharedFilterToken();
626             if (token != null) {
627                 mIsShared = true;
628             }
629             return token;
630         }
631     }
632 
633     /**
634      * Frees a shared filter token.
635      *
636      * @param filterToken the token of the shared filter being released.
637      */
freeSharedFilterToken(@onNull String filterToken)638     public void freeSharedFilterToken(@NonNull String filterToken) {
639         synchronized (mLock) {
640             TunerUtils.checkResourceState(TAG, mIsClosed);
641             if (!mIsShared) {
642                 return;
643             }
644             nativeFreeSharedFilterToken(filterToken);
645             mIsShared = false;
646         }
647     }
648 
649     /**
650      * Set filter time delay.
651      *
652      * <p> Setting a time delay instructs the filter to delay its event callback invocation until
653      * the specified amount of time has passed. The default value (delay disabled) is {@code 0}.
654      *
655      * <p>This functionality is only available in Tuner version 2.0 and higher and will otherwise
656      * be a no-op. Use {@link TunerVersionChecker#getTunerVersion()} to get the version information.
657      *
658      * @param durationInMs specifies the duration of the delay in milliseconds.
659      * @return one of the following results: {@link Tuner#RESULT_SUCCESS},
660      * {@link Tuner#RESULT_UNAVAILABLE}, {@link Tuner#RESULT_NOT_INITIALIZED},
661      * {@link Tuner#RESULT_INVALID_STATE}, {@link Tuner#RESULT_INVALID_ARGUMENT},
662      * {@link Tuner#RESULT_OUT_OF_MEMORY}, or {@link Tuner#RESULT_UNKNOWN_ERROR}.
663      */
delayCallbackForDurationMillis(long durationInMs)664     public int delayCallbackForDurationMillis(long durationInMs) {
665         if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
666                   TunerVersionChecker.TUNER_VERSION_2_0, "setTimeDelayHint")) {
667             return Tuner.RESULT_UNAVAILABLE;
668         }
669 
670         if (durationInMs >= 0 && durationInMs <= Integer.MAX_VALUE) {
671             synchronized (mLock) {
672                 return nativeSetTimeDelayHint((int) durationInMs);
673             }
674         }
675         return Tuner.RESULT_INVALID_ARGUMENT;
676     }
677 
678     /**
679      * Set filter data size delay.
680      *
681      * <p> Setting a data size delay instructs the filter to delay its event callback invocation
682      * until a specified amount of data has accumulated. The default value (delay disabled) is
683      * {@code 0}.
684      *
685      * <p>This functionality is only available in Tuner version 2.0 and higher and will otherwise
686      * be a no-op. Use {@link TunerVersionChecker#getTunerVersion()} to get the version information.
687      *
688      * @param bytesAccumulated specifies the delay condition in bytes.
689      * @return one of the following results: {@link Tuner#RESULT_SUCCESS},
690      * {@link Tuner#RESULT_UNAVAILABLE}, {@link Tuner#RESULT_NOT_INITIALIZED},
691      * {@link Tuner#RESULT_INVALID_STATE}, {@link Tuner#RESULT_INVALID_ARGUMENT},
692      * {@link Tuner#RESULT_OUT_OF_MEMORY}, or {@link Tuner#RESULT_UNKNOWN_ERROR}.
693      */
delayCallbackUntilBytesAccumulated(int bytesAccumulated)694     public int delayCallbackUntilBytesAccumulated(int bytesAccumulated) {
695         if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
696                   TunerVersionChecker.TUNER_VERSION_2_0, "setTimeDelayHint")) {
697             return Tuner.RESULT_UNAVAILABLE;
698         }
699 
700         synchronized (mLock) {
701             return nativeSetDataSizeDelayHint(bytesAccumulated);
702         }
703     }
704 }
705