Merge "Fix race condition betweeen Tuner APIs and with onReclaimResources"

This commit is contained in:
TreeHugger Robot
2021-12-08 05:13:49 +00:00
committed by Android (Google) Code Review
4 changed files with 763 additions and 214 deletions

View File

@@ -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);
}
}

View File

@@ -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.
*

View File

@@ -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);
}

View File

@@ -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);