1 /*
2  * Copyright 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.tv.tunerresourcemanager;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.app.ActivityManager;
22 import android.app.ActivityManager.RunningAppProcessInfo;
23 import android.content.Context;
24 import android.content.pm.PackageManager;
25 import android.hardware.tv.tuner.DemuxFilterMainType;
26 import android.media.IResourceManagerService;
27 import android.media.tv.TvInputManager;
28 import android.media.tv.tunerresourcemanager.CasSessionRequest;
29 import android.media.tv.tunerresourcemanager.IResourcesReclaimListener;
30 import android.media.tv.tunerresourcemanager.ITunerResourceManager;
31 import android.media.tv.tunerresourcemanager.ResourceClientProfile;
32 import android.media.tv.tunerresourcemanager.TunerCiCamRequest;
33 import android.media.tv.tunerresourcemanager.TunerDemuxInfo;
34 import android.media.tv.tunerresourcemanager.TunerDemuxRequest;
35 import android.media.tv.tunerresourcemanager.TunerDescramblerRequest;
36 import android.media.tv.tunerresourcemanager.TunerFrontendInfo;
37 import android.media.tv.tunerresourcemanager.TunerFrontendRequest;
38 import android.media.tv.tunerresourcemanager.TunerLnbRequest;
39 import android.media.tv.tunerresourcemanager.TunerResourceManager;
40 import android.os.Binder;
41 import android.os.IBinder;
42 import android.os.RemoteException;
43 import android.os.SystemClock;
44 import android.os.SystemProperties;
45 import android.util.IndentingPrintWriter;
46 import android.util.Log;
47 import android.util.Slog;
48 import android.util.SparseIntArray;
49 
50 import com.android.internal.annotations.GuardedBy;
51 import com.android.internal.annotations.VisibleForTesting;
52 import com.android.server.SystemService;
53 
54 import java.io.FileDescriptor;
55 import java.io.PrintWriter;
56 import java.util.HashMap;
57 import java.util.HashSet;
58 import java.util.List;
59 import java.util.Map;
60 import java.util.Set;
61 import java.util.concurrent.TimeUnit;
62 import java.util.concurrent.locks.Condition;
63 import java.util.concurrent.locks.ReentrantLock;
64 
65 /**
66  * This class provides a system service that manages the TV tuner resources.
67  *
68  * @hide
69  */
70 public class TunerResourceManagerService extends SystemService implements IBinder.DeathRecipient {
71     private static final String TAG = "TunerResourceManagerService";
72     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
73 
74     public static final int INVALID_CLIENT_ID = -1;
75     private static final int MAX_CLIENT_PRIORITY = 1000;
76     private static final long INVALID_THREAD_ID = -1;
77     private static final long TRMS_LOCK_TIMEOUT = 500;
78 
79     private static final int INVALID_FE_COUNT = -1;
80 
81     // Map of the registered client profiles
82     private Map<Integer, ClientProfile> mClientProfiles = new HashMap<>();
83     private int mNextUnusedClientId = 0;
84 
85     // Map of the current available frontend resources
86     private Map<Integer, FrontendResource> mFrontendResources = new HashMap<>();
87     // SparseIntArray of the max usable number for each frontend resource type
88     private SparseIntArray mFrontendMaxUsableNums = new SparseIntArray();
89     // SparseIntArray of the currently used number for each frontend resource type
90     private SparseIntArray mFrontendUsedNums = new SparseIntArray();
91     // SparseIntArray of the existing number for each frontend resource type
92     private SparseIntArray mFrontendExistingNums = new SparseIntArray();
93 
94     // Backups for the frontend resource maps for enabling testing with custom resource maps
95     // such as TunerTest.testHasUnusedFrontend1()
96     private Map<Integer, FrontendResource> mFrontendResourcesBackup = new HashMap<>();
97     private SparseIntArray mFrontendMaxUsableNumsBackup = new SparseIntArray();
98     private SparseIntArray mFrontendUsedNumsBackup = new SparseIntArray();
99     private SparseIntArray mFrontendExistingNumsBackup = new SparseIntArray();
100 
101     // Map of the current available demux resources
102     private Map<Integer, DemuxResource> mDemuxResources = new HashMap<>();
103     // Map of the current available lnb resources
104     private Map<Integer, LnbResource> mLnbResources = new HashMap<>();
105     // Map of the current available Cas resources
106     private Map<Integer, CasResource> mCasResources = new HashMap<>();
107     // Map of the current available CiCam resources
108     private Map<Integer, CiCamResource> mCiCamResources = new HashMap<>();
109 
110     @GuardedBy("mLock")
111     private Map<Integer, ResourcesReclaimListenerRecord> mListeners = new HashMap<>();
112 
113     private TvInputManager mTvInputManager;
114     private ActivityManager mActivityManager;
115     private IResourceManagerService mMediaResourceManager;
116     private UseCasePriorityHints mPriorityCongfig = new UseCasePriorityHints();
117 
118     // An internal resource request count to help generate resource handle.
119     private int mResourceRequestCount = 0;
120 
121     // Used to synchronize the access to the service.
122     private final Object mLock = new Object();
123 
124     private final ReentrantLock mLockForTRMSLock = new ReentrantLock();
125     private final Condition mTunerApiLockReleasedCV = mLockForTRMSLock.newCondition();
126     private int mTunerApiLockHolder = INVALID_CLIENT_ID;
127     private long mTunerApiLockHolderThreadId = INVALID_THREAD_ID;
128     private int mTunerApiLockNestedCount = 0;
129 
TunerResourceManagerService(@ullable Context context)130     public TunerResourceManagerService(@Nullable Context context) {
131         super(context);
132     }
133 
134     @Override
onStart()135     public void onStart() {
136         onStart(false /*isForTesting*/);
137     }
138 
139     @VisibleForTesting
onStart(boolean isForTesting)140     protected void onStart(boolean isForTesting) {
141         if (!isForTesting) {
142             publishBinderService(Context.TV_TUNER_RESOURCE_MGR_SERVICE, new BinderService());
143         }
144         mTvInputManager = (TvInputManager) getContext().getSystemService(Context.TV_INPUT_SERVICE);
145         mActivityManager =
146                 (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE);
147         mPriorityCongfig.parse();
148 
149         // Call SystemProperties.set() in mock app will throw exception because of permission.
150         if (!isForTesting) {
151             final boolean lazyHal = SystemProperties.getBoolean("ro.tuner.lazyhal", false);
152             if (!lazyHal) {
153                 // The HAL is not a lazy HAL, enable the tuner server.
154                 SystemProperties.set("tuner.server.enable", "true");
155             }
156         }
157 
158         if (mMediaResourceManager == null) {
159             IBinder mediaResourceManagerBinder = getBinderService("media.resource_manager");
160             if (mediaResourceManagerBinder == null) {
161                 Slog.w(TAG, "Resource Manager Service not available.");
162                 return;
163             }
164             try {
165                 mediaResourceManagerBinder.linkToDeath(this, /*flags*/ 0);
166             } catch (RemoteException e) {
167                 Slog.w(TAG, "Could not link to death of native resource manager service.");
168                 return;
169             }
170             mMediaResourceManager = IResourceManagerService.Stub.asInterface(
171                     mediaResourceManagerBinder);
172         }
173     }
174 
175     private final class BinderService extends ITunerResourceManager.Stub {
176         @Override
registerClientProfile(@onNull ResourceClientProfile profile, @NonNull IResourcesReclaimListener listener, @NonNull int[] clientId)177         public void registerClientProfile(@NonNull ResourceClientProfile profile,
178                 @NonNull IResourcesReclaimListener listener, @NonNull int[] clientId)
179                 throws RemoteException {
180             enforceTrmAccessPermission("registerClientProfile");
181             enforceTunerAccessPermission("registerClientProfile");
182             if (profile == null) {
183                 throw new RemoteException("ResourceClientProfile can't be null");
184             }
185 
186             if (clientId == null) {
187                 throw new RemoteException("clientId can't be null!");
188             }
189 
190             if (listener == null) {
191                 throw new RemoteException("IResourcesReclaimListener can't be null!");
192             }
193 
194             if (!mPriorityCongfig.isDefinedUseCase(profile.useCase)) {
195                 throw new RemoteException("Use undefined client use case:" + profile.useCase);
196             }
197 
198             synchronized (mLock) {
199                 registerClientProfileInternal(profile, listener, clientId);
200             }
201         }
202 
203         @Override
unregisterClientProfile(int clientId)204         public void unregisterClientProfile(int clientId) throws RemoteException {
205             enforceTrmAccessPermission("unregisterClientProfile");
206             synchronized (mLock) {
207                 if (!checkClientExists(clientId)) {
208                     Slog.e(TAG, "Unregistering non exists client:" + clientId);
209                     return;
210                 }
211                 unregisterClientProfileInternal(clientId);
212             }
213         }
214 
215         @Override
updateClientPriority(int clientId, int priority, int niceValue)216         public boolean updateClientPriority(int clientId, int priority, int niceValue) {
217             enforceTrmAccessPermission("updateClientPriority");
218             synchronized (mLock) {
219                 return updateClientPriorityInternal(clientId, priority, niceValue);
220             }
221         }
222 
223         @Override
hasUnusedFrontend(int frontendType)224         public boolean hasUnusedFrontend(int frontendType) {
225             enforceTrmAccessPermission("hasUnusedFrontend");
226             synchronized (mLock) {
227                 return hasUnusedFrontendInternal(frontendType);
228             }
229         }
230 
231         @Override
isLowestPriority(int clientId, int frontendType)232         public boolean isLowestPriority(int clientId, int frontendType)
233                 throws RemoteException {
234             enforceTrmAccessPermission("isLowestPriority");
235             synchronized (mLock) {
236                 if (!checkClientExists(clientId)) {
237                     throw new RemoteException("isLowestPriority called from unregistered client: "
238                             + clientId);
239                 }
240                 return isLowestPriorityInternal(clientId, frontendType);
241             }
242         }
243 
244         @Override
setFrontendInfoList(@onNull TunerFrontendInfo[] infos)245         public void setFrontendInfoList(@NonNull TunerFrontendInfo[] infos) throws RemoteException {
246             enforceTrmAccessPermission("setFrontendInfoList");
247             if (infos == null) {
248                 throw new RemoteException("TunerFrontendInfo can't be null");
249             }
250             synchronized (mLock) {
251                 setFrontendInfoListInternal(infos);
252             }
253         }
254 
255         @Override
setDemuxInfoList(@onNull TunerDemuxInfo[] infos)256         public void setDemuxInfoList(@NonNull TunerDemuxInfo[] infos) throws RemoteException {
257             enforceTrmAccessPermission("setDemuxInfoList");
258             if (infos == null) {
259                 throw new RemoteException("TunerDemuxInfo can't be null");
260             }
261             synchronized (mLock) {
262                 setDemuxInfoListInternal(infos);
263             }
264         }
265 
266         @Override
updateCasInfo(int casSystemId, int maxSessionNum)267         public void updateCasInfo(int casSystemId, int maxSessionNum) {
268             enforceTrmAccessPermission("updateCasInfo");
269             synchronized (mLock) {
270                 updateCasInfoInternal(casSystemId, maxSessionNum);
271             }
272         }
273 
274         @Override
setLnbInfoList(int[] lnbHandles)275         public void setLnbInfoList(int[] lnbHandles) throws RemoteException {
276             enforceTrmAccessPermission("setLnbInfoList");
277             if (lnbHandles == null) {
278                 throw new RemoteException("Lnb handle list can't be null");
279             }
280             synchronized (mLock) {
281                 setLnbInfoListInternal(lnbHandles);
282             }
283         }
284 
285         @Override
requestFrontend(@onNull TunerFrontendRequest request, @NonNull int[] frontendHandle)286         public boolean requestFrontend(@NonNull TunerFrontendRequest request,
287                 @NonNull int[] frontendHandle) {
288             enforceTunerAccessPermission("requestFrontend");
289             enforceTrmAccessPermission("requestFrontend");
290             if (frontendHandle == null) {
291                 Slog.e(TAG, "frontendHandle can't be null");
292                 return false;
293             }
294             synchronized (mLock) {
295                 if (!checkClientExists(request.clientId)) {
296                     Slog.e(TAG, "Request frontend from unregistered client: "
297                             + request.clientId);
298                     return false;
299                 }
300                 // If the request client is holding or sharing a frontend, throw an exception.
301                 if (!getClientProfile(request.clientId).getInUseFrontendHandles().isEmpty()) {
302                     Slog.e(TAG, "Release frontend before requesting another one. Client id: "
303                             + request.clientId);
304                     return false;
305                 }
306                 return requestFrontendInternal(request, frontendHandle);
307             }
308         }
309 
310         @Override
setMaxNumberOfFrontends(int frontendType, int maxUsableNum)311         public boolean setMaxNumberOfFrontends(int frontendType, int maxUsableNum) {
312             enforceTunerAccessPermission("setMaxNumberOfFrontends");
313             enforceTrmAccessPermission("setMaxNumberOfFrontends");
314             if (maxUsableNum < 0) {
315                 Slog.w(TAG, "setMaxNumberOfFrontends failed with maxUsableNum:" + maxUsableNum
316                         + " frontendType:" + frontendType);
317                 return false;
318             }
319             synchronized (mLock) {
320                 return setMaxNumberOfFrontendsInternal(frontendType, maxUsableNum);
321             }
322         }
323 
324         @Override
getMaxNumberOfFrontends(int frontendType)325         public int getMaxNumberOfFrontends(int frontendType) {
326             enforceTunerAccessPermission("getMaxNumberOfFrontends");
327             enforceTrmAccessPermission("getMaxNumberOfFrontends");
328             synchronized (mLock) {
329                 return getMaxNumberOfFrontendsInternal(frontendType);
330             }
331         }
332 
333         @Override
shareFrontend(int selfClientId, int targetClientId)334         public void shareFrontend(int selfClientId, int targetClientId) throws RemoteException {
335             enforceTunerAccessPermission("shareFrontend");
336             enforceTrmAccessPermission("shareFrontend");
337             synchronized (mLock) {
338                 if (!checkClientExists(selfClientId)) {
339                     throw new RemoteException("Share frontend request from an unregistered client:"
340                             + selfClientId);
341                 }
342                 if (!checkClientExists(targetClientId)) {
343                     throw new RemoteException("Request to share frontend with an unregistered "
344                             + "client:" + targetClientId);
345                 }
346                 if (getClientProfile(targetClientId).getInUseFrontendHandles().isEmpty()) {
347                     throw new RemoteException("Request to share frontend with a client that has no "
348                             + "frontend resources. Target client id:" + targetClientId);
349                 }
350                 shareFrontendInternal(selfClientId, targetClientId);
351             }
352         }
353 
354         @Override
transferOwner(int resourceType, int currentOwnerId, int newOwnerId)355         public boolean transferOwner(int resourceType, int currentOwnerId, int newOwnerId) {
356             enforceTunerAccessPermission("transferOwner");
357             enforceTrmAccessPermission("transferOwner");
358             synchronized (mLock) {
359                 if (!checkClientExists(currentOwnerId)) {
360                     Slog.e(TAG, "currentOwnerId:" + currentOwnerId + " does not exit");
361                     return false;
362                 }
363                 if (!checkClientExists(newOwnerId)) {
364                     Slog.e(TAG, "newOwnerId:" + newOwnerId + " does not exit");
365                     return false;
366                 }
367                 return transferOwnerInternal(resourceType, currentOwnerId, newOwnerId);
368             }
369         }
370 
371         @Override
requestDemux(@onNull TunerDemuxRequest request, @NonNull int[] demuxHandle)372         public boolean requestDemux(@NonNull TunerDemuxRequest request,
373                     @NonNull int[] demuxHandle) throws RemoteException {
374             enforceTunerAccessPermission("requestDemux");
375             enforceTrmAccessPermission("requestDemux");
376             if (demuxHandle == null) {
377                 throw new RemoteException("demuxHandle can't be null");
378             }
379             synchronized (mLock) {
380                 if (!checkClientExists(request.clientId)) {
381                     throw new RemoteException("Request demux from unregistered client:"
382                             + request.clientId);
383                 }
384                 return requestDemuxInternal(request, demuxHandle);
385             }
386         }
387 
388         @Override
requestDescrambler(@onNull TunerDescramblerRequest request, @NonNull int[] descramblerHandle)389         public boolean requestDescrambler(@NonNull TunerDescramblerRequest request,
390                     @NonNull int[] descramblerHandle) throws RemoteException {
391             enforceDescramblerAccessPermission("requestDescrambler");
392             enforceTrmAccessPermission("requestDescrambler");
393             if (descramblerHandle == null) {
394                 throw new RemoteException("descramblerHandle can't be null");
395             }
396             synchronized (mLock) {
397                 if (!checkClientExists(request.clientId)) {
398                     throw new RemoteException("Request descrambler from unregistered client:"
399                             + request.clientId);
400                 }
401                 return requestDescramblerInternal(request, descramblerHandle);
402             }
403         }
404 
405         @Override
requestCasSession(@onNull CasSessionRequest request, @NonNull int[] casSessionHandle)406         public boolean requestCasSession(@NonNull CasSessionRequest request,
407                 @NonNull int[] casSessionHandle) throws RemoteException {
408             enforceTrmAccessPermission("requestCasSession");
409             if (casSessionHandle == null) {
410                 throw new RemoteException("casSessionHandle can't be null");
411             }
412             synchronized (mLock) {
413                 if (!checkClientExists(request.clientId)) {
414                     throw new RemoteException("Request cas from unregistered client:"
415                             + request.clientId);
416                 }
417                 return requestCasSessionInternal(request, casSessionHandle);
418             }
419         }
420 
421         @Override
requestCiCam(@onNull TunerCiCamRequest request, @NonNull int[] ciCamHandle)422         public boolean requestCiCam(@NonNull TunerCiCamRequest request,
423                 @NonNull int[] ciCamHandle) throws RemoteException {
424             enforceTrmAccessPermission("requestCiCam");
425             if (ciCamHandle == null) {
426                 throw new RemoteException("ciCamHandle can't be null");
427             }
428             synchronized (mLock) {
429                 if (!checkClientExists(request.clientId)) {
430                     throw new RemoteException("Request ciCam from unregistered client:"
431                             + request.clientId);
432                 }
433                 return requestCiCamInternal(request, ciCamHandle);
434             }
435         }
436 
437         @Override
requestLnb(@onNull TunerLnbRequest request, @NonNull int[] lnbHandle)438         public boolean requestLnb(@NonNull TunerLnbRequest request, @NonNull int[] lnbHandle)
439                 throws RemoteException {
440             enforceTunerAccessPermission("requestLnb");
441             enforceTrmAccessPermission("requestLnb");
442             if (lnbHandle == null) {
443                 throw new RemoteException("lnbHandle can't be null");
444             }
445             synchronized (mLock) {
446                 if (!checkClientExists(request.clientId)) {
447                     throw new RemoteException("Request lnb from unregistered client:"
448                             + request.clientId);
449                 }
450                 return requestLnbInternal(request, lnbHandle);
451             }
452         }
453 
454         @Override
releaseFrontend(int frontendHandle, int clientId)455         public void releaseFrontend(int frontendHandle, int clientId) throws RemoteException {
456             enforceTunerAccessPermission("releaseFrontend");
457             enforceTrmAccessPermission("releaseFrontend");
458             if (!validateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND,
459                     frontendHandle)) {
460                 throw new RemoteException("frontendHandle can't be invalid");
461             }
462             synchronized (mLock) {
463                 if (!checkClientExists(clientId)) {
464                     throw new RemoteException("Release frontend from unregistered client:"
465                             + clientId);
466                 }
467                 FrontendResource fe = getFrontendResource(frontendHandle);
468                 if (fe == null) {
469                     throw new RemoteException("Releasing frontend does not exist.");
470                 }
471                 int ownerClientId = fe.getOwnerClientId();
472                 ClientProfile ownerProfile = getClientProfile(ownerClientId);
473                 if (ownerClientId != clientId
474                         && (ownerProfile != null
475                               && !ownerProfile.getShareFeClientIds().contains(clientId))) {
476                     throw new RemoteException(
477                             "Client is not the current owner of the releasing fe.");
478                 }
479                 releaseFrontendInternal(fe, clientId);
480             }
481         }
482 
483         @Override
releaseDemux(int demuxHandle, int clientId)484         public void releaseDemux(int demuxHandle, int clientId) throws RemoteException {
485             enforceTunerAccessPermission("releaseDemux");
486             enforceTrmAccessPermission("releaseDemux");
487             if (DEBUG) {
488                 Slog.e(TAG, "releaseDemux(demuxHandle=" + demuxHandle + ")");
489             }
490 
491             synchronized (mLock) {
492                 // For Tuner 2.0 and below or any HW constraint devices that are unable to support
493                 // ITuner.openDemuxById(), demux resources are not really managed under TRM and
494                 // mDemuxResources.size() will be zero
495                 if (mDemuxResources.size() == 0) {
496                     return;
497                 }
498 
499                 if (!checkClientExists(clientId)) {
500                     throw new RemoteException("Release demux for unregistered client:" + clientId);
501                 }
502                 DemuxResource demux = getDemuxResource(demuxHandle);
503                 if (demux == null) {
504                     throw new RemoteException("Releasing demux does not exist.");
505                 }
506                 if (demux.getOwnerClientId() != clientId) {
507                     throw new RemoteException("Client is not the current owner "
508                             + "of the releasing demux.");
509                 }
510                 releaseDemuxInternal(demux);
511             }
512         }
513 
514         @Override
releaseDescrambler(int descramblerHandle, int clientId)515         public void releaseDescrambler(int descramblerHandle, int clientId) {
516             enforceTunerAccessPermission("releaseDescrambler");
517             enforceTrmAccessPermission("releaseDescrambler");
518             if (DEBUG) {
519                 Slog.d(TAG, "releaseDescrambler(descramblerHandle=" + descramblerHandle + ")");
520             }
521         }
522 
523         @Override
releaseCasSession(int casSessionHandle, int clientId)524         public void releaseCasSession(int casSessionHandle, int clientId) throws RemoteException {
525             enforceTrmAccessPermission("releaseCasSession");
526             if (!validateResourceHandle(
527                     TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, casSessionHandle)) {
528                 throw new RemoteException("casSessionHandle can't be invalid");
529             }
530             synchronized (mLock) {
531                 if (!checkClientExists(clientId)) {
532                     throw new RemoteException("Release cas from unregistered client:" + clientId);
533                 }
534                 int casSystemId = getClientProfile(clientId).getInUseCasSystemId();
535                 CasResource cas = getCasResource(casSystemId);
536                 if (cas == null) {
537                     throw new RemoteException("Releasing cas does not exist.");
538                 }
539                 if (!cas.getOwnerClientIds().contains(clientId)) {
540                     throw new RemoteException(
541                             "Client is not the current owner of the releasing cas.");
542                 }
543                 releaseCasSessionInternal(cas, clientId);
544             }
545         }
546 
547         @Override
releaseCiCam(int ciCamHandle, int clientId)548         public void releaseCiCam(int ciCamHandle, int clientId) throws RemoteException {
549             enforceTrmAccessPermission("releaseCiCam");
550             if (!validateResourceHandle(
551                     TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM, ciCamHandle)) {
552                 throw new RemoteException("ciCamHandle can't be invalid");
553             }
554             synchronized (mLock) {
555                 if (!checkClientExists(clientId)) {
556                     throw new RemoteException("Release ciCam from unregistered client:" + clientId);
557                 }
558                 int ciCamId = getClientProfile(clientId).getInUseCiCamId();
559                 if (ciCamId != getResourceIdFromHandle(ciCamHandle)) {
560                     throw new RemoteException("The client " + clientId + " is not the owner of "
561                             + "the releasing ciCam.");
562                 }
563                 CiCamResource ciCam = getCiCamResource(ciCamId);
564                 if (ciCam == null) {
565                     throw new RemoteException("Releasing ciCam does not exist.");
566                 }
567                 if (!ciCam.getOwnerClientIds().contains(clientId)) {
568                     throw new RemoteException(
569                             "Client is not the current owner of the releasing ciCam.");
570                 }
571                 releaseCiCamInternal(ciCam, clientId);
572             }
573         }
574 
575         @Override
releaseLnb(int lnbHandle, int clientId)576         public void releaseLnb(int lnbHandle, int clientId) throws RemoteException {
577             enforceTunerAccessPermission("releaseLnb");
578             enforceTrmAccessPermission("releaseLnb");
579             if (!validateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_LNB, lnbHandle)) {
580                 throw new RemoteException("lnbHandle can't be invalid");
581             }
582             synchronized (mLock) {
583                 if (!checkClientExists(clientId)) {
584                     throw new RemoteException("Release lnb from unregistered client:" + clientId);
585                 }
586                 LnbResource lnb = getLnbResource(lnbHandle);
587                 if (lnb == null) {
588                     throw new RemoteException("Releasing lnb does not exist.");
589                 }
590                 if (lnb.getOwnerClientId() != clientId) {
591                     throw new RemoteException("Client is not the current owner "
592                             + "of the releasing lnb.");
593                 }
594                 releaseLnbInternal(lnb);
595             }
596         }
597 
598         @Override
isHigherPriority( ResourceClientProfile challengerProfile, ResourceClientProfile holderProfile)599         public boolean isHigherPriority(
600                 ResourceClientProfile challengerProfile, ResourceClientProfile holderProfile)
601                 throws RemoteException {
602             enforceTrmAccessPermission("isHigherPriority");
603             if (challengerProfile == null || holderProfile == null) {
604                 throw new RemoteException("Client profiles can't be null.");
605             }
606             synchronized (mLock) {
607                 return isHigherPriorityInternal(challengerProfile, holderProfile);
608             }
609         }
610 
611         @Override
storeResourceMap(int resourceType)612         public void storeResourceMap(int resourceType) {
613             enforceTrmAccessPermission("storeResourceMap");
614             synchronized (mLock) {
615                 storeResourceMapInternal(resourceType);
616             }
617         }
618 
619         @Override
clearResourceMap(int resourceType)620         public void clearResourceMap(int resourceType) {
621             enforceTrmAccessPermission("clearResourceMap");
622             synchronized (mLock) {
623                 clearResourceMapInternal(resourceType);
624             }
625         }
626 
627         @Override
restoreResourceMap(int resourceType)628         public void restoreResourceMap(int resourceType) {
629             enforceTrmAccessPermission("restoreResourceMap");
630             synchronized (mLock) {
631                 restoreResourceMapInternal(resourceType);
632             }
633         }
634 
635         @Override
acquireLock(int clientId, long clientThreadId)636         public boolean acquireLock(int clientId, long clientThreadId) {
637             enforceTrmAccessPermission("acquireLock");
638             // this must not be locked with mLock
639             return acquireLockInternal(clientId, clientThreadId, TRMS_LOCK_TIMEOUT);
640         }
641 
642         @Override
releaseLock(int clientId)643         public boolean releaseLock(int clientId) {
644             enforceTrmAccessPermission("releaseLock");
645             // this must not be locked with mLock
646             return releaseLockInternal(clientId, TRMS_LOCK_TIMEOUT, false, false);
647         }
648 
649         @Override
dump(FileDescriptor fd, final PrintWriter writer, String[] args)650         protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
651             final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
652 
653             if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
654                     != PackageManager.PERMISSION_GRANTED) {
655                 pw.println("Permission Denial: can't dump!");
656                 return;
657             }
658 
659             synchronized (mLock) {
660                 dumpMap(mClientProfiles, "ClientProfiles:", "\n", pw);
661                 dumpMap(mFrontendResources, "FrontendResources:", "\n", pw);
662                 dumpSIA(mFrontendExistingNums, "FrontendExistingNums:", ", ", pw);
663                 dumpSIA(mFrontendUsedNums, "FrontendUsedNums:", ", ", pw);
664                 dumpSIA(mFrontendMaxUsableNums, "FrontendMaxUsableNums:", ", ", pw);
665                 dumpMap(mFrontendResourcesBackup, "FrontendResourcesBackUp:", "\n", pw);
666                 dumpSIA(mFrontendExistingNumsBackup, "FrontendExistingNumsBackup:", ", ", pw);
667                 dumpSIA(mFrontendUsedNumsBackup, "FrontendUsedNumsBackup:", ", ", pw);
668                 dumpSIA(mFrontendMaxUsableNumsBackup, "FrontendUsedNumsBackup:", ", ", pw);
669                 dumpMap(mDemuxResources, "DemuxResource:", "\n", pw);
670                 dumpMap(mLnbResources, "LnbResource:", "\n", pw);
671                 dumpMap(mCasResources, "CasResource:", "\n", pw);
672                 dumpMap(mCiCamResources, "CiCamResource:", "\n", pw);
673                 dumpMap(mListeners, "Listners:", "\n", pw);
674             }
675         }
676 
677         @Override
getClientPriority(int useCase, int pid)678         public int getClientPriority(int useCase, int pid) throws RemoteException {
679             enforceTrmAccessPermission("getClientPriority");
680             synchronized (mLock) {
681                 return TunerResourceManagerService.this.getClientPriority(
682                         useCase, checkIsForeground(pid));
683             }
684         }
685         @Override
getConfigPriority(int useCase, boolean isForeground)686         public int getConfigPriority(int useCase, boolean isForeground) throws RemoteException {
687             enforceTrmAccessPermission("getConfigPriority");
688             synchronized (mLock) {
689                 return TunerResourceManagerService.this.getClientPriority(useCase, isForeground);
690             }
691         }
692     }
693 
694     /**
695      * Handle the death of the native resource manager service
696      */
697     @Override
binderDied()698     public void binderDied() {
699         if (DEBUG) {
700             Slog.w(TAG, "Native media resource manager service has died");
701         }
702         synchronized (mLock) {
703             mMediaResourceManager = null;
704         }
705     }
706 
707     @VisibleForTesting
registerClientProfileInternal(ResourceClientProfile profile, IResourcesReclaimListener listener, int[] clientId)708     protected void registerClientProfileInternal(ResourceClientProfile profile,
709             IResourcesReclaimListener listener, int[] clientId) {
710         if (DEBUG) {
711             Slog.d(TAG, "registerClientProfile(clientProfile=" + profile + ")");
712         }
713 
714         clientId[0] = INVALID_CLIENT_ID;
715         if (mTvInputManager == null) {
716             Slog.e(TAG, "TvInputManager is null. Can't register client profile.");
717             return;
718         }
719         // TODO tell if the client already exists
720         clientId[0] = mNextUnusedClientId++;
721 
722         int pid = profile.tvInputSessionId == null
723                 ? Binder.getCallingPid() /*callingPid*/
724                 : mTvInputManager.getClientPid(profile.tvInputSessionId); /*tvAppId*/
725 
726         // Update Media Resource Manager with the tvAppId
727         if (profile.tvInputSessionId != null && mMediaResourceManager != null) {
728             try {
729                 mMediaResourceManager.overridePid(Binder.getCallingPid(), pid);
730             } catch (RemoteException e) {
731                 Slog.e(TAG, "Could not overridePid in resourceManagerSercice,"
732                         + " remote exception: " + e);
733             }
734         }
735 
736         ClientProfile clientProfile = new ClientProfile.Builder(clientId[0])
737                                               .tvInputSessionId(profile.tvInputSessionId)
738                                               .useCase(profile.useCase)
739                                               .processId(pid)
740                                               .build();
741         clientProfile.setPriority(
742                 getClientPriority(profile.useCase, checkIsForeground(pid)));
743 
744         addClientProfile(clientId[0], clientProfile, listener);
745     }
746 
747     @VisibleForTesting
unregisterClientProfileInternal(int clientId)748     protected void unregisterClientProfileInternal(int clientId) {
749         if (DEBUG) {
750             Slog.d(TAG, "unregisterClientProfile(clientId=" + clientId + ")");
751         }
752         removeClientProfile(clientId);
753         // Remove the Media Resource Manager callingPid to tvAppId mapping
754         if (mMediaResourceManager != null) {
755             try {
756                 mMediaResourceManager.overridePid(Binder.getCallingPid(), -1);
757             } catch (RemoteException e) {
758                 Slog.e(TAG, "Could not overridePid in resourceManagerSercice when unregister,"
759                         + " remote exception: " + e);
760             }
761         }
762     }
763 
764     @VisibleForTesting
updateClientPriorityInternal(int clientId, int priority, int niceValue)765     protected boolean updateClientPriorityInternal(int clientId, int priority, int niceValue) {
766         if (DEBUG) {
767             Slog.d(TAG,
768                     "updateClientPriority(clientId=" + clientId + ", priority=" + priority
769                             + ", niceValue=" + niceValue + ")");
770         }
771 
772         ClientProfile profile = getClientProfile(clientId);
773         if (profile == null) {
774             Slog.e(TAG,
775                     "Can not find client profile with id " + clientId
776                             + " when trying to update the client priority.");
777             return false;
778         }
779 
780         profile.overwritePriority(priority);
781         profile.setNiceValue(niceValue);
782 
783         return true;
784     }
785 
786 
hasUnusedFrontendInternal(int frontendType)787     protected boolean hasUnusedFrontendInternal(int frontendType) {
788         for (FrontendResource fr : getFrontendResources().values()) {
789             if (fr.getType() == frontendType && !fr.isInUse()) {
790                 return true;
791             }
792         }
793         return false;
794     }
795 
isLowestPriorityInternal(int clientId, int frontendType)796     protected boolean isLowestPriorityInternal(int clientId, int frontendType)
797             throws RemoteException {
798         // Update the client priority
799         ClientProfile requestClient = getClientProfile(clientId);
800         if (requestClient == null) {
801             return true;
802         }
803         clientPriorityUpdateOnRequest(requestClient);
804         int clientPriority = requestClient.getPriority();
805 
806         // Check if there is another holder with lower priority
807         for (FrontendResource fr : getFrontendResources().values()) {
808             if (fr.getType() == frontendType && fr.isInUse()) {
809                 int priority = updateAndGetOwnerClientPriority(fr.getOwnerClientId());
810                 // Returns false only when the clientPriority is strictly greater
811                 // because false means that there is another reclaimable resource
812                 if (clientPriority > priority) {
813                     return false;
814                 }
815             }
816         }
817         return true;
818     }
819 
storeResourceMapInternal(int resourceType)820     protected void storeResourceMapInternal(int resourceType) {
821         switch (resourceType) {
822             case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND:
823                 replaceFeResourceMap(mFrontendResources, mFrontendResourcesBackup);
824                 replaceFeCounts(mFrontendExistingNums, mFrontendExistingNumsBackup);
825                 replaceFeCounts(mFrontendUsedNums, mFrontendUsedNumsBackup);
826                 replaceFeCounts(mFrontendMaxUsableNums, mFrontendMaxUsableNumsBackup);
827                 break;
828                 // TODO: implement for other resource type when needed
829             default:
830                 break;
831         }
832     }
833 
clearResourceMapInternal(int resourceType)834     protected void clearResourceMapInternal(int resourceType) {
835         switch (resourceType) {
836             case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND:
837                 replaceFeResourceMap(null, mFrontendResources);
838                 replaceFeCounts(null, mFrontendExistingNums);
839                 replaceFeCounts(null, mFrontendUsedNums);
840                 replaceFeCounts(null, mFrontendMaxUsableNums);
841                 break;
842                 // TODO: implement for other resource type when needed
843             default:
844                 break;
845         }
846     }
847 
restoreResourceMapInternal(int resourceType)848     protected void restoreResourceMapInternal(int resourceType) {
849         switch (resourceType) {
850             case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND:
851                 replaceFeResourceMap(mFrontendResourcesBackup, mFrontendResources);
852                 replaceFeCounts(mFrontendExistingNumsBackup, mFrontendExistingNums);
853                 replaceFeCounts(mFrontendUsedNumsBackup, mFrontendUsedNums);
854                 replaceFeCounts(mFrontendMaxUsableNumsBackup, mFrontendMaxUsableNums);
855                 break;
856                 // TODO: implement for other resource type when needed
857             default:
858                 break;
859         }
860     }
861 
862     @VisibleForTesting
setFrontendInfoListInternal(TunerFrontendInfo[] infos)863     protected void setFrontendInfoListInternal(TunerFrontendInfo[] infos) {
864         if (DEBUG) {
865             Slog.d(TAG, "updateFrontendInfo:");
866             for (int i = 0; i < infos.length; i++) {
867                 Slog.d(TAG, infos[i].toString());
868             }
869         }
870 
871         // A set to record the frontends pending on updating. Ids will be removed
872         // from this set once its updating finished. Any frontend left in this set when all
873         // the updates are done will be removed from mFrontendResources.
874         Set<Integer> updatingFrontendHandles = new HashSet<>(getFrontendResources().keySet());
875 
876         // Update frontendResources map and other mappings accordingly
877         for (int i = 0; i < infos.length; i++) {
878             if (getFrontendResource(infos[i].handle) != null) {
879                 if (DEBUG) {
880                     Slog.d(TAG, "Frontend handle=" + infos[i].handle + "exists.");
881                 }
882                 updatingFrontendHandles.remove(infos[i].handle);
883             } else {
884                 // Add a new fe resource
885                 FrontendResource newFe = new FrontendResource.Builder(infos[i].handle)
886                                                  .type(infos[i].type)
887                                                  .exclusiveGroupId(infos[i].exclusiveGroupId)
888                                                  .build();
889                 addFrontendResource(newFe);
890             }
891         }
892 
893         for (int removingHandle : updatingFrontendHandles) {
894             // update the exclusive group id member list
895             removeFrontendResource(removingHandle);
896         }
897     }
898 
899     @VisibleForTesting
setDemuxInfoListInternal(TunerDemuxInfo[] infos)900     protected void setDemuxInfoListInternal(TunerDemuxInfo[] infos) {
901         if (DEBUG) {
902             Slog.d(TAG, "updateDemuxInfo:");
903             for (int i = 0; i < infos.length; i++) {
904                 Slog.d(TAG, infos[i].toString());
905             }
906         }
907 
908         // A set to record the demuxes pending on updating. Ids will be removed
909         // from this set once its updating finished. Any demux left in this set when all
910         // the updates are done will be removed from mDemuxResources.
911         Set<Integer> updatingDemuxHandles = new HashSet<>(getDemuxResources().keySet());
912 
913         // Update demuxResources map and other mappings accordingly
914         for (int i = 0; i < infos.length; i++) {
915             if (getDemuxResource(infos[i].handle) != null) {
916                 if (DEBUG) {
917                     Slog.d(TAG, "Demux handle=" + infos[i].handle + "exists.");
918                 }
919                 updatingDemuxHandles.remove(infos[i].handle);
920             } else {
921                 // Add a new demux resource
922                 DemuxResource newDemux = new DemuxResource.Builder(infos[i].handle)
923                                                  .filterTypes(infos[i].filterTypes)
924                                                  .build();
925                 addDemuxResource(newDemux);
926             }
927         }
928 
929         for (int removingHandle : updatingDemuxHandles) {
930             // update the exclusive group id member list
931             removeDemuxResource(removingHandle);
932         }
933     }
934     @VisibleForTesting
setLnbInfoListInternal(int[] lnbHandles)935     protected void setLnbInfoListInternal(int[] lnbHandles) {
936         if (DEBUG) {
937             for (int i = 0; i < lnbHandles.length; i++) {
938                 Slog.d(TAG, "updateLnbInfo(lnbHanle=" + lnbHandles[i] + ")");
939             }
940         }
941 
942         // A set to record the Lnbs pending on updating. Handles will be removed
943         // from this set once its updating finished. Any lnb left in this set when all
944         // the updates are done will be removed from mLnbResources.
945         Set<Integer> updatingLnbHandles = new HashSet<>(getLnbResources().keySet());
946 
947         // Update lnbResources map and other mappings accordingly
948         for (int i = 0; i < lnbHandles.length; i++) {
949             if (getLnbResource(lnbHandles[i]) != null) {
950                 if (DEBUG) {
951                     Slog.d(TAG, "Lnb handle=" + lnbHandles[i] + "exists.");
952                 }
953                 updatingLnbHandles.remove(lnbHandles[i]);
954             } else {
955                 // Add a new lnb resource
956                 LnbResource newLnb = new LnbResource.Builder(lnbHandles[i]).build();
957                 addLnbResource(newLnb);
958             }
959         }
960 
961         for (int removingHandle : updatingLnbHandles) {
962             removeLnbResource(removingHandle);
963         }
964     }
965 
966     @VisibleForTesting
updateCasInfoInternal(int casSystemId, int maxSessionNum)967     protected void updateCasInfoInternal(int casSystemId, int maxSessionNum) {
968         if (DEBUG) {
969             Slog.d(TAG,
970                     "updateCasInfo(casSystemId=" + casSystemId
971                             + ", maxSessionNum=" + maxSessionNum + ")");
972         }
973         // If maxSessionNum is 0, removing the Cas Resource.
974         if (maxSessionNum == 0) {
975             removeCasResource(casSystemId);
976             removeCiCamResource(casSystemId);
977             return;
978         }
979         // If the Cas exists, updates the Cas Resource accordingly.
980         CasResource cas = getCasResource(casSystemId);
981         CiCamResource ciCam = getCiCamResource(casSystemId);
982         if (cas != null) {
983             if (cas.getUsedSessionNum() > maxSessionNum) {
984                 // Sort and release the short number of Cas resources.
985                 int releasingCasResourceNum = cas.getUsedSessionNum() - maxSessionNum;
986                 // TODO: handle CiCam session update.
987             }
988             cas.updateMaxSessionNum(maxSessionNum);
989             if (ciCam != null) {
990                 ciCam.updateMaxSessionNum(maxSessionNum);
991             }
992             return;
993         }
994         // Add the new Cas Resource.
995         cas = new CasResource.Builder(casSystemId)
996                              .maxSessionNum(maxSessionNum)
997                              .build();
998         ciCam = new CiCamResource.Builder(casSystemId)
999                              .maxSessionNum(maxSessionNum)
1000                              .build();
1001         addCasResource(cas);
1002         addCiCamResource(ciCam);
1003     }
1004 
1005     @VisibleForTesting
requestFrontendInternal(TunerFrontendRequest request, int[] frontendHandle)1006     protected boolean requestFrontendInternal(TunerFrontendRequest request, int[] frontendHandle) {
1007         if (DEBUG) {
1008             Slog.d(TAG, "requestFrontend(request=" + request + ")");
1009         }
1010 
1011         frontendHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
1012         ClientProfile requestClient = getClientProfile(request.clientId);
1013         // TODO: check if this is really needed
1014         if (requestClient == null) {
1015             return false;
1016         }
1017         clientPriorityUpdateOnRequest(requestClient);
1018         int grantingFrontendHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
1019         int inUseLowestPriorityFrHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
1020         // Priority max value is 1000
1021         int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
1022         boolean isRequestFromSameProcess = false;
1023         // If the desired frontend id was specified, we only need to check the frontend.
1024         boolean hasDesiredFrontend = request.desiredId != TunerFrontendRequest.DEFAULT_DESIRED_ID;
1025         for (FrontendResource fr : getFrontendResources().values()) {
1026             int frontendId = getResourceIdFromHandle(fr.getHandle());
1027             if (fr.getType() == request.frontendType
1028                     && (!hasDesiredFrontend || frontendId == request.desiredId)) {
1029                 if (!fr.isInUse()) {
1030                     // Unused resource cannot be acquired if the max is already reached, but
1031                     // TRM still has to look for the reclaim candidate
1032                     if (isFrontendMaxNumUseReached(request.frontendType)) {
1033                         continue;
1034                     }
1035                     // Grant unused frontend with no exclusive group members first.
1036                     if (fr.getExclusiveGroupMemberFeHandles().isEmpty()) {
1037                         grantingFrontendHandle = fr.getHandle();
1038                         break;
1039                     } else if (grantingFrontendHandle
1040                             == TunerResourceManager.INVALID_RESOURCE_HANDLE) {
1041                         // Grant the unused frontend with lower id first if all the unused
1042                         // frontends have exclusive group members.
1043                         grantingFrontendHandle = fr.getHandle();
1044                     }
1045                 } else if (grantingFrontendHandle == TunerResourceManager.INVALID_RESOURCE_HANDLE) {
1046                     // Record the frontend id with the lowest client priority among all the
1047                     // in use frontends when no available frontend has been found.
1048                     int priority = getFrontendHighestClientPriority(fr.getOwnerClientId());
1049                     if (currentLowestPriority > priority) {
1050                         inUseLowestPriorityFrHandle = fr.getHandle();
1051                         currentLowestPriority = priority;
1052                         isRequestFromSameProcess = (requestClient.getProcessId()
1053                             == (getClientProfile(fr.getOwnerClientId())).getProcessId());
1054                     }
1055                 }
1056             }
1057         }
1058 
1059         // Grant frontend when there is unused resource.
1060         if (grantingFrontendHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE) {
1061             frontendHandle[0] = grantingFrontendHandle;
1062             updateFrontendClientMappingOnNewGrant(grantingFrontendHandle, request.clientId);
1063             return true;
1064         }
1065 
1066         // When all the resources are occupied, grant the lowest priority resource if the
1067         // request client has higher priority.
1068         if (inUseLowestPriorityFrHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE
1069             && ((requestClient.getPriority() > currentLowestPriority) || (
1070             (requestClient.getPriority() == currentLowestPriority) && isRequestFromSameProcess))) {
1071             if (!reclaimResource(
1072                     getFrontendResource(inUseLowestPriorityFrHandle).getOwnerClientId(),
1073                     TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
1074                 return false;
1075             }
1076             frontendHandle[0] = inUseLowestPriorityFrHandle;
1077             updateFrontendClientMappingOnNewGrant(
1078                     inUseLowestPriorityFrHandle, request.clientId);
1079             return true;
1080         }
1081 
1082         return false;
1083     }
1084 
1085     @VisibleForTesting
shareFrontendInternal(int selfClientId, int targetClientId)1086     protected void shareFrontendInternal(int selfClientId, int targetClientId) {
1087         if (DEBUG) {
1088             Slog.d(TAG, "shareFrontend from " + selfClientId + " with " + targetClientId);
1089         }
1090         for (int feId : getClientProfile(targetClientId).getInUseFrontendHandles()) {
1091             getClientProfile(selfClientId).useFrontend(feId);
1092         }
1093         getClientProfile(targetClientId).shareFrontend(selfClientId);
1094     }
1095 
transferFeOwner(int currentOwnerId, int newOwnerId)1096     private boolean transferFeOwner(int currentOwnerId, int newOwnerId) {
1097         ClientProfile currentOwnerProfile = getClientProfile(currentOwnerId);
1098         ClientProfile newOwnerProfile = getClientProfile(newOwnerId);
1099         // change the owner of all the inUse frontend
1100         newOwnerProfile.shareFrontend(currentOwnerId);
1101         currentOwnerProfile.stopSharingFrontend(newOwnerId);
1102         for (int inUseHandle : newOwnerProfile.getInUseFrontendHandles()) {
1103             getFrontendResource(inUseHandle).setOwner(newOwnerId);
1104         }
1105         // change the primary frontend
1106         newOwnerProfile.setPrimaryFrontend(currentOwnerProfile.getPrimaryFrontend());
1107         currentOwnerProfile.setPrimaryFrontend(TunerResourceManager.INVALID_RESOURCE_HANDLE);
1108         // double check there is no other resources tied to the previous owner
1109         for (int inUseHandle : currentOwnerProfile.getInUseFrontendHandles()) {
1110             int ownerId = getFrontendResource(inUseHandle).getOwnerClientId();
1111             if (ownerId != newOwnerId) {
1112                 Slog.e(TAG, "something is wrong in transferFeOwner:" + inUseHandle
1113                         + ", " + ownerId + ", " + newOwnerId);
1114                 return false;
1115             }
1116         }
1117         return true;
1118     }
1119 
transferFeCiCamOwner(int currentOwnerId, int newOwnerId)1120     private boolean transferFeCiCamOwner(int currentOwnerId, int newOwnerId) {
1121         ClientProfile currentOwnerProfile = getClientProfile(currentOwnerId);
1122         ClientProfile newOwnerProfile = getClientProfile(newOwnerId);
1123 
1124         // link ciCamId to the new profile
1125         int ciCamId = currentOwnerProfile.getInUseCiCamId();
1126         newOwnerProfile.useCiCam(ciCamId);
1127 
1128         // set the new owner Id
1129         CiCamResource ciCam = getCiCamResource(ciCamId);
1130         ciCam.setOwner(newOwnerId);
1131 
1132         // unlink cicam resource from the original owner profile
1133         currentOwnerProfile.releaseCiCam();
1134         return true;
1135     }
1136 
transferLnbOwner(int currentOwnerId, int newOwnerId)1137     private boolean transferLnbOwner(int currentOwnerId, int newOwnerId) {
1138         ClientProfile currentOwnerProfile = getClientProfile(currentOwnerId);
1139         ClientProfile newOwnerProfile = getClientProfile(newOwnerId);
1140 
1141         Set<Integer> inUseLnbHandles = new HashSet<>();
1142         for (Integer lnbHandle : currentOwnerProfile.getInUseLnbHandles()) {
1143             // link lnb handle to the new profile
1144             newOwnerProfile.useLnb(lnbHandle);
1145 
1146             // set new owner Id
1147             LnbResource lnb = getLnbResource(lnbHandle);
1148             lnb.setOwner(newOwnerId);
1149 
1150             inUseLnbHandles.add(lnbHandle);
1151         }
1152 
1153         // unlink lnb handles from the original owner
1154         for (Integer lnbHandle : inUseLnbHandles) {
1155             currentOwnerProfile.releaseLnb(lnbHandle);
1156         }
1157 
1158         return true;
1159     }
1160 
1161     @VisibleForTesting
transferOwnerInternal(int resourceType, int currentOwnerId, int newOwnerId)1162     protected boolean transferOwnerInternal(int resourceType, int currentOwnerId, int newOwnerId) {
1163         switch (resourceType) {
1164             case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND:
1165                 return transferFeOwner(currentOwnerId, newOwnerId);
1166             case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM:
1167                 return transferFeCiCamOwner(currentOwnerId, newOwnerId);
1168             case TunerResourceManager.TUNER_RESOURCE_TYPE_LNB:
1169                 return transferLnbOwner(currentOwnerId, newOwnerId);
1170             default:
1171                 Slog.e(TAG, "transferOwnerInternal. unsupported resourceType: " + resourceType);
1172                 return false;
1173         }
1174     }
1175 
1176     @VisibleForTesting
requestLnbInternal(TunerLnbRequest request, int[] lnbHandle)1177     protected boolean requestLnbInternal(TunerLnbRequest request, int[] lnbHandle) {
1178         if (DEBUG) {
1179             Slog.d(TAG, "requestLnb(request=" + request + ")");
1180         }
1181 
1182         lnbHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
1183         ClientProfile requestClient = getClientProfile(request.clientId);
1184         clientPriorityUpdateOnRequest(requestClient);
1185         int grantingLnbHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
1186         int inUseLowestPriorityLnbHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
1187         // Priority max value is 1000
1188         int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
1189         boolean isRequestFromSameProcess = false;
1190         for (LnbResource lnb : getLnbResources().values()) {
1191             if (!lnb.isInUse()) {
1192                 // Grant the unused lnb with lower handle first
1193                 grantingLnbHandle = lnb.getHandle();
1194                 break;
1195             } else {
1196                 // Record the lnb id with the lowest client priority among all the
1197                 // in use lnb when no available lnb has been found.
1198                 int priority = updateAndGetOwnerClientPriority(lnb.getOwnerClientId());
1199                 if (currentLowestPriority > priority) {
1200                     inUseLowestPriorityLnbHandle = lnb.getHandle();
1201                     currentLowestPriority = priority;
1202                     isRequestFromSameProcess = (requestClient.getProcessId()
1203                         == (getClientProfile(lnb.getOwnerClientId())).getProcessId());
1204                 }
1205             }
1206         }
1207 
1208         // Grant Lnb when there is unused resource.
1209         if (grantingLnbHandle > -1) {
1210             lnbHandle[0] = grantingLnbHandle;
1211             updateLnbClientMappingOnNewGrant(grantingLnbHandle, request.clientId);
1212             return true;
1213         }
1214 
1215         // When all the resources are occupied, grant the lowest priority resource if the
1216         // request client has higher priority.
1217         if (inUseLowestPriorityLnbHandle > TunerResourceManager.INVALID_RESOURCE_HANDLE
1218             && ((requestClient.getPriority() > currentLowestPriority) || (
1219             (requestClient.getPriority() == currentLowestPriority) && isRequestFromSameProcess))) {
1220             if (!reclaimResource(getLnbResource(inUseLowestPriorityLnbHandle).getOwnerClientId(),
1221                     TunerResourceManager.TUNER_RESOURCE_TYPE_LNB)) {
1222                 return false;
1223             }
1224             lnbHandle[0] = inUseLowestPriorityLnbHandle;
1225             updateLnbClientMappingOnNewGrant(inUseLowestPriorityLnbHandle, request.clientId);
1226             return true;
1227         }
1228 
1229         return false;
1230     }
1231 
1232     @VisibleForTesting
requestCasSessionInternal(CasSessionRequest request, int[] casSessionHandle)1233     protected boolean requestCasSessionInternal(CasSessionRequest request, int[] casSessionHandle) {
1234         if (DEBUG) {
1235             Slog.d(TAG, "requestCasSession(request=" + request + ")");
1236         }
1237         CasResource cas = getCasResource(request.casSystemId);
1238         // Unregistered Cas System is treated as having unlimited sessions.
1239         if (cas == null) {
1240             cas = new CasResource.Builder(request.casSystemId)
1241                                  .maxSessionNum(Integer.MAX_VALUE)
1242                                  .build();
1243             addCasResource(cas);
1244         }
1245         casSessionHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
1246         ClientProfile requestClient = getClientProfile(request.clientId);
1247         clientPriorityUpdateOnRequest(requestClient);
1248         int lowestPriorityOwnerId = -1;
1249         // Priority max value is 1000
1250         int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
1251         boolean isRequestFromSameProcess = false;
1252         if (!cas.isFullyUsed()) {
1253             casSessionHandle[0] = generateResourceHandle(
1254                     TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, cas.getSystemId());
1255             updateCasClientMappingOnNewGrant(request.casSystemId, request.clientId);
1256             return true;
1257         }
1258         for (int ownerId : cas.getOwnerClientIds()) {
1259             // Record the client id with lowest priority that is using the current Cas system.
1260             int priority = updateAndGetOwnerClientPriority(ownerId);
1261             if (currentLowestPriority > priority) {
1262                 lowestPriorityOwnerId = ownerId;
1263                 currentLowestPriority = priority;
1264                 isRequestFromSameProcess = (requestClient.getProcessId()
1265                     == (getClientProfile(ownerId)).getProcessId());
1266             }
1267         }
1268 
1269         // When all the Cas sessions are occupied, reclaim the lowest priority client if the
1270         // request client has higher priority.
1271         if (lowestPriorityOwnerId > -1 && ((requestClient.getPriority() > currentLowestPriority)
1272         || ((requestClient.getPriority() == currentLowestPriority) && isRequestFromSameProcess))) {
1273             if (!reclaimResource(lowestPriorityOwnerId,
1274                     TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION)) {
1275                 return false;
1276             }
1277             casSessionHandle[0] = generateResourceHandle(
1278                     TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, cas.getSystemId());
1279             updateCasClientMappingOnNewGrant(request.casSystemId, request.clientId);
1280             return true;
1281         }
1282         return false;
1283     }
1284 
1285     @VisibleForTesting
requestCiCamInternal(TunerCiCamRequest request, int[] ciCamHandle)1286     protected boolean requestCiCamInternal(TunerCiCamRequest request, int[] ciCamHandle) {
1287         if (DEBUG) {
1288             Slog.d(TAG, "requestCiCamInternal(TunerCiCamRequest=" + request + ")");
1289         }
1290         CiCamResource ciCam = getCiCamResource(request.ciCamId);
1291         // Unregistered Cas System is treated as having unlimited sessions.
1292         if (ciCam == null) {
1293             ciCam = new CiCamResource.Builder(request.ciCamId)
1294                                      .maxSessionNum(Integer.MAX_VALUE)
1295                                      .build();
1296             addCiCamResource(ciCam);
1297         }
1298         ciCamHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
1299         ClientProfile requestClient = getClientProfile(request.clientId);
1300         clientPriorityUpdateOnRequest(requestClient);
1301         int lowestPriorityOwnerId = -1;
1302         // Priority max value is 1000
1303         int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
1304         boolean isRequestFromSameProcess = false;
1305         if (!ciCam.isFullyUsed()) {
1306             ciCamHandle[0] = generateResourceHandle(
1307                     TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM, ciCam.getCiCamId());
1308             updateCiCamClientMappingOnNewGrant(request.ciCamId, request.clientId);
1309             return true;
1310         }
1311         for (int ownerId : ciCam.getOwnerClientIds()) {
1312             // Record the client id with lowest priority that is using the current Cas system.
1313             int priority = updateAndGetOwnerClientPriority(ownerId);
1314             if (currentLowestPriority > priority) {
1315                 lowestPriorityOwnerId = ownerId;
1316                 currentLowestPriority = priority;
1317                 isRequestFromSameProcess = (requestClient.getProcessId()
1318                     == (getClientProfile(ownerId)).getProcessId());
1319             }
1320         }
1321 
1322         // When all the CiCam sessions are occupied, reclaim the lowest priority client if the
1323         // request client has higher priority.
1324         if (lowestPriorityOwnerId > -1 && ((requestClient.getPriority() > currentLowestPriority)
1325             || ((requestClient.getPriority() == currentLowestPriority)
1326                 && isRequestFromSameProcess))) {
1327             if (!reclaimResource(lowestPriorityOwnerId,
1328                     TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM)) {
1329                 return false;
1330             }
1331             ciCamHandle[0] = generateResourceHandle(
1332                     TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM, ciCam.getCiCamId());
1333             updateCiCamClientMappingOnNewGrant(request.ciCamId, request.clientId);
1334             return true;
1335         }
1336         return false;
1337     }
1338 
1339     @VisibleForTesting
isHigherPriorityInternal(ResourceClientProfile challengerProfile, ResourceClientProfile holderProfile)1340     protected boolean isHigherPriorityInternal(ResourceClientProfile challengerProfile,
1341             ResourceClientProfile holderProfile) {
1342         if (DEBUG) {
1343             Slog.d(TAG,
1344                     "isHigherPriority(challengerProfile=" + challengerProfile
1345                             + ", holderProfile=" + challengerProfile + ")");
1346         }
1347         if (mTvInputManager == null) {
1348             Slog.e(TAG, "TvInputManager is null. Can't compare the priority.");
1349             // Allow the client to acquire the hardware interface
1350             // when the TRM is not able to compare the priority.
1351             return true;
1352         }
1353 
1354         int challengerPid = challengerProfile.tvInputSessionId == null
1355                 ? Binder.getCallingPid() /*callingPid*/
1356                 : mTvInputManager.getClientPid(challengerProfile.tvInputSessionId); /*tvAppId*/
1357         int holderPid = holderProfile.tvInputSessionId == null
1358                 ? Binder.getCallingPid() /*callingPid*/
1359                 : mTvInputManager.getClientPid(holderProfile.tvInputSessionId); /*tvAppId*/
1360 
1361         int challengerPriority = getClientPriority(
1362                 challengerProfile.useCase, checkIsForeground(challengerPid));
1363         int holderPriority = getClientPriority(holderProfile.useCase, checkIsForeground(holderPid));
1364         return challengerPriority > holderPriority;
1365     }
1366 
1367     @VisibleForTesting
releaseFrontendInternal(FrontendResource fe, int clientId)1368     protected void releaseFrontendInternal(FrontendResource fe, int clientId) {
1369         if (DEBUG) {
1370             Slog.d(TAG, "releaseFrontend(id=" + fe.getHandle() + ", clientId=" + clientId + " )");
1371         }
1372         if (clientId == fe.getOwnerClientId()) {
1373             ClientProfile ownerClient = getClientProfile(fe.getOwnerClientId());
1374             if (ownerClient != null) {
1375                 for (int shareOwnerId : ownerClient.getShareFeClientIds()) {
1376                     reclaimResource(shareOwnerId,
1377                             TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND);
1378                 }
1379             }
1380         }
1381         clearFrontendAndClientMapping(getClientProfile(clientId));
1382     }
1383 
1384     @VisibleForTesting
releaseDemuxInternal(DemuxResource demux)1385     protected void releaseDemuxInternal(DemuxResource demux) {
1386         if (DEBUG) {
1387             Slog.d(TAG, "releaseDemux(DemuxHandle=" + demux.getHandle() + ")");
1388         }
1389         updateDemuxClientMappingOnRelease(demux);
1390     }
1391 
1392     @VisibleForTesting
releaseLnbInternal(LnbResource lnb)1393     protected void releaseLnbInternal(LnbResource lnb) {
1394         if (DEBUG) {
1395             Slog.d(TAG, "releaseLnb(lnbHandle=" + lnb.getHandle() + ")");
1396         }
1397         updateLnbClientMappingOnRelease(lnb);
1398     }
1399 
1400     @VisibleForTesting
releaseCasSessionInternal(CasResource cas, int ownerClientId)1401     protected void releaseCasSessionInternal(CasResource cas, int ownerClientId) {
1402         if (DEBUG) {
1403             Slog.d(TAG, "releaseCasSession(sessionResourceId=" + cas.getSystemId() + ")");
1404         }
1405         updateCasClientMappingOnRelease(cas, ownerClientId);
1406     }
1407 
1408     @VisibleForTesting
releaseCiCamInternal(CiCamResource ciCam, int ownerClientId)1409     protected void releaseCiCamInternal(CiCamResource ciCam, int ownerClientId) {
1410         if (DEBUG) {
1411             Slog.d(TAG, "releaseCiCamInternal(ciCamId=" + ciCam.getCiCamId() + ")");
1412         }
1413         updateCiCamClientMappingOnRelease(ciCam, ownerClientId);
1414     }
1415 
1416     @VisibleForTesting
requestDemuxInternal(TunerDemuxRequest request, int[] demuxHandle)1417     protected boolean requestDemuxInternal(TunerDemuxRequest request, int[] demuxHandle) {
1418         if (DEBUG) {
1419             Slog.d(TAG, "requestDemux(request=" + request + ")");
1420         }
1421 
1422         // For Tuner 2.0 and below or any HW constraint devices that are unable to support
1423         // ITuner.openDemuxById(), demux resources are not really managed under TRM and
1424         // mDemuxResources.size() will be zero
1425         if (mDemuxResources.size() == 0) {
1426             // There are enough Demux resources, so we don't manage Demux in R.
1427             demuxHandle[0] =
1428                     generateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, 0);
1429             return true;
1430         }
1431 
1432         demuxHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
1433         ClientProfile requestClient = getClientProfile(request.clientId);
1434 
1435         if (requestClient == null) {
1436             return false;
1437         }
1438 
1439         clientPriorityUpdateOnRequest(requestClient);
1440         int grantingDemuxHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
1441         int inUseLowestPriorityDrHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
1442         // Priority max value is 1000
1443         int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
1444         boolean isRequestFromSameProcess = false;
1445         // If the desired demux id was specified, we only need to check the demux.
1446         boolean hasDesiredDemuxCap = request.desiredFilterTypes
1447                 != DemuxFilterMainType.UNDEFINED;
1448         int smallestNumOfSupportedCaps = Integer.SIZE + 1;
1449         for (DemuxResource dr : getDemuxResources().values()) {
1450             if (!hasDesiredDemuxCap || dr.hasSufficientCaps(request.desiredFilterTypes)) {
1451                 if (!dr.isInUse()) {
1452                     int numOfSupportedCaps = dr.getNumOfCaps();
1453 
1454                     // look for the demux with minimum caps supporting the desired caps
1455                     if (smallestNumOfSupportedCaps > numOfSupportedCaps) {
1456                         smallestNumOfSupportedCaps = numOfSupportedCaps;
1457                         grantingDemuxHandle = dr.getHandle();
1458                     }
1459                 } else if (grantingDemuxHandle == TunerResourceManager.INVALID_RESOURCE_HANDLE) {
1460                     // Record the demux id with the lowest client priority among all the
1461                     // in use demuxes when no availabledemux has been found.
1462                     int priority = updateAndGetOwnerClientPriority(dr.getOwnerClientId());
1463                     if (currentLowestPriority >= priority) {
1464                         int numOfSupportedCaps = dr.getNumOfCaps();
1465                         boolean shouldUpdate = false;
1466                         // update lowest priority
1467                         if (currentLowestPriority > priority) {
1468                             currentLowestPriority = priority;
1469                             isRequestFromSameProcess = (requestClient.getProcessId()
1470                                 == (getClientProfile(dr.getOwnerClientId())).getProcessId());
1471                             shouldUpdate = true;
1472                         }
1473                         // update smallest caps
1474                         if (smallestNumOfSupportedCaps > numOfSupportedCaps) {
1475                             smallestNumOfSupportedCaps = numOfSupportedCaps;
1476                             shouldUpdate = true;
1477                         }
1478                         if (shouldUpdate) {
1479                             inUseLowestPriorityDrHandle = dr.getHandle();
1480                         }
1481                     }
1482                 }
1483             }
1484         }
1485 
1486         // Grant demux when there is unused resource.
1487         if (grantingDemuxHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE) {
1488             demuxHandle[0] = grantingDemuxHandle;
1489             updateDemuxClientMappingOnNewGrant(grantingDemuxHandle, request.clientId);
1490             return true;
1491         }
1492 
1493         // When all the resources are occupied, grant the lowest priority resource if the
1494         // request client has higher priority.
1495         if (inUseLowestPriorityDrHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE
1496             && ((requestClient.getPriority() > currentLowestPriority) || (
1497             (requestClient.getPriority() == currentLowestPriority) && isRequestFromSameProcess))) {
1498             if (!reclaimResource(
1499                     getDemuxResource(inUseLowestPriorityDrHandle).getOwnerClientId(),
1500                     TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
1501                 return false;
1502             }
1503             demuxHandle[0] = inUseLowestPriorityDrHandle;
1504             updateDemuxClientMappingOnNewGrant(
1505                     inUseLowestPriorityDrHandle, request.clientId);
1506             return true;
1507         }
1508 
1509         return false;
1510     }
1511 
1512     @VisibleForTesting
1513     // This mothod is to sync up the request/holder client's foreground/background status and update
1514     // the client priority accordingly whenever a new resource request comes in.
clientPriorityUpdateOnRequest(ClientProfile profile)1515     protected void clientPriorityUpdateOnRequest(ClientProfile profile) {
1516         if (profile.isPriorityOverwritten()) {
1517             // To avoid overriding the priority set through updateClientPriority API.
1518             return;
1519         }
1520         int pid = profile.getProcessId();
1521         boolean currentIsForeground = checkIsForeground(pid);
1522         profile.setPriority(
1523                 getClientPriority(profile.getUseCase(), currentIsForeground));
1524     }
1525 
1526     @VisibleForTesting
requestDescramblerInternal( TunerDescramblerRequest request, int[] descramblerHandle)1527     protected boolean requestDescramblerInternal(
1528             TunerDescramblerRequest request, int[] descramblerHandle) {
1529         if (DEBUG) {
1530             Slog.d(TAG, "requestDescrambler(request=" + request + ")");
1531         }
1532         // There are enough Descrambler resources, so we don't manage Descrambler in R.
1533         descramblerHandle[0] =
1534                 generateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_DESCRAMBLER, 0);
1535         return true;
1536     }
1537 
1538     // Return value is guaranteed to be positive
getElapsedTime(long begin)1539     private long getElapsedTime(long begin) {
1540         long now = SystemClock.uptimeMillis();
1541         long elapsed;
1542         if (now >= begin) {
1543             elapsed = now - begin;
1544         } else {
1545             elapsed = Long.MAX_VALUE - begin + now;
1546             if (elapsed < 0) {
1547                 elapsed = Long.MAX_VALUE;
1548             }
1549         }
1550         return elapsed;
1551     }
1552 
lockForTunerApiLock(int clientId, long timeoutMS, String callerFunction)1553     private boolean lockForTunerApiLock(int clientId, long timeoutMS, String callerFunction) {
1554         try {
1555             if (mLockForTRMSLock.tryLock(timeoutMS, TimeUnit.MILLISECONDS)) {
1556                 return true;
1557             } else {
1558                 Slog.e(TAG, "FAILED to lock mLockForTRMSLock in " + callerFunction
1559                         + ", clientId:" + clientId + ", timeoutMS:" + timeoutMS
1560                         + ", mTunerApiLockHolder:" + mTunerApiLockHolder);
1561                 return false;
1562             }
1563         } catch (InterruptedException ie) {
1564             Slog.e(TAG, "exception thrown in " + callerFunction + ":" + ie);
1565             if (mLockForTRMSLock.isHeldByCurrentThread()) {
1566                 mLockForTRMSLock.unlock();
1567             }
1568             return false;
1569         }
1570     }
1571 
acquireLockInternal(int clientId, long clientThreadId, long timeoutMS)1572     private boolean acquireLockInternal(int clientId, long clientThreadId, long timeoutMS) {
1573         long begin = SystemClock.uptimeMillis();
1574 
1575         // Grab lock
1576         if (!lockForTunerApiLock(clientId, timeoutMS, "acquireLockInternal()")) {
1577             return false;
1578         }
1579 
1580         try {
1581             boolean available = mTunerApiLockHolder == INVALID_CLIENT_ID;
1582             boolean nestedSelf = (clientId == mTunerApiLockHolder)
1583                     && (clientThreadId == mTunerApiLockHolderThreadId);
1584             boolean recovery = false;
1585 
1586             // Allow same thread to grab the lock multiple times
1587             while (!available && !nestedSelf) {
1588                 // calculate how much time is left before timeout
1589                 long leftOverMS = timeoutMS - getElapsedTime(begin);
1590                 if (leftOverMS <= 0) {
1591                     Slog.e(TAG, "FAILED:acquireLockInternal(" + clientId + ", " + clientThreadId
1592                             + ", " + timeoutMS + ") - timed out, but will grant the lock to "
1593                             + "the callee by stealing it from the current holder:"
1594                             + mTunerApiLockHolder + "(" + mTunerApiLockHolderThreadId + "), "
1595                             + "who likely failed to call releaseLock(), "
1596                             + "to prevent this from becoming an unrecoverable error");
1597                     // This should not normally happen, but there sometimes are cases where
1598                     // in-flight tuner API execution gets scheduled even after binderDied(),
1599                     // which can leave the in-flight execution dissappear/stopped in between
1600                     // acquireLock and releaseLock
1601                     recovery = true;
1602                     break;
1603                 }
1604 
1605                 // Cond wait for left over time
1606                 mTunerApiLockReleasedCV.await(leftOverMS, TimeUnit.MILLISECONDS);
1607 
1608                 // Check the availability for "spurious wakeup"
1609                 // The case that was confirmed is that someone else can acquire this in between
1610                 // signal() and wakup from the above await()
1611                 available = mTunerApiLockHolder == INVALID_CLIENT_ID;
1612 
1613                 if (!available) {
1614                     Slog.w(TAG, "acquireLockInternal(" + clientId + ", " + clientThreadId + ", "
1615                             + timeoutMS + ") - woken up from cond wait, but " + mTunerApiLockHolder
1616                             + "(" + mTunerApiLockHolderThreadId + ") is already holding the lock. "
1617                             + "Going to wait again if timeout hasn't reached yet");
1618                 }
1619             }
1620 
1621             // Will always grant unless exception is thrown (or lock is already held)
1622             if (available || recovery) {
1623                 if (DEBUG) {
1624                     Slog.d(TAG, "SUCCESS:acquireLockInternal(" + clientId + ", " + clientThreadId
1625                             + ", " + timeoutMS + ")");
1626                 }
1627 
1628                 if (mTunerApiLockNestedCount != 0) {
1629                     Slog.w(TAG, "Something is wrong as nestedCount(" + mTunerApiLockNestedCount
1630                             + ") is not zero. Will overriding it to 1 anyways");
1631                 }
1632 
1633                 // set the caller to be the holder
1634                 mTunerApiLockHolder = clientId;
1635                 mTunerApiLockHolderThreadId = clientThreadId;
1636                 mTunerApiLockNestedCount = 1;
1637             } else if (nestedSelf) {
1638                 // Increment the nested count so releaseLockInternal won't signal prematuredly
1639                 mTunerApiLockNestedCount++;
1640                 if (DEBUG) {
1641                     Slog.d(TAG, "acquireLockInternal(" + clientId + ", " + clientThreadId
1642                             + ", " + timeoutMS + ") - nested count incremented to "
1643                             + mTunerApiLockNestedCount);
1644                 }
1645             } else {
1646                 Slog.e(TAG, "acquireLockInternal(" + clientId + ", " + clientThreadId
1647                         + ", " + timeoutMS + ") - should not reach here");
1648             }
1649             // return true in "recovery" so callee knows that the deadlock is possible
1650             // only when the return value is false
1651             return (available || nestedSelf || recovery);
1652         } catch (InterruptedException ie) {
1653             Slog.e(TAG, "exception thrown in acquireLockInternal(" + clientId + ", "
1654                     + clientThreadId + ", " + timeoutMS + "):" + ie);
1655             return false;
1656         } finally {
1657             if (mLockForTRMSLock.isHeldByCurrentThread()) {
1658                 mLockForTRMSLock.unlock();
1659             }
1660         }
1661     }
1662 
releaseLockInternal(int clientId, long timeoutMS, boolean ignoreNestedCount, boolean suppressError)1663     private boolean releaseLockInternal(int clientId, long timeoutMS,
1664             boolean ignoreNestedCount, boolean suppressError) {
1665         // Grab lock first
1666         if (!lockForTunerApiLock(clientId, timeoutMS, "releaseLockInternal()")) {
1667             return false;
1668         }
1669 
1670         try {
1671             if (mTunerApiLockHolder == clientId) {
1672                 // Should always reach here unless called from binderDied()
1673                 mTunerApiLockNestedCount--;
1674                 if (ignoreNestedCount || mTunerApiLockNestedCount <= 0) {
1675                     if (DEBUG) {
1676                         Slog.d(TAG, "SUCCESS:releaseLockInternal(" + clientId + ", " + timeoutMS
1677                                 + ", " + ignoreNestedCount + ", " + suppressError
1678                                 + ") - signaling!");
1679                     }
1680                     // Reset the current holder and signal
1681                     mTunerApiLockHolder = INVALID_CLIENT_ID;
1682                     mTunerApiLockHolderThreadId = INVALID_THREAD_ID;
1683                     mTunerApiLockNestedCount = 0;
1684                     mTunerApiLockReleasedCV.signal();
1685                 } else {
1686                     if (DEBUG) {
1687                         Slog.d(TAG, "releaseLockInternal(" + clientId + ", " + timeoutMS
1688                                 + ", " + ignoreNestedCount + ", " + suppressError
1689                                 + ") - NOT signaling because nested count is not zero ("
1690                                 + mTunerApiLockNestedCount + ")");
1691                     }
1692                 }
1693                 return true;
1694             } else if (mTunerApiLockHolder == INVALID_CLIENT_ID) {
1695                 if (!suppressError) {
1696                     Slog.w(TAG, "releaseLockInternal(" + clientId + ", " + timeoutMS
1697                             + ") - called while there is no current holder");
1698                 }
1699                 // No need to do anything.
1700                 // Shouldn't reach here unless called from binderDied()
1701                 return false;
1702             } else {
1703                 if (!suppressError) {
1704                     Slog.e(TAG, "releaseLockInternal(" + clientId + ", " + timeoutMS
1705                             + ") - called while someone else:" + mTunerApiLockHolder
1706                             + "is the current holder");
1707                 }
1708                 // Cannot reset the holder Id because it reaches here when called
1709                 // from binderDied()
1710                 return false;
1711             }
1712         } finally {
1713             if (mLockForTRMSLock.isHeldByCurrentThread()) {
1714                 mLockForTRMSLock.unlock();
1715             }
1716         }
1717     }
1718 
1719     @VisibleForTesting
1720     protected class ResourcesReclaimListenerRecord implements IBinder.DeathRecipient {
1721         private final IResourcesReclaimListener mListener;
1722         private final int mClientId;
1723 
ResourcesReclaimListenerRecord(IResourcesReclaimListener listener, int clientId)1724         public ResourcesReclaimListenerRecord(IResourcesReclaimListener listener, int clientId) {
1725             mListener = listener;
1726             mClientId = clientId;
1727         }
1728 
1729         @Override
binderDied()1730         public void binderDied() {
1731             try {
1732                 synchronized (mLock) {
1733                     if (checkClientExists(mClientId)) {
1734                         removeClientProfile(mClientId);
1735                     }
1736                 }
1737             } finally {
1738                 // reset the tuner API lock
1739                 releaseLockInternal(mClientId, TRMS_LOCK_TIMEOUT, true, true);
1740             }
1741         }
1742 
getId()1743         public int getId() {
1744             return mClientId;
1745         }
1746 
getListener()1747         public IResourcesReclaimListener getListener() {
1748             return mListener;
1749         }
1750     }
1751 
addResourcesReclaimListener(int clientId, IResourcesReclaimListener listener)1752     private void addResourcesReclaimListener(int clientId, IResourcesReclaimListener listener) {
1753         if (listener == null) {
1754             if (DEBUG) {
1755                 Slog.w(TAG, "Listener is null when client " + clientId + " registered!");
1756             }
1757             return;
1758         }
1759 
1760         ResourcesReclaimListenerRecord record =
1761                 new ResourcesReclaimListenerRecord(listener, clientId);
1762 
1763         try {
1764             listener.asBinder().linkToDeath(record, 0);
1765         } catch (RemoteException e) {
1766             Slog.w(TAG, "Listener already died.");
1767             return;
1768         }
1769 
1770         mListeners.put(clientId, record);
1771     }
1772 
1773     @VisibleForTesting
reclaimResource(int reclaimingClientId, @TunerResourceManager.TunerResourceType int resourceType)1774     protected boolean reclaimResource(int reclaimingClientId,
1775             @TunerResourceManager.TunerResourceType int resourceType) {
1776 
1777         // Allowing this because:
1778         // 1) serialization of resource reclaim is required in the current design
1779         // 2) the outgoing transaction is handled by the system app (with
1780         //    android.Manifest.permission.TUNER_RESOURCE_ACCESS), which goes through full
1781         //    Google certification
1782         Binder.allowBlockingForCurrentThread();
1783 
1784         // Reclaim all the resources of the share owners of the frontend that is used by the current
1785         // resource reclaimed client.
1786         ClientProfile profile = getClientProfile(reclaimingClientId);
1787         // TODO: check if this check is really needed.
1788         if (profile == null) {
1789             return true;
1790         }
1791         Set<Integer> shareFeClientIds = profile.getShareFeClientIds();
1792         for (int clientId : shareFeClientIds) {
1793             try {
1794                 mListeners.get(clientId).getListener().onReclaimResources();
1795             } catch (RemoteException e) {
1796                 Slog.e(TAG, "Failed to reclaim resources on client " + clientId, e);
1797                 return false;
1798             }
1799             clearAllResourcesAndClientMapping(getClientProfile(clientId));
1800         }
1801 
1802         if (DEBUG) {
1803             Slog.d(TAG, "Reclaiming resources because higher priority client request resource type "
1804                     + resourceType + ", clientId:" + reclaimingClientId);
1805         }
1806         try {
1807             mListeners.get(reclaimingClientId).getListener().onReclaimResources();
1808         } catch (RemoteException e) {
1809             Slog.e(TAG, "Failed to reclaim resources on client " + reclaimingClientId, e);
1810             return false;
1811         }
1812         clearAllResourcesAndClientMapping(profile);
1813         return true;
1814     }
1815 
1816     @VisibleForTesting
getClientPriority(int useCase, boolean isForeground)1817     protected int getClientPriority(int useCase, boolean isForeground) {
1818         if (DEBUG) {
1819             Slog.d(TAG, "getClientPriority useCase=" + useCase
1820                     + ", isForeground=" + isForeground + ")");
1821         }
1822 
1823         if (isForeground) {
1824             return mPriorityCongfig.getForegroundPriority(useCase);
1825         }
1826         return mPriorityCongfig.getBackgroundPriority(useCase);
1827     }
1828 
1829     @VisibleForTesting
checkIsForeground(int pid)1830     protected boolean checkIsForeground(int pid) {
1831         if (mActivityManager == null) {
1832             return false;
1833         }
1834         List<RunningAppProcessInfo> appProcesses = mActivityManager.getRunningAppProcesses();
1835         if (appProcesses == null) {
1836             return false;
1837         }
1838         for (RunningAppProcessInfo appProcess : appProcesses) {
1839             if (appProcess.pid == pid
1840                     && appProcess.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
1841                 return true;
1842             }
1843         }
1844         return false;
1845     }
1846 
updateFrontendClientMappingOnNewGrant(int grantingHandle, int ownerClientId)1847     private void updateFrontendClientMappingOnNewGrant(int grantingHandle, int ownerClientId) {
1848         FrontendResource grantingFrontend = getFrontendResource(grantingHandle);
1849         ClientProfile ownerProfile = getClientProfile(ownerClientId);
1850         grantingFrontend.setOwner(ownerClientId);
1851         increFrontendNum(mFrontendUsedNums, grantingFrontend.getType());
1852         ownerProfile.useFrontend(grantingHandle);
1853         for (int exclusiveGroupMember : grantingFrontend.getExclusiveGroupMemberFeHandles()) {
1854             getFrontendResource(exclusiveGroupMember).setOwner(ownerClientId);
1855             ownerProfile.useFrontend(exclusiveGroupMember);
1856         }
1857         ownerProfile.setPrimaryFrontend(grantingHandle);
1858     }
1859 
updateDemuxClientMappingOnNewGrant(int grantingHandle, int ownerClientId)1860     private void updateDemuxClientMappingOnNewGrant(int grantingHandle, int ownerClientId) {
1861         DemuxResource grantingDemux = getDemuxResource(grantingHandle);
1862         if (grantingDemux != null) {
1863             ClientProfile ownerProfile = getClientProfile(ownerClientId);
1864             grantingDemux.setOwner(ownerClientId);
1865             ownerProfile.useDemux(grantingHandle);
1866         }
1867     }
1868 
updateDemuxClientMappingOnRelease(@onNull DemuxResource releasingDemux)1869     private void updateDemuxClientMappingOnRelease(@NonNull DemuxResource releasingDemux) {
1870         ClientProfile ownerProfile = getClientProfile(releasingDemux.getOwnerClientId());
1871         releasingDemux.removeOwner();
1872         ownerProfile.releaseDemux(releasingDemux.getHandle());
1873     }
1874 
updateLnbClientMappingOnNewGrant(int grantingHandle, int ownerClientId)1875     private void updateLnbClientMappingOnNewGrant(int grantingHandle, int ownerClientId) {
1876         LnbResource grantingLnb = getLnbResource(grantingHandle);
1877         ClientProfile ownerProfile = getClientProfile(ownerClientId);
1878         grantingLnb.setOwner(ownerClientId);
1879         ownerProfile.useLnb(grantingHandle);
1880     }
1881 
updateLnbClientMappingOnRelease(@onNull LnbResource releasingLnb)1882     private void updateLnbClientMappingOnRelease(@NonNull LnbResource releasingLnb) {
1883         ClientProfile ownerProfile = getClientProfile(releasingLnb.getOwnerClientId());
1884         releasingLnb.removeOwner();
1885         ownerProfile.releaseLnb(releasingLnb.getHandle());
1886     }
1887 
updateCasClientMappingOnNewGrant(int grantingId, int ownerClientId)1888     private void updateCasClientMappingOnNewGrant(int grantingId, int ownerClientId) {
1889         CasResource grantingCas = getCasResource(grantingId);
1890         ClientProfile ownerProfile = getClientProfile(ownerClientId);
1891         grantingCas.setOwner(ownerClientId);
1892         ownerProfile.useCas(grantingId);
1893     }
1894 
updateCiCamClientMappingOnNewGrant(int grantingId, int ownerClientId)1895     private void updateCiCamClientMappingOnNewGrant(int grantingId, int ownerClientId) {
1896         CiCamResource grantingCiCam = getCiCamResource(grantingId);
1897         ClientProfile ownerProfile = getClientProfile(ownerClientId);
1898         grantingCiCam.setOwner(ownerClientId);
1899         ownerProfile.useCiCam(grantingId);
1900     }
1901 
updateCasClientMappingOnRelease( @onNull CasResource releasingCas, int ownerClientId)1902     private void updateCasClientMappingOnRelease(
1903             @NonNull CasResource releasingCas, int ownerClientId) {
1904         ClientProfile ownerProfile = getClientProfile(ownerClientId);
1905         releasingCas.removeOwner(ownerClientId);
1906         ownerProfile.releaseCas();
1907     }
1908 
updateCiCamClientMappingOnRelease( @onNull CiCamResource releasingCiCam, int ownerClientId)1909     private void updateCiCamClientMappingOnRelease(
1910             @NonNull CiCamResource releasingCiCam, int ownerClientId) {
1911         ClientProfile ownerProfile = getClientProfile(ownerClientId);
1912         releasingCiCam.removeOwner(ownerClientId);
1913         ownerProfile.releaseCiCam();
1914     }
1915 
1916     /**
1917      * Update and get the owner client's priority.
1918      *
1919      * @param clientId the owner client id.
1920      * @return the priority of the owner client.
1921      */
updateAndGetOwnerClientPriority(int clientId)1922     private int updateAndGetOwnerClientPriority(int clientId) {
1923         ClientProfile profile = getClientProfile(clientId);
1924         clientPriorityUpdateOnRequest(profile);
1925         return profile.getPriority();
1926     }
1927 
1928     /**
1929      * Update the owner and sharee clients' priority and get the highest priority
1930      * for frontend resource
1931      *
1932      * @param clientId the owner client id.
1933      * @return the highest priority among all the clients holding the same frontend resource.
1934      */
getFrontendHighestClientPriority(int clientId)1935     private int getFrontendHighestClientPriority(int clientId) {
1936         // Check if the owner profile exists
1937         ClientProfile ownerClient = getClientProfile(clientId);
1938         if (ownerClient == null) {
1939             return 0;
1940         }
1941 
1942         // Update and get the priority of the owner client
1943         int highestPriority = updateAndGetOwnerClientPriority(clientId);
1944 
1945         // Update and get all the client IDs of frontend resource holders
1946         for (int shareeId : ownerClient.getShareFeClientIds()) {
1947             int priority = updateAndGetOwnerClientPriority(shareeId);
1948             if (priority > highestPriority) {
1949                 highestPriority = priority;
1950             }
1951         }
1952         return highestPriority;
1953     }
1954 
1955     @VisibleForTesting
1956     @Nullable
getFrontendResource(int frontendHandle)1957     protected FrontendResource getFrontendResource(int frontendHandle) {
1958         return mFrontendResources.get(frontendHandle);
1959     }
1960 
1961     @VisibleForTesting
getFrontendResources()1962     protected Map<Integer, FrontendResource> getFrontendResources() {
1963         return mFrontendResources;
1964     }
1965 
1966     @VisibleForTesting
1967     @Nullable
getDemuxResource(int demuxHandle)1968     protected DemuxResource getDemuxResource(int demuxHandle) {
1969         return mDemuxResources.get(demuxHandle);
1970     }
1971 
1972     @VisibleForTesting
getDemuxResources()1973     protected Map<Integer, DemuxResource> getDemuxResources() {
1974         return mDemuxResources;
1975     }
1976 
setMaxNumberOfFrontendsInternal(int frontendType, int maxUsableNum)1977     private boolean setMaxNumberOfFrontendsInternal(int frontendType, int maxUsableNum) {
1978         int usedNum = mFrontendUsedNums.get(frontendType, INVALID_FE_COUNT);
1979         if (usedNum == INVALID_FE_COUNT || usedNum <= maxUsableNum) {
1980             mFrontendMaxUsableNums.put(frontendType, maxUsableNum);
1981             return true;
1982         } else {
1983             Slog.e(TAG, "max number of frontend for frontendType: " + frontendType
1984                     + " cannot be set to a value lower than the current usage count."
1985                     + " (requested max num = " + maxUsableNum + ", current usage = " + usedNum);
1986             return false;
1987         }
1988     }
1989 
getMaxNumberOfFrontendsInternal(int frontendType)1990     private int getMaxNumberOfFrontendsInternal(int frontendType) {
1991         int existingNum = mFrontendExistingNums.get(frontendType, INVALID_FE_COUNT);
1992         if (existingNum == INVALID_FE_COUNT) {
1993             Log.e(TAG, "existingNum is -1 for " + frontendType);
1994             return -1;
1995         }
1996         int maxUsableNum = mFrontendMaxUsableNums.get(frontendType, INVALID_FE_COUNT);
1997         if (maxUsableNum == INVALID_FE_COUNT) {
1998             return existingNum;
1999         } else {
2000             return maxUsableNum;
2001         }
2002     }
2003 
isFrontendMaxNumUseReached(int frontendType)2004     private boolean isFrontendMaxNumUseReached(int frontendType) {
2005         int maxUsableNum = mFrontendMaxUsableNums.get(frontendType, INVALID_FE_COUNT);
2006         if (maxUsableNum == INVALID_FE_COUNT) {
2007             return false;
2008         }
2009         int useNum = mFrontendUsedNums.get(frontendType, INVALID_FE_COUNT);
2010         if (useNum == INVALID_FE_COUNT) {
2011             useNum = 0;
2012         }
2013         return useNum >= maxUsableNum;
2014     }
2015 
increFrontendNum(SparseIntArray targetNums, int frontendType)2016     private void increFrontendNum(SparseIntArray targetNums, int frontendType) {
2017         int num = targetNums.get(frontendType, INVALID_FE_COUNT);
2018         if (num == INVALID_FE_COUNT) {
2019             targetNums.put(frontendType, 1);
2020         } else {
2021             targetNums.put(frontendType, num + 1);
2022         }
2023     }
2024 
decreFrontendNum(SparseIntArray targetNums, int frontendType)2025     private void decreFrontendNum(SparseIntArray targetNums, int frontendType) {
2026         int num = targetNums.get(frontendType, INVALID_FE_COUNT);
2027         if (num != INVALID_FE_COUNT) {
2028             targetNums.put(frontendType, num - 1);
2029         }
2030     }
2031 
replaceFeResourceMap(Map<Integer, FrontendResource> srcMap, Map<Integer, FrontendResource> dstMap)2032     private void replaceFeResourceMap(Map<Integer, FrontendResource> srcMap, Map<Integer,
2033             FrontendResource> dstMap) {
2034         if (dstMap != null) {
2035             dstMap.clear();
2036             if (srcMap != null && srcMap.size() > 0) {
2037                 dstMap.putAll(srcMap);
2038             }
2039         }
2040     }
2041 
replaceFeCounts(SparseIntArray srcCounts, SparseIntArray dstCounts)2042     private void replaceFeCounts(SparseIntArray srcCounts, SparseIntArray dstCounts) {
2043         if (dstCounts != null) {
2044             dstCounts.clear();
2045             if (srcCounts != null) {
2046                 for (int i = 0; i < srcCounts.size(); i++) {
2047                     dstCounts.put(srcCounts.keyAt(i), srcCounts.valueAt(i));
2048                 }
2049             }
2050         }
2051     }
dumpMap(Map<?, ?> targetMap, String headline, String delimiter, IndentingPrintWriter pw)2052     private void dumpMap(Map<?, ?> targetMap, String headline, String delimiter,
2053             IndentingPrintWriter pw) {
2054         if (targetMap != null) {
2055             pw.println(headline);
2056             pw.increaseIndent();
2057             for (Map.Entry<?, ?> entry : targetMap.entrySet()) {
2058                 pw.print(entry.getKey() + " : " + entry.getValue());
2059                 pw.print(delimiter);
2060             }
2061             pw.println();
2062             pw.decreaseIndent();
2063         }
2064     }
2065 
dumpSIA(SparseIntArray array, String headline, String delimiter, IndentingPrintWriter pw)2066     private void dumpSIA(SparseIntArray array, String headline, String delimiter,
2067             IndentingPrintWriter pw) {
2068         if (array != null) {
2069             pw.println(headline);
2070             pw.increaseIndent();
2071             for (int i = 0; i < array.size(); i++) {
2072                 pw.print(array.keyAt(i) + " : " + array.valueAt(i));
2073                 pw.print(delimiter);
2074             }
2075             pw.println();
2076             pw.decreaseIndent();
2077         }
2078     }
2079 
addFrontendResource(FrontendResource newFe)2080     private void addFrontendResource(FrontendResource newFe) {
2081         // Update the exclusive group member list in all the existing Frontend resource
2082         for (FrontendResource fe : getFrontendResources().values()) {
2083             if (fe.getExclusiveGroupId() == newFe.getExclusiveGroupId()) {
2084                 newFe.addExclusiveGroupMemberFeHandle(fe.getHandle());
2085                 newFe.addExclusiveGroupMemberFeHandles(fe.getExclusiveGroupMemberFeHandles());
2086                 for (int excGroupmemberFeHandle : fe.getExclusiveGroupMemberFeHandles()) {
2087                     getFrontendResource(excGroupmemberFeHandle)
2088                             .addExclusiveGroupMemberFeHandle(newFe.getHandle());
2089                 }
2090                 fe.addExclusiveGroupMemberFeHandle(newFe.getHandle());
2091                 break;
2092             }
2093         }
2094         // Update resource list and available id list
2095         mFrontendResources.put(newFe.getHandle(), newFe);
2096         increFrontendNum(mFrontendExistingNums, newFe.getType());
2097 
2098     }
2099 
addDemuxResource(DemuxResource newDemux)2100     private void addDemuxResource(DemuxResource newDemux) {
2101         mDemuxResources.put(newDemux.getHandle(), newDemux);
2102     }
2103 
removeFrontendResource(int removingHandle)2104     private void removeFrontendResource(int removingHandle) {
2105         FrontendResource fe = getFrontendResource(removingHandle);
2106         if (fe == null) {
2107             return;
2108         }
2109         if (fe.isInUse()) {
2110             ClientProfile ownerClient = getClientProfile(fe.getOwnerClientId());
2111             for (int shareOwnerId : ownerClient.getShareFeClientIds()) {
2112                 clearFrontendAndClientMapping(getClientProfile(shareOwnerId));
2113             }
2114             clearFrontendAndClientMapping(ownerClient);
2115         }
2116         for (int excGroupmemberFeHandle : fe.getExclusiveGroupMemberFeHandles()) {
2117             getFrontendResource(excGroupmemberFeHandle)
2118                     .removeExclusiveGroupMemberFeId(fe.getHandle());
2119         }
2120         decreFrontendNum(mFrontendExistingNums, fe.getType());
2121         mFrontendResources.remove(removingHandle);
2122     }
2123 
removeDemuxResource(int removingHandle)2124     private void removeDemuxResource(int removingHandle) {
2125         DemuxResource demux = getDemuxResource(removingHandle);
2126         if (demux == null) {
2127             return;
2128         }
2129         if (demux.isInUse()) {
2130             releaseDemuxInternal(demux);
2131         }
2132         mDemuxResources.remove(removingHandle);
2133     }
2134 
2135     @VisibleForTesting
2136     @Nullable
getLnbResource(int lnbHandle)2137     protected LnbResource getLnbResource(int lnbHandle) {
2138         return mLnbResources.get(lnbHandle);
2139     }
2140 
2141     @VisibleForTesting
getLnbResources()2142     protected Map<Integer, LnbResource> getLnbResources() {
2143         return mLnbResources;
2144     }
2145 
addLnbResource(LnbResource newLnb)2146     private void addLnbResource(LnbResource newLnb) {
2147         // Update resource list and available id list
2148         mLnbResources.put(newLnb.getHandle(), newLnb);
2149     }
2150 
removeLnbResource(int removingHandle)2151     private void removeLnbResource(int removingHandle) {
2152         LnbResource lnb = getLnbResource(removingHandle);
2153         if (lnb == null) {
2154             return;
2155         }
2156         if (lnb.isInUse()) {
2157             releaseLnbInternal(lnb);
2158         }
2159         mLnbResources.remove(removingHandle);
2160     }
2161 
2162     @VisibleForTesting
2163     @Nullable
getCasResource(int systemId)2164     protected CasResource getCasResource(int systemId) {
2165         return mCasResources.get(systemId);
2166     }
2167 
2168     @VisibleForTesting
2169     @Nullable
getCiCamResource(int ciCamId)2170     protected CiCamResource getCiCamResource(int ciCamId) {
2171         return mCiCamResources.get(ciCamId);
2172     }
2173 
2174     @VisibleForTesting
getCasResources()2175     protected Map<Integer, CasResource> getCasResources() {
2176         return mCasResources;
2177     }
2178 
2179     @VisibleForTesting
getCiCamResources()2180     protected Map<Integer, CiCamResource> getCiCamResources() {
2181         return mCiCamResources;
2182     }
2183 
addCasResource(CasResource newCas)2184     private void addCasResource(CasResource newCas) {
2185         // Update resource list and available id list
2186         mCasResources.put(newCas.getSystemId(), newCas);
2187     }
2188 
addCiCamResource(CiCamResource newCiCam)2189     private void addCiCamResource(CiCamResource newCiCam) {
2190         // Update resource list and available id list
2191         mCiCamResources.put(newCiCam.getCiCamId(), newCiCam);
2192     }
2193 
removeCasResource(int removingId)2194     private void removeCasResource(int removingId) {
2195         CasResource cas = getCasResource(removingId);
2196         if (cas == null) {
2197             return;
2198         }
2199         for (int ownerId : cas.getOwnerClientIds()) {
2200             getClientProfile(ownerId).releaseCas();
2201         }
2202         mCasResources.remove(removingId);
2203     }
2204 
removeCiCamResource(int removingId)2205     private void removeCiCamResource(int removingId) {
2206         CiCamResource ciCam = getCiCamResource(removingId);
2207         if (ciCam == null) {
2208             return;
2209         }
2210         for (int ownerId : ciCam.getOwnerClientIds()) {
2211             getClientProfile(ownerId).releaseCiCam();
2212         }
2213         mCiCamResources.remove(removingId);
2214     }
2215 
releaseLowerPriorityClientCasResources(int releasingCasResourceNum)2216     private void releaseLowerPriorityClientCasResources(int releasingCasResourceNum) {
2217         // TODO: Sort with a treemap
2218 
2219         // select the first num client to release
2220     }
2221 
2222     @VisibleForTesting
2223     @Nullable
getClientProfile(int clientId)2224     protected ClientProfile getClientProfile(int clientId) {
2225         return mClientProfiles.get(clientId);
2226     }
2227 
addClientProfile(int clientId, ClientProfile profile, IResourcesReclaimListener listener)2228     private void addClientProfile(int clientId, ClientProfile profile,
2229             IResourcesReclaimListener listener) {
2230         mClientProfiles.put(clientId, profile);
2231         addResourcesReclaimListener(clientId, listener);
2232     }
2233 
removeClientProfile(int clientId)2234     private void removeClientProfile(int clientId) {
2235         for (int shareOwnerId : getClientProfile(clientId).getShareFeClientIds()) {
2236             clearFrontendAndClientMapping(getClientProfile(shareOwnerId));
2237         }
2238         clearAllResourcesAndClientMapping(getClientProfile(clientId));
2239         mClientProfiles.remove(clientId);
2240 
2241         // it may be called by unregisterClientProfileInternal under test
2242         synchronized (mLock) {
2243             ResourcesReclaimListenerRecord record = mListeners.remove(clientId);
2244             if (record != null) {
2245                 record.getListener().asBinder().unlinkToDeath(record, 0);
2246             }
2247         }
2248     }
2249 
clearFrontendAndClientMapping(ClientProfile profile)2250     private void clearFrontendAndClientMapping(ClientProfile profile) {
2251         // TODO: check if this check is really needed
2252         if (profile == null) {
2253             return;
2254         }
2255         for (Integer feId : profile.getInUseFrontendHandles()) {
2256             FrontendResource fe = getFrontendResource(feId);
2257             int ownerClientId = fe.getOwnerClientId();
2258             if (ownerClientId == profile.getId()) {
2259                 fe.removeOwner();
2260                 continue;
2261             }
2262             ClientProfile ownerClientProfile = getClientProfile(ownerClientId);
2263             if (ownerClientProfile != null) {
2264                 ownerClientProfile.stopSharingFrontend(profile.getId());
2265             }
2266 
2267         }
2268 
2269         int primaryFeId = profile.getPrimaryFrontend();
2270         if (primaryFeId != TunerResourceManager.INVALID_RESOURCE_HANDLE) {
2271             FrontendResource primaryFe = getFrontendResource(primaryFeId);
2272             if (primaryFe != null) {
2273                 decreFrontendNum(mFrontendUsedNums, primaryFe.getType());
2274             }
2275         }
2276 
2277         profile.releaseFrontend();
2278     }
2279 
clearAllResourcesAndClientMapping(ClientProfile profile)2280     private void clearAllResourcesAndClientMapping(ClientProfile profile) {
2281         // TODO: check if this check is really needed. Maybe needed for reclaimResource path.
2282         if (profile == null) {
2283             return;
2284         }
2285         // Clear Lnb
2286         for (Integer lnbHandle : profile.getInUseLnbHandles()) {
2287             getLnbResource(lnbHandle).removeOwner();
2288         }
2289         // Clear Cas
2290         if (profile.getInUseCasSystemId() != ClientProfile.INVALID_RESOURCE_ID) {
2291             getCasResource(profile.getInUseCasSystemId()).removeOwner(profile.getId());
2292         }
2293         // Clear CiCam
2294         if (profile.getInUseCiCamId() != ClientProfile.INVALID_RESOURCE_ID) {
2295             getCiCamResource(profile.getInUseCiCamId()).removeOwner(profile.getId());
2296         }
2297         // Clear Demux
2298         for (Integer demuxHandle : profile.getInUseDemuxHandles()) {
2299             getDemuxResource(demuxHandle).removeOwner();
2300         }
2301         // Clear Frontend
2302         clearFrontendAndClientMapping(profile);
2303         profile.reclaimAllResources();
2304     }
2305 
2306     @VisibleForTesting
checkClientExists(int clientId)2307     protected boolean checkClientExists(int clientId) {
2308         return mClientProfiles.keySet().contains(clientId);
2309     }
2310 
generateResourceHandle( @unerResourceManager.TunerResourceType int resourceType, int resourceId)2311     private int generateResourceHandle(
2312             @TunerResourceManager.TunerResourceType int resourceType, int resourceId) {
2313         return (resourceType & 0x000000ff) << 24
2314                 | (resourceId << 16)
2315                 | (mResourceRequestCount++ & 0xffff);
2316     }
2317 
2318     @VisibleForTesting
getResourceIdFromHandle(int resourceHandle)2319     protected int getResourceIdFromHandle(int resourceHandle) {
2320         if (resourceHandle == TunerResourceManager.INVALID_RESOURCE_HANDLE) {
2321             return resourceHandle;
2322         }
2323         return (resourceHandle & 0x00ff0000) >> 16;
2324     }
2325 
validateResourceHandle(int resourceType, int resourceHandle)2326     private boolean validateResourceHandle(int resourceType, int resourceHandle) {
2327         if (resourceHandle == TunerResourceManager.INVALID_RESOURCE_HANDLE
2328                 || ((resourceHandle & 0xff000000) >> 24) != resourceType) {
2329             return false;
2330         }
2331         return true;
2332     }
2333 
enforceTrmAccessPermission(String apiName)2334     private void enforceTrmAccessPermission(String apiName) {
2335         getContext().enforceCallingOrSelfPermission("android.permission.TUNER_RESOURCE_ACCESS",
2336                 TAG + ": " + apiName);
2337     }
2338 
enforceTunerAccessPermission(String apiName)2339     private void enforceTunerAccessPermission(String apiName) {
2340         getContext().enforceCallingPermission("android.permission.ACCESS_TV_TUNER",
2341                 TAG + ": " + apiName);
2342     }
2343 
enforceDescramblerAccessPermission(String apiName)2344     private void enforceDescramblerAccessPermission(String apiName) {
2345         getContext().enforceCallingPermission("android.permission.ACCESS_TV_DESCRAMBLER",
2346                 TAG + ": " + apiName);
2347     }
2348 }
2349