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