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.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<Integer> 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<Integer, WeakReference<Descrambler>> d : mDescramblers.entrySet()) {
|
||||
@@ -543,6 +585,7 @@ public class Tuner implements AutoCloseable {
|
||||
mDescramblers.clear();
|
||||
}
|
||||
}
|
||||
|
||||
synchronized (mFilters) {
|
||||
if (!mFilters.isEmpty()) {
|
||||
for (WeakReference<Filter> 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<Filter> weakFilter = new WeakReference<Filter>(filter);
|
||||
mFilters.add(weakFilter);
|
||||
if (mFilters.size() > FILTER_CLEANUP_THRESHOLD) {
|
||||
Iterator<WeakReference<Filter>> iterator = mFilters.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
WeakReference<Filter> 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<Filter> weakFilter = new WeakReference<Filter>(filter);
|
||||
mFilters.add(weakFilter);
|
||||
if (mFilters.size() > FILTER_CLEANUP_THRESHOLD) {
|
||||
Iterator<WeakReference<Filter>> iterator = mFilters.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
WeakReference<Filter> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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
|
||||
*
|
||||
* <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.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<Integer, ClientProfile> 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);
|
||||
|
||||
Reference in New Issue
Block a user