Merge "Enable capability based demux management"
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 4e0e4b9..e5b55b2 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -7503,6 +7503,7 @@
     method public int getAudioFilterCount();
     method public int getDemuxCount();
     method public int getFilterCapabilities();
+    method @NonNull public int[] getFilterTypeCapabilityList();
     method @NonNull @Size(5) public int[] getLinkCapabilities();
     method public int getPcrFilterCount();
     method public int getPesFilterCount();
@@ -7515,6 +7516,12 @@
     method public boolean isTimeFilterSupported();
   }
 
+  public class DemuxInfo {
+    ctor public DemuxInfo(int);
+    method public int getFilterTypes();
+    method public void setFilterTypes(int);
+  }
+
   public class Descrambler implements java.lang.AutoCloseable {
     method public int addPid(int, int, @Nullable android.media.tv.tuner.filter.Filter);
     method public void close();
@@ -7567,6 +7574,7 @@
     method public void clearResourceLostListener();
     method public void close();
     method public void closeFrontend();
+    method public int configureDemux(@Nullable android.media.tv.tuner.DemuxInfo);
     method public int connectCiCam(int);
     method public int connectFrontendToCiCam(int);
     method public int disconnectCiCam();
@@ -7574,6 +7582,7 @@
     method public int getAvSyncHwId(@NonNull android.media.tv.tuner.filter.Filter);
     method public long getAvSyncTime(int);
     method @Nullable public java.util.List<android.media.tv.tuner.frontend.FrontendInfo> getAvailableFrontendInfos();
+    method @Nullable public android.media.tv.tuner.DemuxInfo getCurrentDemuxInfo();
     method @Nullable public String getCurrentFrontendHardwareInfo();
     method @Nullable public android.media.tv.tuner.DemuxCapabilities getDemuxCapabilities();
     method @Nullable public android.media.tv.tuner.frontend.FrontendInfo getFrontendInfo();
diff --git a/media/java/android/media/tv/tuner/DemuxCapabilities.java b/media/java/android/media/tv/tuner/DemuxCapabilities.java
index 14a9144..19cd023a 100644
--- a/media/java/android/media/tv/tuner/DemuxCapabilities.java
+++ b/media/java/android/media/tv/tuner/DemuxCapabilities.java
@@ -36,8 +36,14 @@
 public class DemuxCapabilities {
 
     /** @hide */
-    @IntDef(value = {Filter.TYPE_TS, Filter.TYPE_MMTP, Filter.TYPE_IP, Filter.TYPE_TLV,
-                    Filter.TYPE_ALP})
+    @IntDef(flag = true, prefix = { "TYPE_" }, value = {
+          Filter.TYPE_UNDEFINED,
+          Filter.TYPE_TS,
+          Filter.TYPE_MMTP,
+          Filter.TYPE_IP,
+          Filter.TYPE_TLV,
+          Filter.TYPE_ALP,
+    })
     @Retention(RetentionPolicy.SOURCE)
     public @interface FilterCapabilities {}
 
@@ -51,14 +57,16 @@
     private final int mPesFilterCount;
     private final int mPcrFilterCount;
     private final long mSectionFilterLength;
-    private final int mFilterCaps;
+    private final @FilterCapabilities int mFilterCaps;
+    private final @FilterCapabilities int[] mFilterCapsList;
     private final int[] mLinkCaps;
     private final boolean mSupportTimeFilter;
 
     // Used by JNI
     private DemuxCapabilities(int demuxCount, int recordCount, int playbackCount, int tsFilterCount,
             int sectionFilterCount, int audioFilterCount, int videoFilterCount, int pesFilterCount,
-            int pcrFilterCount, long sectionFilterLength, int filterCaps, int[] linkCaps,
+            int pcrFilterCount, long sectionFilterLength, int filterCaps,
+            @FilterCapabilities int[] filterCapsList, @FilterCapabilities int[] linkCaps,
             boolean timeFilter) {
         mDemuxCount = demuxCount;
         mRecordCount = recordCount;
@@ -71,6 +79,7 @@
         mPcrFilterCount = pcrFilterCount;
         mSectionFilterLength = sectionFilterLength;
         mFilterCaps = filterCaps;
+        mFilterCapsList = filterCapsList;
         mLinkCaps = linkCaps;
         mSupportTimeFilter = timeFilter;
     }
@@ -148,6 +157,24 @@
     }
 
     /**
+     * Gets the list of filter main type capabilities in bit field.
+     *
+     * <p>Each element in the returned array represents the supported filter main types
+     * represented as bitwise OR of the types in {@link FilterConfiguration}.
+     * <p>Whereas getFilterCapabilities() returns the bitwise OR value of all the supported filter
+     * types in the system, this API returns a list of supported filter types in the system with
+     * each entry representing the supported filter types per demux resource.
+     *
+     * @return an array of supported filter main types for the demux resources in the system
+     *         an empty array should be returned for devices with Tuner HAL version 2 and below
+     */
+    @FilterCapabilities
+    @NonNull
+    public int[] getFilterTypeCapabilityList() {
+        return mFilterCapsList;
+    }
+
+    /**
      * Gets link capabilities.
      *
      * <p>The returned array contains the same elements as the number of types in
diff --git a/media/java/android/media/tv/tuner/DemuxInfo.java b/media/java/android/media/tv/tuner/DemuxInfo.java
new file mode 100644
index 0000000..de76165
--- /dev/null
+++ b/media/java/android/media/tv/tuner/DemuxInfo.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2022 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;
+
+import android.annotation.SystemApi;
+import android.media.tv.tuner.DemuxCapabilities.FilterCapabilities;
+
+/**
+ * This class is used to specify information of a demux.
+ *
+ * @hide
+ */
+@SystemApi
+public class DemuxInfo {
+    // Bitwise OR of filter types
+    private int mFilterTypes;
+
+    public DemuxInfo(@FilterCapabilities int filterTypes) {
+        setFilterTypes(filterTypes);
+    }
+
+    /**
+     * Gets the filter types
+     *
+     * @return the filter types
+     */
+    @FilterCapabilities
+    public int getFilterTypes() {
+        return mFilterTypes;
+    }
+
+    /**
+     * Sets the filter types
+     *
+     * @param filterTypes the filter types to set
+     */
+    public void setFilterTypes(@FilterCapabilities int filterTypes) {
+        mFilterTypes = filterTypes;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 7039a3e..27c2a98 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -281,6 +281,7 @@
     private final TunerResourceManager mTunerResourceManager;
     private final int mClientId;
     private static int sTunerVersion = TunerVersionChecker.TUNER_VERSION_UNKNOWN;
+    private DemuxInfo mDesiredDemuxInfo = new DemuxInfo(Filter.TYPE_UNDEFINED);
 
     private Frontend mFrontend;
     private EventHandler mHandler;
@@ -895,12 +896,7 @@
         }
     }
 
-    private void releaseAll() {
-        // release CiCam before frontend because frontend handle is needed to unlink CiCam
-        releaseCiCam();
-
-        releaseFrontend();
-
+    private void closeLnb() {
         mLnbLock.lock();
         try {
             // mLnb will be non-null only for owner tuner
@@ -917,8 +913,23 @@
         } finally {
             mLnbLock.unlock();
         }
+    }
 
+    private void releaseFilters() {
+        synchronized (mFilters) {
+            if (!mFilters.isEmpty()) {
+                for (WeakReference<Filter> weakFilter : mFilters) {
+                    Filter filter = weakFilter.get();
+                    if (filter != null) {
+                        filter.close();
+                    }
+                }
+                mFilters.clear();
+            }
+        }
+    }
 
+    private void releaseDescramblers() {
         synchronized (mDescramblers) {
             if (!mDescramblers.isEmpty()) {
                 for (Map.Entry<Integer, WeakReference<Descrambler>> d : mDescramblers.entrySet()) {
@@ -931,19 +942,9 @@
                 mDescramblers.clear();
             }
         }
+    }
 
-        synchronized (mFilters) {
-            if (!mFilters.isEmpty()) {
-                for (WeakReference<Filter> weakFilter : mFilters) {
-                    Filter filter = weakFilter.get();
-                    if (filter != null) {
-                        filter.close();
-                    }
-                }
-                mFilters.clear();
-            }
-        }
-
+    private void releaseDemux() {
         mDemuxLock.lock();
         try {
             if (mDemuxHandle != null) {
@@ -957,9 +958,17 @@
         } finally {
             mDemuxLock.unlock();
         }
+    }
 
+    private void releaseAll() {
+        // release CiCam before frontend because frontend handle is needed to unlink CiCam
+        releaseCiCam();
+        releaseFrontend();
+        closeLnb();
+        releaseDescramblers();
+        releaseFilters();
+        releaseDemux();
         mTunerResourceManager.unregisterClientProfile(mClientId);
-
     }
 
     /**
@@ -1025,6 +1034,7 @@
     private native DvrPlayback nativeOpenDvrPlayback(long bufferSize);
 
     private native DemuxCapabilities nativeGetDemuxCapabilities();
+    private native DemuxInfo nativeGetDemuxInfo(int demuxHandle);
 
     private native int nativeCloseDemux(int handle);
     private native int nativeCloseFrontend(int handle);
@@ -1865,6 +1875,30 @@
         }
     }
 
+    /**
+     * Gets DemuxInfo of the currently held demux
+     *
+     * @return A {@link DemuxInfo} of currently held demux resource.
+     *         Returns null if no demux resource is held.
+     */
+    @Nullable
+    public DemuxInfo getCurrentDemuxInfo() {
+        mDemuxLock.lock();
+        try {
+            if (mDemuxHandle == null) {
+                return null;
+            }
+            return nativeGetDemuxInfo(mDemuxHandle);
+        } finally {
+            mDemuxLock.unlock();
+        }
+    }
+
+    /** @hide */
+    public DemuxInfo getDesiredDemuxInfo() {
+        return mDesiredDemuxInfo;
+    }
+
     private void onFrontendEvent(int eventType) {
         Log.d(TAG, "Got event from tuning. Event type: " + eventType + " for " + this);
         synchronized (mOnTuneEventLock) {
@@ -2173,6 +2207,11 @@
     /**
      * Opens a filter object based on the given types and buffer size.
      *
+     * <p>For TUNER_VERSION_3_0 and above, configureDemuxInternal() will be called with mainType.
+     * However, unlike when configureDemux() is called directly, the desired filter types will not
+     * be changed when previously set desired filter types are the superset of the newly desired
+     * ones.
+     *
      * @param mainType the main type of the filter.
      * @param subType the subtype of the filter.
      * @param bufferSize the buffer size of the filter to be opened in bytes. The buffer holds the
@@ -2188,6 +2227,15 @@
             @Nullable FilterCallback cb) {
         mDemuxLock.lock();
         try {
+            int tunerMajorVersion = TunerVersionChecker.getMajorVersion(sTunerVersion);
+            if (sTunerVersion >= TunerVersionChecker.TUNER_VERSION_3_0) {
+                DemuxInfo demuxInfo = new DemuxInfo(mainType);
+                int res = configureDemuxInternal(demuxInfo, false /* reduceDesiredFilterTypes */);
+                if (res != RESULT_SUCCESS) {
+                    Log.e(TAG, "openFilter called for unsupported mainType: " + mainType);
+                    return null;
+                }
+            }
             if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
                 return null;
             }
@@ -2470,10 +2518,109 @@
         return filter;
     }
 
+    /**
+     * Configures the desired {@link DemuxInfo}
+     *
+     * <p>The already held demux and filters will be released when desiredDemuxInfo is null or the
+     * desireDemuxInfo.getFilterTypes() is not supported by the already held demux.
+     *
+     * @param desiredDemuxInfo the desired {@link DemuxInfo}, which includes information such as
+     *                         filterTypes ({@link DemuxFilterMainType}).
+     * @return result status of configure demux operation. {@link #RESULT_UNAVAILABLE} is returned
+     *                when a) the desired capabilities are not supported by the system,
+     *                b) this API is called on unsupported version, or
+     *                c) either getDemuxCapabilities or getFilterTypeCapabilityList()
+     *                returns an empty array
+     */
+    @Result
+    public int configureDemux(@Nullable DemuxInfo desiredDemuxInfo) {
+        int tunerMajorVersion = TunerVersionChecker.getMajorVersion(sTunerVersion);
+        if (sTunerVersion < TunerVersionChecker.TUNER_VERSION_3_0) {
+            Log.e(TAG, "configureDemux() is not supported for tuner version:"
+                    + TunerVersionChecker.getMajorVersion(sTunerVersion) + "."
+                    + TunerVersionChecker.getMinorVersion(sTunerVersion) + ".");
+            return RESULT_UNAVAILABLE;
+        }
+
+        synchronized (mDemuxLock) {
+            return configureDemuxInternal(desiredDemuxInfo, true /* reduceDesiredFilterTypes */);
+        }
+    }
+
+    private int configureDemuxInternal(@Nullable DemuxInfo desiredDemuxInfo,
+            boolean reduceDesiredFilterTypes) {
+        // release the currently held demux if the desired demux info is null
+        if (desiredDemuxInfo == null) {
+            if (mDemuxHandle != null) {
+                releaseFilters();
+                releaseDemux();
+            }
+            return RESULT_SUCCESS;
+        }
+
+        int desiredFilterTypes = desiredDemuxInfo.getFilterTypes();
+
+        // just update and return success if the desiredFilterTypes is equal to or a subset of
+        // a previously configured value
+        if ((mDesiredDemuxInfo.getFilterTypes() & desiredFilterTypes)
+                == desiredFilterTypes) {
+            if (reduceDesiredFilterTypes) {
+                mDesiredDemuxInfo.setFilterTypes(desiredFilterTypes);
+            }
+            return RESULT_SUCCESS;
+        }
+
+        // check if the desire capability is supported
+        DemuxCapabilities caps = nativeGetDemuxCapabilities();
+        if (caps == null) {
+            Log.e(TAG, "configureDemuxInternal:failed to get DemuxCapabilities");
+            return RESULT_UNAVAILABLE;
+        }
+
+        int[] filterCapsList = caps.getFilterTypeCapabilityList();
+        if (filterCapsList.length <= 0) {
+            Log.e(TAG, "configureDemuxInternal: getFilterTypeCapabilityList()"
+                    + " returned an empty array");
+            return RESULT_UNAVAILABLE;
+        }
+
+        boolean supported = false;
+        for (int filterCaps : filterCapsList) {
+            if ((desiredFilterTypes & filterCaps) == desiredFilterTypes) {
+                supported = true;
+                break;
+            }
+        }
+        if (!supported) {
+            Log.e(TAG, "configureDemuxInternal: requested caps:" + desiredFilterTypes
+                    + " is not supported by the system");
+            return RESULT_UNAVAILABLE;
+        }
+
+        // close demux if not compatible
+        if (mDemuxHandle != null) {
+            if (desiredFilterTypes != Filter.TYPE_UNDEFINED) {
+                // Release the existing demux only if
+                // the desired caps is not supported
+                DemuxInfo currentDemuxInfo = nativeGetDemuxInfo(mDemuxHandle);
+                if (currentDemuxInfo != null) {
+                    if ((desiredFilterTypes & currentDemuxInfo.getFilterTypes())
+                            != desiredFilterTypes) {
+                        releaseFilters();
+                        releaseDemux();
+                    }
+                }
+            }
+        }
+        mDesiredDemuxInfo.setFilterTypes(desiredFilterTypes);
+        return RESULT_SUCCESS;
+    }
+
     private boolean requestDemux() {
         int[] demuxHandle = new int[1];
         TunerDemuxRequest request = new TunerDemuxRequest();
         request.clientId = mClientId;
+        request.desiredFilterTypes = mDesiredDemuxInfo.getFilterTypes();
         boolean granted = mTunerResourceManager.requestDemux(request, demuxHandle);
         if (granted) {
             mDemuxHandle = demuxHandle[0];
diff --git a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
index 15175a7..d268aeb 100644
--- a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
+++ b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
@@ -289,6 +289,23 @@
     }
 
     /**
+     * Updates the current TRM of the TunerHAL Demux information.
+     *
+     * <p><strong>Note:</strong> This update must happen before the first
+     * {@link #requestDemux(TunerDemuxRequest, int[])} and
+     * {@link #releaseDemux(int, int)} call.
+     *
+     * @param infos an array of the available {@link TunerDemuxInfo} information.
+     */
+    public void setDemuxInfoList(@NonNull TunerDemuxInfo[] infos) {
+        try {
+            mService.setDemuxInfoList(infos);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Updates the TRM of the current CAS information.
      *
      * <p><strong>Note:</strong> This update must happen before the first
diff --git a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
index e0af76d..5399697 100644
--- a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
+++ b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
@@ -20,6 +20,7 @@
 import android.media.tv.tunerresourcemanager.IResourcesReclaimListener;
 import android.media.tv.tunerresourcemanager.ResourceClientProfile;
 import android.media.tv.tunerresourcemanager.TunerCiCamRequest;
+import android.media.tv.tunerresourcemanager.TunerDemuxInfo;
 import android.media.tv.tunerresourcemanager.TunerDemuxRequest;
 import android.media.tv.tunerresourcemanager.TunerDescramblerRequest;
 import android.media.tv.tunerresourcemanager.TunerFrontendInfo;
@@ -130,6 +131,17 @@
     void updateCasInfo(in int casSystemId, in int maxSessionNum);
 
     /*
+     * Updates the available Demux resources information on the current device.
+     *
+     * <p><strong>Note:</strong> This update must happen before the first
+     * {@link #requestDemux(TunerDemux,int[])} and {@link #releaseDemux(int, int)}
+     * call.
+     *
+     * @param infos an array of the available {@link TunerDemux} information.
+     */
+    void setDemuxInfoList(in TunerDemuxInfo[] infos);
+
+    /*
      * Updates the available Lnb resource information on the current device.
      *
      * <p><strong>Note:</strong> This update must happen before the first
diff --git a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/TunerDemuxInfo.aidl b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/TunerDemuxInfo.aidl
new file mode 100644
index 0000000..c14caf5
--- /dev/null
+++ b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/TunerDemuxInfo.aidl
@@ -0,0 +1,35 @@
+/**
+ * Copyright 2022, 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.tunerresourcemanager;
+
+/**
+ * TunerDemuxInfo interface that carries tuner demux information.
+ *
+ * This is used to update the TunerResourceManager demux resources.
+ * @hide
+ */
+parcelable TunerDemuxInfo {
+    /**
+     * Demux handle
+     */
+    int handle;
+
+    /**
+     * Supported filter types (defined in {@link android.media.tv.tuner.filter.Filter})
+     */
+    int filterTypes;
+}
diff --git a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/TunerDemuxRequest.aidl b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/TunerDemuxRequest.aidl
index 457f90c..b24e273 100644
--- a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/TunerDemuxRequest.aidl
+++ b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/TunerDemuxRequest.aidl
@@ -23,4 +23,9 @@
  */
 parcelable TunerDemuxRequest {
     int clientId;
-}
\ No newline at end of file
+
+    /**
+     * Desired filter types (defined in {@link android.media.tv.tuner.filter.Filter})
+     */
+    int desiredFilterTypes;
+}
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index aacea3d..a73725ba 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -47,6 +47,7 @@
 #include <aidl/android/hardware/tv/tuner/DemuxFilterSubType.h>
 #include <aidl/android/hardware/tv/tuner/DemuxFilterTemiEvent.h>
 #include <aidl/android/hardware/tv/tuner/DemuxFilterTsRecordEvent.h>
+#include <aidl/android/hardware/tv/tuner/DemuxInfo.h>
 #include <aidl/android/hardware/tv/tuner/DemuxIpAddress.h>
 #include <aidl/android/hardware/tv/tuner/DemuxIpFilterSettings.h>
 #include <aidl/android/hardware/tv/tuner/DemuxIpFilterType.h>
@@ -192,6 +193,7 @@
 using ::aidl::android::hardware::tv::tuner::DemuxFilterSubType;
 using ::aidl::android::hardware::tv::tuner::DemuxFilterTemiEvent;
 using ::aidl::android::hardware::tv::tuner::DemuxFilterTsRecordEvent;
+using ::aidl::android::hardware::tv::tuner::DemuxInfo;
 using ::aidl::android::hardware::tv::tuner::DemuxIpAddress;
 using ::aidl::android::hardware::tv::tuner::DemuxIpAddressIpAddress;
 using ::aidl::android::hardware::tv::tuner::DemuxIpFilterSettings;
@@ -2083,7 +2085,7 @@
 
     JNIEnv *env = AndroidRuntime::getJNIEnv();
     jclass clazz = env->FindClass("android/media/tv/tuner/DemuxCapabilities");
-    jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(IIIIIIIIIJI[IZ)V");
+    jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(IIIIIIIIIJI[I[IZ)V");
 
     jint numDemux = caps->numDemux;
     jint numRecord = caps->numRecord;
@@ -2095,16 +2097,49 @@
     jint numPesFilter = caps->numPesFilter;
     jint numPcrFilter = caps->numPcrFilter;
     jlong numBytesInSectionFilter = caps->numBytesInSectionFilter;
-    jint filterCaps = caps->filterCaps;
     jboolean bTimeFilter = caps->bTimeFilter;
 
+    jint filterCaps = caps->filterCaps;
+    jintArray filterCapsList = nullptr;
+    vector<DemuxInfo> demuxInfoList;
+    sTunerClient->getDemuxInfoList(&demuxInfoList);
+    if (demuxInfoList.size() > 0) {
+        vector<int32_t> demuxFilterTypesList;
+        for (int i = 0; i < demuxInfoList.size(); i++) {
+            demuxFilterTypesList.push_back(demuxInfoList[i].filterTypes);
+        }
+        filterCapsList = env->NewIntArray(demuxFilterTypesList.size());
+        env->SetIntArrayRegion(filterCapsList, 0, demuxFilterTypesList.size(),
+                               reinterpret_cast<jint *>(&demuxFilterTypesList[0]));
+    } else {
+        filterCapsList = env->NewIntArray(0);
+    }
     jintArray linkCaps = env->NewIntArray(caps->linkCaps.size());
     env->SetIntArrayRegion(linkCaps, 0, caps->linkCaps.size(),
                            reinterpret_cast<jint *>(&caps->linkCaps[0]));
 
     return env->NewObject(clazz, capsInit, numDemux, numRecord, numPlayback, numTsFilter,
             numSectionFilter, numAudioFilter, numVideoFilter, numPesFilter, numPcrFilter,
-            numBytesInSectionFilter, filterCaps, linkCaps, bTimeFilter);
+            numBytesInSectionFilter, filterCaps, filterCapsList, linkCaps, bTimeFilter);
+}
+
+jobject JTuner::getDemuxInfo(int handle) {
+    if (sTunerClient == nullptr) {
+        ALOGE("tuner is not initialized");
+        return nullptr;
+    }
+    shared_ptr<DemuxInfo> demuxInfo = sTunerClient->getDemuxInfo(handle);
+    if (demuxInfo == nullptr) {
+        return nullptr;
+    }
+
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    jclass clazz = env->FindClass("android/media/tv/tuner/DemuxInfo");
+    jmethodID infoInit = env->GetMethodID(clazz, "<init>", "(I)V");
+
+    jint filterTypes = demuxInfo->filterTypes;
+
+    return env->NewObject(clazz, infoInit, filterTypes);
 }
 
 jobject JTuner::getFrontendStatus(jintArray types) {
@@ -4487,6 +4522,11 @@
     return tuner->getDemuxCaps();
 }
 
+static jobject android_media_tv_Tuner_get_demux_info(JNIEnv* env, jobject thiz, jint handle) {
+    sp<JTuner> tuner = getTuner(env, thiz);
+    return tuner->getDemuxInfo(handle);
+}
+
 static jint android_media_tv_Tuner_open_demux(JNIEnv* env, jobject thiz, jint handle) {
     sp<JTuner> tuner = getTuner(env, thiz);
     return (jint)tuner->openDemux(handle);
@@ -4878,6 +4918,8 @@
             (void *)android_media_tv_Tuner_open_dvr_playback },
     { "nativeGetDemuxCapabilities", "()Landroid/media/tv/tuner/DemuxCapabilities;",
             (void *)android_media_tv_Tuner_get_demux_caps },
+    { "nativeGetDemuxInfo", "(I)Landroid/media/tv/tuner/DemuxInfo;",
+            (void *)android_media_tv_Tuner_get_demux_info },
     { "nativeOpenDemuxByhandle", "(I)I", (void *)android_media_tv_Tuner_open_demux },
     { "nativeClose", "()I", (void *)android_media_tv_Tuner_close_tuner },
     { "nativeCloseFrontend", "(I)I", (void *)android_media_tv_Tuner_close_frontend },
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index 2b69e89..4069aaf 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -198,6 +198,7 @@
     jobject openDescrambler();
     jobject openDvr(DvrType type, jlong bufferSize);
     jobject getDemuxCaps();
+    jobject getDemuxInfo(int handle);
     jobject getFrontendStatus(jintArray types);
     Result openDemux(int handle);
     jint close();
diff --git a/media/jni/tuner/TunerClient.cpp b/media/jni/tuner/TunerClient.cpp
index ab28fb4..ea623d9 100644
--- a/media/jni/tuner/TunerClient.cpp
+++ b/media/jni/tuner/TunerClient.cpp
@@ -22,7 +22,6 @@
 
 #include "TunerClient.h"
 
-using ::aidl::android::hardware::tv::tuner::FrontendStatusType;
 using ::aidl::android::hardware::tv::tuner::FrontendType;
 
 namespace android {
@@ -108,6 +107,27 @@
     return nullptr;
 }
 
+shared_ptr<DemuxInfo> TunerClient::getDemuxInfo(int32_t demuxHandle) {
+    if (mTunerService != nullptr) {
+        DemuxInfo aidlDemuxInfo;
+        Status s = mTunerService->getDemuxInfo(demuxHandle, &aidlDemuxInfo);
+        if (!s.isOk()) {
+            return nullptr;
+        }
+        return make_shared<DemuxInfo>(aidlDemuxInfo);
+    }
+    return nullptr;
+}
+
+void TunerClient::getDemuxInfoList(vector<DemuxInfo>* demuxInfoList) {
+    if (mTunerService != nullptr) {
+        Status s = mTunerService->getDemuxInfoList(demuxInfoList);
+        if (!s.isOk()) {
+            demuxInfoList->clear();
+        }
+    }
+}
+
 shared_ptr<DemuxCapabilities> TunerClient::getDemuxCaps() {
     if (mTunerService != nullptr) {
         DemuxCapabilities aidlCaps;
diff --git a/media/jni/tuner/TunerClient.h b/media/jni/tuner/TunerClient.h
index 3f8b21c..6ab120b 100644
--- a/media/jni/tuner/TunerClient.h
+++ b/media/jni/tuner/TunerClient.h
@@ -31,6 +31,7 @@
 using Status = ::ndk::ScopedAStatus;
 
 using ::aidl::android::hardware::tv::tuner::DemuxCapabilities;
+using ::aidl::android::hardware::tv::tuner::DemuxInfo;
 using ::aidl::android::hardware::tv::tuner::FrontendInfo;
 using ::aidl::android::hardware::tv::tuner::FrontendType;
 using ::aidl::android::hardware::tv::tuner::Result;
@@ -83,6 +84,21 @@
     sp<DemuxClient> openDemux(int32_t demuxHandle);
 
     /**
+     * Retrieve the DemuxInfo of a specific demux
+     *
+     * @param demuxHandle the handle of the demux to query demux info for
+     * @return the demux info
+     */
+    shared_ptr<DemuxInfo> getDemuxInfo(int32_t demuxHandle);
+
+    /**
+     * Retrieve a list of demux info
+     *
+     * @return a list of DemuxInfo
+     */
+    void getDemuxInfoList(vector<DemuxInfo>* demuxInfoList);
+
+    /**
      * Retrieve the Demux capabilities.
      *
      * @return the demux’s capabilities.
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
index fb9a4d4..301e612 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
@@ -79,6 +79,8 @@
      */
     private Set<Integer> mShareFeClientIds = new HashSet<>();
 
+    private Set<Integer> mUsingDemuxHandles = new HashSet<>();
+
     /**
      * List of the Lnb handles that are used by the current client.
      */
@@ -233,6 +235,31 @@
     }
 
     /**
+     * Set when the client starts to use a Demux.
+     *
+     * @param demuxHandle the demux being used.
+     */
+    public void useDemux(int demuxHandle) {
+        mUsingDemuxHandles.add(demuxHandle);
+    }
+
+    /**
+     * Get the set of demux handles in use.
+     */
+    public Set<Integer> getInUseDemuxHandles() {
+        return mUsingDemuxHandles;
+    }
+
+    /**
+     * Called when the client released a Demux.
+     *
+     * @param demuxHandle the demux handl being released.
+     */
+    public void releaseDemux(int demuxHandle) {
+        mUsingDemuxHandles.remove(demuxHandle);
+    }
+
+    /**
      * Set when the client starts to use an Lnb.
      *
      * @param lnbHandle being used.
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/DemuxResource.java b/services/core/java/com/android/server/tv/tunerresourcemanager/DemuxResource.java
new file mode 100644
index 0000000..df73565
--- /dev/null
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/DemuxResource.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2022 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 com.android.server.tv.tunerresourcemanager;
+
+/**
+ * A demux resource object used by the Tuner Resource Manager to record the tuner Demux
+ * information.
+ *
+ * @hide
+ */
+public final class DemuxResource extends TunerResourceBasic {
+
+    private final int mFilterTypes;
+
+    private DemuxResource(Builder builder) {
+        super(builder);
+        this.mFilterTypes = builder.mFilterTypes;
+    }
+
+    public int getFilterTypes() {
+        return mFilterTypes;
+    }
+
+    @Override
+    public String toString() {
+        return "DemuxResource[handle=" + this.mHandle + ", filterTypes="
+                + this.mFilterTypes + ", isInUse=" + this.mIsInUse
+                + ", ownerClientId=" + this.mOwnerClientId + "]";
+    }
+
+    /**
+     * Returns true if the desired {@link DemuxFilterMainTypes}  is supported.
+     */
+    public boolean hasSufficientCaps(int desiredCaps) {
+        return desiredCaps == (desiredCaps & mFilterTypes);
+    }
+
+    /**
+     * Returns the number of supported {@link DemuxFilterMainTypes}.
+     */
+    public int getNumOfCaps() {
+        int mask = 1;
+        int numOfCaps = 0;
+        for (int i = 0; i < Integer.SIZE; i++) {
+            if ((mFilterTypes & mask) == mask) {
+                numOfCaps = numOfCaps + 1;
+            }
+            mask = mask << 1;
+        }
+        return numOfCaps;
+    }
+
+    /**
+     * Builder class for {@link DemuxResource}.
+     */
+    public static class Builder extends TunerResourceBasic.Builder {
+        private int mFilterTypes;
+
+        Builder(int handle) {
+            super(handle);
+        }
+
+        /**
+         * Builder for {@link DemuxResource}.
+         *
+         * @param filterTypes the supported {@link DemuxFilterMainTypes}
+         */
+        public Builder filterTypes(int filterTypes) {
+            this.mFilterTypes = filterTypes;
+            return this;
+        }
+
+        /**
+         * Build a {@link DemuxResource}.
+         *
+         * @return {@link DemuxResource}.
+         */
+        @Override
+        public DemuxResource build() {
+            DemuxResource demux = new DemuxResource(this);
+            return demux;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index ad1ff72..ed91775 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -22,6 +22,7 @@
 import android.app.ActivityManager.RunningAppProcessInfo;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.hardware.tv.tuner.DemuxFilterMainType;
 import android.media.IResourceManagerService;
 import android.media.tv.TvInputManager;
 import android.media.tv.tunerresourcemanager.CasSessionRequest;
@@ -29,6 +30,7 @@
 import android.media.tv.tunerresourcemanager.ITunerResourceManager;
 import android.media.tv.tunerresourcemanager.ResourceClientProfile;
 import android.media.tv.tunerresourcemanager.TunerCiCamRequest;
+import android.media.tv.tunerresourcemanager.TunerDemuxInfo;
 import android.media.tv.tunerresourcemanager.TunerDemuxRequest;
 import android.media.tv.tunerresourcemanager.TunerDescramblerRequest;
 import android.media.tv.tunerresourcemanager.TunerFrontendInfo;
@@ -96,6 +98,8 @@
     private SparseIntArray mFrontendUsedNumsBackup = new SparseIntArray();
     private SparseIntArray mFrontendExistingNumsBackup = new SparseIntArray();
 
+    // Map of the current available demux resources
+    private Map<Integer, DemuxResource> mDemuxResources = new HashMap<>();
     // Map of the current available lnb resources
     private Map<Integer, LnbResource> mLnbResources = new HashMap<>();
     // Map of the current available Cas resources
@@ -249,6 +253,17 @@
         }
 
         @Override
+        public void setDemuxInfoList(@NonNull TunerDemuxInfo[] infos) throws RemoteException {
+            enforceTrmAccessPermission("setDemuxInfoList");
+            if (infos == null) {
+                throw new RemoteException("TunerDemuxInfo can't be null");
+            }
+            synchronized (mLock) {
+                setDemuxInfoListInternal(infos);
+            }
+        }
+
+        @Override
         public void updateCasInfo(int casSystemId, int maxSessionNum) {
             enforceTrmAccessPermission("updateCasInfo");
             synchronized (mLock) {
@@ -294,8 +309,8 @@
 
         @Override
         public boolean setMaxNumberOfFrontends(int frontendType, int maxUsableNum) {
-            enforceTunerAccessPermission("requestFrontend");
-            enforceTrmAccessPermission("requestFrontend");
+            enforceTunerAccessPermission("setMaxNumberOfFrontends");
+            enforceTrmAccessPermission("setMaxNumberOfFrontends");
             if (maxUsableNum < 0) {
                 Slog.w(TAG, "setMaxNumberOfFrontends failed with maxUsableNum:" + maxUsableNum
                         + " frontendType:" + frontendType);
@@ -308,8 +323,8 @@
 
         @Override
         public int getMaxNumberOfFrontends(int frontendType) {
-            enforceTunerAccessPermission("requestFrontend");
-            enforceTrmAccessPermission("requestFrontend");
+            enforceTunerAccessPermission("getMaxNumberOfFrontends");
+            enforceTrmAccessPermission("getMaxNumberOfFrontends");
             synchronized (mLock) {
                 return getMaxNumberOfFrontendsInternal(frontendType);
             }
@@ -466,11 +481,33 @@
         }
 
         @Override
-        public void releaseDemux(int demuxHandle, int clientId) {
+        public void releaseDemux(int demuxHandle, int clientId) throws RemoteException {
             enforceTunerAccessPermission("releaseDemux");
             enforceTrmAccessPermission("releaseDemux");
             if (DEBUG) {
-                Slog.d(TAG, "releaseDemux(demuxHandle=" + demuxHandle + ")");
+                Slog.e(TAG, "releaseDemux(demuxHandle=" + demuxHandle + ")");
+            }
+
+            synchronized (mLock) {
+                // For Tuner 2.0 and below or any HW constraint devices that are unable to support
+                // ITuner.openDemuxById(), demux resources are not really managed under TRM and
+                // mDemuxResources.size() will be zero
+                if (mDemuxResources.size() == 0) {
+                    return;
+                }
+
+                if (!checkClientExists(clientId)) {
+                    throw new RemoteException("Release demux for unregistered client:" + clientId);
+                }
+                DemuxResource demux = getDemuxResource(demuxHandle);
+                if (demux == null) {
+                    throw new RemoteException("Releasing demux does not exist.");
+                }
+                if (demux.getOwnerClientId() != clientId) {
+                    throw new RemoteException("Client is not the current owner "
+                            + "of the releasing demux.");
+                }
+                releaseDemuxInternal(demux);
             }
         }
 
@@ -629,6 +666,7 @@
                 dumpSIA(mFrontendExistingNumsBackup, "FrontendExistingNumsBackup:", ", ", pw);
                 dumpSIA(mFrontendUsedNumsBackup, "FrontendUsedNumsBackup:", ", ", pw);
                 dumpSIA(mFrontendMaxUsableNumsBackup, "FrontendUsedNumsBackup:", ", ", pw);
+                dumpMap(mDemuxResources, "DemuxResource:", "\n", pw);
                 dumpMap(mLnbResources, "LnbResource:", "\n", pw);
                 dumpMap(mCasResources, "CasResource:", "\n", pw);
                 dumpMap(mCiCamResources, "CiCamResource:", "\n", pw);
@@ -859,6 +897,41 @@
     }
 
     @VisibleForTesting
+    protected void setDemuxInfoListInternal(TunerDemuxInfo[] infos) {
+        if (DEBUG) {
+            Slog.d(TAG, "updateDemuxInfo:");
+            for (int i = 0; i < infos.length; i++) {
+                Slog.d(TAG, infos[i].toString());
+            }
+        }
+
+        // A set to record the demuxes pending on updating. Ids will be removed
+        // from this set once its updating finished. Any demux left in this set when all
+        // the updates are done will be removed from mDemuxResources.
+        Set<Integer> updatingDemuxHandles = new HashSet<>(getDemuxResources().keySet());
+
+        // Update demuxResources map and other mappings accordingly
+        for (int i = 0; i < infos.length; i++) {
+            if (getDemuxResource(infos[i].handle) != null) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Demux handle=" + infos[i].handle + "exists.");
+                }
+                updatingDemuxHandles.remove(infos[i].handle);
+            } else {
+                // Add a new demux resource
+                DemuxResource newDemux = new DemuxResource.Builder(infos[i].handle)
+                                                 .filterTypes(infos[i].filterTypes)
+                                                 .build();
+                addDemuxResource(newDemux);
+            }
+        }
+
+        for (int removingHandle : updatingDemuxHandles) {
+            // update the exclusive group id member list
+            removeDemuxResource(removingHandle);
+        }
+    }
+    @VisibleForTesting
     protected void setLnbInfoListInternal(int[] lnbHandles) {
         if (DEBUG) {
             for (int i = 0; i < lnbHandles.length; i++) {
@@ -1292,6 +1365,14 @@
     }
 
     @VisibleForTesting
+    protected void releaseDemuxInternal(DemuxResource demux) {
+        if (DEBUG) {
+            Slog.d(TAG, "releaseDemux(DemuxHandle=" + demux.getHandle() + ")");
+        }
+        updateDemuxClientMappingOnRelease(demux);
+    }
+
+    @VisibleForTesting
     protected void releaseLnbInternal(LnbResource lnb) {
         if (DEBUG) {
             Slog.d(TAG, "releaseLnb(lnbHandle=" + lnb.getHandle() + ")");
@@ -1320,9 +1401,91 @@
         if (DEBUG) {
             Slog.d(TAG, "requestDemux(request=" + request + ")");
         }
-        // There are enough Demux resources, so we don't manage Demux in R.
-        demuxHandle[0] = generateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, 0);
-        return true;
+
+        // For Tuner 2.0 and below or any HW constraint devices that are unable to support
+        // ITuner.openDemuxById(), demux resources are not really managed under TRM and
+        // mDemuxResources.size() will be zero
+        if (mDemuxResources.size() == 0) {
+            // There are enough Demux resources, so we don't manage Demux in R.
+            demuxHandle[0] =
+                    generateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, 0);
+            return true;
+        }
+
+        demuxHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
+        ClientProfile requestClient = getClientProfile(request.clientId);
+
+        if (requestClient == null) {
+            return false;
+        }
+
+        clientPriorityUpdateOnRequest(requestClient);
+        int grantingDemuxHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
+        int inUseLowestPriorityDrHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
+        // Priority max value is 1000
+        int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
+        // If the desired demux id was specified, we only need to check the demux.
+        boolean hasDesiredDemuxCap = request.desiredFilterTypes
+                != DemuxFilterMainType.UNDEFINED;
+        int smallestNumOfSupportedCaps = Integer.SIZE + 1;
+        for (DemuxResource dr : getDemuxResources().values()) {
+            if (!hasDesiredDemuxCap || dr.hasSufficientCaps(request.desiredFilterTypes)) {
+                if (!dr.isInUse()) {
+                    int numOfSupportedCaps = dr.getNumOfCaps();
+
+                    // look for the demux with minimum caps supporting the desired caps
+                    if (smallestNumOfSupportedCaps > numOfSupportedCaps) {
+                        smallestNumOfSupportedCaps = numOfSupportedCaps;
+                        grantingDemuxHandle = dr.getHandle();
+                    }
+                } else if (grantingDemuxHandle == TunerResourceManager.INVALID_RESOURCE_HANDLE) {
+                    // Record the demux id with the lowest client priority among all the
+                    // in use demuxes when no availabledemux has been found.
+                    int priority = updateAndGetOwnerClientPriority(dr.getOwnerClientId());
+                    if (currentLowestPriority >= priority) {
+                        int numOfSupportedCaps = dr.getNumOfCaps();
+                        boolean shouldUpdate = false;
+                        // update lowest priority
+                        if (currentLowestPriority > priority) {
+                            currentLowestPriority = priority;
+                            shouldUpdate = true;
+                        }
+                        // update smallest caps
+                        if (smallestNumOfSupportedCaps > numOfSupportedCaps) {
+                            smallestNumOfSupportedCaps = numOfSupportedCaps;
+                            shouldUpdate = true;
+                        }
+                        if (shouldUpdate) {
+                            inUseLowestPriorityDrHandle = dr.getHandle();
+                        }
+                    }
+                }
+            }
+        }
+
+        // Grant demux when there is unused resource.
+        if (grantingDemuxHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE) {
+            demuxHandle[0] = grantingDemuxHandle;
+            updateDemuxClientMappingOnNewGrant(grantingDemuxHandle, request.clientId);
+            return true;
+        }
+
+        // When all the resources are occupied, grant the lowest priority resource if the
+        // request client has higher priority.
+        if (inUseLowestPriorityDrHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE
+                && (requestClient.getPriority() > currentLowestPriority)) {
+            if (!reclaimResource(
+                    getDemuxResource(inUseLowestPriorityDrHandle).getOwnerClientId(),
+                    TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
+                return false;
+            }
+            demuxHandle[0] = inUseLowestPriorityDrHandle;
+            updateDemuxClientMappingOnNewGrant(
+                    inUseLowestPriorityDrHandle, request.clientId);
+            return true;
+        }
+
+        return false;
     }
 
     @VisibleForTesting
@@ -1673,6 +1836,21 @@
         ownerProfile.setPrimaryFrontend(grantingHandle);
     }
 
+    private void updateDemuxClientMappingOnNewGrant(int grantingHandle, int ownerClientId) {
+        DemuxResource grantingDemux = getDemuxResource(grantingHandle);
+        if (grantingDemux != null) {
+            ClientProfile ownerProfile = getClientProfile(ownerClientId);
+            grantingDemux.setOwner(ownerClientId);
+            ownerProfile.useDemux(grantingHandle);
+        }
+    }
+
+    private void updateDemuxClientMappingOnRelease(@NonNull DemuxResource releasingDemux) {
+        ClientProfile ownerProfile = getClientProfile(releasingDemux.getOwnerClientId());
+        releasingDemux.removeOwner();
+        ownerProfile.releaseDemux(releasingDemux.getHandle());
+    }
+
     private void updateLnbClientMappingOnNewGrant(int grantingHandle, int ownerClientId) {
         LnbResource grantingLnb = getLnbResource(grantingHandle);
         ClientProfile ownerProfile = getClientProfile(ownerClientId);
@@ -1764,6 +1942,17 @@
         return mFrontendResources;
     }
 
+    @VisibleForTesting
+    @Nullable
+    protected DemuxResource getDemuxResource(int demuxHandle) {
+        return mDemuxResources.get(demuxHandle);
+    }
+
+    @VisibleForTesting
+    protected Map<Integer, DemuxResource> getDemuxResources() {
+        return mDemuxResources;
+    }
+
     private boolean setMaxNumberOfFrontendsInternal(int frontendType, int maxUsableNum) {
         int usedNum = mFrontendUsedNums.get(frontendType, INVALID_FE_COUNT);
         if (usedNum == INVALID_FE_COUNT || usedNum <= maxUsableNum) {
@@ -1887,6 +2076,10 @@
 
     }
 
+    private void addDemuxResource(DemuxResource newDemux) {
+        mDemuxResources.put(newDemux.getHandle(), newDemux);
+    }
+
     private void removeFrontendResource(int removingHandle) {
         FrontendResource fe = getFrontendResource(removingHandle);
         if (fe == null) {
@@ -1907,6 +2100,17 @@
         mFrontendResources.remove(removingHandle);
     }
 
+    private void removeDemuxResource(int removingHandle) {
+        DemuxResource demux = getDemuxResource(removingHandle);
+        if (demux == null) {
+            return;
+        }
+        if (demux.isInUse()) {
+            releaseDemuxInternal(demux);
+        }
+        mDemuxResources.remove(removingHandle);
+    }
+
     @VisibleForTesting
     @Nullable
     protected LnbResource getLnbResource(int lnbHandle) {
@@ -2062,6 +2266,10 @@
         if (profile.getInUseCiCamId() != ClientProfile.INVALID_RESOURCE_ID) {
             getCiCamResource(profile.getInUseCiCamId()).removeOwner(profile.getId());
         }
+        // Clear Demux
+        for (Integer demuxHandle : profile.getInUseDemuxHandles()) {
+            getDemuxResource(demuxHandle).removeOwner();
+        }
         // Clear Frontend
         clearFrontendAndClientMapping(profile);
         profile.reclaimAllResources();
diff --git a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
index b8cb149..963b27e 100644
--- a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
@@ -25,11 +25,13 @@
 import android.media.tv.ITvInputManager;
 import android.media.tv.TvInputManager;
 import android.media.tv.TvInputService;
+import android.media.tv.tuner.filter.Filter;
 import android.media.tv.tuner.frontend.FrontendSettings;
 import android.media.tv.tunerresourcemanager.CasSessionRequest;
 import android.media.tv.tunerresourcemanager.IResourcesReclaimListener;
 import android.media.tv.tunerresourcemanager.ResourceClientProfile;
 import android.media.tv.tunerresourcemanager.TunerCiCamRequest;
+import android.media.tv.tunerresourcemanager.TunerDemuxInfo;
 import android.media.tv.tunerresourcemanager.TunerDemuxRequest;
 import android.media.tv.tunerresourcemanager.TunerDescramblerRequest;
 import android.media.tv.tunerresourcemanager.TunerFrontendInfo;
@@ -806,20 +808,137 @@
     @Test
     public void requestDemuxTest() {
         // Register client
-        ResourceClientProfile profile = resourceClientProfile("0" /*sessionId*/,
+        ResourceClientProfile profile0 = resourceClientProfile("0" /*sessionId*/,
                 TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
-        int[] clientId = new int[1];
+        ResourceClientProfile profile1 = resourceClientProfile("1" /*sessionId*/,
+                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
+        int[] clientId0 = new int[1];
         mTunerResourceManagerService.registerClientProfileInternal(
-                profile, null /*listener*/, clientId);
-        assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+                profile0, null /*listener*/, clientId0);
+        assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
 
-        int[] demuxHandle = new int[1];
-        TunerDemuxRequest request = new TunerDemuxRequest();
-        request.clientId = clientId[0];
-        assertThat(mTunerResourceManagerService.requestDemuxInternal(request, demuxHandle))
+        TunerDemuxInfo[] infos = new TunerDemuxInfo[3];
+        infos[0] = tunerDemuxInfo(0 /* handle */, Filter.TYPE_TS | Filter.TYPE_IP);
+        infos[1] = tunerDemuxInfo(1 /* handle */, Filter.TYPE_TLV);
+        infos[2] = tunerDemuxInfo(2 /* handle */, Filter.TYPE_TS);
+        mTunerResourceManagerService.setDemuxInfoListInternal(infos);
+
+        int[] demuxHandle0 = new int[1];
+        // first with undefined type (should be the first one with least # of caps)
+        TunerDemuxRequest request = tunerDemuxRequest(clientId0[0], Filter.TYPE_UNDEFINED);
+        assertThat(mTunerResourceManagerService.requestDemuxInternal(request, demuxHandle0))
                 .isTrue();
-        assertThat(mTunerResourceManagerService.getResourceIdFromHandle(demuxHandle[0]))
+        assertThat(demuxHandle0[0]).isEqualTo(1);
+        DemuxResource dr = mTunerResourceManagerService.getDemuxResource(demuxHandle0[0]);
+        mTunerResourceManagerService.releaseDemuxInternal(dr);
+
+        // now with non-supported type (ALP)
+        request.desiredFilterTypes = Filter.TYPE_ALP;
+        demuxHandle0[0] = -1;
+        assertThat(mTunerResourceManagerService.requestDemuxInternal(request, demuxHandle0))
+                .isFalse();
+        assertThat(demuxHandle0[0]).isEqualTo(-1);
+
+        // now with TS (should be the one with least # of caps that supports TS)
+        request.desiredFilterTypes = Filter.TYPE_TS;
+        assertThat(mTunerResourceManagerService.requestDemuxInternal(request, demuxHandle0))
+                .isTrue();
+        assertThat(demuxHandle0[0]).isEqualTo(2);
+
+        // request for another TS
+        int[] clientId1 = new int[1];
+        mTunerResourceManagerService.registerClientProfileInternal(
+                profile1, null /*listener*/, clientId1);
+        assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+        int[] demuxHandle1 = new int[1];
+        TunerDemuxRequest request1 = tunerDemuxRequest(clientId1[0], Filter.TYPE_TS);
+        assertThat(mTunerResourceManagerService.requestDemuxInternal(request1, demuxHandle1))
+                .isTrue();
+        assertThat(demuxHandle1[0]).isEqualTo(0);
+        assertThat(mTunerResourceManagerService.getResourceIdFromHandle(demuxHandle1[0]))
                 .isEqualTo(0);
+
+        // release demuxes
+        dr = mTunerResourceManagerService.getDemuxResource(demuxHandle0[0]);
+        mTunerResourceManagerService.releaseDemuxInternal(dr);
+        dr = mTunerResourceManagerService.getDemuxResource(demuxHandle1[0]);
+        mTunerResourceManagerService.releaseDemuxInternal(dr);
+    }
+
+    @Test
+    public void requestDemuxTest_ResourceReclaim() {
+        // Register clients
+        ResourceClientProfile profile0 = resourceClientProfile("0" /*sessionId*/,
+                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
+        ResourceClientProfile profile1 = resourceClientProfile("1" /*sessionId*/,
+                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_SCAN);
+        ResourceClientProfile profile2 = resourceClientProfile("2" /*sessionId*/,
+                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_SCAN);
+        int[] clientId0 = new int[1];
+        int[] clientId1 = new int[1];
+        int[] clientId2 = new int[1];
+        TestResourcesReclaimListener listener0 = new TestResourcesReclaimListener();
+        TestResourcesReclaimListener listener1 = new TestResourcesReclaimListener();
+        TestResourcesReclaimListener listener2 = new TestResourcesReclaimListener();
+
+        mTunerResourceManagerService.registerClientProfileInternal(
+                profile0, listener0, clientId0);
+        assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+        mTunerResourceManagerService.registerClientProfileInternal(
+                profile1, listener1, clientId1);
+        assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+        mTunerResourceManagerService.registerClientProfileInternal(
+                profile2, listener2, clientId1);
+        assertThat(clientId2[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+
+        // Init demux resources.
+        TunerDemuxInfo[] infos = new TunerDemuxInfo[2];
+        infos[0] = tunerDemuxInfo(0 /*handle*/, Filter.TYPE_TS | Filter.TYPE_IP);
+        infos[1] = tunerDemuxInfo(1 /*handle*/, Filter.TYPE_TS);
+        mTunerResourceManagerService.setDemuxInfoListInternal(infos);
+
+        // let clientId0(prio:100) request for IP - should succeed
+        TunerDemuxRequest request0 = tunerDemuxRequest(clientId0[0], Filter.TYPE_IP);
+        int[] demuxHandle0 = new int[1];
+        assertThat(mTunerResourceManagerService
+                .requestDemuxInternal(request0, demuxHandle0)).isTrue();
+        assertThat(demuxHandle0[0]).isEqualTo(0);
+
+        // let clientId1(prio:50) request for IP - should fail
+        TunerDemuxRequest request1 = tunerDemuxRequest(clientId1[0], Filter.TYPE_IP);
+        int[] demuxHandle1 = new int[1];
+        demuxHandle1[0] = -1;
+        assertThat(mTunerResourceManagerService
+                .requestDemuxInternal(request1, demuxHandle1)).isFalse();
+        assertThat(listener0.isReclaimed()).isFalse();
+        assertThat(demuxHandle1[0]).isEqualTo(-1);
+
+        // let clientId1(prio:50) request for TS - should succeed
+        request1.desiredFilterTypes = Filter.TYPE_TS;
+        assertThat(mTunerResourceManagerService
+                .requestDemuxInternal(request1, demuxHandle1)).isTrue();
+        assertThat(demuxHandle1[0]).isEqualTo(1);
+        assertThat(listener0.isReclaimed()).isFalse();
+
+        // now release demux for the clientId0 (higher priority) and request demux
+        DemuxResource dr = mTunerResourceManagerService.getDemuxResource(demuxHandle0[0]);
+        mTunerResourceManagerService.releaseDemuxInternal(dr);
+
+        // let clientId2(prio:50) request for TS - should succeed
+        TunerDemuxRequest request2 = tunerDemuxRequest(clientId2[0], Filter.TYPE_TS);
+        int[] demuxHandle2 = new int[1];
+        assertThat(mTunerResourceManagerService
+                .requestDemuxInternal(request2, demuxHandle2)).isTrue();
+        assertThat(demuxHandle2[0]).isEqualTo(0);
+        assertThat(listener1.isReclaimed()).isFalse();
+
+        // let clientId0(prio:100) request for TS - should reclaim from clientId2
+        // , who has the smaller caps
+        request0.desiredFilterTypes = Filter.TYPE_TS;
+        assertThat(mTunerResourceManagerService
+                .requestDemuxInternal(request0, demuxHandle0)).isTrue();
+        assertThat(listener1.isReclaimed()).isFalse();
+        assertThat(listener2.isReclaimed()).isTrue();
     }
 
     @Test
@@ -1188,4 +1307,18 @@
         request.ciCamId = ciCamId;
         return request;
     }
+
+    private TunerDemuxInfo tunerDemuxInfo(int handle, int supportedFilterTypes) {
+        TunerDemuxInfo info = new TunerDemuxInfo();
+        info.handle = handle;
+        info.filterTypes = supportedFilterTypes;
+        return info;
+    }
+
+    private TunerDemuxRequest tunerDemuxRequest(int clientId, int desiredFilterTypes) {
+        TunerDemuxRequest request = new TunerDemuxRequest();
+        request.clientId = clientId;
+        request.desiredFilterTypes = desiredFilterTypes;
+        return request;
+    }
 }