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