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