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.media.IResourceManagerService;
25 import android.media.tv.TvInputManager;
26 import android.media.tv.tuner.TunerFrontendInfo;
27 import android.media.tv.tunerresourcemanager.CasSessionRequest;
28 import android.media.tv.tunerresourcemanager.IResourcesReclaimListener;
29 import android.media.tv.tunerresourcemanager.ITunerResourceManager;
30 import android.media.tv.tunerresourcemanager.ResourceClientProfile;
31 import android.media.tv.tunerresourcemanager.TunerCiCamRequest;
32 import android.media.tv.tunerresourcemanager.TunerDemuxRequest;
33 import android.media.tv.tunerresourcemanager.TunerDescramblerRequest;
34 import android.media.tv.tunerresourcemanager.TunerFrontendRequest;
35 import android.media.tv.tunerresourcemanager.TunerLnbRequest;
36 import android.media.tv.tunerresourcemanager.TunerResourceManager;
37 import android.os.Binder;
38 import android.os.IBinder;
39 import android.os.RemoteException;
40 import android.util.Log;
41 import android.util.Slog;
42 
43 import com.android.internal.annotations.GuardedBy;
44 import com.android.internal.annotations.VisibleForTesting;
45 import com.android.server.SystemService;
46 
47 import java.util.HashMap;
48 import java.util.HashSet;
49 import java.util.List;
50 import java.util.Map;
51 import java.util.Set;
52 
53 /**
54  * This class provides a system service that manages the TV tuner resources.
55  *
56  * @hide
57  */
58 public class TunerResourceManagerService extends SystemService implements IBinder.DeathRecipient {
59     private static final String TAG = "TunerResourceManagerService";
60     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
61 
62     public static final int INVALID_CLIENT_ID = -1;
63     private static final int MAX_CLIENT_PRIORITY = 1000;
64 
65     // Map of the registered client profiles
66     private Map<Integer, ClientProfile> mClientProfiles = new HashMap<>();
67     private int mNextUnusedClientId = 0;
68 
69     // Map of the current available frontend resources
70     private Map<Integer, FrontendResource> mFrontendResources = new HashMap<>();
71     // Map of the current available lnb resources
72     private Map<Integer, LnbResource> mLnbResources = new HashMap<>();
73     // Map of the current available Cas resources
74     private Map<Integer, CasResource> mCasResources = new HashMap<>();
75     // Map of the current available CiCam resources
76     private Map<Integer, CiCamResource> mCiCamResources = new HashMap<>();
77 
78     @GuardedBy("mLock")
79     private Map<Integer, ResourcesReclaimListenerRecord> mListeners = new HashMap<>();
80 
81     private TvInputManager mTvInputManager;
82     private ActivityManager mActivityManager;
83     private IResourceManagerService mMediaResourceManager;
84     private UseCasePriorityHints mPriorityCongfig = new UseCasePriorityHints();
85 
86     // An internal resource request count to help generate resource handle.
87     private int mResourceRequestCount = 0;
88 
89     // Used to synchronize the access to the service.
90     private final Object mLock = new Object();
91 
TunerResourceManagerService(@ullable Context context)92     public TunerResourceManagerService(@Nullable Context context) {
93         super(context);
94     }
95 
96     @Override
onStart()97     public void onStart() {
98         onStart(false /*isForTesting*/);
99     }
100 
101     @VisibleForTesting
onStart(boolean isForTesting)102     protected void onStart(boolean isForTesting) {
103         if (!isForTesting) {
104             publishBinderService(Context.TV_TUNER_RESOURCE_MGR_SERVICE, new BinderService());
105         }
106         mTvInputManager = (TvInputManager) getContext().getSystemService(Context.TV_INPUT_SERVICE);
107         mActivityManager =
108                 (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE);
109         mPriorityCongfig.parse();
110 
111         if (mMediaResourceManager == null) {
112             IBinder mediaResourceManagerBinder = getBinderService("media.resource_manager");
113             if (mediaResourceManagerBinder == null) {
114                 Slog.w(TAG, "Resource Manager Service not available.");
115                 return;
116             }
117             try {
118                 mediaResourceManagerBinder.linkToDeath(this, /*flags*/ 0);
119             } catch (RemoteException e) {
120                 Slog.w(TAG, "Could not link to death of native resource manager service.");
121                 return;
122             }
123             mMediaResourceManager = IResourceManagerService.Stub.asInterface(
124                     mediaResourceManagerBinder);
125         }
126     }
127 
128     private final class BinderService extends ITunerResourceManager.Stub {
129         @Override
registerClientProfile(@onNull ResourceClientProfile profile, @NonNull IResourcesReclaimListener listener, @NonNull int[] clientId)130         public void registerClientProfile(@NonNull ResourceClientProfile profile,
131                 @NonNull IResourcesReclaimListener listener, @NonNull int[] clientId)
132                 throws RemoteException {
133             enforceTrmAccessPermission("registerClientProfile");
134             enforceTunerAccessPermission("registerClientProfile");
135             if (profile == null) {
136                 throw new RemoteException("ResourceClientProfile can't be null");
137             }
138 
139             if (clientId == null) {
140                 throw new RemoteException("clientId can't be null!");
141             }
142 
143             if (listener == null) {
144                 throw new RemoteException("IResourcesReclaimListener can't be null!");
145             }
146 
147             if (!mPriorityCongfig.isDefinedUseCase(profile.useCase)) {
148                 throw new RemoteException("Use undefined client use case:" + profile.useCase);
149             }
150 
151             synchronized (mLock) {
152                 registerClientProfileInternal(profile, listener, clientId);
153             }
154         }
155 
156         @Override
unregisterClientProfile(int clientId)157         public void unregisterClientProfile(int clientId) throws RemoteException {
158             enforceTrmAccessPermission("unregisterClientProfile");
159             synchronized (mLock) {
160                 if (!checkClientExists(clientId)) {
161                     Slog.e(TAG, "Unregistering non exists client:" + clientId);
162                     return;
163                 }
164                 unregisterClientProfileInternal(clientId);
165             }
166         }
167 
168         @Override
updateClientPriority(int clientId, int priority, int niceValue)169         public boolean updateClientPriority(int clientId, int priority, int niceValue) {
170             enforceTrmAccessPermission("updateClientPriority");
171             synchronized (mLock) {
172                 return updateClientPriorityInternal(clientId, priority, niceValue);
173             }
174         }
175 
176         @Override
setFrontendInfoList(@onNull TunerFrontendInfo[] infos)177         public void setFrontendInfoList(@NonNull TunerFrontendInfo[] infos) throws RemoteException {
178             enforceTrmAccessPermission("setFrontendInfoList");
179             if (infos == null) {
180                 throw new RemoteException("TunerFrontendInfo can't be null");
181             }
182             synchronized (mLock) {
183                 setFrontendInfoListInternal(infos);
184             }
185         }
186 
187         @Override
updateCasInfo(int casSystemId, int maxSessionNum)188         public void updateCasInfo(int casSystemId, int maxSessionNum) {
189             enforceTrmAccessPermission("updateCasInfo");
190             synchronized (mLock) {
191                 updateCasInfoInternal(casSystemId, maxSessionNum);
192             }
193         }
194 
195         @Override
setLnbInfoList(int[] lnbHandles)196         public void setLnbInfoList(int[] lnbHandles) throws RemoteException {
197             enforceTrmAccessPermission("setLnbInfoList");
198             if (lnbHandles == null) {
199                 throw new RemoteException("Lnb handle list can't be null");
200             }
201             synchronized (mLock) {
202                 setLnbInfoListInternal(lnbHandles);
203             }
204         }
205 
206         @Override
requestFrontend(@onNull TunerFrontendRequest request, @NonNull int[] frontendHandle)207         public boolean requestFrontend(@NonNull TunerFrontendRequest request,
208                 @NonNull int[] frontendHandle) throws RemoteException {
209             enforceTunerAccessPermission("requestFrontend");
210             enforceTrmAccessPermission("requestFrontend");
211             if (frontendHandle == null) {
212                 throw new RemoteException("frontendHandle can't be null");
213             }
214             synchronized (mLock) {
215                 if (!checkClientExists(request.clientId)) {
216                     throw new RemoteException("Request frontend from unregistered client: "
217                             + request.clientId);
218                 }
219                 // If the request client is holding or sharing a frontend, throw an exception.
220                 if (!getClientProfile(request.clientId).getInUseFrontendHandles().isEmpty()) {
221                     throw new RemoteException("Release frontend before requesting another one. "
222                             + "Client id: " + request.clientId);
223                 }
224                 return requestFrontendInternal(request, frontendHandle);
225             }
226         }
227 
228         @Override
shareFrontend(int selfClientId, int targetClientId)229         public void shareFrontend(int selfClientId, int targetClientId) throws RemoteException {
230             enforceTunerAccessPermission("shareFrontend");
231             enforceTrmAccessPermission("shareFrontend");
232             synchronized (mLock) {
233                 if (!checkClientExists(selfClientId)) {
234                     throw new RemoteException("Share frontend request from an unregistered client:"
235                             + selfClientId);
236                 }
237                 if (!checkClientExists(targetClientId)) {
238                     throw new RemoteException("Request to share frontend with an unregistered "
239                             + "client:" + targetClientId);
240                 }
241                 if (getClientProfile(targetClientId).getInUseFrontendHandles().isEmpty()) {
242                     throw new RemoteException("Request to share frontend with a client that has no "
243                             + "frontend resources. Target client id:" + targetClientId);
244                 }
245                 shareFrontendInternal(selfClientId, targetClientId);
246             }
247         }
248 
249         @Override
requestDemux(@onNull TunerDemuxRequest request, @NonNull int[] demuxHandle)250         public boolean requestDemux(@NonNull TunerDemuxRequest request,
251                     @NonNull int[] demuxHandle) throws RemoteException {
252             enforceTunerAccessPermission("requestDemux");
253             enforceTrmAccessPermission("requestDemux");
254             if (demuxHandle == null) {
255                 throw new RemoteException("demuxHandle can't be null");
256             }
257             synchronized (mLock) {
258                 if (!checkClientExists(request.clientId)) {
259                     throw new RemoteException("Request demux from unregistered client:"
260                             + request.clientId);
261                 }
262                 return requestDemuxInternal(request, demuxHandle);
263             }
264         }
265 
266         @Override
requestDescrambler(@onNull TunerDescramblerRequest request, @NonNull int[] descramblerHandle)267         public boolean requestDescrambler(@NonNull TunerDescramblerRequest request,
268                     @NonNull int[] descramblerHandle) throws RemoteException {
269             enforceDescramblerAccessPermission("requestDescrambler");
270             enforceTrmAccessPermission("requestDescrambler");
271             if (descramblerHandle == null) {
272                 throw new RemoteException("descramblerHandle can't be null");
273             }
274             synchronized (mLock) {
275                 if (!checkClientExists(request.clientId)) {
276                     throw new RemoteException("Request descrambler from unregistered client:"
277                             + request.clientId);
278                 }
279                 return requestDescramblerInternal(request, descramblerHandle);
280             }
281         }
282 
283         @Override
requestCasSession(@onNull CasSessionRequest request, @NonNull int[] casSessionHandle)284         public boolean requestCasSession(@NonNull CasSessionRequest request,
285                 @NonNull int[] casSessionHandle) throws RemoteException {
286             enforceTrmAccessPermission("requestCasSession");
287             if (casSessionHandle == null) {
288                 throw new RemoteException("casSessionHandle can't be null");
289             }
290             synchronized (mLock) {
291                 if (!checkClientExists(request.clientId)) {
292                     throw new RemoteException("Request cas from unregistered client:"
293                             + request.clientId);
294                 }
295                 return requestCasSessionInternal(request, casSessionHandle);
296             }
297         }
298 
299         @Override
requestCiCam(@onNull TunerCiCamRequest request, @NonNull int[] ciCamHandle)300         public boolean requestCiCam(@NonNull TunerCiCamRequest request,
301                 @NonNull int[] ciCamHandle) throws RemoteException {
302             enforceTrmAccessPermission("requestCiCam");
303             if (ciCamHandle == null) {
304                 throw new RemoteException("ciCamHandle can't be null");
305             }
306             synchronized (mLock) {
307                 if (!checkClientExists(request.clientId)) {
308                     throw new RemoteException("Request ciCam from unregistered client:"
309                             + request.clientId);
310                 }
311                 return requestCiCamInternal(request, ciCamHandle);
312             }
313         }
314 
315         @Override
requestLnb(@onNull TunerLnbRequest request, @NonNull int[] lnbHandle)316         public boolean requestLnb(@NonNull TunerLnbRequest request, @NonNull int[] lnbHandle)
317                 throws RemoteException {
318             enforceTunerAccessPermission("requestLnb");
319             enforceTrmAccessPermission("requestLnb");
320             if (lnbHandle == null) {
321                 throw new RemoteException("lnbHandle can't be null");
322             }
323             synchronized (mLock) {
324                 if (!checkClientExists(request.clientId)) {
325                     throw new RemoteException("Request lnb from unregistered client:"
326                             + request.clientId);
327                 }
328                 return requestLnbInternal(request, lnbHandle);
329             }
330         }
331 
332         @Override
releaseFrontend(int frontendHandle, int clientId)333         public void releaseFrontend(int frontendHandle, int clientId) throws RemoteException {
334             enforceTunerAccessPermission("releaseFrontend");
335             enforceTrmAccessPermission("releaseFrontend");
336             if (!validateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND,
337                     frontendHandle)) {
338                 throw new RemoteException("frontendHandle can't be invalid");
339             }
340             synchronized (mLock) {
341                 if (!checkClientExists(clientId)) {
342                     throw new RemoteException("Release frontend from unregistered client:"
343                             + clientId);
344                 }
345                 FrontendResource fe = getFrontendResource(frontendHandle);
346                 if (fe == null) {
347                     throw new RemoteException("Releasing frontend does not exist.");
348                 }
349                 if (fe.getOwnerClientId() != clientId) {
350                     throw new RemoteException(
351                             "Client is not the current owner of the releasing fe.");
352                 }
353                 releaseFrontendInternal(fe, clientId);
354             }
355         }
356 
357         @Override
releaseDemux(int demuxHandle, int clientId)358         public void releaseDemux(int demuxHandle, int clientId) {
359             enforceTunerAccessPermission("releaseDemux");
360             enforceTrmAccessPermission("releaseDemux");
361             if (DEBUG) {
362                 Slog.d(TAG, "releaseDemux(demuxHandle=" + demuxHandle + ")");
363             }
364         }
365 
366         @Override
releaseDescrambler(int descramblerHandle, int clientId)367         public void releaseDescrambler(int descramblerHandle, int clientId) {
368             enforceTunerAccessPermission("releaseDescrambler");
369             enforceTrmAccessPermission("releaseDescrambler");
370             if (DEBUG) {
371                 Slog.d(TAG, "releaseDescrambler(descramblerHandle=" + descramblerHandle + ")");
372             }
373         }
374 
375         @Override
releaseCasSession(int casSessionHandle, int clientId)376         public void releaseCasSession(int casSessionHandle, int clientId) throws RemoteException {
377             enforceTrmAccessPermission("releaseCasSession");
378             if (!validateResourceHandle(
379                     TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, casSessionHandle)) {
380                 throw new RemoteException("casSessionHandle can't be invalid");
381             }
382             synchronized (mLock) {
383                 if (!checkClientExists(clientId)) {
384                     throw new RemoteException("Release cas from unregistered client:" + clientId);
385                 }
386                 int casSystemId = getClientProfile(clientId).getInUseCasSystemId();
387                 CasResource cas = getCasResource(casSystemId);
388                 if (cas == null) {
389                     throw new RemoteException("Releasing cas does not exist.");
390                 }
391                 if (!cas.getOwnerClientIds().contains(clientId)) {
392                     throw new RemoteException(
393                             "Client is not the current owner of the releasing cas.");
394                 }
395                 releaseCasSessionInternal(cas, clientId);
396             }
397         }
398 
399         @Override
releaseCiCam(int ciCamHandle, int clientId)400         public void releaseCiCam(int ciCamHandle, int clientId) throws RemoteException {
401             enforceTrmAccessPermission("releaseCiCam");
402             if (!validateResourceHandle(
403                     TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM, ciCamHandle)) {
404                 throw new RemoteException("ciCamHandle can't be invalid");
405             }
406             synchronized (mLock) {
407                 if (!checkClientExists(clientId)) {
408                     throw new RemoteException("Release ciCam from unregistered client:" + clientId);
409                 }
410                 int ciCamId = getClientProfile(clientId).getInUseCiCamId();
411                 if (ciCamId != getResourceIdFromHandle(ciCamHandle)) {
412                     throw new RemoteException("The client " + clientId + " is not the owner of "
413                             + "the releasing ciCam.");
414                 }
415                 CiCamResource ciCam = getCiCamResource(ciCamId);
416                 if (ciCam == null) {
417                     throw new RemoteException("Releasing ciCam does not exist.");
418                 }
419                 if (!ciCam.getOwnerClientIds().contains(clientId)) {
420                     throw new RemoteException(
421                             "Client is not the current owner of the releasing ciCam.");
422                 }
423                 releaseCiCamInternal(ciCam, clientId);
424             }
425         }
426 
427         @Override
releaseLnb(int lnbHandle, int clientId)428         public void releaseLnb(int lnbHandle, int clientId) throws RemoteException {
429             enforceTunerAccessPermission("releaseLnb");
430             enforceTrmAccessPermission("releaseLnb");
431             if (!validateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_LNB, lnbHandle)) {
432                 throw new RemoteException("lnbHandle can't be invalid");
433             }
434             if (!checkClientExists(clientId)) {
435                 throw new RemoteException("Release lnb from unregistered client:" + clientId);
436             }
437             LnbResource lnb = getLnbResource(lnbHandle);
438             if (lnb == null) {
439                 throw new RemoteException("Releasing lnb does not exist.");
440             }
441             if (lnb.getOwnerClientId() != clientId) {
442                 throw new RemoteException("Client is not the current owner of the releasing lnb.");
443             }
444             synchronized (mLock) {
445                 releaseLnbInternal(lnb);
446             }
447         }
448 
449         @Override
isHigherPriority( ResourceClientProfile challengerProfile, ResourceClientProfile holderProfile)450         public boolean isHigherPriority(
451                 ResourceClientProfile challengerProfile, ResourceClientProfile holderProfile)
452                 throws RemoteException {
453             enforceTrmAccessPermission("isHigherPriority");
454             if (challengerProfile == null || holderProfile == null) {
455                 throw new RemoteException("Client profiles can't be null.");
456             }
457             synchronized (mLock) {
458                 return isHigherPriorityInternal(challengerProfile, holderProfile);
459             }
460         }
461     }
462 
463     /**
464      * Handle the death of the native resource manager service
465      */
466     @Override
binderDied()467     public void binderDied() {
468         if (DEBUG) {
469             Slog.w(TAG, "Native media resource manager service has died");
470         }
471         synchronized (mLock) {
472             mMediaResourceManager = null;
473         }
474     }
475 
476     @VisibleForTesting
registerClientProfileInternal(ResourceClientProfile profile, IResourcesReclaimListener listener, int[] clientId)477     protected void registerClientProfileInternal(ResourceClientProfile profile,
478             IResourcesReclaimListener listener, int[] clientId) {
479         if (DEBUG) {
480             Slog.d(TAG, "registerClientProfile(clientProfile=" + profile + ")");
481         }
482 
483         clientId[0] = INVALID_CLIENT_ID;
484         if (mTvInputManager == null) {
485             Slog.e(TAG, "TvInputManager is null. Can't register client profile.");
486             return;
487         }
488         // TODO tell if the client already exists
489         clientId[0] = mNextUnusedClientId++;
490 
491         int pid = profile.tvInputSessionId == null
492                 ? Binder.getCallingPid() /*callingPid*/
493                 : mTvInputManager.getClientPid(profile.tvInputSessionId); /*tvAppId*/
494 
495         // Update Media Resource Manager with the tvAppId
496         if (profile.tvInputSessionId != null && mMediaResourceManager != null) {
497             try {
498                 mMediaResourceManager.overridePid(Binder.getCallingPid(), pid);
499             } catch (RemoteException e) {
500                 Slog.e(TAG, "Could not overridePid in resourceManagerSercice,"
501                         + " remote exception: " + e);
502             }
503         }
504 
505         ClientProfile clientProfile = new ClientProfile.Builder(clientId[0])
506                                               .tvInputSessionId(profile.tvInputSessionId)
507                                               .useCase(profile.useCase)
508                                               .processId(pid)
509                                               .build();
510         clientProfile.setForeground(checkIsForeground(pid));
511         clientProfile.setPriority(
512                 getClientPriority(profile.useCase, clientProfile.isForeground()));
513 
514         addClientProfile(clientId[0], clientProfile, listener);
515     }
516 
517     @VisibleForTesting
unregisterClientProfileInternal(int clientId)518     protected void unregisterClientProfileInternal(int clientId) {
519         if (DEBUG) {
520             Slog.d(TAG, "unregisterClientProfile(clientId=" + clientId + ")");
521         }
522         removeClientProfile(clientId);
523         // Remove the Media Resource Manager callingPid to tvAppId mapping
524         if (mMediaResourceManager != null) {
525             try {
526                 mMediaResourceManager.overridePid(Binder.getCallingPid(), -1);
527             } catch (RemoteException e) {
528                 Slog.e(TAG, "Could not overridePid in resourceManagerSercice when unregister,"
529                         + " remote exception: " + e);
530             }
531         }
532     }
533 
534     @VisibleForTesting
updateClientPriorityInternal(int clientId, int priority, int niceValue)535     protected boolean updateClientPriorityInternal(int clientId, int priority, int niceValue) {
536         if (DEBUG) {
537             Slog.d(TAG,
538                     "updateClientPriority(clientId=" + clientId + ", priority=" + priority
539                             + ", niceValue=" + niceValue + ")");
540         }
541 
542         ClientProfile profile = getClientProfile(clientId);
543         if (profile == null) {
544             Slog.e(TAG,
545                     "Can not find client profile with id " + clientId
546                             + " when trying to update the client priority.");
547             return false;
548         }
549 
550         profile.setForeground(checkIsForeground(profile.getProcessId()));
551         profile.setPriority(priority);
552         profile.setNiceValue(niceValue);
553 
554         return true;
555     }
556 
557     @VisibleForTesting
setFrontendInfoListInternal(TunerFrontendInfo[] infos)558     protected void setFrontendInfoListInternal(TunerFrontendInfo[] infos) {
559         if (DEBUG) {
560             Slog.d(TAG, "updateFrontendInfo:");
561             for (int i = 0; i < infos.length; i++) {
562                 Slog.d(TAG, infos[i].toString());
563             }
564         }
565 
566         // A set to record the frontends pending on updating. Ids will be removed
567         // from this set once its updating finished. Any frontend left in this set when all
568         // the updates are done will be removed from mFrontendResources.
569         Set<Integer> updatingFrontendHandles = new HashSet<>(getFrontendResources().keySet());
570 
571         // Update frontendResources map and other mappings accordingly
572         for (int i = 0; i < infos.length; i++) {
573             if (getFrontendResource(infos[i].handle) != null) {
574                 if (DEBUG) {
575                     Slog.d(TAG, "Frontend handle=" + infos[i].handle + "exists.");
576                 }
577                 updatingFrontendHandles.remove(infos[i].handle);
578             } else {
579                 // Add a new fe resource
580                 FrontendResource newFe = new FrontendResource.Builder(infos[i].handle)
581                                                  .type(infos[i].type)
582                                                  .exclusiveGroupId(infos[i].exclusiveGroupId)
583                                                  .build();
584                 addFrontendResource(newFe);
585             }
586         }
587 
588         for (int removingHandle : updatingFrontendHandles) {
589             // update the exclusive group id member list
590             removeFrontendResource(removingHandle);
591         }
592     }
593 
594     @VisibleForTesting
setLnbInfoListInternal(int[] lnbHandles)595     protected void setLnbInfoListInternal(int[] lnbHandles) {
596         if (DEBUG) {
597             for (int i = 0; i < lnbHandles.length; i++) {
598                 Slog.d(TAG, "updateLnbInfo(lnbHanle=" + lnbHandles[i] + ")");
599             }
600         }
601 
602         // A set to record the Lnbs pending on updating. Handles will be removed
603         // from this set once its updating finished. Any lnb left in this set when all
604         // the updates are done will be removed from mLnbResources.
605         Set<Integer> updatingLnbHandles = new HashSet<>(getLnbResources().keySet());
606 
607         // Update lnbResources map and other mappings accordingly
608         for (int i = 0; i < lnbHandles.length; i++) {
609             if (getLnbResource(lnbHandles[i]) != null) {
610                 if (DEBUG) {
611                     Slog.d(TAG, "Lnb handle=" + lnbHandles[i] + "exists.");
612                 }
613                 updatingLnbHandles.remove(lnbHandles[i]);
614             } else {
615                 // Add a new lnb resource
616                 LnbResource newLnb = new LnbResource.Builder(lnbHandles[i]).build();
617                 addLnbResource(newLnb);
618             }
619         }
620 
621         for (int removingHandle : updatingLnbHandles) {
622             removeLnbResource(removingHandle);
623         }
624     }
625 
626     @VisibleForTesting
updateCasInfoInternal(int casSystemId, int maxSessionNum)627     protected void updateCasInfoInternal(int casSystemId, int maxSessionNum) {
628         if (DEBUG) {
629             Slog.d(TAG,
630                     "updateCasInfo(casSystemId=" + casSystemId
631                             + ", maxSessionNum=" + maxSessionNum + ")");
632         }
633         // If maxSessionNum is 0, removing the Cas Resource.
634         if (maxSessionNum == 0) {
635             removeCasResource(casSystemId);
636             removeCiCamResource(casSystemId);
637             return;
638         }
639         // If the Cas exists, updates the Cas Resource accordingly.
640         CasResource cas = getCasResource(casSystemId);
641         CiCamResource ciCam = getCiCamResource(casSystemId);
642         if (cas != null) {
643             if (cas.getUsedSessionNum() > maxSessionNum) {
644                 // Sort and release the short number of Cas resources.
645                 int releasingCasResourceNum = cas.getUsedSessionNum() - maxSessionNum;
646                 // TODO: handle CiCam session update.
647             }
648             cas.updateMaxSessionNum(maxSessionNum);
649             if (ciCam != null) {
650                 ciCam.updateMaxSessionNum(maxSessionNum);
651             }
652             return;
653         }
654         // Add the new Cas Resource.
655         cas = new CasResource.Builder(casSystemId)
656                              .maxSessionNum(maxSessionNum)
657                              .build();
658         ciCam = new CiCamResource.Builder(casSystemId)
659                              .maxSessionNum(maxSessionNum)
660                              .build();
661         addCasResource(cas);
662         addCiCamResource(ciCam);
663     }
664 
665     @VisibleForTesting
requestFrontendInternal(TunerFrontendRequest request, int[] frontendHandle)666     protected boolean requestFrontendInternal(TunerFrontendRequest request, int[] frontendHandle) {
667         if (DEBUG) {
668             Slog.d(TAG, "requestFrontend(request=" + request + ")");
669         }
670 
671         frontendHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
672         ClientProfile requestClient = getClientProfile(request.clientId);
673         if (requestClient == null) {
674             return false;
675         }
676         clientPriorityUpdateOnRequest(requestClient);
677         int grantingFrontendHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
678         int inUseLowestPriorityFrHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
679         // Priority max value is 1000
680         int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
681         for (FrontendResource fr : getFrontendResources().values()) {
682             if (fr.getType() == request.frontendType) {
683                 if (!fr.isInUse()) {
684                     // Grant unused frontend with no exclusive group members first.
685                     if (fr.getExclusiveGroupMemberFeHandles().isEmpty()) {
686                         grantingFrontendHandle = fr.getHandle();
687                         break;
688                     } else if (grantingFrontendHandle
689                             == TunerResourceManager.INVALID_RESOURCE_HANDLE) {
690                         // Grant the unused frontend with lower id first if all the unused
691                         // frontends have exclusive group members.
692                         grantingFrontendHandle = fr.getHandle();
693                     }
694                 } else if (grantingFrontendHandle == TunerResourceManager.INVALID_RESOURCE_HANDLE) {
695                     // Record the frontend id with the lowest client priority among all the
696                     // in use frontends when no available frontend has been found.
697                     int priority = getOwnerClientPriority(fr.getOwnerClientId());
698                     if (currentLowestPriority > priority) {
699                         inUseLowestPriorityFrHandle = fr.getHandle();
700                         currentLowestPriority = priority;
701                     }
702                 }
703             }
704         }
705 
706         // Grant frontend when there is unused resource.
707         if (grantingFrontendHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE) {
708             frontendHandle[0] = grantingFrontendHandle;
709             updateFrontendClientMappingOnNewGrant(grantingFrontendHandle, request.clientId);
710             return true;
711         }
712 
713         // When all the resources are occupied, grant the lowest priority resource if the
714         // request client has higher priority.
715         if (inUseLowestPriorityFrHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE
716                 && (requestClient.getPriority() > currentLowestPriority)) {
717             if (!reclaimResource(
718                     getFrontendResource(inUseLowestPriorityFrHandle).getOwnerClientId(),
719                     TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
720                 return false;
721             }
722             frontendHandle[0] = inUseLowestPriorityFrHandle;
723             updateFrontendClientMappingOnNewGrant(
724                     inUseLowestPriorityFrHandle, request.clientId);
725             return true;
726         }
727 
728         return false;
729     }
730 
731     @VisibleForTesting
shareFrontendInternal(int selfClientId, int targetClientId)732     protected void shareFrontendInternal(int selfClientId, int targetClientId) {
733         if (DEBUG) {
734             Slog.d(TAG, "shareFrontend from " + selfClientId + " with " + targetClientId);
735         }
736         for (int feId : getClientProfile(targetClientId).getInUseFrontendHandles()) {
737             getClientProfile(selfClientId).useFrontend(feId);
738         }
739         getClientProfile(targetClientId).shareFrontend(selfClientId);
740     }
741 
742     @VisibleForTesting
requestLnbInternal(TunerLnbRequest request, int[] lnbHandle)743     protected boolean requestLnbInternal(TunerLnbRequest request, int[] lnbHandle) {
744         if (DEBUG) {
745             Slog.d(TAG, "requestLnb(request=" + request + ")");
746         }
747 
748         lnbHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
749         ClientProfile requestClient = getClientProfile(request.clientId);
750         clientPriorityUpdateOnRequest(requestClient);
751         int grantingLnbHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
752         int inUseLowestPriorityLnbHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
753         // Priority max value is 1000
754         int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
755         for (LnbResource lnb : getLnbResources().values()) {
756             if (!lnb.isInUse()) {
757                 // Grant the unused lnb with lower handle first
758                 grantingLnbHandle = lnb.getHandle();
759                 break;
760             } else {
761                 // Record the lnb id with the lowest client priority among all the
762                 // in use lnb when no available lnb has been found.
763                 int priority = getOwnerClientPriority(lnb.getOwnerClientId());
764                 if (currentLowestPriority > priority) {
765                     inUseLowestPriorityLnbHandle = lnb.getHandle();
766                     currentLowestPriority = priority;
767                 }
768             }
769         }
770 
771         // Grant Lnb when there is unused resource.
772         if (grantingLnbHandle > -1) {
773             lnbHandle[0] = grantingLnbHandle;
774             updateLnbClientMappingOnNewGrant(grantingLnbHandle, request.clientId);
775             return true;
776         }
777 
778         // When all the resources are occupied, grant the lowest priority resource if the
779         // request client has higher priority.
780         if (inUseLowestPriorityLnbHandle > TunerResourceManager.INVALID_RESOURCE_HANDLE
781                 && (requestClient.getPriority() > currentLowestPriority)) {
782             if (!reclaimResource(getLnbResource(inUseLowestPriorityLnbHandle).getOwnerClientId(),
783                     TunerResourceManager.TUNER_RESOURCE_TYPE_LNB)) {
784                 return false;
785             }
786             lnbHandle[0] = inUseLowestPriorityLnbHandle;
787             updateLnbClientMappingOnNewGrant(inUseLowestPriorityLnbHandle, request.clientId);
788             return true;
789         }
790 
791         return false;
792     }
793 
794     @VisibleForTesting
requestCasSessionInternal(CasSessionRequest request, int[] casSessionHandle)795     protected boolean requestCasSessionInternal(CasSessionRequest request, int[] casSessionHandle) {
796         if (DEBUG) {
797             Slog.d(TAG, "requestCasSession(request=" + request + ")");
798         }
799         CasResource cas = getCasResource(request.casSystemId);
800         // Unregistered Cas System is treated as having unlimited sessions.
801         if (cas == null) {
802             cas = new CasResource.Builder(request.casSystemId)
803                                  .maxSessionNum(Integer.MAX_VALUE)
804                                  .build();
805             addCasResource(cas);
806         }
807         casSessionHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
808         ClientProfile requestClient = getClientProfile(request.clientId);
809         clientPriorityUpdateOnRequest(requestClient);
810         int lowestPriorityOwnerId = -1;
811         // Priority max value is 1000
812         int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
813         if (!cas.isFullyUsed()) {
814             casSessionHandle[0] = generateResourceHandle(
815                     TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, cas.getSystemId());
816             updateCasClientMappingOnNewGrant(request.casSystemId, request.clientId);
817             return true;
818         }
819         for (int ownerId : cas.getOwnerClientIds()) {
820             // Record the client id with lowest priority that is using the current Cas system.
821             int priority = getOwnerClientPriority(ownerId);
822             if (currentLowestPriority > priority) {
823                 lowestPriorityOwnerId = ownerId;
824                 currentLowestPriority = priority;
825             }
826         }
827 
828         // When all the Cas sessions are occupied, reclaim the lowest priority client if the
829         // request client has higher priority.
830         if (lowestPriorityOwnerId > -1 && (requestClient.getPriority() > currentLowestPriority)) {
831             if (!reclaimResource(lowestPriorityOwnerId,
832                     TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION)) {
833                 return false;
834             }
835             casSessionHandle[0] = generateResourceHandle(
836                     TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, cas.getSystemId());
837             updateCasClientMappingOnNewGrant(request.casSystemId, request.clientId);
838             return true;
839         }
840         return false;
841     }
842 
843     @VisibleForTesting
requestCiCamInternal(TunerCiCamRequest request, int[] ciCamHandle)844     protected boolean requestCiCamInternal(TunerCiCamRequest request, int[] ciCamHandle) {
845         if (DEBUG) {
846             Slog.d(TAG, "requestCiCamInternal(TunerCiCamRequest=" + request + ")");
847         }
848         CiCamResource ciCam = getCiCamResource(request.ciCamId);
849         // Unregistered Cas System is treated as having unlimited sessions.
850         if (ciCam == null) {
851             ciCam = new CiCamResource.Builder(request.ciCamId)
852                                      .maxSessionNum(Integer.MAX_VALUE)
853                                      .build();
854             addCiCamResource(ciCam);
855         }
856         ciCamHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
857         ClientProfile requestClient = getClientProfile(request.clientId);
858         clientPriorityUpdateOnRequest(requestClient);
859         int lowestPriorityOwnerId = -1;
860         // Priority max value is 1000
861         int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
862         if (!ciCam.isFullyUsed()) {
863             ciCamHandle[0] = generateResourceHandle(
864                     TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM, ciCam.getCiCamId());
865             updateCiCamClientMappingOnNewGrant(request.ciCamId, request.clientId);
866             return true;
867         }
868         for (int ownerId : ciCam.getOwnerClientIds()) {
869             // Record the client id with lowest priority that is using the current Cas system.
870             int priority = getOwnerClientPriority(ownerId);
871             if (currentLowestPriority > priority) {
872                 lowestPriorityOwnerId = ownerId;
873                 currentLowestPriority = priority;
874             }
875         }
876 
877         // When all the CiCam sessions are occupied, reclaim the lowest priority client if the
878         // request client has higher priority.
879         if (lowestPriorityOwnerId > -1 && (requestClient.getPriority() > currentLowestPriority)) {
880             if (!reclaimResource(lowestPriorityOwnerId,
881                     TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM)) {
882                 return false;
883             }
884             ciCamHandle[0] = generateResourceHandle(
885                     TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM, ciCam.getCiCamId());
886             updateCiCamClientMappingOnNewGrant(request.ciCamId, request.clientId);
887             return true;
888         }
889         return false;
890     }
891 
892     @VisibleForTesting
isHigherPriorityInternal(ResourceClientProfile challengerProfile, ResourceClientProfile holderProfile)893     protected boolean isHigherPriorityInternal(ResourceClientProfile challengerProfile,
894             ResourceClientProfile holderProfile) {
895         if (DEBUG) {
896             Slog.d(TAG,
897                     "isHigherPriority(challengerProfile=" + challengerProfile
898                             + ", holderProfile=" + challengerProfile + ")");
899         }
900         if (mTvInputManager == null) {
901             Slog.e(TAG, "TvInputManager is null. Can't compare the priority.");
902             // Allow the client to acquire the hardware interface
903             // when the TRM is not able to compare the priority.
904             return true;
905         }
906 
907         int challengerPid = challengerProfile.tvInputSessionId == null
908                 ? Binder.getCallingPid() /*callingPid*/
909                 : mTvInputManager.getClientPid(challengerProfile.tvInputSessionId); /*tvAppId*/
910         int holderPid = holderProfile.tvInputSessionId == null
911                 ? Binder.getCallingPid() /*callingPid*/
912                 : mTvInputManager.getClientPid(holderProfile.tvInputSessionId); /*tvAppId*/
913 
914         int challengerPriority = getClientPriority(
915                 challengerProfile.useCase, checkIsForeground(challengerPid));
916         int holderPriority = getClientPriority(holderProfile.useCase, checkIsForeground(holderPid));
917         return challengerPriority > holderPriority;
918     }
919 
920     @VisibleForTesting
releaseFrontendInternal(FrontendResource fe, int clientId)921     protected void releaseFrontendInternal(FrontendResource fe, int clientId) {
922         if (DEBUG) {
923             Slog.d(TAG, "releaseFrontend(id=" + fe.getHandle() + ", clientId=" + clientId + " )");
924         }
925         if (clientId == fe.getOwnerClientId()) {
926             ClientProfile ownerClient = getClientProfile(fe.getOwnerClientId());
927             for (int shareOwnerId : ownerClient.getShareFeClientIds()) {
928                 clearFrontendAndClientMapping(getClientProfile(shareOwnerId));
929             }
930         }
931         clearFrontendAndClientMapping(getClientProfile(clientId));
932     }
933 
934     @VisibleForTesting
releaseLnbInternal(LnbResource lnb)935     protected void releaseLnbInternal(LnbResource lnb) {
936         if (DEBUG) {
937             Slog.d(TAG, "releaseLnb(lnbHandle=" + lnb.getHandle() + ")");
938         }
939         updateLnbClientMappingOnRelease(lnb);
940     }
941 
942     @VisibleForTesting
releaseCasSessionInternal(CasResource cas, int ownerClientId)943     protected void releaseCasSessionInternal(CasResource cas, int ownerClientId) {
944         if (DEBUG) {
945             Slog.d(TAG, "releaseCasSession(sessionResourceId=" + cas.getSystemId() + ")");
946         }
947         updateCasClientMappingOnRelease(cas, ownerClientId);
948     }
949 
950     @VisibleForTesting
releaseCiCamInternal(CiCamResource ciCam, int ownerClientId)951     protected void releaseCiCamInternal(CiCamResource ciCam, int ownerClientId) {
952         if (DEBUG) {
953             Slog.d(TAG, "releaseCiCamInternal(ciCamId=" + ciCam.getCiCamId() + ")");
954         }
955         updateCiCamClientMappingOnRelease(ciCam, ownerClientId);
956     }
957 
958     @VisibleForTesting
requestDemuxInternal(TunerDemuxRequest request, int[] demuxHandle)959     protected boolean requestDemuxInternal(TunerDemuxRequest request, int[] demuxHandle) {
960         if (DEBUG) {
961             Slog.d(TAG, "requestDemux(request=" + request + ")");
962         }
963         // There are enough Demux resources, so we don't manage Demux in R.
964         demuxHandle[0] = generateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, 0);
965         return true;
966     }
967 
968     @VisibleForTesting
969     // This mothod is to sync up the request client's foreground/background status and update
970     // the client priority accordingly whenever new resource request comes in.
clientPriorityUpdateOnRequest(ClientProfile requestProfile)971     protected void clientPriorityUpdateOnRequest(ClientProfile requestProfile) {
972         int pid = requestProfile.getProcessId();
973         boolean currentIsForeground = checkIsForeground(pid);
974         if (requestProfile.isForeground() == currentIsForeground) {
975             // To avoid overriding the priority set through updateClientPriority API.
976             return;
977         }
978         requestProfile.setForeground(currentIsForeground);
979         requestProfile.setPriority(
980                 getClientPriority(requestProfile.getUseCase(), currentIsForeground));
981     }
982 
983     @VisibleForTesting
requestDescramblerInternal( TunerDescramblerRequest request, int[] descramblerHandle)984     protected boolean requestDescramblerInternal(
985             TunerDescramblerRequest request, int[] descramblerHandle) {
986         if (DEBUG) {
987             Slog.d(TAG, "requestDescrambler(request=" + request + ")");
988         }
989         // There are enough Descrambler resources, so we don't manage Descrambler in R.
990         descramblerHandle[0] =
991                 generateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_DESCRAMBLER, 0);
992         return true;
993     }
994 
995     @VisibleForTesting
996     protected class ResourcesReclaimListenerRecord implements IBinder.DeathRecipient {
997         private final IResourcesReclaimListener mListener;
998         private final int mClientId;
999 
ResourcesReclaimListenerRecord(IResourcesReclaimListener listener, int clientId)1000         public ResourcesReclaimListenerRecord(IResourcesReclaimListener listener, int clientId) {
1001             mListener = listener;
1002             mClientId = clientId;
1003         }
1004 
1005         @Override
binderDied()1006         public void binderDied() {
1007             synchronized (mLock) {
1008                 removeClientProfile(mClientId);
1009             }
1010         }
1011 
getId()1012         public int getId() {
1013             return mClientId;
1014         }
1015 
getListener()1016         public IResourcesReclaimListener getListener() {
1017             return mListener;
1018         }
1019     }
1020 
addResourcesReclaimListener(int clientId, IResourcesReclaimListener listener)1021     private void addResourcesReclaimListener(int clientId, IResourcesReclaimListener listener) {
1022         if (listener == null) {
1023             if (DEBUG) {
1024                 Slog.w(TAG, "Listener is null when client " + clientId + " registered!");
1025             }
1026             return;
1027         }
1028 
1029         ResourcesReclaimListenerRecord record =
1030                 new ResourcesReclaimListenerRecord(listener, clientId);
1031 
1032         try {
1033             listener.asBinder().linkToDeath(record, 0);
1034         } catch (RemoteException e) {
1035             Slog.w(TAG, "Listener already died.");
1036             return;
1037         }
1038 
1039         mListeners.put(clientId, record);
1040     }
1041 
1042     @VisibleForTesting
reclaimResource(int reclaimingClientId, @TunerResourceManager.TunerResourceType int resourceType)1043     protected boolean reclaimResource(int reclaimingClientId,
1044             @TunerResourceManager.TunerResourceType int resourceType) {
1045         if (DEBUG) {
1046             Slog.d(TAG, "Reclaiming resources because higher priority client request resource type "
1047                     + resourceType);
1048         }
1049         try {
1050             mListeners.get(reclaimingClientId).getListener().onReclaimResources();
1051         } catch (RemoteException e) {
1052             Slog.e(TAG, "Failed to reclaim resources on client " + reclaimingClientId, e);
1053             return false;
1054         }
1055 
1056         // Reclaim all the resources of the share owners of the frontend that is used by the current
1057         // resource reclaimed client.
1058         ClientProfile profile = getClientProfile(reclaimingClientId);
1059         Set<Integer> shareFeClientIds = profile.getShareFeClientIds();
1060         for (int clientId : shareFeClientIds) {
1061             try {
1062                 mListeners.get(clientId).getListener().onReclaimResources();
1063             } catch (RemoteException e) {
1064                 Slog.e(TAG, "Failed to reclaim resources on client " + clientId, e);
1065                 return false;
1066             }
1067             clearAllResourcesAndClientMapping(getClientProfile(clientId));
1068         }
1069         clearAllResourcesAndClientMapping(profile);
1070         return true;
1071     }
1072 
1073     @VisibleForTesting
getClientPriority(int useCase, boolean isForeground)1074     protected int getClientPriority(int useCase, boolean isForeground) {
1075         if (DEBUG) {
1076             Slog.d(TAG, "getClientPriority useCase=" + useCase
1077                     + ", isForeground=" + isForeground + ")");
1078         }
1079 
1080         if (isForeground) {
1081             return mPriorityCongfig.getForegroundPriority(useCase);
1082         }
1083         return mPriorityCongfig.getBackgroundPriority(useCase);
1084     }
1085 
1086     @VisibleForTesting
checkIsForeground(int pid)1087     protected boolean checkIsForeground(int pid) {
1088         if (mActivityManager == null) {
1089             return false;
1090         }
1091         List<RunningAppProcessInfo> appProcesses = mActivityManager.getRunningAppProcesses();
1092         if (appProcesses == null) {
1093             return false;
1094         }
1095         for (RunningAppProcessInfo appProcess : appProcesses) {
1096             if (appProcess.pid == pid
1097                     && appProcess.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
1098                 return true;
1099             }
1100         }
1101         return false;
1102     }
1103 
updateFrontendClientMappingOnNewGrant(int grantingHandle, int ownerClientId)1104     private void updateFrontendClientMappingOnNewGrant(int grantingHandle, int ownerClientId) {
1105         FrontendResource grantingFrontend = getFrontendResource(grantingHandle);
1106         ClientProfile ownerProfile = getClientProfile(ownerClientId);
1107         grantingFrontend.setOwner(ownerClientId);
1108         ownerProfile.useFrontend(grantingHandle);
1109         for (int exclusiveGroupMember : grantingFrontend.getExclusiveGroupMemberFeHandles()) {
1110             getFrontendResource(exclusiveGroupMember).setOwner(ownerClientId);
1111             ownerProfile.useFrontend(exclusiveGroupMember);
1112         }
1113     }
1114 
updateLnbClientMappingOnNewGrant(int grantingHandle, int ownerClientId)1115     private void updateLnbClientMappingOnNewGrant(int grantingHandle, int ownerClientId) {
1116         LnbResource grantingLnb = getLnbResource(grantingHandle);
1117         ClientProfile ownerProfile = getClientProfile(ownerClientId);
1118         grantingLnb.setOwner(ownerClientId);
1119         ownerProfile.useLnb(grantingHandle);
1120     }
1121 
updateLnbClientMappingOnRelease(@onNull LnbResource releasingLnb)1122     private void updateLnbClientMappingOnRelease(@NonNull LnbResource releasingLnb) {
1123         ClientProfile ownerProfile = getClientProfile(releasingLnb.getOwnerClientId());
1124         releasingLnb.removeOwner();
1125         ownerProfile.releaseLnb(releasingLnb.getHandle());
1126     }
1127 
updateCasClientMappingOnNewGrant(int grantingId, int ownerClientId)1128     private void updateCasClientMappingOnNewGrant(int grantingId, int ownerClientId) {
1129         CasResource grantingCas = getCasResource(grantingId);
1130         ClientProfile ownerProfile = getClientProfile(ownerClientId);
1131         grantingCas.setOwner(ownerClientId);
1132         ownerProfile.useCas(grantingId);
1133     }
1134 
updateCiCamClientMappingOnNewGrant(int grantingId, int ownerClientId)1135     private void updateCiCamClientMappingOnNewGrant(int grantingId, int ownerClientId) {
1136         CiCamResource grantingCiCam = getCiCamResource(grantingId);
1137         ClientProfile ownerProfile = getClientProfile(ownerClientId);
1138         grantingCiCam.setOwner(ownerClientId);
1139         ownerProfile.useCiCam(grantingId);
1140     }
1141 
updateCasClientMappingOnRelease( @onNull CasResource releasingCas, int ownerClientId)1142     private void updateCasClientMappingOnRelease(
1143             @NonNull CasResource releasingCas, int ownerClientId) {
1144         ClientProfile ownerProfile = getClientProfile(ownerClientId);
1145         releasingCas.removeOwner(ownerClientId);
1146         ownerProfile.releaseCas();
1147     }
1148 
updateCiCamClientMappingOnRelease( @onNull CiCamResource releasingCiCam, int ownerClientId)1149     private void updateCiCamClientMappingOnRelease(
1150             @NonNull CiCamResource releasingCiCam, int ownerClientId) {
1151         ClientProfile ownerProfile = getClientProfile(ownerClientId);
1152         releasingCiCam.removeOwner(ownerClientId);
1153         ownerProfile.releaseCiCam();
1154     }
1155 
1156     /**
1157      * Get the owner client's priority.
1158      *
1159      * @param clientId the owner client id.
1160      * @return the priority of the owner client.
1161      */
getOwnerClientPriority(int clientId)1162     private int getOwnerClientPriority(int clientId) {
1163         return getClientProfile(clientId).getPriority();
1164     }
1165 
1166     @VisibleForTesting
1167     @Nullable
getFrontendResource(int frontendHandle)1168     protected FrontendResource getFrontendResource(int frontendHandle) {
1169         return mFrontendResources.get(frontendHandle);
1170     }
1171 
1172     @VisibleForTesting
getFrontendResources()1173     protected Map<Integer, FrontendResource> getFrontendResources() {
1174         return mFrontendResources;
1175     }
1176 
addFrontendResource(FrontendResource newFe)1177     private void addFrontendResource(FrontendResource newFe) {
1178         // Update the exclusive group member list in all the existing Frontend resource
1179         for (FrontendResource fe : getFrontendResources().values()) {
1180             if (fe.getExclusiveGroupId() == newFe.getExclusiveGroupId()) {
1181                 newFe.addExclusiveGroupMemberFeHandle(fe.getHandle());
1182                 newFe.addExclusiveGroupMemberFeHandles(fe.getExclusiveGroupMemberFeHandles());
1183                 for (int excGroupmemberFeHandle : fe.getExclusiveGroupMemberFeHandles()) {
1184                     getFrontendResource(excGroupmemberFeHandle)
1185                             .addExclusiveGroupMemberFeHandle(newFe.getHandle());
1186                 }
1187                 fe.addExclusiveGroupMemberFeHandle(newFe.getHandle());
1188                 break;
1189             }
1190         }
1191         // Update resource list and available id list
1192         mFrontendResources.put(newFe.getHandle(), newFe);
1193     }
1194 
removeFrontendResource(int removingHandle)1195     private void removeFrontendResource(int removingHandle) {
1196         FrontendResource fe = getFrontendResource(removingHandle);
1197         if (fe == null) {
1198             return;
1199         }
1200         if (fe.isInUse()) {
1201             ClientProfile ownerClient = getClientProfile(fe.getOwnerClientId());
1202             for (int shareOwnerId : ownerClient.getShareFeClientIds()) {
1203                 clearFrontendAndClientMapping(getClientProfile(shareOwnerId));
1204             }
1205             clearFrontendAndClientMapping(ownerClient);
1206         }
1207         for (int excGroupmemberFeHandle : fe.getExclusiveGroupMemberFeHandles()) {
1208             getFrontendResource(excGroupmemberFeHandle)
1209                     .removeExclusiveGroupMemberFeId(fe.getHandle());
1210         }
1211         mFrontendResources.remove(removingHandle);
1212     }
1213 
1214     @VisibleForTesting
1215     @Nullable
getLnbResource(int lnbHandle)1216     protected LnbResource getLnbResource(int lnbHandle) {
1217         return mLnbResources.get(lnbHandle);
1218     }
1219 
1220     @VisibleForTesting
getLnbResources()1221     protected Map<Integer, LnbResource> getLnbResources() {
1222         return mLnbResources;
1223     }
1224 
addLnbResource(LnbResource newLnb)1225     private void addLnbResource(LnbResource newLnb) {
1226         // Update resource list and available id list
1227         mLnbResources.put(newLnb.getHandle(), newLnb);
1228     }
1229 
removeLnbResource(int removingHandle)1230     private void removeLnbResource(int removingHandle) {
1231         LnbResource lnb = getLnbResource(removingHandle);
1232         if (lnb == null) {
1233             return;
1234         }
1235         if (lnb.isInUse()) {
1236             releaseLnbInternal(lnb);
1237         }
1238         mLnbResources.remove(removingHandle);
1239     }
1240 
1241     @VisibleForTesting
1242     @Nullable
getCasResource(int systemId)1243     protected CasResource getCasResource(int systemId) {
1244         return mCasResources.get(systemId);
1245     }
1246 
1247     @VisibleForTesting
1248     @Nullable
getCiCamResource(int ciCamId)1249     protected CiCamResource getCiCamResource(int ciCamId) {
1250         return mCiCamResources.get(ciCamId);
1251     }
1252 
1253     @VisibleForTesting
getCasResources()1254     protected Map<Integer, CasResource> getCasResources() {
1255         return mCasResources;
1256     }
1257 
1258     @VisibleForTesting
getCiCamResources()1259     protected Map<Integer, CiCamResource> getCiCamResources() {
1260         return mCiCamResources;
1261     }
1262 
addCasResource(CasResource newCas)1263     private void addCasResource(CasResource newCas) {
1264         // Update resource list and available id list
1265         mCasResources.put(newCas.getSystemId(), newCas);
1266     }
1267 
addCiCamResource(CiCamResource newCiCam)1268     private void addCiCamResource(CiCamResource newCiCam) {
1269         // Update resource list and available id list
1270         mCiCamResources.put(newCiCam.getCiCamId(), newCiCam);
1271     }
1272 
removeCasResource(int removingId)1273     private void removeCasResource(int removingId) {
1274         CasResource cas = getCasResource(removingId);
1275         if (cas == null) {
1276             return;
1277         }
1278         for (int ownerId : cas.getOwnerClientIds()) {
1279             getClientProfile(ownerId).releaseCas();
1280         }
1281         mCasResources.remove(removingId);
1282     }
1283 
removeCiCamResource(int removingId)1284     private void removeCiCamResource(int removingId) {
1285         CiCamResource ciCam = getCiCamResource(removingId);
1286         if (ciCam == null) {
1287             return;
1288         }
1289         for (int ownerId : ciCam.getOwnerClientIds()) {
1290             getClientProfile(ownerId).releaseCiCam();
1291         }
1292         mCiCamResources.remove(removingId);
1293     }
1294 
releaseLowerPriorityClientCasResources(int releasingCasResourceNum)1295     private void releaseLowerPriorityClientCasResources(int releasingCasResourceNum) {
1296         // TODO: Sort with a treemap
1297 
1298         // select the first num client to release
1299     }
1300 
1301     @VisibleForTesting
1302     @Nullable
getClientProfile(int clientId)1303     protected ClientProfile getClientProfile(int clientId) {
1304         return mClientProfiles.get(clientId);
1305     }
1306 
addClientProfile(int clientId, ClientProfile profile, IResourcesReclaimListener listener)1307     private void addClientProfile(int clientId, ClientProfile profile,
1308             IResourcesReclaimListener listener) {
1309         mClientProfiles.put(clientId, profile);
1310         addResourcesReclaimListener(clientId, listener);
1311     }
1312 
removeClientProfile(int clientId)1313     private void removeClientProfile(int clientId) {
1314         for (int shareOwnerId : getClientProfile(clientId).getShareFeClientIds()) {
1315             clearFrontendAndClientMapping(getClientProfile(shareOwnerId));
1316         }
1317         clearAllResourcesAndClientMapping(getClientProfile(clientId));
1318         mClientProfiles.remove(clientId);
1319         mListeners.remove(clientId);
1320     }
1321 
clearFrontendAndClientMapping(ClientProfile profile)1322     private void clearFrontendAndClientMapping(ClientProfile profile) {
1323         for (Integer feId : profile.getInUseFrontendHandles()) {
1324             FrontendResource fe = getFrontendResource(feId);
1325             if (fe.getOwnerClientId() == profile.getId()) {
1326                 fe.removeOwner();
1327                 continue;
1328             }
1329             getClientProfile(fe.getOwnerClientId()).stopSharingFrontend(profile.getId());
1330         }
1331         profile.releaseFrontend();
1332     }
1333 
clearAllResourcesAndClientMapping(ClientProfile profile)1334     private void clearAllResourcesAndClientMapping(ClientProfile profile) {
1335         // Clear Lnb
1336         for (Integer lnbHandle : profile.getInUseLnbHandles()) {
1337             getLnbResource(lnbHandle).removeOwner();
1338         }
1339         // Clear Cas
1340         if (profile.getInUseCasSystemId() != ClientProfile.INVALID_RESOURCE_ID) {
1341             getCasResource(profile.getInUseCasSystemId()).removeOwner(profile.getId());
1342         }
1343         // Clear CiCam
1344         if (profile.getInUseCiCamId() != ClientProfile.INVALID_RESOURCE_ID) {
1345             getCiCamResource(profile.getInUseCiCamId()).removeOwner(profile.getId());
1346         }
1347         // Clear Frontend
1348         clearFrontendAndClientMapping(profile);
1349         profile.reclaimAllResources();
1350     }
1351 
1352     @VisibleForTesting
checkClientExists(int clientId)1353     protected boolean checkClientExists(int clientId) {
1354         return mClientProfiles.keySet().contains(clientId);
1355     }
1356 
generateResourceHandle( @unerResourceManager.TunerResourceType int resourceType, int resourceId)1357     private int generateResourceHandle(
1358             @TunerResourceManager.TunerResourceType int resourceType, int resourceId) {
1359         return (resourceType & 0x000000ff) << 24
1360                 | (resourceId << 16)
1361                 | (mResourceRequestCount++ & 0xffff);
1362     }
1363 
1364     @VisibleForTesting
getResourceIdFromHandle(int resourceHandle)1365     protected int getResourceIdFromHandle(int resourceHandle) {
1366         if (resourceHandle == TunerResourceManager.INVALID_RESOURCE_HANDLE) {
1367             return resourceHandle;
1368         }
1369         return (resourceHandle & 0x00ff0000) >> 16;
1370     }
1371 
validateResourceHandle(int resourceType, int resourceHandle)1372     private boolean validateResourceHandle(int resourceType, int resourceHandle) {
1373         if (resourceHandle == TunerResourceManager.INVALID_RESOURCE_HANDLE
1374                 || ((resourceHandle & 0xff000000) >> 24) != resourceType) {
1375             return false;
1376         }
1377         return true;
1378     }
1379 
enforceTrmAccessPermission(String apiName)1380     private void enforceTrmAccessPermission(String apiName) {
1381         getContext().enforceCallingOrSelfPermission("android.permission.TUNER_RESOURCE_ACCESS",
1382                 TAG + ": " + apiName);
1383     }
1384 
enforceTunerAccessPermission(String apiName)1385     private void enforceTunerAccessPermission(String apiName) {
1386         getContext().enforceCallingPermission("android.permission.ACCESS_TV_TUNER",
1387                 TAG + ": " + apiName);
1388     }
1389 
enforceDescramblerAccessPermission(String apiName)1390     private void enforceDescramblerAccessPermission(String apiName) {
1391         getContext().enforceCallingPermission("android.permission.ACCESS_TV_DESCRAMBLER",
1392                 TAG + ": " + apiName);
1393     }
1394 }
1395