Enhance resource sharing and enable ownership transfer
New public APIs
- Tuner.closeFrontend()
- Enables TIS app to continue using other resource while changing
frontend resource
- close & release Frontend resource when called on the owner, while
it unshares Frontend resource when called on the sharee.
- Lnb.addCallback(Callback, Executor) and Lnb.removeCallback(Callback)
- Enables TIS app to receive callback from the sharee
- Also helps with the ownership transfer
- Tuner.transferOwner(Tuner newOwner)
- Transfers the ownership of Frontend, CiCam, and Lnb resource
Additionally, added the following:
- Call nativeSetLnb() in requestFrontend() in case mLnb resource
is already held. (this use case becomes a possibility now that we
support Tuner.closeFrontend())
Bug: 192010866
Test: cts.TunerTest#testTransferOwner, testLnbAddAndRemoveSharee
Change-Id: I4c39c3726f0dd7bd1c153975ad01393ff2773005
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 5a6ea89..ee2dfd9 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -6338,7 +6338,9 @@
}
public class Lnb implements java.lang.AutoCloseable {
+ method public void addCallback(@NonNull android.media.tv.tuner.LnbCallback, @NonNull java.util.concurrent.Executor);
method public void close();
+ method public boolean removeCallback(@NonNull android.media.tv.tuner.LnbCallback);
method public int sendDiseqcMessage(@NonNull byte[]);
method public int setSatellitePosition(int);
method public int setTone(int);
@@ -6375,6 +6377,7 @@
method public void clearOnTuneEventListener();
method public void clearResourceLostListener();
method public void close();
+ method public void closeFrontend();
method public int connectCiCam(int);
method public int connectFrontendToCiCam(int);
method public int disconnectCiCam();
@@ -6400,6 +6403,7 @@
method public void setOnTuneEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.frontend.OnTuneEventListener);
method public void setResourceLostListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.Tuner.OnResourceLostListener);
method public void shareFrontendFromTuner(@NonNull android.media.tv.tuner.Tuner);
+ method public int transferOwner(@NonNull android.media.tv.tuner.Tuner);
method public int tune(@NonNull android.media.tv.tuner.frontend.FrontendSettings);
method @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public void updateResourcePriority(int, int);
field public static final int INVALID_AV_SYNC_ID = -1; // 0xffffffff
diff --git a/media/java/android/media/tv/tuner/Lnb.java b/media/java/android/media/tv/tuner/Lnb.java
index 6a6a22c..50a2083 100644
--- a/media/java/android/media/tv/tuner/Lnb.java
+++ b/media/java/android/media/tv/tuner/Lnb.java
@@ -28,6 +28,9 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.Executor;
/**
@@ -145,9 +148,9 @@
private static final String TAG = "Lnb";
- LnbCallback mCallback;
- Executor mExecutor;
- Tuner mTuner;
+ Map<LnbCallback, Executor> mCallbackMap =
+ new HashMap<LnbCallback, Executor>();
+ Tuner mOwner;
private final Object mCallbackLock = new Object();
@@ -164,38 +167,82 @@
private Lnb() {}
- void setCallback(Executor executor, @Nullable LnbCallback callback, Tuner tuner) {
+ void setCallbackAndOwner(Executor executor, @Nullable LnbCallback callback, Tuner tuner) {
synchronized (mCallbackLock) {
- mCallback = callback;
- mExecutor = executor;
- mTuner = tuner;
+ if (callback != null && executor != null) {
+ addCallback(callback, executor);
+ }
+ }
+ setOwner(tuner);
+ }
+
+ /**
+ * Adds LnbCallback
+ *
+ * @param callback the callback to receive notifications from LNB.
+ * @param executor the executor on which callback will be invoked. Cannot be null.
+ */
+ public void addCallback(@NonNull LnbCallback callback, @NonNull Executor executor) {
+ Objects.requireNonNull(callback, "callback must not be null");
+ Objects.requireNonNull(executor, "executor must not be null");
+ synchronized (mCallbackLock) {
+ mCallbackMap.put(callback, executor);
+ }
+ }
+
+ /**
+ * Removes LnbCallback
+ *
+ * @param callback the callback be removed for callback
+ *
+ * @return {@code true} when successful. {@code false} otherwise.
+ */
+ public boolean removeCallback(@NonNull LnbCallback callback) {
+ Objects.requireNonNull(callback, "callback must not be null");
+ synchronized (mCallbackLock) {
+ boolean result = (mCallbackMap.remove(callback) != null);
+ return result;
+ }
+ }
+
+ // allow owner transfer independent of whether callback is registered or not
+ /* package */ void setOwner(@NonNull Tuner newOwner) {
+ Objects.requireNonNull(newOwner, "newOwner must not be null");
+ synchronized (mLock) {
+ mOwner = newOwner;
}
}
private void onEvent(int eventType) {
synchronized (mCallbackLock) {
- if (mExecutor != null && mCallback != null) {
- mExecutor.execute(() -> {
- synchronized (mCallbackLock) {
- if (mCallback != null) {
- mCallback.onEvent(eventType);
+ for (LnbCallback callback : mCallbackMap.keySet()) {
+ Executor executor = mCallbackMap.get(callback);
+ if (callback != null && executor != null) {
+ executor.execute(() -> {
+ synchronized (mCallbackLock) {
+ if (callback != null) {
+ callback.onEvent(eventType);
+ }
}
- }
- });
+ });
+ }
}
}
}
private void onDiseqcMessage(byte[] diseqcMessage) {
synchronized (mCallbackLock) {
- if (mExecutor != null && mCallback != null) {
- mExecutor.execute(() -> {
- synchronized (mCallbackLock) {
- if (mCallback != null) {
- mCallback.onDiseqcMessage(diseqcMessage);
+ for (LnbCallback callback : mCallbackMap.keySet()) {
+ Executor executor = mCallbackMap.get(callback);
+ if (callback != null && executor != null) {
+ executor.execute(() -> {
+ synchronized (mCallbackLock) {
+ if (callback != null) {
+ callback.onDiseqcMessage(diseqcMessage);
+ }
}
- }
- });
+ });
+ }
}
}
}
@@ -279,7 +326,11 @@
TunerUtils.throwExceptionForResult(res, "Failed to close LNB");
} else {
mIsClosed = true;
- mTuner.releaseLnb();
+ if (mOwner != null) {
+ mOwner.releaseLnb();
+ mOwner = null;
+ }
+ mCallbackMap.clear();
}
}
}
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 255b391b..c06d0fe 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -240,7 +240,7 @@
private static final String TAG = "MediaTvTuner";
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final int MSG_RESOURCE_LOST = 1;
private static final int MSG_ON_FILTER_EVENT = 2;
@@ -249,7 +249,6 @@
private static final int FILTER_CLEANUP_THRESHOLD = 256;
-
/** @hide */
@IntDef(prefix = "DVR_TYPE_", value = {DVR_TYPE_RECORD, DVR_TYPE_PLAYBACK})
@Retention(RetentionPolicy.SOURCE)
@@ -454,6 +453,260 @@
}
/**
+ * Transfers the ownership of shared frontend and its associated resources.
+ *
+ * @param newOwner the Tuner instance to be the new owner.
+ *
+ * @return result status of tune operation.
+ */
+ public int transferOwner(@NonNull Tuner newOwner) {
+ acquireTRMSLock("transferOwner()");
+ mFrontendLock.lock();
+ mFrontendCiCamLock.lock();
+ mLnbLock.lock();
+ try {
+
+ if (!isFrontendOwner() || !isNewOwnerQualifiedForTransfer(newOwner)) {
+ return RESULT_INVALID_STATE;
+ }
+
+ int res = transferFeOwner(newOwner);
+ if (res != RESULT_SUCCESS) {
+ return res;
+ }
+
+ res = transferCiCamOwner(newOwner);
+ if (res != RESULT_SUCCESS) {
+ return res;
+ }
+
+ res = transferLnbOwner(newOwner);
+ if (res != RESULT_SUCCESS) {
+ return res;
+ }
+ } finally {
+ mFrontendLock.unlock();
+ mFrontendCiCamLock.unlock();
+ mLnbLock.unlock();
+ releaseTRMSLock();
+ }
+ return RESULT_SUCCESS;
+ }
+
+ /**
+ * Resets or copies Frontend related settings.
+ */
+ private void replicateFrontendSettings(@Nullable Tuner src) {
+ mFrontendLock.lock();
+ try {
+ if (src == null) {
+ if (DEBUG) {
+ Log.d(TAG, "resetting Frontend params for " + mClientId);
+ }
+ mFrontend = null;
+ mFrontendHandle = null;
+ mFrontendInfo = null;
+ mFrontendType = FrontendSettings.TYPE_UNDEFINED;
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "copying Frontend params from " + src.mClientId
+ + " to " + mClientId);
+ }
+ mFrontend = src.mFrontend;
+ mFrontendHandle = src.mFrontendHandle;
+ mFrontendInfo = src.mFrontendInfo;
+ mFrontendType = src.mFrontendType;
+ }
+ } finally {
+ mFrontendLock.unlock();
+ }
+ }
+
+ /**
+ * Sets the frontend owner. mFeOwnerTuner should be null for the owner Tuner instance.
+ */
+ private void setFrontendOwner(Tuner owner) {
+ mFrontendLock.lock();
+ try {
+ mFeOwnerTuner = owner;
+ } finally {
+ mFrontendLock.unlock();
+ }
+ }
+
+ /**
+ * Resets or copies the CiCam related settings.
+ */
+ private void replicateCiCamSettings(@Nullable Tuner src) {
+ mFrontendCiCamLock.lock();
+ try {
+ if (src == null) {
+ if (DEBUG) {
+ Log.d(TAG, "resetting CiCamParams: " + mClientId);
+ }
+ mFrontendCiCamHandle = null;
+ mFrontendCiCamId = null;
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "copying CiCamParams from " + src.mClientId + " to " + mClientId);
+ Log.d(TAG, "mFrontendCiCamHandle:" + src.mFrontendCiCamHandle + ", "
+ + "mFrontendCiCamId:" + src.mFrontendCiCamId);
+ }
+ mFrontendCiCamHandle = src.mFrontendCiCamHandle;
+ mFrontendCiCamId = src.mFrontendCiCamId;
+ }
+ } finally {
+ mFrontendCiCamLock.unlock();
+ }
+ }
+
+ /**
+ * Resets or copies Lnb related settings.
+ */
+ private void replicateLnbSettings(@Nullable Tuner src) {
+ mLnbLock.lock();
+ try {
+ if (src == null) {
+ if (DEBUG) {
+ Log.d(TAG, "resetting Lnb params");
+ }
+ mLnb = null;
+ mLnbHandle = null;
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "copying Lnb params from " + src.mClientId + " to " + mClientId);
+ }
+ mLnb = src.mLnb;
+ mLnbHandle = src.mLnbHandle;
+ }
+ } finally {
+ mLnbLock.unlock();
+ }
+ }
+
+ /**
+ * Checks if it is a frontend resource owner.
+ * Proper mutex must be held prior to calling this.
+ */
+ private boolean isFrontendOwner() {
+ boolean notAnOwner = (mFeOwnerTuner != null);
+ if (notAnOwner) {
+ Log.e(TAG, "transferOwner() - cannot be called on the non-owner");
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Checks if the newOwner is qualified.
+ * Proper mutex must be held prior to calling this.
+ */
+ private boolean isNewOwnerQualifiedForTransfer(@NonNull Tuner newOwner) {
+ // new owner must be the current sharee
+ boolean newOwnerIsTheCurrentSharee = (newOwner.mFeOwnerTuner == this)
+ && (newOwner.mFrontendHandle.equals(mFrontendHandle));
+ if (!newOwnerIsTheCurrentSharee) {
+ Log.e(TAG, "transferOwner() - new owner must be the current sharee");
+ return false;
+ }
+
+ // new owner must not be holding any of the to-be-shared resources
+ boolean newOwnerAlreadyHoldsToBeSharedResource =
+ (newOwner.mFrontendCiCamHandle != null || newOwner.mLnb != null);
+ if (newOwnerAlreadyHoldsToBeSharedResource) {
+ Log.e(TAG, "transferOwner() - new owner cannot be holding CiCam"
+ + " nor Lnb resource");
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Transfers the ownership of the already held frontend resource.
+ * Proper mutex must be held prior to calling this.
+ */
+ private int transferFeOwner(@NonNull Tuner newOwner) {
+ // handle native resource first
+ newOwner.nativeUpdateFrontend(getNativeContext());
+ nativeUpdateFrontend(0);
+
+ // transfer frontend related settings
+ newOwner.replicateFrontendSettings(this);
+
+ // transfer the frontend owner info
+ setFrontendOwner(newOwner);
+ newOwner.setFrontendOwner(null);
+
+ // handle TRM
+ if (mTunerResourceManager.transferOwner(
+ TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND,
+ mClientId, newOwner.mClientId)) {
+ return RESULT_SUCCESS;
+ } else {
+ return RESULT_UNKNOWN_ERROR;
+ }
+ }
+
+ /**
+ * Transfers the ownership of CiCam resource.
+ * This is a no-op if the CiCam resource is not held.
+ * Proper mutex must be held prior to calling this.
+ */
+ private int transferCiCamOwner(Tuner newOwner) {
+ boolean notAnOwner = (mFrontendCiCamHandle == null);
+ if (notAnOwner) {
+ // There is nothing to do here if there is no CiCam
+ return RESULT_SUCCESS;
+ }
+
+ // no need to handle at native level
+
+ // transfer the CiCam info at Tuner level
+ newOwner.replicateCiCamSettings(this);
+ replicateCiCamSettings(null);
+
+ // handle TRM
+ if (mTunerResourceManager.transferOwner(
+ TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM,
+ mClientId, newOwner.mClientId)) {
+ return RESULT_SUCCESS;
+ } else {
+ return RESULT_UNKNOWN_ERROR;
+ }
+ }
+
+ /**
+ * Transfers the ownership of Lnb resource.
+ * This is a no-op if the Lnb resource is not held.
+ * Proper mutex must be held prior to calling this.
+ */
+ private int transferLnbOwner(Tuner newOwner) {
+ boolean notAnOwner = (mLnb == null);
+ if (notAnOwner) {
+ // There is nothing to do here if there is no Lnb
+ return RESULT_SUCCESS;
+ }
+
+ // no need to handle at native level
+
+ // set the new owner
+ mLnb.setOwner(newOwner);
+
+ newOwner.replicateLnbSettings(this);
+ replicateLnbSettings(null);
+
+ // handle TRM
+ if (mTunerResourceManager.transferOwner(
+ TunerResourceManager.TUNER_RESOURCE_TYPE_LNB,
+ mClientId, newOwner.mClientId)) {
+ return RESULT_SUCCESS;
+ } else {
+ return RESULT_UNKNOWN_ERROR;
+ }
+ }
+
+ /**
* Updates client priority with an arbitrary value along with a nice value.
*
* <p>Tuner resource manager (TRM) uses the client priority value to decide whether it is able
@@ -546,59 +799,114 @@
}
}
+ /**
+ * Either unshares the frontend resource (for sharee) or release Frontend (for owner)
+ */
+ public void closeFrontend() {
+ acquireTRMSLock("closeFrontend()");
+ try {
+ releaseFrontend();
+ } finally {
+ releaseTRMSLock();
+ }
+ }
+
+ /**
+ * Releases frontend resource for the owner. Unshares frontend resource for the sharee.
+ */
private void releaseFrontend() {
+ if (DEBUG) {
+ Log.d(TAG, "Tuner#releaseFrontend");
+ }
mFrontendLock.lock();
try {
if (mFrontendHandle != null) {
+ if (DEBUG) {
+ Log.d(TAG, "mFrontendHandle not null");
+ }
if (mFeOwnerTuner != null) {
+ if (DEBUG) {
+ Log.d(TAG, "mFeOwnerTuner not null - sharee");
+ }
// unregister self from the Frontend callback
mFeOwnerTuner.unregisterFrontendCallbackListener(this);
mFeOwnerTuner = null;
+ nativeUnshareFrontend();
} else {
+ if (DEBUG) {
+ Log.d(TAG, "mFeOwnerTuner null - owner");
+ }
// close resource as owner
int res = nativeCloseFrontend(mFrontendHandle);
if (res != Tuner.RESULT_SUCCESS) {
TunerUtils.throwExceptionForResult(res, "failed to close frontend");
}
- mTunerResourceManager.releaseFrontend(mFrontendHandle, mClientId);
}
+ if (DEBUG) {
+ Log.d(TAG, "call TRM#releaseFrontend :" + mFrontendHandle + ", " + mClientId);
+ }
+ mTunerResourceManager.releaseFrontend(mFrontendHandle, mClientId);
FrameworkStatsLog
.write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__UNKNOWN);
- mFrontendHandle = null;
- mFrontend = null;
+ replicateFrontendSettings(null);
}
} finally {
mFrontendLock.unlock();
}
}
+ /**
+ * Releases CiCam resource if held. No-op otherwise.
+ */
+ private void releaseCiCam() {
+ mFrontendCiCamLock.lock();
+ try {
+ if (mFrontendCiCamHandle != null) {
+ if (DEBUG) {
+ Log.d(TAG, "unlinking CiCam : " + mFrontendCiCamHandle + " for " + mClientId);
+ }
+ int result = nativeUnlinkCiCam(mFrontendCiCamId);
+ if (result == RESULT_SUCCESS) {
+ mTunerResourceManager.releaseCiCam(mFrontendCiCamHandle, mClientId);
+ replicateCiCamSettings(null);
+ } else {
+ Log.e(TAG, "nativeUnlinkCiCam(" + mFrontendCiCamHandle + ") for mClientId:"
+ + mClientId + "failed with result:" + result);
+ }
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "NOT unlinking CiCam : " + mClientId);
+ }
+ }
+ } finally {
+ mFrontendCiCamLock.unlock();
+ }
+ }
+
private void releaseAll() {
+ // release CiCam before frontend because frontend handle is needed to unlink CiCam
+ releaseCiCam();
+
releaseFrontend();
mLnbLock.lock();
try {
// mLnb will be non-null only for owner tuner
if (mLnb != null) {
+ if (DEBUG) {
+ Log.d(TAG, "calling mLnb.close() : " + mClientId);
+ }
mLnb.close();
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "NOT calling mLnb.close() : " + mClientId);
+ }
}
} 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()) {
@@ -668,8 +976,11 @@
*/
private native Frontend nativeOpenFrontendByHandle(int handle);
private native int nativeShareFrontend(int id);
+ private native int nativeUnshareFrontend();
private native void nativeRegisterFeCbListener(long nativeContext);
private native void nativeUnregisterFeCbListener(long nativeContext);
+ // nativeUpdateFrontend must be called on the new owner first
+ private native void nativeUpdateFrontend(long nativeContext);
@Result
private native int nativeTune(int type, FrontendSettings settings);
private native int nativeStopTune();
@@ -993,6 +1304,21 @@
mFrontendHandle = feHandle[0];
mFrontend = nativeOpenFrontendByHandle(mFrontendHandle);
}
+
+ // For satellite type, set Lnb if valid handle exists.
+ // This is necessary as now that we support closeFrontend().
+ if (mFrontendType == FrontendSettings.TYPE_DVBS
+ || mFrontendType == FrontendSettings.TYPE_ISDBS
+ || mFrontendType == FrontendSettings.TYPE_ISDBS3) {
+ mLnbLock.lock();
+ try {
+ if (mLnbHandle != null && mLnb != null) {
+ nativeSetLnb(mLnb);
+ }
+ } finally {
+ mLnbLock.unlock();
+ }
+ }
return granted;
}
@@ -1643,12 +1969,12 @@
Objects.requireNonNull(executor, "executor must not be null");
Objects.requireNonNull(cb, "LnbCallback must not be null");
if (mLnb != null) {
- mLnb.setCallback(executor, cb, this);
+ mLnb.setCallbackAndOwner(executor, cb, this);
return mLnb;
}
if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_LNB, mLnbLock)
&& mLnb != null) {
- mLnb.setCallback(executor, cb, this);
+ mLnb.setCallbackAndOwner(executor, cb, this);
setLnb(mLnb);
return mLnb;
}
@@ -1682,7 +2008,7 @@
mLnbHandle = null;
}
mLnb = newLnb;
- mLnb.setCallback(executor, cb, this);
+ mLnb.setCallbackAndOwner(executor, cb, this);
setLnb(mLnb);
}
return mLnb;
@@ -1968,8 +2294,15 @@
try {
if (mLnbHandle != null) {
// LNB handle can be null if it's opened by name.
+ if (DEBUG) {
+ Log.d(TAG, "releasing Lnb");
+ }
mTunerResourceManager.releaseLnb(mLnbHandle, mClientId);
mLnbHandle = null;
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "NOT releasing Lnb because mLnbHandle is null");
+ }
}
mLnb = null;
} finally {
diff --git a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
index fe611c7..e978404 100644
--- a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
+++ b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
@@ -415,6 +415,25 @@
}
/**
+ * Transfers the ownership of shared resource.
+ *
+ * <p><strong>Note:</strong> Only the existing frontend sharee can be the new owner.
+ *
+ * @param resourceType the type of the resource to transfer the ownership for.
+ * @param currentOwnerId the id of the current owner client.
+ * @param newOwnerId the id of the new owner client.
+ *
+ * @return true if successful and false otherwise.
+ */
+ public boolean transferOwner(int resourceType, int currentOwnerId, int newOwnerId) {
+ try {
+ return mService.transferOwner(resourceType, currentOwnerId, newOwnerId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Requests a Tuner Demux resource.
*
* <p>There are three possible scenarios:
diff --git a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
index 5f35820..e9ad668 100644
--- a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
+++ b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
@@ -177,6 +177,19 @@
void shareFrontend(in int selfClientId, in int targetClientId);
/*
+ * Transfers the ownership of the shared resource.
+ *
+ * <p><strong>Note:</strong> Only the existing frontend sharee can be the new owner.
+ *
+ * @param resourceType the type of resource to transfer the ownership for.
+ * @param currentOwnerId the id of the current owner client.
+ * @param newOwnerId the id of the new owner client.
+ *
+ * @return true if successful. false otherwise.
+ */
+ boolean transferOwner(in int resourceType, in int currentOwnerId, in int newOwnerId);
+
+ /*
* This API is used by the Tuner framework to request an available demux from the TunerHAL.
*
* <p>There are three possible scenarios:
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index c230df3..38a0db4 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -959,7 +959,8 @@
void FrontendClientCallbackImpl::addCallbackListener(JTuner* jtuner, jweak listener) {
JNIEnv *env = AndroidRuntime::getJNIEnv();
jweak listenerRef = env->NewWeakGlobalRef(listener);
- ALOGV("addCallbackListener() with listener:%p and ref:%p @%p", listener, listenerRef, this);
+ ALOGV("addCallbackListener() with listener:%p and ref:%p @%p",
+ listener, listenerRef, this);
std::scoped_lock<std::mutex> lock(mMutex);
mListenersMap[jtuner] = listenerRef;
}
@@ -1314,18 +1315,43 @@
return (int)Result::SUCCESS;
}
+int JTuner::unshareFrontend() {
+ if (mFeClient != nullptr) {
+ ALOGE("Cannot unshare frontend because this session is already holding %d"
+ " as an owner instead of as a sharee", mFeClient->getId());
+ return (int)Result::INVALID_STATE;
+ }
+
+ mSharedFeId = (int)Constant::INVALID_FRONTEND_ID;
+ return (int)Result::SUCCESS;
+}
+
void JTuner::registerFeCbListener(JTuner* jtuner) {
+ ALOGV("registerFeCbListener: %p", jtuner);
if (mFeClientCb != nullptr && jtuner != nullptr) {
mFeClientCb->addCallbackListener(jtuner, jtuner->getObject());
}
}
void JTuner::unregisterFeCbListener(JTuner* jtuner) {
+ ALOGV("unregisterFeCbListener: %p", jtuner);
if (mFeClientCb != nullptr && jtuner != nullptr) {
mFeClientCb->removeCallbackListener(jtuner);
}
}
+void JTuner::updateFrontend(JTuner* jtuner) {
+ if (jtuner == nullptr) {
+ ALOGV("JTuner::updateFrontend(null) called for previous owner: %p", this);
+ mFeClient = nullptr;
+ mFeClientCb = nullptr;
+ } else {
+ ALOGV("JTuner::updateFrontend(%p) called for new owner: %p", jtuner, this);
+ mFeClient = jtuner->mFeClient;
+ mFeClientCb = jtuner->mFeClientCb;
+ }
+}
+
jobject JTuner::getAnalogFrontendCaps(JNIEnv *env, FrontendCapabilities &caps) {
jclass clazz = env->FindClass("android/media/tv/tuner/frontend/AnalogFrontendCapabilities");
jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(II)V");
@@ -3245,6 +3271,12 @@
return tuner->shareFrontend(id);
}
+static int android_media_tv_Tuner_unshare_frontend(
+ JNIEnv *env, jobject thiz) {
+ sp<JTuner> tuner = getTuner(env, thiz);
+ return tuner->unshareFrontend();
+}
+
static void android_media_tv_Tuner_register_fe_cb_listener(
JNIEnv *env, jobject thiz, jlong shareeJTuner) {
sp<JTuner> tuner = getTuner(env, thiz);
@@ -3259,6 +3291,17 @@
tuner->unregisterFeCbListener(jtuner);
}
+static void android_media_tv_Tuner_update_frontend(JNIEnv *env, jobject thiz, jlong jtunerPtr) {
+ sp<JTuner> tuner = getTuner(env, thiz);
+ JTuner *jtuner;
+ if (jtunerPtr == 0) {
+ jtuner = nullptr;
+ } else {
+ jtuner = (JTuner *) jtunerPtr;
+ }
+ tuner->updateFrontend(jtuner);
+}
+
static int android_media_tv_Tuner_tune(JNIEnv *env, jobject thiz, jint type, jobject settings) {
sp<JTuner> tuner = getTuner(env, thiz);
FrontendSettings setting = getFrontendSettings(env, type, settings);
@@ -4432,10 +4475,14 @@
(void *)android_media_tv_Tuner_open_frontend_by_handle },
{ "nativeShareFrontend", "(I)I",
(void *)android_media_tv_Tuner_share_frontend },
+ { "nativeUnshareFrontend", "()I",
+ (void *)android_media_tv_Tuner_unshare_frontend },
{ "nativeRegisterFeCbListener", "(J)V",
(void*)android_media_tv_Tuner_register_fe_cb_listener },
{ "nativeUnregisterFeCbListener", "(J)V",
(void*)android_media_tv_Tuner_unregister_fe_cb_listener },
+ { "nativeUpdateFrontend", "(J)V",
+ (void*)android_media_tv_Tuner_update_frontend },
{ "nativeTune", "(ILandroid/media/tv/tuner/frontend/FrontendSettings;)I",
(void *)android_media_tv_Tuner_tune },
{ "nativeStopTune", "()I", (void *)android_media_tv_Tuner_stop_tune },
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index 06e2492..7195821 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -178,8 +178,10 @@
jobject getFrontendIds();
jobject openFrontendByHandle(int feHandle);
int shareFrontend(int feId);
+ int unshareFrontend();
void registerFeCbListener(JTuner* jtuner);
void unregisterFeCbListener(JTuner* jtuner);
+ void updateFrontend(JTuner* jtuner);
jint closeFrontendById(int id);
jobject getFrontendInfo(int id);
int tune(const FrontendSettings& settings);
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index e508260..9e4c0234 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -288,6 +288,23 @@
}
@Override
+ public boolean transferOwner(int resourceType, int currentOwnerId, int newOwnerId) {
+ enforceTunerAccessPermission("transferOwner");
+ enforceTrmAccessPermission("transferOwner");
+ synchronized (mLock) {
+ if (!checkClientExists(currentOwnerId)) {
+ Slog.e(TAG, "currentOwnerId:" + currentOwnerId + " does not exit");
+ return false;
+ }
+ if (!checkClientExists(newOwnerId)) {
+ Slog.e(TAG, "newOwnerId:" + newOwnerId + " does not exit");
+ return false;
+ }
+ return transferOwnerInternal(resourceType, currentOwnerId, newOwnerId);
+ }
+ }
+
+ @Override
public boolean requestDemux(@NonNull TunerDemuxRequest request,
@NonNull int[] demuxHandle) throws RemoteException {
enforceTunerAccessPermission("requestDemux");
@@ -387,7 +404,11 @@
if (fe == null) {
throw new RemoteException("Releasing frontend does not exist.");
}
- if (fe.getOwnerClientId() != clientId) {
+ int ownerClientId = fe.getOwnerClientId();
+ ClientProfile ownerProfile = getClientProfile(ownerClientId);
+ if (ownerClientId != clientId
+ && (ownerProfile != null
+ && !ownerProfile.getShareFeClientIds().contains(clientId))) {
throw new RemoteException(
"Client is not the current owner of the releasing fe.");
}
@@ -969,6 +990,83 @@
getClientProfile(targetClientId).shareFrontend(selfClientId);
}
+ private boolean transferFeOwner(int currentOwnerId, int newOwnerId) {
+ ClientProfile currentOwnerProfile = getClientProfile(currentOwnerId);
+ ClientProfile newOwnerProfile = getClientProfile(newOwnerId);
+ // change the owner of all the inUse frontend
+ newOwnerProfile.shareFrontend(currentOwnerId);
+ currentOwnerProfile.stopSharingFrontend(newOwnerId);
+ for (int inUseHandle : newOwnerProfile.getInUseFrontendHandles()) {
+ getFrontendResource(inUseHandle).setOwner(newOwnerId);
+ }
+ // double check there is no other resources tied to the previous owner
+ for (int inUseHandle : currentOwnerProfile.getInUseFrontendHandles()) {
+ int ownerId = getFrontendResource(inUseHandle).getOwnerClientId();
+ if (ownerId != newOwnerId) {
+ Slog.e(TAG, "something is wrong in transferFeOwner:" + inUseHandle
+ + ", " + ownerId + ", " + newOwnerId);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean transferFeCiCamOwner(int currentOwnerId, int newOwnerId) {
+ ClientProfile currentOwnerProfile = getClientProfile(currentOwnerId);
+ ClientProfile newOwnerProfile = getClientProfile(newOwnerId);
+
+ // link ciCamId to the new profile
+ int ciCamId = currentOwnerProfile.getInUseCiCamId();
+ newOwnerProfile.useCiCam(ciCamId);
+
+ // set the new owner Id
+ CiCamResource ciCam = getCiCamResource(ciCamId);
+ ciCam.setOwner(newOwnerId);
+
+ // unlink cicam resource from the original owner profile
+ currentOwnerProfile.releaseCiCam();
+ return true;
+ }
+
+ private boolean transferLnbOwner(int currentOwnerId, int newOwnerId) {
+ ClientProfile currentOwnerProfile = getClientProfile(currentOwnerId);
+ ClientProfile newOwnerProfile = getClientProfile(newOwnerId);
+
+ Set<Integer> inUseLnbHandles = new HashSet<>();
+ for (Integer lnbHandle : currentOwnerProfile.getInUseLnbHandles()) {
+ // link lnb handle to the new profile
+ newOwnerProfile.useLnb(lnbHandle);
+
+ // set new owner Id
+ LnbResource lnb = getLnbResource(lnbHandle);
+ lnb.setOwner(newOwnerId);
+
+ inUseLnbHandles.add(lnbHandle);
+ }
+
+ // unlink lnb handles from the original owner
+ for (Integer lnbHandle : inUseLnbHandles) {
+ currentOwnerProfile.releaseLnb(lnbHandle);
+ }
+
+ return true;
+ }
+
+ @VisibleForTesting
+ protected boolean transferOwnerInternal(int resourceType, int currentOwnerId, int newOwnerId) {
+ switch (resourceType) {
+ case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND:
+ return transferFeOwner(currentOwnerId, newOwnerId);
+ case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM:
+ return transferFeCiCamOwner(currentOwnerId, newOwnerId);
+ case TunerResourceManager.TUNER_RESOURCE_TYPE_LNB:
+ return transferLnbOwner(currentOwnerId, newOwnerId);
+ default:
+ Slog.e(TAG, "transferOwnerInternal. unsupported resourceType: " + resourceType);
+ return false;
+ }
+ }
+
@VisibleForTesting
protected boolean requestLnbInternal(TunerLnbRequest request, int[] lnbHandle) {
if (DEBUG) {