All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.com.android.server.tv.tunerresourcemanager.TunerResourceManagerService Maven / Gradle / Ivy

/*
 * Copyright 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.tv.tunerresourcemanager;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.content.Context;
import android.media.IResourceManagerService;
import android.media.tv.TvInputManager;
import android.media.tv.tuner.TunerFrontendInfo;
import android.media.tv.tunerresourcemanager.CasSessionRequest;
import android.media.tv.tunerresourcemanager.IResourcesReclaimListener;
import android.media.tv.tunerresourcemanager.ITunerResourceManager;
import android.media.tv.tunerresourcemanager.ResourceClientProfile;
import android.media.tv.tunerresourcemanager.TunerCiCamRequest;
import android.media.tv.tunerresourcemanager.TunerDemuxRequest;
import android.media.tv.tunerresourcemanager.TunerDescramblerRequest;
import android.media.tv.tunerresourcemanager.TunerFrontendRequest;
import android.media.tv.tunerresourcemanager.TunerLnbRequest;
import android.media.tv.tunerresourcemanager.TunerResourceManager;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.util.Slog;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.SystemService;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * This class provides a system service that manages the TV tuner resources.
 *
 * @hide
 */
public class TunerResourceManagerService extends SystemService implements IBinder.DeathRecipient {
    private static final String TAG = "TunerResourceManagerService";
    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);

    public static final int INVALID_CLIENT_ID = -1;
    private static final int MAX_CLIENT_PRIORITY = 1000;

    // Map of the registered client profiles
    private Map mClientProfiles = new HashMap<>();
    private int mNextUnusedClientId = 0;

    // Map of the current available frontend resources
    private Map mFrontendResources = new HashMap<>();
    // Map of the current available lnb resources
    private Map mLnbResources = new HashMap<>();
    // Map of the current available Cas resources
    private Map mCasResources = new HashMap<>();
    // Map of the current available CiCam resources
    private Map mCiCamResources = new HashMap<>();

    @GuardedBy("mLock")
    private Map mListeners = new HashMap<>();

    private TvInputManager mTvInputManager;
    private ActivityManager mActivityManager;
    private IResourceManagerService mMediaResourceManager;
    private UseCasePriorityHints mPriorityCongfig = new UseCasePriorityHints();

    // An internal resource request count to help generate resource handle.
    private int mResourceRequestCount = 0;

    // Used to synchronize the access to the service.
    private final Object mLock = new Object();

    public TunerResourceManagerService(@Nullable Context context) {
        super(context);
    }

    @Override
    public void onStart() {
        onStart(false /*isForTesting*/);
    }

    @VisibleForTesting
    protected void onStart(boolean isForTesting) {
        if (!isForTesting) {
            publishBinderService(Context.TV_TUNER_RESOURCE_MGR_SERVICE, new BinderService());
        }
        mTvInputManager = (TvInputManager) getContext().getSystemService(Context.TV_INPUT_SERVICE);
        mActivityManager =
                (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE);
        mPriorityCongfig.parse();

        if (mMediaResourceManager == null) {
            IBinder mediaResourceManagerBinder = getBinderService("media.resource_manager");
            if (mediaResourceManagerBinder == null) {
                Slog.w(TAG, "Resource Manager Service not available.");
                return;
            }
            try {
                mediaResourceManagerBinder.linkToDeath(this, /*flags*/ 0);
            } catch (RemoteException e) {
                Slog.w(TAG, "Could not link to death of native resource manager service.");
                return;
            }
            mMediaResourceManager = IResourceManagerService.Stub.asInterface(
                    mediaResourceManagerBinder);
        }
    }

    private final class BinderService extends ITunerResourceManager.Stub {
        @Override
        public void registerClientProfile(@NonNull ResourceClientProfile profile,
                @NonNull IResourcesReclaimListener listener, @NonNull int[] clientId)
                throws RemoteException {
            enforceTrmAccessPermission("registerClientProfile");
            enforceTunerAccessPermission("registerClientProfile");
            if (profile == null) {
                throw new RemoteException("ResourceClientProfile can't be null");
            }

            if (clientId == null) {
                throw new RemoteException("clientId can't be null!");
            }

            if (listener == null) {
                throw new RemoteException("IResourcesReclaimListener can't be null!");
            }

            if (!mPriorityCongfig.isDefinedUseCase(profile.useCase)) {
                throw new RemoteException("Use undefined client use case:" + profile.useCase);
            }

            synchronized (mLock) {
                registerClientProfileInternal(profile, listener, clientId);
            }
        }

        @Override
        public void unregisterClientProfile(int clientId) throws RemoteException {
            enforceTrmAccessPermission("unregisterClientProfile");
            synchronized (mLock) {
                if (!checkClientExists(clientId)) {
                    Slog.e(TAG, "Unregistering non exists client:" + clientId);
                    return;
                }
                unregisterClientProfileInternal(clientId);
            }
        }

        @Override
        public boolean updateClientPriority(int clientId, int priority, int niceValue) {
            enforceTrmAccessPermission("updateClientPriority");
            synchronized (mLock) {
                return updateClientPriorityInternal(clientId, priority, niceValue);
            }
        }

        @Override
        public void setFrontendInfoList(@NonNull TunerFrontendInfo[] infos) throws RemoteException {
            enforceTrmAccessPermission("setFrontendInfoList");
            if (infos == null) {
                throw new RemoteException("TunerFrontendInfo can't be null");
            }
            synchronized (mLock) {
                setFrontendInfoListInternal(infos);
            }
        }

        @Override
        public void updateCasInfo(int casSystemId, int maxSessionNum) {
            enforceTrmAccessPermission("updateCasInfo");
            synchronized (mLock) {
                updateCasInfoInternal(casSystemId, maxSessionNum);
            }
        }

        @Override
        public void setLnbInfoList(int[] lnbHandles) throws RemoteException {
            enforceTrmAccessPermission("setLnbInfoList");
            if (lnbHandles == null) {
                throw new RemoteException("Lnb handle list can't be null");
            }
            synchronized (mLock) {
                setLnbInfoListInternal(lnbHandles);
            }
        }

        @Override
        public boolean requestFrontend(@NonNull TunerFrontendRequest request,
                @NonNull int[] frontendHandle) throws RemoteException {
            enforceTunerAccessPermission("requestFrontend");
            enforceTrmAccessPermission("requestFrontend");
            if (frontendHandle == null) {
                throw new RemoteException("frontendHandle can't be null");
            }
            synchronized (mLock) {
                if (!checkClientExists(request.clientId)) {
                    throw new RemoteException("Request frontend from unregistered client: "
                            + request.clientId);
                }
                // If the request client is holding or sharing a frontend, throw an exception.
                if (!getClientProfile(request.clientId).getInUseFrontendHandles().isEmpty()) {
                    throw new RemoteException("Release frontend before requesting another one. "
                            + "Client id: " + request.clientId);
                }
                return requestFrontendInternal(request, frontendHandle);
            }
        }

        @Override
        public void shareFrontend(int selfClientId, int targetClientId) throws RemoteException {
            enforceTunerAccessPermission("shareFrontend");
            enforceTrmAccessPermission("shareFrontend");
            synchronized (mLock) {
                if (!checkClientExists(selfClientId)) {
                    throw new RemoteException("Share frontend request from an unregistered client:"
                            + selfClientId);
                }
                if (!checkClientExists(targetClientId)) {
                    throw new RemoteException("Request to share frontend with an unregistered "
                            + "client:" + targetClientId);
                }
                if (getClientProfile(targetClientId).getInUseFrontendHandles().isEmpty()) {
                    throw new RemoteException("Request to share frontend with a client that has no "
                            + "frontend resources. Target client id:" + targetClientId);
                }
                shareFrontendInternal(selfClientId, targetClientId);
            }
        }

        @Override
        public boolean requestDemux(@NonNull TunerDemuxRequest request,
                    @NonNull int[] demuxHandle) throws RemoteException {
            enforceTunerAccessPermission("requestDemux");
            enforceTrmAccessPermission("requestDemux");
            if (demuxHandle == null) {
                throw new RemoteException("demuxHandle can't be null");
            }
            synchronized (mLock) {
                if (!checkClientExists(request.clientId)) {
                    throw new RemoteException("Request demux from unregistered client:"
                            + request.clientId);
                }
                return requestDemuxInternal(request, demuxHandle);
            }
        }

        @Override
        public boolean requestDescrambler(@NonNull TunerDescramblerRequest request,
                    @NonNull int[] descramblerHandle) throws RemoteException {
            enforceDescramblerAccessPermission("requestDescrambler");
            enforceTrmAccessPermission("requestDescrambler");
            if (descramblerHandle == null) {
                throw new RemoteException("descramblerHandle can't be null");
            }
            synchronized (mLock) {
                if (!checkClientExists(request.clientId)) {
                    throw new RemoteException("Request descrambler from unregistered client:"
                            + request.clientId);
                }
                return requestDescramblerInternal(request, descramblerHandle);
            }
        }

        @Override
        public boolean requestCasSession(@NonNull CasSessionRequest request,
                @NonNull int[] casSessionHandle) throws RemoteException {
            enforceTrmAccessPermission("requestCasSession");
            if (casSessionHandle == null) {
                throw new RemoteException("casSessionHandle can't be null");
            }
            synchronized (mLock) {
                if (!checkClientExists(request.clientId)) {
                    throw new RemoteException("Request cas from unregistered client:"
                            + request.clientId);
                }
                return requestCasSessionInternal(request, casSessionHandle);
            }
        }

        @Override
        public boolean requestCiCam(@NonNull TunerCiCamRequest request,
                @NonNull int[] ciCamHandle) throws RemoteException {
            enforceTrmAccessPermission("requestCiCam");
            if (ciCamHandle == null) {
                throw new RemoteException("ciCamHandle can't be null");
            }
            synchronized (mLock) {
                if (!checkClientExists(request.clientId)) {
                    throw new RemoteException("Request ciCam from unregistered client:"
                            + request.clientId);
                }
                return requestCiCamInternal(request, ciCamHandle);
            }
        }

        @Override
        public boolean requestLnb(@NonNull TunerLnbRequest request, @NonNull int[] lnbHandle)
                throws RemoteException {
            enforceTunerAccessPermission("requestLnb");
            enforceTrmAccessPermission("requestLnb");
            if (lnbHandle == null) {
                throw new RemoteException("lnbHandle can't be null");
            }
            synchronized (mLock) {
                if (!checkClientExists(request.clientId)) {
                    throw new RemoteException("Request lnb from unregistered client:"
                            + request.clientId);
                }
                return requestLnbInternal(request, lnbHandle);
            }
        }

        @Override
        public void releaseFrontend(int frontendHandle, int clientId) throws RemoteException {
            enforceTunerAccessPermission("releaseFrontend");
            enforceTrmAccessPermission("releaseFrontend");
            if (!validateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND,
                    frontendHandle)) {
                throw new RemoteException("frontendHandle can't be invalid");
            }
            synchronized (mLock) {
                if (!checkClientExists(clientId)) {
                    throw new RemoteException("Release frontend from unregistered client:"
                            + clientId);
                }
                FrontendResource fe = getFrontendResource(frontendHandle);
                if (fe == null) {
                    throw new RemoteException("Releasing frontend does not exist.");
                }
                if (fe.getOwnerClientId() != clientId) {
                    throw new RemoteException(
                            "Client is not the current owner of the releasing fe.");
                }
                releaseFrontendInternal(fe, clientId);
            }
        }

        @Override
        public void releaseDemux(int demuxHandle, int clientId) {
            enforceTunerAccessPermission("releaseDemux");
            enforceTrmAccessPermission("releaseDemux");
            if (DEBUG) {
                Slog.d(TAG, "releaseDemux(demuxHandle=" + demuxHandle + ")");
            }
        }

        @Override
        public void releaseDescrambler(int descramblerHandle, int clientId) {
            enforceTunerAccessPermission("releaseDescrambler");
            enforceTrmAccessPermission("releaseDescrambler");
            if (DEBUG) {
                Slog.d(TAG, "releaseDescrambler(descramblerHandle=" + descramblerHandle + ")");
            }
        }

        @Override
        public void releaseCasSession(int casSessionHandle, int clientId) throws RemoteException {
            enforceTrmAccessPermission("releaseCasSession");
            if (!validateResourceHandle(
                    TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, casSessionHandle)) {
                throw new RemoteException("casSessionHandle can't be invalid");
            }
            synchronized (mLock) {
                if (!checkClientExists(clientId)) {
                    throw new RemoteException("Release cas from unregistered client:" + clientId);
                }
                int casSystemId = getClientProfile(clientId).getInUseCasSystemId();
                CasResource cas = getCasResource(casSystemId);
                if (cas == null) {
                    throw new RemoteException("Releasing cas does not exist.");
                }
                if (!cas.getOwnerClientIds().contains(clientId)) {
                    throw new RemoteException(
                            "Client is not the current owner of the releasing cas.");
                }
                releaseCasSessionInternal(cas, clientId);
            }
        }

        @Override
        public void releaseCiCam(int ciCamHandle, int clientId) throws RemoteException {
            enforceTrmAccessPermission("releaseCiCam");
            if (!validateResourceHandle(
                    TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM, ciCamHandle)) {
                throw new RemoteException("ciCamHandle can't be invalid");
            }
            synchronized (mLock) {
                if (!checkClientExists(clientId)) {
                    throw new RemoteException("Release ciCam from unregistered client:" + clientId);
                }
                int ciCamId = getClientProfile(clientId).getInUseCiCamId();
                if (ciCamId != getResourceIdFromHandle(ciCamHandle)) {
                    throw new RemoteException("The client " + clientId + " is not the owner of "
                            + "the releasing ciCam.");
                }
                CiCamResource ciCam = getCiCamResource(ciCamId);
                if (ciCam == null) {
                    throw new RemoteException("Releasing ciCam does not exist.");
                }
                if (!ciCam.getOwnerClientIds().contains(clientId)) {
                    throw new RemoteException(
                            "Client is not the current owner of the releasing ciCam.");
                }
                releaseCiCamInternal(ciCam, clientId);
            }
        }

        @Override
        public void releaseLnb(int lnbHandle, int clientId) throws RemoteException {
            enforceTunerAccessPermission("releaseLnb");
            enforceTrmAccessPermission("releaseLnb");
            if (!validateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_LNB, lnbHandle)) {
                throw new RemoteException("lnbHandle can't be invalid");
            }
            if (!checkClientExists(clientId)) {
                throw new RemoteException("Release lnb from unregistered client:" + clientId);
            }
            LnbResource lnb = getLnbResource(lnbHandle);
            if (lnb == null) {
                throw new RemoteException("Releasing lnb does not exist.");
            }
            if (lnb.getOwnerClientId() != clientId) {
                throw new RemoteException("Client is not the current owner of the releasing lnb.");
            }
            synchronized (mLock) {
                releaseLnbInternal(lnb);
            }
        }

        @Override
        public boolean isHigherPriority(
                ResourceClientProfile challengerProfile, ResourceClientProfile holderProfile)
                throws RemoteException {
            enforceTrmAccessPermission("isHigherPriority");
            if (challengerProfile == null || holderProfile == null) {
                throw new RemoteException("Client profiles can't be null.");
            }
            synchronized (mLock) {
                return isHigherPriorityInternal(challengerProfile, holderProfile);
            }
        }
    }

    /**
     * Handle the death of the native resource manager service
     */
    @Override
    public void binderDied() {
        if (DEBUG) {
            Slog.w(TAG, "Native media resource manager service has died");
        }
        synchronized (mLock) {
            mMediaResourceManager = null;
        }
    }

    @VisibleForTesting
    protected void registerClientProfileInternal(ResourceClientProfile profile,
            IResourcesReclaimListener listener, int[] clientId) {
        if (DEBUG) {
            Slog.d(TAG, "registerClientProfile(clientProfile=" + profile + ")");
        }

        clientId[0] = INVALID_CLIENT_ID;
        if (mTvInputManager == null) {
            Slog.e(TAG, "TvInputManager is null. Can't register client profile.");
            return;
        }
        // TODO tell if the client already exists
        clientId[0] = mNextUnusedClientId++;

        int pid = profile.tvInputSessionId == null
                ? Binder.getCallingPid() /*callingPid*/
                : mTvInputManager.getClientPid(profile.tvInputSessionId); /*tvAppId*/

        // Update Media Resource Manager with the tvAppId
        if (profile.tvInputSessionId != null && mMediaResourceManager != null) {
            try {
                mMediaResourceManager.overridePid(Binder.getCallingPid(), pid);
            } catch (RemoteException e) {
                Slog.e(TAG, "Could not overridePid in resourceManagerSercice,"
                        + " remote exception: " + e);
            }
        }

        ClientProfile clientProfile = new ClientProfile.Builder(clientId[0])
                                              .tvInputSessionId(profile.tvInputSessionId)
                                              .useCase(profile.useCase)
                                              .processId(pid)
                                              .build();
        clientProfile.setForeground(checkIsForeground(pid));
        clientProfile.setPriority(
                getClientPriority(profile.useCase, clientProfile.isForeground()));

        addClientProfile(clientId[0], clientProfile, listener);
    }

    @VisibleForTesting
    protected void unregisterClientProfileInternal(int clientId) {
        if (DEBUG) {
            Slog.d(TAG, "unregisterClientProfile(clientId=" + clientId + ")");
        }
        removeClientProfile(clientId);
        // Remove the Media Resource Manager callingPid to tvAppId mapping
        if (mMediaResourceManager != null) {
            try {
                mMediaResourceManager.overridePid(Binder.getCallingPid(), -1);
            } catch (RemoteException e) {
                Slog.e(TAG, "Could not overridePid in resourceManagerSercice when unregister,"
                        + " remote exception: " + e);
            }
        }
    }

    @VisibleForTesting
    protected boolean updateClientPriorityInternal(int clientId, int priority, int niceValue) {
        if (DEBUG) {
            Slog.d(TAG,
                    "updateClientPriority(clientId=" + clientId + ", priority=" + priority
                            + ", niceValue=" + niceValue + ")");
        }

        ClientProfile profile = getClientProfile(clientId);
        if (profile == null) {
            Slog.e(TAG,
                    "Can not find client profile with id " + clientId
                            + " when trying to update the client priority.");
            return false;
        }

        profile.setForeground(checkIsForeground(profile.getProcessId()));
        profile.setPriority(priority);
        profile.setNiceValue(niceValue);

        return true;
    }

    @VisibleForTesting
    protected void setFrontendInfoListInternal(TunerFrontendInfo[] infos) {
        if (DEBUG) {
            Slog.d(TAG, "updateFrontendInfo:");
            for (int i = 0; i < infos.length; i++) {
                Slog.d(TAG, infos[i].toString());
            }
        }

        // A set to record the frontends pending on updating. Ids will be removed
        // from this set once its updating finished. Any frontend left in this set when all
        // the updates are done will be removed from mFrontendResources.
        Set updatingFrontendHandles = new HashSet<>(getFrontendResources().keySet());

        // Update frontendResources map and other mappings accordingly
        for (int i = 0; i < infos.length; i++) {
            if (getFrontendResource(infos[i].handle) != null) {
                if (DEBUG) {
                    Slog.d(TAG, "Frontend handle=" + infos[i].handle + "exists.");
                }
                updatingFrontendHandles.remove(infos[i].handle);
            } else {
                // Add a new fe resource
                FrontendResource newFe = new FrontendResource.Builder(infos[i].handle)
                                                 .type(infos[i].type)
                                                 .exclusiveGroupId(infos[i].exclusiveGroupId)
                                                 .build();
                addFrontendResource(newFe);
            }
        }

        for (int removingHandle : updatingFrontendHandles) {
            // update the exclusive group id member list
            removeFrontendResource(removingHandle);
        }
    }

    @VisibleForTesting
    protected void setLnbInfoListInternal(int[] lnbHandles) {
        if (DEBUG) {
            for (int i = 0; i < lnbHandles.length; i++) {
                Slog.d(TAG, "updateLnbInfo(lnbHanle=" + lnbHandles[i] + ")");
            }
        }

        // A set to record the Lnbs pending on updating. Handles will be removed
        // from this set once its updating finished. Any lnb left in this set when all
        // the updates are done will be removed from mLnbResources.
        Set updatingLnbHandles = new HashSet<>(getLnbResources().keySet());

        // Update lnbResources map and other mappings accordingly
        for (int i = 0; i < lnbHandles.length; i++) {
            if (getLnbResource(lnbHandles[i]) != null) {
                if (DEBUG) {
                    Slog.d(TAG, "Lnb handle=" + lnbHandles[i] + "exists.");
                }
                updatingLnbHandles.remove(lnbHandles[i]);
            } else {
                // Add a new lnb resource
                LnbResource newLnb = new LnbResource.Builder(lnbHandles[i]).build();
                addLnbResource(newLnb);
            }
        }

        for (int removingHandle : updatingLnbHandles) {
            removeLnbResource(removingHandle);
        }
    }

    @VisibleForTesting
    protected void updateCasInfoInternal(int casSystemId, int maxSessionNum) {
        if (DEBUG) {
            Slog.d(TAG,
                    "updateCasInfo(casSystemId=" + casSystemId
                            + ", maxSessionNum=" + maxSessionNum + ")");
        }
        // If maxSessionNum is 0, removing the Cas Resource.
        if (maxSessionNum == 0) {
            removeCasResource(casSystemId);
            removeCiCamResource(casSystemId);
            return;
        }
        // If the Cas exists, updates the Cas Resource accordingly.
        CasResource cas = getCasResource(casSystemId);
        CiCamResource ciCam = getCiCamResource(casSystemId);
        if (cas != null) {
            if (cas.getUsedSessionNum() > maxSessionNum) {
                // Sort and release the short number of Cas resources.
                int releasingCasResourceNum = cas.getUsedSessionNum() - maxSessionNum;
                // TODO: handle CiCam session update.
            }
            cas.updateMaxSessionNum(maxSessionNum);
            if (ciCam != null) {
                ciCam.updateMaxSessionNum(maxSessionNum);
            }
            return;
        }
        // Add the new Cas Resource.
        cas = new CasResource.Builder(casSystemId)
                             .maxSessionNum(maxSessionNum)
                             .build();
        ciCam = new CiCamResource.Builder(casSystemId)
                             .maxSessionNum(maxSessionNum)
                             .build();
        addCasResource(cas);
        addCiCamResource(ciCam);
    }

    @VisibleForTesting
    protected boolean requestFrontendInternal(TunerFrontendRequest request, int[] frontendHandle) {
        if (DEBUG) {
            Slog.d(TAG, "requestFrontend(request=" + request + ")");
        }

        frontendHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
        ClientProfile requestClient = getClientProfile(request.clientId);
        if (requestClient == null) {
            return false;
        }
        clientPriorityUpdateOnRequest(requestClient);
        int grantingFrontendHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
        int inUseLowestPriorityFrHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
        // Priority max value is 1000
        int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
        for (FrontendResource fr : getFrontendResources().values()) {
            if (fr.getType() == request.frontendType) {
                if (!fr.isInUse()) {
                    // Grant unused frontend with no exclusive group members first.
                    if (fr.getExclusiveGroupMemberFeHandles().isEmpty()) {
                        grantingFrontendHandle = fr.getHandle();
                        break;
                    } else if (grantingFrontendHandle
                            == TunerResourceManager.INVALID_RESOURCE_HANDLE) {
                        // Grant the unused frontend with lower id first if all the unused
                        // frontends have exclusive group members.
                        grantingFrontendHandle = fr.getHandle();
                    }
                } else if (grantingFrontendHandle == TunerResourceManager.INVALID_RESOURCE_HANDLE) {
                    // Record the frontend id with the lowest client priority among all the
                    // in use frontends when no available frontend has been found.
                    int priority = getOwnerClientPriority(fr.getOwnerClientId());
                    if (currentLowestPriority > priority) {
                        inUseLowestPriorityFrHandle = fr.getHandle();
                        currentLowestPriority = priority;
                    }
                }
            }
        }

        // Grant frontend when there is unused resource.
        if (grantingFrontendHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE) {
            frontendHandle[0] = grantingFrontendHandle;
            updateFrontendClientMappingOnNewGrant(grantingFrontendHandle, request.clientId);
            return true;
        }

        // When all the resources are occupied, grant the lowest priority resource if the
        // request client has higher priority.
        if (inUseLowestPriorityFrHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE
                && (requestClient.getPriority() > currentLowestPriority)) {
            if (!reclaimResource(
                    getFrontendResource(inUseLowestPriorityFrHandle).getOwnerClientId(),
                    TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
                return false;
            }
            frontendHandle[0] = inUseLowestPriorityFrHandle;
            updateFrontendClientMappingOnNewGrant(
                    inUseLowestPriorityFrHandle, request.clientId);
            return true;
        }

        return false;
    }

    @VisibleForTesting
    protected void shareFrontendInternal(int selfClientId, int targetClientId) {
        if (DEBUG) {
            Slog.d(TAG, "shareFrontend from " + selfClientId + " with " + targetClientId);
        }
        for (int feId : getClientProfile(targetClientId).getInUseFrontendHandles()) {
            getClientProfile(selfClientId).useFrontend(feId);
        }
        getClientProfile(targetClientId).shareFrontend(selfClientId);
    }

    @VisibleForTesting
    protected boolean requestLnbInternal(TunerLnbRequest request, int[] lnbHandle) {
        if (DEBUG) {
            Slog.d(TAG, "requestLnb(request=" + request + ")");
        }

        lnbHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
        ClientProfile requestClient = getClientProfile(request.clientId);
        clientPriorityUpdateOnRequest(requestClient);
        int grantingLnbHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
        int inUseLowestPriorityLnbHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
        // Priority max value is 1000
        int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
        for (LnbResource lnb : getLnbResources().values()) {
            if (!lnb.isInUse()) {
                // Grant the unused lnb with lower handle first
                grantingLnbHandle = lnb.getHandle();
                break;
            } else {
                // Record the lnb id with the lowest client priority among all the
                // in use lnb when no available lnb has been found.
                int priority = getOwnerClientPriority(lnb.getOwnerClientId());
                if (currentLowestPriority > priority) {
                    inUseLowestPriorityLnbHandle = lnb.getHandle();
                    currentLowestPriority = priority;
                }
            }
        }

        // Grant Lnb when there is unused resource.
        if (grantingLnbHandle > -1) {
            lnbHandle[0] = grantingLnbHandle;
            updateLnbClientMappingOnNewGrant(grantingLnbHandle, request.clientId);
            return true;
        }

        // When all the resources are occupied, grant the lowest priority resource if the
        // request client has higher priority.
        if (inUseLowestPriorityLnbHandle > TunerResourceManager.INVALID_RESOURCE_HANDLE
                && (requestClient.getPriority() > currentLowestPriority)) {
            if (!reclaimResource(getLnbResource(inUseLowestPriorityLnbHandle).getOwnerClientId(),
                    TunerResourceManager.TUNER_RESOURCE_TYPE_LNB)) {
                return false;
            }
            lnbHandle[0] = inUseLowestPriorityLnbHandle;
            updateLnbClientMappingOnNewGrant(inUseLowestPriorityLnbHandle, request.clientId);
            return true;
        }

        return false;
    }

    @VisibleForTesting
    protected boolean requestCasSessionInternal(CasSessionRequest request, int[] casSessionHandle) {
        if (DEBUG) {
            Slog.d(TAG, "requestCasSession(request=" + request + ")");
        }
        CasResource cas = getCasResource(request.casSystemId);
        // Unregistered Cas System is treated as having unlimited sessions.
        if (cas == null) {
            cas = new CasResource.Builder(request.casSystemId)
                                 .maxSessionNum(Integer.MAX_VALUE)
                                 .build();
            addCasResource(cas);
        }
        casSessionHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
        ClientProfile requestClient = getClientProfile(request.clientId);
        clientPriorityUpdateOnRequest(requestClient);
        int lowestPriorityOwnerId = -1;
        // Priority max value is 1000
        int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
        if (!cas.isFullyUsed()) {
            casSessionHandle[0] = generateResourceHandle(
                    TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, cas.getSystemId());
            updateCasClientMappingOnNewGrant(request.casSystemId, request.clientId);
            return true;
        }
        for (int ownerId : cas.getOwnerClientIds()) {
            // Record the client id with lowest priority that is using the current Cas system.
            int priority = getOwnerClientPriority(ownerId);
            if (currentLowestPriority > priority) {
                lowestPriorityOwnerId = ownerId;
                currentLowestPriority = priority;
            }
        }

        // When all the Cas sessions are occupied, reclaim the lowest priority client if the
        // request client has higher priority.
        if (lowestPriorityOwnerId > -1 && (requestClient.getPriority() > currentLowestPriority)) {
            if (!reclaimResource(lowestPriorityOwnerId,
                    TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION)) {
                return false;
            }
            casSessionHandle[0] = generateResourceHandle(
                    TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, cas.getSystemId());
            updateCasClientMappingOnNewGrant(request.casSystemId, request.clientId);
            return true;
        }
        return false;
    }

    @VisibleForTesting
    protected boolean requestCiCamInternal(TunerCiCamRequest request, int[] ciCamHandle) {
        if (DEBUG) {
            Slog.d(TAG, "requestCiCamInternal(TunerCiCamRequest=" + request + ")");
        }
        CiCamResource ciCam = getCiCamResource(request.ciCamId);
        // Unregistered Cas System is treated as having unlimited sessions.
        if (ciCam == null) {
            ciCam = new CiCamResource.Builder(request.ciCamId)
                                     .maxSessionNum(Integer.MAX_VALUE)
                                     .build();
            addCiCamResource(ciCam);
        }
        ciCamHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
        ClientProfile requestClient = getClientProfile(request.clientId);
        clientPriorityUpdateOnRequest(requestClient);
        int lowestPriorityOwnerId = -1;
        // Priority max value is 1000
        int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
        if (!ciCam.isFullyUsed()) {
            ciCamHandle[0] = generateResourceHandle(
                    TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM, ciCam.getCiCamId());
            updateCiCamClientMappingOnNewGrant(request.ciCamId, request.clientId);
            return true;
        }
        for (int ownerId : ciCam.getOwnerClientIds()) {
            // Record the client id with lowest priority that is using the current Cas system.
            int priority = getOwnerClientPriority(ownerId);
            if (currentLowestPriority > priority) {
                lowestPriorityOwnerId = ownerId;
                currentLowestPriority = priority;
            }
        }

        // When all the CiCam sessions are occupied, reclaim the lowest priority client if the
        // request client has higher priority.
        if (lowestPriorityOwnerId > -1 && (requestClient.getPriority() > currentLowestPriority)) {
            if (!reclaimResource(lowestPriorityOwnerId,
                    TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM)) {
                return false;
            }
            ciCamHandle[0] = generateResourceHandle(
                    TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM, ciCam.getCiCamId());
            updateCiCamClientMappingOnNewGrant(request.ciCamId, request.clientId);
            return true;
        }
        return false;
    }

    @VisibleForTesting
    protected boolean isHigherPriorityInternal(ResourceClientProfile challengerProfile,
            ResourceClientProfile holderProfile) {
        if (DEBUG) {
            Slog.d(TAG,
                    "isHigherPriority(challengerProfile=" + challengerProfile
                            + ", holderProfile=" + challengerProfile + ")");
        }
        if (mTvInputManager == null) {
            Slog.e(TAG, "TvInputManager is null. Can't compare the priority.");
            // Allow the client to acquire the hardware interface
            // when the TRM is not able to compare the priority.
            return true;
        }

        int challengerPid = challengerProfile.tvInputSessionId == null
                ? Binder.getCallingPid() /*callingPid*/
                : mTvInputManager.getClientPid(challengerProfile.tvInputSessionId); /*tvAppId*/
        int holderPid = holderProfile.tvInputSessionId == null
                ? Binder.getCallingPid() /*callingPid*/
                : mTvInputManager.getClientPid(holderProfile.tvInputSessionId); /*tvAppId*/

        int challengerPriority = getClientPriority(
                challengerProfile.useCase, checkIsForeground(challengerPid));
        int holderPriority = getClientPriority(holderProfile.useCase, checkIsForeground(holderPid));
        return challengerPriority > holderPriority;
    }

    @VisibleForTesting
    protected void releaseFrontendInternal(FrontendResource fe, int clientId) {
        if (DEBUG) {
            Slog.d(TAG, "releaseFrontend(id=" + fe.getHandle() + ", clientId=" + clientId + " )");
        }
        if (clientId == fe.getOwnerClientId()) {
            ClientProfile ownerClient = getClientProfile(fe.getOwnerClientId());
            for (int shareOwnerId : ownerClient.getShareFeClientIds()) {
                clearFrontendAndClientMapping(getClientProfile(shareOwnerId));
            }
        }
        clearFrontendAndClientMapping(getClientProfile(clientId));
    }

    @VisibleForTesting
    protected void releaseLnbInternal(LnbResource lnb) {
        if (DEBUG) {
            Slog.d(TAG, "releaseLnb(lnbHandle=" + lnb.getHandle() + ")");
        }
        updateLnbClientMappingOnRelease(lnb);
    }

    @VisibleForTesting
    protected void releaseCasSessionInternal(CasResource cas, int ownerClientId) {
        if (DEBUG) {
            Slog.d(TAG, "releaseCasSession(sessionResourceId=" + cas.getSystemId() + ")");
        }
        updateCasClientMappingOnRelease(cas, ownerClientId);
    }

    @VisibleForTesting
    protected void releaseCiCamInternal(CiCamResource ciCam, int ownerClientId) {
        if (DEBUG) {
            Slog.d(TAG, "releaseCiCamInternal(ciCamId=" + ciCam.getCiCamId() + ")");
        }
        updateCiCamClientMappingOnRelease(ciCam, ownerClientId);
    }

    @VisibleForTesting
    protected boolean requestDemuxInternal(TunerDemuxRequest request, int[] demuxHandle) {
        if (DEBUG) {
            Slog.d(TAG, "requestDemux(request=" + request + ")");
        }
        // There are enough Demux resources, so we don't manage Demux in R.
        demuxHandle[0] = generateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, 0);
        return true;
    }

    @VisibleForTesting
    // This mothod is to sync up the request client's foreground/background status and update
    // the client priority accordingly whenever new resource request comes in.
    protected void clientPriorityUpdateOnRequest(ClientProfile requestProfile) {
        int pid = requestProfile.getProcessId();
        boolean currentIsForeground = checkIsForeground(pid);
        if (requestProfile.isForeground() == currentIsForeground) {
            // To avoid overriding the priority set through updateClientPriority API.
            return;
        }
        requestProfile.setForeground(currentIsForeground);
        requestProfile.setPriority(
                getClientPriority(requestProfile.getUseCase(), currentIsForeground));
    }

    @VisibleForTesting
    protected boolean requestDescramblerInternal(
            TunerDescramblerRequest request, int[] descramblerHandle) {
        if (DEBUG) {
            Slog.d(TAG, "requestDescrambler(request=" + request + ")");
        }
        // There are enough Descrambler resources, so we don't manage Descrambler in R.
        descramblerHandle[0] =
                generateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_DESCRAMBLER, 0);
        return true;
    }

    @VisibleForTesting
    protected class ResourcesReclaimListenerRecord implements IBinder.DeathRecipient {
        private final IResourcesReclaimListener mListener;
        private final int mClientId;

        public ResourcesReclaimListenerRecord(IResourcesReclaimListener listener, int clientId) {
            mListener = listener;
            mClientId = clientId;
        }

        @Override
        public void binderDied() {
            synchronized (mLock) {
                removeClientProfile(mClientId);
            }
        }

        public int getId() {
            return mClientId;
        }

        public IResourcesReclaimListener getListener() {
            return mListener;
        }
    }

    private void addResourcesReclaimListener(int clientId, IResourcesReclaimListener listener) {
        if (listener == null) {
            if (DEBUG) {
                Slog.w(TAG, "Listener is null when client " + clientId + " registered!");
            }
            return;
        }

        ResourcesReclaimListenerRecord record =
                new ResourcesReclaimListenerRecord(listener, clientId);

        try {
            listener.asBinder().linkToDeath(record, 0);
        } catch (RemoteException e) {
            Slog.w(TAG, "Listener already died.");
            return;
        }

        mListeners.put(clientId, record);
    }

    @VisibleForTesting
    protected boolean reclaimResource(int reclaimingClientId,
            @TunerResourceManager.TunerResourceType int resourceType) {
        if (DEBUG) {
            Slog.d(TAG, "Reclaiming resources because higher priority client request resource type "
                    + resourceType);
        }
        try {
            mListeners.get(reclaimingClientId).getListener().onReclaimResources();
        } catch (RemoteException e) {
            Slog.e(TAG, "Failed to reclaim resources on client " + reclaimingClientId, e);
            return false;
        }

        // Reclaim all the resources of the share owners of the frontend that is used by the current
        // resource reclaimed client.
        ClientProfile profile = getClientProfile(reclaimingClientId);
        Set shareFeClientIds = profile.getShareFeClientIds();
        for (int clientId : shareFeClientIds) {
            try {
                mListeners.get(clientId).getListener().onReclaimResources();
            } catch (RemoteException e) {
                Slog.e(TAG, "Failed to reclaim resources on client " + clientId, e);
                return false;
            }
            clearAllResourcesAndClientMapping(getClientProfile(clientId));
        }
        clearAllResourcesAndClientMapping(profile);
        return true;
    }

    @VisibleForTesting
    protected int getClientPriority(int useCase, boolean isForeground) {
        if (DEBUG) {
            Slog.d(TAG, "getClientPriority useCase=" + useCase
                    + ", isForeground=" + isForeground + ")");
        }

        if (isForeground) {
            return mPriorityCongfig.getForegroundPriority(useCase);
        }
        return mPriorityCongfig.getBackgroundPriority(useCase);
    }

    @VisibleForTesting
    protected boolean checkIsForeground(int pid) {
        if (mActivityManager == null) {
            return false;
        }
        List appProcesses = mActivityManager.getRunningAppProcesses();
        if (appProcesses == null) {
            return false;
        }
        for (RunningAppProcessInfo appProcess : appProcesses) {
            if (appProcess.pid == pid
                    && appProcess.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
                return true;
            }
        }
        return false;
    }

    private void updateFrontendClientMappingOnNewGrant(int grantingHandle, int ownerClientId) {
        FrontendResource grantingFrontend = getFrontendResource(grantingHandle);
        ClientProfile ownerProfile = getClientProfile(ownerClientId);
        grantingFrontend.setOwner(ownerClientId);
        ownerProfile.useFrontend(grantingHandle);
        for (int exclusiveGroupMember : grantingFrontend.getExclusiveGroupMemberFeHandles()) {
            getFrontendResource(exclusiveGroupMember).setOwner(ownerClientId);
            ownerProfile.useFrontend(exclusiveGroupMember);
        }
    }

    private void updateLnbClientMappingOnNewGrant(int grantingHandle, int ownerClientId) {
        LnbResource grantingLnb = getLnbResource(grantingHandle);
        ClientProfile ownerProfile = getClientProfile(ownerClientId);
        grantingLnb.setOwner(ownerClientId);
        ownerProfile.useLnb(grantingHandle);
    }

    private void updateLnbClientMappingOnRelease(@NonNull LnbResource releasingLnb) {
        ClientProfile ownerProfile = getClientProfile(releasingLnb.getOwnerClientId());
        releasingLnb.removeOwner();
        ownerProfile.releaseLnb(releasingLnb.getHandle());
    }

    private void updateCasClientMappingOnNewGrant(int grantingId, int ownerClientId) {
        CasResource grantingCas = getCasResource(grantingId);
        ClientProfile ownerProfile = getClientProfile(ownerClientId);
        grantingCas.setOwner(ownerClientId);
        ownerProfile.useCas(grantingId);
    }

    private void updateCiCamClientMappingOnNewGrant(int grantingId, int ownerClientId) {
        CiCamResource grantingCiCam = getCiCamResource(grantingId);
        ClientProfile ownerProfile = getClientProfile(ownerClientId);
        grantingCiCam.setOwner(ownerClientId);
        ownerProfile.useCiCam(grantingId);
    }

    private void updateCasClientMappingOnRelease(
            @NonNull CasResource releasingCas, int ownerClientId) {
        ClientProfile ownerProfile = getClientProfile(ownerClientId);
        releasingCas.removeOwner(ownerClientId);
        ownerProfile.releaseCas();
    }

    private void updateCiCamClientMappingOnRelease(
            @NonNull CiCamResource releasingCiCam, int ownerClientId) {
        ClientProfile ownerProfile = getClientProfile(ownerClientId);
        releasingCiCam.removeOwner(ownerClientId);
        ownerProfile.releaseCiCam();
    }

    /**
     * Get the owner client's priority.
     *
     * @param clientId the owner client id.
     * @return the priority of the owner client.
     */
    private int getOwnerClientPriority(int clientId) {
        return getClientProfile(clientId).getPriority();
    }

    @VisibleForTesting
    @Nullable
    protected FrontendResource getFrontendResource(int frontendHandle) {
        return mFrontendResources.get(frontendHandle);
    }

    @VisibleForTesting
    protected Map getFrontendResources() {
        return mFrontendResources;
    }

    private void addFrontendResource(FrontendResource newFe) {
        // Update the exclusive group member list in all the existing Frontend resource
        for (FrontendResource fe : getFrontendResources().values()) {
            if (fe.getExclusiveGroupId() == newFe.getExclusiveGroupId()) {
                newFe.addExclusiveGroupMemberFeHandle(fe.getHandle());
                newFe.addExclusiveGroupMemberFeHandles(fe.getExclusiveGroupMemberFeHandles());
                for (int excGroupmemberFeHandle : fe.getExclusiveGroupMemberFeHandles()) {
                    getFrontendResource(excGroupmemberFeHandle)
                            .addExclusiveGroupMemberFeHandle(newFe.getHandle());
                }
                fe.addExclusiveGroupMemberFeHandle(newFe.getHandle());
                break;
            }
        }
        // Update resource list and available id list
        mFrontendResources.put(newFe.getHandle(), newFe);
    }

    private void removeFrontendResource(int removingHandle) {
        FrontendResource fe = getFrontendResource(removingHandle);
        if (fe == null) {
            return;
        }
        if (fe.isInUse()) {
            ClientProfile ownerClient = getClientProfile(fe.getOwnerClientId());
            for (int shareOwnerId : ownerClient.getShareFeClientIds()) {
                clearFrontendAndClientMapping(getClientProfile(shareOwnerId));
            }
            clearFrontendAndClientMapping(ownerClient);
        }
        for (int excGroupmemberFeHandle : fe.getExclusiveGroupMemberFeHandles()) {
            getFrontendResource(excGroupmemberFeHandle)
                    .removeExclusiveGroupMemberFeId(fe.getHandle());
        }
        mFrontendResources.remove(removingHandle);
    }

    @VisibleForTesting
    @Nullable
    protected LnbResource getLnbResource(int lnbHandle) {
        return mLnbResources.get(lnbHandle);
    }

    @VisibleForTesting
    protected Map getLnbResources() {
        return mLnbResources;
    }

    private void addLnbResource(LnbResource newLnb) {
        // Update resource list and available id list
        mLnbResources.put(newLnb.getHandle(), newLnb);
    }

    private void removeLnbResource(int removingHandle) {
        LnbResource lnb = getLnbResource(removingHandle);
        if (lnb == null) {
            return;
        }
        if (lnb.isInUse()) {
            releaseLnbInternal(lnb);
        }
        mLnbResources.remove(removingHandle);
    }

    @VisibleForTesting
    @Nullable
    protected CasResource getCasResource(int systemId) {
        return mCasResources.get(systemId);
    }

    @VisibleForTesting
    @Nullable
    protected CiCamResource getCiCamResource(int ciCamId) {
        return mCiCamResources.get(ciCamId);
    }

    @VisibleForTesting
    protected Map getCasResources() {
        return mCasResources;
    }

    @VisibleForTesting
    protected Map getCiCamResources() {
        return mCiCamResources;
    }

    private void addCasResource(CasResource newCas) {
        // Update resource list and available id list
        mCasResources.put(newCas.getSystemId(), newCas);
    }

    private void addCiCamResource(CiCamResource newCiCam) {
        // Update resource list and available id list
        mCiCamResources.put(newCiCam.getCiCamId(), newCiCam);
    }

    private void removeCasResource(int removingId) {
        CasResource cas = getCasResource(removingId);
        if (cas == null) {
            return;
        }
        for (int ownerId : cas.getOwnerClientIds()) {
            getClientProfile(ownerId).releaseCas();
        }
        mCasResources.remove(removingId);
    }

    private void removeCiCamResource(int removingId) {
        CiCamResource ciCam = getCiCamResource(removingId);
        if (ciCam == null) {
            return;
        }
        for (int ownerId : ciCam.getOwnerClientIds()) {
            getClientProfile(ownerId).releaseCiCam();
        }
        mCiCamResources.remove(removingId);
    }

    private void releaseLowerPriorityClientCasResources(int releasingCasResourceNum) {
        // TODO: Sort with a treemap

        // select the first num client to release
    }

    @VisibleForTesting
    @Nullable
    protected ClientProfile getClientProfile(int clientId) {
        return mClientProfiles.get(clientId);
    }

    private void addClientProfile(int clientId, ClientProfile profile,
            IResourcesReclaimListener listener) {
        mClientProfiles.put(clientId, profile);
        addResourcesReclaimListener(clientId, listener);
    }

    private void removeClientProfile(int clientId) {
        for (int shareOwnerId : getClientProfile(clientId).getShareFeClientIds()) {
            clearFrontendAndClientMapping(getClientProfile(shareOwnerId));
        }
        clearAllResourcesAndClientMapping(getClientProfile(clientId));
        mClientProfiles.remove(clientId);
        mListeners.remove(clientId);
    }

    private void clearFrontendAndClientMapping(ClientProfile profile) {
        for (Integer feId : profile.getInUseFrontendHandles()) {
            FrontendResource fe = getFrontendResource(feId);
            if (fe.getOwnerClientId() == profile.getId()) {
                fe.removeOwner();
                continue;
            }
            getClientProfile(fe.getOwnerClientId()).stopSharingFrontend(profile.getId());
        }
        profile.releaseFrontend();
    }

    private void clearAllResourcesAndClientMapping(ClientProfile profile) {
        // Clear Lnb
        for (Integer lnbHandle : profile.getInUseLnbHandles()) {
            getLnbResource(lnbHandle).removeOwner();
        }
        // Clear Cas
        if (profile.getInUseCasSystemId() != ClientProfile.INVALID_RESOURCE_ID) {
            getCasResource(profile.getInUseCasSystemId()).removeOwner(profile.getId());
        }
        // Clear CiCam
        if (profile.getInUseCiCamId() != ClientProfile.INVALID_RESOURCE_ID) {
            getCiCamResource(profile.getInUseCiCamId()).removeOwner(profile.getId());
        }
        // Clear Frontend
        clearFrontendAndClientMapping(profile);
        profile.reclaimAllResources();
    }

    @VisibleForTesting
    protected boolean checkClientExists(int clientId) {
        return mClientProfiles.keySet().contains(clientId);
    }

    private int generateResourceHandle(
            @TunerResourceManager.TunerResourceType int resourceType, int resourceId) {
        return (resourceType & 0x000000ff) << 24
                | (resourceId << 16)
                | (mResourceRequestCount++ & 0xffff);
    }

    @VisibleForTesting
    protected int getResourceIdFromHandle(int resourceHandle) {
        if (resourceHandle == TunerResourceManager.INVALID_RESOURCE_HANDLE) {
            return resourceHandle;
        }
        return (resourceHandle & 0x00ff0000) >> 16;
    }

    private boolean validateResourceHandle(int resourceType, int resourceHandle) {
        if (resourceHandle == TunerResourceManager.INVALID_RESOURCE_HANDLE
                || ((resourceHandle & 0xff000000) >> 24) != resourceType) {
            return false;
        }
        return true;
    }

    private void enforceTrmAccessPermission(String apiName) {
        getContext().enforceCallingOrSelfPermission("android.permission.TUNER_RESOURCE_ACCESS",
                TAG + ": " + apiName);
    }

    private void enforceTunerAccessPermission(String apiName) {
        getContext().enforceCallingPermission("android.permission.ACCESS_TV_TUNER",
                TAG + ": " + apiName);
    }

    private void enforceDescramblerAccessPermission(String apiName) {
        getContext().enforceCallingPermission("android.permission.ACCESS_TV_DESCRAMBLER",
                TAG + ": " + apiName);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy