diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 95d06b0351e49..c0dcceb9f10a5 100755 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -20,6 +20,7 @@ package android { 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 @@ package android.media.tv.tuner { 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 @@ package android.media.tv.tuner.filter { 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 @@ package android.media.tv.tuner.filter { 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 b3a16d97b27b9..df69ed005514a 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -5802,6 +5802,12 @@ + + + diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 25fb2230c6157..33cc61b8f84fc 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -425,6 +425,7 @@ applications that come with the platform + diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index 405f9eedab14f..0d2676198d376 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.RequiresPermission; 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; 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 @@ public class Tuner implements AutoCloseable { 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 @@ public class Tuner implements AutoCloseable { 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 3a15daf742324..5541508780735 100644 --- a/media/java/android/media/tv/tuner/TunerUtils.java +++ b/media/java/android/media/tv/tuner/TunerUtils.java @@ -172,5 +172,16 @@ public final class TunerUtils { } } + /** + * 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 bd860d9e3492e..ae2711200afd4 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; 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 @@ public class Filter implements AutoCloseable { 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 @@ public class Filter implements AutoCloseable { 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 @@ public class Filter implements AutoCloseable { * coming events until it receives {@link RestartEvent} through {@link FilterCallback} to avoid * using the events from the previous configuration. * + *

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 class Filter implements AutoCloseable { 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 @@ public class Filter implements AutoCloseable { * will cause no-op. Use {@link TunerVersionChecker#getTunerVersion()} to get the version * information. * + *

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 class Filter implements AutoCloseable { 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 @@ public class Filter implements AutoCloseable { * extract all protocols' header. Then a filter's data source can be output * from another filter. * + *

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 class Filter implements AutoCloseable { 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 @@ public class Filter implements AutoCloseable { * coming events until it receives {@link RestartEvent} through {@link FilterCallback} to avoid * using the events from the previous configuration. * + *

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 @@ public class Filter implements AutoCloseable { * coming events until it receives {@link RestartEvent} through {@link FilterCallback} to avoid * using the events from the previous configuration. * + *

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 @@ public class Filter implements AutoCloseable { *

The data which is already produced by filter but not consumed yet will * be cleared. * + *

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 @@ public class Filter implements AutoCloseable { /** * Copies filtered data from filter output to the given byte array. * + *

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 class Filter implements AutoCloseable { 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 @@ public class Filter implements AutoCloseable { /** * Stops filtering data and releases the Filter instance. + * + *

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 @@ public class Filter implements AutoCloseable { 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 0000000000000..f86ad11ac791d --- /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. + * + *

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. + * + *

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. + * + *

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. + * + *

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 0000000000000..86a4c1a9c8e3d --- /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 4bee485eb9b1a..35f1875193abf 100644 --- a/media/jni/android_media_tv_Tuner.cpp +++ b/media/jni/android_media_tv_Tuner.cpp @@ -298,6 +298,7 @@ struct fields_t { jfieldID dvrRecorderContext; jfieldID dvrPlaybackContext; jfieldID mediaEventContext; + jfieldID sharedFilterContext; jmethodID frontendInitID; jmethodID filterInitID; jmethodID timeFilterInitID; @@ -314,6 +315,9 @@ struct fields_t { jmethodID descramblerInitID; jmethodID linearBlockInitID; jmethodID linearBlockSetInternalStateID; + jmethodID sharedFilterInitID; + jmethodID onSharedFilterStatusID; + jmethodID onSharedFilterEventID; }; static fields_t gFields; @@ -884,10 +888,11 @@ void FilterClientCallbackImpl::onFilterEvent(const vector &eve } 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 @@ void FilterClientCallbackImpl::onFilterStatus(const DemuxFilterStatus status) { 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(status)); } else { ALOGE("FilterClientCallbackImpl::onFilterStatus:" - "Filter object has been freed. Ignoring callback."); + "Filter object has been freed. Ignoring callback."); } } @@ -914,6 +920,15 @@ void FilterClientCallbackImpl::setFilter(jweak filterObj, sp filte // Java Object mFilterObj = filterObj; mFilterClient = filterClient; + mSharedFilter = false; +} + +void FilterClientCallbackImpl::setSharedFilter(jweak filterObj, sp filterClient) { + ALOGV("FilterClientCallbackImpl::setFilter"); + // Java Object + mFilterObj = filterObj; + mFilterClient = filterClient; + mSharedFilter = true; } FilterClientCallbackImpl::~FilterClientCallbackImpl() { @@ -2974,6 +2989,10 @@ static sp getFilterClient(JNIEnv *env, jobject filter) { return (FilterClient *)env->GetLongField(filter, gFields.filterContext); } +static sp getSharedFilterClient(JNIEnv *env, jobject filter) { + return (FilterClient *)env->GetLongField(filter, gFields.sharedFilterContext); +} + static sp getLnbClient(JNIEnv *env, jobject lnb) { return (LnbClient *)env->GetLongField(lnb, gFields.lnbContext); } @@ -3049,6 +3068,14 @@ static void android_media_tv_Tuner_native_init(JNIEnv *env) { 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, "", "()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, "", "()V"); @@ -3718,7 +3745,12 @@ static jint android_media_tv_Tuner_set_filter_data_source( } static jint android_media_tv_Tuner_start_filter(JNIEnv *env, jobject filter) { - sp filterClient = getFilterClient(env, filter); + sp 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_start_filter(JNIEnv *env, jobject filter) { } static jint android_media_tv_Tuner_stop_filter(JNIEnv *env, jobject filter) { - sp filterClient = getFilterClient(env, filter); + sp 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_stop_filter(JNIEnv *env, jobject filter) { } static jint android_media_tv_Tuner_flush_filter(JNIEnv *env, jobject filter) { - sp filterClient = getFilterClient(env, filter); + sp 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 = getFilterClient(env, filter); + sp 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_read_filter_fmq( } static jint android_media_tv_Tuner_close_filter(JNIEnv *env, jobject filter) { - sp filterClient = getFilterClient(env, filter); + sp 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 = 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 = 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 getTimeFilterClient(JNIEnv *env, jobject filter) { @@ -3843,10 +3933,8 @@ static int android_media_tv_Tuner_time_filter_close(JNIEnv *env, jobject filter) } 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 @@ static jint android_media_tv_Tuner_close_descrambler(JNIEnv* env, jobject descra 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 @@ static jint android_media_tv_Tuner_close_demux(JNIEnv* env, jobject thiz, jint / return tuner->closeDemux(); } +static jobject android_media_tv_Tuner_open_shared_filter( + JNIEnv* env, jobject /* thiz */, jstring token) { + + sp tunerClient = new TunerClient(); + std::string filterToken(env->GetStringUTFChars(token, nullptr)); + sp filterClient; + sp 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 tuner = getTuner(env, thiz); return tuner->closeFrontend(); @@ -4014,7 +4124,14 @@ static jint android_media_tv_Tuner_close_dvr(JNIEnv* env, jobject dvr) { 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_lnb_send_diseqc_msg(JNIEnv* env, jobject lnb, static int android_media_tv_Tuner_close_lnb(JNIEnv* env, jobject lnb) { sp 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 @@ static const JNINativeMethod gTunerMethods[] = { { "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 @@ static const JNINativeMethod gFilterMethods[] = { { "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[] = { @@ -4321,6 +4451,13 @@ static bool register_android_media_tv_Tuner(JNIEnv *env) { ALOGE("Failed to register filter native methods"); 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, diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h index 5401ddd10f5de..31d24ee13cf7d 100644 --- a/media/jni/android_media_tv_Tuner.h +++ b/media/jni/android_media_tv_Tuner.h @@ -130,9 +130,12 @@ struct FilterClientCallbackImpl : public FilterClientCallback { virtual void onFilterStatus(const DemuxFilterStatus status); void setFilter(jweak filterObj, sp filterClient); + void setSharedFilter(jweak filterObj, sp filterClient); + private: jweak mFilterObj; sp 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 d4d8837c554a1..fe746fa7971f4 100644 --- a/media/jni/tuner/FilterClient.cpp +++ b/media/jni/tuner/FilterClient.cpp @@ -193,8 +193,27 @@ Result FilterClient::close() { 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) : mFilterClientCallback(filterClientCallback) {} @@ -215,14 +234,14 @@ Status TunerFilterCallback::onFilterEvent(const vector& filter } 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 136d1f58b205b..c031b2ac0b5ba 100644 --- a/media/jni/tuner/FilterClient.h +++ b/media/jni/tuner/FilterClient.h @@ -142,6 +142,16 @@ public: */ 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 d19ee0d460ea6..861d78d6c3eca 100644 --- a/media/jni/tuner/TunerClient.cpp +++ b/media/jni/tuner/TunerClient.cpp @@ -33,8 +33,10 @@ int32_t TunerClient::mTunerVersion; /////////////// 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() { } TunerClient::~TunerClient() { - mTunerVersion = 0; - mTunerService = nullptr; } vector TunerClient::getFrontendIds() { @@ -163,4 +163,26 @@ sp TunerClient::openLnbByName(string lnbName) { return nullptr; } +sp TunerClient::openSharedFilter(const string& filterToken, + sp cb) { + if (cb == nullptr) { + return nullptr; + } + + if (mTunerService != nullptr) { + shared_ptr tunerFilter; + shared_ptr callback = + ::ndk::SharedRefBase::make(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 641f106d6dffc..3e59e26dea818 100644 --- a/media/jni/tuner/TunerClient.h +++ b/media/jni/tuner/TunerClient.h @@ -20,10 +20,12 @@ #include #include -#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 @@ public: */ 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 openSharedFilter(const string& filterToken, sp 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 ce626bf053d0c..ddf0289e47953 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -424,6 +424,7 @@ +