Home | History | Annotate | Download | only in tv
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server.tv;
     18 
     19 import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
     20 import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED_STANDBY;
     21 import static android.media.tv.TvInputManager.INPUT_STATE_DISCONNECTED;
     22 import static android.media.tv.TvInputManager.INPUT_STATE_UNKNOWN;
     23 
     24 import android.app.ActivityManager;
     25 import android.content.BroadcastReceiver;
     26 import android.content.ComponentName;
     27 import android.content.ContentProviderOperation;
     28 import android.content.ContentProviderResult;
     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.OperationApplicationException;
     36 import android.content.ServiceConnection;
     37 import android.content.pm.ActivityInfo;
     38 import android.content.pm.PackageManager;
     39 import android.content.pm.ResolveInfo;
     40 import android.content.pm.ServiceInfo;
     41 import android.graphics.Rect;
     42 import android.hardware.hdmi.HdmiControlManager;
     43 import android.hardware.hdmi.HdmiDeviceInfo;
     44 import android.media.tv.ITvInputClient;
     45 import android.media.tv.ITvInputHardware;
     46 import android.media.tv.ITvInputHardwareCallback;
     47 import android.media.tv.ITvInputManager;
     48 import android.media.tv.ITvInputManagerCallback;
     49 import android.media.tv.ITvInputService;
     50 import android.media.tv.ITvInputServiceCallback;
     51 import android.media.tv.ITvInputSession;
     52 import android.media.tv.ITvInputSessionCallback;
     53 import android.media.tv.TvContentRating;
     54 import android.media.tv.TvContentRatingSystemInfo;
     55 import android.media.tv.TvContract;
     56 import android.media.tv.TvInputHardwareInfo;
     57 import android.media.tv.TvInputInfo;
     58 import android.media.tv.TvInputManager;
     59 import android.media.tv.TvInputService;
     60 import android.media.tv.TvStreamConfig;
     61 import android.media.tv.TvTrackInfo;
     62 import android.net.Uri;
     63 import android.os.Binder;
     64 import android.os.Bundle;
     65 import android.os.Handler;
     66 import android.os.IBinder;
     67 import android.os.Looper;
     68 import android.os.Message;
     69 import android.os.Process;
     70 import android.os.RemoteException;
     71 import android.os.UserHandle;
     72 import android.util.Slog;
     73 import android.util.SparseArray;
     74 import android.view.InputChannel;
     75 import android.view.Surface;
     76 
     77 import com.android.internal.content.PackageMonitor;
     78 import com.android.internal.os.SomeArgs;
     79 import com.android.internal.util.IndentingPrintWriter;
     80 import com.android.server.IoThread;
     81 import com.android.server.SystemService;
     82 
     83 import org.xmlpull.v1.XmlPullParserException;
     84 
     85 import java.io.FileDescriptor;
     86 import java.io.IOException;
     87 import java.io.PrintWriter;
     88 import java.util.ArrayList;
     89 import java.util.Arrays;
     90 import java.util.HashMap;
     91 import java.util.HashSet;
     92 import java.util.Iterator;
     93 import java.util.List;
     94 import java.util.Map;
     95 import java.util.Set;
     96 
     97 /** This class provides a system service that manages television inputs. */
     98 public final class TvInputManagerService extends SystemService {
     99     private static final boolean DEBUG = false;
    100     private static final String TAG = "TvInputManagerService";
    101 
    102     private final Context mContext;
    103     private final TvInputHardwareManager mTvInputHardwareManager;
    104 
    105     private final ContentResolver mContentResolver;
    106 
    107     // A global lock.
    108     private final Object mLock = new Object();
    109 
    110     // ID of the current user.
    111     private int mCurrentUserId = UserHandle.USER_OWNER;
    112 
    113     // A map from user id to UserState.
    114     private final SparseArray<UserState> mUserStates = new SparseArray<UserState>();
    115 
    116     private final WatchLogHandler mWatchLogHandler;
    117 
    118     public TvInputManagerService(Context context) {
    119         super(context);
    120 
    121         mContext = context;
    122         mContentResolver = context.getContentResolver();
    123         mWatchLogHandler = new WatchLogHandler(mContentResolver, IoThread.get().getLooper());
    124 
    125         mTvInputHardwareManager = new TvInputHardwareManager(context, new HardwareListener());
    126 
    127         synchronized (mLock) {
    128             mUserStates.put(mCurrentUserId, new UserState(mContext, mCurrentUserId));
    129         }
    130     }
    131 
    132     @Override
    133     public void onStart() {
    134         publishBinderService(Context.TV_INPUT_SERVICE, new BinderService());
    135     }
    136 
    137     @Override
    138     public void onBootPhase(int phase) {
    139         if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
    140             registerBroadcastReceivers();
    141         } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
    142             synchronized (mLock) {
    143                 buildTvInputListLocked(mCurrentUserId, null);
    144                 buildTvContentRatingSystemListLocked(mCurrentUserId);
    145             }
    146         }
    147         mTvInputHardwareManager.onBootPhase(phase);
    148     }
    149 
    150     private void registerBroadcastReceivers() {
    151         PackageMonitor monitor = new PackageMonitor() {
    152             private void buildTvInputList(String[] packages) {
    153                 synchronized (mLock) {
    154                     buildTvInputListLocked(getChangingUserId(), packages);
    155                     buildTvContentRatingSystemListLocked(getChangingUserId());
    156                 }
    157             }
    158 
    159             @Override
    160             public void onPackageUpdateFinished(String packageName, int uid) {
    161                 if (DEBUG) Slog.d(TAG, "onPackageUpdateFinished(packageName=" + packageName + ")");
    162                 // This callback is invoked when the TV input is reinstalled.
    163                 // In this case, isReplacing() always returns true.
    164                 buildTvInputList(new String[] { packageName });
    165             }
    166 
    167             @Override
    168             public void onPackagesAvailable(String[] packages) {
    169                 if (DEBUG) {
    170                     Slog.d(TAG, "onPackagesAvailable(packages=" + Arrays.toString(packages) + ")");
    171                 }
    172                 // This callback is invoked when the media on which some packages exist become
    173                 // available.
    174                 if (isReplacing()) {
    175                     buildTvInputList(packages);
    176                 }
    177             }
    178 
    179             @Override
    180             public void onPackagesUnavailable(String[] packages) {
    181                 // This callback is invoked when the media on which some packages exist become
    182                 // unavailable.
    183                 if (DEBUG)  {
    184                     Slog.d(TAG, "onPackagesUnavailable(packages=" + Arrays.toString(packages)
    185                             + ")");
    186                 }
    187                 if (isReplacing()) {
    188                     buildTvInputList(packages);
    189                 }
    190             }
    191 
    192             @Override
    193             public void onSomePackagesChanged() {
    194                 // TODO: Use finer-grained methods(e.g. onPackageAdded, onPackageRemoved) to manage
    195                 // the TV inputs.
    196                 if (DEBUG) Slog.d(TAG, "onSomePackagesChanged()");
    197                 if (isReplacing()) {
    198                     if (DEBUG) Slog.d(TAG, "Skipped building TV input list due to replacing");
    199                     // When the package is updated, buildTvInputListLocked is called in other
    200                     // methods instead.
    201                     return;
    202                 }
    203                 buildTvInputList(null);
    204             }
    205 
    206             @Override
    207             public void onPackageRemoved(String packageName, int uid) {
    208                 synchronized (mLock) {
    209                     UserState userState = getUserStateLocked(getChangingUserId());
    210                     if (!userState.packageSet.contains(packageName)) {
    211                         // Not a TV input package.
    212                         return;
    213                     }
    214                 }
    215 
    216                 ArrayList<ContentProviderOperation> operations =
    217                         new ArrayList<ContentProviderOperation>();
    218 
    219                 String selection = TvContract.BaseTvColumns.COLUMN_PACKAGE_NAME + "=?";
    220                 String[] selectionArgs = { packageName };
    221 
    222                 operations.add(ContentProviderOperation.newDelete(TvContract.Channels.CONTENT_URI)
    223                         .withSelection(selection, selectionArgs).build());
    224                 operations.add(ContentProviderOperation.newDelete(TvContract.Programs.CONTENT_URI)
    225                         .withSelection(selection, selectionArgs).build());
    226                 operations.add(ContentProviderOperation
    227                         .newDelete(TvContract.WatchedPrograms.CONTENT_URI)
    228                         .withSelection(selection, selectionArgs).build());
    229 
    230                 ContentProviderResult[] results = null;
    231                 try {
    232                     results = mContentResolver.applyBatch(TvContract.AUTHORITY, operations);
    233                 } catch (RemoteException | OperationApplicationException e) {
    234                     Slog.e(TAG, "error in applyBatch", e);
    235                 }
    236 
    237                 if (DEBUG) {
    238                     Slog.d(TAG, "onPackageRemoved(packageName=" + packageName + ", uid=" + uid
    239                             + ")");
    240                     Slog.d(TAG, "results=" + results);
    241                 }
    242             }
    243         };
    244         monitor.register(mContext, null, UserHandle.ALL, true);
    245 
    246         IntentFilter intentFilter = new IntentFilter();
    247         intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
    248         intentFilter.addAction(Intent.ACTION_USER_REMOVED);
    249         mContext.registerReceiverAsUser(new BroadcastReceiver() {
    250             @Override
    251             public void onReceive(Context context, Intent intent) {
    252                 String action = intent.getAction();
    253                 if (Intent.ACTION_USER_SWITCHED.equals(action)) {
    254                     switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
    255                 } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
    256                     removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
    257                 }
    258             }
    259         }, UserHandle.ALL, intentFilter, null, null);
    260     }
    261 
    262     private static boolean hasHardwarePermission(PackageManager pm, ComponentName component) {
    263         return pm.checkPermission(android.Manifest.permission.TV_INPUT_HARDWARE,
    264                 component.getPackageName()) == PackageManager.PERMISSION_GRANTED;
    265     }
    266 
    267     private void buildTvInputListLocked(int userId, String[] updatedPackages) {
    268         UserState userState = getUserStateLocked(userId);
    269         userState.packageSet.clear();
    270 
    271         if (DEBUG) Slog.d(TAG, "buildTvInputList");
    272         PackageManager pm = mContext.getPackageManager();
    273         List<ResolveInfo> services = pm.queryIntentServices(
    274                 new Intent(TvInputService.SERVICE_INTERFACE),
    275                 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
    276         List<TvInputInfo> inputList = new ArrayList<TvInputInfo>();
    277         for (ResolveInfo ri : services) {
    278             ServiceInfo si = ri.serviceInfo;
    279             if (!android.Manifest.permission.BIND_TV_INPUT.equals(si.permission)) {
    280                 Slog.w(TAG, "Skipping TV input " + si.name + ": it does not require the permission "
    281                         + android.Manifest.permission.BIND_TV_INPUT);
    282                 continue;
    283             }
    284 
    285             ComponentName component = new ComponentName(si.packageName, si.name);
    286             if (hasHardwarePermission(pm, component)) {
    287                 ServiceState serviceState = userState.serviceStateMap.get(component);
    288                 if (serviceState == null) {
    289                     // We see this hardware TV input service for the first time; we need to
    290                     // prepare the ServiceState object so that we can connect to the service and
    291                     // let it add TvInputInfo objects to mInputList if there's any.
    292                     serviceState = new ServiceState(component, userId);
    293                     userState.serviceStateMap.put(component, serviceState);
    294                     updateServiceConnectionLocked(component, userId);
    295                 } else {
    296                     inputList.addAll(serviceState.inputList);
    297                 }
    298             } else {
    299                 try {
    300                     inputList.add(TvInputInfo.createTvInputInfo(mContext, ri));
    301                 } catch (XmlPullParserException | IOException e) {
    302                     Slog.e(TAG, "failed to load TV input " + si.name, e);
    303                     continue;
    304                 }
    305             }
    306             userState.packageSet.add(si.packageName);
    307         }
    308 
    309         Map<String, TvInputState> inputMap = new HashMap<String, TvInputState>();
    310         for (TvInputInfo info : inputList) {
    311             if (DEBUG) {
    312                 Slog.d(TAG, "add " + info.getId());
    313             }
    314             TvInputState state = userState.inputMap.get(info.getId());
    315             if (state == null) {
    316                 state = new TvInputState();
    317             }
    318             state.info = info;
    319             inputMap.put(info.getId(), state);
    320         }
    321 
    322         for (String inputId : inputMap.keySet()) {
    323             if (!userState.inputMap.containsKey(inputId)) {
    324                 notifyInputAddedLocked(userState, inputId);
    325             } else if (updatedPackages != null) {
    326                 // Notify the package updates
    327                 ComponentName component = inputMap.get(inputId).info.getComponent();
    328                 for (String updatedPackage : updatedPackages) {
    329                     if (component.getPackageName().equals(updatedPackage)) {
    330                         updateServiceConnectionLocked(component, userId);
    331                         notifyInputUpdatedLocked(userState, inputId);
    332                         break;
    333                     }
    334                 }
    335             }
    336         }
    337 
    338         for (String inputId : userState.inputMap.keySet()) {
    339             if (!inputMap.containsKey(inputId)) {
    340                 TvInputInfo info = userState.inputMap.get(inputId).info;
    341                 ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
    342                 if (serviceState != null) {
    343                     abortPendingCreateSessionRequestsLocked(serviceState, inputId, userId);
    344                 }
    345                 notifyInputRemovedLocked(userState, inputId);
    346             }
    347         }
    348 
    349         userState.inputMap.clear();
    350         userState.inputMap = inputMap;
    351     }
    352 
    353     private void buildTvContentRatingSystemListLocked(int userId) {
    354         UserState userState = getUserStateLocked(userId);
    355         userState.contentRatingSystemList.clear();
    356 
    357         final PackageManager pm = mContext.getPackageManager();
    358         Intent intent = new Intent(TvInputManager.ACTION_QUERY_CONTENT_RATING_SYSTEMS);
    359         for (ResolveInfo resolveInfo :
    360                 pm.queryBroadcastReceivers(intent, PackageManager.GET_META_DATA)) {
    361             ActivityInfo receiver = resolveInfo.activityInfo;
    362             Bundle metaData = receiver.metaData;
    363             if (metaData == null) {
    364                 continue;
    365             }
    366 
    367             int xmlResId = metaData.getInt(TvInputManager.META_DATA_CONTENT_RATING_SYSTEMS);
    368             if (xmlResId == 0) {
    369                 Slog.w(TAG, "Missing meta-data '"
    370                         + TvInputManager.META_DATA_CONTENT_RATING_SYSTEMS + "' on receiver "
    371                         + receiver.packageName + "/" + receiver.name);
    372                 continue;
    373             }
    374             userState.contentRatingSystemList.add(
    375                     TvContentRatingSystemInfo.createTvContentRatingSystemInfo(xmlResId,
    376                             receiver.applicationInfo));
    377         }
    378     }
    379 
    380     private void switchUser(int userId) {
    381         synchronized (mLock) {
    382             if (mCurrentUserId == userId) {
    383                 return;
    384             }
    385             // final int oldUserId = mCurrentUserId;
    386             // TODO: Release services and sessions in the old user state, if needed.
    387             mCurrentUserId = userId;
    388 
    389             UserState userState = mUserStates.get(userId);
    390             if (userState == null) {
    391                 userState = new UserState(mContext, userId);
    392             }
    393             mUserStates.put(userId, userState);
    394             buildTvInputListLocked(userId, null);
    395             buildTvContentRatingSystemListLocked(userId);
    396         }
    397     }
    398 
    399     private void removeUser(int userId) {
    400         synchronized (mLock) {
    401             UserState userState = mUserStates.get(userId);
    402             if (userState == null) {
    403                 return;
    404             }
    405             // Release created sessions.
    406             for (SessionState state : userState.sessionStateMap.values()) {
    407                 if (state.session != null) {
    408                     try {
    409                         state.session.release();
    410                     } catch (RemoteException e) {
    411                         Slog.e(TAG, "error in release", e);
    412                     }
    413                 }
    414             }
    415             userState.sessionStateMap.clear();
    416 
    417             // Unregister all callbacks and unbind all services.
    418             for (ServiceState serviceState : userState.serviceStateMap.values()) {
    419                 if (serviceState.callback != null) {
    420                     try {
    421                         serviceState.service.unregisterCallback(serviceState.callback);
    422                     } catch (RemoteException e) {
    423                         Slog.e(TAG, "error in unregisterCallback", e);
    424                     }
    425                 }
    426                 mContext.unbindService(serviceState.connection);
    427             }
    428             userState.serviceStateMap.clear();
    429 
    430             // Clear everything else.
    431             userState.inputMap.clear();
    432             userState.packageSet.clear();
    433             userState.contentRatingSystemList.clear();
    434             userState.clientStateMap.clear();
    435             userState.callbackSet.clear();
    436             userState.mainSessionToken = null;
    437 
    438             mUserStates.remove(userId);
    439         }
    440     }
    441 
    442     private UserState getUserStateLocked(int userId) {
    443         UserState userState = mUserStates.get(userId);
    444         if (userState == null) {
    445             throw new IllegalStateException("User state not found for user ID " + userId);
    446         }
    447         return userState;
    448     }
    449 
    450     private ServiceState getServiceStateLocked(ComponentName component, int userId) {
    451         UserState userState = getUserStateLocked(userId);
    452         ServiceState serviceState = userState.serviceStateMap.get(component);
    453         if (serviceState == null) {
    454             throw new IllegalStateException("Service state not found for " + component + " (userId="
    455                     + userId + ")");
    456         }
    457         return serviceState;
    458     }
    459 
    460     private SessionState getSessionStateLocked(IBinder sessionToken, int callingUid, int userId) {
    461         UserState userState = getUserStateLocked(userId);
    462         SessionState sessionState = userState.sessionStateMap.get(sessionToken);
    463         if (sessionState == null) {
    464             throw new SessionNotFoundException("Session state not found for token " + sessionToken);
    465         }
    466         // Only the application that requested this session or the system can access it.
    467         if (callingUid != Process.SYSTEM_UID && callingUid != sessionState.callingUid) {
    468             throw new SecurityException("Illegal access to the session with token " + sessionToken
    469                     + " from uid " + callingUid);
    470         }
    471         return sessionState;
    472     }
    473 
    474     private ITvInputSession getSessionLocked(IBinder sessionToken, int callingUid, int userId) {
    475         return getSessionLocked(getSessionStateLocked(sessionToken, callingUid, userId));
    476     }
    477 
    478     private ITvInputSession getSessionLocked(SessionState sessionState) {
    479         ITvInputSession session = sessionState.session;
    480         if (session == null) {
    481             throw new IllegalStateException("Session not yet created for token "
    482                     + sessionState.sessionToken);
    483         }
    484         return session;
    485     }
    486 
    487     private int resolveCallingUserId(int callingPid, int callingUid, int requestedUserId,
    488             String methodName) {
    489         return ActivityManager.handleIncomingUser(callingPid, callingUid, requestedUserId, false,
    490                 false, methodName, null);
    491     }
    492 
    493     private static boolean shouldMaintainConnection(ServiceState serviceState) {
    494         return !serviceState.sessionTokens.isEmpty() || serviceState.isHardware;
    495         // TODO: Find a way to maintain connection to hardware TV input service only when necessary.
    496     }
    497 
    498     private void updateServiceConnectionLocked(ComponentName component, int userId) {
    499         UserState userState = getUserStateLocked(userId);
    500         ServiceState serviceState = userState.serviceStateMap.get(component);
    501         if (serviceState == null) {
    502             return;
    503         }
    504         if (serviceState.reconnecting) {
    505             if (!serviceState.sessionTokens.isEmpty()) {
    506                 // wait until all the sessions are removed.
    507                 return;
    508             }
    509             serviceState.reconnecting = false;
    510         }
    511         boolean maintainConnection = shouldMaintainConnection(serviceState);
    512         if (serviceState.service == null && maintainConnection && userId == mCurrentUserId) {
    513             // This means that the service is not yet connected but its state indicates that we
    514             // have pending requests. Then, connect the service.
    515             if (serviceState.bound) {
    516                 // We have already bound to the service so we don't try to bind again until after we
    517                 // unbind later on.
    518                 return;
    519             }
    520             if (DEBUG) {
    521                 Slog.d(TAG, "bindServiceAsUser(service=" + component + ", userId=" + userId + ")");
    522             }
    523 
    524             Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(component);
    525             serviceState.bound = mContext.bindServiceAsUser(
    526                     i, serviceState.connection, Context.BIND_AUTO_CREATE, new UserHandle(userId));
    527         } else if (serviceState.service != null && !maintainConnection) {
    528             // This means that the service is already connected but its state indicates that we have
    529             // nothing to do with it. Then, disconnect the service.
    530             if (DEBUG) {
    531                 Slog.d(TAG, "unbindService(service=" + component + ")");
    532             }
    533             mContext.unbindService(serviceState.connection);
    534             userState.serviceStateMap.remove(component);
    535         }
    536     }
    537 
    538     private void abortPendingCreateSessionRequestsLocked(ServiceState serviceState,
    539             String inputId, int userId) {
    540         // Let clients know the create session requests are failed.
    541         UserState userState = getUserStateLocked(userId);
    542         List<SessionState> sessionsToAbort = new ArrayList<>();
    543         for (IBinder sessionToken : serviceState.sessionTokens) {
    544             SessionState sessionState = userState.sessionStateMap.get(sessionToken);
    545             if (sessionState.session == null && (inputId == null
    546                     || sessionState.info.getId().equals(inputId))) {
    547                 sessionsToAbort.add(sessionState);
    548             }
    549         }
    550         for (SessionState sessionState : sessionsToAbort) {
    551             removeSessionStateLocked(sessionState.sessionToken, sessionState.userId);
    552             sendSessionTokenToClientLocked(sessionState.client,
    553                     sessionState.info.getId(), null, null, sessionState.seq);
    554         }
    555         updateServiceConnectionLocked(serviceState.component, userId);
    556     }
    557 
    558     private void createSessionInternalLocked(ITvInputService service, IBinder sessionToken,
    559             int userId) {
    560         UserState userState = getUserStateLocked(userId);
    561         SessionState sessionState = userState.sessionStateMap.get(sessionToken);
    562         if (DEBUG) {
    563             Slog.d(TAG, "createSessionInternalLocked(inputId=" + sessionState.info.getId() + ")");
    564         }
    565         InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString());
    566 
    567         // Set up a callback to send the session token.
    568         ITvInputSessionCallback callback = new SessionCallback(sessionState, channels);
    569 
    570         // Create a session. When failed, send a null token immediately.
    571         try {
    572             service.createSession(channels[1], callback, sessionState.info.getId());
    573         } catch (RemoteException e) {
    574             Slog.e(TAG, "error in createSession", e);
    575             removeSessionStateLocked(sessionToken, userId);
    576             sendSessionTokenToClientLocked(sessionState.client, sessionState.info.getId(), null,
    577                     null, sessionState.seq);
    578         }
    579         channels[1].dispose();
    580     }
    581 
    582     private void sendSessionTokenToClientLocked(ITvInputClient client, String inputId,
    583             IBinder sessionToken, InputChannel channel, int seq) {
    584         try {
    585             client.onSessionCreated(inputId, sessionToken, channel, seq);
    586         } catch (RemoteException e) {
    587             Slog.e(TAG, "error in onSessionCreated", e);
    588         }
    589     }
    590 
    591     private void releaseSessionLocked(IBinder sessionToken, int callingUid, int userId) {
    592         SessionState sessionState = null;
    593         try {
    594             sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
    595             if (sessionState.session != null) {
    596                 UserState userState = getUserStateLocked(userId);
    597                 if (sessionToken == userState.mainSessionToken) {
    598                     setMainLocked(sessionToken, false, callingUid, userId);
    599                 }
    600                 sessionState.session.release();
    601             }
    602         } catch (RemoteException | SessionNotFoundException e) {
    603             Slog.e(TAG, "error in releaseSession", e);
    604         } finally {
    605             if (sessionState != null) {
    606                 sessionState.session = null;
    607             }
    608         }
    609         removeSessionStateLocked(sessionToken, userId);
    610     }
    611 
    612     private void removeSessionStateLocked(IBinder sessionToken, int userId) {
    613         UserState userState = getUserStateLocked(userId);
    614         if (sessionToken == userState.mainSessionToken) {
    615             if (DEBUG) {
    616                 Slog.d(TAG, "mainSessionToken=null");
    617             }
    618             userState.mainSessionToken = null;
    619         }
    620 
    621         // Remove the session state from the global session state map of the current user.
    622         SessionState sessionState = userState.sessionStateMap.remove(sessionToken);
    623 
    624         if (sessionState == null) {
    625             return;
    626         }
    627 
    628         // Also remove the session token from the session token list of the current client and
    629         // service.
    630         ClientState clientState = userState.clientStateMap.get(sessionState.client.asBinder());
    631         if (clientState != null) {
    632             clientState.sessionTokens.remove(sessionToken);
    633             if (clientState.isEmpty()) {
    634                 userState.clientStateMap.remove(sessionState.client.asBinder());
    635             }
    636         }
    637 
    638         TvInputInfo info = sessionState.info;
    639         if (info != null) {
    640             ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
    641             if (serviceState != null) {
    642                 serviceState.sessionTokens.remove(sessionToken);
    643             }
    644         }
    645         updateServiceConnectionLocked(sessionState.info.getComponent(), userId);
    646 
    647         // Log the end of watch.
    648         SomeArgs args = SomeArgs.obtain();
    649         args.arg1 = sessionToken;
    650         args.arg2 = System.currentTimeMillis();
    651         mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_LOG_WATCH_END, args).sendToTarget();
    652     }
    653 
    654     private void setMainLocked(IBinder sessionToken, boolean isMain, int callingUid, int userId) {
    655         try {
    656             SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
    657             if (sessionState.hardwareSessionToken != null) {
    658                 sessionState = getSessionStateLocked(sessionState.hardwareSessionToken,
    659                         Process.SYSTEM_UID, userId);
    660             }
    661             ServiceState serviceState = getServiceStateLocked(sessionState.info.getComponent(), userId);
    662             if (!serviceState.isHardware) {
    663                 return;
    664             }
    665             ITvInputSession session = getSessionLocked(sessionState);
    666             session.setMain(isMain);
    667         } catch (RemoteException | SessionNotFoundException e) {
    668             Slog.e(TAG, "error in setMain", e);
    669         }
    670     }
    671 
    672     private void notifyInputAddedLocked(UserState userState, String inputId) {
    673         if (DEBUG) {
    674             Slog.d(TAG, "notifyInputAddedLocked(inputId=" + inputId + ")");
    675         }
    676         for (ITvInputManagerCallback callback : userState.callbackSet) {
    677             try {
    678                 callback.onInputAdded(inputId);
    679             } catch (RemoteException e) {
    680                 Slog.e(TAG, "failed to report added input to callback", e);
    681             }
    682         }
    683     }
    684 
    685     private void notifyInputRemovedLocked(UserState userState, String inputId) {
    686         if (DEBUG) {
    687             Slog.d(TAG, "notifyInputRemovedLocked(inputId=" + inputId + ")");
    688         }
    689         for (ITvInputManagerCallback callback : userState.callbackSet) {
    690             try {
    691                 callback.onInputRemoved(inputId);
    692             } catch (RemoteException e) {
    693                 Slog.e(TAG, "failed to report removed input to callback", e);
    694             }
    695         }
    696     }
    697 
    698     private void notifyInputUpdatedLocked(UserState userState, String inputId) {
    699         if (DEBUG) {
    700             Slog.d(TAG, "notifyInputUpdatedLocked(inputId=" + inputId + ")");
    701         }
    702         for (ITvInputManagerCallback callback : userState.callbackSet) {
    703             try {
    704                 callback.onInputUpdated(inputId);
    705             } catch (RemoteException e) {
    706                 Slog.e(TAG, "failed to report updated input to callback", e);
    707             }
    708         }
    709     }
    710 
    711     private void notifyInputStateChangedLocked(UserState userState, String inputId,
    712             int state, ITvInputManagerCallback targetCallback) {
    713         if (DEBUG) {
    714             Slog.d(TAG, "notifyInputStateChangedLocked(inputId=" + inputId
    715                     + ", state=" + state + ")");
    716         }
    717         if (targetCallback == null) {
    718             for (ITvInputManagerCallback callback : userState.callbackSet) {
    719                 try {
    720                     callback.onInputStateChanged(inputId, state);
    721                 } catch (RemoteException e) {
    722                     Slog.e(TAG, "failed to report state change to callback", e);
    723                 }
    724             }
    725         } else {
    726             try {
    727                 targetCallback.onInputStateChanged(inputId, state);
    728             } catch (RemoteException e) {
    729                 Slog.e(TAG, "failed to report state change to callback", e);
    730             }
    731         }
    732     }
    733 
    734     private void setStateLocked(String inputId, int state, int userId) {
    735         UserState userState = getUserStateLocked(userId);
    736         TvInputState inputState = userState.inputMap.get(inputId);
    737         ServiceState serviceState = userState.serviceStateMap.get(inputState.info.getComponent());
    738         int oldState = inputState.state;
    739         inputState.state = state;
    740         if (serviceState != null && serviceState.service == null
    741                 && shouldMaintainConnection(serviceState)) {
    742             // We don't notify state change while reconnecting. It should remain disconnected.
    743             return;
    744         }
    745         if (oldState != state) {
    746             notifyInputStateChangedLocked(userState, inputId, state, null);
    747         }
    748     }
    749 
    750     private final class BinderService extends ITvInputManager.Stub {
    751         @Override
    752         public List<TvInputInfo> getTvInputList(int userId) {
    753             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
    754                     Binder.getCallingUid(), userId, "getTvInputList");
    755             final long identity = Binder.clearCallingIdentity();
    756             try {
    757                 synchronized (mLock) {
    758                     UserState userState = getUserStateLocked(resolvedUserId);
    759                     List<TvInputInfo> inputList = new ArrayList<TvInputInfo>();
    760                     for (TvInputState state : userState.inputMap.values()) {
    761                         inputList.add(state.info);
    762                     }
    763                     return inputList;
    764                 }
    765             } finally {
    766                 Binder.restoreCallingIdentity(identity);
    767             }
    768         }
    769 
    770         @Override
    771         public TvInputInfo getTvInputInfo(String inputId, int userId) {
    772             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
    773                     Binder.getCallingUid(), userId, "getTvInputInfo");
    774             final long identity = Binder.clearCallingIdentity();
    775             try {
    776                 synchronized (mLock) {
    777                     UserState userState = getUserStateLocked(resolvedUserId);
    778                     TvInputState state = userState.inputMap.get(inputId);
    779                     return state == null ? null : state.info;
    780                 }
    781             } finally {
    782                 Binder.restoreCallingIdentity(identity);
    783             }
    784         }
    785 
    786         @Override
    787         public int getTvInputState(String inputId, int userId) {
    788             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
    789                     Binder.getCallingUid(), userId, "getTvInputState");
    790             final long identity = Binder.clearCallingIdentity();
    791             try {
    792                 synchronized (mLock) {
    793                     UserState userState = getUserStateLocked(resolvedUserId);
    794                     TvInputState state = userState.inputMap.get(inputId);
    795                     return state == null ? INPUT_STATE_UNKNOWN : state.state;
    796                 }
    797             } finally {
    798                 Binder.restoreCallingIdentity(identity);
    799             }
    800         }
    801 
    802         @Override
    803         public List<TvContentRatingSystemInfo> getTvContentRatingSystemList(int userId) {
    804             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
    805                     Binder.getCallingUid(), userId, "getTvContentRatingSystemList");
    806             final long identity = Binder.clearCallingIdentity();
    807             try {
    808                 synchronized (mLock) {
    809                     UserState userState = getUserStateLocked(resolvedUserId);
    810                     return userState.contentRatingSystemList;
    811                 }
    812             } finally {
    813                 Binder.restoreCallingIdentity(identity);
    814             }
    815         }
    816 
    817         @Override
    818         public void registerCallback(final ITvInputManagerCallback callback, int userId) {
    819             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
    820                     Binder.getCallingUid(), userId, "registerCallback");
    821             final long identity = Binder.clearCallingIdentity();
    822             try {
    823                 synchronized (mLock) {
    824                     final UserState userState = getUserStateLocked(resolvedUserId);
    825                     userState.callbackSet.add(callback);
    826                     try {
    827                         callback.asBinder().linkToDeath(new IBinder.DeathRecipient() {
    828                             @Override
    829                             public void binderDied() {
    830                                 synchronized (mLock) {
    831                                     if (userState.callbackSet != null) {
    832                                         userState.callbackSet.remove(callback);
    833                                     }
    834                                 }
    835                             }
    836                         }, 0);
    837                     } catch (RemoteException e) {
    838                         Slog.e(TAG, "client process has already died", e);
    839                     }
    840                 }
    841             } finally {
    842                 Binder.restoreCallingIdentity(identity);
    843             }
    844         }
    845 
    846         @Override
    847         public void unregisterCallback(ITvInputManagerCallback callback, int userId) {
    848             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
    849                     Binder.getCallingUid(), userId, "unregisterCallback");
    850             final long identity = Binder.clearCallingIdentity();
    851             try {
    852                 synchronized (mLock) {
    853                     UserState userState = getUserStateLocked(resolvedUserId);
    854                     userState.callbackSet.remove(callback);
    855                 }
    856             } finally {
    857                 Binder.restoreCallingIdentity(identity);
    858             }
    859         }
    860 
    861         @Override
    862         public boolean isParentalControlsEnabled(int userId) {
    863             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
    864                     Binder.getCallingUid(), userId, "isParentalControlsEnabled");
    865             final long identity = Binder.clearCallingIdentity();
    866             try {
    867                 synchronized (mLock) {
    868                     UserState userState = getUserStateLocked(resolvedUserId);
    869                     return userState.persistentDataStore.isParentalControlsEnabled();
    870                 }
    871             } finally {
    872                 Binder.restoreCallingIdentity(identity);
    873             }
    874         }
    875 
    876         @Override
    877         public void setParentalControlsEnabled(boolean enabled, int userId) {
    878             ensureParentalControlsPermission();
    879             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
    880                     Binder.getCallingUid(), userId, "setParentalControlsEnabled");
    881             final long identity = Binder.clearCallingIdentity();
    882             try {
    883                 synchronized (mLock) {
    884                     UserState userState = getUserStateLocked(resolvedUserId);
    885                     userState.persistentDataStore.setParentalControlsEnabled(enabled);
    886                 }
    887             } finally {
    888                 Binder.restoreCallingIdentity(identity);
    889             }
    890         }
    891 
    892         @Override
    893         public boolean isRatingBlocked(String rating, int userId) {
    894             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
    895                     Binder.getCallingUid(), userId, "isRatingBlocked");
    896             final long identity = Binder.clearCallingIdentity();
    897             try {
    898                 synchronized (mLock) {
    899                     UserState userState = getUserStateLocked(resolvedUserId);
    900                     return userState.persistentDataStore.isRatingBlocked(
    901                             TvContentRating.unflattenFromString(rating));
    902                 }
    903             } finally {
    904                 Binder.restoreCallingIdentity(identity);
    905             }
    906         }
    907 
    908         @Override
    909         public List<String> getBlockedRatings(int userId) {
    910             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
    911                     Binder.getCallingUid(), userId, "getBlockedRatings");
    912             final long identity = Binder.clearCallingIdentity();
    913             try {
    914                 synchronized (mLock) {
    915                     UserState userState = getUserStateLocked(resolvedUserId);
    916                     List<String> ratings = new ArrayList<String>();
    917                     for (TvContentRating rating
    918                             : userState.persistentDataStore.getBlockedRatings()) {
    919                         ratings.add(rating.flattenToString());
    920                     }
    921                     return ratings;
    922                 }
    923             } finally {
    924                 Binder.restoreCallingIdentity(identity);
    925             }
    926         }
    927 
    928         @Override
    929         public void addBlockedRating(String rating, int userId) {
    930             ensureParentalControlsPermission();
    931             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
    932                     Binder.getCallingUid(), userId, "addBlockedRating");
    933             final long identity = Binder.clearCallingIdentity();
    934             try {
    935                 synchronized (mLock) {
    936                     UserState userState = getUserStateLocked(resolvedUserId);
    937                     userState.persistentDataStore.addBlockedRating(
    938                             TvContentRating.unflattenFromString(rating));
    939                 }
    940             } finally {
    941                 Binder.restoreCallingIdentity(identity);
    942             }
    943         }
    944 
    945         @Override
    946         public void removeBlockedRating(String rating, int userId) {
    947             ensureParentalControlsPermission();
    948             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
    949                     Binder.getCallingUid(), userId, "removeBlockedRating");
    950             final long identity = Binder.clearCallingIdentity();
    951             try {
    952                 synchronized (mLock) {
    953                     UserState userState = getUserStateLocked(resolvedUserId);
    954                     userState.persistentDataStore.removeBlockedRating(
    955                             TvContentRating.unflattenFromString(rating));
    956                 }
    957             } finally {
    958                 Binder.restoreCallingIdentity(identity);
    959             }
    960         }
    961 
    962         private void ensureParentalControlsPermission() {
    963             if (mContext.checkCallingPermission(
    964                     android.Manifest.permission.MODIFY_PARENTAL_CONTROLS)
    965                     != PackageManager.PERMISSION_GRANTED) {
    966                 throw new SecurityException(
    967                         "The caller does not have parental controls permission");
    968             }
    969         }
    970 
    971         @Override
    972         public void createSession(final ITvInputClient client, final String inputId,
    973                 int seq, int userId) {
    974             final int callingUid = Binder.getCallingUid();
    975             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
    976                     userId, "createSession");
    977             final long identity = Binder.clearCallingIdentity();
    978             try {
    979                 synchronized (mLock) {
    980                     UserState userState = getUserStateLocked(resolvedUserId);
    981                     TvInputState inputState = userState.inputMap.get(inputId);
    982                     if (inputState == null) {
    983                         Slog.w(TAG, "Failed to find input state for inputId=" + inputId);
    984                         sendSessionTokenToClientLocked(client, inputId, null, null, seq);
    985                         return;
    986                     }
    987                     TvInputInfo info = inputState.info;
    988                     ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
    989                     if (serviceState == null) {
    990                         serviceState = new ServiceState(info.getComponent(), resolvedUserId);
    991                         userState.serviceStateMap.put(info.getComponent(), serviceState);
    992                     }
    993                     // Send a null token immediately while reconnecting.
    994                     if (serviceState.reconnecting == true) {
    995                         sendSessionTokenToClientLocked(client, inputId, null, null, seq);
    996                         return;
    997                     }
    998 
    999                     // Create a new session token and a session state.
   1000                     IBinder sessionToken = new Binder();
   1001                     SessionState sessionState = new SessionState(sessionToken, info, client,
   1002                             seq, callingUid, resolvedUserId);
   1003 
   1004                     // Add them to the global session state map of the current user.
   1005                     userState.sessionStateMap.put(sessionToken, sessionState);
   1006 
   1007                     // Also, add them to the session state map of the current service.
   1008                     serviceState.sessionTokens.add(sessionToken);
   1009 
   1010                     if (serviceState.service != null) {
   1011                         createSessionInternalLocked(serviceState.service, sessionToken,
   1012                                 resolvedUserId);
   1013                     } else {
   1014                         updateServiceConnectionLocked(info.getComponent(), resolvedUserId);
   1015                     }
   1016                 }
   1017             } finally {
   1018                 Binder.restoreCallingIdentity(identity);
   1019             }
   1020         }
   1021 
   1022         @Override
   1023         public void releaseSession(IBinder sessionToken, int userId) {
   1024             if (DEBUG) {
   1025                 Slog.d(TAG, "releaseSession(sessionToken=" + sessionToken + ")");
   1026             }
   1027             final int callingUid = Binder.getCallingUid();
   1028             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
   1029                     userId, "releaseSession");
   1030             final long identity = Binder.clearCallingIdentity();
   1031             try {
   1032                 synchronized (mLock) {
   1033                     releaseSessionLocked(sessionToken, callingUid, resolvedUserId);
   1034                 }
   1035             } finally {
   1036                 Binder.restoreCallingIdentity(identity);
   1037             }
   1038         }
   1039 
   1040         @Override
   1041         public void setMainSession(IBinder sessionToken, int userId) {
   1042             if (DEBUG) {
   1043                 Slog.d(TAG, "setMainSession(sessionToken=" + sessionToken + ")");
   1044             }
   1045             final int callingUid = Binder.getCallingUid();
   1046             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
   1047                     userId, "setMainSession");
   1048             final long identity = Binder.clearCallingIdentity();
   1049             try {
   1050                 synchronized (mLock) {
   1051                     UserState userState = getUserStateLocked(resolvedUserId);
   1052                     if (userState.mainSessionToken == sessionToken) {
   1053                         return;
   1054                     }
   1055                     if (DEBUG) {
   1056                         Slog.d(TAG, "mainSessionToken=" + sessionToken);
   1057                     }
   1058                     IBinder oldMainSessionToken = userState.mainSessionToken;
   1059                     userState.mainSessionToken = sessionToken;
   1060 
   1061                     // Inform the new main session first.
   1062                     // See {@link TvInputService.Session#onSetMain}.
   1063                     if (sessionToken != null) {
   1064                         setMainLocked(sessionToken, true, callingUid, userId);
   1065                     }
   1066                     if (oldMainSessionToken != null) {
   1067                         setMainLocked(oldMainSessionToken, false, Process.SYSTEM_UID, userId);
   1068                     }
   1069                 }
   1070             } finally {
   1071                 Binder.restoreCallingIdentity(identity);
   1072             }
   1073         }
   1074 
   1075         @Override
   1076         public void setSurface(IBinder sessionToken, Surface surface, int userId) {
   1077             final int callingUid = Binder.getCallingUid();
   1078             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
   1079                     userId, "setSurface");
   1080             final long identity = Binder.clearCallingIdentity();
   1081             try {
   1082                 synchronized (mLock) {
   1083                     try {
   1084                         SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
   1085                                 resolvedUserId);
   1086                         if (sessionState.hardwareSessionToken == null) {
   1087                             getSessionLocked(sessionState).setSurface(surface);
   1088                         } else {
   1089                             getSessionLocked(sessionState.hardwareSessionToken,
   1090                                     Process.SYSTEM_UID, resolvedUserId).setSurface(surface);
   1091                         }
   1092                     } catch (RemoteException | SessionNotFoundException e) {
   1093                         Slog.e(TAG, "error in setSurface", e);
   1094                     }
   1095                 }
   1096             } finally {
   1097                 if (surface != null) {
   1098                     // surface is not used in TvInputManagerService.
   1099                     surface.release();
   1100                 }
   1101                 Binder.restoreCallingIdentity(identity);
   1102             }
   1103         }
   1104 
   1105         @Override
   1106         public void dispatchSurfaceChanged(IBinder sessionToken, int format, int width,
   1107                 int height, int userId) {
   1108             final int callingUid = Binder.getCallingUid();
   1109             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
   1110                     userId, "dispatchSurfaceChanged");
   1111             final long identity = Binder.clearCallingIdentity();
   1112             try {
   1113                 synchronized (mLock) {
   1114                     try {
   1115                         SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
   1116                                 resolvedUserId);
   1117                         getSessionLocked(sessionState).dispatchSurfaceChanged(format, width,
   1118                                 height);
   1119                         if (sessionState.hardwareSessionToken != null) {
   1120                             getSessionLocked(sessionState.hardwareSessionToken, Process.SYSTEM_UID,
   1121                                     resolvedUserId).dispatchSurfaceChanged(format, width, height);
   1122                         }
   1123                     } catch (RemoteException | SessionNotFoundException e) {
   1124                         Slog.e(TAG, "error in dispatchSurfaceChanged", e);
   1125                     }
   1126                 }
   1127             } finally {
   1128                 Binder.restoreCallingIdentity(identity);
   1129             }
   1130         }
   1131 
   1132         @Override
   1133         public void setVolume(IBinder sessionToken, float volume, int userId) {
   1134             final float REMOTE_VOLUME_ON = 1.0f;
   1135             final float REMOTE_VOLUME_OFF = 0f;
   1136             final int callingUid = Binder.getCallingUid();
   1137             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
   1138                     userId, "setVolume");
   1139             final long identity = Binder.clearCallingIdentity();
   1140             try {
   1141                 synchronized (mLock) {
   1142                     try {
   1143                         SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
   1144                                 resolvedUserId);
   1145                         getSessionLocked(sessionState).setVolume(volume);
   1146                         if (sessionState.hardwareSessionToken != null) {
   1147                             // Here, we let the hardware session know only whether volume is on or
   1148                             // off to prevent that the volume is controlled in the both side.
   1149                             getSessionLocked(sessionState.hardwareSessionToken,
   1150                                     Process.SYSTEM_UID, resolvedUserId).setVolume((volume > 0.0f)
   1151                                             ? REMOTE_VOLUME_ON : REMOTE_VOLUME_OFF);
   1152                         }
   1153                     } catch (RemoteException | SessionNotFoundException e) {
   1154                         Slog.e(TAG, "error in setVolume", e);
   1155                     }
   1156                 }
   1157             } finally {
   1158                 Binder.restoreCallingIdentity(identity);
   1159             }
   1160         }
   1161 
   1162         @Override
   1163         public void tune(IBinder sessionToken, final Uri channelUri, Bundle params, int userId) {
   1164             final int callingUid = Binder.getCallingUid();
   1165             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
   1166                     userId, "tune");
   1167             final long identity = Binder.clearCallingIdentity();
   1168             try {
   1169                 synchronized (mLock) {
   1170                     try {
   1171                         getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(
   1172                                 channelUri, params);
   1173                         if (TvContract.isChannelUriForPassthroughInput(channelUri)) {
   1174                             // Do not log the watch history for passthrough inputs.
   1175                             return;
   1176                         }
   1177 
   1178                         UserState userState = getUserStateLocked(resolvedUserId);
   1179                         SessionState sessionState = userState.sessionStateMap.get(sessionToken);
   1180 
   1181                         // Log the start of watch.
   1182                         SomeArgs args = SomeArgs.obtain();
   1183                         args.arg1 = sessionState.info.getComponent().getPackageName();
   1184                         args.arg2 = System.currentTimeMillis();
   1185                         args.arg3 = ContentUris.parseId(channelUri);
   1186                         args.arg4 = params;
   1187                         args.arg5 = sessionToken;
   1188                         mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_LOG_WATCH_START, args)
   1189                                 .sendToTarget();
   1190                     } catch (RemoteException | SessionNotFoundException e) {
   1191                         Slog.e(TAG, "error in tune", e);
   1192                         return;
   1193                     }
   1194                 }
   1195             } finally {
   1196                 Binder.restoreCallingIdentity(identity);
   1197             }
   1198         }
   1199 
   1200         @Override
   1201         public void requestUnblockContent(
   1202                 IBinder sessionToken, String unblockedRating, int userId) {
   1203             final int callingUid = Binder.getCallingUid();
   1204             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
   1205                     userId, "unblockContent");
   1206             final long identity = Binder.clearCallingIdentity();
   1207             try {
   1208                 synchronized (mLock) {
   1209                     try {
   1210                         getSessionLocked(sessionToken, callingUid, resolvedUserId)
   1211                                 .requestUnblockContent(unblockedRating);
   1212                     } catch (RemoteException | SessionNotFoundException e) {
   1213                         Slog.e(TAG, "error in requestUnblockContent", e);
   1214                     }
   1215                 }
   1216             } finally {
   1217                 Binder.restoreCallingIdentity(identity);
   1218             }
   1219         }
   1220 
   1221         @Override
   1222         public void setCaptionEnabled(IBinder sessionToken, boolean enabled, int userId) {
   1223             final int callingUid = Binder.getCallingUid();
   1224             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
   1225                     userId, "setCaptionEnabled");
   1226             final long identity = Binder.clearCallingIdentity();
   1227             try {
   1228                 synchronized (mLock) {
   1229                     try {
   1230                         getSessionLocked(sessionToken, callingUid, resolvedUserId)
   1231                                 .setCaptionEnabled(enabled);
   1232                     } catch (RemoteException | SessionNotFoundException e) {
   1233                         Slog.e(TAG, "error in setCaptionEnabled", e);
   1234                     }
   1235                 }
   1236             } finally {
   1237                 Binder.restoreCallingIdentity(identity);
   1238             }
   1239         }
   1240 
   1241         @Override
   1242         public void selectTrack(IBinder sessionToken, int type, String trackId, int userId) {
   1243             final int callingUid = Binder.getCallingUid();
   1244             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
   1245                     userId, "selectTrack");
   1246             final long identity = Binder.clearCallingIdentity();
   1247             try {
   1248                 synchronized (mLock) {
   1249                     try {
   1250                         getSessionLocked(sessionToken, callingUid, resolvedUserId).selectTrack(
   1251                                 type, trackId);
   1252                     } catch (RemoteException | SessionNotFoundException e) {
   1253                         Slog.e(TAG, "error in selectTrack", e);
   1254                     }
   1255                 }
   1256             } finally {
   1257                 Binder.restoreCallingIdentity(identity);
   1258             }
   1259         }
   1260 
   1261         @Override
   1262         public void sendAppPrivateCommand(IBinder sessionToken, String command, Bundle data,
   1263                 int userId) {
   1264             final int callingUid = Binder.getCallingUid();
   1265             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
   1266                     userId, "sendAppPrivateCommand");
   1267             final long identity = Binder.clearCallingIdentity();
   1268             try {
   1269                 synchronized (mLock) {
   1270                     try {
   1271                         getSessionLocked(sessionToken, callingUid, resolvedUserId)
   1272                                 .appPrivateCommand(command, data);
   1273                     } catch (RemoteException | SessionNotFoundException e) {
   1274                         Slog.e(TAG, "error in appPrivateCommand", e);
   1275                     }
   1276                 }
   1277             } finally {
   1278                 Binder.restoreCallingIdentity(identity);
   1279             }
   1280         }
   1281 
   1282         @Override
   1283         public void createOverlayView(IBinder sessionToken, IBinder windowToken, Rect frame,
   1284                 int userId) {
   1285             final int callingUid = Binder.getCallingUid();
   1286             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
   1287                     userId, "createOverlayView");
   1288             final long identity = Binder.clearCallingIdentity();
   1289             try {
   1290                 synchronized (mLock) {
   1291                     try {
   1292                         getSessionLocked(sessionToken, callingUid, resolvedUserId)
   1293                                 .createOverlayView(windowToken, frame);
   1294                     } catch (RemoteException | SessionNotFoundException e) {
   1295                         Slog.e(TAG, "error in createOverlayView", e);
   1296                     }
   1297                 }
   1298             } finally {
   1299                 Binder.restoreCallingIdentity(identity);
   1300             }
   1301         }
   1302 
   1303         @Override
   1304         public void relayoutOverlayView(IBinder sessionToken, Rect frame, int userId) {
   1305             final int callingUid = Binder.getCallingUid();
   1306             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
   1307                     userId, "relayoutOverlayView");
   1308             final long identity = Binder.clearCallingIdentity();
   1309             try {
   1310                 synchronized (mLock) {
   1311                     try {
   1312                         getSessionLocked(sessionToken, callingUid, resolvedUserId)
   1313                                 .relayoutOverlayView(frame);
   1314                     } catch (RemoteException | SessionNotFoundException e) {
   1315                         Slog.e(TAG, "error in relayoutOverlayView", e);
   1316                     }
   1317                 }
   1318             } finally {
   1319                 Binder.restoreCallingIdentity(identity);
   1320             }
   1321         }
   1322 
   1323         @Override
   1324         public void removeOverlayView(IBinder sessionToken, int userId) {
   1325             final int callingUid = Binder.getCallingUid();
   1326             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
   1327                     userId, "removeOverlayView");
   1328             final long identity = Binder.clearCallingIdentity();
   1329             try {
   1330                 synchronized (mLock) {
   1331                     try {
   1332                         getSessionLocked(sessionToken, callingUid, resolvedUserId)
   1333                                 .removeOverlayView();
   1334                     } catch (RemoteException | SessionNotFoundException e) {
   1335                         Slog.e(TAG, "error in removeOverlayView", e);
   1336                     }
   1337                 }
   1338             } finally {
   1339                 Binder.restoreCallingIdentity(identity);
   1340             }
   1341         }
   1342 
   1343         @Override
   1344         public List<TvInputHardwareInfo> getHardwareList() throws RemoteException {
   1345             if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
   1346                     != PackageManager.PERMISSION_GRANTED) {
   1347                 return null;
   1348             }
   1349 
   1350             final long identity = Binder.clearCallingIdentity();
   1351             try {
   1352                 return mTvInputHardwareManager.getHardwareList();
   1353             } finally {
   1354                 Binder.restoreCallingIdentity(identity);
   1355             }
   1356         }
   1357 
   1358         @Override
   1359         public ITvInputHardware acquireTvInputHardware(int deviceId,
   1360                 ITvInputHardwareCallback callback, TvInputInfo info, int userId)
   1361                 throws RemoteException {
   1362             if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
   1363                     != PackageManager.PERMISSION_GRANTED) {
   1364                 return null;
   1365             }
   1366 
   1367             final long identity = Binder.clearCallingIdentity();
   1368             final int callingUid = Binder.getCallingUid();
   1369             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
   1370                     userId, "acquireTvInputHardware");
   1371             try {
   1372                 return mTvInputHardwareManager.acquireHardware(
   1373                         deviceId, callback, info, callingUid, resolvedUserId);
   1374             } finally {
   1375                 Binder.restoreCallingIdentity(identity);
   1376             }
   1377         }
   1378 
   1379         @Override
   1380         public void releaseTvInputHardware(int deviceId, ITvInputHardware hardware, int userId)
   1381                 throws RemoteException {
   1382             if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
   1383                     != PackageManager.PERMISSION_GRANTED) {
   1384                 return;
   1385             }
   1386 
   1387             final long identity = Binder.clearCallingIdentity();
   1388             final int callingUid = Binder.getCallingUid();
   1389             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
   1390                     userId, "releaseTvInputHardware");
   1391             try {
   1392                 mTvInputHardwareManager.releaseHardware(
   1393                         deviceId, hardware, callingUid, resolvedUserId);
   1394             } finally {
   1395                 Binder.restoreCallingIdentity(identity);
   1396             }
   1397         }
   1398 
   1399         @Override
   1400         public List<TvStreamConfig> getAvailableTvStreamConfigList(String inputId, int userId)
   1401                 throws RemoteException {
   1402             if (mContext.checkCallingPermission(
   1403                     android.Manifest.permission.CAPTURE_TV_INPUT)
   1404                     != PackageManager.PERMISSION_GRANTED) {
   1405                 throw new SecurityException("Requires CAPTURE_TV_INPUT permission");
   1406             }
   1407 
   1408             final long identity = Binder.clearCallingIdentity();
   1409             final int callingUid = Binder.getCallingUid();
   1410             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
   1411                     userId, "getAvailableTvStreamConfigList");
   1412             try {
   1413                 return mTvInputHardwareManager.getAvailableTvStreamConfigList(
   1414                         inputId, callingUid, resolvedUserId);
   1415             } finally {
   1416                 Binder.restoreCallingIdentity(identity);
   1417             }
   1418         }
   1419 
   1420         @Override
   1421         public boolean captureFrame(String inputId, Surface surface, TvStreamConfig config,
   1422                 int userId)
   1423                 throws RemoteException {
   1424             if (mContext.checkCallingPermission(
   1425                     android.Manifest.permission.CAPTURE_TV_INPUT)
   1426                     != PackageManager.PERMISSION_GRANTED) {
   1427                 throw new SecurityException("Requires CAPTURE_TV_INPUT permission");
   1428             }
   1429 
   1430             final long identity = Binder.clearCallingIdentity();
   1431             final int callingUid = Binder.getCallingUid();
   1432             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
   1433                     userId, "captureFrame");
   1434             try {
   1435                 String hardwareInputId = null;
   1436                 synchronized (mLock) {
   1437                     UserState userState = getUserStateLocked(resolvedUserId);
   1438                     if (userState.inputMap.get(inputId) == null) {
   1439                         Slog.e(TAG, "input not found for " + inputId);
   1440                         return false;
   1441                     }
   1442                     for (SessionState sessionState : userState.sessionStateMap.values()) {
   1443                         if (sessionState.info.getId().equals(inputId)
   1444                                 && sessionState.hardwareSessionToken != null) {
   1445                             hardwareInputId = userState.sessionStateMap.get(
   1446                                     sessionState.hardwareSessionToken).info.getId();
   1447                             break;
   1448                         }
   1449                     }
   1450                 }
   1451                 return mTvInputHardwareManager.captureFrame(
   1452                         (hardwareInputId != null) ? hardwareInputId : inputId,
   1453                         surface, config, callingUid, resolvedUserId);
   1454             } finally {
   1455                 Binder.restoreCallingIdentity(identity);
   1456             }
   1457         }
   1458 
   1459         @Override
   1460         public boolean isSingleSessionActive(int userId) throws RemoteException {
   1461             final long identity = Binder.clearCallingIdentity();
   1462             final int callingUid = Binder.getCallingUid();
   1463             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
   1464                     userId, "isSingleSessionActive");
   1465             try {
   1466                 synchronized (mLock) {
   1467                     UserState userState = getUserStateLocked(resolvedUserId);
   1468                     if (userState.sessionStateMap.size() == 1) {
   1469                         return true;
   1470                     }
   1471                     else if (userState.sessionStateMap.size() == 2) {
   1472                         SessionState[] sessionStates = userState.sessionStateMap.values().toArray(
   1473                                 new SessionState[0]);
   1474                         // Check if there is a wrapper input.
   1475                         if (sessionStates[0].hardwareSessionToken != null
   1476                                 || sessionStates[1].hardwareSessionToken != null) {
   1477                             return true;
   1478                         }
   1479                     }
   1480                     return false;
   1481                 }
   1482             } finally {
   1483                 Binder.restoreCallingIdentity(identity);
   1484             }
   1485         }
   1486 
   1487         @Override
   1488         @SuppressWarnings("resource")
   1489         protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
   1490             final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
   1491             if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
   1492                     != PackageManager.PERMISSION_GRANTED) {
   1493                 pw.println("Permission Denial: can't dump TvInputManager from pid="
   1494                         + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
   1495                 return;
   1496             }
   1497 
   1498             synchronized (mLock) {
   1499                 pw.println("User Ids (Current user: " + mCurrentUserId + "):");
   1500                 pw.increaseIndent();
   1501                 for (int i = 0; i < mUserStates.size(); i++) {
   1502                     int userId = mUserStates.keyAt(i);
   1503                     pw.println(Integer.valueOf(userId));
   1504                 }
   1505                 pw.decreaseIndent();
   1506 
   1507                 for (int i = 0; i < mUserStates.size(); i++) {
   1508                     int userId = mUserStates.keyAt(i);
   1509                     UserState userState = getUserStateLocked(userId);
   1510                     pw.println("UserState (" + userId + "):");
   1511                     pw.increaseIndent();
   1512 
   1513                     pw.println("inputMap: inputId -> TvInputState");
   1514                     pw.increaseIndent();
   1515                     for (Map.Entry<String, TvInputState> entry: userState.inputMap.entrySet()) {
   1516                         pw.println(entry.getKey() + ": " + entry.getValue());
   1517                     }
   1518                     pw.decreaseIndent();
   1519 
   1520                     pw.println("packageSet:");
   1521                     pw.increaseIndent();
   1522                     for (String packageName : userState.packageSet) {
   1523                         pw.println(packageName);
   1524                     }
   1525                     pw.decreaseIndent();
   1526 
   1527                     pw.println("clientStateMap: ITvInputClient -> ClientState");
   1528                     pw.increaseIndent();
   1529                     for (Map.Entry<IBinder, ClientState> entry :
   1530                             userState.clientStateMap.entrySet()) {
   1531                         ClientState client = entry.getValue();
   1532                         pw.println(entry.getKey() + ": " + client);
   1533 
   1534                         pw.increaseIndent();
   1535 
   1536                         pw.println("sessionTokens:");
   1537                         pw.increaseIndent();
   1538                         for (IBinder token : client.sessionTokens) {
   1539                             pw.println("" + token);
   1540                         }
   1541                         pw.decreaseIndent();
   1542 
   1543                         pw.println("clientTokens: " + client.clientToken);
   1544                         pw.println("userId: " + client.userId);
   1545 
   1546                         pw.decreaseIndent();
   1547                     }
   1548                     pw.decreaseIndent();
   1549 
   1550                     pw.println("serviceStateMap: ComponentName -> ServiceState");
   1551                     pw.increaseIndent();
   1552                     for (Map.Entry<ComponentName, ServiceState> entry :
   1553                             userState.serviceStateMap.entrySet()) {
   1554                         ServiceState service = entry.getValue();
   1555                         pw.println(entry.getKey() + ": " + service);
   1556 
   1557                         pw.increaseIndent();
   1558 
   1559                         pw.println("sessionTokens:");
   1560                         pw.increaseIndent();
   1561                         for (IBinder token : service.sessionTokens) {
   1562                             pw.println("" + token);
   1563                         }
   1564                         pw.decreaseIndent();
   1565 
   1566                         pw.println("service: " + service.service);
   1567                         pw.println("callback: " + service.callback);
   1568                         pw.println("bound: " + service.bound);
   1569                         pw.println("reconnecting: " + service.reconnecting);
   1570 
   1571                         pw.decreaseIndent();
   1572                     }
   1573                     pw.decreaseIndent();
   1574 
   1575                     pw.println("sessionStateMap: ITvInputSession -> SessionState");
   1576                     pw.increaseIndent();
   1577                     for (Map.Entry<IBinder, SessionState> entry :
   1578                             userState.sessionStateMap.entrySet()) {
   1579                         SessionState session = entry.getValue();
   1580                         pw.println(entry.getKey() + ": " + session);
   1581 
   1582                         pw.increaseIndent();
   1583                         pw.println("info: " + session.info);
   1584                         pw.println("client: " + session.client);
   1585                         pw.println("seq: " + session.seq);
   1586                         pw.println("callingUid: " + session.callingUid);
   1587                         pw.println("userId: " + session.userId);
   1588                         pw.println("sessionToken: " + session.sessionToken);
   1589                         pw.println("session: " + session.session);
   1590                         pw.println("logUri: " + session.logUri);
   1591                         pw.println("hardwareSessionToken: " + session.hardwareSessionToken);
   1592                         pw.decreaseIndent();
   1593                     }
   1594                     pw.decreaseIndent();
   1595 
   1596                     pw.println("callbackSet:");
   1597                     pw.increaseIndent();
   1598                     for (ITvInputManagerCallback callback : userState.callbackSet) {
   1599                         pw.println(callback.toString());
   1600                     }
   1601                     pw.decreaseIndent();
   1602 
   1603                     pw.println("mainSessionToken: " + userState.mainSessionToken);
   1604                     pw.decreaseIndent();
   1605                 }
   1606             }
   1607         }
   1608     }
   1609 
   1610     private static final class UserState {
   1611         // A mapping from the TV input id to its TvInputState.
   1612         private Map<String, TvInputState> inputMap = new HashMap<String, TvInputState>();
   1613 
   1614         // A set of all TV input packages.
   1615         private final Set<String> packageSet = new HashSet<String>();
   1616 
   1617         // A list of all TV content rating systems defined.
   1618         private final List<TvContentRatingSystemInfo>
   1619                 contentRatingSystemList = new ArrayList<TvContentRatingSystemInfo>();
   1620 
   1621         // A mapping from the token of a client to its state.
   1622         private final Map<IBinder, ClientState> clientStateMap =
   1623                 new HashMap<IBinder, ClientState>();
   1624 
   1625         // A mapping from the name of a TV input service to its state.
   1626         private final Map<ComponentName, ServiceState> serviceStateMap =
   1627                 new HashMap<ComponentName, ServiceState>();
   1628 
   1629         // A mapping from the token of a TV input session to its state.
   1630         private final Map<IBinder, SessionState> sessionStateMap =
   1631                 new HashMap<IBinder, SessionState>();
   1632 
   1633         // A set of callbacks.
   1634         private final Set<ITvInputManagerCallback> callbackSet =
   1635                 new HashSet<ITvInputManagerCallback>();
   1636 
   1637         // The token of a "main" TV input session.
   1638         private IBinder mainSessionToken = null;
   1639 
   1640         // Persistent data store for all internal settings maintained by the TV input manager
   1641         // service.
   1642         private final PersistentDataStore persistentDataStore;
   1643 
   1644         private UserState(Context context, int userId) {
   1645             persistentDataStore = new PersistentDataStore(context, userId);
   1646         }
   1647     }
   1648 
   1649     private final class ClientState implements IBinder.DeathRecipient {
   1650         private final List<IBinder> sessionTokens = new ArrayList<IBinder>();
   1651 
   1652         private IBinder clientToken;
   1653         private final int userId;
   1654 
   1655         ClientState(IBinder clientToken, int userId) {
   1656             this.clientToken = clientToken;
   1657             this.userId = userId;
   1658         }
   1659 
   1660         public boolean isEmpty() {
   1661             return sessionTokens.isEmpty();
   1662         }
   1663 
   1664         @Override
   1665         public void binderDied() {
   1666             synchronized (mLock) {
   1667                 UserState userState = getUserStateLocked(userId);
   1668                 // DO NOT remove the client state of clientStateMap in this method. It will be
   1669                 // removed in releaseSessionLocked().
   1670                 ClientState clientState = userState.clientStateMap.get(clientToken);
   1671                 if (clientState != null) {
   1672                     while (clientState.sessionTokens.size() > 0) {
   1673                         releaseSessionLocked(
   1674                                 clientState.sessionTokens.get(0), Process.SYSTEM_UID, userId);
   1675                     }
   1676                 }
   1677                 clientToken = null;
   1678             }
   1679         }
   1680     }
   1681 
   1682     private final class ServiceState {
   1683         private final List<IBinder> sessionTokens = new ArrayList<IBinder>();
   1684         private final ServiceConnection connection;
   1685         private final ComponentName component;
   1686         private final boolean isHardware;
   1687         private final List<TvInputInfo> inputList = new ArrayList<TvInputInfo>();
   1688 
   1689         private ITvInputService service;
   1690         private ServiceCallback callback;
   1691         private boolean bound;
   1692         private boolean reconnecting;
   1693 
   1694         private ServiceState(ComponentName component, int userId) {
   1695             this.component = component;
   1696             this.connection = new InputServiceConnection(component, userId);
   1697             this.isHardware = hasHardwarePermission(mContext.getPackageManager(), component);
   1698         }
   1699     }
   1700 
   1701     private static final class TvInputState {
   1702         // A TvInputInfo object which represents the TV input.
   1703         private TvInputInfo info;
   1704 
   1705         // The state of TV input. Connected by default.
   1706         private int state = INPUT_STATE_CONNECTED;
   1707 
   1708         @Override
   1709         public String toString() {
   1710             return "info: " + info + "; state: " + state;
   1711         }
   1712     }
   1713 
   1714     private final class SessionState implements IBinder.DeathRecipient {
   1715         private final TvInputInfo info;
   1716         private final ITvInputClient client;
   1717         private final int seq;
   1718         private final int callingUid;
   1719         private final int userId;
   1720         private final IBinder sessionToken;
   1721         private ITvInputSession session;
   1722         private Uri logUri;
   1723         // Not null if this session represents an external device connected to a hardware TV input.
   1724         private IBinder hardwareSessionToken;
   1725 
   1726         private SessionState(IBinder sessionToken, TvInputInfo info, ITvInputClient client,
   1727                 int seq, int callingUid, int userId) {
   1728             this.sessionToken = sessionToken;
   1729             this.info = info;
   1730             this.client = client;
   1731             this.seq = seq;
   1732             this.callingUid = callingUid;
   1733             this.userId = userId;
   1734         }
   1735 
   1736         @Override
   1737         public void binderDied() {
   1738             synchronized (mLock) {
   1739                 session = null;
   1740                 if (client != null) {
   1741                     try {
   1742                         client.onSessionReleased(seq);
   1743                     } catch(RemoteException e) {
   1744                         Slog.e(TAG, "error in onSessionReleased", e);
   1745                     }
   1746                 }
   1747                 // If there are any other sessions based on this session, they should be released.
   1748                 UserState userState = getUserStateLocked(userId);
   1749                 for (SessionState sessionState : userState.sessionStateMap.values()) {
   1750                     if (sessionToken == sessionState.hardwareSessionToken) {
   1751                         releaseSessionLocked(sessionState.sessionToken, Process.SYSTEM_UID,
   1752                                 userId);
   1753                         try {
   1754                             sessionState.client.onSessionReleased(sessionState.seq);
   1755                         } catch (RemoteException e) {
   1756                             Slog.e(TAG, "error in onSessionReleased", e);
   1757                         }
   1758                     }
   1759                 }
   1760                 removeSessionStateLocked(sessionToken, userId);
   1761             }
   1762         }
   1763     }
   1764 
   1765     private final class InputServiceConnection implements ServiceConnection {
   1766         private final ComponentName mComponent;
   1767         private final int mUserId;
   1768 
   1769         private InputServiceConnection(ComponentName component, int userId) {
   1770             mComponent = component;
   1771             mUserId = userId;
   1772         }
   1773 
   1774         @Override
   1775         public void onServiceConnected(ComponentName component, IBinder service) {
   1776             if (DEBUG) {
   1777                 Slog.d(TAG, "onServiceConnected(component=" + component + ")");
   1778             }
   1779             synchronized (mLock) {
   1780                 UserState userState = getUserStateLocked(mUserId);
   1781                 ServiceState serviceState = userState.serviceStateMap.get(mComponent);
   1782                 serviceState.service = ITvInputService.Stub.asInterface(service);
   1783 
   1784                 // Register a callback, if we need to.
   1785                 if (serviceState.isHardware && serviceState.callback == null) {
   1786                     serviceState.callback = new ServiceCallback(mComponent, mUserId);
   1787                     try {
   1788                         serviceState.service.registerCallback(serviceState.callback);
   1789                     } catch (RemoteException e) {
   1790                         Slog.e(TAG, "error in registerCallback", e);
   1791                     }
   1792                 }
   1793 
   1794                 // And create sessions, if any.
   1795                 for (IBinder sessionToken : serviceState.sessionTokens) {
   1796                     createSessionInternalLocked(serviceState.service, sessionToken, mUserId);
   1797                 }
   1798 
   1799                 for (TvInputState inputState : userState.inputMap.values()) {
   1800                     if (inputState.info.getComponent().equals(component)
   1801                             && inputState.state != INPUT_STATE_DISCONNECTED) {
   1802                         notifyInputStateChangedLocked(userState, inputState.info.getId(),
   1803                                 inputState.state, null);
   1804                     }
   1805                 }
   1806 
   1807                 if (serviceState.isHardware) {
   1808                     List<TvInputHardwareInfo> hardwareInfoList =
   1809                             mTvInputHardwareManager.getHardwareList();
   1810                     for (TvInputHardwareInfo hardwareInfo : hardwareInfoList) {
   1811                         try {
   1812                             serviceState.service.notifyHardwareAdded(hardwareInfo);
   1813                         } catch (RemoteException e) {
   1814                             Slog.e(TAG, "error in notifyHardwareAdded", e);
   1815                         }
   1816                     }
   1817 
   1818                     List<HdmiDeviceInfo> deviceInfoList =
   1819                             mTvInputHardwareManager.getHdmiDeviceList();
   1820                     for (HdmiDeviceInfo deviceInfo : deviceInfoList) {
   1821                         try {
   1822                             serviceState.service.notifyHdmiDeviceAdded(deviceInfo);
   1823                         } catch (RemoteException e) {
   1824                             Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
   1825                         }
   1826                     }
   1827                 }
   1828             }
   1829         }
   1830 
   1831         @Override
   1832         public void onServiceDisconnected(ComponentName component) {
   1833             if (DEBUG) {
   1834                 Slog.d(TAG, "onServiceDisconnected(component=" + component + ")");
   1835             }
   1836             if (!mComponent.equals(component)) {
   1837                 throw new IllegalArgumentException("Mismatched ComponentName: "
   1838                         + mComponent + " (expected), " + component + " (actual).");
   1839             }
   1840             synchronized (mLock) {
   1841                 UserState userState = getUserStateLocked(mUserId);
   1842                 ServiceState serviceState = userState.serviceStateMap.get(mComponent);
   1843                 if (serviceState != null) {
   1844                     serviceState.reconnecting = true;
   1845                     serviceState.bound = false;
   1846                     serviceState.service = null;
   1847                     serviceState.callback = null;
   1848 
   1849                     abortPendingCreateSessionRequestsLocked(serviceState, null, mUserId);
   1850 
   1851                     for (TvInputState inputState : userState.inputMap.values()) {
   1852                         if (inputState.info.getComponent().equals(component)) {
   1853                             notifyInputStateChangedLocked(userState, inputState.info.getId(),
   1854                                     INPUT_STATE_DISCONNECTED, null);
   1855                         }
   1856                     }
   1857                 }
   1858             }
   1859         }
   1860     }
   1861 
   1862     private final class ServiceCallback extends ITvInputServiceCallback.Stub {
   1863         private final ComponentName mComponent;
   1864         private final int mUserId;
   1865 
   1866         ServiceCallback(ComponentName component, int userId) {
   1867             mComponent = component;
   1868             mUserId = userId;
   1869         }
   1870 
   1871         private void ensureHardwarePermission() {
   1872             if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
   1873                     != PackageManager.PERMISSION_GRANTED) {
   1874                 throw new SecurityException("The caller does not have hardware permission");
   1875             }
   1876         }
   1877 
   1878         private void ensureValidInput(TvInputInfo inputInfo) {
   1879             if (inputInfo.getId() == null || !mComponent.equals(inputInfo.getComponent())) {
   1880                 throw new IllegalArgumentException("Invalid TvInputInfo");
   1881             }
   1882         }
   1883 
   1884         private void addTvInputLocked(TvInputInfo inputInfo) {
   1885             ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
   1886             serviceState.inputList.add(inputInfo);
   1887             buildTvInputListLocked(mUserId, null);
   1888         }
   1889 
   1890         @Override
   1891         public void addHardwareTvInput(int deviceId, TvInputInfo inputInfo) {
   1892             ensureHardwarePermission();
   1893             ensureValidInput(inputInfo);
   1894             synchronized (mLock) {
   1895                 mTvInputHardwareManager.addHardwareTvInput(deviceId, inputInfo);
   1896                 addTvInputLocked(inputInfo);
   1897             }
   1898         }
   1899 
   1900         @Override
   1901         public void addHdmiTvInput(int id, TvInputInfo inputInfo) {
   1902             ensureHardwarePermission();
   1903             ensureValidInput(inputInfo);
   1904             synchronized (mLock) {
   1905                 mTvInputHardwareManager.addHdmiTvInput(id, inputInfo);
   1906                 addTvInputLocked(inputInfo);
   1907             }
   1908         }
   1909 
   1910         @Override
   1911         public void removeTvInput(String inputId) {
   1912             ensureHardwarePermission();
   1913             synchronized (mLock) {
   1914                 ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
   1915                 boolean removed = false;
   1916                 for (Iterator<TvInputInfo> it = serviceState.inputList.iterator();
   1917                         it.hasNext(); ) {
   1918                     if (it.next().getId().equals(inputId)) {
   1919                         it.remove();
   1920                         removed = true;
   1921                         break;
   1922                     }
   1923                 }
   1924                 if (removed) {
   1925                     buildTvInputListLocked(mUserId, null);
   1926                     mTvInputHardwareManager.removeTvInput(inputId);
   1927                 } else {
   1928                     Slog.e(TAG, "failed to remove input " + inputId);
   1929                 }
   1930             }
   1931         }
   1932     }
   1933 
   1934     private final class SessionCallback extends ITvInputSessionCallback.Stub {
   1935         private final SessionState mSessionState;
   1936         private final InputChannel[] mChannels;
   1937 
   1938         SessionCallback(SessionState sessionState, InputChannel[] channels) {
   1939             mSessionState = sessionState;
   1940             mChannels = channels;
   1941         }
   1942 
   1943         @Override
   1944         public void onSessionCreated(ITvInputSession session, IBinder harewareSessionToken) {
   1945             if (DEBUG) {
   1946                 Slog.d(TAG, "onSessionCreated(inputId=" + mSessionState.info.getId() + ")");
   1947             }
   1948             synchronized (mLock) {
   1949                 mSessionState.session = session;
   1950                 mSessionState.hardwareSessionToken = harewareSessionToken;
   1951                 if (session != null && addSessionTokenToClientStateLocked(session)) {
   1952                     sendSessionTokenToClientLocked(mSessionState.client,
   1953                             mSessionState.info.getId(), mSessionState.sessionToken, mChannels[0],
   1954                             mSessionState.seq);
   1955                 } else {
   1956                     removeSessionStateLocked(mSessionState.sessionToken, mSessionState.userId);
   1957                     sendSessionTokenToClientLocked(mSessionState.client,
   1958                             mSessionState.info.getId(), null, null, mSessionState.seq);
   1959                 }
   1960                 mChannels[0].dispose();
   1961             }
   1962         }
   1963 
   1964         private boolean addSessionTokenToClientStateLocked(ITvInputSession session) {
   1965             try {
   1966                 session.asBinder().linkToDeath(mSessionState, 0);
   1967             } catch (RemoteException e) {
   1968                 Slog.e(TAG, "session process has already died", e);
   1969                 return false;
   1970             }
   1971 
   1972             IBinder clientToken = mSessionState.client.asBinder();
   1973             UserState userState = getUserStateLocked(mSessionState.userId);
   1974             ClientState clientState = userState.clientStateMap.get(clientToken);
   1975             if (clientState == null) {
   1976                 clientState = new ClientState(clientToken, mSessionState.userId);
   1977                 try {
   1978                     clientToken.linkToDeath(clientState, 0);
   1979                 } catch (RemoteException e) {
   1980                     Slog.e(TAG, "client process has already died", e);
   1981                     return false;
   1982                 }
   1983                 userState.clientStateMap.put(clientToken, clientState);
   1984             }
   1985             clientState.sessionTokens.add(mSessionState.sessionToken);
   1986             return true;
   1987         }
   1988 
   1989         @Override
   1990         public void onChannelRetuned(Uri channelUri) {
   1991             synchronized (mLock) {
   1992                 if (DEBUG) {
   1993                     Slog.d(TAG, "onChannelRetuned(" + channelUri + ")");
   1994                 }
   1995                 if (mSessionState.session == null || mSessionState.client == null) {
   1996                     return;
   1997                 }
   1998                 try {
   1999                     // TODO: Consider adding this channel change in the watch log. When we do
   2000                     // that, how we can protect the watch log from malicious tv inputs should
   2001                     // be addressed. e.g. add a field which represents where the channel change
   2002                     // originated from.
   2003                     mSessionState.client.onChannelRetuned(channelUri, mSessionState.seq);
   2004                 } catch (RemoteException e) {
   2005                     Slog.e(TAG, "error in onChannelRetuned", e);
   2006                 }
   2007             }
   2008         }
   2009 
   2010         @Override
   2011         public void onTracksChanged(List<TvTrackInfo> tracks) {
   2012             synchronized (mLock) {
   2013                 if (DEBUG) {
   2014                     Slog.d(TAG, "onTracksChanged(" + tracks + ")");
   2015                 }
   2016                 if (mSessionState.session == null || mSessionState.client == null) {
   2017                     return;
   2018                 }
   2019                 try {
   2020                     mSessionState.client.onTracksChanged(tracks, mSessionState.seq);
   2021                 } catch (RemoteException e) {
   2022                     Slog.e(TAG, "error in onTracksChanged", e);
   2023                 }
   2024             }
   2025         }
   2026 
   2027         @Override
   2028         public void onTrackSelected(int type, String trackId) {
   2029             synchronized (mLock) {
   2030                 if (DEBUG) {
   2031                     Slog.d(TAG, "onTrackSelected(type=" + type + ", trackId=" + trackId + ")");
   2032                 }
   2033                 if (mSessionState.session == null || mSessionState.client == null) {
   2034                     return;
   2035                 }
   2036                 try {
   2037                     mSessionState.client.onTrackSelected(type, trackId, mSessionState.seq);
   2038                 } catch (RemoteException e) {
   2039                     Slog.e(TAG, "error in onTrackSelected", e);
   2040                 }
   2041             }
   2042         }
   2043 
   2044         @Override
   2045         public void onVideoAvailable() {
   2046             synchronized (mLock) {
   2047                 if (DEBUG) {
   2048                     Slog.d(TAG, "onVideoAvailable()");
   2049                 }
   2050                 if (mSessionState.session == null || mSessionState.client == null) {
   2051                     return;
   2052                 }
   2053                 try {
   2054                     mSessionState.client.onVideoAvailable(mSessionState.seq);
   2055                 } catch (RemoteException e) {
   2056                     Slog.e(TAG, "error in onVideoAvailable", e);
   2057                 }
   2058             }
   2059         }
   2060 
   2061         @Override
   2062         public void onVideoUnavailable(int reason) {
   2063             synchronized (mLock) {
   2064                 if (DEBUG) {
   2065                     Slog.d(TAG, "onVideoUnavailable(" + reason + ")");
   2066                 }
   2067                 if (mSessionState.session == null || mSessionState.client == null) {
   2068                     return;
   2069                 }
   2070                 try {
   2071                     mSessionState.client.onVideoUnavailable(reason, mSessionState.seq);
   2072                 } catch (RemoteException e) {
   2073                     Slog.e(TAG, "error in onVideoUnavailable", e);
   2074                 }
   2075             }
   2076         }
   2077 
   2078         @Override
   2079         public void onContentAllowed() {
   2080             synchronized (mLock) {
   2081                 if (DEBUG) {
   2082                     Slog.d(TAG, "onContentAllowed()");
   2083                 }
   2084                 if (mSessionState.session == null || mSessionState.client == null) {
   2085                     return;
   2086                 }
   2087                 try {
   2088                     mSessionState.client.onContentAllowed(mSessionState.seq);
   2089                 } catch (RemoteException e) {
   2090                     Slog.e(TAG, "error in onContentAllowed", e);
   2091                 }
   2092             }
   2093         }
   2094 
   2095         @Override
   2096         public void onContentBlocked(String rating) {
   2097             synchronized (mLock) {
   2098                 if (DEBUG) {
   2099                     Slog.d(TAG, "onContentBlocked()");
   2100                 }
   2101                 if (mSessionState.session == null || mSessionState.client == null) {
   2102                     return;
   2103                 }
   2104                 try {
   2105                     mSessionState.client.onContentBlocked(rating, mSessionState.seq);
   2106                 } catch (RemoteException e) {
   2107                     Slog.e(TAG, "error in onContentBlocked", e);
   2108                 }
   2109             }
   2110         }
   2111 
   2112         @Override
   2113         public void onLayoutSurface(int left, int top, int right, int bottom) {
   2114             synchronized (mLock) {
   2115                 if (DEBUG) {
   2116                     Slog.d(TAG, "onLayoutSurface (left=" + left + ", top=" + top
   2117                             + ", right=" + right + ", bottom=" + bottom + ",)");
   2118                 }
   2119                 if (mSessionState.session == null || mSessionState.client == null) {
   2120                     return;
   2121                 }
   2122                 try {
   2123                     mSessionState.client.onLayoutSurface(left, top, right, bottom,
   2124                             mSessionState.seq);
   2125                 } catch (RemoteException e) {
   2126                     Slog.e(TAG, "error in onLayoutSurface", e);
   2127                 }
   2128             }
   2129         }
   2130 
   2131         @Override
   2132         public void onSessionEvent(String eventType, Bundle eventArgs) {
   2133             synchronized (mLock) {
   2134                 if (DEBUG) {
   2135                     Slog.d(TAG, "onEvent(what=" + eventType + ", data=" + eventArgs + ")");
   2136                 }
   2137                 if (mSessionState.session == null || mSessionState.client == null) {
   2138                     return;
   2139                 }
   2140                 try {
   2141                     mSessionState.client.onSessionEvent(eventType, eventArgs, mSessionState.seq);
   2142                 } catch (RemoteException e) {
   2143                     Slog.e(TAG, "error in onSessionEvent", e);
   2144                 }
   2145             }
   2146         }
   2147     }
   2148 
   2149     private static final class WatchLogHandler extends Handler {
   2150         // There are only two kinds of watch events that can happen on the system:
   2151         // 1. The current TV input session is tuned to a new channel.
   2152         // 2. The session is released for some reason.
   2153         // The former indicates the end of the previous log entry, if any, followed by the start of
   2154         // a new entry. The latter indicates the end of the most recent entry for the given session.
   2155         // Here the system supplies the database the smallest set of information only that is
   2156         // sufficient to consolidate the log entries while minimizing database operations in the
   2157         // system service.
   2158         private static final int MSG_LOG_WATCH_START = 1;
   2159         private static final int MSG_LOG_WATCH_END = 2;
   2160 
   2161         private final ContentResolver mContentResolver;
   2162 
   2163         public WatchLogHandler(ContentResolver contentResolver, Looper looper) {
   2164             super(looper);
   2165             mContentResolver = contentResolver;
   2166         }
   2167 
   2168         @Override
   2169         public void handleMessage(Message msg) {
   2170             switch (msg.what) {
   2171                 case MSG_LOG_WATCH_START: {
   2172                     SomeArgs args = (SomeArgs) msg.obj;
   2173                     String packageName = (String) args.arg1;
   2174                     long watchStartTime = (long) args.arg2;
   2175                     long channelId = (long) args.arg3;
   2176                     Bundle tuneParams = (Bundle) args.arg4;
   2177                     IBinder sessionToken = (IBinder) args.arg5;
   2178 
   2179                     ContentValues values = new ContentValues();
   2180                     values.put(TvContract.WatchedPrograms.COLUMN_PACKAGE_NAME, packageName);
   2181                     values.put(TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
   2182                             watchStartTime);
   2183                     values.put(TvContract.WatchedPrograms.COLUMN_CHANNEL_ID, channelId);
   2184                     if (tuneParams != null) {
   2185                         values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_TUNE_PARAMS,
   2186                                 encodeTuneParams(tuneParams));
   2187                     }
   2188                     values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN,
   2189                             sessionToken.toString());
   2190 
   2191                     mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
   2192                     args.recycle();
   2193                     return;
   2194                 }
   2195                 case MSG_LOG_WATCH_END: {
   2196                     SomeArgs args = (SomeArgs) msg.obj;
   2197                     IBinder sessionToken = (IBinder) args.arg1;
   2198                     long watchEndTime = (long) args.arg2;
   2199 
   2200                     ContentValues values = new ContentValues();
   2201                     values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS,
   2202                             watchEndTime);
   2203                     values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN,
   2204                             sessionToken.toString());
   2205 
   2206                     mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
   2207                     args.recycle();
   2208                     return;
   2209                 }
   2210                 default: {
   2211                     Slog.w(TAG, "Unhandled message code: " + msg.what);
   2212                     return;
   2213                 }
   2214             }
   2215         }
   2216 
   2217         private String encodeTuneParams(Bundle tuneParams) {
   2218             StringBuilder builder = new StringBuilder();
   2219             Set<String> keySet = tuneParams.keySet();
   2220             Iterator<String> it = keySet.iterator();
   2221             while (it.hasNext()) {
   2222                 String key = it.next();
   2223                 Object value = tuneParams.get(key);
   2224                 if (value == null) {
   2225                     continue;
   2226                 }
   2227                 builder.append(replaceEscapeCharacters(key));
   2228                 builder.append("=");
   2229                 builder.append(replaceEscapeCharacters(value.toString()));
   2230                 if (it.hasNext()) {
   2231                     builder.append(", ");
   2232                 }
   2233             }
   2234             return builder.toString();
   2235         }
   2236 
   2237         private String replaceEscapeCharacters(String src) {
   2238             final char ESCAPE_CHARACTER = '%';
   2239             final String ENCODING_TARGET_CHARACTERS = "%=,";
   2240             StringBuilder builder = new StringBuilder();
   2241             for (char ch : src.toCharArray()) {
   2242                 if (ENCODING_TARGET_CHARACTERS.indexOf(ch) >= 0) {
   2243                     builder.append(ESCAPE_CHARACTER);
   2244                 }
   2245                 builder.append(ch);
   2246             }
   2247             return builder.toString();
   2248         }
   2249     }
   2250 
   2251     private final class HardwareListener implements TvInputHardwareManager.Listener {
   2252         @Override
   2253         public void onStateChanged(String inputId, int state) {
   2254             synchronized (mLock) {
   2255                 setStateLocked(inputId, state, mCurrentUserId);
   2256             }
   2257         }
   2258 
   2259         @Override
   2260         public void onHardwareDeviceAdded(TvInputHardwareInfo info) {
   2261             synchronized (mLock) {
   2262                 UserState userState = getUserStateLocked(mCurrentUserId);
   2263                 // Broadcast the event to all hardware inputs.
   2264                 for (ServiceState serviceState : userState.serviceStateMap.values()) {
   2265                     if (!serviceState.isHardware || serviceState.service == null) continue;
   2266                     try {
   2267                         serviceState.service.notifyHardwareAdded(info);
   2268                     } catch (RemoteException e) {
   2269                         Slog.e(TAG, "error in notifyHardwareAdded", e);
   2270                     }
   2271                 }
   2272             }
   2273         }
   2274 
   2275         @Override
   2276         public void onHardwareDeviceRemoved(TvInputHardwareInfo info) {
   2277             synchronized (mLock) {
   2278                 UserState userState = getUserStateLocked(mCurrentUserId);
   2279                 // Broadcast the event to all hardware inputs.
   2280                 for (ServiceState serviceState : userState.serviceStateMap.values()) {
   2281                     if (!serviceState.isHardware || serviceState.service == null) continue;
   2282                     try {
   2283                         serviceState.service.notifyHardwareRemoved(info);
   2284                     } catch (RemoteException e) {
   2285                         Slog.e(TAG, "error in notifyHardwareRemoved", e);
   2286                     }
   2287                 }
   2288             }
   2289         }
   2290 
   2291         @Override
   2292         public void onHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) {
   2293             synchronized (mLock) {
   2294                 UserState userState = getUserStateLocked(mCurrentUserId);
   2295                 // Broadcast the event to all hardware inputs.
   2296                 for (ServiceState serviceState : userState.serviceStateMap.values()) {
   2297                     if (!serviceState.isHardware || serviceState.service == null) continue;
   2298                     try {
   2299                         serviceState.service.notifyHdmiDeviceAdded(deviceInfo);
   2300                     } catch (RemoteException e) {
   2301                         Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
   2302                     }
   2303                 }
   2304             }
   2305         }
   2306 
   2307         @Override
   2308         public void onHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo) {
   2309             synchronized (mLock) {
   2310                 UserState userState = getUserStateLocked(mCurrentUserId);
   2311                 // Broadcast the event to all hardware inputs.
   2312                 for (ServiceState serviceState : userState.serviceStateMap.values()) {
   2313                     if (!serviceState.isHardware || serviceState.service == null) continue;
   2314                     try {
   2315                         serviceState.service.notifyHdmiDeviceRemoved(deviceInfo);
   2316                     } catch (RemoteException e) {
   2317                         Slog.e(TAG, "error in notifyHdmiDeviceRemoved", e);
   2318                     }
   2319                 }
   2320             }
   2321         }
   2322 
   2323         @Override
   2324         public void onHdmiDeviceUpdated(String inputId, HdmiDeviceInfo deviceInfo) {
   2325             synchronized (mLock) {
   2326                 Integer state = null;
   2327                 switch (deviceInfo.getDevicePowerStatus()) {
   2328                     case HdmiControlManager.POWER_STATUS_ON:
   2329                         state = INPUT_STATE_CONNECTED;
   2330                         break;
   2331                     case HdmiControlManager.POWER_STATUS_STANDBY:
   2332                     case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON:
   2333                     case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY:
   2334                         state = INPUT_STATE_CONNECTED_STANDBY;
   2335                         break;
   2336                     case HdmiControlManager.POWER_STATUS_UNKNOWN:
   2337                     default:
   2338                         state = null;
   2339                         break;
   2340                 }
   2341                 if (state != null) {
   2342                     setStateLocked(inputId, state.intValue(), mCurrentUserId);
   2343                 }
   2344             }
   2345         }
   2346     }
   2347 
   2348     private static class SessionNotFoundException extends IllegalArgumentException {
   2349         public SessionNotFoundException() {
   2350         }
   2351 
   2352         public SessionNotFoundException(String name) {
   2353             super(name);
   2354         }
   2355     }
   2356 }
   2357