1 /*
2  * Copyright 2021 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.media.tv.tuner.Tuner;
25 import android.media.tv.tuner.Tuner.Result;
26 import android.media.tv.tuner.TunerUtils;
27 import java.lang.annotation.Retention;
28 import java.lang.annotation.RetentionPolicy;
29 import java.util.concurrent.Executor;
30 
31 /**
32  * Tuner shared data filter.
33  *
34  * <p>This class is used to filter wanted data in a different process.
35  *
36  * @hide
37  */
38 @SystemApi
39 public final class SharedFilter implements AutoCloseable {
40     /** @hide */
41     @IntDef(prefix = "STATUS_", value = {STATUS_INACCESSIBLE})
42     @Retention(RetentionPolicy.SOURCE)
43     public @interface Status {}
44 
45     /**
46      * The status of a shared filter that its data becomes inaccessible.
47      */
48     public static final int STATUS_INACCESSIBLE = 1 << 7;
49 
50     private static final String TAG = "SharedFilter";
51 
52     private long mNativeContext;
53     private SharedFilterCallback mCallback;
54     private Executor mExecutor;
55     private Object mCallbackLock = null;
56     private boolean mIsClosed = false;
57     private boolean mIsAccessible = true;
58     private Object mLock = null;
59 
nativeStartSharedFilter()60     private native int nativeStartSharedFilter();
nativeStopSharedFilter()61     private native int nativeStopSharedFilter();
nativeFlushSharedFilter()62     private native int nativeFlushSharedFilter();
nativeSharedRead(byte[] buffer, long offset, long size)63     private native int nativeSharedRead(byte[] buffer, long offset, long size);
nativeSharedClose()64     private native int nativeSharedClose();
65 
66     // Called by JNI
SharedFilter()67     private SharedFilter() {
68         mCallbackLock = new Object();
69         mLock = new Object();
70     }
71 
onFilterStatus(int status)72     private void onFilterStatus(int status) {
73         synchronized (mLock) {
74             if (status == STATUS_INACCESSIBLE) {
75                 mIsAccessible = false;
76             }
77         }
78         synchronized (mCallbackLock) {
79             if (mCallback != null && mExecutor != null) {
80                 mExecutor.execute(() -> {
81                     synchronized (mCallbackLock) {
82                         if (mCallback != null) {
83                             mCallback.onFilterStatusChanged(this, status);
84                         }
85                     }
86                 });
87             }
88         }
89     }
90 
onFilterEvent(FilterEvent[] events)91     private void onFilterEvent(FilterEvent[] events) {
92         synchronized (mCallbackLock) {
93             if (mCallback != null && mExecutor != null) {
94                 mExecutor.execute(() -> {
95                     synchronized (mCallbackLock) {
96                         if (mCallback != null) {
97                             mCallback.onFilterEvent(this, events);
98                         } else {
99                             for (FilterEvent event : events) {
100                                 if (event instanceof MediaEvent) {
101                                     ((MediaEvent)event).release();
102                                 }
103                             }
104                         }
105                     }
106                 });
107             } else {
108                 for (FilterEvent event : events) {
109                     if (event instanceof MediaEvent) {
110                         ((MediaEvent)event).release();
111                     }
112                 }
113             }
114         }
115     }
116 
117     /** @hide */
setCallback(SharedFilterCallback cb, Executor executor)118     public void setCallback(SharedFilterCallback cb, Executor executor) {
119         synchronized (mCallbackLock) {
120             mCallback = cb;
121             mExecutor = executor;
122         }
123     }
124 
125     /** @hide */
getCallback()126     public SharedFilterCallback getCallback() {
127         synchronized (mCallbackLock) { return mCallback; }
128     }
129 
130     /**
131      * Starts filtering data.
132      *
133      * <p>Does nothing if the filter is already started.
134      *
135      * @return result status of the operation.
136      */
137     @Result
start()138     public int start() {
139         synchronized (mLock) {
140             TunerUtils.checkResourceAccessible(TAG, mIsAccessible);
141             TunerUtils.checkResourceState(TAG, mIsClosed);
142             return nativeStartSharedFilter();
143         }
144     }
145 
146     /**
147      * Stops filtering data.
148      *
149      * <p>Does nothing if the filter is stopped or not started.
150      *
151      * @return result status of the operation.
152      */
153     @Result
stop()154     public int stop() {
155         synchronized (mLock) {
156             TunerUtils.checkResourceAccessible(TAG, mIsAccessible);
157             TunerUtils.checkResourceState(TAG, mIsClosed);
158             return nativeStopSharedFilter();
159         }
160     }
161 
162     /**
163      * Flushes the shared filter.
164      *
165      * <p>The data which is already produced by filter but not consumed yet will
166      * be cleared.
167      *
168      * @return result status of the operation.
169      */
170     @Result
flush()171     public int flush() {
172         synchronized (mLock) {
173             TunerUtils.checkResourceAccessible(TAG, mIsAccessible);
174             TunerUtils.checkResourceState(TAG, mIsClosed);
175             return nativeFlushSharedFilter();
176         }
177     }
178 
179     /**
180      * Copies filtered data from shared filter output to the given byte array.
181      *
182      * @param buffer the buffer to store the filtered data.
183      * @param offset the index of the first byte in {@code buffer} to write.
184      * @param size the maximum number of bytes to read.
185      * @return the number of bytes read.
186      */
read(@onNull byte[] buffer, @BytesLong long offset, @BytesLong long size)187     public int read(@NonNull byte[] buffer, @BytesLong long offset, @BytesLong long size) {
188         synchronized (mLock) {
189             TunerUtils.checkResourceAccessible(TAG, mIsAccessible);
190             TunerUtils.checkResourceState(TAG, mIsClosed);
191             size = Math.min(size, buffer.length - offset);
192             return nativeSharedRead(buffer, offset, size);
193         }
194     }
195 
196     /**
197      * Stops filtering data and releases the shared filter instance.
198      */
199     @Override
close()200     public void close() {
201         synchronized (mLock) {
202             if (mIsClosed) {
203                 return;
204             }
205             synchronized (mCallbackLock) {
206                 mCallback = null;
207                 mExecutor = null;
208             }
209             nativeSharedClose();
210             mIsClosed = true;
211             mCallbackLock = null;
212          }
213     }
214 }
215