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