blob: 8f416082374ee6cd7b3f12c8a6b4e4b8b81b7a89 [file] [log] [blame]
/*
* 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.content.pm.PackageManager;
import android.hardware.tv.tuner.DemuxFilterMainType;
import android.media.IResourceManagerService;
import android.media.tv.TvInputManager;
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.TunerDemuxInfo;
import android.media.tv.tunerresourcemanager.TunerDemuxRequest;
import android.media.tv.tunerresourcemanager.TunerDescramblerRequest;
import android.media.tv.tunerresourcemanager.TunerFrontendInfo;
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.os.SystemClock;
import android.os.SystemProperties;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Slog;
import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.SystemService;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* This class provides a system service that manages the TV tuner resources.
*
* @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;
private static final long INVALID_THREAD_ID = -1;
private static final long TRMS_LOCK_TIMEOUT = 500;
private static final int INVALID_FE_COUNT = -1;
// Map of the registered client profiles
private Map<Integer, ClientProfile> mClientProfiles = new HashMap<>();
private int mNextUnusedClientId = 0;
// Map of the current available frontend resources
private Map<Integer, FrontendResource> mFrontendResources = new HashMap<>();
// SparseIntArray of the max usable number for each frontend resource type
private SparseIntArray mFrontendMaxUsableNums = new SparseIntArray();
// SparseIntArray of the currently used number for each frontend resource type
private SparseIntArray mFrontendUsedNums = new SparseIntArray();
// SparseIntArray of the existing number for each frontend resource type
private SparseIntArray mFrontendExistingNums = new SparseIntArray();
// Backups for the frontend resource maps for enabling testing with custom resource maps
// such as TunerTest.testHasUnusedFrontend1()
private Map<Integer, FrontendResource> mFrontendResourcesBackup = new HashMap<>();
private SparseIntArray mFrontendMaxUsableNumsBackup = new SparseIntArray();
private SparseIntArray mFrontendUsedNumsBackup = new SparseIntArray();
private SparseIntArray mFrontendExistingNumsBackup = new SparseIntArray();
// Map of the current available demux resources
private Map<Integer, DemuxResource> mDemuxResources = new HashMap<>();
// Map of the current available lnb resources
private Map<Integer, LnbResource> mLnbResources = new HashMap<>();
// Map of the current available Cas resources
private Map<Integer, CasResource> mCasResources = new HashMap<>();
// Map of the current available CiCam resources
private Map<Integer, CiCamResource> mCiCamResources = new HashMap<>();
@GuardedBy("mLock")
private Map<Integer, ResourcesReclaimListenerRecord> 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();
private final ReentrantLock mLockForTRMSLock = new ReentrantLock();
private final Condition mTunerApiLockReleasedCV = mLockForTRMSLock.newCondition();
private int mTunerApiLockHolder = INVALID_CLIENT_ID;
private long mTunerApiLockHolderThreadId = INVALID_THREAD_ID;
private int mTunerApiLockNestedCount = 0;
public TunerResourceManagerService(@Nullable Context context) {
super(context);
}
@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();
// Call SystemProperties.set() in mock app will throw exception because of permission.
if (!isForTesting) {
final boolean lazyHal = SystemProperties.getBoolean("ro.tuner.lazyhal", false);
if (!lazyHal) {
// The HAL is not a lazy HAL, enable the tuner server.
SystemProperties.set("tuner.server.enable", "true");
}
}
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 boolean hasUnusedFrontend(int frontendType) {
enforceTrmAccessPermission("hasUnusedFrontend");
synchronized (mLock) {
return hasUnusedFrontendInternal(frontendType);
}
}
@Override
public boolean isLowestPriority(int clientId, int frontendType)
throws RemoteException {
enforceTrmAccessPermission("isLowestPriority");
synchronized (mLock) {
if (!checkClientExists(clientId)) {
throw new RemoteException("isLowestPriority called from unregistered client: "
+ clientId);
}
return isLowestPriorityInternal(clientId, frontendType);
}
}
@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 setDemuxInfoList(@NonNull TunerDemuxInfo[] infos) throws RemoteException {
enforceTrmAccessPermission("setDemuxInfoList");
if (infos == null) {
throw new RemoteException("TunerDemuxInfo can't be null");
}
synchronized (mLock) {
setDemuxInfoListInternal(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) {
enforceTunerAccessPermission("requestFrontend");
enforceTrmAccessPermission("requestFrontend");
if (frontendHandle == null) {
Slog.e(TAG, "frontendHandle can't be null");
return false;
}
synchronized (mLock) {
if (!checkClientExists(request.clientId)) {
Slog.e(TAG, "Request frontend from unregistered client: "
+ request.clientId);
return false;
}
// If the request client is holding or sharing a frontend, throw an exception.
if (!getClientProfile(request.clientId).getInUseFrontendHandles().isEmpty()) {
Slog.e(TAG, "Release frontend before requesting another one. Client id: "
+ request.clientId);
return false;
}
return requestFrontendInternal(request, frontendHandle);
}
}
@Override
public boolean setMaxNumberOfFrontends(int frontendType, int maxUsableNum) {
enforceTunerAccessPermission("setMaxNumberOfFrontends");
enforceTrmAccessPermission("setMaxNumberOfFrontends");
if (maxUsableNum < 0) {
Slog.w(TAG, "setMaxNumberOfFrontends failed with maxUsableNum:" + maxUsableNum
+ " frontendType:" + frontendType);
return false;
}
synchronized (mLock) {
return setMaxNumberOfFrontendsInternal(frontendType, maxUsableNum);
}
}
@Override
public int getMaxNumberOfFrontends(int frontendType) {
enforceTunerAccessPermission("getMaxNumberOfFrontends");
enforceTrmAccessPermission("getMaxNumberOfFrontends");
synchronized (mLock) {
return getMaxNumberOfFrontendsInternal(frontendType);
}
}
@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 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");
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.");
}
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.");
}
releaseFrontendInternal(fe, clientId);
}
}
@Override
public void releaseDemux(int demuxHandle, int clientId) throws RemoteException {
enforceTunerAccessPermission("releaseDemux");
enforceTrmAccessPermission("releaseDemux");
if (DEBUG) {
Slog.e(TAG, "releaseDemux(demuxHandle=" + demuxHandle + ")");
}
synchronized (mLock) {
// For Tuner 2.0 and below or any HW constraint devices that are unable to support
// ITuner.openDemuxById(), demux resources are not really managed under TRM and
// mDemuxResources.size() will be zero
if (mDemuxResources.size() == 0) {
return;
}
if (!checkClientExists(clientId)) {
throw new RemoteException("Release demux for unregistered client:" + clientId);
}
DemuxResource demux = getDemuxResource(demuxHandle);
if (demux == null) {
throw new RemoteException("Releasing demux does not exist.");
}
if (demux.getOwnerClientId() != clientId) {
throw new RemoteException("Client is not the current owner "
+ "of the releasing demux.");
}
releaseDemuxInternal(demux);
}
}
@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");
}
synchronized (mLock) {
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.");
}
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);
}
}
@Override
public void storeResourceMap(int resourceType) {
enforceTrmAccessPermission("storeResourceMap");
synchronized (mLock) {
storeResourceMapInternal(resourceType);
}
}
@Override
public void clearResourceMap(int resourceType) {
enforceTrmAccessPermission("clearResourceMap");
synchronized (mLock) {
clearResourceMapInternal(resourceType);
}
}
@Override
public void restoreResourceMap(int resourceType) {
enforceTrmAccessPermission("restoreResourceMap");
synchronized (mLock) {
restoreResourceMapInternal(resourceType);
}
}
@Override
public boolean acquireLock(int clientId, long clientThreadId) {
enforceTrmAccessPermission("acquireLock");
// this must not be locked with mLock
return acquireLockInternal(clientId, clientThreadId, TRMS_LOCK_TIMEOUT);
}
@Override
public boolean releaseLock(int clientId) {
enforceTrmAccessPermission("releaseLock");
// this must not be locked with mLock
return releaseLockInternal(clientId, TRMS_LOCK_TIMEOUT, false, false);
}
@Override
protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
pw.println("Permission Denial: can't dump!");
return;
}
synchronized (mLock) {
dumpMap(mClientProfiles, "ClientProfiles:", "\n", pw);
dumpMap(mFrontendResources, "FrontendResources:", "\n", pw);
dumpSIA(mFrontendExistingNums, "FrontendExistingNums:", ", ", pw);
dumpSIA(mFrontendUsedNums, "FrontendUsedNums:", ", ", pw);
dumpSIA(mFrontendMaxUsableNums, "FrontendMaxUsableNums:", ", ", pw);
dumpMap(mFrontendResourcesBackup, "FrontendResourcesBackUp:", "\n", pw);
dumpSIA(mFrontendExistingNumsBackup, "FrontendExistingNumsBackup:", ", ", pw);
dumpSIA(mFrontendUsedNumsBackup, "FrontendUsedNumsBackup:", ", ", pw);
dumpSIA(mFrontendMaxUsableNumsBackup, "FrontendUsedNumsBackup:", ", ", pw);
dumpMap(mDemuxResources, "DemuxResource:", "\n", pw);
dumpMap(mLnbResources, "LnbResource:", "\n", pw);
dumpMap(mCasResources, "CasResource:", "\n", pw);
dumpMap(mCiCamResources, "CiCamResource:", "\n", pw);
dumpMap(mListeners, "Listners:", "\n", pw);
}
}
@Override
public int getClientPriority(int useCase, int pid) throws RemoteException {
enforceTrmAccessPermission("getClientPriority");
synchronized (mLock) {
return TunerResourceManagerService.this.getClientPriority(
useCase, checkIsForeground(pid));
}
}
@Override
public int getConfigPriority(int useCase, boolean isForeground) throws RemoteException {
enforceTrmAccessPermission("getConfigPriority");
synchronized (mLock) {
return TunerResourceManagerService.this.getClientPriority(useCase, isForeground);
}
}
}
/**
* 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.setPriority(
getClientPriority(profile.useCase, checkIsForeground(pid)));
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.overwritePriority(priority);
profile.setNiceValue(niceValue);
return true;
}
protected boolean hasUnusedFrontendInternal(int frontendType) {
for (FrontendResource fr : getFrontendResources().values()) {
if (fr.getType() == frontendType && !fr.isInUse()) {
return true;
}
}
return false;
}
protected boolean isLowestPriorityInternal(int clientId, int frontendType)
throws RemoteException {
// Update the client priority
ClientProfile requestClient = getClientProfile(clientId);
if (requestClient == null) {
return true;
}
clientPriorityUpdateOnRequest(requestClient);
int clientPriority = requestClient.getPriority();
// Check if there is another holder with lower priority
for (FrontendResource fr : getFrontendResources().values()) {
if (fr.getType() == frontendType && fr.isInUse()) {
int priority = updateAndGetOwnerClientPriority(fr.getOwnerClientId());
// Returns false only when the clientPriority is strictly greater
// because false means that there is another reclaimable resource
if (clientPriority > priority) {
return false;
}
}
}
return true;
}
protected void storeResourceMapInternal(int resourceType) {
switch (resourceType) {
case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND:
replaceFeResourceMap(mFrontendResources, mFrontendResourcesBackup);
replaceFeCounts(mFrontendExistingNums, mFrontendExistingNumsBackup);
replaceFeCounts(mFrontendUsedNums, mFrontendUsedNumsBackup);
replaceFeCounts(mFrontendMaxUsableNums, mFrontendMaxUsableNumsBackup);
break;
// TODO: implement for other resource type when needed
default:
break;
}
}
protected void clearResourceMapInternal(int resourceType) {
switch (resourceType) {
case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND:
replaceFeResourceMap(null, mFrontendResources);
replaceFeCounts(null, mFrontendExistingNums);
replaceFeCounts(null, mFrontendUsedNums);
replaceFeCounts(null, mFrontendMaxUsableNums);
break;
// TODO: implement for other resource type when needed
default:
break;
}
}
protected void restoreResourceMapInternal(int resourceType) {
switch (resourceType) {
case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND:
replaceFeResourceMap(mFrontendResourcesBackup, mFrontendResources);
replaceFeCounts(mFrontendExistingNumsBackup, mFrontendExistingNums);
replaceFeCounts(mFrontendUsedNumsBackup, mFrontendUsedNums);
replaceFeCounts(mFrontendMaxUsableNumsBackup, mFrontendMaxUsableNums);
break;
// TODO: implement for other resource type when needed
default:
break;
}
}
@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<Integer> 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 setDemuxInfoListInternal(TunerDemuxInfo[] infos) {
if (DEBUG) {
Slog.d(TAG, "updateDemuxInfo:");
for (int i = 0; i < infos.length; i++) {
Slog.d(TAG, infos[i].toString());
}
}
// A set to record the demuxes pending on updating. Ids will be removed
// from this set once its updating finished. Any demux left in this set when all
// the updates are done will be removed from mDemuxResources.
Set<Integer> updatingDemuxHandles = new HashSet<>(getDemuxResources().keySet());
// Update demuxResources map and other mappings accordingly
for (int i = 0; i < infos.length; i++) {
if (getDemuxResource(infos[i].handle) != null) {
if (DEBUG) {
Slog.d(TAG, "Demux handle=" + infos[i].handle + "exists.");
}
updatingDemuxHandles.remove(infos[i].handle);
} else {
// Add a new demux resource
DemuxResource newDemux = new DemuxResource.Builder(infos[i].handle)
.filterTypes(infos[i].filterTypes)
.build();
addDemuxResource(newDemux);
}
}
for (int removingHandle : updatingDemuxHandles) {
// update the exclusive group id member list
removeDemuxResource(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<Integer> 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);
// TODO: check if this is really needed
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;
boolean isRequestFromSameProcess = false;
// If the desired frontend id was specified, we only need to check the frontend.
boolean hasDesiredFrontend = request.desiredId != TunerFrontendRequest.DEFAULT_DESIRED_ID;
for (FrontendResource fr : getFrontendResources().values()) {
int frontendId = getResourceIdFromHandle(fr.getHandle());
if (fr.getType() == request.frontendType
&& (!hasDesiredFrontend || frontendId == request.desiredId)) {
if (!fr.isInUse()) {
// Unused resource cannot be acquired if the max is already reached, but
// TRM still has to look for the reclaim candidate
if (isFrontendMaxNumUseReached(request.frontendType)) {
continue;
}
// 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 = getFrontendHighestClientPriority(fr.getOwnerClientId());
if (currentLowestPriority > priority) {
inUseLowestPriorityFrHandle = fr.getHandle();
currentLowestPriority = priority;
isRequestFromSameProcess = (requestClient.getProcessId()
== (getClientProfile(fr.getOwnerClientId())).getProcessId());
}
}
}
}
// 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) || (
(requestClient.getPriority() == currentLowestPriority) && isRequestFromSameProcess))) {
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);
}
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);
}
// change the primary frontend
newOwnerProfile.setPrimaryFrontend(currentOwnerProfile.getPrimaryFrontend());
currentOwnerProfile.setPrimaryFrontend(TunerResourceManager.INVALID_RESOURCE_HANDLE);
// 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) {
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;
boolean isRequestFromSameProcess = false;
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 = updateAndGetOwnerClientPriority(lnb.getOwnerClientId());
if (currentLowestPriority > priority) {
inUseLowestPriorityLnbHandle = lnb.getHandle();
currentLowestPriority = priority;
isRequestFromSameProcess = (requestClient.getProcessId()
== (getClientProfile(lnb.getOwnerClientId())).getProcessId());
}
}
}
// 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) || (
(requestClient.getPriority() == currentLowestPriority) && isRequestFromSameProcess))) {
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;
boolean isRequestFromSameProcess = false;
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 = updateAndGetOwnerClientPriority(ownerId);
if (currentLowestPriority > priority) {
lowestPriorityOwnerId = ownerId;
currentLowestPriority = priority;
isRequestFromSameProcess = (requestClient.getProcessId()
== (getClientProfile(ownerId)).getProcessId());
}
}
// 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)
|| ((requestClient.getPriority() == currentLowestPriority) && isRequestFromSameProcess))) {
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;
boolean isRequestFromSameProcess = false;
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 = updateAndGetOwnerClientPriority(ownerId);
if (currentLowestPriority > priority) {
lowestPriorityOwnerId = ownerId;
currentLowestPriority = priority;
isRequestFromSameProcess = (requestClient.getProcessId()
== (getClientProfile(ownerId)).getProcessId());
}
}
// 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)
|| ((requestClient.getPriority() == currentLowestPriority)
&& isRequestFromSameProcess))) {
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());
if (ownerClient != null) {
for (int shareOwnerId : ownerClient.getShareFeClientIds()) {
reclaimResource(shareOwnerId,
TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND);
}
}
}
clearFrontendAndClientMapping(getClientProfile(clientId));
}
@VisibleForTesting
protected void releaseDemuxInternal(DemuxResource demux) {
if (DEBUG) {
Slog.d(TAG, "releaseDemux(DemuxHandle=" + demux.getHandle() + ")");
}
updateDemuxClientMappingOnRelease(demux);
}
@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 + ")");
}
// For Tuner 2.0 and below or any HW constraint devices that are unable to support
// ITuner.openDemuxById(), demux resources are not really managed under TRM and
// mDemuxResources.size() will be zero
if (mDemuxResources.size() == 0) {
// There are enough Demux resources, so we don't manage Demux in R.
demuxHandle[0] =
generateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, 0);
return true;
}
demuxHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
ClientProfile requestClient = getClientProfile(request.clientId);
if (requestClient == null) {
return false;
}
clientPriorityUpdateOnRequest(requestClient);
int grantingDemuxHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
int inUseLowestPriorityDrHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
// Priority max value is 1000
int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
boolean isRequestFromSameProcess = false;
// If the desired demux id was specified, we only need to check the demux.
boolean hasDesiredDemuxCap = request.desiredFilterTypes
!= DemuxFilterMainType.UNDEFINED;
int smallestNumOfSupportedCaps = Integer.SIZE + 1;
for (DemuxResource dr : getDemuxResources().values()) {
if (!hasDesiredDemuxCap || dr.hasSufficientCaps(request.desiredFilterTypes)) {
if (!dr.isInUse()) {
int numOfSupportedCaps = dr.getNumOfCaps();
// look for the demux with minimum caps supporting the desired caps
if (smallestNumOfSupportedCaps > numOfSupportedCaps) {
smallestNumOfSupportedCaps = numOfSupportedCaps;
grantingDemuxHandle = dr.getHandle();
}
} else if (grantingDemuxHandle == TunerResourceManager.INVALID_RESOURCE_HANDLE) {
// Record the demux id with the lowest client priority among all the
// in use demuxes when no availabledemux has been found.
int priority = updateAndGetOwnerClientPriority(dr.getOwnerClientId());
if (currentLowestPriority >= priority) {
int numOfSupportedCaps = dr.getNumOfCaps();
boolean shouldUpdate = false;
// update lowest priority
if (currentLowestPriority > priority) {
currentLowestPriority = priority;
isRequestFromSameProcess = (requestClient.getProcessId()
== (getClientProfile(dr.getOwnerClientId())).getProcessId());
shouldUpdate = true;
}
// update smallest caps
if (smallestNumOfSupportedCaps > numOfSupportedCaps) {
smallestNumOfSupportedCaps = numOfSupportedCaps;
shouldUpdate = true;
}
if (shouldUpdate) {
inUseLowestPriorityDrHandle = dr.getHandle();
}
}
}
}
}
// Grant demux when there is unused resource.
if (grantingDemuxHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE) {
demuxHandle[0] = grantingDemuxHandle;
updateDemuxClientMappingOnNewGrant(grantingDemuxHandle, request.clientId);
return true;
}
// When all the resources are occupied, grant the lowest priority resource if the
// request client has higher priority.
if (inUseLowestPriorityDrHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE
&& ((requestClient.getPriority() > currentLowestPriority) || (
(requestClient.getPriority() == currentLowestPriority) && isRequestFromSameProcess))) {
if (!reclaimResource(
getDemuxResource(inUseLowestPriorityDrHandle).getOwnerClientId(),
TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
return false;
}
demuxHandle[0] = inUseLowestPriorityDrHandle;
updateDemuxClientMappingOnNewGrant(
inUseLowestPriorityDrHandle, request.clientId);
return true;
}
return false;
}
@VisibleForTesting
// This mothod is to sync up the request/holder client's foreground/background status and update
// the client priority accordingly whenever a new resource request comes in.
protected void clientPriorityUpdateOnRequest(ClientProfile profile) {
if (profile.isPriorityOverwritten()) {
// To avoid overriding the priority set through updateClientPriority API.
return;
}
int pid = profile.getProcessId();
boolean currentIsForeground = checkIsForeground(pid);
profile.setPriority(
getClientPriority(profile.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;
}
// Return value is guaranteed to be positive
private long getElapsedTime(long begin) {
long now = SystemClock.uptimeMillis();
long elapsed;
if (now >= begin) {
elapsed = now - begin;
} else {
elapsed = Long.MAX_VALUE - begin + now;
if (elapsed < 0) {
elapsed = Long.MAX_VALUE;
}
}
return elapsed;
}
private boolean lockForTunerApiLock(int clientId, long timeoutMS, String callerFunction) {
try {
if (mLockForTRMSLock.tryLock(timeoutMS, TimeUnit.MILLISECONDS)) {
return true;
} else {
Slog.e(TAG, "FAILED to lock mLockForTRMSLock in " + callerFunction
+ ", clientId:" + clientId + ", timeoutMS:" + timeoutMS
+ ", mTunerApiLockHolder:" + mTunerApiLockHolder);
return false;
}
} catch (InterruptedException ie) {
Slog.e(TAG, "exception thrown in " + callerFunction + ":" + ie);
if (mLockForTRMSLock.isHeldByCurrentThread()) {
mLockForTRMSLock.unlock();
}
return false;
}
}
private boolean acquireLockInternal(int clientId, long clientThreadId, long timeoutMS) {
long begin = SystemClock.uptimeMillis();
// Grab lock
if (!lockForTunerApiLock(clientId, timeoutMS, "acquireLockInternal()")) {
return false;
}
try {
boolean available = mTunerApiLockHolder == INVALID_CLIENT_ID;
boolean nestedSelf = (clientId == mTunerApiLockHolder)
&& (clientThreadId == mTunerApiLockHolderThreadId);
boolean recovery = false;
// Allow same thread to grab the lock multiple times
while (!available && !nestedSelf) {
// calculate how much time is left before timeout
long leftOverMS = timeoutMS - getElapsedTime(begin);
if (leftOverMS <= 0) {
Slog.e(TAG, "FAILED:acquireLockInternal(" + clientId + ", " + clientThreadId
+ ", " + timeoutMS + ") - timed out, but will grant the lock to "
+ "the callee by stealing it from the current holder:"
+ mTunerApiLockHolder + "(" + mTunerApiLockHolderThreadId + "), "
+ "who likely failed to call releaseLock(), "
+ "to prevent this from becoming an unrecoverable error");
// This should not normally happen, but there sometimes are cases where
// in-flight tuner API execution gets scheduled even after binderDied(),
// which can leave the in-flight execution dissappear/stopped in between
// acquireLock and releaseLock
recovery = true;
break;
}
// Cond wait for left over time
mTunerApiLockReleasedCV.await(leftOverMS, TimeUnit.MILLISECONDS);
// Check the availability for "spurious wakeup"
// The case that was confirmed is that someone else can acquire this in between
// signal() and wakup from the above await()
available = mTunerApiLockHolder == INVALID_CLIENT_ID;
if (!available) {
Slog.w(TAG, "acquireLockInternal(" + clientId + ", " + clientThreadId + ", "
+ timeoutMS + ") - woken up from cond wait, but " + mTunerApiLockHolder
+ "(" + mTunerApiLockHolderThreadId + ") is already holding the lock. "
+ "Going to wait again if timeout hasn't reached yet");
}
}
// Will always grant unless exception is thrown (or lock is already held)
if (available || recovery) {
if (DEBUG) {
Slog.d(TAG, "SUCCESS:acquireLockInternal(" + clientId + ", " + clientThreadId
+ ", " + timeoutMS + ")");
}
if (mTunerApiLockNestedCount != 0) {
Slog.w(TAG, "Something is wrong as nestedCount(" + mTunerApiLockNestedCount
+ ") is not zero. Will overriding it to 1 anyways");
}
// set the caller to be the holder
mTunerApiLockHolder = clientId;
mTunerApiLockHolderThreadId = clientThreadId;
mTunerApiLockNestedCount = 1;
} else if (nestedSelf) {
// Increment the nested count so releaseLockInternal won't signal prematuredly
mTunerApiLockNestedCount++;
if (DEBUG) {
Slog.d(TAG, "acquireLockInternal(" + clientId + ", " + clientThreadId
+ ", " + timeoutMS + ") - nested count incremented to "
+ mTunerApiLockNestedCount);
}
} else {
Slog.e(TAG, "acquireLockInternal(" + clientId + ", " + clientThreadId
+ ", " + timeoutMS + ") - should not reach here");
}
// return true in "recovery" so callee knows that the deadlock is possible
// only when the return value is false
return (available || nestedSelf || recovery);
} catch (InterruptedException ie) {
Slog.e(TAG, "exception thrown in acquireLockInternal(" + clientId + ", "
+ clientThreadId + ", " + timeoutMS + "):" + ie);
return false;
} finally {
if (mLockForTRMSLock.isHeldByCurrentThread()) {
mLockForTRMSLock.unlock();
}
}
}
private boolean releaseLockInternal(int clientId, long timeoutMS,
boolean ignoreNestedCount, boolean suppressError) {
// Grab lock first
if (!lockForTunerApiLock(clientId, timeoutMS, "releaseLockInternal()")) {
return false;
}
try {
if (mTunerApiLockHolder == clientId) {
// Should always reach here unless called from binderDied()
mTunerApiLockNestedCount--;
if (ignoreNestedCount || mTunerApiLockNestedCount <= 0) {
if (DEBUG) {
Slog.d(TAG, "SUCCESS:releaseLockInternal(" + clientId + ", " + timeoutMS
+ ", " + ignoreNestedCount + ", " + suppressError
+ ") - signaling!");
}
// Reset the current holder and signal
mTunerApiLockHolder = INVALID_CLIENT_ID;
mTunerApiLockHolderThreadId = INVALID_THREAD_ID;
mTunerApiLockNestedCount = 0;
mTunerApiLockReleasedCV.signal();
} else {
if (DEBUG) {
Slog.d(TAG, "releaseLockInternal(" + clientId + ", " + timeoutMS
+ ", " + ignoreNestedCount + ", " + suppressError
+ ") - NOT signaling because nested count is not zero ("
+ mTunerApiLockNestedCount + ")");
}
}
return true;
} else if (mTunerApiLockHolder == INVALID_CLIENT_ID) {
if (!suppressError) {
Slog.w(TAG, "releaseLockInternal(" + clientId + ", " + timeoutMS
+ ") - called while there is no current holder");
}
// No need to do anything.
// Shouldn't reach here unless called from binderDied()
return false;
} else {
if (!suppressError) {
Slog.e(TAG, "releaseLockInternal(" + clientId + ", " + timeoutMS
+ ") - called while someone else:" + mTunerApiLockHolder
+ "is the current holder");
}
// Cannot reset the holder Id because it reaches here when called
// from binderDied()
return false;
}
} finally {
if (mLockForTRMSLock.isHeldByCurrentThread()) {
mLockForTRMSLock.unlock();
}
}
}
@VisibleForTesting
protected class ResourcesReclaimListenerRecord implements IBinder.DeathRecipient {
private final IResourcesReclaimListener mListener;
private final int mClientId;
public ResourcesReclaimListenerRecord(IResourcesReclaimListener listener, int clientId) {
mListener = listener;
mClientId = clientId;
}
@Override
public void binderDied() {
try {
synchronized (mLock) {
if (checkClientExists(mClientId)) {
removeClientProfile(mClientId);
}
}
} finally {
// reset the tuner API lock
releaseLockInternal(mClientId, TRMS_LOCK_TIMEOUT, true, true);
}
}
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) {
// Allowing this because:
// 1) serialization of resource reclaim is required in the current design
// 2) the outgoing transaction is handled by the system app (with
// android.Manifest.permission.TUNER_RESOURCE_ACCESS), which goes through full
// Google certification
Binder.allowBlockingForCurrentThread();
// Reclaim all the resources of the share owners of the frontend that is used by the current
// resource reclaimed client.
ClientProfile profile = getClientProfile(reclaimingClientId);
// TODO: check if this check is really needed.
if (profile == null) {
return true;
}
Set<Integer> 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));
}
if (DEBUG) {
Slog.d(TAG, "Reclaiming resources because higher priority client request resource type "
+ resourceType + ", clientId:" + reclaimingClientId);
}
try {
mListeners.get(reclaimingClientId).getListener().onReclaimResources();
} catch (RemoteException e) {
Slog.e(TAG, "Failed to reclaim resources on client " + reclaimingClientId, e);
return false;
}
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<RunningAppProcessInfo> 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);
increFrontendNum(mFrontendUsedNums, grantingFrontend.getType());
ownerProfile.useFrontend(grantingHandle);
for (int exclusiveGroupMember : grantingFrontend.getExclusiveGroupMemberFeHandles()) {
getFrontendResource(exclusiveGroupMember).setOwner(ownerClientId);
ownerProfile.useFrontend(exclusiveGroupMember);
}
ownerProfile.setPrimaryFrontend(grantingHandle);
}
private void updateDemuxClientMappingOnNewGrant(int grantingHandle, int ownerClientId) {
DemuxResource grantingDemux = getDemuxResource(grantingHandle);
if (grantingDemux != null) {
ClientProfile ownerProfile = getClientProfile(ownerClientId);
grantingDemux.setOwner(ownerClientId);
ownerProfile.useDemux(grantingHandle);
}
}
private void updateDemuxClientMappingOnRelease(@NonNull DemuxResource releasingDemux) {
ClientProfile ownerProfile = getClientProfile(releasingDemux.getOwnerClientId());
releasingDemux.removeOwner();
ownerProfile.releaseDemux(releasingDemux.getHandle());
}
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();
}
/**
* Update and get the owner client's priority.
*
* @param clientId the owner client id.
* @return the priority of the owner client.
*/
private int updateAndGetOwnerClientPriority(int clientId) {
ClientProfile profile = getClientProfile(clientId);
clientPriorityUpdateOnRequest(profile);
return profile.getPriority();
}
/**
* Update the owner and sharee clients' priority and get the highest priority
* for frontend resource
*
* @param clientId the owner client id.
* @return the highest priority among all the clients holding the same frontend resource.
*/
private int getFrontendHighestClientPriority(int clientId) {
// Check if the owner profile exists
ClientProfile ownerClient = getClientProfile(clientId);
if (ownerClient == null) {
return 0;
}
// Update and get the priority of the owner client
int highestPriority = updateAndGetOwnerClientPriority(clientId);
// Update and get all the client IDs of frontend resource holders
for (int shareeId : ownerClient.getShareFeClientIds()) {
int priority = updateAndGetOwnerClientPriority(shareeId);
if (priority > highestPriority) {
highestPriority = priority;
}
}
return highestPriority;
}
@VisibleForTesting
@Nullable
protected FrontendResource getFrontendResource(int frontendHandle) {
return mFrontendResources.get(frontendHandle);
}
@VisibleForTesting
protected Map<Integer, FrontendResource> getFrontendResources() {
return mFrontendResources;
}
@VisibleForTesting
@Nullable
protected DemuxResource getDemuxResource(int demuxHandle) {
return mDemuxResources.get(demuxHandle);
}
@VisibleForTesting
protected Map<Integer, DemuxResource> getDemuxResources() {
return mDemuxResources;
}
private boolean setMaxNumberOfFrontendsInternal(int frontendType, int maxUsableNum) {
int usedNum = mFrontendUsedNums.get(frontendType, INVALID_FE_COUNT);
if (usedNum == INVALID_FE_COUNT || usedNum <= maxUsableNum) {
mFrontendMaxUsableNums.put(frontendType, maxUsableNum);
return true;
} else {
Slog.e(TAG, "max number of frontend for frontendType: " + frontendType
+ " cannot be set to a value lower than the current usage count."
+ " (requested max num = " + maxUsableNum + ", current usage = " + usedNum);
return false;
}
}
private int getMaxNumberOfFrontendsInternal(int frontendType) {
int existingNum = mFrontendExistingNums.get(frontendType, INVALID_FE_COUNT);
if (existingNum == INVALID_FE_COUNT) {
Log.e(TAG, "existingNum is -1 for " + frontendType);
return -1;
}
int maxUsableNum = mFrontendMaxUsableNums.get(frontendType, INVALID_FE_COUNT);
if (maxUsableNum == INVALID_FE_COUNT) {
return existingNum;
} else {
return maxUsableNum;
}
}
private boolean isFrontendMaxNumUseReached(int frontendType) {
int maxUsableNum = mFrontendMaxUsableNums.get(frontendType, INVALID_FE_COUNT);
if (maxUsableNum == INVALID_FE_COUNT) {
return false;
}
int useNum = mFrontendUsedNums.get(frontendType, INVALID_FE_COUNT);
if (useNum == INVALID_FE_COUNT) {
useNum = 0;
}
return useNum >= maxUsableNum;
}
private void increFrontendNum(SparseIntArray targetNums, int frontendType) {
int num = targetNums.get(frontendType, INVALID_FE_COUNT);
if (num == INVALID_FE_COUNT) {
targetNums.put(frontendType, 1);
} else {
targetNums.put(frontendType, num + 1);
}
}
private void decreFrontendNum(SparseIntArray targetNums, int frontendType) {
int num = targetNums.get(frontendType, INVALID_FE_COUNT);
if (num != INVALID_FE_COUNT) {
targetNums.put(frontendType, num - 1);
}
}
private void replaceFeResourceMap(Map<Integer, FrontendResource> srcMap, Map<Integer,
FrontendResource> dstMap) {
if (dstMap != null) {
dstMap.clear();
if (srcMap != null && srcMap.size() > 0) {
dstMap.putAll(srcMap);
}
}
}
private void replaceFeCounts(SparseIntArray srcCounts, SparseIntArray dstCounts) {
if (dstCounts != null) {
dstCounts.clear();
if (srcCounts != null) {
for (int i = 0; i < srcCounts.size(); i++) {
dstCounts.put(srcCounts.keyAt(i), srcCounts.valueAt(i));
}
}
}
}
private void dumpMap(Map<?, ?> targetMap, String headline, String delimiter,
IndentingPrintWriter pw) {
if (targetMap != null) {
pw.println(headline);
pw.increaseIndent();
for (Map.Entry<?, ?> entry : targetMap.entrySet()) {
pw.print(entry.getKey() + " : " + entry.getValue());
pw.print(delimiter);
}
pw.println();
pw.decreaseIndent();
}
}
private void dumpSIA(SparseIntArray array, String headline, String delimiter,
IndentingPrintWriter pw) {
if (array != null) {
pw.println(headline);
pw.increaseIndent();
for (int i = 0; i < array.size(); i++) {
pw.print(array.keyAt(i) + " : " + array.valueAt(i));
pw.print(delimiter);
}
pw.println();
pw.decreaseIndent();
}
}
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);
increFrontendNum(mFrontendExistingNums, newFe.getType());
}
private void addDemuxResource(DemuxResource newDemux) {
mDemuxResources.put(newDemux.getHandle(), newDemux);
}
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());
}
decreFrontendNum(mFrontendExistingNums, fe.getType());
mFrontendResources.remove(removingHandle);
}
private void removeDemuxResource(int removingHandle) {
DemuxResource demux = getDemuxResource(removingHandle);
if (demux == null) {
return;
}
if (demux.isInUse()) {
releaseDemuxInternal(demux);
}
mDemuxResources.remove(removingHandle);
}
@VisibleForTesting
@Nullable
protected LnbResource getLnbResource(int lnbHandle) {
return mLnbResources.get(lnbHandle);
}
@VisibleForTesting
protected Map<Integer, LnbResource> 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<Integer, CasResource> getCasResources() {
return mCasResources;
}
@VisibleForTesting
protected Map<Integer, CiCamResource> 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);
// it may be called by unregisterClientProfileInternal under test
synchronized (mLock) {
ResourcesReclaimListenerRecord record = mListeners.remove(clientId);
if (record != null) {
record.getListener().asBinder().unlinkToDeath(record, 0);
}
}
}
private void clearFrontendAndClientMapping(ClientProfile profile) {
// TODO: check if this check is really needed
if (profile == null) {
return;
}
for (Integer feId : profile.getInUseFrontendHandles()) {
FrontendResource fe = getFrontendResource(feId);
int ownerClientId = fe.getOwnerClientId();
if (ownerClientId == profile.getId()) {
fe.removeOwner();
continue;
}
ClientProfile ownerClientProfile = getClientProfile(ownerClientId);
if (ownerClientProfile != null) {
ownerClientProfile.stopSharingFrontend(profile.getId());
}
}
int primaryFeId = profile.getPrimaryFrontend();
if (primaryFeId != TunerResourceManager.INVALID_RESOURCE_HANDLE) {
FrontendResource primaryFe = getFrontendResource(primaryFeId);
if (primaryFe != null) {
decreFrontendNum(mFrontendUsedNums, primaryFe.getType());
}
}
profile.releaseFrontend();
}
private void clearAllResourcesAndClientMapping(ClientProfile profile) {
// TODO: check if this check is really needed. Maybe needed for reclaimResource path.
if (profile == null) {
return;
}
// 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 Demux
for (Integer demuxHandle : profile.getInUseDemuxHandles()) {
getDemuxResource(demuxHandle).removeOwner();
}
// 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);
}
}