1 /*
2  * Copyright (C) 2014 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;
18 
19 import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
20 import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED_STANDBY;
21 
22 import android.annotation.Nullable;
23 import android.app.ActivityManager;
24 import android.content.BroadcastReceiver;
25 import android.content.ComponentName;
26 import android.content.ContentResolver;
27 import android.content.ContentUris;
28 import android.content.ContentValues;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.IntentFilter;
32 import android.content.ServiceConnection;
33 import android.content.pm.ActivityInfo;
34 import android.content.pm.PackageManager;
35 import android.content.pm.PackageManager.NameNotFoundException;
36 import android.content.pm.ResolveInfo;
37 import android.content.pm.ServiceInfo;
38 import android.graphics.Rect;
39 import android.hardware.hdmi.HdmiControlManager;
40 import android.hardware.hdmi.HdmiDeviceInfo;
41 import android.media.PlaybackParams;
42 import android.media.tv.DvbDeviceInfo;
43 import android.media.tv.ITvInputClient;
44 import android.media.tv.ITvInputHardware;
45 import android.media.tv.ITvInputHardwareCallback;
46 import android.media.tv.ITvInputManager;
47 import android.media.tv.ITvInputManagerCallback;
48 import android.media.tv.ITvInputService;
49 import android.media.tv.ITvInputServiceCallback;
50 import android.media.tv.ITvInputSession;
51 import android.media.tv.ITvInputSessionCallback;
52 import android.media.tv.TvContentRating;
53 import android.media.tv.TvContentRatingSystemInfo;
54 import android.media.tv.TvContract;
55 import android.media.tv.TvInputHardwareInfo;
56 import android.media.tv.TvInputInfo;
57 import android.media.tv.TvInputManager;
58 import android.media.tv.TvInputService;
59 import android.media.tv.TvStreamConfig;
60 import android.media.tv.TvTrackInfo;
61 import android.net.Uri;
62 import android.os.Binder;
63 import android.os.Bundle;
64 import android.os.Handler;
65 import android.os.IBinder;
66 import android.os.Looper;
67 import android.os.Message;
68 import android.os.ParcelFileDescriptor;
69 import android.os.Process;
70 import android.os.RemoteCallbackList;
71 import android.os.RemoteException;
72 import android.os.UserHandle;
73 import android.text.TextUtils;
74 import android.util.Slog;
75 import android.util.SparseArray;
76 import android.view.InputChannel;
77 import android.view.Surface;
78 
79 import com.android.internal.content.PackageMonitor;
80 import com.android.internal.os.SomeArgs;
81 import com.android.internal.util.DumpUtils;
82 import com.android.internal.util.IndentingPrintWriter;
83 import com.android.server.IoThread;
84 import com.android.server.SystemService;
85 
86 import java.io.File;
87 import java.io.FileDescriptor;
88 import java.io.FileNotFoundException;
89 import java.io.PrintWriter;
90 import java.util.ArrayList;
91 import java.util.Arrays;
92 import java.util.Collections;
93 import java.util.HashMap;
94 import java.util.HashSet;
95 import java.util.Iterator;
96 import java.util.List;
97 import java.util.Map;
98 import java.util.Set;
99 import java.util.regex.Matcher;
100 import java.util.regex.Pattern;
101 
102 /** This class provides a system service that manages television inputs. */
103 public final class TvInputManagerService extends SystemService {
104     private static final boolean DEBUG = false;
105     private static final String TAG = "TvInputManagerService";
106     private static final String DVB_DIRECTORY = "/dev/dvb";
107 
108     // There are two different formats of DVB frontend devices. One is /dev/dvb%d.frontend%d,
109     // another one is /dev/dvb/adapter%d/frontend%d. Followings are the patterns for selecting the
110     // DVB frontend devices from the list of files in the /dev and /dev/dvb/adapter%d directory.
111     private static final Pattern sFrontEndDevicePattern =
112             Pattern.compile("^dvb([0-9]+)\\.frontend([0-9]+)$");
113     private static final Pattern sAdapterDirPattern =
114             Pattern.compile("^adapter([0-9]+)$");
115     private static final Pattern sFrontEndInAdapterDirPattern =
116             Pattern.compile("^frontend([0-9]+)$");
117 
118     private final Context mContext;
119     private final TvInputHardwareManager mTvInputHardwareManager;
120 
121     // A global lock.
122     private final Object mLock = new Object();
123 
124     // ID of the current user.
125     private int mCurrentUserId = UserHandle.USER_SYSTEM;
126 
127     // A map from user id to UserState.
128     private final SparseArray<UserState> mUserStates = new SparseArray<>();
129 
130     private final WatchLogHandler mWatchLogHandler;
131 
TvInputManagerService(Context context)132     public TvInputManagerService(Context context) {
133         super(context);
134 
135         mContext = context;
136         mWatchLogHandler = new WatchLogHandler(mContext.getContentResolver(),
137                 IoThread.get().getLooper());
138         mTvInputHardwareManager = new TvInputHardwareManager(context, new HardwareListener());
139 
140         synchronized (mLock) {
141             getOrCreateUserStateLocked(mCurrentUserId);
142         }
143     }
144 
145     @Override
onStart()146     public void onStart() {
147         publishBinderService(Context.TV_INPUT_SERVICE, new BinderService());
148     }
149 
150     @Override
onBootPhase(int phase)151     public void onBootPhase(int phase) {
152         if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
153             registerBroadcastReceivers();
154         } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
155             synchronized (mLock) {
156                 buildTvInputListLocked(mCurrentUserId, null);
157                 buildTvContentRatingSystemListLocked(mCurrentUserId);
158             }
159         }
160         mTvInputHardwareManager.onBootPhase(phase);
161     }
162 
163     @Override
onUnlockUser(int userHandle)164     public void onUnlockUser(int userHandle) {
165         if (DEBUG) Slog.d(TAG, "onUnlockUser(userHandle=" + userHandle + ")");
166         synchronized (mLock) {
167             if (mCurrentUserId != userHandle) {
168                 return;
169             }
170             buildTvInputListLocked(mCurrentUserId, null);
171             buildTvContentRatingSystemListLocked(mCurrentUserId);
172         }
173     }
174 
registerBroadcastReceivers()175     private void registerBroadcastReceivers() {
176         PackageMonitor monitor = new PackageMonitor() {
177             private void buildTvInputList(String[] packages) {
178                 synchronized (mLock) {
179                     if (mCurrentUserId == getChangingUserId()) {
180                         buildTvInputListLocked(mCurrentUserId, packages);
181                         buildTvContentRatingSystemListLocked(mCurrentUserId);
182                     }
183                 }
184             }
185 
186             @Override
187             public void onPackageUpdateFinished(String packageName, int uid) {
188                 if (DEBUG) Slog.d(TAG, "onPackageUpdateFinished(packageName=" + packageName + ")");
189                 // This callback is invoked when the TV input is reinstalled.
190                 // In this case, isReplacing() always returns true.
191                 buildTvInputList(new String[] { packageName });
192             }
193 
194             @Override
195             public void onPackagesAvailable(String[] packages) {
196                 if (DEBUG) {
197                     Slog.d(TAG, "onPackagesAvailable(packages=" + Arrays.toString(packages) + ")");
198                 }
199                 // This callback is invoked when the media on which some packages exist become
200                 // available.
201                 if (isReplacing()) {
202                     buildTvInputList(packages);
203                 }
204             }
205 
206             @Override
207             public void onPackagesUnavailable(String[] packages) {
208                 // This callback is invoked when the media on which some packages exist become
209                 // unavailable.
210                 if (DEBUG)  {
211                     Slog.d(TAG, "onPackagesUnavailable(packages=" + Arrays.toString(packages)
212                             + ")");
213                 }
214                 if (isReplacing()) {
215                     buildTvInputList(packages);
216                 }
217             }
218 
219             @Override
220             public void onSomePackagesChanged() {
221                 // TODO: Use finer-grained methods(e.g. onPackageAdded, onPackageRemoved) to manage
222                 // the TV inputs.
223                 if (DEBUG) Slog.d(TAG, "onSomePackagesChanged()");
224                 if (isReplacing()) {
225                     if (DEBUG) Slog.d(TAG, "Skipped building TV input list due to replacing");
226                     // When the package is updated, buildTvInputListLocked is called in other
227                     // methods instead.
228                     return;
229                 }
230                 buildTvInputList(null);
231             }
232 
233             @Override
234             public boolean onPackageChanged(String packageName, int uid, String[] components) {
235                 // The input list needs to be updated in any cases, regardless of whether
236                 // it happened to the whole package or a specific component. Returning true so that
237                 // the update can be handled in {@link #onSomePackagesChanged}.
238                 return true;
239             }
240         };
241         monitor.register(mContext, null, UserHandle.ALL, true);
242 
243         IntentFilter intentFilter = new IntentFilter();
244         intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
245         intentFilter.addAction(Intent.ACTION_USER_REMOVED);
246         mContext.registerReceiverAsUser(new BroadcastReceiver() {
247             @Override
248             public void onReceive(Context context, Intent intent) {
249                 String action = intent.getAction();
250                 if (Intent.ACTION_USER_SWITCHED.equals(action)) {
251                     switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
252                 } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
253                     removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
254                 }
255             }
256         }, UserHandle.ALL, intentFilter, null, null);
257     }
258 
hasHardwarePermission(PackageManager pm, ComponentName component)259     private static boolean hasHardwarePermission(PackageManager pm, ComponentName component) {
260         return pm.checkPermission(android.Manifest.permission.TV_INPUT_HARDWARE,
261                 component.getPackageName()) == PackageManager.PERMISSION_GRANTED;
262     }
263 
buildTvInputListLocked(int userId, String[] updatedPackages)264     private void buildTvInputListLocked(int userId, String[] updatedPackages) {
265         UserState userState = getOrCreateUserStateLocked(userId);
266         userState.packageSet.clear();
267 
268         if (DEBUG) Slog.d(TAG, "buildTvInputList");
269         PackageManager pm = mContext.getPackageManager();
270         List<ResolveInfo> services = pm.queryIntentServicesAsUser(
271                 new Intent(TvInputService.SERVICE_INTERFACE),
272                 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
273                 userId);
274         List<TvInputInfo> inputList = new ArrayList<>();
275         for (ResolveInfo ri : services) {
276             ServiceInfo si = ri.serviceInfo;
277             if (!android.Manifest.permission.BIND_TV_INPUT.equals(si.permission)) {
278                 Slog.w(TAG, "Skipping TV input " + si.name + ": it does not require the permission "
279                         + android.Manifest.permission.BIND_TV_INPUT);
280                 continue;
281             }
282 
283             ComponentName component = new ComponentName(si.packageName, si.name);
284             if (hasHardwarePermission(pm, component)) {
285                 ServiceState serviceState = userState.serviceStateMap.get(component);
286                 if (serviceState == null) {
287                     // New hardware input found. Create a new ServiceState and connect to the
288                     // service to populate the hardware list.
289                     serviceState = new ServiceState(component, userId);
290                     userState.serviceStateMap.put(component, serviceState);
291                     updateServiceConnectionLocked(component, userId);
292                 } else {
293                     inputList.addAll(serviceState.hardwareInputMap.values());
294                 }
295             } else {
296                 try {
297                     TvInputInfo info = new TvInputInfo.Builder(mContext, ri).build();
298                     inputList.add(info);
299                 } catch (Exception e) {
300                     Slog.e(TAG, "failed to load TV input " + si.name, e);
301                     continue;
302                 }
303             }
304             userState.packageSet.add(si.packageName);
305         }
306 
307         Map<String, TvInputState> inputMap = new HashMap<>();
308         for (TvInputInfo info : inputList) {
309             if (DEBUG) {
310                 Slog.d(TAG, "add " + info.getId());
311             }
312             TvInputState inputState = userState.inputMap.get(info.getId());
313             if (inputState == null) {
314                 inputState = new TvInputState();
315             }
316             inputState.info = info;
317             inputMap.put(info.getId(), inputState);
318         }
319 
320         for (String inputId : inputMap.keySet()) {
321             if (!userState.inputMap.containsKey(inputId)) {
322                 notifyInputAddedLocked(userState, inputId);
323             } else if (updatedPackages != null) {
324                 // Notify the package updates
325                 ComponentName component = inputMap.get(inputId).info.getComponent();
326                 for (String updatedPackage : updatedPackages) {
327                     if (component.getPackageName().equals(updatedPackage)) {
328                         updateServiceConnectionLocked(component, userId);
329                         notifyInputUpdatedLocked(userState, inputId);
330                         break;
331                     }
332                 }
333             }
334         }
335 
336         for (String inputId : userState.inputMap.keySet()) {
337             if (!inputMap.containsKey(inputId)) {
338                 TvInputInfo info = userState.inputMap.get(inputId).info;
339                 ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
340                 if (serviceState != null) {
341                     abortPendingCreateSessionRequestsLocked(serviceState, inputId, userId);
342                 }
343                 notifyInputRemovedLocked(userState, inputId);
344             }
345         }
346 
347         userState.inputMap.clear();
348         userState.inputMap = inputMap;
349     }
350 
buildTvContentRatingSystemListLocked(int userId)351     private void buildTvContentRatingSystemListLocked(int userId) {
352         UserState userState = getOrCreateUserStateLocked(userId);
353         userState.contentRatingSystemList.clear();
354 
355         final PackageManager pm = mContext.getPackageManager();
356         Intent intent = new Intent(TvInputManager.ACTION_QUERY_CONTENT_RATING_SYSTEMS);
357         for (ResolveInfo resolveInfo :
358                 pm.queryBroadcastReceivers(intent, PackageManager.GET_META_DATA)) {
359             ActivityInfo receiver = resolveInfo.activityInfo;
360             Bundle metaData = receiver.metaData;
361             if (metaData == null) {
362                 continue;
363             }
364 
365             int xmlResId = metaData.getInt(TvInputManager.META_DATA_CONTENT_RATING_SYSTEMS);
366             if (xmlResId == 0) {
367                 Slog.w(TAG, "Missing meta-data '"
368                         + TvInputManager.META_DATA_CONTENT_RATING_SYSTEMS + "' on receiver "
369                         + receiver.packageName + "/" + receiver.name);
370                 continue;
371             }
372             userState.contentRatingSystemList.add(
373                     TvContentRatingSystemInfo.createTvContentRatingSystemInfo(xmlResId,
374                             receiver.applicationInfo));
375         }
376     }
377 
switchUser(int userId)378     private void switchUser(int userId) {
379         synchronized (mLock) {
380             if (mCurrentUserId == userId) {
381                 return;
382             }
383             UserState userState = mUserStates.get(mCurrentUserId);
384             List<SessionState> sessionStatesToRelease = new ArrayList<>();
385             for (SessionState sessionState : userState.sessionStateMap.values()) {
386                 if (sessionState.session != null && !sessionState.isRecordingSession) {
387                     sessionStatesToRelease.add(sessionState);
388                 }
389             }
390             for (SessionState sessionState : sessionStatesToRelease) {
391                 try {
392                     sessionState.session.release();
393                 } catch (RemoteException e) {
394                     Slog.e(TAG, "error in release", e);
395                 }
396                 clearSessionAndNotifyClientLocked(sessionState);
397             }
398 
399             for (Iterator<ComponentName> it = userState.serviceStateMap.keySet().iterator();
400                  it.hasNext(); ) {
401                 ComponentName component = it.next();
402                 ServiceState serviceState = userState.serviceStateMap.get(component);
403                 if (serviceState != null && serviceState.sessionTokens.isEmpty()) {
404                     if (serviceState.callback != null) {
405                         try {
406                             serviceState.service.unregisterCallback(serviceState.callback);
407                         } catch (RemoteException e) {
408                             Slog.e(TAG, "error in unregisterCallback", e);
409                         }
410                     }
411                     mContext.unbindService(serviceState.connection);
412                     it.remove();
413                 }
414             }
415 
416             mCurrentUserId = userId;
417             getOrCreateUserStateLocked(userId);
418             buildTvInputListLocked(userId, null);
419             buildTvContentRatingSystemListLocked(userId);
420             mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_SWITCH_CONTENT_RESOLVER,
421                     getContentResolverForUser(userId)).sendToTarget();
422         }
423     }
424 
clearSessionAndNotifyClientLocked(SessionState state)425     private void clearSessionAndNotifyClientLocked(SessionState state) {
426         if (state.client != null) {
427             try {
428                 state.client.onSessionReleased(state.seq);
429             } catch(RemoteException e) {
430                 Slog.e(TAG, "error in onSessionReleased", e);
431             }
432         }
433         // If there are any other sessions based on this session, they should be released.
434         UserState userState = getOrCreateUserStateLocked(state.userId);
435         for (SessionState sessionState : userState.sessionStateMap.values()) {
436             if (state.sessionToken == sessionState.hardwareSessionToken) {
437                 releaseSessionLocked(sessionState.sessionToken, Process.SYSTEM_UID, state.userId);
438                 try {
439                     sessionState.client.onSessionReleased(sessionState.seq);
440                 } catch (RemoteException e) {
441                     Slog.e(TAG, "error in onSessionReleased", e);
442                 }
443             }
444         }
445         removeSessionStateLocked(state.sessionToken, state.userId);
446     }
447 
removeUser(int userId)448     private void removeUser(int userId) {
449         synchronized (mLock) {
450             UserState userState = mUserStates.get(userId);
451             if (userState == null) {
452                 return;
453             }
454             // Release all created sessions.
455             for (SessionState state : userState.sessionStateMap.values()) {
456                 if (state.session != null) {
457                     try {
458                         state.session.release();
459                     } catch (RemoteException e) {
460                         Slog.e(TAG, "error in release", e);
461                     }
462                 }
463             }
464             userState.sessionStateMap.clear();
465 
466             // Unregister all callbacks and unbind all services.
467             for (ServiceState serviceState : userState.serviceStateMap.values()) {
468                 if (serviceState.service != null) {
469                     if (serviceState.callback != null) {
470                         try {
471                             serviceState.service.unregisterCallback(serviceState.callback);
472                         } catch (RemoteException e) {
473                             Slog.e(TAG, "error in unregisterCallback", e);
474                         }
475                     }
476                     mContext.unbindService(serviceState.connection);
477                 }
478             }
479             userState.serviceStateMap.clear();
480 
481             // Clear everything else.
482             userState.inputMap.clear();
483             userState.packageSet.clear();
484             userState.contentRatingSystemList.clear();
485             userState.clientStateMap.clear();
486             userState.mCallbacks.kill();
487             userState.mainSessionToken = null;
488 
489             mUserStates.remove(userId);
490         }
491     }
492 
getContentResolverForUser(int userId)493     private ContentResolver getContentResolverForUser(int userId) {
494         UserHandle user = new UserHandle(userId);
495         Context context;
496         try {
497             context = mContext.createPackageContextAsUser("android", 0, user);
498         } catch (NameNotFoundException e) {
499             Slog.e(TAG, "failed to create package context as user " + user);
500             context = mContext;
501         }
502         return context.getContentResolver();
503     }
504 
getOrCreateUserStateLocked(int userId)505     private UserState getOrCreateUserStateLocked(int userId) {
506         UserState userState = mUserStates.get(userId);
507         if (userState == null) {
508             userState = new UserState(mContext, userId);
509             mUserStates.put(userId, userState);
510         }
511         return userState;
512     }
513 
getServiceStateLocked(ComponentName component, int userId)514     private ServiceState getServiceStateLocked(ComponentName component, int userId) {
515         UserState userState = getOrCreateUserStateLocked(userId);
516         ServiceState serviceState = userState.serviceStateMap.get(component);
517         if (serviceState == null) {
518             throw new IllegalStateException("Service state not found for " + component + " (userId="
519                     + userId + ")");
520         }
521         return serviceState;
522     }
523 
getSessionStateLocked(IBinder sessionToken, int callingUid, int userId)524     private SessionState getSessionStateLocked(IBinder sessionToken, int callingUid, int userId) {
525         UserState userState = getOrCreateUserStateLocked(userId);
526         SessionState sessionState = userState.sessionStateMap.get(sessionToken);
527         if (sessionState == null) {
528             throw new SessionNotFoundException("Session state not found for token " + sessionToken);
529         }
530         // Only the application that requested this session or the system can access it.
531         if (callingUid != Process.SYSTEM_UID && callingUid != sessionState.callingUid) {
532             throw new SecurityException("Illegal access to the session with token " + sessionToken
533                     + " from uid " + callingUid);
534         }
535         return sessionState;
536     }
537 
getSessionLocked(IBinder sessionToken, int callingUid, int userId)538     private ITvInputSession getSessionLocked(IBinder sessionToken, int callingUid, int userId) {
539         return getSessionLocked(getSessionStateLocked(sessionToken, callingUid, userId));
540     }
541 
getSessionLocked(SessionState sessionState)542     private ITvInputSession getSessionLocked(SessionState sessionState) {
543         ITvInputSession session = sessionState.session;
544         if (session == null) {
545             throw new IllegalStateException("Session not yet created for token "
546                     + sessionState.sessionToken);
547         }
548         return session;
549     }
550 
resolveCallingUserId(int callingPid, int callingUid, int requestedUserId, String methodName)551     private int resolveCallingUserId(int callingPid, int callingUid, int requestedUserId,
552             String methodName) {
553         return ActivityManager.handleIncomingUser(callingPid, callingUid, requestedUserId, false,
554                 false, methodName, null);
555     }
556 
updateServiceConnectionLocked(ComponentName component, int userId)557     private void updateServiceConnectionLocked(ComponentName component, int userId) {
558         UserState userState = getOrCreateUserStateLocked(userId);
559         ServiceState serviceState = userState.serviceStateMap.get(component);
560         if (serviceState == null) {
561             return;
562         }
563         if (serviceState.reconnecting) {
564             if (!serviceState.sessionTokens.isEmpty()) {
565                 // wait until all the sessions are removed.
566                 return;
567             }
568             serviceState.reconnecting = false;
569         }
570 
571         boolean shouldBind;
572         if (userId == mCurrentUserId) {
573             shouldBind = !serviceState.sessionTokens.isEmpty() || serviceState.isHardware;
574         } else {
575             // For a non-current user,
576             // if sessionTokens is not empty, it contains recording sessions only
577             // because other sessions must have been removed while switching user
578             // and non-recording sessions are not created by createSession().
579             shouldBind = !serviceState.sessionTokens.isEmpty();
580         }
581 
582         if (serviceState.service == null && shouldBind) {
583             // This means that the service is not yet connected but its state indicates that we
584             // have pending requests. Then, connect the service.
585             if (serviceState.bound) {
586                 // We have already bound to the service so we don't try to bind again until after we
587                 // unbind later on.
588                 return;
589             }
590             if (DEBUG) {
591                 Slog.d(TAG, "bindServiceAsUser(service=" + component + ", userId=" + userId + ")");
592             }
593 
594             Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(component);
595             serviceState.bound = mContext.bindServiceAsUser(
596                     i, serviceState.connection,
597                     Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
598                     new UserHandle(userId));
599         } else if (serviceState.service != null && !shouldBind) {
600             // This means that the service is already connected but its state indicates that we have
601             // nothing to do with it. Then, disconnect the service.
602             if (DEBUG) {
603                 Slog.d(TAG, "unbindService(service=" + component + ")");
604             }
605             mContext.unbindService(serviceState.connection);
606             userState.serviceStateMap.remove(component);
607         }
608     }
609 
abortPendingCreateSessionRequestsLocked(ServiceState serviceState, String inputId, int userId)610     private void abortPendingCreateSessionRequestsLocked(ServiceState serviceState,
611             String inputId, int userId) {
612         // Let clients know the create session requests are failed.
613         UserState userState = getOrCreateUserStateLocked(userId);
614         List<SessionState> sessionsToAbort = new ArrayList<>();
615         for (IBinder sessionToken : serviceState.sessionTokens) {
616             SessionState sessionState = userState.sessionStateMap.get(sessionToken);
617             if (sessionState.session == null && (inputId == null
618                     || sessionState.inputId.equals(inputId))) {
619                 sessionsToAbort.add(sessionState);
620             }
621         }
622         for (SessionState sessionState : sessionsToAbort) {
623             removeSessionStateLocked(sessionState.sessionToken, sessionState.userId);
624             sendSessionTokenToClientLocked(sessionState.client,
625                     sessionState.inputId, null, null, sessionState.seq);
626         }
627         updateServiceConnectionLocked(serviceState.component, userId);
628     }
629 
createSessionInternalLocked(ITvInputService service, IBinder sessionToken, int userId)630     private boolean createSessionInternalLocked(ITvInputService service, IBinder sessionToken,
631             int userId) {
632         UserState userState = getOrCreateUserStateLocked(userId);
633         SessionState sessionState = userState.sessionStateMap.get(sessionToken);
634         if (DEBUG) {
635             Slog.d(TAG, "createSessionInternalLocked(inputId=" + sessionState.inputId + ")");
636         }
637         InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString());
638 
639         // Set up a callback to send the session token.
640         ITvInputSessionCallback callback = new SessionCallback(sessionState, channels);
641 
642         boolean created = true;
643         // Create a session. When failed, send a null token immediately.
644         try {
645             if (sessionState.isRecordingSession) {
646                 service.createRecordingSession(callback, sessionState.inputId);
647             } else {
648                 service.createSession(channels[1], callback, sessionState.inputId);
649             }
650         } catch (RemoteException e) {
651             Slog.e(TAG, "error in createSession", e);
652             sendSessionTokenToClientLocked(sessionState.client, sessionState.inputId, null,
653                     null, sessionState.seq);
654             created = false;
655         }
656         channels[1].dispose();
657         return created;
658     }
659 
sendSessionTokenToClientLocked(ITvInputClient client, String inputId, IBinder sessionToken, InputChannel channel, int seq)660     private void sendSessionTokenToClientLocked(ITvInputClient client, String inputId,
661             IBinder sessionToken, InputChannel channel, int seq) {
662         try {
663             client.onSessionCreated(inputId, sessionToken, channel, seq);
664         } catch (RemoteException e) {
665             Slog.e(TAG, "error in onSessionCreated", e);
666         }
667     }
668 
releaseSessionLocked(IBinder sessionToken, int callingUid, int userId)669     private void releaseSessionLocked(IBinder sessionToken, int callingUid, int userId) {
670         SessionState sessionState = null;
671         try {
672             sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
673             if (sessionState.session != null) {
674                 UserState userState = getOrCreateUserStateLocked(userId);
675                 if (sessionToken == userState.mainSessionToken) {
676                     setMainLocked(sessionToken, false, callingUid, userId);
677                 }
678                 sessionState.session.asBinder().unlinkToDeath(sessionState, 0);
679                 sessionState.session.release();
680             }
681         } catch (RemoteException | SessionNotFoundException e) {
682             Slog.e(TAG, "error in releaseSession", e);
683         } finally {
684             if (sessionState != null) {
685                 sessionState.session = null;
686             }
687         }
688         removeSessionStateLocked(sessionToken, userId);
689     }
690 
removeSessionStateLocked(IBinder sessionToken, int userId)691     private void removeSessionStateLocked(IBinder sessionToken, int userId) {
692         UserState userState = getOrCreateUserStateLocked(userId);
693         if (sessionToken == userState.mainSessionToken) {
694             if (DEBUG) {
695                 Slog.d(TAG, "mainSessionToken=null");
696             }
697             userState.mainSessionToken = null;
698         }
699 
700         // Remove the session state from the global session state map of the current user.
701         SessionState sessionState = userState.sessionStateMap.remove(sessionToken);
702 
703         if (sessionState == null) {
704             return;
705         }
706 
707         // Also remove the session token from the session token list of the current client and
708         // service.
709         ClientState clientState = userState.clientStateMap.get(sessionState.client.asBinder());
710         if (clientState != null) {
711             clientState.sessionTokens.remove(sessionToken);
712             if (clientState.isEmpty()) {
713                 userState.clientStateMap.remove(sessionState.client.asBinder());
714                 sessionState.client.asBinder().unlinkToDeath(clientState, 0);
715             }
716         }
717 
718         ServiceState serviceState = userState.serviceStateMap.get(sessionState.componentName);
719         if (serviceState != null) {
720             serviceState.sessionTokens.remove(sessionToken);
721         }
722         updateServiceConnectionLocked(sessionState.componentName, userId);
723 
724         // Log the end of watch.
725         SomeArgs args = SomeArgs.obtain();
726         args.arg1 = sessionToken;
727         args.arg2 = System.currentTimeMillis();
728         mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_LOG_WATCH_END, args).sendToTarget();
729     }
730 
setMainLocked(IBinder sessionToken, boolean isMain, int callingUid, int userId)731     private void setMainLocked(IBinder sessionToken, boolean isMain, int callingUid, int userId) {
732         try {
733             SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
734             if (sessionState.hardwareSessionToken != null) {
735                 sessionState = getSessionStateLocked(sessionState.hardwareSessionToken,
736                         Process.SYSTEM_UID, userId);
737             }
738             ServiceState serviceState = getServiceStateLocked(sessionState.componentName, userId);
739             if (!serviceState.isHardware) {
740                 return;
741             }
742             ITvInputSession session = getSessionLocked(sessionState);
743             session.setMain(isMain);
744         } catch (RemoteException | SessionNotFoundException e) {
745             Slog.e(TAG, "error in setMain", e);
746         }
747     }
748 
notifyInputAddedLocked(UserState userState, String inputId)749     private void notifyInputAddedLocked(UserState userState, String inputId) {
750         if (DEBUG) {
751             Slog.d(TAG, "notifyInputAddedLocked(inputId=" + inputId + ")");
752         }
753         int n = userState.mCallbacks.beginBroadcast();
754         for (int i = 0; i < n; ++i) {
755             try {
756                 userState.mCallbacks.getBroadcastItem(i).onInputAdded(inputId);
757             } catch (RemoteException e) {
758                 Slog.e(TAG, "failed to report added input to callback", e);
759             }
760         }
761         userState.mCallbacks.finishBroadcast();
762     }
763 
notifyInputRemovedLocked(UserState userState, String inputId)764     private void notifyInputRemovedLocked(UserState userState, String inputId) {
765         if (DEBUG) {
766             Slog.d(TAG, "notifyInputRemovedLocked(inputId=" + inputId + ")");
767         }
768         int n = userState.mCallbacks.beginBroadcast();
769         for (int i = 0; i < n; ++i) {
770             try {
771                 userState.mCallbacks.getBroadcastItem(i).onInputRemoved(inputId);
772             } catch (RemoteException e) {
773                 Slog.e(TAG, "failed to report removed input to callback", e);
774             }
775         }
776         userState.mCallbacks.finishBroadcast();
777     }
778 
notifyInputUpdatedLocked(UserState userState, String inputId)779     private void notifyInputUpdatedLocked(UserState userState, String inputId) {
780         if (DEBUG) {
781             Slog.d(TAG, "notifyInputUpdatedLocked(inputId=" + inputId + ")");
782         }
783         int n = userState.mCallbacks.beginBroadcast();
784         for (int i = 0; i < n; ++i) {
785             try {
786                 userState.mCallbacks.getBroadcastItem(i).onInputUpdated(inputId);
787             } catch (RemoteException e) {
788                 Slog.e(TAG, "failed to report updated input to callback", e);
789             }
790         }
791         userState.mCallbacks.finishBroadcast();
792     }
793 
notifyInputStateChangedLocked(UserState userState, String inputId, int state, ITvInputManagerCallback targetCallback)794     private void notifyInputStateChangedLocked(UserState userState, String inputId,
795             int state, ITvInputManagerCallback targetCallback) {
796         if (DEBUG) {
797             Slog.d(TAG, "notifyInputStateChangedLocked(inputId=" + inputId
798                     + ", state=" + state + ")");
799         }
800         if (targetCallback == null) {
801             int n = userState.mCallbacks.beginBroadcast();
802             for (int i = 0; i < n; ++i) {
803                 try {
804                     userState.mCallbacks.getBroadcastItem(i).onInputStateChanged(inputId, state);
805                 } catch (RemoteException e) {
806                     Slog.e(TAG, "failed to report state change to callback", e);
807                 }
808             }
809             userState.mCallbacks.finishBroadcast();
810         } else {
811             try {
812                 targetCallback.onInputStateChanged(inputId, state);
813             } catch (RemoteException e) {
814                 Slog.e(TAG, "failed to report state change to callback", e);
815             }
816         }
817     }
818 
updateTvInputInfoLocked(UserState userState, TvInputInfo inputInfo)819     private void updateTvInputInfoLocked(UserState userState, TvInputInfo inputInfo) {
820         if (DEBUG) {
821             Slog.d(TAG, "updateTvInputInfoLocked(inputInfo=" + inputInfo + ")");
822         }
823         String inputId = inputInfo.getId();
824         TvInputState inputState = userState.inputMap.get(inputId);
825         if (inputState == null) {
826             Slog.e(TAG, "failed to set input info - unknown input id " + inputId);
827             return;
828         }
829         inputState.info = inputInfo;
830 
831         int n = userState.mCallbacks.beginBroadcast();
832         for (int i = 0; i < n; ++i) {
833             try {
834                 userState.mCallbacks.getBroadcastItem(i).onTvInputInfoUpdated(inputInfo);
835             } catch (RemoteException e) {
836                 Slog.e(TAG, "failed to report updated input info to callback", e);
837             }
838         }
839         userState.mCallbacks.finishBroadcast();
840     }
841 
setStateLocked(String inputId, int state, int userId)842     private void setStateLocked(String inputId, int state, int userId) {
843         UserState userState = getOrCreateUserStateLocked(userId);
844         TvInputState inputState = userState.inputMap.get(inputId);
845         if (inputState == null) {
846             Slog.e(TAG, "failed to setStateLocked - unknown input id " + inputId);
847             return;
848         }
849         ServiceState serviceState = userState.serviceStateMap.get(inputState.info.getComponent());
850         int oldState = inputState.state;
851         inputState.state = state;
852         if (serviceState != null && serviceState.service == null
853                 && (!serviceState.sessionTokens.isEmpty() || serviceState.isHardware)) {
854             // We don't notify state change while reconnecting. It should remain disconnected.
855             return;
856         }
857         if (oldState != state) {
858             notifyInputStateChangedLocked(userState, inputId, state, null);
859         }
860     }
861 
862     private final class BinderService extends ITvInputManager.Stub {
863         @Override
getTvInputList(int userId)864         public List<TvInputInfo> getTvInputList(int userId) {
865             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
866                     Binder.getCallingUid(), userId, "getTvInputList");
867             final long identity = Binder.clearCallingIdentity();
868             try {
869                 synchronized (mLock) {
870                     UserState userState = getOrCreateUserStateLocked(resolvedUserId);
871                     List<TvInputInfo> inputList = new ArrayList<>();
872                     for (TvInputState state : userState.inputMap.values()) {
873                         inputList.add(state.info);
874                     }
875                     return inputList;
876                 }
877             } finally {
878                 Binder.restoreCallingIdentity(identity);
879             }
880         }
881 
882         @Override
getTvInputInfo(String inputId, int userId)883         public TvInputInfo getTvInputInfo(String inputId, int userId) {
884             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
885                     Binder.getCallingUid(), userId, "getTvInputInfo");
886             final long identity = Binder.clearCallingIdentity();
887             try {
888                 synchronized (mLock) {
889                     UserState userState = getOrCreateUserStateLocked(resolvedUserId);
890                     TvInputState state = userState.inputMap.get(inputId);
891                     return state == null ? null : state.info;
892                 }
893             } finally {
894                 Binder.restoreCallingIdentity(identity);
895             }
896         }
897 
updateTvInputInfo(TvInputInfo inputInfo, int userId)898         public void updateTvInputInfo(TvInputInfo inputInfo, int userId) {
899             String inputInfoPackageName = inputInfo.getServiceInfo().packageName;
900             String callingPackageName = getCallingPackageName();
901             if (!TextUtils.equals(inputInfoPackageName, callingPackageName)
902                     && mContext.checkCallingPermission(
903                             android.Manifest.permission.WRITE_SECURE_SETTINGS)
904                                     != PackageManager.PERMISSION_GRANTED) {
905                 // Only the app owning the input and system settings are allowed to update info.
906                 throw new IllegalArgumentException("calling package " + callingPackageName
907                         + " is not allowed to change TvInputInfo for " + inputInfoPackageName);
908             }
909 
910             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
911                     Binder.getCallingUid(), userId, "updateTvInputInfo");
912             final long identity = Binder.clearCallingIdentity();
913             try {
914                 synchronized (mLock) {
915                     UserState userState = getOrCreateUserStateLocked(resolvedUserId);
916                     updateTvInputInfoLocked(userState, inputInfo);
917                 }
918             } finally {
919                 Binder.restoreCallingIdentity(identity);
920             }
921         }
922 
getCallingPackageName()923         private String getCallingPackageName() {
924             final String[] packages = mContext.getPackageManager().getPackagesForUid(
925                     Binder.getCallingUid());
926             if (packages != null && packages.length > 0) {
927                 return packages[0];
928             }
929             return "unknown";
930         }
931 
932         @Override
getTvInputState(String inputId, int userId)933         public int getTvInputState(String inputId, int userId) {
934             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
935                     Binder.getCallingUid(), userId, "getTvInputState");
936             final long identity = Binder.clearCallingIdentity();
937             try {
938                 synchronized (mLock) {
939                     UserState userState = getOrCreateUserStateLocked(resolvedUserId);
940                     TvInputState state = userState.inputMap.get(inputId);
941                     return state == null ? INPUT_STATE_CONNECTED : state.state;
942                 }
943             } finally {
944                 Binder.restoreCallingIdentity(identity);
945             }
946         }
947 
948         @Override
getTvContentRatingSystemList(int userId)949         public List<TvContentRatingSystemInfo> getTvContentRatingSystemList(int userId) {
950             if (mContext.checkCallingPermission(
951                     android.Manifest.permission.READ_CONTENT_RATING_SYSTEMS)
952                     != PackageManager.PERMISSION_GRANTED) {
953                 throw new SecurityException(
954                         "The caller does not have permission to read content rating systems");
955             }
956             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
957                     Binder.getCallingUid(), userId, "getTvContentRatingSystemList");
958             final long identity = Binder.clearCallingIdentity();
959             try {
960                 synchronized (mLock) {
961                     UserState userState = getOrCreateUserStateLocked(resolvedUserId);
962                     return userState.contentRatingSystemList;
963                 }
964             } finally {
965                 Binder.restoreCallingIdentity(identity);
966             }
967         }
968 
969         @Override
sendTvInputNotifyIntent(Intent intent, int userId)970         public void sendTvInputNotifyIntent(Intent intent, int userId) {
971             if (mContext.checkCallingPermission(android.Manifest.permission.NOTIFY_TV_INPUTS)
972                     != PackageManager.PERMISSION_GRANTED) {
973                 throw new SecurityException("The caller: " + getCallingPackageName()
974                         + " doesn't have permission: "
975                         + android.Manifest.permission.NOTIFY_TV_INPUTS);
976             }
977             if (TextUtils.isEmpty(intent.getPackage())) {
978                 throw new IllegalArgumentException("Must specify package name to notify.");
979             }
980             switch (intent.getAction()) {
981                 case TvContract.ACTION_PREVIEW_PROGRAM_BROWSABLE_DISABLED:
982                     if (intent.getLongExtra(TvContract.EXTRA_PREVIEW_PROGRAM_ID, -1) < 0) {
983                         throw new IllegalArgumentException("Invalid preview program ID.");
984                     }
985                     break;
986                 case TvContract.ACTION_WATCH_NEXT_PROGRAM_BROWSABLE_DISABLED:
987                     if (intent.getLongExtra(TvContract.EXTRA_WATCH_NEXT_PROGRAM_ID, -1) < 0) {
988                         throw new IllegalArgumentException("Invalid watch next program ID.");
989                     }
990                     break;
991                 case TvContract.ACTION_PREVIEW_PROGRAM_ADDED_TO_WATCH_NEXT:
992                     if (intent.getLongExtra(TvContract.EXTRA_PREVIEW_PROGRAM_ID, -1) < 0) {
993                         throw new IllegalArgumentException("Invalid preview program ID.");
994                     }
995                     if (intent.getLongExtra(TvContract.EXTRA_WATCH_NEXT_PROGRAM_ID, -1) < 0) {
996                         throw new IllegalArgumentException("Invalid watch next program ID.");
997                     }
998                     break;
999                 default:
1000                     throw new IllegalArgumentException("Invalid TV input notifying action: "
1001                             + intent.getAction());
1002             }
1003             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1004                     Binder.getCallingUid(), userId, "sendTvInputNotifyIntent");
1005             final long identity = Binder.clearCallingIdentity();
1006             try {
1007                 getContext().sendBroadcastAsUser(intent, new UserHandle(resolvedUserId));
1008             } finally {
1009                 Binder.restoreCallingIdentity(identity);
1010             }
1011         }
1012 
1013         @Override
registerCallback(final ITvInputManagerCallback callback, int userId)1014         public void registerCallback(final ITvInputManagerCallback callback, int userId) {
1015             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1016                     Binder.getCallingUid(), userId, "registerCallback");
1017             final long identity = Binder.clearCallingIdentity();
1018             try {
1019                 synchronized (mLock) {
1020                     final UserState userState = getOrCreateUserStateLocked(resolvedUserId);
1021                     if (!userState.mCallbacks.register(callback)) {
1022                         Slog.e(TAG, "client process has already died");
1023                     }
1024                 }
1025             } finally {
1026                 Binder.restoreCallingIdentity(identity);
1027             }
1028         }
1029 
1030         @Override
unregisterCallback(ITvInputManagerCallback callback, int userId)1031         public void unregisterCallback(ITvInputManagerCallback callback, int userId) {
1032             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1033                     Binder.getCallingUid(), userId, "unregisterCallback");
1034             final long identity = Binder.clearCallingIdentity();
1035             try {
1036                 synchronized (mLock) {
1037                     UserState userState = getOrCreateUserStateLocked(resolvedUserId);
1038                     userState.mCallbacks.unregister(callback);
1039                 }
1040             } finally {
1041                 Binder.restoreCallingIdentity(identity);
1042             }
1043         }
1044 
1045         @Override
isParentalControlsEnabled(int userId)1046         public boolean isParentalControlsEnabled(int userId) {
1047             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1048                     Binder.getCallingUid(), userId, "isParentalControlsEnabled");
1049             final long identity = Binder.clearCallingIdentity();
1050             try {
1051                 synchronized (mLock) {
1052                     UserState userState = getOrCreateUserStateLocked(resolvedUserId);
1053                     return userState.persistentDataStore.isParentalControlsEnabled();
1054                 }
1055             } finally {
1056                 Binder.restoreCallingIdentity(identity);
1057             }
1058         }
1059 
1060         @Override
setParentalControlsEnabled(boolean enabled, int userId)1061         public void setParentalControlsEnabled(boolean enabled, int userId) {
1062             ensureParentalControlsPermission();
1063             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1064                     Binder.getCallingUid(), userId, "setParentalControlsEnabled");
1065             final long identity = Binder.clearCallingIdentity();
1066             try {
1067                 synchronized (mLock) {
1068                     UserState userState = getOrCreateUserStateLocked(resolvedUserId);
1069                     userState.persistentDataStore.setParentalControlsEnabled(enabled);
1070                 }
1071             } finally {
1072                 Binder.restoreCallingIdentity(identity);
1073             }
1074         }
1075 
1076         @Override
isRatingBlocked(String rating, int userId)1077         public boolean isRatingBlocked(String rating, int userId) {
1078             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1079                     Binder.getCallingUid(), userId, "isRatingBlocked");
1080             final long identity = Binder.clearCallingIdentity();
1081             try {
1082                 synchronized (mLock) {
1083                     UserState userState = getOrCreateUserStateLocked(resolvedUserId);
1084                     return userState.persistentDataStore.isRatingBlocked(
1085                             TvContentRating.unflattenFromString(rating));
1086                 }
1087             } finally {
1088                 Binder.restoreCallingIdentity(identity);
1089             }
1090         }
1091 
1092         @Override
getBlockedRatings(int userId)1093         public List<String> getBlockedRatings(int userId) {
1094             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1095                     Binder.getCallingUid(), userId, "getBlockedRatings");
1096             final long identity = Binder.clearCallingIdentity();
1097             try {
1098                 synchronized (mLock) {
1099                     UserState userState = getOrCreateUserStateLocked(resolvedUserId);
1100                     List<String> ratings = new ArrayList<>();
1101                     for (TvContentRating rating
1102                             : userState.persistentDataStore.getBlockedRatings()) {
1103                         ratings.add(rating.flattenToString());
1104                     }
1105                     return ratings;
1106                 }
1107             } finally {
1108                 Binder.restoreCallingIdentity(identity);
1109             }
1110         }
1111 
1112         @Override
addBlockedRating(String rating, int userId)1113         public void addBlockedRating(String rating, int userId) {
1114             ensureParentalControlsPermission();
1115             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1116                     Binder.getCallingUid(), userId, "addBlockedRating");
1117             final long identity = Binder.clearCallingIdentity();
1118             try {
1119                 synchronized (mLock) {
1120                     UserState userState = getOrCreateUserStateLocked(resolvedUserId);
1121                     userState.persistentDataStore.addBlockedRating(
1122                             TvContentRating.unflattenFromString(rating));
1123                 }
1124             } finally {
1125                 Binder.restoreCallingIdentity(identity);
1126             }
1127         }
1128 
1129         @Override
removeBlockedRating(String rating, int userId)1130         public void removeBlockedRating(String rating, int userId) {
1131             ensureParentalControlsPermission();
1132             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
1133                     Binder.getCallingUid(), userId, "removeBlockedRating");
1134             final long identity = Binder.clearCallingIdentity();
1135             try {
1136                 synchronized (mLock) {
1137                     UserState userState = getOrCreateUserStateLocked(resolvedUserId);
1138                     userState.persistentDataStore.removeBlockedRating(
1139                             TvContentRating.unflattenFromString(rating));
1140                 }
1141             } finally {
1142                 Binder.restoreCallingIdentity(identity);
1143             }
1144         }
1145 
ensureParentalControlsPermission()1146         private void ensureParentalControlsPermission() {
1147             if (mContext.checkCallingPermission(
1148                     android.Manifest.permission.MODIFY_PARENTAL_CONTROLS)
1149                     != PackageManager.PERMISSION_GRANTED) {
1150                 throw new SecurityException(
1151                         "The caller does not have parental controls permission");
1152             }
1153         }
1154 
1155         @Override
createSession(final ITvInputClient client, final String inputId, boolean isRecordingSession, int seq, int userId)1156         public void createSession(final ITvInputClient client, final String inputId,
1157                 boolean isRecordingSession, int seq, int userId) {
1158             final int callingUid = Binder.getCallingUid();
1159             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1160                     userId, "createSession");
1161             final long identity = Binder.clearCallingIdentity();
1162             try {
1163                 synchronized (mLock) {
1164                     if (userId != mCurrentUserId && !isRecordingSession) {
1165                         // A non-recording session of a backgroud (non-current) user
1166                         // should not be created.
1167                         // Let the client get onConnectionFailed callback for this case.
1168                         sendSessionTokenToClientLocked(client, inputId, null, null, seq);
1169                         return;
1170                     }
1171                     UserState userState = getOrCreateUserStateLocked(resolvedUserId);
1172                     TvInputState inputState = userState.inputMap.get(inputId);
1173                     if (inputState == null) {
1174                         Slog.w(TAG, "Failed to find input state for inputId=" + inputId);
1175                         sendSessionTokenToClientLocked(client, inputId, null, null, seq);
1176                         return;
1177                     }
1178                     TvInputInfo info = inputState.info;
1179                     ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
1180                     if (serviceState == null) {
1181                         serviceState = new ServiceState(info.getComponent(), resolvedUserId);
1182                         userState.serviceStateMap.put(info.getComponent(), serviceState);
1183                     }
1184                     // Send a null token immediately while reconnecting.
1185                     if (serviceState.reconnecting) {
1186                         sendSessionTokenToClientLocked(client, inputId, null, null, seq);
1187                         return;
1188                     }
1189 
1190                     // Create a new session token and a session state.
1191                     IBinder sessionToken = new Binder();
1192                     SessionState sessionState = new SessionState(sessionToken, info.getId(),
1193                             info.getComponent(), isRecordingSession, client, seq, callingUid,
1194                             resolvedUserId);
1195 
1196                     // Add them to the global session state map of the current user.
1197                     userState.sessionStateMap.put(sessionToken, sessionState);
1198 
1199                     // Also, add them to the session state map of the current service.
1200                     serviceState.sessionTokens.add(sessionToken);
1201 
1202                     if (serviceState.service != null) {
1203                         if (!createSessionInternalLocked(serviceState.service, sessionToken,
1204                                 resolvedUserId)) {
1205                             removeSessionStateLocked(sessionToken, resolvedUserId);
1206                         }
1207                     } else {
1208                         updateServiceConnectionLocked(info.getComponent(), resolvedUserId);
1209                     }
1210                 }
1211             } finally {
1212                 Binder.restoreCallingIdentity(identity);
1213             }
1214         }
1215 
1216         @Override
releaseSession(IBinder sessionToken, int userId)1217         public void releaseSession(IBinder sessionToken, int userId) {
1218             if (DEBUG) {
1219                 Slog.d(TAG, "releaseSession(sessionToken=" + sessionToken + ")");
1220             }
1221             final int callingUid = Binder.getCallingUid();
1222             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1223                     userId, "releaseSession");
1224             final long identity = Binder.clearCallingIdentity();
1225             try {
1226                 synchronized (mLock) {
1227                     releaseSessionLocked(sessionToken, callingUid, resolvedUserId);
1228                 }
1229             } finally {
1230                 Binder.restoreCallingIdentity(identity);
1231             }
1232         }
1233 
1234         @Override
setMainSession(IBinder sessionToken, int userId)1235         public void setMainSession(IBinder sessionToken, int userId) {
1236             if (mContext.checkCallingPermission(
1237                     android.Manifest.permission.CHANGE_HDMI_CEC_ACTIVE_SOURCE)
1238                     != PackageManager.PERMISSION_GRANTED) {
1239                 throw new SecurityException(
1240                         "The caller does not have CHANGE_HDMI_CEC_ACTIVE_SOURCE permission");
1241             }
1242             if (DEBUG) {
1243                 Slog.d(TAG, "setMainSession(sessionToken=" + sessionToken + ")");
1244             }
1245             final int callingUid = Binder.getCallingUid();
1246             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1247                     userId, "setMainSession");
1248             final long identity = Binder.clearCallingIdentity();
1249             try {
1250                 synchronized (mLock) {
1251                     UserState userState = getOrCreateUserStateLocked(resolvedUserId);
1252                     if (userState.mainSessionToken == sessionToken) {
1253                         return;
1254                     }
1255                     if (DEBUG) {
1256                         Slog.d(TAG, "mainSessionToken=" + sessionToken);
1257                     }
1258                     IBinder oldMainSessionToken = userState.mainSessionToken;
1259                     userState.mainSessionToken = sessionToken;
1260 
1261                     // Inform the new main session first.
1262                     // See {@link TvInputService.Session#onSetMain}.
1263                     if (sessionToken != null) {
1264                         setMainLocked(sessionToken, true, callingUid, userId);
1265                     }
1266                     if (oldMainSessionToken != null) {
1267                         setMainLocked(oldMainSessionToken, false, Process.SYSTEM_UID, userId);
1268                     }
1269                 }
1270             } finally {
1271                 Binder.restoreCallingIdentity(identity);
1272             }
1273         }
1274 
1275         @Override
setSurface(IBinder sessionToken, Surface surface, int userId)1276         public void setSurface(IBinder sessionToken, Surface surface, int userId) {
1277             final int callingUid = Binder.getCallingUid();
1278             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1279                     userId, "setSurface");
1280             final long identity = Binder.clearCallingIdentity();
1281             try {
1282                 synchronized (mLock) {
1283                     try {
1284                         SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
1285                                 resolvedUserId);
1286                         if (sessionState.hardwareSessionToken == null) {
1287                             getSessionLocked(sessionState).setSurface(surface);
1288                         } else {
1289                             getSessionLocked(sessionState.hardwareSessionToken,
1290                                     Process.SYSTEM_UID, resolvedUserId).setSurface(surface);
1291                         }
1292                     } catch (RemoteException | SessionNotFoundException e) {
1293                         Slog.e(TAG, "error in setSurface", e);
1294                     }
1295                 }
1296             } finally {
1297                 if (surface != null) {
1298                     // surface is not used in TvInputManagerService.
1299                     surface.release();
1300                 }
1301                 Binder.restoreCallingIdentity(identity);
1302             }
1303         }
1304 
1305         @Override
dispatchSurfaceChanged(IBinder sessionToken, int format, int width, int height, int userId)1306         public void dispatchSurfaceChanged(IBinder sessionToken, int format, int width,
1307                 int height, int userId) {
1308             final int callingUid = Binder.getCallingUid();
1309             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1310                     userId, "dispatchSurfaceChanged");
1311             final long identity = Binder.clearCallingIdentity();
1312             try {
1313                 synchronized (mLock) {
1314                     try {
1315                         SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
1316                                 resolvedUserId);
1317                         getSessionLocked(sessionState).dispatchSurfaceChanged(format, width,
1318                                 height);
1319                         if (sessionState.hardwareSessionToken != null) {
1320                             getSessionLocked(sessionState.hardwareSessionToken, Process.SYSTEM_UID,
1321                                     resolvedUserId).dispatchSurfaceChanged(format, width, height);
1322                         }
1323                     } catch (RemoteException | SessionNotFoundException e) {
1324                         Slog.e(TAG, "error in dispatchSurfaceChanged", e);
1325                     }
1326                 }
1327             } finally {
1328                 Binder.restoreCallingIdentity(identity);
1329             }
1330         }
1331 
1332         @Override
setVolume(IBinder sessionToken, float volume, int userId)1333         public void setVolume(IBinder sessionToken, float volume, int userId) {
1334             final float REMOTE_VOLUME_ON = 1.0f;
1335             final float REMOTE_VOLUME_OFF = 0f;
1336             final int callingUid = Binder.getCallingUid();
1337             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1338                     userId, "setVolume");
1339             final long identity = Binder.clearCallingIdentity();
1340             try {
1341                 synchronized (mLock) {
1342                     try {
1343                         SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
1344                                 resolvedUserId);
1345                         getSessionLocked(sessionState).setVolume(volume);
1346                         if (sessionState.hardwareSessionToken != null) {
1347                             // Here, we let the hardware session know only whether volume is on or
1348                             // off to prevent that the volume is controlled in the both side.
1349                             getSessionLocked(sessionState.hardwareSessionToken,
1350                                     Process.SYSTEM_UID, resolvedUserId).setVolume((volume > 0.0f)
1351                                             ? REMOTE_VOLUME_ON : REMOTE_VOLUME_OFF);
1352                         }
1353                     } catch (RemoteException | SessionNotFoundException e) {
1354                         Slog.e(TAG, "error in setVolume", e);
1355                     }
1356                 }
1357             } finally {
1358                 Binder.restoreCallingIdentity(identity);
1359             }
1360         }
1361 
1362         @Override
tune(IBinder sessionToken, final Uri channelUri, Bundle params, int userId)1363         public void tune(IBinder sessionToken, final Uri channelUri, Bundle params, int userId) {
1364             final int callingUid = Binder.getCallingUid();
1365             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1366                     userId, "tune");
1367             final long identity = Binder.clearCallingIdentity();
1368             try {
1369                 synchronized (mLock) {
1370                     try {
1371                         getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(
1372                                 channelUri, params);
1373                         if (TvContract.isChannelUriForPassthroughInput(channelUri)) {
1374                             // Do not log the watch history for passthrough inputs.
1375                             return;
1376                         }
1377 
1378                         UserState userState = getOrCreateUserStateLocked(resolvedUserId);
1379                         SessionState sessionState = userState.sessionStateMap.get(sessionToken);
1380                         if (sessionState.isRecordingSession) {
1381                             return;
1382                         }
1383 
1384                         // Log the start of watch.
1385                         SomeArgs args = SomeArgs.obtain();
1386                         args.arg1 = sessionState.componentName.getPackageName();
1387                         args.arg2 = System.currentTimeMillis();
1388                         args.arg3 = ContentUris.parseId(channelUri);
1389                         args.arg4 = params;
1390                         args.arg5 = sessionToken;
1391                         mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_LOG_WATCH_START, args)
1392                                 .sendToTarget();
1393                     } catch (RemoteException | SessionNotFoundException e) {
1394                         Slog.e(TAG, "error in tune", e);
1395                     }
1396                 }
1397             } finally {
1398                 Binder.restoreCallingIdentity(identity);
1399             }
1400         }
1401 
1402         @Override
unblockContent( IBinder sessionToken, String unblockedRating, int userId)1403         public void unblockContent(
1404                 IBinder sessionToken, String unblockedRating, int userId) {
1405             ensureParentalControlsPermission();
1406             final int callingUid = Binder.getCallingUid();
1407             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1408                     userId, "unblockContent");
1409             final long identity = Binder.clearCallingIdentity();
1410             try {
1411                 synchronized (mLock) {
1412                     try {
1413                         getSessionLocked(sessionToken, callingUid, resolvedUserId)
1414                                 .unblockContent(unblockedRating);
1415                     } catch (RemoteException | SessionNotFoundException e) {
1416                         Slog.e(TAG, "error in unblockContent", e);
1417                     }
1418                 }
1419             } finally {
1420                 Binder.restoreCallingIdentity(identity);
1421             }
1422         }
1423 
1424         @Override
setCaptionEnabled(IBinder sessionToken, boolean enabled, int userId)1425         public void setCaptionEnabled(IBinder sessionToken, boolean enabled, int userId) {
1426             final int callingUid = Binder.getCallingUid();
1427             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1428                     userId, "setCaptionEnabled");
1429             final long identity = Binder.clearCallingIdentity();
1430             try {
1431                 synchronized (mLock) {
1432                     try {
1433                         getSessionLocked(sessionToken, callingUid, resolvedUserId)
1434                                 .setCaptionEnabled(enabled);
1435                     } catch (RemoteException | SessionNotFoundException e) {
1436                         Slog.e(TAG, "error in setCaptionEnabled", e);
1437                     }
1438                 }
1439             } finally {
1440                 Binder.restoreCallingIdentity(identity);
1441             }
1442         }
1443 
1444         @Override
selectTrack(IBinder sessionToken, int type, String trackId, int userId)1445         public void selectTrack(IBinder sessionToken, int type, String trackId, int userId) {
1446             final int callingUid = Binder.getCallingUid();
1447             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1448                     userId, "selectTrack");
1449             final long identity = Binder.clearCallingIdentity();
1450             try {
1451                 synchronized (mLock) {
1452                     try {
1453                         getSessionLocked(sessionToken, callingUid, resolvedUserId).selectTrack(
1454                                 type, trackId);
1455                     } catch (RemoteException | SessionNotFoundException e) {
1456                         Slog.e(TAG, "error in selectTrack", e);
1457                     }
1458                 }
1459             } finally {
1460                 Binder.restoreCallingIdentity(identity);
1461             }
1462         }
1463 
1464         @Override
sendAppPrivateCommand(IBinder sessionToken, String command, Bundle data, int userId)1465         public void sendAppPrivateCommand(IBinder sessionToken, String command, Bundle data,
1466                 int userId) {
1467             final int callingUid = Binder.getCallingUid();
1468             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1469                     userId, "sendAppPrivateCommand");
1470             final long identity = Binder.clearCallingIdentity();
1471             try {
1472                 synchronized (mLock) {
1473                     try {
1474                         getSessionLocked(sessionToken, callingUid, resolvedUserId)
1475                                 .appPrivateCommand(command, data);
1476                     } catch (RemoteException | SessionNotFoundException e) {
1477                         Slog.e(TAG, "error in appPrivateCommand", e);
1478                     }
1479                 }
1480             } finally {
1481                 Binder.restoreCallingIdentity(identity);
1482             }
1483         }
1484 
1485         @Override
createOverlayView(IBinder sessionToken, IBinder windowToken, Rect frame, int userId)1486         public void createOverlayView(IBinder sessionToken, IBinder windowToken, Rect frame,
1487                 int userId) {
1488             final int callingUid = Binder.getCallingUid();
1489             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1490                     userId, "createOverlayView");
1491             final long identity = Binder.clearCallingIdentity();
1492             try {
1493                 synchronized (mLock) {
1494                     try {
1495                         getSessionLocked(sessionToken, callingUid, resolvedUserId)
1496                                 .createOverlayView(windowToken, frame);
1497                     } catch (RemoteException | SessionNotFoundException e) {
1498                         Slog.e(TAG, "error in createOverlayView", e);
1499                     }
1500                 }
1501             } finally {
1502                 Binder.restoreCallingIdentity(identity);
1503             }
1504         }
1505 
1506         @Override
relayoutOverlayView(IBinder sessionToken, Rect frame, int userId)1507         public void relayoutOverlayView(IBinder sessionToken, Rect frame, int userId) {
1508             final int callingUid = Binder.getCallingUid();
1509             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1510                     userId, "relayoutOverlayView");
1511             final long identity = Binder.clearCallingIdentity();
1512             try {
1513                 synchronized (mLock) {
1514                     try {
1515                         getSessionLocked(sessionToken, callingUid, resolvedUserId)
1516                                 .relayoutOverlayView(frame);
1517                     } catch (RemoteException | SessionNotFoundException e) {
1518                         Slog.e(TAG, "error in relayoutOverlayView", e);
1519                     }
1520                 }
1521             } finally {
1522                 Binder.restoreCallingIdentity(identity);
1523             }
1524         }
1525 
1526         @Override
removeOverlayView(IBinder sessionToken, int userId)1527         public void removeOverlayView(IBinder sessionToken, int userId) {
1528             final int callingUid = Binder.getCallingUid();
1529             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1530                     userId, "removeOverlayView");
1531             final long identity = Binder.clearCallingIdentity();
1532             try {
1533                 synchronized (mLock) {
1534                     try {
1535                         getSessionLocked(sessionToken, callingUid, resolvedUserId)
1536                                 .removeOverlayView();
1537                     } catch (RemoteException | SessionNotFoundException e) {
1538                         Slog.e(TAG, "error in removeOverlayView", e);
1539                     }
1540                 }
1541             } finally {
1542                 Binder.restoreCallingIdentity(identity);
1543             }
1544         }
1545 
1546         @Override
timeShiftPlay(IBinder sessionToken, final Uri recordedProgramUri, int userId)1547         public void timeShiftPlay(IBinder sessionToken, final Uri recordedProgramUri, int userId) {
1548             final int callingUid = Binder.getCallingUid();
1549             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1550                     userId, "timeShiftPlay");
1551             final long identity = Binder.clearCallingIdentity();
1552             try {
1553                 synchronized (mLock) {
1554                     try {
1555                         getSessionLocked(sessionToken, callingUid, resolvedUserId).timeShiftPlay(
1556                                 recordedProgramUri);
1557                     } catch (RemoteException | SessionNotFoundException e) {
1558                         Slog.e(TAG, "error in timeShiftPlay", e);
1559                     }
1560                 }
1561             } finally {
1562                 Binder.restoreCallingIdentity(identity);
1563             }
1564         }
1565 
1566         @Override
timeShiftPause(IBinder sessionToken, int userId)1567         public void timeShiftPause(IBinder sessionToken, int userId) {
1568             final int callingUid = Binder.getCallingUid();
1569             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1570                     userId, "timeShiftPause");
1571             final long identity = Binder.clearCallingIdentity();
1572             try {
1573                 synchronized (mLock) {
1574                     try {
1575                         getSessionLocked(sessionToken, callingUid, resolvedUserId).timeShiftPause();
1576                     } catch (RemoteException | SessionNotFoundException e) {
1577                         Slog.e(TAG, "error in timeShiftPause", e);
1578                     }
1579                 }
1580             } finally {
1581                 Binder.restoreCallingIdentity(identity);
1582             }
1583         }
1584 
1585         @Override
timeShiftResume(IBinder sessionToken, int userId)1586         public void timeShiftResume(IBinder sessionToken, int userId) {
1587             final int callingUid = Binder.getCallingUid();
1588             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1589                     userId, "timeShiftResume");
1590             final long identity = Binder.clearCallingIdentity();
1591             try {
1592                 synchronized (mLock) {
1593                     try {
1594                         getSessionLocked(sessionToken, callingUid, resolvedUserId)
1595                                 .timeShiftResume();
1596                     } catch (RemoteException | SessionNotFoundException e) {
1597                         Slog.e(TAG, "error in timeShiftResume", e);
1598                     }
1599                 }
1600             } finally {
1601                 Binder.restoreCallingIdentity(identity);
1602             }
1603         }
1604 
1605         @Override
timeShiftSeekTo(IBinder sessionToken, long timeMs, int userId)1606         public void timeShiftSeekTo(IBinder sessionToken, long timeMs, int userId) {
1607             final int callingUid = Binder.getCallingUid();
1608             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1609                     userId, "timeShiftSeekTo");
1610             final long identity = Binder.clearCallingIdentity();
1611             try {
1612                 synchronized (mLock) {
1613                     try {
1614                         getSessionLocked(sessionToken, callingUid, resolvedUserId)
1615                                 .timeShiftSeekTo(timeMs);
1616                     } catch (RemoteException | SessionNotFoundException e) {
1617                         Slog.e(TAG, "error in timeShiftSeekTo", e);
1618                     }
1619                 }
1620             } finally {
1621                 Binder.restoreCallingIdentity(identity);
1622             }
1623         }
1624 
1625         @Override
timeShiftSetPlaybackParams(IBinder sessionToken, PlaybackParams params, int userId)1626         public void timeShiftSetPlaybackParams(IBinder sessionToken, PlaybackParams params,
1627                 int userId) {
1628             final int callingUid = Binder.getCallingUid();
1629             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1630                     userId, "timeShiftSetPlaybackParams");
1631             final long identity = Binder.clearCallingIdentity();
1632             try {
1633                 synchronized (mLock) {
1634                     try {
1635                         getSessionLocked(sessionToken, callingUid, resolvedUserId)
1636                                 .timeShiftSetPlaybackParams(params);
1637                     } catch (RemoteException | SessionNotFoundException e) {
1638                         Slog.e(TAG, "error in timeShiftSetPlaybackParams", e);
1639                     }
1640                 }
1641             } finally {
1642                 Binder.restoreCallingIdentity(identity);
1643             }
1644         }
1645 
1646         @Override
timeShiftEnablePositionTracking(IBinder sessionToken, boolean enable, int userId)1647         public void timeShiftEnablePositionTracking(IBinder sessionToken, boolean enable,
1648                 int userId) {
1649             final int callingUid = Binder.getCallingUid();
1650             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1651                     userId, "timeShiftEnablePositionTracking");
1652             final long identity = Binder.clearCallingIdentity();
1653             try {
1654                 synchronized (mLock) {
1655                     try {
1656                         getSessionLocked(sessionToken, callingUid, resolvedUserId)
1657                                 .timeShiftEnablePositionTracking(enable);
1658                     } catch (RemoteException | SessionNotFoundException e) {
1659                         Slog.e(TAG, "error in timeShiftEnablePositionTracking", e);
1660                     }
1661                 }
1662             } finally {
1663                 Binder.restoreCallingIdentity(identity);
1664             }
1665         }
1666 
1667         @Override
startRecording(IBinder sessionToken, @Nullable Uri programUri, int userId)1668         public void startRecording(IBinder sessionToken, @Nullable Uri programUri, int userId) {
1669             final int callingUid = Binder.getCallingUid();
1670             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1671                     userId, "startRecording");
1672             final long identity = Binder.clearCallingIdentity();
1673             try {
1674                 synchronized (mLock) {
1675                     try {
1676                         getSessionLocked(sessionToken, callingUid, resolvedUserId).startRecording(
1677                                 programUri);
1678                     } catch (RemoteException | SessionNotFoundException e) {
1679                         Slog.e(TAG, "error in startRecording", e);
1680                     }
1681                 }
1682             } finally {
1683                 Binder.restoreCallingIdentity(identity);
1684             }
1685         }
1686 
1687         @Override
stopRecording(IBinder sessionToken, int userId)1688         public void stopRecording(IBinder sessionToken, int userId) {
1689             final int callingUid = Binder.getCallingUid();
1690             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1691                     userId, "stopRecording");
1692             final long identity = Binder.clearCallingIdentity();
1693             try {
1694                 synchronized (mLock) {
1695                     try {
1696                         getSessionLocked(sessionToken, callingUid, resolvedUserId).stopRecording();
1697                     } catch (RemoteException | SessionNotFoundException e) {
1698                         Slog.e(TAG, "error in stopRecording", e);
1699                     }
1700                 }
1701             } finally {
1702                 Binder.restoreCallingIdentity(identity);
1703             }
1704         }
1705 
1706         @Override
getHardwareList()1707         public List<TvInputHardwareInfo> getHardwareList() throws RemoteException {
1708             if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
1709                     != PackageManager.PERMISSION_GRANTED) {
1710                 return null;
1711             }
1712 
1713             final long identity = Binder.clearCallingIdentity();
1714             try {
1715                 return mTvInputHardwareManager.getHardwareList();
1716             } finally {
1717                 Binder.restoreCallingIdentity(identity);
1718             }
1719         }
1720 
1721         @Override
acquireTvInputHardware(int deviceId, ITvInputHardwareCallback callback, TvInputInfo info, int userId)1722         public ITvInputHardware acquireTvInputHardware(int deviceId,
1723                 ITvInputHardwareCallback callback, TvInputInfo info, int userId)
1724                 throws RemoteException {
1725             if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
1726                     != PackageManager.PERMISSION_GRANTED) {
1727                 return null;
1728             }
1729 
1730             final long identity = Binder.clearCallingIdentity();
1731             final int callingUid = Binder.getCallingUid();
1732             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1733                     userId, "acquireTvInputHardware");
1734             try {
1735                 return mTvInputHardwareManager.acquireHardware(
1736                         deviceId, callback, info, callingUid, resolvedUserId);
1737             } finally {
1738                 Binder.restoreCallingIdentity(identity);
1739             }
1740         }
1741 
1742         @Override
releaseTvInputHardware(int deviceId, ITvInputHardware hardware, int userId)1743         public void releaseTvInputHardware(int deviceId, ITvInputHardware hardware, int userId)
1744                 throws RemoteException {
1745             if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
1746                     != PackageManager.PERMISSION_GRANTED) {
1747                 return;
1748             }
1749 
1750             final long identity = Binder.clearCallingIdentity();
1751             final int callingUid = Binder.getCallingUid();
1752             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1753                     userId, "releaseTvInputHardware");
1754             try {
1755                 mTvInputHardwareManager.releaseHardware(
1756                         deviceId, hardware, callingUid, resolvedUserId);
1757             } finally {
1758                 Binder.restoreCallingIdentity(identity);
1759             }
1760         }
1761 
1762         @Override
getDvbDeviceList()1763         public List<DvbDeviceInfo> getDvbDeviceList() throws RemoteException {
1764             if (mContext.checkCallingPermission(android.Manifest.permission.DVB_DEVICE)
1765                     != PackageManager.PERMISSION_GRANTED) {
1766                 throw new SecurityException("Requires DVB_DEVICE permission");
1767             }
1768 
1769             final long identity = Binder.clearCallingIdentity();
1770             try {
1771                 // Pattern1: /dev/dvb%d.frontend%d
1772                 ArrayList<DvbDeviceInfo> deviceInfosFromPattern1 = new ArrayList<>();
1773                 File devDirectory = new File("/dev");
1774                 boolean dvbDirectoryFound = false;
1775                 for (String fileName : devDirectory.list()) {
1776                     Matcher matcher = sFrontEndDevicePattern.matcher(fileName);
1777                     if (matcher.find()) {
1778                         int adapterId = Integer.parseInt(matcher.group(1));
1779                         int deviceId = Integer.parseInt(matcher.group(2));
1780                         deviceInfosFromPattern1.add(new DvbDeviceInfo(adapterId, deviceId));
1781                     }
1782                     if (TextUtils.equals("dvb", fileName)) {
1783                         dvbDirectoryFound = true;
1784                     }
1785                 }
1786                 if (!dvbDirectoryFound) {
1787                     return Collections.unmodifiableList(deviceInfosFromPattern1);
1788                 }
1789                 File dvbDirectory = new File(DVB_DIRECTORY);
1790                 // Pattern2: /dev/dvb/adapter%d/frontend%d
1791                 ArrayList<DvbDeviceInfo> deviceInfosFromPattern2 = new ArrayList<>();
1792                 for (String fileNameInDvb : dvbDirectory.list()) {
1793                     Matcher adapterMatcher = sAdapterDirPattern.matcher(fileNameInDvb);
1794                     if (adapterMatcher.find()) {
1795                         int adapterId = Integer.parseInt(adapterMatcher.group(1));
1796                         File adapterDirectory = new File(DVB_DIRECTORY + "/" + fileNameInDvb);
1797                         for (String fileNameInAdapter : adapterDirectory.list()) {
1798                             Matcher frontendMatcher = sFrontEndInAdapterDirPattern.matcher(
1799                                     fileNameInAdapter);
1800                             if (frontendMatcher.find()) {
1801                                 int deviceId = Integer.parseInt(frontendMatcher.group(1));
1802                                 deviceInfosFromPattern2.add(
1803                                         new DvbDeviceInfo(adapterId, deviceId));
1804                             }
1805                         }
1806                     }
1807                 }
1808                 return deviceInfosFromPattern2.isEmpty()
1809                         ? Collections.unmodifiableList(deviceInfosFromPattern1)
1810                         : Collections.unmodifiableList(deviceInfosFromPattern2);
1811             } finally {
1812                 Binder.restoreCallingIdentity(identity);
1813             }
1814         }
1815 
1816         @Override
openDvbDevice(DvbDeviceInfo info, @TvInputManager.DvbDeviceType int deviceType)1817         public ParcelFileDescriptor openDvbDevice(DvbDeviceInfo info,
1818                 @TvInputManager.DvbDeviceType int deviceType)  throws RemoteException {
1819             if (mContext.checkCallingPermission(android.Manifest.permission.DVB_DEVICE)
1820                     != PackageManager.PERMISSION_GRANTED) {
1821                 throw new SecurityException("Requires DVB_DEVICE permission");
1822             }
1823 
1824             File devDirectory = new File("/dev");
1825             boolean dvbDeviceFound = false;
1826             for (String fileName : devDirectory.list()) {
1827                 if (TextUtils.equals("dvb", fileName)) {
1828                     File dvbDirectory = new File(DVB_DIRECTORY);
1829                     for (String fileNameInDvb : dvbDirectory.list()) {
1830                         Matcher adapterMatcher = sAdapterDirPattern.matcher(fileNameInDvb);
1831                         if (adapterMatcher.find()) {
1832                             File adapterDirectory = new File(DVB_DIRECTORY + "/" + fileNameInDvb);
1833                             for (String fileNameInAdapter : adapterDirectory.list()) {
1834                                 Matcher frontendMatcher = sFrontEndInAdapterDirPattern.matcher(
1835                                         fileNameInAdapter);
1836                                 if (frontendMatcher.find()) {
1837                                     dvbDeviceFound = true;
1838                                     break;
1839                                 }
1840                             }
1841                         }
1842                         if (dvbDeviceFound) {
1843                             break;
1844                         }
1845                     }
1846                 }
1847                 if (dvbDeviceFound) {
1848                     break;
1849                 }
1850             }
1851 
1852             final long identity = Binder.clearCallingIdentity();
1853             try {
1854                 String deviceFileName;
1855                 switch (deviceType) {
1856                     case TvInputManager.DVB_DEVICE_DEMUX:
1857                         deviceFileName = String.format(dvbDeviceFound
1858                                 ? "/dev/dvb/adapter%d/demux%d" : "/dev/dvb%d.demux%d",
1859                                 info.getAdapterId(), info.getDeviceId());
1860                         break;
1861                     case TvInputManager.DVB_DEVICE_DVR:
1862                         deviceFileName = String.format(dvbDeviceFound
1863                                 ? "/dev/dvb/adapter%d/dvr%d" : "/dev/dvb%d.dvr%d",
1864                                 info.getAdapterId(), info.getDeviceId());
1865                         break;
1866                     case TvInputManager.DVB_DEVICE_FRONTEND:
1867                         deviceFileName = String.format(dvbDeviceFound
1868                                 ? "/dev/dvb/adapter%d/frontend%d" : "/dev/dvb%d.frontend%d",
1869                                 info.getAdapterId(), info.getDeviceId());
1870                         break;
1871                     default:
1872                         throw new IllegalArgumentException("Invalid DVB device: " + deviceType);
1873                 }
1874                 try {
1875                     // The DVB frontend device only needs to be opened in read/write mode, which
1876                     // allows performing tuning operations. The DVB demux and DVR device are enough
1877                     // to be opened in read only mode.
1878                     return ParcelFileDescriptor.open(new File(deviceFileName),
1879                             TvInputManager.DVB_DEVICE_FRONTEND == deviceType
1880                                     ? ParcelFileDescriptor.MODE_READ_WRITE
1881                                     : ParcelFileDescriptor.MODE_READ_ONLY);
1882                 } catch (FileNotFoundException e) {
1883                     return null;
1884                 }
1885             } finally {
1886                 Binder.restoreCallingIdentity(identity);
1887             }
1888         }
1889 
1890         @Override
getAvailableTvStreamConfigList(String inputId, int userId)1891         public List<TvStreamConfig> getAvailableTvStreamConfigList(String inputId, int userId)
1892                 throws RemoteException {
1893             ensureCaptureTvInputPermission();
1894 
1895             final long identity = Binder.clearCallingIdentity();
1896             final int callingUid = Binder.getCallingUid();
1897             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1898                     userId, "getAvailableTvStreamConfigList");
1899             try {
1900                 return mTvInputHardwareManager.getAvailableTvStreamConfigList(
1901                         inputId, callingUid, resolvedUserId);
1902             } finally {
1903                 Binder.restoreCallingIdentity(identity);
1904             }
1905         }
1906 
1907         @Override
captureFrame(String inputId, Surface surface, TvStreamConfig config, int userId)1908         public boolean captureFrame(String inputId, Surface surface, TvStreamConfig config,
1909                 int userId)
1910                 throws RemoteException {
1911             ensureCaptureTvInputPermission();
1912 
1913             final long identity = Binder.clearCallingIdentity();
1914             final int callingUid = Binder.getCallingUid();
1915             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1916                     userId, "captureFrame");
1917             try {
1918                 String hardwareInputId = null;
1919                 synchronized (mLock) {
1920                     UserState userState = getOrCreateUserStateLocked(resolvedUserId);
1921                     if (userState.inputMap.get(inputId) == null) {
1922                         Slog.e(TAG, "input not found for " + inputId);
1923                         return false;
1924                     }
1925                     for (SessionState sessionState : userState.sessionStateMap.values()) {
1926                         if (sessionState.inputId.equals(inputId)
1927                                 && sessionState.hardwareSessionToken != null) {
1928                             hardwareInputId = userState.sessionStateMap.get(
1929                                     sessionState.hardwareSessionToken).inputId;
1930                             break;
1931                         }
1932                     }
1933                 }
1934                 return mTvInputHardwareManager.captureFrame(
1935                         (hardwareInputId != null) ? hardwareInputId : inputId,
1936                         surface, config, callingUid, resolvedUserId);
1937             } finally {
1938                 Binder.restoreCallingIdentity(identity);
1939             }
1940         }
1941 
1942         @Override
isSingleSessionActive(int userId)1943         public boolean isSingleSessionActive(int userId) throws RemoteException {
1944             ensureCaptureTvInputPermission();
1945             final long identity = Binder.clearCallingIdentity();
1946             final int callingUid = Binder.getCallingUid();
1947             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1948                     userId, "isSingleSessionActive");
1949             try {
1950                 synchronized (mLock) {
1951                     UserState userState = getOrCreateUserStateLocked(resolvedUserId);
1952                     if (userState.sessionStateMap.size() == 1) {
1953                         return true;
1954                     } else if (userState.sessionStateMap.size() == 2) {
1955                         SessionState[] sessionStates = userState.sessionStateMap.values().toArray(
1956                                 new SessionState[2]);
1957                         // Check if there is a wrapper input.
1958                         if (sessionStates[0].hardwareSessionToken != null
1959                                 || sessionStates[1].hardwareSessionToken != null) {
1960                             return true;
1961                         }
1962                     }
1963                     return false;
1964                 }
1965             } finally {
1966                 Binder.restoreCallingIdentity(identity);
1967             }
1968         }
1969 
ensureCaptureTvInputPermission()1970         private void ensureCaptureTvInputPermission() {
1971             if (mContext.checkCallingPermission(
1972                 android.Manifest.permission.CAPTURE_TV_INPUT)
1973                 != PackageManager.PERMISSION_GRANTED) {
1974                 throw new SecurityException("Requires CAPTURE_TV_INPUT permission");
1975             }
1976         }
1977 
1978         @Override
requestChannelBrowsable(Uri channelUri, int userId)1979         public void requestChannelBrowsable(Uri channelUri, int userId)
1980                 throws RemoteException {
1981             final String callingPackageName = getCallingPackageName();
1982             final long identity = Binder.clearCallingIdentity();
1983             final int callingUid = Binder.getCallingUid();
1984             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1985                 userId, "requestChannelBrowsable");
1986             try {
1987                 Intent intent = new Intent(TvContract.ACTION_CHANNEL_BROWSABLE_REQUESTED);
1988                 List<ResolveInfo> list = getContext().getPackageManager()
1989                     .queryBroadcastReceivers(intent, 0);
1990                 if (list != null) {
1991                     for (ResolveInfo info : list) {
1992                         String receiverPackageName = info.activityInfo.packageName;
1993                         intent.putExtra(TvContract.EXTRA_CHANNEL_ID, ContentUris.parseId(
1994                                 channelUri));
1995                         intent.putExtra(TvContract.EXTRA_PACKAGE_NAME, callingPackageName);
1996                         intent.setPackage(receiverPackageName);
1997                         getContext().sendBroadcastAsUser(intent, new UserHandle(resolvedUserId));
1998                     }
1999                 }
2000             } finally {
2001                 Binder.restoreCallingIdentity(identity);
2002             }
2003         }
2004 
2005         @Override
2006         @SuppressWarnings("resource")
dump(FileDescriptor fd, final PrintWriter writer, String[] args)2007         protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
2008             final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
2009             if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
2010 
2011             synchronized (mLock) {
2012                 pw.println("User Ids (Current user: " + mCurrentUserId + "):");
2013                 pw.increaseIndent();
2014                 for (int i = 0; i < mUserStates.size(); i++) {
2015                     int userId = mUserStates.keyAt(i);
2016                     pw.println(Integer.valueOf(userId));
2017                 }
2018                 pw.decreaseIndent();
2019 
2020                 for (int i = 0; i < mUserStates.size(); i++) {
2021                     int userId = mUserStates.keyAt(i);
2022                     UserState userState = getOrCreateUserStateLocked(userId);
2023                     pw.println("UserState (" + userId + "):");
2024                     pw.increaseIndent();
2025 
2026                     pw.println("inputMap: inputId -> TvInputState");
2027                     pw.increaseIndent();
2028                     for (Map.Entry<String, TvInputState> entry: userState.inputMap.entrySet()) {
2029                         pw.println(entry.getKey() + ": " + entry.getValue());
2030                     }
2031                     pw.decreaseIndent();
2032 
2033                     pw.println("packageSet:");
2034                     pw.increaseIndent();
2035                     for (String packageName : userState.packageSet) {
2036                         pw.println(packageName);
2037                     }
2038                     pw.decreaseIndent();
2039 
2040                     pw.println("clientStateMap: ITvInputClient -> ClientState");
2041                     pw.increaseIndent();
2042                     for (Map.Entry<IBinder, ClientState> entry :
2043                             userState.clientStateMap.entrySet()) {
2044                         ClientState client = entry.getValue();
2045                         pw.println(entry.getKey() + ": " + client);
2046 
2047                         pw.increaseIndent();
2048 
2049                         pw.println("sessionTokens:");
2050                         pw.increaseIndent();
2051                         for (IBinder token : client.sessionTokens) {
2052                             pw.println("" + token);
2053                         }
2054                         pw.decreaseIndent();
2055 
2056                         pw.println("clientTokens: " + client.clientToken);
2057                         pw.println("userId: " + client.userId);
2058 
2059                         pw.decreaseIndent();
2060                     }
2061                     pw.decreaseIndent();
2062 
2063                     pw.println("serviceStateMap: ComponentName -> ServiceState");
2064                     pw.increaseIndent();
2065                     for (Map.Entry<ComponentName, ServiceState> entry :
2066                             userState.serviceStateMap.entrySet()) {
2067                         ServiceState service = entry.getValue();
2068                         pw.println(entry.getKey() + ": " + service);
2069 
2070                         pw.increaseIndent();
2071 
2072                         pw.println("sessionTokens:");
2073                         pw.increaseIndent();
2074                         for (IBinder token : service.sessionTokens) {
2075                             pw.println("" + token);
2076                         }
2077                         pw.decreaseIndent();
2078 
2079                         pw.println("service: " + service.service);
2080                         pw.println("callback: " + service.callback);
2081                         pw.println("bound: " + service.bound);
2082                         pw.println("reconnecting: " + service.reconnecting);
2083 
2084                         pw.decreaseIndent();
2085                     }
2086                     pw.decreaseIndent();
2087 
2088                     pw.println("sessionStateMap: ITvInputSession -> SessionState");
2089                     pw.increaseIndent();
2090                     for (Map.Entry<IBinder, SessionState> entry :
2091                             userState.sessionStateMap.entrySet()) {
2092                         SessionState session = entry.getValue();
2093                         pw.println(entry.getKey() + ": " + session);
2094 
2095                         pw.increaseIndent();
2096                         pw.println("inputId: " + session.inputId);
2097                         pw.println("client: " + session.client);
2098                         pw.println("seq: " + session.seq);
2099                         pw.println("callingUid: " + session.callingUid);
2100                         pw.println("userId: " + session.userId);
2101                         pw.println("sessionToken: " + session.sessionToken);
2102                         pw.println("session: " + session.session);
2103                         pw.println("logUri: " + session.logUri);
2104                         pw.println("hardwareSessionToken: " + session.hardwareSessionToken);
2105                         pw.decreaseIndent();
2106                     }
2107                     pw.decreaseIndent();
2108 
2109                     pw.println("mCallbacks:");
2110                     pw.increaseIndent();
2111                     int n = userState.mCallbacks.beginBroadcast();
2112                     for (int j = 0; j < n; ++j) {
2113                         pw.println(userState.mCallbacks.getRegisteredCallbackItem(j).toString());
2114                     }
2115                     userState.mCallbacks.finishBroadcast();
2116                     pw.decreaseIndent();
2117 
2118                     pw.println("mainSessionToken: " + userState.mainSessionToken);
2119                     pw.decreaseIndent();
2120                 }
2121             }
2122             mTvInputHardwareManager.dump(fd, writer, args);
2123         }
2124     }
2125 
2126     private static final class UserState {
2127         // A mapping from the TV input id to its TvInputState.
2128         private Map<String, TvInputState> inputMap = new HashMap<>();
2129 
2130         // A set of all TV input packages.
2131         private final Set<String> packageSet = new HashSet<>();
2132 
2133         // A list of all TV content rating systems defined.
2134         private final List<TvContentRatingSystemInfo>
2135                 contentRatingSystemList = new ArrayList<>();
2136 
2137         // A mapping from the token of a client to its state.
2138         private final Map<IBinder, ClientState> clientStateMap = new HashMap<>();
2139 
2140         // A mapping from the name of a TV input service to its state.
2141         private final Map<ComponentName, ServiceState> serviceStateMap = new HashMap<>();
2142 
2143         // A mapping from the token of a TV input session to its state.
2144         private final Map<IBinder, SessionState> sessionStateMap = new HashMap<>();
2145 
2146         // A list of callbacks.
2147         private final RemoteCallbackList<ITvInputManagerCallback> mCallbacks =
2148                 new RemoteCallbackList<ITvInputManagerCallback>();
2149 
2150         // The token of a "main" TV input session.
2151         private IBinder mainSessionToken = null;
2152 
2153         // Persistent data store for all internal settings maintained by the TV input manager
2154         // service.
2155         private final PersistentDataStore persistentDataStore;
2156 
UserState(Context context, int userId)2157         private UserState(Context context, int userId) {
2158             persistentDataStore = new PersistentDataStore(context, userId);
2159         }
2160     }
2161 
2162     private final class ClientState implements IBinder.DeathRecipient {
2163         private final List<IBinder> sessionTokens = new ArrayList<>();
2164 
2165         private IBinder clientToken;
2166         private final int userId;
2167 
ClientState(IBinder clientToken, int userId)2168         ClientState(IBinder clientToken, int userId) {
2169             this.clientToken = clientToken;
2170             this.userId = userId;
2171         }
2172 
isEmpty()2173         public boolean isEmpty() {
2174             return sessionTokens.isEmpty();
2175         }
2176 
2177         @Override
binderDied()2178         public void binderDied() {
2179             synchronized (mLock) {
2180                 UserState userState = getOrCreateUserStateLocked(userId);
2181                 // DO NOT remove the client state of clientStateMap in this method. It will be
2182                 // removed in releaseSessionLocked().
2183                 ClientState clientState = userState.clientStateMap.get(clientToken);
2184                 if (clientState != null) {
2185                     while (clientState.sessionTokens.size() > 0) {
2186                         releaseSessionLocked(
2187                                 clientState.sessionTokens.get(0), Process.SYSTEM_UID, userId);
2188                     }
2189                 }
2190                 clientToken = null;
2191             }
2192         }
2193     }
2194 
2195     private final class ServiceState {
2196         private final List<IBinder> sessionTokens = new ArrayList<>();
2197         private final ServiceConnection connection;
2198         private final ComponentName component;
2199         private final boolean isHardware;
2200         private final Map<String, TvInputInfo> hardwareInputMap = new HashMap<>();
2201 
2202         private ITvInputService service;
2203         private ServiceCallback callback;
2204         private boolean bound;
2205         private boolean reconnecting;
2206 
ServiceState(ComponentName component, int userId)2207         private ServiceState(ComponentName component, int userId) {
2208             this.component = component;
2209             this.connection = new InputServiceConnection(component, userId);
2210             this.isHardware = hasHardwarePermission(mContext.getPackageManager(), component);
2211         }
2212     }
2213 
2214     private static final class TvInputState {
2215         // A TvInputInfo object which represents the TV input.
2216         private TvInputInfo info;
2217 
2218         // The state of TV input. Connected by default.
2219         private int state = INPUT_STATE_CONNECTED;
2220 
2221         @Override
toString()2222         public String toString() {
2223             return "info: " + info + "; state: " + state;
2224         }
2225     }
2226 
2227     private final class SessionState implements IBinder.DeathRecipient {
2228         private final String inputId;
2229         private final ComponentName componentName;
2230         private final boolean isRecordingSession;
2231         private final ITvInputClient client;
2232         private final int seq;
2233         private final int callingUid;
2234         private final int userId;
2235         private final IBinder sessionToken;
2236         private ITvInputSession session;
2237         private Uri logUri;
2238         // Not null if this session represents an external device connected to a hardware TV input.
2239         private IBinder hardwareSessionToken;
2240 
SessionState(IBinder sessionToken, String inputId, ComponentName componentName, boolean isRecordingSession, ITvInputClient client, int seq, int callingUid, int userId)2241         private SessionState(IBinder sessionToken, String inputId, ComponentName componentName,
2242                 boolean isRecordingSession, ITvInputClient client, int seq, int callingUid,
2243                 int userId) {
2244             this.sessionToken = sessionToken;
2245             this.inputId = inputId;
2246             this.componentName = componentName;
2247             this.isRecordingSession = isRecordingSession;
2248             this.client = client;
2249             this.seq = seq;
2250             this.callingUid = callingUid;
2251             this.userId = userId;
2252         }
2253 
2254         @Override
binderDied()2255         public void binderDied() {
2256             synchronized (mLock) {
2257                 session = null;
2258                 clearSessionAndNotifyClientLocked(this);
2259             }
2260         }
2261     }
2262 
2263     private final class InputServiceConnection implements ServiceConnection {
2264         private final ComponentName mComponent;
2265         private final int mUserId;
2266 
InputServiceConnection(ComponentName component, int userId)2267         private InputServiceConnection(ComponentName component, int userId) {
2268             mComponent = component;
2269             mUserId = userId;
2270         }
2271 
2272         @Override
onServiceConnected(ComponentName component, IBinder service)2273         public void onServiceConnected(ComponentName component, IBinder service) {
2274             if (DEBUG) {
2275                 Slog.d(TAG, "onServiceConnected(component=" + component + ")");
2276             }
2277             synchronized (mLock) {
2278                 UserState userState = mUserStates.get(mUserId);
2279                 if (userState == null) {
2280                     // The user was removed while connecting.
2281                     mContext.unbindService(this);
2282                     return;
2283                 }
2284                 ServiceState serviceState = userState.serviceStateMap.get(mComponent);
2285                 serviceState.service = ITvInputService.Stub.asInterface(service);
2286 
2287                 // Register a callback, if we need to.
2288                 if (serviceState.isHardware && serviceState.callback == null) {
2289                     serviceState.callback = new ServiceCallback(mComponent, mUserId);
2290                     try {
2291                         serviceState.service.registerCallback(serviceState.callback);
2292                     } catch (RemoteException e) {
2293                         Slog.e(TAG, "error in registerCallback", e);
2294                     }
2295                 }
2296 
2297                 List<IBinder> tokensToBeRemoved = new ArrayList<>();
2298 
2299                 // And create sessions, if any.
2300                 for (IBinder sessionToken : serviceState.sessionTokens) {
2301                     if (!createSessionInternalLocked(serviceState.service, sessionToken, mUserId)) {
2302                         tokensToBeRemoved.add(sessionToken);
2303                     }
2304                 }
2305 
2306                 for (IBinder sessionToken : tokensToBeRemoved) {
2307                     removeSessionStateLocked(sessionToken, mUserId);
2308                 }
2309 
2310                 for (TvInputState inputState : userState.inputMap.values()) {
2311                     if (inputState.info.getComponent().equals(component)
2312                             && inputState.state != INPUT_STATE_CONNECTED) {
2313                         notifyInputStateChangedLocked(userState, inputState.info.getId(),
2314                                 inputState.state, null);
2315                     }
2316                 }
2317 
2318                 if (serviceState.isHardware) {
2319                     serviceState.hardwareInputMap.clear();
2320                     for (TvInputHardwareInfo hardware : mTvInputHardwareManager.getHardwareList()) {
2321                         try {
2322                             serviceState.service.notifyHardwareAdded(hardware);
2323                         } catch (RemoteException e) {
2324                             Slog.e(TAG, "error in notifyHardwareAdded", e);
2325                         }
2326                     }
2327                     for (HdmiDeviceInfo device : mTvInputHardwareManager.getHdmiDeviceList()) {
2328                         try {
2329                             serviceState.service.notifyHdmiDeviceAdded(device);
2330                         } catch (RemoteException e) {
2331                             Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
2332                         }
2333                     }
2334                 }
2335             }
2336         }
2337 
2338         @Override
onServiceDisconnected(ComponentName component)2339         public void onServiceDisconnected(ComponentName component) {
2340             if (DEBUG) {
2341                 Slog.d(TAG, "onServiceDisconnected(component=" + component + ")");
2342             }
2343             if (!mComponent.equals(component)) {
2344                 throw new IllegalArgumentException("Mismatched ComponentName: "
2345                         + mComponent + " (expected), " + component + " (actual).");
2346             }
2347             synchronized (mLock) {
2348                 UserState userState = getOrCreateUserStateLocked(mUserId);
2349                 ServiceState serviceState = userState.serviceStateMap.get(mComponent);
2350                 if (serviceState != null) {
2351                     serviceState.reconnecting = true;
2352                     serviceState.bound = false;
2353                     serviceState.service = null;
2354                     serviceState.callback = null;
2355 
2356                     abortPendingCreateSessionRequestsLocked(serviceState, null, mUserId);
2357                 }
2358             }
2359         }
2360     }
2361 
2362     private final class ServiceCallback extends ITvInputServiceCallback.Stub {
2363         private final ComponentName mComponent;
2364         private final int mUserId;
2365 
ServiceCallback(ComponentName component, int userId)2366         ServiceCallback(ComponentName component, int userId) {
2367             mComponent = component;
2368             mUserId = userId;
2369         }
2370 
ensureHardwarePermission()2371         private void ensureHardwarePermission() {
2372             if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
2373                     != PackageManager.PERMISSION_GRANTED) {
2374                 throw new SecurityException("The caller does not have hardware permission");
2375             }
2376         }
2377 
ensureValidInput(TvInputInfo inputInfo)2378         private void ensureValidInput(TvInputInfo inputInfo) {
2379             if (inputInfo.getId() == null || !mComponent.equals(inputInfo.getComponent())) {
2380                 throw new IllegalArgumentException("Invalid TvInputInfo");
2381             }
2382         }
2383 
addHardwareInputLocked(TvInputInfo inputInfo)2384         private void addHardwareInputLocked(TvInputInfo inputInfo) {
2385             ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
2386             serviceState.hardwareInputMap.put(inputInfo.getId(), inputInfo);
2387             buildTvInputListLocked(mUserId, null);
2388         }
2389 
addHardwareInput(int deviceId, TvInputInfo inputInfo)2390         public void addHardwareInput(int deviceId, TvInputInfo inputInfo) {
2391             ensureHardwarePermission();
2392             ensureValidInput(inputInfo);
2393             synchronized (mLock) {
2394                 mTvInputHardwareManager.addHardwareInput(deviceId, inputInfo);
2395                 addHardwareInputLocked(inputInfo);
2396             }
2397         }
2398 
addHdmiInput(int id, TvInputInfo inputInfo)2399         public void addHdmiInput(int id, TvInputInfo inputInfo) {
2400             ensureHardwarePermission();
2401             ensureValidInput(inputInfo);
2402             synchronized (mLock) {
2403                 mTvInputHardwareManager.addHdmiInput(id, inputInfo);
2404                 addHardwareInputLocked(inputInfo);
2405             }
2406         }
2407 
removeHardwareInput(String inputId)2408         public void removeHardwareInput(String inputId) {
2409             ensureHardwarePermission();
2410             synchronized (mLock) {
2411                 ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
2412                 boolean removed = serviceState.hardwareInputMap.remove(inputId) != null;
2413                 if (removed) {
2414                     buildTvInputListLocked(mUserId, null);
2415                     mTvInputHardwareManager.removeHardwareInput(inputId);
2416                 } else {
2417                     Slog.e(TAG, "failed to remove input " + inputId);
2418                 }
2419             }
2420         }
2421     }
2422 
2423     private final class SessionCallback extends ITvInputSessionCallback.Stub {
2424         private final SessionState mSessionState;
2425         private final InputChannel[] mChannels;
2426 
SessionCallback(SessionState sessionState, InputChannel[] channels)2427         SessionCallback(SessionState sessionState, InputChannel[] channels) {
2428             mSessionState = sessionState;
2429             mChannels = channels;
2430         }
2431 
2432         @Override
onSessionCreated(ITvInputSession session, IBinder hardwareSessionToken)2433         public void onSessionCreated(ITvInputSession session, IBinder hardwareSessionToken) {
2434             if (DEBUG) {
2435                 Slog.d(TAG, "onSessionCreated(inputId=" + mSessionState.inputId + ")");
2436             }
2437             synchronized (mLock) {
2438                 mSessionState.session = session;
2439                 mSessionState.hardwareSessionToken = hardwareSessionToken;
2440                 if (session != null && addSessionTokenToClientStateLocked(session)) {
2441                     sendSessionTokenToClientLocked(mSessionState.client,
2442                             mSessionState.inputId, mSessionState.sessionToken, mChannels[0],
2443                             mSessionState.seq);
2444                 } else {
2445                     removeSessionStateLocked(mSessionState.sessionToken, mSessionState.userId);
2446                     sendSessionTokenToClientLocked(mSessionState.client,
2447                             mSessionState.inputId, null, null, mSessionState.seq);
2448                 }
2449                 mChannels[0].dispose();
2450             }
2451         }
2452 
addSessionTokenToClientStateLocked(ITvInputSession session)2453         private boolean addSessionTokenToClientStateLocked(ITvInputSession session) {
2454             try {
2455                 session.asBinder().linkToDeath(mSessionState, 0);
2456             } catch (RemoteException e) {
2457                 Slog.e(TAG, "session process has already died", e);
2458                 return false;
2459             }
2460 
2461             IBinder clientToken = mSessionState.client.asBinder();
2462             UserState userState = getOrCreateUserStateLocked(mSessionState.userId);
2463             ClientState clientState = userState.clientStateMap.get(clientToken);
2464             if (clientState == null) {
2465                 clientState = new ClientState(clientToken, mSessionState.userId);
2466                 try {
2467                     clientToken.linkToDeath(clientState, 0);
2468                 } catch (RemoteException e) {
2469                     Slog.e(TAG, "client process has already died", e);
2470                     return false;
2471                 }
2472                 userState.clientStateMap.put(clientToken, clientState);
2473             }
2474             clientState.sessionTokens.add(mSessionState.sessionToken);
2475             return true;
2476         }
2477 
2478         @Override
onChannelRetuned(Uri channelUri)2479         public void onChannelRetuned(Uri channelUri) {
2480             synchronized (mLock) {
2481                 if (DEBUG) {
2482                     Slog.d(TAG, "onChannelRetuned(" + channelUri + ")");
2483                 }
2484                 if (mSessionState.session == null || mSessionState.client == null) {
2485                     return;
2486                 }
2487                 try {
2488                     // TODO: Consider adding this channel change in the watch log. When we do
2489                     // that, how we can protect the watch log from malicious tv inputs should
2490                     // be addressed. e.g. add a field which represents where the channel change
2491                     // originated from.
2492                     mSessionState.client.onChannelRetuned(channelUri, mSessionState.seq);
2493                 } catch (RemoteException e) {
2494                     Slog.e(TAG, "error in onChannelRetuned", e);
2495                 }
2496             }
2497         }
2498 
2499         @Override
onTracksChanged(List<TvTrackInfo> tracks)2500         public void onTracksChanged(List<TvTrackInfo> tracks) {
2501             synchronized (mLock) {
2502                 if (DEBUG) {
2503                     Slog.d(TAG, "onTracksChanged(" + tracks + ")");
2504                 }
2505                 if (mSessionState.session == null || mSessionState.client == null) {
2506                     return;
2507                 }
2508                 try {
2509                     mSessionState.client.onTracksChanged(tracks, mSessionState.seq);
2510                 } catch (RemoteException e) {
2511                     Slog.e(TAG, "error in onTracksChanged", e);
2512                 }
2513             }
2514         }
2515 
2516         @Override
onTrackSelected(int type, String trackId)2517         public void onTrackSelected(int type, String trackId) {
2518             synchronized (mLock) {
2519                 if (DEBUG) {
2520                     Slog.d(TAG, "onTrackSelected(type=" + type + ", trackId=" + trackId + ")");
2521                 }
2522                 if (mSessionState.session == null || mSessionState.client == null) {
2523                     return;
2524                 }
2525                 try {
2526                     mSessionState.client.onTrackSelected(type, trackId, mSessionState.seq);
2527                 } catch (RemoteException e) {
2528                     Slog.e(TAG, "error in onTrackSelected", e);
2529                 }
2530             }
2531         }
2532 
2533         @Override
onVideoAvailable()2534         public void onVideoAvailable() {
2535             synchronized (mLock) {
2536                 if (DEBUG) {
2537                     Slog.d(TAG, "onVideoAvailable()");
2538                 }
2539                 if (mSessionState.session == null || mSessionState.client == null) {
2540                     return;
2541                 }
2542                 try {
2543                     mSessionState.client.onVideoAvailable(mSessionState.seq);
2544                 } catch (RemoteException e) {
2545                     Slog.e(TAG, "error in onVideoAvailable", e);
2546                 }
2547             }
2548         }
2549 
2550         @Override
onVideoUnavailable(int reason)2551         public void onVideoUnavailable(int reason) {
2552             synchronized (mLock) {
2553                 if (DEBUG) {
2554                     Slog.d(TAG, "onVideoUnavailable(" + reason + ")");
2555                 }
2556                 if (mSessionState.session == null || mSessionState.client == null) {
2557                     return;
2558                 }
2559                 try {
2560                     mSessionState.client.onVideoUnavailable(reason, mSessionState.seq);
2561                 } catch (RemoteException e) {
2562                     Slog.e(TAG, "error in onVideoUnavailable", e);
2563                 }
2564             }
2565         }
2566 
2567         @Override
onContentAllowed()2568         public void onContentAllowed() {
2569             synchronized (mLock) {
2570                 if (DEBUG) {
2571                     Slog.d(TAG, "onContentAllowed()");
2572                 }
2573                 if (mSessionState.session == null || mSessionState.client == null) {
2574                     return;
2575                 }
2576                 try {
2577                     mSessionState.client.onContentAllowed(mSessionState.seq);
2578                 } catch (RemoteException e) {
2579                     Slog.e(TAG, "error in onContentAllowed", e);
2580                 }
2581             }
2582         }
2583 
2584         @Override
onContentBlocked(String rating)2585         public void onContentBlocked(String rating) {
2586             synchronized (mLock) {
2587                 if (DEBUG) {
2588                     Slog.d(TAG, "onContentBlocked()");
2589                 }
2590                 if (mSessionState.session == null || mSessionState.client == null) {
2591                     return;
2592                 }
2593                 try {
2594                     mSessionState.client.onContentBlocked(rating, mSessionState.seq);
2595                 } catch (RemoteException e) {
2596                     Slog.e(TAG, "error in onContentBlocked", e);
2597                 }
2598             }
2599         }
2600 
2601         @Override
onLayoutSurface(int left, int top, int right, int bottom)2602         public void onLayoutSurface(int left, int top, int right, int bottom) {
2603             synchronized (mLock) {
2604                 if (DEBUG) {
2605                     Slog.d(TAG, "onLayoutSurface (left=" + left + ", top=" + top
2606                             + ", right=" + right + ", bottom=" + bottom + ",)");
2607                 }
2608                 if (mSessionState.session == null || mSessionState.client == null) {
2609                     return;
2610                 }
2611                 try {
2612                     mSessionState.client.onLayoutSurface(left, top, right, bottom,
2613                             mSessionState.seq);
2614                 } catch (RemoteException e) {
2615                     Slog.e(TAG, "error in onLayoutSurface", e);
2616                 }
2617             }
2618         }
2619 
2620         @Override
onSessionEvent(String eventType, Bundle eventArgs)2621         public void onSessionEvent(String eventType, Bundle eventArgs) {
2622             synchronized (mLock) {
2623                 if (DEBUG) {
2624                     Slog.d(TAG, "onEvent(eventType=" + eventType + ", eventArgs=" + eventArgs
2625                             + ")");
2626                 }
2627                 if (mSessionState.session == null || mSessionState.client == null) {
2628                     return;
2629                 }
2630                 try {
2631                     mSessionState.client.onSessionEvent(eventType, eventArgs, mSessionState.seq);
2632                 } catch (RemoteException e) {
2633                     Slog.e(TAG, "error in onSessionEvent", e);
2634                 }
2635             }
2636         }
2637 
2638         @Override
onTimeShiftStatusChanged(int status)2639         public void onTimeShiftStatusChanged(int status) {
2640             synchronized (mLock) {
2641                 if (DEBUG) {
2642                     Slog.d(TAG, "onTimeShiftStatusChanged(status=" + status + ")");
2643                 }
2644                 if (mSessionState.session == null || mSessionState.client == null) {
2645                     return;
2646                 }
2647                 try {
2648                     mSessionState.client.onTimeShiftStatusChanged(status, mSessionState.seq);
2649                 } catch (RemoteException e) {
2650                     Slog.e(TAG, "error in onTimeShiftStatusChanged", e);
2651                 }
2652             }
2653         }
2654 
2655         @Override
onTimeShiftStartPositionChanged(long timeMs)2656         public void onTimeShiftStartPositionChanged(long timeMs) {
2657             synchronized (mLock) {
2658                 if (DEBUG) {
2659                     Slog.d(TAG, "onTimeShiftStartPositionChanged(timeMs=" + timeMs + ")");
2660                 }
2661                 if (mSessionState.session == null || mSessionState.client == null) {
2662                     return;
2663                 }
2664                 try {
2665                     mSessionState.client.onTimeShiftStartPositionChanged(timeMs, mSessionState.seq);
2666                 } catch (RemoteException e) {
2667                     Slog.e(TAG, "error in onTimeShiftStartPositionChanged", e);
2668                 }
2669             }
2670         }
2671 
2672         @Override
onTimeShiftCurrentPositionChanged(long timeMs)2673         public void onTimeShiftCurrentPositionChanged(long timeMs) {
2674             synchronized (mLock) {
2675                 if (DEBUG) {
2676                     Slog.d(TAG, "onTimeShiftCurrentPositionChanged(timeMs=" + timeMs + ")");
2677                 }
2678                 if (mSessionState.session == null || mSessionState.client == null) {
2679                     return;
2680                 }
2681                 try {
2682                     mSessionState.client.onTimeShiftCurrentPositionChanged(timeMs,
2683                             mSessionState.seq);
2684                 } catch (RemoteException e) {
2685                     Slog.e(TAG, "error in onTimeShiftCurrentPositionChanged", e);
2686                 }
2687             }
2688         }
2689 
2690         // For the recording session only
2691         @Override
onTuned(Uri channelUri)2692         public void onTuned(Uri channelUri) {
2693             synchronized (mLock) {
2694                 if (DEBUG) {
2695                     Slog.d(TAG, "onTuned()");
2696                 }
2697                 if (mSessionState.session == null || mSessionState.client == null) {
2698                     return;
2699                 }
2700                 try {
2701                     mSessionState.client.onTuned(mSessionState.seq, channelUri);
2702                 } catch (RemoteException e) {
2703                     Slog.e(TAG, "error in onTuned", e);
2704                 }
2705             }
2706         }
2707 
2708         // For the recording session only
2709         @Override
onRecordingStopped(Uri recordedProgramUri)2710         public void onRecordingStopped(Uri recordedProgramUri) {
2711             synchronized (mLock) {
2712                 if (DEBUG) {
2713                     Slog.d(TAG, "onRecordingStopped(recordedProgramUri=" + recordedProgramUri
2714                             + ")");
2715                 }
2716                 if (mSessionState.session == null || mSessionState.client == null) {
2717                     return;
2718                 }
2719                 try {
2720                     mSessionState.client.onRecordingStopped(recordedProgramUri, mSessionState.seq);
2721                 } catch (RemoteException e) {
2722                     Slog.e(TAG, "error in onRecordingStopped", e);
2723                 }
2724             }
2725         }
2726 
2727         // For the recording session only
2728         @Override
onError(int error)2729         public void onError(int error) {
2730             synchronized (mLock) {
2731                 if (DEBUG) {
2732                     Slog.d(TAG, "onError(error=" + error + ")");
2733                 }
2734                 if (mSessionState.session == null || mSessionState.client == null) {
2735                     return;
2736                 }
2737                 try {
2738                     mSessionState.client.onError(error, mSessionState.seq);
2739                 } catch (RemoteException e) {
2740                     Slog.e(TAG, "error in onError", e);
2741                 }
2742             }
2743         }
2744     }
2745 
2746     private static final class WatchLogHandler extends Handler {
2747         // There are only two kinds of watch events that can happen on the system:
2748         // 1. The current TV input session is tuned to a new channel.
2749         // 2. The session is released for some reason.
2750         // The former indicates the end of the previous log entry, if any, followed by the start of
2751         // a new entry. The latter indicates the end of the most recent entry for the given session.
2752         // Here the system supplies the database the smallest set of information only that is
2753         // sufficient to consolidate the log entries while minimizing database operations in the
2754         // system service.
2755         static final int MSG_LOG_WATCH_START = 1;
2756         static final int MSG_LOG_WATCH_END = 2;
2757         static final int MSG_SWITCH_CONTENT_RESOLVER = 3;
2758 
2759         private ContentResolver mContentResolver;
2760 
WatchLogHandler(ContentResolver contentResolver, Looper looper)2761         WatchLogHandler(ContentResolver contentResolver, Looper looper) {
2762             super(looper);
2763             mContentResolver = contentResolver;
2764         }
2765 
2766         @Override
handleMessage(Message msg)2767         public void handleMessage(Message msg) {
2768             switch (msg.what) {
2769                 case MSG_LOG_WATCH_START: {
2770                     SomeArgs args = (SomeArgs) msg.obj;
2771                     String packageName = (String) args.arg1;
2772                     long watchStartTime = (long) args.arg2;
2773                     long channelId = (long) args.arg3;
2774                     Bundle tuneParams = (Bundle) args.arg4;
2775                     IBinder sessionToken = (IBinder) args.arg5;
2776 
2777                     ContentValues values = new ContentValues();
2778                     values.put(TvContract.WatchedPrograms.COLUMN_PACKAGE_NAME, packageName);
2779                     values.put(TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
2780                             watchStartTime);
2781                     values.put(TvContract.WatchedPrograms.COLUMN_CHANNEL_ID, channelId);
2782                     if (tuneParams != null) {
2783                         values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_TUNE_PARAMS,
2784                                 encodeTuneParams(tuneParams));
2785                     }
2786                     values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN,
2787                             sessionToken.toString());
2788 
2789                     mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
2790                     args.recycle();
2791                     break;
2792                 }
2793                 case MSG_LOG_WATCH_END: {
2794                     SomeArgs args = (SomeArgs) msg.obj;
2795                     IBinder sessionToken = (IBinder) args.arg1;
2796                     long watchEndTime = (long) args.arg2;
2797 
2798                     ContentValues values = new ContentValues();
2799                     values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS,
2800                             watchEndTime);
2801                     values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN,
2802                             sessionToken.toString());
2803 
2804                     mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
2805                     args.recycle();
2806                     break;
2807                 }
2808                 case MSG_SWITCH_CONTENT_RESOLVER: {
2809                     mContentResolver = (ContentResolver) msg.obj;
2810                     break;
2811                 }
2812                 default: {
2813                     Slog.w(TAG, "unhandled message code: " + msg.what);
2814                     break;
2815                 }
2816             }
2817         }
2818 
encodeTuneParams(Bundle tuneParams)2819         private String encodeTuneParams(Bundle tuneParams) {
2820             StringBuilder builder = new StringBuilder();
2821             Set<String> keySet = tuneParams.keySet();
2822             Iterator<String> it = keySet.iterator();
2823             while (it.hasNext()) {
2824                 String key = it.next();
2825                 Object value = tuneParams.get(key);
2826                 if (value == null) {
2827                     continue;
2828                 }
2829                 builder.append(replaceEscapeCharacters(key));
2830                 builder.append("=");
2831                 builder.append(replaceEscapeCharacters(value.toString()));
2832                 if (it.hasNext()) {
2833                     builder.append(", ");
2834                 }
2835             }
2836             return builder.toString();
2837         }
2838 
replaceEscapeCharacters(String src)2839         private String replaceEscapeCharacters(String src) {
2840             final char ESCAPE_CHARACTER = '%';
2841             final String ENCODING_TARGET_CHARACTERS = "%=,";
2842             StringBuilder builder = new StringBuilder();
2843             for (char ch : src.toCharArray()) {
2844                 if (ENCODING_TARGET_CHARACTERS.indexOf(ch) >= 0) {
2845                     builder.append(ESCAPE_CHARACTER);
2846                 }
2847                 builder.append(ch);
2848             }
2849             return builder.toString();
2850         }
2851     }
2852 
2853     private final class HardwareListener implements TvInputHardwareManager.Listener {
2854         @Override
onStateChanged(String inputId, int state)2855         public void onStateChanged(String inputId, int state) {
2856             synchronized (mLock) {
2857                 setStateLocked(inputId, state, mCurrentUserId);
2858             }
2859         }
2860 
2861         @Override
onHardwareDeviceAdded(TvInputHardwareInfo info)2862         public void onHardwareDeviceAdded(TvInputHardwareInfo info) {
2863             synchronized (mLock) {
2864                 UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
2865                 // Broadcast the event to all hardware inputs.
2866                 for (ServiceState serviceState : userState.serviceStateMap.values()) {
2867                     if (!serviceState.isHardware || serviceState.service == null) continue;
2868                     try {
2869                         serviceState.service.notifyHardwareAdded(info);
2870                     } catch (RemoteException e) {
2871                         Slog.e(TAG, "error in notifyHardwareAdded", e);
2872                     }
2873                 }
2874             }
2875         }
2876 
2877         @Override
onHardwareDeviceRemoved(TvInputHardwareInfo info)2878         public void onHardwareDeviceRemoved(TvInputHardwareInfo info) {
2879             synchronized (mLock) {
2880                 UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
2881                 // Broadcast the event to all hardware inputs.
2882                 for (ServiceState serviceState : userState.serviceStateMap.values()) {
2883                     if (!serviceState.isHardware || serviceState.service == null) continue;
2884                     try {
2885                         serviceState.service.notifyHardwareRemoved(info);
2886                     } catch (RemoteException e) {
2887                         Slog.e(TAG, "error in notifyHardwareRemoved", e);
2888                     }
2889                 }
2890             }
2891         }
2892 
2893         @Override
onHdmiDeviceAdded(HdmiDeviceInfo deviceInfo)2894         public void onHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) {
2895             synchronized (mLock) {
2896                 UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
2897                 // Broadcast the event to all hardware inputs.
2898                 for (ServiceState serviceState : userState.serviceStateMap.values()) {
2899                     if (!serviceState.isHardware || serviceState.service == null) continue;
2900                     try {
2901                         serviceState.service.notifyHdmiDeviceAdded(deviceInfo);
2902                     } catch (RemoteException e) {
2903                         Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
2904                     }
2905                 }
2906             }
2907         }
2908 
2909         @Override
onHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo)2910         public void onHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo) {
2911             synchronized (mLock) {
2912                 UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
2913                 // Broadcast the event to all hardware inputs.
2914                 for (ServiceState serviceState : userState.serviceStateMap.values()) {
2915                     if (!serviceState.isHardware || serviceState.service == null) continue;
2916                     try {
2917                         serviceState.service.notifyHdmiDeviceRemoved(deviceInfo);
2918                     } catch (RemoteException e) {
2919                         Slog.e(TAG, "error in notifyHdmiDeviceRemoved", e);
2920                     }
2921                 }
2922             }
2923         }
2924 
2925         @Override
onHdmiDeviceUpdated(String inputId, HdmiDeviceInfo deviceInfo)2926         public void onHdmiDeviceUpdated(String inputId, HdmiDeviceInfo deviceInfo) {
2927             synchronized (mLock) {
2928                 Integer state;
2929                 switch (deviceInfo.getDevicePowerStatus()) {
2930                     case HdmiControlManager.POWER_STATUS_ON:
2931                         state = INPUT_STATE_CONNECTED;
2932                         break;
2933                     case HdmiControlManager.POWER_STATUS_STANDBY:
2934                     case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON:
2935                     case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY:
2936                         state = INPUT_STATE_CONNECTED_STANDBY;
2937                         break;
2938                     case HdmiControlManager.POWER_STATUS_UNKNOWN:
2939                     default:
2940                         state = null;
2941                         break;
2942                 }
2943                 if (state != null) {
2944                     setStateLocked(inputId, state, mCurrentUserId);
2945                 }
2946                 UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
2947                 // Broadcast the event to all hardware inputs.
2948                 for (ServiceState serviceState : userState.serviceStateMap.values()) {
2949                     if (!serviceState.isHardware || serviceState.service == null) continue;
2950                     try {
2951                         serviceState.service.notifyHdmiDeviceUpdated(deviceInfo);
2952                     } catch (RemoteException e) {
2953                         Slog.e(TAG, "error in notifyHdmiDeviceUpdated", e);
2954                     }
2955                 }
2956             }
2957         }
2958     }
2959 
2960     private static class SessionNotFoundException extends IllegalArgumentException {
SessionNotFoundException(String name)2961         public SessionNotFoundException(String name) {
2962             super(name);
2963         }
2964     }
2965 }
2966