TvInputManagerService.java revision fdce9e541e143332ba4ece6a0bda4f11520f0b07
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
17package com.android.server.tv;
18
19import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
20import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED_STANDBY;
21import static android.media.tv.TvInputManager.INPUT_STATE_DISCONNECTED;
22import static android.media.tv.TvInputManager.INPUT_STATE_UNKNOWN;
23
24import android.app.ActivityManager;
25import android.content.BroadcastReceiver;
26import android.content.ComponentName;
27import android.content.ContentProviderOperation;
28import android.content.ContentProviderResult;
29import android.content.ContentResolver;
30import android.content.ContentUris;
31import android.content.ContentValues;
32import android.content.Context;
33import android.content.Intent;
34import android.content.IntentFilter;
35import android.content.OperationApplicationException;
36import android.content.ServiceConnection;
37import android.content.pm.ActivityInfo;
38import android.content.pm.PackageManager;
39import android.content.pm.ResolveInfo;
40import android.content.pm.ServiceInfo;
41import android.graphics.Rect;
42import android.hardware.hdmi.HdmiControlManager;
43import android.hardware.hdmi.HdmiDeviceInfo;
44import android.media.tv.ITvInputClient;
45import android.media.tv.ITvInputHardware;
46import android.media.tv.ITvInputHardwareCallback;
47import android.media.tv.ITvInputManager;
48import android.media.tv.ITvInputManagerCallback;
49import android.media.tv.ITvInputService;
50import android.media.tv.ITvInputServiceCallback;
51import android.media.tv.ITvInputSession;
52import android.media.tv.ITvInputSessionCallback;
53import android.media.tv.TvContentRating;
54import android.media.tv.TvContentRatingSystemInfo;
55import android.media.tv.TvContract;
56import android.media.tv.TvInputHardwareInfo;
57import android.media.tv.TvInputInfo;
58import android.media.tv.TvInputManager;
59import android.media.tv.TvInputService;
60import android.media.tv.TvStreamConfig;
61import android.media.tv.TvTrackInfo;
62import android.net.Uri;
63import android.os.Binder;
64import android.os.Bundle;
65import android.os.Handler;
66import android.os.IBinder;
67import android.os.Looper;
68import android.os.Message;
69import android.os.Process;
70import android.os.RemoteException;
71import android.os.UserHandle;
72import android.util.Slog;
73import android.util.SparseArray;
74import android.view.InputChannel;
75import android.view.Surface;
76
77import com.android.internal.content.PackageMonitor;
78import com.android.internal.os.SomeArgs;
79import com.android.internal.util.IndentingPrintWriter;
80import com.android.server.IoThread;
81import com.android.server.SystemService;
82
83import org.xmlpull.v1.XmlPullParserException;
84
85import java.io.FileDescriptor;
86import java.io.IOException;
87import java.io.PrintWriter;
88import java.util.ArrayList;
89import java.util.Arrays;
90import java.util.HashMap;
91import java.util.HashSet;
92import java.util.Iterator;
93import java.util.List;
94import java.util.Map;
95import java.util.Set;
96
97/** This class provides a system service that manages television inputs. */
98public 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