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
This commit is contained in:
@@ -74,6 +74,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is used to interact with hardware tuners devices.
|
* 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;
|
private static final int FILTER_CLEANUP_THRESHOLD = 256;
|
||||||
|
|
||||||
|
|
||||||
/** @hide */
|
/** @hide */
|
||||||
@IntDef(prefix = "DVR_TYPE_", value = {DVR_TYPE_RECORD, DVR_TYPE_PLAYBACK})
|
@IntDef(prefix = "DVR_TYPE_", value = {DVR_TYPE_RECORD, DVR_TYPE_PLAYBACK})
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
@@ -304,6 +306,11 @@ public class Tuner implements AutoCloseable {
|
|||||||
private final Object mOnTuneEventLock = new Object();
|
private final Object mOnTuneEventLock = new Object();
|
||||||
private final Object mScanCallbackLock = new Object();
|
private final Object mScanCallbackLock = new Object();
|
||||||
private final Object mOnResourceLostListenerLock = 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 mDemuxHandle;
|
||||||
private Integer mFrontendCiCamHandle;
|
private Integer mFrontendCiCamHandle;
|
||||||
@@ -391,7 +398,12 @@ public class Tuner implements AutoCloseable {
|
|||||||
|
|
||||||
/** @hide */
|
/** @hide */
|
||||||
public List<Integer> getFrontendIds() {
|
public List<Integer> getFrontendIds() {
|
||||||
|
mFrontendLock.lock();
|
||||||
|
try {
|
||||||
return nativeGetFrontendIds();
|
return nativeGetFrontendIds();
|
||||||
|
} finally {
|
||||||
|
mFrontendLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -426,6 +438,9 @@ public class Tuner implements AutoCloseable {
|
|||||||
* @param tuner the Tuner instance to share frontend resource with.
|
* @param tuner the Tuner instance to share frontend resource with.
|
||||||
*/
|
*/
|
||||||
public void shareFrontendFromTuner(@NonNull Tuner tuner) {
|
public void shareFrontendFromTuner(@NonNull Tuner tuner) {
|
||||||
|
acquireTRMSLock("shareFrontendFromTuner()");
|
||||||
|
mFrontendLock.lock();
|
||||||
|
try {
|
||||||
mTunerResourceManager.shareFrontend(mClientId, tuner.mClientId);
|
mTunerResourceManager.shareFrontend(mClientId, tuner.mClientId);
|
||||||
synchronized (mIsSharedFrontend) {
|
synchronized (mIsSharedFrontend) {
|
||||||
mFrontendHandle = tuner.mFrontendHandle;
|
mFrontendHandle = tuner.mFrontendHandle;
|
||||||
@@ -433,6 +448,10 @@ public class Tuner implements AutoCloseable {
|
|||||||
mIsSharedFrontend = true;
|
mIsSharedFrontend = true;
|
||||||
}
|
}
|
||||||
nativeShareFrontend(mFrontend.mId);
|
nativeShareFrontend(mFrontend.mId);
|
||||||
|
} finally {
|
||||||
|
releaseTRMSLock();
|
||||||
|
mFrontendLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -498,11 +517,18 @@ public class Tuner implements AutoCloseable {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
|
acquireTRMSLock("close()");
|
||||||
|
try {
|
||||||
releaseAll();
|
releaseAll();
|
||||||
TunerUtils.throwExceptionForResult(nativeClose(), "failed to close tuner");
|
TunerUtils.throwExceptionForResult(nativeClose(), "failed to close tuner");
|
||||||
|
} finally {
|
||||||
|
releaseTRMSLock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void releaseAll() {
|
private void releaseAll() {
|
||||||
|
mFrontendLock.lock();
|
||||||
|
try {
|
||||||
if (mFrontendHandle != null) {
|
if (mFrontendHandle != null) {
|
||||||
synchronized (mIsSharedFrontend) {
|
synchronized (mIsSharedFrontend) {
|
||||||
if (!mIsSharedFrontend) {
|
if (!mIsSharedFrontend) {
|
||||||
@@ -520,9 +546,21 @@ public class Tuner implements AutoCloseable {
|
|||||||
mFrontendHandle = null;
|
mFrontendHandle = null;
|
||||||
mFrontend = null;
|
mFrontend = null;
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
mFrontendLock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
mLnbLock.lock();
|
||||||
|
try {
|
||||||
if (mLnb != null) {
|
if (mLnb != null) {
|
||||||
mLnb.close();
|
mLnb.close();
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
mLnbLock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
mFrontendCiCamLock.lock();
|
||||||
|
try {
|
||||||
if (mFrontendCiCamHandle != null) {
|
if (mFrontendCiCamHandle != null) {
|
||||||
int result = nativeUnlinkCiCam(mFrontendCiCamId);
|
int result = nativeUnlinkCiCam(mFrontendCiCamId);
|
||||||
if (result == RESULT_SUCCESS) {
|
if (result == RESULT_SUCCESS) {
|
||||||
@@ -531,6 +569,10 @@ public class Tuner implements AutoCloseable {
|
|||||||
mFrontendCiCamHandle = null;
|
mFrontendCiCamHandle = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
mFrontendCiCamLock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
synchronized (mDescramblers) {
|
synchronized (mDescramblers) {
|
||||||
if (!mDescramblers.isEmpty()) {
|
if (!mDescramblers.isEmpty()) {
|
||||||
for (Map.Entry<Integer, WeakReference<Descrambler>> d : mDescramblers.entrySet()) {
|
for (Map.Entry<Integer, WeakReference<Descrambler>> d : mDescramblers.entrySet()) {
|
||||||
@@ -543,6 +585,7 @@ public class Tuner implements AutoCloseable {
|
|||||||
mDescramblers.clear();
|
mDescramblers.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized (mFilters) {
|
synchronized (mFilters) {
|
||||||
if (!mFilters.isEmpty()) {
|
if (!mFilters.isEmpty()) {
|
||||||
for (WeakReference<Filter> weakFilter : mFilters) {
|
for (WeakReference<Filter> weakFilter : mFilters) {
|
||||||
@@ -554,6 +597,9 @@ public class Tuner implements AutoCloseable {
|
|||||||
mFilters.clear();
|
mFilters.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mDemuxLock.lock();
|
||||||
|
try {
|
||||||
if (mDemuxHandle != null) {
|
if (mDemuxHandle != null) {
|
||||||
int res = nativeCloseDemux(mDemuxHandle);
|
int res = nativeCloseDemux(mDemuxHandle);
|
||||||
if (res != Tuner.RESULT_SUCCESS) {
|
if (res != Tuner.RESULT_SUCCESS) {
|
||||||
@@ -562,6 +608,9 @@ public class Tuner implements AutoCloseable {
|
|||||||
mTunerResourceManager.releaseDemux(mDemuxHandle, mClientId);
|
mTunerResourceManager.releaseDemux(mDemuxHandle, mClientId);
|
||||||
mDemuxHandle = null;
|
mDemuxHandle = null;
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
mDemuxLock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
mTunerResourceManager.unregisterClientProfile(mClientId);
|
mTunerResourceManager.unregisterClientProfile(mClientId);
|
||||||
|
|
||||||
@@ -763,9 +812,12 @@ public class Tuner implements AutoCloseable {
|
|||||||
*/
|
*/
|
||||||
@Result
|
@Result
|
||||||
public int tune(@NonNull FrontendSettings settings) {
|
public int tune(@NonNull FrontendSettings settings) {
|
||||||
|
mFrontendLock.lock();
|
||||||
|
try {
|
||||||
final int type = settings.getType();
|
final int type = settings.getType();
|
||||||
if (mFrontendHandle != null && type != mFrontendType) {
|
if (mFrontendHandle != null && type != mFrontendType) {
|
||||||
Log.e(TAG, "Frontend was opened with type " + mFrontendType + ", new type is " + type);
|
Log.e(TAG, "Frontend was opened with type " + mFrontendType
|
||||||
|
+ ", new type is " + type);
|
||||||
return RESULT_INVALID_STATE;
|
return RESULT_INVALID_STATE;
|
||||||
}
|
}
|
||||||
Log.d(TAG, "Tune to " + settings.getFrequencyLong());
|
Log.d(TAG, "Tune to " + settings.getFrequencyLong());
|
||||||
@@ -776,16 +828,22 @@ public class Tuner implements AutoCloseable {
|
|||||||
return RESULT_UNAVAILABLE;
|
return RESULT_UNAVAILABLE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
|
|
||||||
|
if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND, mFrontendLock)) {
|
||||||
mFrontendInfo = null;
|
mFrontendInfo = null;
|
||||||
Log.d(TAG, "Write Stats Log for tuning.");
|
Log.d(TAG, "Write Stats Log for tuning.");
|
||||||
FrameworkStatsLog
|
FrameworkStatsLog
|
||||||
.write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
|
.write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
|
||||||
FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__TUNING);
|
FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__TUNING);
|
||||||
return nativeTune(settings.getType(), settings);
|
int res = nativeTune(settings.getType(), settings);
|
||||||
}
|
return res;
|
||||||
|
} else {
|
||||||
return RESULT_UNAVAILABLE;
|
return RESULT_UNAVAILABLE;
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
mFrontendLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stops a previous tuning.
|
* Stops a previous tuning.
|
||||||
@@ -797,7 +855,12 @@ public class Tuner implements AutoCloseable {
|
|||||||
*/
|
*/
|
||||||
@Result
|
@Result
|
||||||
public int cancelTuning() {
|
public int cancelTuning() {
|
||||||
|
mFrontendLock.lock();
|
||||||
|
try {
|
||||||
return nativeStopTune();
|
return nativeStopTune();
|
||||||
|
} finally {
|
||||||
|
mFrontendLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -824,11 +887,15 @@ public class Tuner implements AutoCloseable {
|
|||||||
@Result
|
@Result
|
||||||
public int scan(@NonNull FrontendSettings settings, @ScanType int scanType,
|
public int scan(@NonNull FrontendSettings settings, @ScanType int scanType,
|
||||||
@NonNull @CallbackExecutor Executor executor, @NonNull ScanCallback scanCallback) {
|
@NonNull @CallbackExecutor Executor executor, @NonNull ScanCallback scanCallback) {
|
||||||
|
|
||||||
|
mFrontendLock.lock();
|
||||||
|
try {
|
||||||
synchronized (mScanCallbackLock) {
|
synchronized (mScanCallbackLock) {
|
||||||
// Scan can be called again for blink scan if scanCallback and executor are same as
|
// Scan can be called again for blink scan if scanCallback and executor are same as
|
||||||
//before.
|
//before.
|
||||||
if (((mScanCallback != null) && (mScanCallback != scanCallback))
|
if (((mScanCallback != null) && (mScanCallback != scanCallback))
|
||||||
|| ((mScanCallbackExecutor != null) && (mScanCallbackExecutor != executor))) {
|
|| ((mScanCallbackExecutor != null)
|
||||||
|
&& (mScanCallbackExecutor != executor))) {
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
"Different Scan session already in progress. stopScan must be called "
|
"Different Scan session already in progress. stopScan must be called "
|
||||||
+ "before a new scan session can be " + "started.");
|
+ "before a new scan session can be " + "started.");
|
||||||
@@ -841,7 +908,8 @@ public class Tuner implements AutoCloseable {
|
|||||||
return RESULT_UNAVAILABLE;
|
return RESULT_UNAVAILABLE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
|
if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND,
|
||||||
|
mFrontendLock)) {
|
||||||
mScanCallback = scanCallback;
|
mScanCallback = scanCallback;
|
||||||
mScanCallbackExecutor = executor;
|
mScanCallbackExecutor = executor;
|
||||||
mFrontendInfo = null;
|
mFrontendInfo = null;
|
||||||
@@ -852,6 +920,9 @@ public class Tuner implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
return RESULT_UNAVAILABLE;
|
return RESULT_UNAVAILABLE;
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
mFrontendLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -867,6 +938,8 @@ public class Tuner implements AutoCloseable {
|
|||||||
*/
|
*/
|
||||||
@Result
|
@Result
|
||||||
public int cancelScanning() {
|
public int cancelScanning() {
|
||||||
|
mFrontendLock.lock();
|
||||||
|
try {
|
||||||
synchronized (mScanCallbackLock) {
|
synchronized (mScanCallbackLock) {
|
||||||
FrameworkStatsLog.write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
|
FrameworkStatsLog.write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
|
||||||
FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__SCAN_STOPPED);
|
FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__SCAN_STOPPED);
|
||||||
@@ -876,6 +949,9 @@ public class Tuner implements AutoCloseable {
|
|||||||
mScanCallbackExecutor = null;
|
mScanCallbackExecutor = null;
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
mFrontendLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean requestFrontend() {
|
private boolean requestFrontend() {
|
||||||
@@ -903,7 +979,12 @@ public class Tuner implements AutoCloseable {
|
|||||||
*/
|
*/
|
||||||
@Result
|
@Result
|
||||||
private int setLnb(@NonNull Lnb lnb) {
|
private int setLnb(@NonNull Lnb lnb) {
|
||||||
|
mLnbLock.lock();
|
||||||
|
try {
|
||||||
return nativeSetLnb(lnb);
|
return nativeSetLnb(lnb);
|
||||||
|
} finally {
|
||||||
|
mLnbLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -929,10 +1010,15 @@ public class Tuner implements AutoCloseable {
|
|||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public FrontendStatus getFrontendStatus(@NonNull @FrontendStatusType int[] statusTypes) {
|
public FrontendStatus getFrontendStatus(@NonNull @FrontendStatusType int[] statusTypes) {
|
||||||
|
mFrontendLock.lock();
|
||||||
|
try {
|
||||||
if (mFrontend == null) {
|
if (mFrontend == null) {
|
||||||
throw new IllegalStateException("frontend is not initialized");
|
throw new IllegalStateException("frontend is not initialized");
|
||||||
}
|
}
|
||||||
return nativeGetFrontendStatus(statusTypes);
|
return nativeGetFrontendStatus(statusTypes);
|
||||||
|
} finally {
|
||||||
|
mFrontendLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -942,11 +1028,16 @@ public class Tuner implements AutoCloseable {
|
|||||||
* @return the id of hardware A/V sync.
|
* @return the id of hardware A/V sync.
|
||||||
*/
|
*/
|
||||||
public int getAvSyncHwId(@NonNull Filter filter) {
|
public int getAvSyncHwId(@NonNull Filter filter) {
|
||||||
if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
|
mDemuxLock.lock();
|
||||||
|
try {
|
||||||
|
if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
|
||||||
return INVALID_AV_SYNC_ID;
|
return INVALID_AV_SYNC_ID;
|
||||||
}
|
}
|
||||||
Integer id = nativeGetAvSyncHwId(filter);
|
Integer id = nativeGetAvSyncHwId(filter);
|
||||||
return id == null ? INVALID_AV_SYNC_ID : id;
|
return id == null ? INVALID_AV_SYNC_ID : id;
|
||||||
|
} finally {
|
||||||
|
mDemuxLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -959,11 +1050,16 @@ public class Tuner implements AutoCloseable {
|
|||||||
* @return the current timestamp of hardware A/V sync.
|
* @return the current timestamp of hardware A/V sync.
|
||||||
*/
|
*/
|
||||||
public long getAvSyncTime(int avSyncHwId) {
|
public long getAvSyncTime(int avSyncHwId) {
|
||||||
if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
|
mDemuxLock.lock();
|
||||||
|
try {
|
||||||
|
if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
|
||||||
return INVALID_TIMESTAMP;
|
return INVALID_TIMESTAMP;
|
||||||
}
|
}
|
||||||
Long time = nativeGetAvSyncTime(avSyncHwId);
|
Long time = nativeGetAvSyncTime(avSyncHwId);
|
||||||
return time == null ? INVALID_TIMESTAMP : time;
|
return time == null ? INVALID_TIMESTAMP : time;
|
||||||
|
} finally {
|
||||||
|
mDemuxLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -980,10 +1076,15 @@ public class Tuner implements AutoCloseable {
|
|||||||
*/
|
*/
|
||||||
@Result
|
@Result
|
||||||
public int connectCiCam(int ciCamId) {
|
public int connectCiCam(int ciCamId) {
|
||||||
if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
|
mDemuxLock.lock();
|
||||||
|
try {
|
||||||
|
if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
|
||||||
return nativeConnectCiCam(ciCamId);
|
return nativeConnectCiCam(ciCamId);
|
||||||
}
|
}
|
||||||
return RESULT_UNAVAILABLE;
|
return RESULT_UNAVAILABLE;
|
||||||
|
} finally {
|
||||||
|
mDemuxLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1011,14 +1112,30 @@ public class Tuner implements AutoCloseable {
|
|||||||
* {@link TunerVersionChecker#getTunerVersion()}.
|
* {@link TunerVersionChecker#getTunerVersion()}.
|
||||||
*/
|
*/
|
||||||
public int connectFrontendToCiCam(int ciCamId) {
|
public int connectFrontendToCiCam(int ciCamId) {
|
||||||
if (TunerVersionChecker.checkHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1,
|
// 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")) {
|
"linkFrontendToCiCam")) {
|
||||||
if (checkCiCamResource(ciCamId)
|
mRequestedCiCamId = ciCamId;
|
||||||
&& checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
|
// 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 nativeLinkCiCam(ciCamId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return INVALID_LTS_ID;
|
return INVALID_LTS_ID;
|
||||||
|
} finally {
|
||||||
|
releaseTRMSLock();
|
||||||
|
mFrontendCiCamLock.unlock();
|
||||||
|
mFrontendLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1033,10 +1150,15 @@ public class Tuner implements AutoCloseable {
|
|||||||
*/
|
*/
|
||||||
@Result
|
@Result
|
||||||
public int disconnectCiCam() {
|
public int disconnectCiCam() {
|
||||||
|
mDemuxLock.lock();
|
||||||
|
try {
|
||||||
if (mDemuxHandle != null) {
|
if (mDemuxHandle != null) {
|
||||||
return nativeDisconnectCiCam();
|
return nativeDisconnectCiCam();
|
||||||
}
|
}
|
||||||
return RESULT_UNAVAILABLE;
|
return RESULT_UNAVAILABLE;
|
||||||
|
} finally {
|
||||||
|
mDemuxLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1057,8 +1179,12 @@ public class Tuner implements AutoCloseable {
|
|||||||
*/
|
*/
|
||||||
@Result
|
@Result
|
||||||
public int disconnectFrontendToCiCam(int ciCamId) {
|
public int disconnectFrontendToCiCam(int ciCamId) {
|
||||||
if (TunerVersionChecker.checkHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1,
|
acquireTRMSLock("disconnectFrontendToCiCam()");
|
||||||
|
try {
|
||||||
|
if (TunerVersionChecker.checkHigherOrEqualVersionTo(
|
||||||
|
TunerVersionChecker.TUNER_VERSION_1_1,
|
||||||
"unlinkFrontendToCiCam")) {
|
"unlinkFrontendToCiCam")) {
|
||||||
|
mFrontendCiCamLock.lock();
|
||||||
if (mFrontendCiCamHandle != null && mFrontendCiCamId != null
|
if (mFrontendCiCamHandle != null && mFrontendCiCamId != null
|
||||||
&& mFrontendCiCamId == ciCamId) {
|
&& mFrontendCiCamId == ciCamId) {
|
||||||
int result = nativeUnlinkCiCam(ciCamId);
|
int result = nativeUnlinkCiCam(ciCamId);
|
||||||
@@ -1071,6 +1197,12 @@ public class Tuner implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return RESULT_UNAVAILABLE;
|
return RESULT_UNAVAILABLE;
|
||||||
|
} finally {
|
||||||
|
if (mFrontendCiCamLock.isLocked()) {
|
||||||
|
mFrontendCiCamLock.unlock();
|
||||||
|
}
|
||||||
|
releaseTRMSLock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1082,7 +1214,9 @@ public class Tuner implements AutoCloseable {
|
|||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public FrontendInfo getFrontendInfo() {
|
public FrontendInfo getFrontendInfo() {
|
||||||
if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
|
mFrontendLock.lock();
|
||||||
|
try {
|
||||||
|
if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND, mFrontendLock)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (mFrontend == null) {
|
if (mFrontend == null) {
|
||||||
@@ -1092,6 +1226,9 @@ public class Tuner implements AutoCloseable {
|
|||||||
mFrontendInfo = getFrontendInfoById(mFrontend.mId);
|
mFrontendInfo = getFrontendInfoById(mFrontend.mId);
|
||||||
}
|
}
|
||||||
return mFrontendInfo;
|
return mFrontendInfo;
|
||||||
|
} finally {
|
||||||
|
mFrontendLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1114,7 +1251,12 @@ public class Tuner implements AutoCloseable {
|
|||||||
|
|
||||||
/** @hide */
|
/** @hide */
|
||||||
public FrontendInfo getFrontendInfoById(int id) {
|
public FrontendInfo getFrontendInfoById(int id) {
|
||||||
|
mFrontendLock.lock();
|
||||||
|
try {
|
||||||
return nativeGetFrontendInfo(id);
|
return nativeGetFrontendInfo(id);
|
||||||
|
} finally {
|
||||||
|
mFrontendLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1125,7 +1267,12 @@ public class Tuner implements AutoCloseable {
|
|||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public DemuxCapabilities getDemuxCapabilities() {
|
public DemuxCapabilities getDemuxCapabilities() {
|
||||||
|
mDemuxLock.lock();
|
||||||
|
try {
|
||||||
return nativeGetDemuxCapabilities();
|
return nativeGetDemuxCapabilities();
|
||||||
|
} finally {
|
||||||
|
mDemuxLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onFrontendEvent(int eventType) {
|
private void onFrontendEvent(int eventType) {
|
||||||
@@ -1417,7 +1564,9 @@ public class Tuner implements AutoCloseable {
|
|||||||
public Filter openFilter(@Type int mainType, @Subtype int subType,
|
public Filter openFilter(@Type int mainType, @Subtype int subType,
|
||||||
@BytesLong long bufferSize, @CallbackExecutor @Nullable Executor executor,
|
@BytesLong long bufferSize, @CallbackExecutor @Nullable Executor executor,
|
||||||
@Nullable FilterCallback cb) {
|
@Nullable FilterCallback cb) {
|
||||||
if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
|
mDemuxLock.lock();
|
||||||
|
try {
|
||||||
|
if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
Filter filter = nativeOpenFilter(
|
Filter filter = nativeOpenFilter(
|
||||||
@@ -1443,6 +1592,9 @@ public class Tuner implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return filter;
|
return filter;
|
||||||
|
} finally {
|
||||||
|
mDemuxLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1457,18 +1609,24 @@ public class Tuner implements AutoCloseable {
|
|||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public Lnb openLnb(@CallbackExecutor @NonNull Executor executor, @NonNull LnbCallback cb) {
|
public Lnb openLnb(@CallbackExecutor @NonNull Executor executor, @NonNull LnbCallback cb) {
|
||||||
|
mLnbLock.lock();
|
||||||
|
try {
|
||||||
Objects.requireNonNull(executor, "executor must not be null");
|
Objects.requireNonNull(executor, "executor must not be null");
|
||||||
Objects.requireNonNull(cb, "LnbCallback must not be null");
|
Objects.requireNonNull(cb, "LnbCallback must not be null");
|
||||||
if (mLnb != null) {
|
if (mLnb != null) {
|
||||||
mLnb.setCallback(executor, cb, this);
|
mLnb.setCallback(executor, cb, this);
|
||||||
return mLnb;
|
return mLnb;
|
||||||
}
|
}
|
||||||
if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_LNB) && mLnb != null) {
|
if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_LNB, mLnbLock)
|
||||||
|
&& mLnb != null) {
|
||||||
mLnb.setCallback(executor, cb, this);
|
mLnb.setCallback(executor, cb, this);
|
||||||
setLnb(mLnb);
|
setLnb(mLnb);
|
||||||
return mLnb;
|
return mLnb;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
} finally {
|
||||||
|
mLnbLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1483,6 +1641,8 @@ public class Tuner implements AutoCloseable {
|
|||||||
@Nullable
|
@Nullable
|
||||||
public Lnb openLnbByName(@NonNull String name, @CallbackExecutor @NonNull Executor executor,
|
public Lnb openLnbByName(@NonNull String name, @CallbackExecutor @NonNull Executor executor,
|
||||||
@NonNull LnbCallback cb) {
|
@NonNull LnbCallback cb) {
|
||||||
|
mLnbLock.lock();
|
||||||
|
try {
|
||||||
Objects.requireNonNull(name, "LNB name must not be null");
|
Objects.requireNonNull(name, "LNB name must not be null");
|
||||||
Objects.requireNonNull(executor, "executor must not be null");
|
Objects.requireNonNull(executor, "executor must not be null");
|
||||||
Objects.requireNonNull(cb, "LnbCallback must not be null");
|
Objects.requireNonNull(cb, "LnbCallback must not be null");
|
||||||
@@ -1497,6 +1657,9 @@ public class Tuner implements AutoCloseable {
|
|||||||
setLnb(mLnb);
|
setLnb(mLnb);
|
||||||
}
|
}
|
||||||
return mLnb;
|
return mLnb;
|
||||||
|
} finally {
|
||||||
|
mLnbLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean requestLnb() {
|
private boolean requestLnb() {
|
||||||
@@ -1518,10 +1681,15 @@ public class Tuner implements AutoCloseable {
|
|||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public TimeFilter openTimeFilter() {
|
public TimeFilter openTimeFilter() {
|
||||||
if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
|
mDemuxLock.lock();
|
||||||
|
try {
|
||||||
|
if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return nativeOpenTimeFilter();
|
return nativeOpenTimeFilter();
|
||||||
|
} finally {
|
||||||
|
mDemuxLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1532,10 +1700,15 @@ public class Tuner implements AutoCloseable {
|
|||||||
@RequiresPermission(android.Manifest.permission.ACCESS_TV_DESCRAMBLER)
|
@RequiresPermission(android.Manifest.permission.ACCESS_TV_DESCRAMBLER)
|
||||||
@Nullable
|
@Nullable
|
||||||
public Descrambler openDescrambler() {
|
public Descrambler openDescrambler() {
|
||||||
if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
|
mDemuxLock.lock();
|
||||||
|
try {
|
||||||
|
if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return requestDescrambler();
|
return requestDescrambler();
|
||||||
|
} finally {
|
||||||
|
mDemuxLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1553,14 +1726,19 @@ public class Tuner implements AutoCloseable {
|
|||||||
@BytesLong long bufferSize,
|
@BytesLong long bufferSize,
|
||||||
@CallbackExecutor @NonNull Executor executor,
|
@CallbackExecutor @NonNull Executor executor,
|
||||||
@NonNull OnRecordStatusChangedListener l) {
|
@NonNull OnRecordStatusChangedListener l) {
|
||||||
|
mDemuxLock.lock();
|
||||||
|
try {
|
||||||
Objects.requireNonNull(executor, "executor must not be null");
|
Objects.requireNonNull(executor, "executor must not be null");
|
||||||
Objects.requireNonNull(l, "OnRecordStatusChangedListener must not be null");
|
Objects.requireNonNull(l, "OnRecordStatusChangedListener must not be null");
|
||||||
if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
|
if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
DvrRecorder dvr = nativeOpenDvrRecorder(bufferSize);
|
DvrRecorder dvr = nativeOpenDvrRecorder(bufferSize);
|
||||||
dvr.setListener(executor, l);
|
dvr.setListener(executor, l);
|
||||||
return dvr;
|
return dvr;
|
||||||
|
} finally {
|
||||||
|
mDemuxLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1578,14 +1756,19 @@ public class Tuner implements AutoCloseable {
|
|||||||
@BytesLong long bufferSize,
|
@BytesLong long bufferSize,
|
||||||
@CallbackExecutor @NonNull Executor executor,
|
@CallbackExecutor @NonNull Executor executor,
|
||||||
@NonNull OnPlaybackStatusChangedListener l) {
|
@NonNull OnPlaybackStatusChangedListener l) {
|
||||||
|
mDemuxLock.lock();
|
||||||
|
try {
|
||||||
Objects.requireNonNull(executor, "executor must not be null");
|
Objects.requireNonNull(executor, "executor must not be null");
|
||||||
Objects.requireNonNull(l, "OnPlaybackStatusChangedListener must not be null");
|
Objects.requireNonNull(l, "OnPlaybackStatusChangedListener must not be null");
|
||||||
if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
|
if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
DvrPlayback dvr = nativeOpenDvrPlayback(bufferSize);
|
DvrPlayback dvr = nativeOpenDvrPlayback(bufferSize);
|
||||||
dvr.setListener(executor, l);
|
dvr.setListener(executor, l);
|
||||||
return dvr;
|
return dvr;
|
||||||
|
} finally {
|
||||||
|
mDemuxLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1602,6 +1785,8 @@ public class Tuner implements AutoCloseable {
|
|||||||
static public SharedFilter openSharedFilter(@NonNull Context context,
|
static public SharedFilter openSharedFilter(@NonNull Context context,
|
||||||
@NonNull String sharedFilterToken, @CallbackExecutor @NonNull Executor executor,
|
@NonNull String sharedFilterToken, @CallbackExecutor @NonNull Executor executor,
|
||||||
@NonNull SharedFilterCallback cb) {
|
@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(sharedFilterToken, "sharedFilterToken must not be null");
|
||||||
Objects.requireNonNull(executor, "executor must not be null");
|
Objects.requireNonNull(executor, "executor must not be null");
|
||||||
Objects.requireNonNull(cb, "SharedFilterCallback must not be null");
|
Objects.requireNonNull(cb, "SharedFilterCallback must not be null");
|
||||||
@@ -1665,22 +1850,28 @@ public class Tuner implements AutoCloseable {
|
|||||||
return granted;
|
return granted;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkResource(int resourceType) {
|
private boolean checkResource(int resourceType, ReentrantLock localLock) {
|
||||||
switch (resourceType) {
|
switch (resourceType) {
|
||||||
case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND: {
|
case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND: {
|
||||||
if (mFrontendHandle == null && !requestFrontend()) {
|
if (mFrontendHandle == null && !requestResource(resourceType, localLock)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case TunerResourceManager.TUNER_RESOURCE_TYPE_LNB: {
|
case TunerResourceManager.TUNER_RESOURCE_TYPE_LNB: {
|
||||||
if (mLnb == null && !requestLnb()) {
|
if (mLnb == null && !requestResource(resourceType, localLock)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX: {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -1691,24 +1882,91 @@ public class Tuner implements AutoCloseable {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkCiCamResource(int ciCamId) {
|
// Expected flow of how to use this function is:
|
||||||
if (mFrontendCiCamHandle == null && !requestFrontendCiCam(ciCamId)) {
|
// 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;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
} finally {
|
||||||
|
if (enableLockOperations) {
|
||||||
|
releaseTRMSLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* package */ void releaseLnb() {
|
/* package */ void releaseLnb() {
|
||||||
|
acquireTRMSLock("releaseLnb()");
|
||||||
|
mLnbLock.lock();
|
||||||
|
try {
|
||||||
if (mLnbHandle != null) {
|
if (mLnbHandle != null) {
|
||||||
// LNB handle can be null if it's opened by name.
|
// LNB handle can be null if it's opened by name.
|
||||||
mTunerResourceManager.releaseLnb(mLnbHandle, mClientId);
|
mTunerResourceManager.releaseLnb(mLnbHandle, mClientId);
|
||||||
mLnbHandle = null;
|
mLnbHandle = null;
|
||||||
}
|
}
|
||||||
mLnb = null;
|
mLnb = null;
|
||||||
|
} finally {
|
||||||
|
releaseTRMSLock();
|
||||||
|
mLnbLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @hide */
|
/** @hide */
|
||||||
public int getClientId() {
|
public int getClientId() {
|
||||||
return mClientId;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -319,6 +319,48 @@ public class TunerResourceManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grants the lock to the caller for public {@link Tuner} APIs
|
||||||
|
*
|
||||||
|
* <p>{@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().
|
||||||
|
*
|
||||||
|
* <p>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
|
||||||
|
*
|
||||||
|
* <p>This API must be called in pair with {@link #acquireLock(int, int)}
|
||||||
|
*
|
||||||
|
* <p>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.
|
* Requests a frontend resource.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -412,4 +412,34 @@ interface ITunerResourceManager {
|
|||||||
* @param resourceType The resource type to restore the map for.
|
* @param resourceType The resource type to restore the map for.
|
||||||
*/
|
*/
|
||||||
void restoreResourceMap(in int resourceType);
|
void restoreResourceMap(in int resourceType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grants the lock to the caller for public {@link Tuner} APIs
|
||||||
|
*
|
||||||
|
* <p>{@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().
|
||||||
|
*
|
||||||
|
* <p>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
|
||||||
|
*
|
||||||
|
* <p>This API must be called in pair with {@link #acquireLock(int, int)}
|
||||||
|
*
|
||||||
|
* <p>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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import android.media.tv.tunerresourcemanager.TunerResourceManager;
|
|||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
|
import android.os.SystemClock;
|
||||||
import android.util.IndentingPrintWriter;
|
import android.util.IndentingPrintWriter;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Slog;
|
import android.util.Slog;
|
||||||
@@ -52,6 +53,9 @@ import java.util.HashSet;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
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.
|
* 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;
|
public static final int INVALID_CLIENT_ID = -1;
|
||||||
private static final int MAX_CLIENT_PRIORITY = 1000;
|
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
|
// Map of the registered client profiles
|
||||||
private Map<Integer, ClientProfile> mClientProfiles = new HashMap<>();
|
private Map<Integer, ClientProfile> mClientProfiles = new HashMap<>();
|
||||||
@@ -94,6 +100,12 @@ public class TunerResourceManagerService extends SystemService implements IBinde
|
|||||||
// Used to synchronize the access to the service.
|
// Used to synchronize the access to the service.
|
||||||
private final Object mLock = new Object();
|
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) {
|
public TunerResourceManagerService(@Nullable Context context) {
|
||||||
super(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
|
@Override
|
||||||
protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
|
protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
|
||||||
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
|
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
|
||||||
@@ -1194,6 +1220,187 @@ public class TunerResourceManagerService extends SystemService implements IBinde
|
|||||||
return true;
|
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
|
@VisibleForTesting
|
||||||
protected class ResourcesReclaimListenerRecord implements IBinder.DeathRecipient {
|
protected class ResourcesReclaimListenerRecord implements IBinder.DeathRecipient {
|
||||||
private final IResourcesReclaimListener mListener;
|
private final IResourcesReclaimListener mListener;
|
||||||
@@ -1206,11 +1413,16 @@ public class TunerResourceManagerService extends SystemService implements IBinde
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void binderDied() {
|
public void binderDied() {
|
||||||
|
try {
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
if (checkClientExists(mClientId)) {
|
if (checkClientExists(mClientId)) {
|
||||||
removeClientProfile(mClientId);
|
removeClientProfile(mClientId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
// reset the tuner API lock
|
||||||
|
releaseLockInternal(mClientId, TRMS_LOCK_TIMEOUT, true, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getId() {
|
public int getId() {
|
||||||
@@ -1247,6 +1459,13 @@ public class TunerResourceManagerService extends SystemService implements IBinde
|
|||||||
protected boolean reclaimResource(int reclaimingClientId,
|
protected boolean reclaimResource(int reclaimingClientId,
|
||||||
@TunerResourceManager.TunerResourceType int resourceType) {
|
@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
|
// Reclaim all the resources of the share owners of the frontend that is used by the current
|
||||||
// resource reclaimed client.
|
// resource reclaimed client.
|
||||||
ClientProfile profile = getClientProfile(reclaimingClientId);
|
ClientProfile profile = getClientProfile(reclaimingClientId);
|
||||||
|
|||||||
Reference in New Issue
Block a user