Add SharedFilter to tuner frameworks.
The SharedFilter is used to share the Filter created by TIS to another
process. The TIS uses Filter.createSharedFilter() to create a token and
share it to another process. The new process will use
Tuner.openSharedFilter() to open that new token and create a
SharedFilter. The new process will be able to read the TIS owned
Filter's data with this new SharedFilter.
Bug: 196124225
Test: atest android.media.tv.tuner.cts on both AIDL and HIDL HAL.
Change-Id: Id93b30e4db242d35a3d1ae6023b967a90a78c448
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 95d06b0..c0dcceb 100755
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -20,6 +20,7 @@
field public static final String ACCESS_SURFACE_FLINGER = "android.permission.ACCESS_SURFACE_FLINGER";
field public static final String ACCESS_TUNED_INFO = "android.permission.ACCESS_TUNED_INFO";
field public static final String ACCESS_TV_DESCRAMBLER = "android.permission.ACCESS_TV_DESCRAMBLER";
+ field public static final String ACCESS_TV_SHARED_FILTER = "android.permission.ACCESS_TV_SHARED_FILTER";
field public static final String ACCESS_TV_TUNER = "android.permission.ACCESS_TV_TUNER";
field public static final String ACCESS_VIBRATOR_STATE = "android.permission.ACCESS_VIBRATOR_STATE";
field public static final String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING";
@@ -6125,6 +6126,7 @@
method @Nullable public android.media.tv.tuner.filter.Filter openFilter(int, int, long, @Nullable java.util.concurrent.Executor, @Nullable android.media.tv.tuner.filter.FilterCallback);
method @Nullable public android.media.tv.tuner.Lnb openLnb(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.LnbCallback);
method @Nullable public android.media.tv.tuner.Lnb openLnbByName(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.LnbCallback);
+ method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_SHARED_FILTER) public static android.media.tv.tuner.filter.SharedFilter openSharedFilter(@NonNull android.content.Context, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.filter.SharedFilterCallback);
method @Nullable public android.media.tv.tuner.filter.TimeFilter openTimeFilter();
method public int scan(@NonNull android.media.tv.tuner.frontend.FrontendSettings, int, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.frontend.ScanCallback);
method public int setLnaEnabled(boolean);
@@ -6333,10 +6335,12 @@
public class Filter implements java.lang.AutoCloseable {
method public void close();
method public int configure(@NonNull android.media.tv.tuner.filter.FilterConfiguration);
+ method @Nullable public String createSharedFilter();
method public int flush();
method @Deprecated public int getId();
method public long getIdLong();
method public int read(@NonNull byte[], long, long);
+ method public void releaseSharedFilter(@NonNull String);
method public int setDataSource(@Nullable android.media.tv.tuner.filter.Filter);
method public int setMonitorEventMask(int);
method public int start();
@@ -6586,6 +6590,20 @@
method public int getType();
}
+ public final class SharedFilter implements java.lang.AutoCloseable {
+ method public void close();
+ method public int flush();
+ method public int read(@NonNull byte[], long, long);
+ method public int start();
+ method public int stop();
+ field public static final int STATUS_INACCESSIBLE = 128; // 0x80
+ }
+
+ public interface SharedFilterCallback {
+ method public void onFilterEvent(@NonNull android.media.tv.tuner.filter.SharedFilter, @NonNull android.media.tv.tuner.filter.FilterEvent[]);
+ method public void onFilterStatusChanged(@NonNull android.media.tv.tuner.filter.SharedFilter, int);
+ }
+
public class TemiEvent extends android.media.tv.tuner.filter.FilterEvent {
method @NonNull public byte[] getDescriptorData();
method public byte getDescriptorTag();
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index b3a16d9..df69ed0 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5802,6 +5802,12 @@
<permission android:name="android.permission.ACCESS_TV_DESCRAMBLER"
android:protectionLevel="signature|privileged|vendorPrivileged" />
+ <!-- @SystemApi Allows an application to access shared filter of TV tuner HAL
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.ACCESS_TV_SHARED_FILTER"
+ android:protectionLevel="signature|privileged|vendorPrivileged" />
+
<!-- Allows an application to create trusted displays. @hide -->
<permission android:name="android.permission.ADD_TRUSTED_DISPLAY"
android:protectionLevel="signature" />
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 25fb223..33cc61b 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -425,6 +425,7 @@
<permission name="android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS"/>
<!-- Permissions required for CTS test - TunerTest -->
<permission name="android.permission.ACCESS_TV_DESCRAMBLER" />
+ <permission name="android.permission.ACCESS_TV_SHARED_FILTER" />
<permission name="android.permission.ACCESS_TV_TUNER" />
<permission name="android.permission.TUNER_RESOURCE_ACCESS" />
<!-- Permissions required for CTS test - TVInputManagerTest -->
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 405f9ee..0d26761 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -25,6 +25,7 @@
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.hardware.tv.tuner.Constant;
import android.hardware.tv.tuner.Constant64Bit;
import android.hardware.tv.tuner.FrontendScanType;
@@ -37,6 +38,8 @@
import android.media.tv.tuner.filter.Filter.Subtype;
import android.media.tv.tuner.filter.Filter.Type;
import android.media.tv.tuner.filter.FilterCallback;
+import android.media.tv.tuner.filter.SharedFilter;
+import android.media.tv.tuner.filter.SharedFilterCallback;
import android.media.tv.tuner.filter.TimeFilter;
import android.media.tv.tuner.frontend.Atsc3PlpInfo;
import android.media.tv.tuner.frontend.FrontendInfo;
@@ -620,6 +623,7 @@
private native int nativeCloseFrontend(int handle);
private native int nativeClose();
+ private static native SharedFilter nativeOpenSharedFilter(String token);
/**
* Listener for resource lost.
@@ -1570,6 +1574,37 @@
return dvr;
}
+ /**
+ * Open a shared filter instance.
+ *
+ * @param context the context of the caller.
+ * @param sharedFilterToken the token of the shared filter being opened.
+ * @param executor the executor on which callback will be invoked.
+ * @param cb the listener to receive notifications from shared filter.
+ * @return the opened shared filter object. {@code null} if the operation failed.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_SHARED_FILTER)
+ @Nullable
+ static public SharedFilter openSharedFilter(@NonNull Context context,
+ @NonNull String sharedFilterToken, @CallbackExecutor @NonNull Executor executor,
+ @NonNull SharedFilterCallback cb) {
+ Objects.requireNonNull(sharedFilterToken, "sharedFilterToken must not be null");
+ Objects.requireNonNull(executor, "executor must not be null");
+ Objects.requireNonNull(cb, "SharedFilterCallback must not be null");
+
+ if (context.checkCallingOrSelfPermission(
+ android.Manifest.permission.ACCESS_TV_SHARED_FILTER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Caller must have ACCESS_TV_SHAREDFILTER permission.");
+ }
+
+ SharedFilter filter = nativeOpenSharedFilter(sharedFilterToken);
+ if (filter != null) {
+ filter.setCallback(cb, executor);
+ }
+ return filter;
+ }
+
private boolean requestDemux() {
int[] demuxHandle = new int[1];
TunerDemuxRequest request = new TunerDemuxRequest();
diff --git a/media/java/android/media/tv/tuner/TunerUtils.java b/media/java/android/media/tv/tuner/TunerUtils.java
index 3a15daf..5541508 100644
--- a/media/java/android/media/tv/tuner/TunerUtils.java
+++ b/media/java/android/media/tv/tuner/TunerUtils.java
@@ -172,5 +172,16 @@
}
}
+ /**
+ * Checks the accessibility of a resource instance.
+ *
+ * @throws IllegalStateException if the resource has already been inaccessible.
+ */
+ public static void checkResourceAccessible(String name, boolean accessible) {
+ if (!accessible) {
+ throw new IllegalStateException(name + " is inaccessible");
+ }
+ }
+
private TunerUtils() {}
}
diff --git a/media/java/android/media/tv/tuner/filter/Filter.java b/media/java/android/media/tv/tuner/filter/Filter.java
index bd860d9..ae271120 100644
--- a/media/java/android/media/tv/tuner/filter/Filter.java
+++ b/media/java/android/media/tv/tuner/filter/Filter.java
@@ -28,6 +28,7 @@
import android.media.tv.tuner.Tuner.Result;
import android.media.tv.tuner.TunerUtils;
import android.media.tv.tuner.TunerVersionChecker;
+import android.util.Log;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -235,6 +236,8 @@
private Filter mSource;
private boolean mStarted;
private boolean mIsClosed = false;
+ private boolean mIsStarted = false;
+ private boolean mIsShared = false;
private final Object mLock = new Object();
private native int nativeConfigureFilter(
@@ -248,6 +251,8 @@
private native int nativeFlushFilter();
private native int nativeRead(byte[] buffer, long offset, long size);
private native int nativeClose();
+ private native String nativeCreateSharedFilter();
+ private native void nativeReleaseSharedFilter(String token);
// Called by JNI
private Filter(long id) {
@@ -312,6 +317,8 @@
* coming events until it receives {@link RestartEvent} through {@link FilterCallback} to avoid
* using the events from the previous configuration.
*
+ * <p>If this filter is shared, do nothing and just return {@link Tuner#RESULT_INVALID_STATE}.
+ *
* @param config the configuration of the filter.
* @return result status of the operation.
*/
@@ -319,6 +326,9 @@
public int configure(@NonNull FilterConfiguration config) {
synchronized (mLock) {
TunerUtils.checkResourceState(TAG, mIsClosed);
+ if (mIsShared) {
+ return Tuner.RESULT_INVALID_STATE;
+ }
Settings s = config.getSettings();
int subType = (s == null) ? mSubtype : s.getType();
if (mMainType != config.getType() || mSubtype != subType) {
@@ -374,6 +384,8 @@
* will cause no-op. Use {@link TunerVersionChecker#getTunerVersion()} to get the version
* information.
*
+ * <p>If this filter is shared, do nothing and just return {@link Tuner#RESULT_INVALID_STATE}.
+ *
* @param monitorEventMask Types of event to be monitored. Set corresponding bit to
* monitor it. Reset to stop monitoring.
* @return result status of the operation.
@@ -382,6 +394,9 @@
public int setMonitorEventMask(@MonitorEventMask int monitorEventMask) {
synchronized (mLock) {
TunerUtils.checkResourceState(TAG, mIsClosed);
+ if (mIsShared) {
+ return Tuner.RESULT_INVALID_STATE;
+ }
if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
TunerVersionChecker.TUNER_VERSION_1_1, "setMonitorEventMask")) {
return Tuner.RESULT_UNAVAILABLE;
@@ -398,6 +413,8 @@
* extract all protocols' header. Then a filter's data source can be output
* from another filter.
*
+ * <p>If this filter is shared, do nothing and just return {@link Tuner#RESULT_INVALID_STATE}.
+ *
* @param source the filter instance which provides data input. Switch to
* use demux as data source if the filter instance is NULL.
* @return result status of the operation.
@@ -407,6 +424,9 @@
public int setDataSource(@Nullable Filter source) {
synchronized (mLock) {
TunerUtils.checkResourceState(TAG, mIsClosed);
+ if (mIsShared) {
+ return Tuner.RESULT_INVALID_STATE;
+ }
if (mSource != null) {
throw new IllegalStateException("Data source is existing");
}
@@ -427,17 +447,25 @@
* coming events until it receives {@link RestartEvent} through {@link FilterCallback} to avoid
* using the events from the previous configuration.
*
+ * <p>If this filter is shared, do nothing and just return {@link Tuner#RESULT_INVALID_STATE}.
+ *
* @return result status of the operation.
*/
@Result
public int start() {
synchronized (mLock) {
TunerUtils.checkResourceState(TAG, mIsClosed);
- return nativeStartFilter();
+ if (mIsShared) {
+ return Tuner.RESULT_INVALID_STATE;
+ }
+ int res = nativeStartFilter();
+ if (res == Tuner.RESULT_SUCCESS) {
+ mIsStarted = true;
+ }
+ return res;
}
}
-
/**
* Stops filtering data.
*
@@ -449,13 +477,22 @@
* coming events until it receives {@link RestartEvent} through {@link FilterCallback} to avoid
* using the events from the previous configuration.
*
+ * <p>If this filter is shared, do nothing and just return {@link Tuner#RESULT_INVALID_STATE}.
+ *
* @return result status of the operation.
*/
@Result
public int stop() {
synchronized (mLock) {
TunerUtils.checkResourceState(TAG, mIsClosed);
- return nativeStopFilter();
+ if (mIsShared) {
+ return Tuner.RESULT_INVALID_STATE;
+ }
+ int res = nativeStopFilter();
+ if (res == Tuner.RESULT_SUCCESS) {
+ mIsStarted = false;
+ }
+ return res;
}
}
@@ -465,12 +502,17 @@
* <p>The data which is already produced by filter but not consumed yet will
* be cleared.
*
+ * <p>If this filter is shared, do nothing and just return {@link Tuner#RESULT_INVALID_STATE}.
+ *
* @return result status of the operation.
*/
@Result
public int flush() {
synchronized (mLock) {
TunerUtils.checkResourceState(TAG, mIsClosed);
+ if (mIsShared) {
+ return Tuner.RESULT_INVALID_STATE;
+ }
return nativeFlushFilter();
}
}
@@ -478,6 +520,8 @@
/**
* Copies filtered data from filter output to the given byte array.
*
+ * <p>If this filter is shared, do nothing and just return {@link Tuner#RESULT_INVALID_STATE}.
+ *
* @param buffer the buffer to store the filtered data.
* @param offset the index of the first byte in {@code buffer} to write.
* @param size the maximum number of bytes to read.
@@ -486,6 +530,9 @@
public int read(@NonNull byte[] buffer, @BytesLong long offset, @BytesLong long size) {
synchronized (mLock) {
TunerUtils.checkResourceState(TAG, mIsClosed);
+ if (mIsShared) {
+ return 0;
+ }
size = Math.min(size, buffer.length - offset);
return nativeRead(buffer, offset, size);
}
@@ -493,6 +540,10 @@
/**
* Stops filtering data and releases the Filter instance.
+ *
+ * <p>If this filter is shared, this filter will be closed and a
+ * {@link SharedFilterCallback#STATUS_INACCESSIBLE} event will be sent to shared filter before
+ * closing.
*/
@Override
public void close() {
@@ -504,8 +555,47 @@
if (res != Tuner.RESULT_SUCCESS) {
TunerUtils.throwExceptionForResult(res, "Failed to close filter.");
} else {
+ mIsStarted = false;
mIsClosed = true;
}
}
}
+
+ /**
+ * Creates a shared filter.
+ *
+ * @return a string shared filter token.
+ */
+ @Nullable
+ public String createSharedFilter() {
+ synchronized (mLock) {
+ TunerUtils.checkResourceState(TAG, mIsClosed);
+ if (mIsStarted || mIsShared) {
+ Log.d(TAG, "Create shared filter in a wrong state, started: " +
+ mIsStarted + "shared: " + mIsShared);
+ return null;
+ }
+ String token = nativeCreateSharedFilter();
+ if (token != null) {
+ mIsShared = true;
+ }
+ return token;
+ }
+ }
+
+ /**
+ * Releases a shared filter.
+ *
+ * @param filterToken the token of the shared filter being released.
+ */
+ public void releaseSharedFilter(@NonNull String filterToken) {
+ synchronized (mLock) {
+ TunerUtils.checkResourceState(TAG, mIsClosed);
+ if (!mIsShared) {
+ return;
+ }
+ nativeReleaseSharedFilter(filterToken);
+ mIsShared = false;
+ }
+ }
}
diff --git a/media/java/android/media/tv/tuner/filter/SharedFilter.java b/media/java/android/media/tv/tuner/filter/SharedFilter.java
new file mode 100644
index 0000000..f86ad11
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/SharedFilter.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.filter;
+
+import android.annotation.BytesLong;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.media.tv.tuner.Tuner;
+import android.media.tv.tuner.Tuner.Result;
+import android.media.tv.tuner.TunerUtils;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+
+/**
+ * Tuner shared data filter.
+ *
+ * <p>This class is used to filter wanted data in a different process.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SharedFilter implements AutoCloseable {
+ /** @hide */
+ @IntDef(flag = true, prefix = "STATUS_", value = {STATUS_INACCESSIBLE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Status {}
+
+ /**
+ * The status of a shared filter that its data becomes inaccessible.
+ */
+ public static final int STATUS_INACCESSIBLE = 1 << 7;
+
+ private static final String TAG = "SharedFilter";
+
+ private long mNativeContext;
+ private SharedFilterCallback mCallback;
+ private Executor mExecutor;
+ private final Object mCallbackLock = new Object();
+ private boolean mIsClosed = false;
+ private boolean mIsAccessible = true;
+ private final Object mLock = new Object();
+
+ private native int nativeStartSharedFilter();
+ private native int nativeStopSharedFilter();
+ private native int nativeFlushSharedFilter();
+ private native int nativeSharedRead(byte[] buffer, long offset, long size);
+ private native int nativeSharedClose();
+
+ // Called by JNI
+ private SharedFilter() {}
+
+ private void onFilterStatus(int status) {
+ synchronized (mLock) {
+ if (status == STATUS_INACCESSIBLE) {
+ mIsAccessible = false;
+ }
+ }
+ synchronized (mCallbackLock) {
+ if (mCallback != null && mExecutor != null) {
+ mExecutor.execute(() -> {
+ synchronized (mCallbackLock) {
+ if (mCallback != null) {
+ mCallback.onFilterStatusChanged(this, status);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ private void onFilterEvent(FilterEvent[] events) {
+ synchronized (mCallbackLock) {
+ if (mCallback != null && mExecutor != null) {
+ mExecutor.execute(() -> {
+ synchronized (mCallbackLock) {
+ if (mCallback != null) {
+ mCallback.onFilterEvent(this, events);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ /** @hide */
+ public void setCallback(SharedFilterCallback cb, Executor executor) {
+ synchronized (mCallbackLock) {
+ mCallback = cb;
+ mExecutor = executor;
+ }
+ }
+
+ /** @hide */
+ public SharedFilterCallback getCallback() {
+ synchronized (mCallbackLock) { return mCallback; }
+ }
+
+ /**
+ * Starts filtering data.
+ *
+ * <p>Does nothing if the filter is already started.
+ *
+ * @return result status of the operation.
+ */
+ @Result
+ public int start() {
+ synchronized (mLock) {
+ TunerUtils.checkResourceAccessible(TAG, mIsAccessible);
+ TunerUtils.checkResourceState(TAG, mIsClosed);
+ return nativeStartSharedFilter();
+ }
+ }
+
+ /**
+ * Stops filtering data.
+ *
+ * <p>Does nothing if the filter is stopped or not started.
+ *
+ * @return result status of the operation.
+ */
+ @Result
+ public int stop() {
+ synchronized (mLock) {
+ TunerUtils.checkResourceAccessible(TAG, mIsAccessible);
+ TunerUtils.checkResourceState(TAG, mIsClosed);
+ return nativeStopSharedFilter();
+ }
+ }
+
+ /**
+ * Flushes the filter.
+ *
+ * <p>The data which is already produced by filter but not consumed yet will
+ * be cleared.
+ *
+ * @return result status of the operation.
+ */
+ @Result
+ public int flush() {
+ synchronized (mLock) {
+ TunerUtils.checkResourceAccessible(TAG, mIsAccessible);
+ TunerUtils.checkResourceState(TAG, mIsClosed);
+ return nativeFlushSharedFilter();
+ }
+ }
+
+ /**
+ * Copies filtered data from filter output to the given byte array.
+ *
+ * @param buffer the buffer to store the filtered data.
+ * @param offset the index of the first byte in {@code buffer} to write.
+ * @param size the maximum number of bytes to read.
+ * @return the number of bytes read.
+ */
+ public int read(@NonNull byte[] buffer, @BytesLong long offset, @BytesLong long size) {
+ synchronized (mLock) {
+ TunerUtils.checkResourceAccessible(TAG, mIsAccessible);
+ TunerUtils.checkResourceState(TAG, mIsClosed);
+ size = Math.min(size, buffer.length - offset);
+ return nativeSharedRead(buffer, offset, size);
+ }
+ }
+
+ /**
+ * Stops filtering data and releases the Filter instance.
+ */
+ @Override
+ public void close() {
+ synchronized (mLock) {
+ if (mIsClosed) {
+ return;
+ }
+ nativeSharedClose();
+ mIsClosed = true;
+ }
+ }
+}
diff --git a/media/java/android/media/tv/tuner/filter/SharedFilterCallback.java b/media/java/android/media/tv/tuner/filter/SharedFilterCallback.java
new file mode 100644
index 0000000..86a4c1a
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/SharedFilterCallback.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.filter;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+
+/**
+ * Callback interface for receiving information from the corresponding shared filters.
+ *
+ * @hide
+ */
+@SystemApi
+public interface SharedFilterCallback {
+ /**
+ * Invoked when there are filter events.
+ *
+ * @param sharedfilter the corresponding shared filter which sent the events.
+ * @param events the filter events sent from the filter.
+ */
+ void onFilterEvent(@NonNull SharedFilter sharedfilter,
+ @SuppressLint("ArrayReturn") @NonNull FilterEvent[] events);
+ /**
+ * Invoked when filter status changed.
+ *
+ * @param sharedfilter the corresponding shared filter whose status is changed.
+ * @param status the new status of the filter.
+ */
+ void onFilterStatusChanged(@NonNull SharedFilter sharedfilter, @Filter.Status int status);
+}
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 4bee485..35f18751 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -298,6 +298,7 @@
jfieldID dvrRecorderContext;
jfieldID dvrPlaybackContext;
jfieldID mediaEventContext;
+ jfieldID sharedFilterContext;
jmethodID frontendInitID;
jmethodID filterInitID;
jmethodID timeFilterInitID;
@@ -314,6 +315,9 @@
jmethodID descramblerInitID;
jmethodID linearBlockInitID;
jmethodID linearBlockSetInternalStateID;
+ jmethodID sharedFilterInitID;
+ jmethodID onSharedFilterStatusID;
+ jmethodID onSharedFilterEventID;
};
static fields_t gFields;
@@ -884,10 +888,11 @@
}
jobject filter(env->NewLocalRef(mFilterObj));
if (!env->IsSameObject(filter, nullptr)) {
- env->CallVoidMethod(
- filter,
- gFields.onFilterEventID,
- array);
+ jmethodID methodID = gFields.onFilterEventID;
+ if (mSharedFilter) {
+ methodID = gFields.onSharedFilterEventID;
+ }
+ env->CallVoidMethod(filter, methodID, array);
} else {
ALOGE("FilterClientCallbackImpl::onFilterEvent:"
"Filter object has been freed. Ignoring callback.");
@@ -899,13 +904,14 @@
JNIEnv *env = AndroidRuntime::getJNIEnv();
jobject filter(env->NewLocalRef(mFilterObj));
if (!env->IsSameObject(filter, nullptr)) {
- env->CallVoidMethod(
- filter,
- gFields.onFilterStatusID,
- (jint)status);
+ jmethodID methodID = gFields.onFilterStatusID;
+ if (mSharedFilter) {
+ methodID = gFields.onSharedFilterStatusID;
+ }
+ env->CallVoidMethod(filter, methodID, (jint)static_cast<uint8_t>(status));
} else {
ALOGE("FilterClientCallbackImpl::onFilterStatus:"
- "Filter object has been freed. Ignoring callback.");
+ "Filter object has been freed. Ignoring callback.");
}
}
@@ -914,6 +920,15 @@
// Java Object
mFilterObj = filterObj;
mFilterClient = filterClient;
+ mSharedFilter = false;
+}
+
+void FilterClientCallbackImpl::setSharedFilter(jweak filterObj, sp<FilterClient> filterClient) {
+ ALOGV("FilterClientCallbackImpl::setFilter");
+ // Java Object
+ mFilterObj = filterObj;
+ mFilterClient = filterClient;
+ mSharedFilter = true;
}
FilterClientCallbackImpl::~FilterClientCallbackImpl() {
@@ -2974,6 +2989,10 @@
return (FilterClient *)env->GetLongField(filter, gFields.filterContext);
}
+static sp<FilterClient> getSharedFilterClient(JNIEnv *env, jobject filter) {
+ return (FilterClient *)env->GetLongField(filter, gFields.sharedFilterContext);
+}
+
static sp<LnbClient> getLnbClient(JNIEnv *env, jobject lnb) {
return (LnbClient *)env->GetLongField(lnb, gFields.lnbContext);
}
@@ -3049,6 +3068,14 @@
env->GetMethodID(filterClazz, "onFilterEvent",
"([Landroid/media/tv/tuner/filter/FilterEvent;)V");
+ jclass sharedFilterClazz = env->FindClass("android/media/tv/tuner/filter/SharedFilter");
+ gFields.sharedFilterContext = env->GetFieldID(filterClazz, "mNativeContext", "J");
+ gFields.sharedFilterInitID = env->GetMethodID(sharedFilterClazz, "<init>", "()V");
+ gFields.onSharedFilterStatusID = env->GetMethodID(sharedFilterClazz, "onFilterStatus", "(I)V");
+ gFields.onSharedFilterEventID =
+ env->GetMethodID(sharedFilterClazz, "onFilterEvent",
+ "([Landroid/media/tv/tuner/filter/FilterEvent;)V");
+
jclass timeFilterClazz = env->FindClass("android/media/tv/tuner/filter/TimeFilter");
gFields.timeFilterContext = env->GetFieldID(timeFilterClazz, "mNativeContext", "J");
gFields.timeFilterInitID = env->GetMethodID(timeFilterClazz, "<init>", "()V");
@@ -3718,7 +3745,12 @@
}
static jint android_media_tv_Tuner_start_filter(JNIEnv *env, jobject filter) {
- sp<FilterClient> filterClient = getFilterClient(env, filter);
+ sp<FilterClient> filterClient = nullptr;
+ if (env->IsInstanceOf(filter, env->FindClass("android/media/tv/tuner/filter/SharedFilter"))) {
+ filterClient = getSharedFilterClient(env, filter);
+ } else {
+ filterClient = getFilterClient(env, filter);
+ }
if (filterClient == nullptr) {
ALOGD("Failed to start filter: filter client not found");
return (int)Result::NOT_INITIALIZED;
@@ -3727,7 +3759,12 @@
}
static jint android_media_tv_Tuner_stop_filter(JNIEnv *env, jobject filter) {
- sp<FilterClient> filterClient = getFilterClient(env, filter);
+ sp<FilterClient> filterClient = nullptr;
+ if (env->IsInstanceOf(filter, env->FindClass("android/media/tv/tuner/filter/SharedFilter"))) {
+ filterClient = getSharedFilterClient(env, filter);
+ } else {
+ filterClient = getFilterClient(env, filter);
+ }
if (filterClient == nullptr) {
ALOGD("Failed to stop filter: filter client not found");
return (int)Result::NOT_INITIALIZED;
@@ -3736,17 +3773,27 @@
}
static jint android_media_tv_Tuner_flush_filter(JNIEnv *env, jobject filter) {
- sp<FilterClient> filterClient = getFilterClient(env, filter);
+ sp<FilterClient> filterClient = nullptr;
+ if (env->IsInstanceOf(filter, env->FindClass("android/media/tv/tuner/filter/SharedFilter"))) {
+ filterClient = getSharedFilterClient(env, filter);
+ } else {
+ filterClient = getFilterClient(env, filter);
+ }
if (filterClient == nullptr) {
ALOGD("Failed to flush filter: filter client not found");
- return (int)Result::NOT_INITIALIZED;
+ return (jint)Result::NOT_INITIALIZED;
}
return (jint)filterClient->flush();
}
static jint android_media_tv_Tuner_read_filter_fmq(
JNIEnv *env, jobject filter, jbyteArray buffer, jlong offset, jlong size) {
- sp<FilterClient> filterClient = getFilterClient(env, filter);
+ sp<FilterClient> filterClient = nullptr;
+ if (env->IsInstanceOf(filter, env->FindClass("android/media/tv/tuner/filter/SharedFilter"))) {
+ filterClient = getSharedFilterClient(env, filter);
+ } else {
+ filterClient = getFilterClient(env, filter);
+ }
if (filterClient == nullptr) {
jniThrowException(env, "java/lang/IllegalStateException",
"Failed to read filter FMQ: filter client not found");
@@ -3766,14 +3813,57 @@
}
static jint android_media_tv_Tuner_close_filter(JNIEnv *env, jobject filter) {
- sp<FilterClient> filterClient = getFilterClient(env, filter);
+ sp<FilterClient> filterClient = nullptr;
+ bool shared = env->IsInstanceOf(
+ filter, env->FindClass("android/media/tv/tuner/filter/SharedFilter"));
+ if (shared) {
+ filterClient = getSharedFilterClient(env, filter);
+ } else {
+ filterClient = getFilterClient(env, filter);
+ }
if (filterClient == nullptr) {
jniThrowException(env, "java/lang/IllegalStateException",
"Failed to close filter: filter client not found");
return 0;
}
- return (jint)filterClient->close();
+ Result r = filterClient->close();
+ filterClient->decStrong(filter);
+ env->SetLongField(filter, gFields.sharedFilterContext, 0);
+ if (shared) {
+ } else {
+ env->SetLongField(filter, gFields.filterContext, 0);
+ }
+
+ return (jint)r;
+}
+
+static jstring android_media_tv_Tuner_create_shared_filter(JNIEnv *env, jobject filter) {
+ sp<FilterClient> filterClient = getFilterClient(env, filter);
+ if (filterClient == nullptr) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Failed to create shared filter: filter client not found");
+ return nullptr;
+ }
+
+ string token = filterClient->createSharedFilter();
+ if (token.empty()) {
+ return nullptr;
+ }
+ return env->NewStringUTF(token.data());
+}
+
+static void android_media_tv_Tuner_release_shared_filter(
+ JNIEnv *env, jobject filter, jstring token) {
+ sp<FilterClient> filterClient = getFilterClient(env, filter);
+ if (filterClient == nullptr) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Failed to release shared filter: filter client not found");
+ return;
+ }
+
+ std::string filterToken(env->GetStringUTFChars(token, nullptr));
+ filterClient->releaseSharedFilter(filterToken);
}
static sp<TimeFilterClient> getTimeFilterClient(JNIEnv *env, jobject filter) {
@@ -3843,10 +3933,8 @@
}
Result r = timeFilterClient->close();
- if (r == Result::SUCCESS) {
- timeFilterClient->decStrong(filter);
- env->SetLongField(filter, gFields.timeFilterContext, 0);
- }
+ timeFilterClient->decStrong(filter);
+ env->SetLongField(filter, gFields.timeFilterContext, 0);
return (int)r;
}
@@ -3896,9 +3984,8 @@
return (jint)Result::NOT_INITIALIZED;
}
Result r = descramblerClient->close();
- if (r == Result::SUCCESS) {
- descramblerClient->decStrong(descrambler);
- }
+ descramblerClient->decStrong(descrambler);
+ env->SetLongField(descrambler, gFields.descramblerContext, 0);
return (jint)r;
}
@@ -3935,6 +4022,29 @@
return tuner->closeDemux();
}
+static jobject android_media_tv_Tuner_open_shared_filter(
+ JNIEnv* env, jobject /* thiz */, jstring token) {
+
+ sp<TunerClient> tunerClient = new TunerClient();
+ std::string filterToken(env->GetStringUTFChars(token, nullptr));
+ sp<FilterClient> filterClient;
+ sp<FilterClientCallbackImpl> callback = new FilterClientCallbackImpl();
+ filterClient = tunerClient->openSharedFilter(filterToken, callback);
+ if (filterClient == nullptr) {
+ ALOGD("Failed to open shared filter %s", filterToken.c_str());
+ return nullptr;
+ }
+
+ jobject filterObj = env->NewObject(env->FindClass("android/media/tv/tuner/filter/SharedFilter"),
+ gFields.sharedFilterInitID);
+
+ filterClient->incStrong(filterObj);
+ env->SetLongField(filterObj, gFields.sharedFilterContext, (jlong)filterClient.get());
+ callback->setSharedFilter(env->NewWeakGlobalRef(filterObj), filterClient);
+
+ return filterObj;
+}
+
static jint android_media_tv_Tuner_close_frontend(JNIEnv* env, jobject thiz, jint /* handle */) {
sp<JTuner> tuner = getTuner(env, thiz);
return tuner->closeFrontend();
@@ -4014,7 +4124,14 @@
ALOGD("Failed to close dvr: dvr client not found");
return (jint)Result::NOT_INITIALIZED;
}
- return (jint)dvrClient->close();
+ Result r = dvrClient->close();
+ bool isRecorder =
+ env->IsInstanceOf(dvr, env->FindClass("android/media/tv/tuner/dvr/DvrRecorder"));
+ jfieldID fieldId =
+ isRecorder ? gFields.dvrRecorderContext : gFields.dvrPlaybackContext;
+ dvrClient->decStrong(dvr);
+ env->SetLongField(dvr, fieldId, 0);
+ return (jint)r;
}
static jint android_media_tv_Tuner_lnb_set_voltage(JNIEnv* env, jobject lnb, jint voltage) {
@@ -4043,10 +4160,8 @@
static int android_media_tv_Tuner_close_lnb(JNIEnv* env, jobject lnb) {
sp<LnbClient> lnbClient = getLnbClient(env, lnb);
Result r = lnbClient->close();
- if (r == Result::SUCCESS) {
- lnbClient->decStrong(lnb);
- env->SetLongField(lnb, gFields.lnbContext, 0);
- }
+ lnbClient->decStrong(lnb);
+ env->SetLongField(lnb, gFields.lnbContext, 0);
return (jint)r;
}
@@ -4221,6 +4336,9 @@
{ "nativeClose", "()I", (void *)android_media_tv_Tuner_close_tuner },
{ "nativeCloseFrontend", "(I)I", (void *)android_media_tv_Tuner_close_frontend },
{ "nativeCloseDemux", "(I)I", (void *)android_media_tv_Tuner_close_demux },
+ {"nativeOpenSharedFilter",
+ "(Ljava/lang/String;)Landroid/media/tv/tuner/filter/SharedFilter;",
+ (void *)android_media_tv_Tuner_open_shared_filter},
};
static const JNINativeMethod gFilterMethods[] = {
@@ -4238,6 +4356,18 @@
{ "nativeFlushFilter", "()I", (void *)android_media_tv_Tuner_flush_filter },
{ "nativeRead", "([BJJ)I", (void *)android_media_tv_Tuner_read_filter_fmq },
{ "nativeClose", "()I", (void *)android_media_tv_Tuner_close_filter },
+ {"nativeCreateSharedFilter", "()Ljava/lang/String;",
+ (void *)android_media_tv_Tuner_create_shared_filter},
+ {"nativeReleaseSharedFilter", "(Ljava/lang/String;)V",
+ (void *)android_media_tv_Tuner_release_shared_filter},
+};
+
+static const JNINativeMethod gSharedFilterMethods[] = {
+ {"nativeStartSharedFilter", "()I", (void *)android_media_tv_Tuner_start_filter},
+ {"nativeStopSharedFilter", "()I", (void *)android_media_tv_Tuner_stop_filter},
+ {"nativeFlushSharedFilter", "()I", (void *)android_media_tv_Tuner_flush_filter},
+ {"nativeSharedRead", "([BJJ)I", (void *)android_media_tv_Tuner_read_filter_fmq},
+ {"nativeSharedClose", "()I", (void *)android_media_tv_Tuner_close_filter},
};
static const JNINativeMethod gTimeFilterMethods[] = {
@@ -4322,6 +4452,13 @@
return false;
}
if (AndroidRuntime::registerNativeMethods(
+ env, "android/media/tv/tuner/filter/SharedFilter",
+ gSharedFilterMethods,
+ NELEM(gSharedFilterMethods)) != JNI_OK) {
+ ALOGE("Failed to register shared filter native methods");
+ return false;
+ }
+ if (AndroidRuntime::registerNativeMethods(
env, "android/media/tv/tuner/filter/TimeFilter",
gTimeFilterMethods,
NELEM(gTimeFilterMethods)) != JNI_OK) {
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index 5401ddd..31d24ee 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -130,9 +130,12 @@
virtual void onFilterStatus(const DemuxFilterStatus status);
void setFilter(jweak filterObj, sp<FilterClient> filterClient);
+ void setSharedFilter(jweak filterObj, sp<FilterClient> filterClient);
+
private:
jweak mFilterObj;
sp<FilterClient> mFilterClient;
+ bool mSharedFilter;
void getSectionEvent(jobjectArray& arr, const int size, const DemuxFilterEvent& event);
void getMediaEvent(jobjectArray& arr, const int size, const DemuxFilterEvent& event);
void getPesEvent(jobjectArray& arr, const int size, const DemuxFilterEvent& event);
diff --git a/media/jni/tuner/FilterClient.cpp b/media/jni/tuner/FilterClient.cpp
index d4d8837..fe746fa 100644
--- a/media/jni/tuner/FilterClient.cpp
+++ b/media/jni/tuner/FilterClient.cpp
@@ -193,8 +193,27 @@
return Result::INVALID_STATE;
}
-/////////////// TunerFilterCallback ///////////////////////
+string FilterClient::createSharedFilter() {
+ if (mTunerFilter != nullptr) {
+ string filterToken;
+ if (mTunerFilter->createSharedFilter(&filterToken).isOk()) {
+ return filterToken;
+ }
+ }
+ return "";
+}
+
+Result FilterClient::releaseSharedFilter(const string& filterToken) {
+ if (mTunerFilter != nullptr) {
+ Status s = mTunerFilter->releaseSharedFilter(filterToken);
+ return ClientHelper::getServiceSpecificErrorCode(s);
+ }
+
+ return Result::INVALID_STATE;
+}
+
+/////////////// TunerFilterCallback ///////////////////////
TunerFilterCallback::TunerFilterCallback(sp<FilterClientCallback> filterClientCallback)
: mFilterClientCallback(filterClientCallback) {}
@@ -215,14 +234,14 @@
}
Result FilterClient::getFilterMq() {
- if (mFilterMQ != NULL) {
+ if (mFilterMQ != nullptr) {
return Result::SUCCESS;
}
AidlMQDesc aidlMqDesc;
Result res = Result::UNAVAILABLE;
- if (mTunerFilter != NULL) {
+ if (mTunerFilter != nullptr) {
Status s = mTunerFilter->getQueueDesc(&aidlMqDesc);
if (s.isOk()) {
mFilterMQ = new (nothrow) AidlMQ(aidlMqDesc, false/*resetPointer*/);
diff --git a/media/jni/tuner/FilterClient.h b/media/jni/tuner/FilterClient.h
index 136d1f5..c031b2a 100644
--- a/media/jni/tuner/FilterClient.h
+++ b/media/jni/tuner/FilterClient.h
@@ -142,6 +142,16 @@
*/
Result close();
+ /**
+ * Create a new SharedFiler.
+ */
+ string createSharedFilter();
+
+ /**
+ * Release SharedFiler.
+ */
+ Result releaseSharedFilter(const string& filterToken);
+
private:
Result getFilterMq();
int64_t copyData(int8_t* buffer, int64_t size);
diff --git a/media/jni/tuner/TunerClient.cpp b/media/jni/tuner/TunerClient.cpp
index d19ee0d..861d78d 100644
--- a/media/jni/tuner/TunerClient.cpp
+++ b/media/jni/tuner/TunerClient.cpp
@@ -33,8 +33,10 @@
/////////////// TunerClient ///////////////////////
TunerClient::TunerClient() {
- ::ndk::SpAIBinder binder(AServiceManager_getService("media.tuner"));
- mTunerService = ITunerService::fromBinder(binder);
+ if (mTunerService == nullptr) {
+ ::ndk::SpAIBinder binder(AServiceManager_getService("media.tuner"));
+ mTunerService = ITunerService::fromBinder(binder);
+ }
if (mTunerService == nullptr) {
ALOGE("Failed to get tuner service");
} else {
@@ -43,8 +45,6 @@
}
TunerClient::~TunerClient() {
- mTunerVersion = 0;
- mTunerService = nullptr;
}
vector<int32_t> TunerClient::getFrontendIds() {
@@ -163,4 +163,26 @@
return nullptr;
}
+sp<FilterClient> TunerClient::openSharedFilter(const string& filterToken,
+ sp<FilterClientCallback> cb) {
+ if (cb == nullptr) {
+ return nullptr;
+ }
+
+ if (mTunerService != nullptr) {
+ shared_ptr<ITunerFilter> tunerFilter;
+ shared_ptr<TunerFilterCallback> callback =
+ ::ndk::SharedRefBase::make<TunerFilterCallback>(cb);
+ Status s = mTunerService->openSharedFilter(filterToken, callback, &tunerFilter);
+ if (!s.isOk()) {
+ return nullptr;
+ }
+ DemuxFilterType type;
+ tunerFilter->getFilterType(&type);
+ return new FilterClient(type, tunerFilter);
+ }
+
+ return nullptr;
+}
+
} // namespace android
diff --git a/media/jni/tuner/TunerClient.h b/media/jni/tuner/TunerClient.h
index 641f106..3e59e26 100644
--- a/media/jni/tuner/TunerClient.h
+++ b/media/jni/tuner/TunerClient.h
@@ -20,10 +20,12 @@
#include <aidl/android/media/tv/tuner/ITunerService.h>
#include <android/binder_parcel_utils.h>
-#include "DemuxClient.h"
#include "ClientHelper.h"
-#include "FrontendClient.h"
+#include "DemuxClient.h"
#include "DescramblerClient.h"
+#include "FilterClient.h"
+#include "FilterClientCallback.h"
+#include "FrontendClient.h"
#include "LnbClient.h"
using Status = ::ndk::ScopedAStatus;
@@ -116,6 +118,15 @@
*/
int32_t getHalTunerVersion() { return mTunerVersion; }
+ /**
+ * Open a new shared filter client.
+ *
+ * @param filterToken the shared filter token created by FilterClient.
+ * @param cb the FilterClientCallback to receive filter events.
+ * @return a newly created TunerFilter interface.
+ */
+ sp<FilterClient> openSharedFilter(const string& filterToken, sp<FilterClientCallback> cb);
+
private:
/**
* An AIDL Tuner Service Singleton assigned at the first time the Tuner Client
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index ce626bf..ddf0289 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -424,6 +424,7 @@
<!-- Permissions required for CTS test - TunerTest -->
<uses-permission android:name="android.permission.ACCESS_TV_DESCRAMBLER" />
+ <uses-permission android:name="android.permission.ACCESS_TV_SHARED_FILTER" />
<uses-permission android:name="android.permission.ACCESS_TV_TUNER" />
<uses-permission android:name="android.permission.TUNER_RESOURCE_ACCESS" />