From 17c4a944eb598f84bee4327f26565dffc514f123 Mon Sep 17 00:00:00 2001 From: Kensuke Miyagi Date: Thu, 2 Dec 2021 10:32:06 -0800 Subject: [PATCH] Fix race condition betweeen Tuner APIs and with onReclaimResources - Added acquire/releaseLock() in ITunerResourceManager.aidl for Tuner API implementations to utilize. - Added more lock around resource related operations that have race condition with releaseAll() in Tuner.java - This fix addresses race condition when Tuner object dies as well. - Added these calls into Tuner API implementations Also, suppress FLAG_ONEWAY warning for onReclaimResources as it is only called against OEM/SoC packages that are part of image that goes throug Google certification. Bug: 206725267 Bug: 206726459 Bug: 206723996 Test: TunerTest#testResourceReclaimedDifferentProcess() and testResourceReclaimedDifferentThread() Change-Id: Ic9aee1e15f53b2d4984c3b4214a392c2c4d5731e --- media/java/android/media/tv/tuner/Tuner.java | 680 ++++++++++++------ .../TunerResourceManager.java | 42 ++ .../ITunerResourceManager.aidl | 30 + .../TunerResourceManagerService.java | 225 +++++- 4 files changed, 763 insertions(+), 214 deletions(-) diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index 73a821e09f20c..94de7fa17ae7c 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -74,6 +74,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.Executor; +import java.util.concurrent.locks.ReentrantLock; /** * This class is used to interact with hardware tuners devices. @@ -248,6 +249,7 @@ public class Tuner implements AutoCloseable { private static final int FILTER_CLEANUP_THRESHOLD = 256; + /** @hide */ @IntDef(prefix = "DVR_TYPE_", value = {DVR_TYPE_RECORD, DVR_TYPE_PLAYBACK}) @Retention(RetentionPolicy.SOURCE) @@ -304,6 +306,11 @@ public class Tuner implements AutoCloseable { private final Object mOnTuneEventLock = new Object(); private final Object mScanCallbackLock = new Object(); private final Object mOnResourceLostListenerLock = new Object(); + private final ReentrantLock mFrontendLock = new ReentrantLock(); + private final ReentrantLock mLnbLock = new ReentrantLock(); + private final ReentrantLock mFrontendCiCamLock = new ReentrantLock(); + private final ReentrantLock mDemuxLock = new ReentrantLock(); + private int mRequestedCiCamId; private Integer mDemuxHandle; private Integer mFrontendCiCamHandle; @@ -391,7 +398,12 @@ public class Tuner implements AutoCloseable { /** @hide */ public List getFrontendIds() { - return nativeGetFrontendIds(); + mFrontendLock.lock(); + try { + return nativeGetFrontendIds(); + } finally { + mFrontendLock.unlock(); + } } /** @@ -426,13 +438,20 @@ public class Tuner implements AutoCloseable { * @param tuner the Tuner instance to share frontend resource with. */ public void shareFrontendFromTuner(@NonNull Tuner tuner) { - mTunerResourceManager.shareFrontend(mClientId, tuner.mClientId); - synchronized (mIsSharedFrontend) { - mFrontendHandle = tuner.mFrontendHandle; - mFrontend = tuner.mFrontend; - mIsSharedFrontend = true; + acquireTRMSLock("shareFrontendFromTuner()"); + mFrontendLock.lock(); + try { + mTunerResourceManager.shareFrontend(mClientId, tuner.mClientId); + synchronized (mIsSharedFrontend) { + mFrontendHandle = tuner.mFrontendHandle; + mFrontend = tuner.mFrontend; + mIsSharedFrontend = true; + } + nativeShareFrontend(mFrontend.mId); + } finally { + releaseTRMSLock(); + mFrontendLock.unlock(); } - nativeShareFrontend(mFrontend.mId); } /** @@ -498,39 +517,62 @@ public class Tuner implements AutoCloseable { */ @Override public void close() { - releaseAll(); - TunerUtils.throwExceptionForResult(nativeClose(), "failed to close tuner"); + acquireTRMSLock("close()"); + try { + releaseAll(); + TunerUtils.throwExceptionForResult(nativeClose(), "failed to close tuner"); + } finally { + releaseTRMSLock(); + } } private void releaseAll() { - if (mFrontendHandle != null) { - synchronized (mIsSharedFrontend) { - if (!mIsSharedFrontend) { - int res = nativeCloseFrontend(mFrontendHandle); - if (res != Tuner.RESULT_SUCCESS) { - TunerUtils.throwExceptionForResult(res, "failed to close frontend"); + mFrontendLock.lock(); + try { + if (mFrontendHandle != null) { + synchronized (mIsSharedFrontend) { + if (!mIsSharedFrontend) { + int res = nativeCloseFrontend(mFrontendHandle); + if (res != Tuner.RESULT_SUCCESS) { + TunerUtils.throwExceptionForResult(res, "failed to close frontend"); + } + mTunerResourceManager.releaseFrontend(mFrontendHandle, mClientId); } - mTunerResourceManager.releaseFrontend(mFrontendHandle, mClientId); + mIsSharedFrontend = false; } - mIsSharedFrontend = false; + FrameworkStatsLog + .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId, + FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__UNKNOWN); + mFrontendHandle = null; + mFrontend = null; } - FrameworkStatsLog - .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId, - FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__UNKNOWN); - mFrontendHandle = null; - mFrontend = null; + } finally { + mFrontendLock.unlock(); } - if (mLnb != null) { - mLnb.close(); - } - if (mFrontendCiCamHandle != null) { - int result = nativeUnlinkCiCam(mFrontendCiCamId); - if (result == RESULT_SUCCESS) { - mTunerResourceManager.releaseCiCam(mFrontendCiCamHandle, mClientId); - mFrontendCiCamId = null; - mFrontendCiCamHandle = null; + + mLnbLock.lock(); + try { + if (mLnb != null) { + mLnb.close(); } + } finally { + mLnbLock.unlock(); } + + mFrontendCiCamLock.lock(); + try { + if (mFrontendCiCamHandle != null) { + int result = nativeUnlinkCiCam(mFrontendCiCamId); + if (result == RESULT_SUCCESS) { + mTunerResourceManager.releaseCiCam(mFrontendCiCamHandle, mClientId); + mFrontendCiCamId = null; + mFrontendCiCamHandle = null; + } + } + } finally { + mFrontendCiCamLock.unlock(); + } + synchronized (mDescramblers) { if (!mDescramblers.isEmpty()) { for (Map.Entry> d : mDescramblers.entrySet()) { @@ -543,6 +585,7 @@ public class Tuner implements AutoCloseable { mDescramblers.clear(); } } + synchronized (mFilters) { if (!mFilters.isEmpty()) { for (WeakReference weakFilter : mFilters) { @@ -554,13 +597,19 @@ public class Tuner implements AutoCloseable { mFilters.clear(); } } - if (mDemuxHandle != null) { - int res = nativeCloseDemux(mDemuxHandle); - if (res != Tuner.RESULT_SUCCESS) { - TunerUtils.throwExceptionForResult(res, "failed to close demux"); + + mDemuxLock.lock(); + try { + if (mDemuxHandle != null) { + int res = nativeCloseDemux(mDemuxHandle); + if (res != Tuner.RESULT_SUCCESS) { + TunerUtils.throwExceptionForResult(res, "failed to close demux"); + } + mTunerResourceManager.releaseDemux(mDemuxHandle, mClientId); + mDemuxHandle = null; } - mTunerResourceManager.releaseDemux(mDemuxHandle, mClientId); - mDemuxHandle = null; + } finally { + mDemuxLock.unlock(); } mTunerResourceManager.unregisterClientProfile(mClientId); @@ -763,28 +812,37 @@ public class Tuner implements AutoCloseable { */ @Result public int tune(@NonNull FrontendSettings settings) { - final int type = settings.getType(); - if (mFrontendHandle != null && type != mFrontendType) { - Log.e(TAG, "Frontend was opened with type " + mFrontendType + ", new type is " + type); - return RESULT_INVALID_STATE; - } - Log.d(TAG, "Tune to " + settings.getFrequencyLong()); - mFrontendType = type; - if (mFrontendType == FrontendSettings.TYPE_DTMB) { - if (!TunerVersionChecker.checkHigherOrEqualVersionTo( - TunerVersionChecker.TUNER_VERSION_1_1, "Tuner with DTMB Frontend")) { + mFrontendLock.lock(); + try { + final int type = settings.getType(); + if (mFrontendHandle != null && type != mFrontendType) { + Log.e(TAG, "Frontend was opened with type " + mFrontendType + + ", new type is " + type); + return RESULT_INVALID_STATE; + } + Log.d(TAG, "Tune to " + settings.getFrequencyLong()); + mFrontendType = type; + if (mFrontendType == FrontendSettings.TYPE_DTMB) { + if (!TunerVersionChecker.checkHigherOrEqualVersionTo( + TunerVersionChecker.TUNER_VERSION_1_1, "Tuner with DTMB Frontend")) { + return RESULT_UNAVAILABLE; + } + } + + if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND, mFrontendLock)) { + mFrontendInfo = null; + Log.d(TAG, "Write Stats Log for tuning."); + FrameworkStatsLog + .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId, + FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__TUNING); + int res = nativeTune(settings.getType(), settings); + return res; + } else { return RESULT_UNAVAILABLE; } + } finally { + mFrontendLock.unlock(); } - if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) { - mFrontendInfo = null; - Log.d(TAG, "Write Stats Log for tuning."); - FrameworkStatsLog - .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId, - FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__TUNING); - return nativeTune(settings.getType(), settings); - } - return RESULT_UNAVAILABLE; } /** @@ -797,7 +855,12 @@ public class Tuner implements AutoCloseable { */ @Result public int cancelTuning() { - return nativeStopTune(); + mFrontendLock.lock(); + try { + return nativeStopTune(); + } finally { + mFrontendLock.unlock(); + } } /** @@ -824,33 +887,41 @@ public class Tuner implements AutoCloseable { @Result public int scan(@NonNull FrontendSettings settings, @ScanType int scanType, @NonNull @CallbackExecutor Executor executor, @NonNull ScanCallback scanCallback) { - synchronized (mScanCallbackLock) { - // Scan can be called again for blink scan if scanCallback and executor are same as - //before. - if (((mScanCallback != null) && (mScanCallback != scanCallback)) - || ((mScanCallbackExecutor != null) && (mScanCallbackExecutor != executor))) { - throw new IllegalStateException( - "Different Scan session already in progress. stopScan must be called " - + "before a new scan session can be " + "started."); - } - mFrontendType = settings.getType(); - if (mFrontendType == FrontendSettings.TYPE_DTMB) { - if (!TunerVersionChecker.checkHigherOrEqualVersionTo( - TunerVersionChecker.TUNER_VERSION_1_1, - "Scan with DTMB Frontend")) { - return RESULT_UNAVAILABLE; + + mFrontendLock.lock(); + try { + synchronized (mScanCallbackLock) { + // Scan can be called again for blink scan if scanCallback and executor are same as + //before. + if (((mScanCallback != null) && (mScanCallback != scanCallback)) + || ((mScanCallbackExecutor != null) + && (mScanCallbackExecutor != executor))) { + throw new IllegalStateException( + "Different Scan session already in progress. stopScan must be called " + + "before a new scan session can be " + "started."); } + mFrontendType = settings.getType(); + if (mFrontendType == FrontendSettings.TYPE_DTMB) { + if (!TunerVersionChecker.checkHigherOrEqualVersionTo( + TunerVersionChecker.TUNER_VERSION_1_1, + "Scan with DTMB Frontend")) { + return RESULT_UNAVAILABLE; + } + } + if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND, + mFrontendLock)) { + mScanCallback = scanCallback; + mScanCallbackExecutor = executor; + mFrontendInfo = null; + FrameworkStatsLog + .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId, + FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__SCANNING); + return nativeScan(settings.getType(), settings, scanType); + } + return RESULT_UNAVAILABLE; } - if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) { - mScanCallback = scanCallback; - mScanCallbackExecutor = executor; - mFrontendInfo = null; - FrameworkStatsLog - .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId, - FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__SCANNING); - return nativeScan(settings.getType(), settings, scanType); - } - return RESULT_UNAVAILABLE; + } finally { + mFrontendLock.unlock(); } } @@ -867,14 +938,19 @@ public class Tuner implements AutoCloseable { */ @Result public int cancelScanning() { - synchronized (mScanCallbackLock) { - FrameworkStatsLog.write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId, - FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__SCAN_STOPPED); + mFrontendLock.lock(); + try { + synchronized (mScanCallbackLock) { + FrameworkStatsLog.write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId, + FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__SCAN_STOPPED); - int retVal = nativeStopScan(); - mScanCallback = null; - mScanCallbackExecutor = null; - return retVal; + int retVal = nativeStopScan(); + mScanCallback = null; + mScanCallbackExecutor = null; + return retVal; + } + } finally { + mFrontendLock.unlock(); } } @@ -903,7 +979,12 @@ public class Tuner implements AutoCloseable { */ @Result private int setLnb(@NonNull Lnb lnb) { - return nativeSetLnb(lnb); + mLnbLock.lock(); + try { + return nativeSetLnb(lnb); + } finally { + mLnbLock.unlock(); + } } /** @@ -929,10 +1010,15 @@ public class Tuner implements AutoCloseable { */ @Nullable public FrontendStatus getFrontendStatus(@NonNull @FrontendStatusType int[] statusTypes) { - if (mFrontend == null) { - throw new IllegalStateException("frontend is not initialized"); + mFrontendLock.lock(); + try { + if (mFrontend == null) { + throw new IllegalStateException("frontend is not initialized"); + } + return nativeGetFrontendStatus(statusTypes); + } finally { + mFrontendLock.unlock(); } - return nativeGetFrontendStatus(statusTypes); } /** @@ -942,11 +1028,16 @@ public class Tuner implements AutoCloseable { * @return the id of hardware A/V sync. */ public int getAvSyncHwId(@NonNull Filter filter) { - if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) { - return INVALID_AV_SYNC_ID; + mDemuxLock.lock(); + try { + if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) { + return INVALID_AV_SYNC_ID; + } + Integer id = nativeGetAvSyncHwId(filter); + return id == null ? INVALID_AV_SYNC_ID : id; + } finally { + mDemuxLock.unlock(); } - Integer id = nativeGetAvSyncHwId(filter); - return id == null ? INVALID_AV_SYNC_ID : id; } /** @@ -959,11 +1050,16 @@ public class Tuner implements AutoCloseable { * @return the current timestamp of hardware A/V sync. */ public long getAvSyncTime(int avSyncHwId) { - if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) { - return INVALID_TIMESTAMP; + mDemuxLock.lock(); + try { + if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) { + return INVALID_TIMESTAMP; + } + Long time = nativeGetAvSyncTime(avSyncHwId); + return time == null ? INVALID_TIMESTAMP : time; + } finally { + mDemuxLock.unlock(); } - Long time = nativeGetAvSyncTime(avSyncHwId); - return time == null ? INVALID_TIMESTAMP : time; } /** @@ -980,10 +1076,15 @@ public class Tuner implements AutoCloseable { */ @Result public int connectCiCam(int ciCamId) { - if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) { - return nativeConnectCiCam(ciCamId); + mDemuxLock.lock(); + try { + if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) { + return nativeConnectCiCam(ciCamId); + } + return RESULT_UNAVAILABLE; + } finally { + mDemuxLock.unlock(); } - return RESULT_UNAVAILABLE; } /** @@ -1011,14 +1112,30 @@ public class Tuner implements AutoCloseable { * {@link TunerVersionChecker#getTunerVersion()}. */ public int connectFrontendToCiCam(int ciCamId) { - if (TunerVersionChecker.checkHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1, - "linkFrontendToCiCam")) { - if (checkCiCamResource(ciCamId) - && checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) { - return nativeLinkCiCam(ciCamId); + // TODO: change this so TRMS lock is held only when the resource handles for + // CiCam/Frontend is null. Current implementation can only handle one local lock for that. + acquireTRMSLock("connectFrontendToCiCam()"); + mFrontendCiCamLock.lock(); + mFrontendLock.lock(); + try { + if (TunerVersionChecker.checkHigherOrEqualVersionTo( + TunerVersionChecker.TUNER_VERSION_1_1, + "linkFrontendToCiCam")) { + mRequestedCiCamId = ciCamId; + // No need to unlock mFrontendCiCamLock and mFrontendLock below becauase + // TRMS lock is already acquired. Pass null to disable lock related operations + if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM, null) + && checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND, null) + ) { + return nativeLinkCiCam(ciCamId); + } } + return INVALID_LTS_ID; + } finally { + releaseTRMSLock(); + mFrontendCiCamLock.unlock(); + mFrontendLock.unlock(); } - return INVALID_LTS_ID; } /** @@ -1033,10 +1150,15 @@ public class Tuner implements AutoCloseable { */ @Result public int disconnectCiCam() { - if (mDemuxHandle != null) { - return nativeDisconnectCiCam(); + mDemuxLock.lock(); + try { + if (mDemuxHandle != null) { + return nativeDisconnectCiCam(); + } + return RESULT_UNAVAILABLE; + } finally { + mDemuxLock.unlock(); } - return RESULT_UNAVAILABLE; } /** @@ -1057,20 +1179,30 @@ public class Tuner implements AutoCloseable { */ @Result public int disconnectFrontendToCiCam(int ciCamId) { - if (TunerVersionChecker.checkHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1, - "unlinkFrontendToCiCam")) { - if (mFrontendCiCamHandle != null && mFrontendCiCamId != null - && mFrontendCiCamId == ciCamId) { - int result = nativeUnlinkCiCam(ciCamId); - if (result == RESULT_SUCCESS) { - mTunerResourceManager.releaseCiCam(mFrontendCiCamHandle, mClientId); - mFrontendCiCamId = null; - mFrontendCiCamHandle = null; + acquireTRMSLock("disconnectFrontendToCiCam()"); + try { + if (TunerVersionChecker.checkHigherOrEqualVersionTo( + TunerVersionChecker.TUNER_VERSION_1_1, + "unlinkFrontendToCiCam")) { + mFrontendCiCamLock.lock(); + if (mFrontendCiCamHandle != null && mFrontendCiCamId != null + && mFrontendCiCamId == ciCamId) { + int result = nativeUnlinkCiCam(ciCamId); + if (result == RESULT_SUCCESS) { + mTunerResourceManager.releaseCiCam(mFrontendCiCamHandle, mClientId); + mFrontendCiCamId = null; + mFrontendCiCamHandle = null; + } + return result; } - return result; } + return RESULT_UNAVAILABLE; + } finally { + if (mFrontendCiCamLock.isLocked()) { + mFrontendCiCamLock.unlock(); + } + releaseTRMSLock(); } - return RESULT_UNAVAILABLE; } /** @@ -1082,16 +1214,21 @@ public class Tuner implements AutoCloseable { */ @Nullable public FrontendInfo getFrontendInfo() { - if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) { - return null; + mFrontendLock.lock(); + try { + if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND, mFrontendLock)) { + return null; + } + if (mFrontend == null) { + throw new IllegalStateException("frontend is not initialized"); + } + if (mFrontendInfo == null) { + mFrontendInfo = getFrontendInfoById(mFrontend.mId); + } + return mFrontendInfo; + } finally { + mFrontendLock.unlock(); } - if (mFrontend == null) { - throw new IllegalStateException("frontend is not initialized"); - } - if (mFrontendInfo == null) { - mFrontendInfo = getFrontendInfoById(mFrontend.mId); - } - return mFrontendInfo; } /** @@ -1114,7 +1251,12 @@ public class Tuner implements AutoCloseable { /** @hide */ public FrontendInfo getFrontendInfoById(int id) { - return nativeGetFrontendInfo(id); + mFrontendLock.lock(); + try { + return nativeGetFrontendInfo(id); + } finally { + mFrontendLock.unlock(); + } } /** @@ -1125,7 +1267,12 @@ public class Tuner implements AutoCloseable { */ @Nullable public DemuxCapabilities getDemuxCapabilities() { - return nativeGetDemuxCapabilities(); + mDemuxLock.lock(); + try { + return nativeGetDemuxCapabilities(); + } finally { + mDemuxLock.unlock(); + } } private void onFrontendEvent(int eventType) { @@ -1417,32 +1564,37 @@ public class Tuner implements AutoCloseable { public Filter openFilter(@Type int mainType, @Subtype int subType, @BytesLong long bufferSize, @CallbackExecutor @Nullable Executor executor, @Nullable FilterCallback cb) { - if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) { - return null; - } - Filter filter = nativeOpenFilter( - mainType, TunerUtils.getFilterSubtype(mainType, subType), bufferSize); - if (filter != null) { - filter.setType(mainType, subType); - filter.setCallback(cb, executor); - if (mHandler == null) { - mHandler = createEventHandler(); + mDemuxLock.lock(); + try { + if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) { + return null; } - synchronized (mFilters) { - WeakReference weakFilter = new WeakReference(filter); - mFilters.add(weakFilter); - if (mFilters.size() > FILTER_CLEANUP_THRESHOLD) { - Iterator> iterator = mFilters.iterator(); - while (iterator.hasNext()) { - WeakReference wFilter = iterator.next(); - if (wFilter.get() == null) { - iterator.remove(); + Filter filter = nativeOpenFilter( + mainType, TunerUtils.getFilterSubtype(mainType, subType), bufferSize); + if (filter != null) { + filter.setType(mainType, subType); + filter.setCallback(cb, executor); + if (mHandler == null) { + mHandler = createEventHandler(); + } + synchronized (mFilters) { + WeakReference weakFilter = new WeakReference(filter); + mFilters.add(weakFilter); + if (mFilters.size() > FILTER_CLEANUP_THRESHOLD) { + Iterator> iterator = mFilters.iterator(); + while (iterator.hasNext()) { + WeakReference wFilter = iterator.next(); + if (wFilter.get() == null) { + iterator.remove(); + } } } } } + return filter; + } finally { + mDemuxLock.unlock(); } - return filter; } /** @@ -1457,18 +1609,24 @@ public class Tuner implements AutoCloseable { */ @Nullable public Lnb openLnb(@CallbackExecutor @NonNull Executor executor, @NonNull LnbCallback cb) { - Objects.requireNonNull(executor, "executor must not be null"); - Objects.requireNonNull(cb, "LnbCallback must not be null"); - if (mLnb != null) { - mLnb.setCallback(executor, cb, this); - return mLnb; + mLnbLock.lock(); + try { + Objects.requireNonNull(executor, "executor must not be null"); + Objects.requireNonNull(cb, "LnbCallback must not be null"); + if (mLnb != null) { + mLnb.setCallback(executor, cb, this); + return mLnb; + } + if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_LNB, mLnbLock) + && mLnb != null) { + mLnb.setCallback(executor, cb, this); + setLnb(mLnb); + return mLnb; + } + return null; + } finally { + mLnbLock.unlock(); } - if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_LNB) && mLnb != null) { - mLnb.setCallback(executor, cb, this); - setLnb(mLnb); - return mLnb; - } - return null; } /** @@ -1483,20 +1641,25 @@ public class Tuner implements AutoCloseable { @Nullable public Lnb openLnbByName(@NonNull String name, @CallbackExecutor @NonNull Executor executor, @NonNull LnbCallback cb) { - Objects.requireNonNull(name, "LNB name must not be null"); - Objects.requireNonNull(executor, "executor must not be null"); - Objects.requireNonNull(cb, "LnbCallback must not be null"); - Lnb newLnb = nativeOpenLnbByName(name); - if (newLnb != null) { - if (mLnb != null) { - mLnb.close(); - mLnbHandle = null; + mLnbLock.lock(); + try { + Objects.requireNonNull(name, "LNB name must not be null"); + Objects.requireNonNull(executor, "executor must not be null"); + Objects.requireNonNull(cb, "LnbCallback must not be null"); + Lnb newLnb = nativeOpenLnbByName(name); + if (newLnb != null) { + if (mLnb != null) { + mLnb.close(); + mLnbHandle = null; + } + mLnb = newLnb; + mLnb.setCallback(executor, cb, this); + setLnb(mLnb); } - mLnb = newLnb; - mLnb.setCallback(executor, cb, this); - setLnb(mLnb); + return mLnb; + } finally { + mLnbLock.unlock(); } - return mLnb; } private boolean requestLnb() { @@ -1518,10 +1681,15 @@ public class Tuner implements AutoCloseable { */ @Nullable public TimeFilter openTimeFilter() { - if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) { - return null; + mDemuxLock.lock(); + try { + if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) { + return null; + } + return nativeOpenTimeFilter(); + } finally { + mDemuxLock.unlock(); } - return nativeOpenTimeFilter(); } /** @@ -1532,10 +1700,15 @@ public class Tuner implements AutoCloseable { @RequiresPermission(android.Manifest.permission.ACCESS_TV_DESCRAMBLER) @Nullable public Descrambler openDescrambler() { - if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) { - return null; + mDemuxLock.lock(); + try { + if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) { + return null; + } + return requestDescrambler(); + } finally { + mDemuxLock.unlock(); } - return requestDescrambler(); } /** @@ -1553,14 +1726,19 @@ public class Tuner implements AutoCloseable { @BytesLong long bufferSize, @CallbackExecutor @NonNull Executor executor, @NonNull OnRecordStatusChangedListener l) { - Objects.requireNonNull(executor, "executor must not be null"); - Objects.requireNonNull(l, "OnRecordStatusChangedListener must not be null"); - if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) { - return null; + mDemuxLock.lock(); + try { + Objects.requireNonNull(executor, "executor must not be null"); + Objects.requireNonNull(l, "OnRecordStatusChangedListener must not be null"); + if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) { + return null; + } + DvrRecorder dvr = nativeOpenDvrRecorder(bufferSize); + dvr.setListener(executor, l); + return dvr; + } finally { + mDemuxLock.unlock(); } - DvrRecorder dvr = nativeOpenDvrRecorder(bufferSize); - dvr.setListener(executor, l); - return dvr; } /** @@ -1578,14 +1756,19 @@ public class Tuner implements AutoCloseable { @BytesLong long bufferSize, @CallbackExecutor @NonNull Executor executor, @NonNull OnPlaybackStatusChangedListener l) { - Objects.requireNonNull(executor, "executor must not be null"); - Objects.requireNonNull(l, "OnPlaybackStatusChangedListener must not be null"); - if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) { - return null; + mDemuxLock.lock(); + try { + Objects.requireNonNull(executor, "executor must not be null"); + Objects.requireNonNull(l, "OnPlaybackStatusChangedListener must not be null"); + if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) { + return null; + } + DvrPlayback dvr = nativeOpenDvrPlayback(bufferSize); + dvr.setListener(executor, l); + return dvr; + } finally { + mDemuxLock.unlock(); } - DvrPlayback dvr = nativeOpenDvrPlayback(bufferSize); - dvr.setListener(executor, l); - return dvr; } /** @@ -1602,6 +1785,8 @@ public class Tuner implements AutoCloseable { static public SharedFilter openSharedFilter(@NonNull Context context, @NonNull String sharedFilterToken, @CallbackExecutor @NonNull Executor executor, @NonNull SharedFilterCallback cb) { + // TODO: check what happenes when onReclaimResources() is called and see if + // this needs to be protected with TRMS lock Objects.requireNonNull(sharedFilterToken, "sharedFilterToken must not be null"); Objects.requireNonNull(executor, "executor must not be null"); Objects.requireNonNull(cb, "SharedFilterCallback must not be null"); @@ -1665,22 +1850,28 @@ public class Tuner implements AutoCloseable { return granted; } - private boolean checkResource(int resourceType) { + private boolean checkResource(int resourceType, ReentrantLock localLock) { switch (resourceType) { case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND: { - if (mFrontendHandle == null && !requestFrontend()) { + if (mFrontendHandle == null && !requestResource(resourceType, localLock)) { return false; } break; } case TunerResourceManager.TUNER_RESOURCE_TYPE_LNB: { - if (mLnb == null && !requestLnb()) { + if (mLnb == null && !requestResource(resourceType, localLock)) { return false; } break; } case TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX: { - if (mDemuxHandle == null && !requestDemux()) { + if (mDemuxHandle == null && !requestResource(resourceType, localLock)) { + return false; + } + break; + } + case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM: { + if (mFrontendCiCamHandle == null && !requestResource(resourceType, localLock)) { return false; } break; @@ -1691,24 +1882,91 @@ public class Tuner implements AutoCloseable { return true; } - private boolean checkCiCamResource(int ciCamId) { - if (mFrontendCiCamHandle == null && !requestFrontendCiCam(ciCamId)) { - return false; + // Expected flow of how to use this function is: + // 1) lock the localLock and check if the resource is already held + // 2) if yes, no need to call this function and continue with the handle with the lock held + // 3) if no, then first release the held lock and grab the TRMS lock to avoid deadlock + // 4) grab the local lock again and release the TRMS lock + // If localLock is null, we'll assume the caller does not want the lock related operations + private boolean requestResource(int resourceType, ReentrantLock localLock) { + boolean enableLockOperations = localLock != null; + + // release the local lock first to avoid deadlock + if (enableLockOperations) { + if (localLock.isLocked()) { + localLock.unlock(); + } else { + throw new IllegalStateException("local lock must be locked beforehand"); + } + } + + // now safe to grab TRMS lock + if (enableLockOperations) { + acquireTRMSLock("requestResource:" + resourceType); + } + + try { + // lock the local lock + if (enableLockOperations) { + localLock.lock(); + } + switch (resourceType) { + case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND: { + return requestFrontend(); + } + case TunerResourceManager.TUNER_RESOURCE_TYPE_LNB: { + return requestLnb(); + } + case TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX: { + return requestDemux(); + } + case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM: { + return requestFrontendCiCam(mRequestedCiCamId); + } + default: + return false; + } + } finally { + if (enableLockOperations) { + releaseTRMSLock(); + } } - return true; } /* package */ void releaseLnb() { - if (mLnbHandle != null) { - // LNB handle can be null if it's opened by name. - mTunerResourceManager.releaseLnb(mLnbHandle, mClientId); - mLnbHandle = null; + acquireTRMSLock("releaseLnb()"); + mLnbLock.lock(); + try { + if (mLnbHandle != null) { + // LNB handle can be null if it's opened by name. + mTunerResourceManager.releaseLnb(mLnbHandle, mClientId); + mLnbHandle = null; + } + mLnb = null; + } finally { + releaseTRMSLock(); + mLnbLock.unlock(); } - mLnb = null; } /** @hide */ public int getClientId() { return mClientId; } + + private void acquireTRMSLock(String functionNameForLog) { + if (DEBUG) { + Log.d(TAG, "ATTEMPT:acquireLock() in " + functionNameForLog + + "for clientId:" + mClientId); + } + if (!mTunerResourceManager.acquireLock(mClientId)) { + Log.e(TAG, "FAILED:acquireLock() in " + functionNameForLog + + " for clientId:" + mClientId + " - this can cause deadlock between" + + " Tuner API calls and onReclaimResources()"); + } + } + + private void releaseTRMSLock() { + mTunerResourceManager.releaseLock(mClientId); + } } diff --git a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java index 244fd0e796aa0..fe611c7112733 100644 --- a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java +++ b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java @@ -319,6 +319,48 @@ public class TunerResourceManager { } } + /** + * Grants the lock to the caller for public {@link Tuner} APIs + * + *

{@link Tuner} functions that call both [@link TunerResourceManager} APIs and + * grabs lock that are also used in {@link IResourcesReclaimListener#onReclaimResources()} + * must call this API before acquiring lock used in onReclaimResources(). + * + *

This API will block until it releases the lock or fails + * + * @param clientId The ID of the caller. + * + * @return true if the lock is granted. If false is returned, calling this API again is not + * guaranteed to work and may be unrecoverrable. (This should not happen.) + */ + public boolean acquireLock(int clientId) { + try { + return mService.acquireLock(clientId, Thread.currentThread().getId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Releases the lock to the caller for public {@link Tuner} APIs + * + *

This API must be called in pair with {@link #acquireLock(int, int)} + * + *

This API will block until it releases the lock or fails + * + * @param clientId The ID of the caller. + * + * @return true if the lock is granted. If false is returned, calling this API again is not + * guaranteed to work and may be unrecoverrable. (This should not happen.) + */ + public boolean releaseLock(int clientId) { + try { + return mService.releaseLock(clientId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Requests a frontend resource. * 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 7bc50586fb37d..5f3582046d151 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 @@ -412,4 +412,34 @@ interface ITunerResourceManager { * @param resourceType The resource type to restore the map for. */ void restoreResourceMap(in int resourceType); + + /** + * Grants the lock to the caller for public {@link Tuner} APIs + * + *

{@link Tuner} functions that call both [@link TunerResourceManager} APIs and + * grabs lock that are also used in {@link IResourcesReclaimListener#onReclaimResources()} + * must call this API before acquiring lock used in onReclaimResources(). + * + *

This API will block until it releases the lock or fails + * + * @param clientId The ID of the caller. + * + * @return true if the lock is granted. If false is returned, calling this API again is not + * guaranteed to work and may be unrecoverrable. (This should not happen.) + */ + boolean acquireLock(in int clientId, in long clientThreadId); + + /** + * Releases the lock to the caller for public {@link Tuner} APIs + * + *

This API must be called in pair with {@link #acquireLock(int, int)} + * + *

This API will block until it releases the lock or fails + * + * @param clientId The ID of the caller. + * + * @return true if the lock is granted. If false is returned, calling this API again is not + * guaranteed to work and may be unrecoverrable. (This should not happen.) + */ + boolean releaseLock(in int clientId); } 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 043646041158f..6628802ea9db5 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java @@ -37,6 +37,7 @@ import android.media.tv.tunerresourcemanager.TunerResourceManager; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; +import android.os.SystemClock; import android.util.IndentingPrintWriter; import android.util.Log; import android.util.Slog; @@ -52,6 +53,9 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; /** * This class provides a system service that manages the TV tuner resources. @@ -64,6 +68,8 @@ public class TunerResourceManagerService extends SystemService implements IBinde public static final int INVALID_CLIENT_ID = -1; private static final int MAX_CLIENT_PRIORITY = 1000; + private static final long INVALID_THREAD_ID = -1; + private static final long TRMS_LOCK_TIMEOUT = 500; // Map of the registered client profiles private Map mClientProfiles = new HashMap<>(); @@ -94,6 +100,12 @@ public class TunerResourceManagerService extends SystemService implements IBinde // Used to synchronize the access to the service. private final Object mLock = new Object(); + private final ReentrantLock mLockForTRMSLock = new ReentrantLock(); + private final Condition mTunerApiLockReleasedCV = mLockForTRMSLock.newCondition(); + private int mTunerApiLockHolder = INVALID_CLIENT_ID; + private long mTunerApiLockHolderThreadId = INVALID_THREAD_ID; + private int mTunerApiLockNestedCount = 0; + public TunerResourceManagerService(@Nullable Context context) { super(context); } @@ -510,6 +522,20 @@ public class TunerResourceManagerService extends SystemService implements IBinde } } + @Override + public boolean acquireLock(int clientId, long clientThreadId) { + enforceTrmAccessPermission("acquireLock"); + // this must not be locked with mLock + return acquireLockInternal(clientId, clientThreadId, TRMS_LOCK_TIMEOUT); + } + + @Override + public boolean releaseLock(int clientId) { + enforceTrmAccessPermission("releaseLock"); + // this must not be locked with mLock + return releaseLockInternal(clientId, TRMS_LOCK_TIMEOUT, false, false); + } + @Override protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) { final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); @@ -1194,6 +1220,187 @@ public class TunerResourceManagerService extends SystemService implements IBinde return true; } + // Return value is guaranteed to be positive + private long getElapsedTime(long begin) { + long now = SystemClock.uptimeMillis(); + long elapsed; + if (now >= begin) { + elapsed = now - begin; + } else { + elapsed = Long.MAX_VALUE - begin + now; + if (elapsed < 0) { + elapsed = Long.MAX_VALUE; + } + } + return elapsed; + } + + private boolean lockForTunerApiLock(int clientId, long timeoutMS, String callerFunction) { + try { + if (mLockForTRMSLock.tryLock(timeoutMS, TimeUnit.MILLISECONDS)) { + return true; + } else { + Slog.e(TAG, "FAILED to lock mLockForTRMSLock in " + callerFunction + + ", clientId:" + clientId + ", timeoutMS:" + timeoutMS + + ", mTunerApiLockHolder:" + mTunerApiLockHolder); + return false; + } + } catch (InterruptedException ie) { + Slog.e(TAG, "exception thrown in " + callerFunction + ":" + ie); + if (mLockForTRMSLock.isHeldByCurrentThread()) { + mLockForTRMSLock.unlock(); + } + return false; + } + } + + private boolean acquireLockInternal(int clientId, long clientThreadId, long timeoutMS) { + long begin = SystemClock.uptimeMillis(); + + // Grab lock + if (!lockForTunerApiLock(clientId, timeoutMS, "acquireLockInternal()")) { + return false; + } + + try { + boolean available = mTunerApiLockHolder == INVALID_CLIENT_ID; + boolean nestedSelf = (clientId == mTunerApiLockHolder) + && (clientThreadId == mTunerApiLockHolderThreadId); + boolean recovery = false; + + // Allow same thread to grab the lock multiple times + while (!available && !nestedSelf) { + // calculate how much time is left before timeout + long leftOverMS = timeoutMS - getElapsedTime(begin); + if (leftOverMS <= 0) { + Slog.e(TAG, "FAILED:acquireLockInternal(" + clientId + ", " + clientThreadId + + ", " + timeoutMS + ") - timed out, but will grant the lock to " + + "the callee by stealing it from the current holder:" + + mTunerApiLockHolder + "(" + mTunerApiLockHolderThreadId + "), " + + "who likely failed to call releaseLock(), " + + "to prevent this from becoming an unrecoverable error"); + // This should not normally happen, but there sometimes are cases where + // in-flight tuner API execution gets scheduled even after binderDied(), + // which can leave the in-flight execution dissappear/stopped in between + // acquireLock and releaseLock + recovery = true; + break; + } + + // Cond wait for left over time + mTunerApiLockReleasedCV.await(leftOverMS, TimeUnit.MILLISECONDS); + + // Check the availability for "spurious wakeup" + // The case that was confirmed is that someone else can acquire this in between + // signal() and wakup from the above await() + available = mTunerApiLockHolder == INVALID_CLIENT_ID; + + if (!available) { + Slog.w(TAG, "acquireLockInternal(" + clientId + ", " + clientThreadId + ", " + + timeoutMS + ") - woken up from cond wait, but " + mTunerApiLockHolder + + "(" + mTunerApiLockHolderThreadId + ") is already holding the lock. " + + "Going to wait again if timeout hasn't reached yet"); + } + } + + // Will always grant unless exception is thrown (or lock is already held) + if (available || recovery) { + if (DEBUG) { + Slog.d(TAG, "SUCCESS:acquireLockInternal(" + clientId + ", " + clientThreadId + + ", " + timeoutMS + ")"); + } + + if (mTunerApiLockNestedCount != 0) { + Slog.w(TAG, "Something is wrong as nestedCount(" + mTunerApiLockNestedCount + + ") is not zero. Will overriding it to 1 anyways"); + } + + // set the caller to be the holder + mTunerApiLockHolder = clientId; + mTunerApiLockHolderThreadId = clientThreadId; + mTunerApiLockNestedCount = 1; + } else if (nestedSelf) { + // Increment the nested count so releaseLockInternal won't signal prematuredly + mTunerApiLockNestedCount++; + if (DEBUG) { + Slog.d(TAG, "acquireLockInternal(" + clientId + ", " + clientThreadId + + ", " + timeoutMS + ") - nested count incremented to " + + mTunerApiLockNestedCount); + } + } else { + Slog.e(TAG, "acquireLockInternal(" + clientId + ", " + clientThreadId + + ", " + timeoutMS + ") - should not reach here"); + } + // return true in "recovery" so callee knows that the deadlock is possible + // only when the return value is false + return (available || nestedSelf || recovery); + } catch (InterruptedException ie) { + Slog.e(TAG, "exception thrown in acquireLockInternal(" + clientId + ", " + + clientThreadId + ", " + timeoutMS + "):" + ie); + return false; + } finally { + if (mLockForTRMSLock.isHeldByCurrentThread()) { + mLockForTRMSLock.unlock(); + } + } + } + + private boolean releaseLockInternal(int clientId, long timeoutMS, + boolean ignoreNestedCount, boolean suppressError) { + // Grab lock first + if (!lockForTunerApiLock(clientId, timeoutMS, "releaseLockInternal()")) { + return false; + } + + try { + if (mTunerApiLockHolder == clientId) { + // Should always reach here unless called from binderDied() + mTunerApiLockNestedCount--; + if (ignoreNestedCount || mTunerApiLockNestedCount <= 0) { + if (DEBUG) { + Slog.d(TAG, "SUCCESS:releaseLockInternal(" + clientId + ", " + timeoutMS + + ", " + ignoreNestedCount + ", " + suppressError + + ") - signaling!"); + } + // Reset the current holder and signal + mTunerApiLockHolder = INVALID_CLIENT_ID; + mTunerApiLockHolderThreadId = INVALID_THREAD_ID; + mTunerApiLockNestedCount = 0; + mTunerApiLockReleasedCV.signal(); + } else { + if (DEBUG) { + Slog.d(TAG, "releaseLockInternal(" + clientId + ", " + timeoutMS + + ", " + ignoreNestedCount + ", " + suppressError + + ") - NOT signaling because nested count is not zero (" + + mTunerApiLockNestedCount + ")"); + } + } + return true; + } else if (mTunerApiLockHolder == INVALID_CLIENT_ID) { + if (!suppressError) { + Slog.w(TAG, "releaseLockInternal(" + clientId + ", " + timeoutMS + + ") - called while there is no current holder"); + } + // No need to do anything. + // Shouldn't reach here unless called from binderDied() + return false; + } else { + if (!suppressError) { + Slog.e(TAG, "releaseLockInternal(" + clientId + ", " + timeoutMS + + ") - called while someone else:" + mTunerApiLockHolder + + "is the current holder"); + } + // Cannot reset the holder Id because it reaches here when called + // from binderDied() + return false; + } + } finally { + if (mLockForTRMSLock.isHeldByCurrentThread()) { + mLockForTRMSLock.unlock(); + } + } + } + @VisibleForTesting protected class ResourcesReclaimListenerRecord implements IBinder.DeathRecipient { private final IResourcesReclaimListener mListener; @@ -1206,10 +1413,15 @@ public class TunerResourceManagerService extends SystemService implements IBinde @Override public void binderDied() { - synchronized (mLock) { - if (checkClientExists(mClientId)) { - removeClientProfile(mClientId); + try { + synchronized (mLock) { + if (checkClientExists(mClientId)) { + removeClientProfile(mClientId); + } } + } finally { + // reset the tuner API lock + releaseLockInternal(mClientId, TRMS_LOCK_TIMEOUT, true, true); } } @@ -1247,6 +1459,13 @@ public class TunerResourceManagerService extends SystemService implements IBinde protected boolean reclaimResource(int reclaimingClientId, @TunerResourceManager.TunerResourceType int resourceType) { + // Allowing this because: + // 1) serialization of resource reclaim is required in the current design + // 2) the outgoing transaction is handled by the system app (with + // android.Manifest.permission.TUNER_RESOURCE_ACCESS), which goes through full + // Google certification + Binder.allowBlockingForCurrentThread(); + // Reclaim all the resources of the share owners of the frontend that is used by the current // resource reclaimed client. ClientProfile profile = getClientProfile(reclaimingClientId);