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