Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b6580b4a authored by Kensuke Miyagi's avatar Kensuke Miyagi
Browse files

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
parent 238f9d57
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -6338,7 +6338,9 @@ package android.media.tv.tuner {
  }
  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 @@ package android.media.tv.tuner {
    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 @@ package android.media.tv.tuner {
    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
+73 −22
Original line number Diff line number Diff line
@@ -28,6 +28,9 @@ import android.media.tv.tuner.Tuner.Result;

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 @@ public class Lnb implements AutoCloseable {

    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,41 +167,85 @@ public class Lnb implements AutoCloseable {

    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(() -> {
            for (LnbCallback callback : mCallbackMap.keySet()) {
                Executor executor = mCallbackMap.get(callback);
                if (callback != null && executor != null) {
                    executor.execute(() -> {
                        synchronized (mCallbackLock) {
                        if (mCallback != null) {
                            mCallback.onEvent(eventType);
                            if (callback != null) {
                                callback.onEvent(eventType);
                            }
                        }
                    });
                }
            }
        }
    }

    private void onDiseqcMessage(byte[] diseqcMessage) {
        synchronized (mCallbackLock) {
            if (mExecutor != null && mCallback != null) {
                mExecutor.execute(() -> {
            for (LnbCallback callback : mCallbackMap.keySet()) {
                Executor executor = mCallbackMap.get(callback);
                if (callback != null && executor != null) {
                    executor.execute(() -> {
                        synchronized (mCallbackLock) {
                        if (mCallback != null) {
                            mCallback.onDiseqcMessage(diseqcMessage);
                            if (callback != null) {
                                callback.onDiseqcMessage(diseqcMessage);
                            }
                        }
                    });
                }
            }
        }
    }

    /* package */ boolean isClosed() {
        synchronized (mLock) {
@@ -279,7 +326,11 @@ public class Lnb implements AutoCloseable {
                TunerUtils.throwExceptionForResult(res, "Failed to close LNB");
            } else {
                mIsClosed = true;
                mTuner.releaseLnb();
                if (mOwner != null) {
                    mOwner.releaseLnb();
                    mOwner = null;
                }
                mCallbackMap.clear();
            }
        }
    }
+354 −21
Original line number Diff line number Diff line
@@ -240,7 +240,7 @@ public class Tuner implements AutoCloseable {


    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 @@ 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)
@@ -453,6 +452,260 @@ public class Tuner implements AutoCloseable {
        }
    }

    /**
     * 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.
     *
@@ -546,59 +799,114 @@ public class Tuner implements AutoCloseable {
        }
    }

    /**
     * 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 @@ public class Tuner implements AutoCloseable {
     */
    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 @@ public class Tuner implements AutoCloseable {
            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 @@ public class Tuner implements AutoCloseable {
            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 @@ public class Tuner implements AutoCloseable {
                    mLnbHandle = null;
                }
                mLnb = newLnb;
                mLnb.setCallback(executor, cb, this);
                mLnb.setCallbackAndOwner(executor, cb, this);
                setLnb(mLnb);
            }
            return mLnb;
@@ -1968,8 +2294,15 @@ public class Tuner implements AutoCloseable {
        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 {
+19 −0
Original line number Diff line number Diff line
@@ -414,6 +414,25 @@ public class TunerResourceManager {
        }
    }

    /**
     * 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.
     *
+13 −0
Original line number Diff line number Diff line
@@ -176,6 +176,19 @@ interface ITunerResourceManager {
     */
    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.
     *
Loading