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" />