TvInputManagerService.java revision abca0ee7949f59e72b8d2764dafa23af18eb51db
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        return getSessionLocked(getSessionStateLocked(sessionToken, callingUid, userId));
375    }
376
377    private ITvInputSession getSessionLocked(SessionState sessionState) {
378        ITvInputSession session = sessionState.mSession;
379        if (session == null) {
380            throw new IllegalStateException("Session not yet created for token "
381                    + sessionState.mSessionToken);
382        }
383        return session;
384    }
385
386    private int resolveCallingUserId(int callingPid, int callingUid, int requestedUserId,
387            String methodName) {
388        return ActivityManager.handleIncomingUser(callingPid, callingUid, requestedUserId, false,
389                false, methodName, null);
390    }
391
392    private static boolean shouldMaintainConnection(ServiceState serviceState) {
393        return !serviceState.mClientTokens.isEmpty()
394                || !serviceState.mSessionTokens.isEmpty()
395                || serviceState.mIsHardware;
396        // TODO: Find a way to maintain connection only when necessary.
397    }
398
399    private void updateServiceConnectionLocked(ComponentName service, int userId) {
400        UserState userState = getUserStateLocked(userId);
401        ServiceState serviceState = userState.serviceStateMap.get(service);
402        if (serviceState == null) {
403            return;
404        }
405        if (serviceState.mReconnecting) {
406            if (!serviceState.mSessionTokens.isEmpty()) {
407                // wait until all the sessions are removed.
408                return;
409            }
410            serviceState.mReconnecting = false;
411        }
412        boolean maintainConnection = shouldMaintainConnection(serviceState);
413        if (serviceState.mService == null && maintainConnection && userId == mCurrentUserId) {
414            // This means that the service is not yet connected but its state indicates that we
415            // have pending requests. Then, connect the service.
416            if (serviceState.mBound) {
417                // We have already bound to the service so we don't try to bind again until after we
418                // unbind later on.
419                return;
420            }
421            if (DEBUG) {
422                Slog.d(TAG, "bindServiceAsUser(service=" + service + ", userId=" + userId + ")");
423            }
424
425            Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(service);
426            // Binding service may fail if the service is updating.
427            // In that case, the connection will be revived in buildTvInputListLocked called by
428            // onSomePackagesChanged.
429            serviceState.mBound = mContext.bindServiceAsUser(
430                    i, serviceState.mConnection, Context.BIND_AUTO_CREATE, new UserHandle(userId));
431        } else if (serviceState.mService != null && !maintainConnection) {
432            // This means that the service is already connected but its state indicates that we have
433            // nothing to do with it. Then, disconnect the service.
434            if (DEBUG) {
435                Slog.d(TAG, "unbindService(service=" + service + ")");
436            }
437            mContext.unbindService(serviceState.mConnection);
438            userState.serviceStateMap.remove(service);
439        }
440    }
441
442    private ClientState createClientStateLocked(IBinder clientToken, int userId) {
443        UserState userState = getUserStateLocked(userId);
444        ClientState clientState = new ClientState(clientToken, userId);
445        try {
446            clientToken.linkToDeath(clientState, 0);
447        } catch (RemoteException e) {
448            Slog.e(TAG, "Client is already died.");
449        }
450        userState.clientStateMap.put(clientToken, clientState);
451        return clientState;
452    }
453
454    private void createSessionInternalLocked(ITvInputService service, final IBinder sessionToken,
455            final int userId) {
456        final UserState userState = getUserStateLocked(userId);
457        final SessionState sessionState = userState.sessionStateMap.get(sessionToken);
458        if (DEBUG) {
459            Slog.d(TAG, "createSessionInternalLocked(inputId=" + sessionState.mInfo.getId() + ")");
460        }
461
462        final InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString());
463
464        // Set up a callback to send the session token.
465        ITvInputSessionCallback callback = new ITvInputSessionCallback.Stub() {
466            @Override
467            public void onSessionCreated(ITvInputSession session) {
468                if (DEBUG) {
469                    Slog.d(TAG, "onSessionCreated(inputId=" + sessionState.mInfo.getId() + ")");
470                }
471                synchronized (mLock) {
472                    sessionState.mSession = session;
473                    if (session == null) {
474                        removeSessionStateLocked(sessionToken, userId);
475                        sendSessionTokenToClientLocked(sessionState.mClient,
476                                sessionState.mInfo.getId(), null, null, sessionState.mSeq);
477                    } else {
478                        try {
479                            session.asBinder().linkToDeath(sessionState, 0);
480                        } catch (RemoteException e) {
481                            Slog.e(TAG, "Session is already died.");
482                        }
483
484                        IBinder clientToken = sessionState.mClient.asBinder();
485                        ClientState clientState = userState.clientStateMap.get(clientToken);
486                        if (clientState == null) {
487                            clientState = createClientStateLocked(clientToken, userId);
488                        }
489                        clientState.mSessionTokens.add(sessionState.mSessionToken);
490
491                        sendSessionTokenToClientLocked(sessionState.mClient,
492                                sessionState.mInfo.getId(), sessionToken, channels[0],
493                                sessionState.mSeq);
494                    }
495                    channels[0].dispose();
496                }
497            }
498
499            @Override
500            public void onChannelRetuned(Uri channelUri) {
501                synchronized (mLock) {
502                    if (DEBUG) {
503                        Slog.d(TAG, "onChannelRetuned(" + channelUri + ")");
504                    }
505                    if (sessionState.mSession == null || sessionState.mClient == null) {
506                        return;
507                    }
508                    try {
509                        // TODO: Consider adding this channel change in the watch log. When we do
510                        // that, how we can protect the watch log from malicious tv inputs should
511                        // be addressed. e.g. add a field which represents where the channel change
512                        // originated from.
513                        sessionState.mClient.onChannelRetuned(channelUri, sessionState.mSeq);
514                    } catch (RemoteException e) {
515                        Slog.e(TAG, "error in onChannelRetuned");
516                    }
517                }
518            }
519
520            @Override
521            public void onTrackInfoChanged(List<TvTrackInfo> tracks) {
522                synchronized (mLock) {
523                    if (DEBUG) {
524                        Slog.d(TAG, "onTrackInfoChanged(" + tracks + ")");
525                    }
526                    if (sessionState.mSession == null || sessionState.mClient == null) {
527                        return;
528                    }
529                    try {
530                        sessionState.mClient.onTrackInfoChanged(tracks,
531                                sessionState.mSeq);
532                    } catch (RemoteException e) {
533                        Slog.e(TAG, "error in onTrackInfoChanged");
534                    }
535                }
536            }
537
538            @Override
539            public void onVideoAvailable() {
540                synchronized (mLock) {
541                    if (DEBUG) {
542                        Slog.d(TAG, "onVideoAvailable()");
543                    }
544                    if (sessionState.mSession == null || sessionState.mClient == null) {
545                        return;
546                    }
547                    try {
548                        sessionState.mClient.onVideoAvailable(sessionState.mSeq);
549                    } catch (RemoteException e) {
550                        Slog.e(TAG, "error in onVideoAvailable");
551                    }
552                }
553            }
554
555            @Override
556            public void onVideoUnavailable(int reason) {
557                synchronized (mLock) {
558                    if (DEBUG) {
559                        Slog.d(TAG, "onVideoUnavailable(" + reason + ")");
560                    }
561                    if (sessionState.mSession == null || sessionState.mClient == null) {
562                        return;
563                    }
564                    try {
565                        sessionState.mClient.onVideoUnavailable(reason, sessionState.mSeq);
566                    } catch (RemoteException e) {
567                        Slog.e(TAG, "error in onVideoUnavailable");
568                    }
569                }
570            }
571
572            @Override
573            public void onContentAllowed() {
574                synchronized (mLock) {
575                    if (DEBUG) {
576                        Slog.d(TAG, "onContentAllowed()");
577                    }
578                    if (sessionState.mSession == null || sessionState.mClient == null) {
579                        return;
580                    }
581                    try {
582                        sessionState.mClient.onContentAllowed(sessionState.mSeq);
583                    } catch (RemoteException e) {
584                        Slog.e(TAG, "error in onContentAllowed");
585                    }
586                }
587            }
588
589            @Override
590            public void onContentBlocked(String rating) {
591                synchronized (mLock) {
592                    if (DEBUG) {
593                        Slog.d(TAG, "onContentBlocked()");
594                    }
595                    if (sessionState.mSession == null || sessionState.mClient == null) {
596                        return;
597                    }
598                    try {
599                        sessionState.mClient.onContentBlocked(rating, sessionState.mSeq);
600                    } catch (RemoteException e) {
601                        Slog.e(TAG, "error in onContentBlocked");
602                    }
603                }
604            }
605
606            @Override
607            public void onSessionEvent(String eventType, Bundle eventArgs) {
608                synchronized (mLock) {
609                    if (DEBUG) {
610                        Slog.d(TAG, "onEvent(what=" + eventType + ", data=" + eventArgs + ")");
611                    }
612                    if (sessionState.mSession == null || sessionState.mClient == null) {
613                        return;
614                    }
615                    try {
616                        sessionState.mClient.onSessionEvent(eventType, eventArgs,
617                                sessionState.mSeq);
618                    } catch (RemoteException e) {
619                        Slog.e(TAG, "error in onSessionEvent");
620                    }
621                }
622            }
623        };
624
625        // Create a session. When failed, send a null token immediately.
626        try {
627            service.createSession(channels[1], callback, sessionState.mInfo.getId());
628        } catch (RemoteException e) {
629            Slog.e(TAG, "error in createSession", e);
630            removeSessionStateLocked(sessionToken, userId);
631            sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mInfo.getId(), null,
632                    null, sessionState.mSeq);
633        }
634        channels[1].dispose();
635    }
636
637    private void sendSessionTokenToClientLocked(ITvInputClient client, String inputId,
638            IBinder sessionToken, InputChannel channel, int seq) {
639        try {
640            client.onSessionCreated(inputId, sessionToken, channel, seq);
641        } catch (RemoteException exception) {
642            Slog.e(TAG, "error in onSessionCreated", exception);
643        }
644    }
645
646    private void releaseSessionLocked(IBinder sessionToken, int callingUid, int userId) {
647        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
648        if (sessionState.mSession != null) {
649            try {
650                sessionState.mSession.release();
651            } catch (RemoteException e) {
652                Slog.w(TAG, "session is already disapeared", e);
653            }
654            sessionState.mSession = null;
655        }
656        removeSessionStateLocked(sessionToken, userId);
657    }
658
659    private void removeSessionStateLocked(IBinder sessionToken, int userId) {
660        UserState userState = getUserStateLocked(userId);
661        if (sessionToken == userState.mainSessionToken) {
662            userState.mainSessionToken = null;
663        }
664
665        // Remove the session state from the global session state map of the current user.
666        SessionState sessionState = userState.sessionStateMap.remove(sessionToken);
667
668        // Close the open log entry, if any.
669        if (sessionState.mLogUri != null) {
670            SomeArgs args = SomeArgs.obtain();
671            args.arg1 = sessionState.mLogUri;
672            args.arg2 = System.currentTimeMillis();
673            mLogHandler.obtainMessage(LogHandler.MSG_CLOSE_ENTRY, args).sendToTarget();
674        }
675
676        // Also remove the session token from the session token list of the current client and
677        // service.
678        ClientState clientState = userState.clientStateMap.get(sessionState.mClient.asBinder());
679        if (clientState != null) {
680            clientState.mSessionTokens.remove(sessionToken);
681            if (clientState.isEmpty()) {
682                userState.clientStateMap.remove(sessionState.mClient.asBinder());
683            }
684        }
685
686        TvInputInfo info = sessionState.mInfo;
687        if (info != null) {
688            ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
689            if (serviceState != null) {
690                serviceState.mSessionTokens.remove(sessionToken);
691            }
692        }
693        updateServiceConnectionLocked(sessionState.mInfo.getComponent(), userId);
694    }
695
696    private void unregisterClientInternalLocked(IBinder clientToken, String inputId,
697            int userId) {
698        UserState userState = getUserStateLocked(userId);
699        ClientState clientState = userState.clientStateMap.get(clientToken);
700        if (clientState != null) {
701            clientState.mInputIds.remove(inputId);
702            if (clientState.isEmpty()) {
703                userState.clientStateMap.remove(clientToken);
704            }
705        }
706
707        TvInputInfo info = userState.inputMap.get(inputId).mInfo;
708        if (info == null) {
709            return;
710        }
711        ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
712        if (serviceState == null) {
713            return;
714        }
715
716        // Remove this client from the client list and unregister the callback.
717        serviceState.mClientTokens.remove(clientToken);
718        if (!serviceState.mClientTokens.isEmpty()) {
719            // We have other clients who want to keep the callback. Do this later.
720            return;
721        }
722        if (serviceState.mService == null || serviceState.mCallback == null) {
723            return;
724        }
725        try {
726            serviceState.mService.unregisterCallback(serviceState.mCallback);
727        } catch (RemoteException e) {
728            Slog.e(TAG, "error in unregisterCallback", e);
729        } finally {
730            serviceState.mCallback = null;
731            updateServiceConnectionLocked(info.getComponent(), userId);
732        }
733    }
734
735    private void notifyInputAddedLocked(UserState userState, String inputId) {
736        if (DEBUG) {
737            Slog.d(TAG, "notifyInputAdded: inputId = " + inputId);
738        }
739        for (ITvInputManagerCallback callback : userState.callbackSet) {
740            try {
741                callback.onInputAdded(inputId);
742            } catch (RemoteException e) {
743                Slog.e(TAG, "Failed to report added input to callback.");
744            }
745        }
746    }
747
748    private void notifyInputRemovedLocked(UserState userState, String inputId) {
749        if (DEBUG) {
750            Slog.d(TAG, "notifyInputRemovedLocked: inputId = " + inputId);
751        }
752        for (ITvInputManagerCallback callback : userState.callbackSet) {
753            try {
754                callback.onInputRemoved(inputId);
755            } catch (RemoteException e) {
756                Slog.e(TAG, "Failed to report removed input to callback.");
757            }
758        }
759    }
760
761    private void notifyInputStateChangedLocked(UserState userState, String inputId,
762            int state, ITvInputManagerCallback targetCallback) {
763        if (DEBUG) {
764            Slog.d(TAG, "notifyInputStateChangedLocked: inputId = " + inputId
765                    + "; state = " + state);
766        }
767        if (targetCallback == null) {
768            for (ITvInputManagerCallback callback : userState.callbackSet) {
769                try {
770                    callback.onInputStateChanged(inputId, state);
771                } catch (RemoteException e) {
772                    Slog.e(TAG, "Failed to report state change to callback.");
773                }
774            }
775        } else {
776            try {
777                targetCallback.onInputStateChanged(inputId, state);
778            } catch (RemoteException e) {
779                Slog.e(TAG, "Failed to report state change to callback.");
780            }
781        }
782    }
783
784    private void setStateLocked(String inputId, int state, int userId) {
785        UserState userState = getUserStateLocked(userId);
786        TvInputState inputState = userState.inputMap.get(inputId);
787        ServiceState serviceState = userState.serviceStateMap.get(inputId);
788        int oldState = inputState.mState;
789        inputState.mState = state;
790        if (serviceState != null && serviceState.mService == null
791                && shouldMaintainConnection(serviceState)) {
792            // We don't notify state change while reconnecting. It should remain disconnected.
793            return;
794        }
795        if (oldState != state) {
796            notifyInputStateChangedLocked(userState, inputId, state, null);
797        }
798    }
799
800    private final class BinderService extends ITvInputManager.Stub {
801        @Override
802        public List<TvInputInfo> getTvInputList(int userId) {
803            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
804                    Binder.getCallingUid(), userId, "getTvInputList");
805            final long identity = Binder.clearCallingIdentity();
806            try {
807                synchronized (mLock) {
808                    UserState userState = getUserStateLocked(resolvedUserId);
809                    List<TvInputInfo> inputList = new ArrayList<TvInputInfo>();
810                    for (TvInputState state : userState.inputMap.values()) {
811                        inputList.add(state.mInfo);
812                    }
813                    return inputList;
814                }
815            } finally {
816                Binder.restoreCallingIdentity(identity);
817            }
818        }
819
820        @Override
821        public TvInputInfo getTvInputInfo(String inputId, int userId) {
822            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
823                    Binder.getCallingUid(), userId, "getTvInputInfo");
824            final long identity = Binder.clearCallingIdentity();
825            try {
826                synchronized (mLock) {
827                    UserState userState = getUserStateLocked(resolvedUserId);
828                    TvInputState state = userState.inputMap.get(inputId);
829                    return state == null ? null : state.mInfo;
830                }
831            } finally {
832                Binder.restoreCallingIdentity(identity);
833            }
834        }
835
836        @Override
837        public void registerCallback(final ITvInputManagerCallback callback, int userId) {
838            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
839                    Binder.getCallingUid(), userId, "registerCallback");
840            final long identity = Binder.clearCallingIdentity();
841            try {
842                synchronized (mLock) {
843                    UserState userState = getUserStateLocked(resolvedUserId);
844                    userState.callbackSet.add(callback);
845                    for (TvInputState state : userState.inputMap.values()) {
846                        notifyInputStateChangedLocked(userState, state.mInfo.getId(),
847                                state.mState, callback);
848                    }
849                }
850            } finally {
851                Binder.restoreCallingIdentity(identity);
852            }
853        }
854
855        @Override
856        public void unregisterCallback(ITvInputManagerCallback callback, int userId) {
857            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
858                    Binder.getCallingUid(), userId, "unregisterCallback");
859            final long identity = Binder.clearCallingIdentity();
860            try {
861                synchronized (mLock) {
862                    UserState userState = getUserStateLocked(resolvedUserId);
863                    userState.callbackSet.remove(callback);
864                }
865            } finally {
866                Binder.restoreCallingIdentity(identity);
867            }
868        }
869
870        @Override
871        public void createSession(final ITvInputClient client, final String inputId,
872                int seq, int userId) {
873            final int callingUid = Binder.getCallingUid();
874            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
875                    userId, "createSession");
876            final long identity = Binder.clearCallingIdentity();
877            try {
878                synchronized (mLock) {
879                    UserState userState = getUserStateLocked(resolvedUserId);
880                    TvInputInfo info = userState.inputMap.get(inputId).mInfo;
881                    ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
882                    if (serviceState == null) {
883                        serviceState = new ServiceState(info.getComponent(), resolvedUserId);
884                        userState.serviceStateMap.put(info.getComponent(), serviceState);
885                    }
886                    // Send a null token immediately while reconnecting.
887                    if (serviceState.mReconnecting == true) {
888                        sendSessionTokenToClientLocked(client, inputId, null, null, seq);
889                        return;
890                    }
891
892                    // Create a new session token and a session state.
893                    IBinder sessionToken = new Binder();
894                    SessionState sessionState = new SessionState(sessionToken, info, client,
895                            seq, callingUid, resolvedUserId);
896
897                    // Add them to the global session state map of the current user.
898                    userState.sessionStateMap.put(sessionToken, sessionState);
899
900                    // Also, add them to the session state map of the current service.
901                    serviceState.mSessionTokens.add(sessionToken);
902
903                    if (serviceState.mService != null) {
904                        createSessionInternalLocked(serviceState.mService, sessionToken,
905                                resolvedUserId);
906                    } else {
907                        updateServiceConnectionLocked(info.getComponent(), resolvedUserId);
908                    }
909                }
910            } finally {
911                Binder.restoreCallingIdentity(identity);
912            }
913        }
914
915        @Override
916        public void releaseSession(IBinder sessionToken, int userId) {
917            final int callingUid = Binder.getCallingUid();
918            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
919                    userId, "releaseSession");
920            final long identity = Binder.clearCallingIdentity();
921            try {
922                synchronized (mLock) {
923                    releaseSessionLocked(sessionToken, callingUid, resolvedUserId);
924                }
925            } finally {
926                Binder.restoreCallingIdentity(identity);
927            }
928        }
929
930        @Override
931        public void setMainSession(IBinder sessionToken, int userId) {
932            final int callingUid = Binder.getCallingUid();
933            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
934                    userId, "setMainSession");
935            final long identity = Binder.clearCallingIdentity();
936            try {
937                synchronized (mLock) {
938                    UserState userState = getUserStateLocked(resolvedUserId);
939                    if (sessionToken == userState.mainSessionToken) {
940                        return;
941                    }
942
943                    SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
944                            resolvedUserId);
945                    ServiceState serviceState = getServiceStateLocked(
946                            sessionState.mInfo.getComponent(), resolvedUserId);
947                    ITvInputSession session = getSessionLocked(sessionState);
948
949                    ServiceState prevMainServiceState = null;
950                    ITvInputSession prevMainSession = null;
951                    if (userState.mainSessionToken != null) {
952                        SessionState prevMainSessionState = getSessionStateLocked(
953                                userState.mainSessionToken, Process.SYSTEM_UID, resolvedUserId);
954                        prevMainServiceState = getServiceStateLocked(
955                                prevMainSessionState.mInfo.getComponent(), resolvedUserId);
956                        prevMainSession = getSessionLocked(prevMainSessionState);
957                    }
958
959                    userState.mainSessionToken = sessionToken;
960
961                    // Inform the new main session first. See {@link TvInputService#onSetMain}.
962                    if (serviceState.mIsHardware) {
963                        try {
964                            session.setMainSession(true);
965                        } catch (RemoteException e) {
966                            Slog.e(TAG, "error in setMainSession", e);
967                        }
968                    }
969                    if (prevMainSession != null && prevMainServiceState.mIsHardware) {
970                        try {
971                            prevMainSession.setMainSession(false);
972                        } catch (RemoteException e) {
973                            Slog.e(TAG, "error in setMainSession", e);
974                        }
975                    }
976                }
977            } finally {
978                Binder.restoreCallingIdentity(identity);
979            }
980        }
981
982        @Override
983        public void setSurface(IBinder sessionToken, Surface surface, int userId) {
984            final int callingUid = Binder.getCallingUid();
985            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
986                    userId, "setSurface");
987            final long identity = Binder.clearCallingIdentity();
988            try {
989                synchronized (mLock) {
990                    try {
991                        getSessionLocked(sessionToken, callingUid, resolvedUserId).setSurface(
992                                surface);
993                    } catch (RemoteException e) {
994                        Slog.e(TAG, "error in setSurface", e);
995                    }
996                }
997            } finally {
998                if (surface != null) {
999                    // surface is not used in TvInputManagerService.
1000                    surface.release();
1001                }
1002                Binder.restoreCallingIdentity(identity);
1003            }
1004        }
1005
1006        @Override
1007        public void dispatchSurfaceChanged(IBinder sessionToken, int format, int width,
1008                int height, int userId) {
1009            final int callingUid = Binder.getCallingUid();
1010            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1011                    userId, "dispatchSurfaceChanged");
1012            final long identity = Binder.clearCallingIdentity();
1013            try {
1014                synchronized (mLock) {
1015                    try {
1016                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
1017                                .dispatchSurfaceChanged(format, width, height);
1018                    } catch (RemoteException e) {
1019                        Slog.e(TAG, "error in dispatchSurfaceChanged", e);
1020                    }
1021                }
1022            } finally {
1023                Binder.restoreCallingIdentity(identity);
1024            }
1025        }
1026
1027        @Override
1028        public void setVolume(IBinder sessionToken, float volume, int userId) {
1029            final int callingUid = Binder.getCallingUid();
1030            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1031                    userId, "setVolume");
1032            final long identity = Binder.clearCallingIdentity();
1033            try {
1034                synchronized (mLock) {
1035                    try {
1036                        getSessionLocked(sessionToken, callingUid, resolvedUserId).setVolume(
1037                                volume);
1038                    } catch (RemoteException e) {
1039                        Slog.e(TAG, "error in setVolume", e);
1040                    }
1041                }
1042            } finally {
1043                Binder.restoreCallingIdentity(identity);
1044            }
1045        }
1046
1047        @Override
1048        public void tune(IBinder sessionToken, final Uri channelUri, int userId) {
1049            final int callingUid = Binder.getCallingUid();
1050            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1051                    userId, "tune");
1052            final long identity = Binder.clearCallingIdentity();
1053            try {
1054                synchronized (mLock) {
1055                    try {
1056                        getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(channelUri);
1057                        if (TvContract.isChannelUriForPassthroughTvInput(channelUri)) {
1058                            // Do not log the watch history for passthrough inputs.
1059                            return;
1060                        }
1061                        long currentTime = System.currentTimeMillis();
1062                        long channelId = ContentUris.parseId(channelUri);
1063
1064                        // Close the open log entry first, if any.
1065                        UserState userState = getUserStateLocked(resolvedUserId);
1066                        SessionState sessionState = userState.sessionStateMap.get(sessionToken);
1067                        if (sessionState.mLogUri != null) {
1068                            SomeArgs args = SomeArgs.obtain();
1069                            args.arg1 = sessionState.mLogUri;
1070                            args.arg2 = currentTime;
1071                            mLogHandler.obtainMessage(LogHandler.MSG_CLOSE_ENTRY, args)
1072                                    .sendToTarget();
1073                        }
1074
1075                        // Create a log entry and fill it later.
1076                        String packageName = sessionState.mInfo.getServiceInfo().packageName;
1077                        ContentValues values = new ContentValues();
1078                        values.put(TvContract.WatchedPrograms.COLUMN_PACKAGE_NAME, packageName);
1079                        values.put(TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
1080                                currentTime);
1081                        values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, 0);
1082                        values.put(TvContract.WatchedPrograms.COLUMN_CHANNEL_ID, channelId);
1083
1084                        sessionState.mLogUri = mContentResolver.insert(
1085                                TvContract.WatchedPrograms.CONTENT_URI, values);
1086                        SomeArgs args = SomeArgs.obtain();
1087                        args.arg1 = sessionState.mLogUri;
1088                        args.arg2 = ContentUris.parseId(channelUri);
1089                        args.arg3 = currentTime;
1090                        mLogHandler.obtainMessage(LogHandler.MSG_OPEN_ENTRY, args).sendToTarget();
1091                    } catch (RemoteException e) {
1092                        Slog.e(TAG, "error in tune", e);
1093                        return;
1094                    }
1095                }
1096            } finally {
1097                Binder.restoreCallingIdentity(identity);
1098            }
1099        }
1100
1101        @Override
1102        public void requestUnblockContent(
1103                IBinder sessionToken, String unblockedRating, int userId) {
1104            final int callingUid = Binder.getCallingUid();
1105            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1106                    userId, "unblockContent");
1107            final long identity = Binder.clearCallingIdentity();
1108            try {
1109                synchronized (mLock) {
1110                    try {
1111                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
1112                                .requestUnblockContent(unblockedRating);
1113                    } catch (RemoteException e) {
1114                        Slog.e(TAG, "error in unblockContent", e);
1115                    }
1116                }
1117            } finally {
1118                Binder.restoreCallingIdentity(identity);
1119            }
1120        }
1121
1122        @Override
1123        public void setCaptionEnabled(IBinder sessionToken, boolean enabled, int userId) {
1124            final int callingUid = Binder.getCallingUid();
1125            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1126                    userId, "setCaptionEnabled");
1127            final long identity = Binder.clearCallingIdentity();
1128            try {
1129                synchronized (mLock) {
1130                    try {
1131                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
1132                                .setCaptionEnabled(enabled);
1133                    } catch (RemoteException e) {
1134                        Slog.e(TAG, "error in setCaptionEnabled", e);
1135                    }
1136                }
1137            } finally {
1138                Binder.restoreCallingIdentity(identity);
1139            }
1140        }
1141
1142        @Override
1143        public void selectTrack(IBinder sessionToken, TvTrackInfo track, int userId) {
1144            final int callingUid = Binder.getCallingUid();
1145            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1146                    userId, "selectTrack");
1147            final long identity = Binder.clearCallingIdentity();
1148            try {
1149                synchronized (mLock) {
1150                    try {
1151                        getSessionLocked(sessionToken, callingUid, resolvedUserId).selectTrack(
1152                                track);
1153                    } catch (RemoteException e) {
1154                        Slog.e(TAG, "error in selectTrack", e);
1155                    }
1156                }
1157            } finally {
1158                Binder.restoreCallingIdentity(identity);
1159            }
1160        }
1161
1162        @Override
1163        public void unselectTrack(IBinder sessionToken, TvTrackInfo track, int userId) {
1164            final int callingUid = Binder.getCallingUid();
1165            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1166                    userId, "unselectTrack");
1167            final long identity = Binder.clearCallingIdentity();
1168            try {
1169                synchronized (mLock) {
1170                    try {
1171                        getSessionLocked(sessionToken, callingUid, resolvedUserId).unselectTrack(
1172                                track);
1173                    } catch (RemoteException e) {
1174                        Slog.e(TAG, "error in unselectTrack", e);
1175                    }
1176                }
1177            } finally {
1178                Binder.restoreCallingIdentity(identity);
1179            }
1180        }
1181
1182        @Override
1183        public void sendAppPrivateCommand(IBinder sessionToken, String command, Bundle data,
1184                int userId) {
1185            final int callingUid = Binder.getCallingUid();
1186            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1187                    userId, "sendAppPrivateCommand");
1188            final long identity = Binder.clearCallingIdentity();
1189            try {
1190                synchronized (mLock) {
1191                    try {
1192                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
1193                                .appPrivateCommand(command, data);
1194                    } catch (RemoteException e) {
1195                        Slog.e(TAG, "error in sendAppPrivateCommand", e);
1196                    }
1197                }
1198            } finally {
1199                Binder.restoreCallingIdentity(identity);
1200            }
1201        }
1202
1203        @Override
1204        public void createOverlayView(IBinder sessionToken, IBinder windowToken, Rect frame,
1205                int userId) {
1206            final int callingUid = Binder.getCallingUid();
1207            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1208                    userId, "createOverlayView");
1209            final long identity = Binder.clearCallingIdentity();
1210            try {
1211                synchronized (mLock) {
1212                    try {
1213                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
1214                                .createOverlayView(windowToken, frame);
1215                    } catch (RemoteException e) {
1216                        Slog.e(TAG, "error in createOverlayView", e);
1217                    }
1218                }
1219            } finally {
1220                Binder.restoreCallingIdentity(identity);
1221            }
1222        }
1223
1224        @Override
1225        public void relayoutOverlayView(IBinder sessionToken, Rect frame, int userId) {
1226            final int callingUid = Binder.getCallingUid();
1227            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1228                    userId, "relayoutOverlayView");
1229            final long identity = Binder.clearCallingIdentity();
1230            try {
1231                synchronized (mLock) {
1232                    try {
1233                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
1234                                .relayoutOverlayView(frame);
1235                    } catch (RemoteException e) {
1236                        Slog.e(TAG, "error in relayoutOverlayView", e);
1237                    }
1238                }
1239            } finally {
1240                Binder.restoreCallingIdentity(identity);
1241            }
1242        }
1243
1244        @Override
1245        public void removeOverlayView(IBinder sessionToken, int userId) {
1246            final int callingUid = Binder.getCallingUid();
1247            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1248                    userId, "removeOverlayView");
1249            final long identity = Binder.clearCallingIdentity();
1250            try {
1251                synchronized (mLock) {
1252                    try {
1253                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
1254                                .removeOverlayView();
1255                    } catch (RemoteException e) {
1256                        Slog.e(TAG, "error in removeOverlayView", e);
1257                    }
1258                }
1259            } finally {
1260                Binder.restoreCallingIdentity(identity);
1261            }
1262        }
1263
1264        @Override
1265        public List<TvInputHardwareInfo> getHardwareList() throws RemoteException {
1266            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
1267                    != PackageManager.PERMISSION_GRANTED) {
1268                return null;
1269            }
1270
1271            final long identity = Binder.clearCallingIdentity();
1272            try {
1273                return mTvInputHardwareManager.getHardwareList();
1274            } finally {
1275                Binder.restoreCallingIdentity(identity);
1276            }
1277        }
1278
1279        @Override
1280        public ITvInputHardware acquireTvInputHardware(int deviceId,
1281                ITvInputHardwareCallback callback, TvInputInfo info, int userId)
1282                throws RemoteException {
1283            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
1284                    != PackageManager.PERMISSION_GRANTED) {
1285                return null;
1286            }
1287
1288            final long identity = Binder.clearCallingIdentity();
1289            final int callingUid = Binder.getCallingUid();
1290            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1291                    userId, "acquireTvInputHardware");
1292            try {
1293                return mTvInputHardwareManager.acquireHardware(
1294                        deviceId, callback, info, callingUid, resolvedUserId);
1295            } finally {
1296                Binder.restoreCallingIdentity(identity);
1297            }
1298        }
1299
1300        @Override
1301        public void releaseTvInputHardware(int deviceId, ITvInputHardware hardware, int userId)
1302                throws RemoteException {
1303            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
1304                    != PackageManager.PERMISSION_GRANTED) {
1305                return;
1306            }
1307
1308            final long identity = Binder.clearCallingIdentity();
1309            final int callingUid = Binder.getCallingUid();
1310            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1311                    userId, "releaseTvInputHardware");
1312            try {
1313                mTvInputHardwareManager.releaseHardware(
1314                        deviceId, hardware, callingUid, resolvedUserId);
1315            } finally {
1316                Binder.restoreCallingIdentity(identity);
1317            }
1318        }
1319
1320        @Override
1321        public List<TvStreamConfig> getAvailableTvStreamConfigList(String inputId, int userId)
1322                throws RemoteException {
1323            if (mContext.checkCallingPermission(
1324                    android.Manifest.permission.CAPTURE_TV_INPUT)
1325                    != PackageManager.PERMISSION_GRANTED) {
1326                throw new SecurityException("Requires CAPTURE_TV_INPUT permission");
1327            }
1328
1329            final long identity = Binder.clearCallingIdentity();
1330            final int callingUid = Binder.getCallingUid();
1331            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1332                    userId, "getAvailableTvStreamConfigList");
1333            try {
1334                return mTvInputHardwareManager.getAvailableTvStreamConfigList(
1335                        inputId, callingUid, resolvedUserId);
1336            } finally {
1337                Binder.restoreCallingIdentity(identity);
1338            }
1339        }
1340
1341        @Override
1342        public boolean captureFrame(String inputId, Surface surface, TvStreamConfig config,
1343                int userId)
1344                throws RemoteException {
1345            if (mContext.checkCallingPermission(
1346                    android.Manifest.permission.CAPTURE_TV_INPUT)
1347                    != PackageManager.PERMISSION_GRANTED) {
1348                throw new SecurityException("Requires CAPTURE_TV_INPUT permission");
1349            }
1350
1351            final long identity = Binder.clearCallingIdentity();
1352            final int callingUid = Binder.getCallingUid();
1353            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1354                    userId, "captureFrame");
1355            try {
1356                final String wrappedInputId;
1357                synchronized (mLock) {
1358                    UserState userState = getUserStateLocked(resolvedUserId);
1359                    wrappedInputId = userState.wrappedInputMap.get(inputId);
1360                }
1361                return mTvInputHardwareManager.captureFrame(
1362                        (wrappedInputId != null) ? wrappedInputId : inputId,
1363                        surface, config, callingUid, resolvedUserId);
1364            } finally {
1365                Binder.restoreCallingIdentity(identity);
1366            }
1367        }
1368
1369        @Override
1370        @SuppressWarnings("resource")
1371        protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
1372            final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
1373            if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
1374                    != PackageManager.PERMISSION_GRANTED) {
1375                pw.println("Permission Denial: can't dump TvInputManager from pid="
1376                        + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
1377                return;
1378            }
1379
1380            synchronized (mLock) {
1381                pw.println("User Ids (Current user: " + mCurrentUserId + "):");
1382                pw.increaseIndent();
1383                for (int i = 0; i < mUserStates.size(); i++) {
1384                    int userId = mUserStates.keyAt(i);
1385                    pw.println(Integer.valueOf(userId));
1386                }
1387                pw.decreaseIndent();
1388
1389                for (int i = 0; i < mUserStates.size(); i++) {
1390                    int userId = mUserStates.keyAt(i);
1391                    UserState userState = getUserStateLocked(userId);
1392                    pw.println("UserState (" + userId + "):");
1393                    pw.increaseIndent();
1394
1395                    pw.println("inputMap: inputId -> TvInputState");
1396                    pw.increaseIndent();
1397                    for (Map.Entry<String, TvInputState> entry: userState.inputMap.entrySet()) {
1398                        pw.println(entry.getKey() + ": " + entry.getValue());
1399                    }
1400                    pw.decreaseIndent();
1401
1402                    pw.println("packageSet:");
1403                    pw.increaseIndent();
1404                    for (String packageName : userState.packageSet) {
1405                        pw.println(packageName);
1406                    }
1407                    pw.decreaseIndent();
1408
1409                    pw.println("clientStateMap: ITvInputClient -> ClientState");
1410                    pw.increaseIndent();
1411                    for (Map.Entry<IBinder, ClientState> entry :
1412                            userState.clientStateMap.entrySet()) {
1413                        ClientState client = entry.getValue();
1414                        pw.println(entry.getKey() + ": " + client);
1415
1416                        pw.increaseIndent();
1417
1418                        pw.println("mInputIds:");
1419                        pw.increaseIndent();
1420                        for (String inputId : client.mInputIds) {
1421                            pw.println(inputId);
1422                        }
1423                        pw.decreaseIndent();
1424
1425                        pw.println("mSessionTokens:");
1426                        pw.increaseIndent();
1427                        for (IBinder token : client.mSessionTokens) {
1428                            pw.println("" + token);
1429                        }
1430                        pw.decreaseIndent();
1431
1432                        pw.println("mClientTokens: " + client.mClientToken);
1433                        pw.println("mUserId: " + client.mUserId);
1434
1435                        pw.decreaseIndent();
1436                    }
1437                    pw.decreaseIndent();
1438
1439                    pw.println("serviceStateMap: ComponentName -> ServiceState");
1440                    pw.increaseIndent();
1441                    for (Map.Entry<ComponentName, ServiceState> entry :
1442                            userState.serviceStateMap.entrySet()) {
1443                        ServiceState service = entry.getValue();
1444                        pw.println(entry.getKey() + ": " + service);
1445
1446                        pw.increaseIndent();
1447
1448                        pw.println("mClientTokens:");
1449                        pw.increaseIndent();
1450                        for (IBinder token : service.mClientTokens) {
1451                            pw.println("" + token);
1452                        }
1453                        pw.decreaseIndent();
1454
1455                        pw.println("mSessionTokens:");
1456                        pw.increaseIndent();
1457                        for (IBinder token : service.mSessionTokens) {
1458                            pw.println("" + token);
1459                        }
1460                        pw.decreaseIndent();
1461
1462                        pw.println("mService: " + service.mService);
1463                        pw.println("mCallback: " + service.mCallback);
1464                        pw.println("mBound: " + service.mBound);
1465                        pw.println("mReconnecting: " + service.mReconnecting);
1466
1467                        pw.decreaseIndent();
1468                    }
1469                    pw.decreaseIndent();
1470
1471                    pw.println("sessionStateMap: ITvInputSession -> SessionState");
1472                    pw.increaseIndent();
1473                    for (Map.Entry<IBinder, SessionState> entry :
1474                            userState.sessionStateMap.entrySet()) {
1475                        SessionState session = entry.getValue();
1476                        pw.println(entry.getKey() + ": " + session);
1477
1478                        pw.increaseIndent();
1479                        pw.println("mInfo: " + session.mInfo);
1480                        pw.println("mClient: " + session.mClient);
1481                        pw.println("mSeq: " + session.mSeq);
1482                        pw.println("mCallingUid: " + session.mCallingUid);
1483                        pw.println("mUserId: " + session.mUserId);
1484                        pw.println("mSessionToken: " + session.mSessionToken);
1485                        pw.println("mSession: " + session.mSession);
1486                        pw.println("mLogUri: " + session.mLogUri);
1487                        pw.decreaseIndent();
1488                    }
1489                    pw.decreaseIndent();
1490
1491                    pw.println("callbackSet:");
1492                    pw.increaseIndent();
1493                    for (ITvInputManagerCallback callback : userState.callbackSet) {
1494                        pw.println(callback.toString());
1495                    }
1496                    pw.decreaseIndent();
1497
1498                    pw.decreaseIndent();
1499                }
1500            }
1501        }
1502    }
1503
1504    private static final class TvInputState {
1505        // A TvInputInfo object which represents the TV input.
1506        private TvInputInfo mInfo;
1507
1508        // The state of TV input. Connected by default.
1509        private int mState = INPUT_STATE_CONNECTED;
1510
1511        @Override
1512        public String toString() {
1513            return "mInfo: " + mInfo + "; mState: " + mState;
1514        }
1515    }
1516
1517    private static final class UserState {
1518        // A mapping from the TV input id to its TvInputState.
1519        private Map<String, TvInputState> inputMap = new HashMap<String, TvInputState>();
1520
1521        // A set of all TV input packages.
1522        private final Set<String> packageSet = new HashSet<String>();
1523
1524        // A mapping from the token of a client to its state.
1525        private final Map<IBinder, ClientState> clientStateMap =
1526                new HashMap<IBinder, ClientState>();
1527
1528        // A mapping from the name of a TV input service to its state.
1529        private final Map<ComponentName, ServiceState> serviceStateMap =
1530                new HashMap<ComponentName, ServiceState>();
1531
1532        // A mapping from the token of a TV input session to its state.
1533        private final Map<IBinder, SessionState> sessionStateMap =
1534                new HashMap<IBinder, SessionState>();
1535
1536        // A set of callbacks.
1537        private final Set<ITvInputManagerCallback> callbackSet =
1538                new HashSet<ITvInputManagerCallback>();
1539
1540        // A mapping from the TV input id to wrapped input id.
1541        private final Map<String, String> wrappedInputMap = new HashMap<String, String>();
1542
1543        // The token of a "main" TV input session.
1544        private IBinder mainSessionToken = null;
1545    }
1546
1547    private final class ClientState implements IBinder.DeathRecipient {
1548        private final List<String> mInputIds = new ArrayList<String>();
1549        private final List<IBinder> mSessionTokens = new ArrayList<IBinder>();
1550
1551        private IBinder mClientToken;
1552        private final int mUserId;
1553
1554        ClientState(IBinder clientToken, int userId) {
1555            mClientToken = clientToken;
1556            mUserId = userId;
1557        }
1558
1559        public boolean isEmpty() {
1560            return mInputIds.isEmpty() && mSessionTokens.isEmpty();
1561        }
1562
1563        @Override
1564        public void binderDied() {
1565            synchronized (mLock) {
1566                UserState userState = getUserStateLocked(mUserId);
1567                // DO NOT remove the client state of clientStateMap in this method. It will be
1568                // removed in releaseSessionLocked() or unregisterClientInternalLocked().
1569                ClientState clientState = userState.clientStateMap.get(mClientToken);
1570                if (clientState != null) {
1571                    while (clientState.mSessionTokens.size() > 0) {
1572                        releaseSessionLocked(
1573                                clientState.mSessionTokens.get(0), Process.SYSTEM_UID, mUserId);
1574                    }
1575                    while (clientState.mInputIds.size() > 0) {
1576                        unregisterClientInternalLocked(
1577                                mClientToken, clientState.mInputIds.get(0), mUserId);
1578                    }
1579                }
1580                mClientToken = null;
1581            }
1582        }
1583    }
1584
1585    private final class ServiceState {
1586        private final List<IBinder> mClientTokens = new ArrayList<IBinder>();
1587        private final List<IBinder> mSessionTokens = new ArrayList<IBinder>();
1588        private final ServiceConnection mConnection;
1589        private final ComponentName mName;
1590        private final boolean mIsHardware;
1591        private final List<TvInputInfo> mInputList = new ArrayList<TvInputInfo>();
1592
1593        private ITvInputService mService;
1594        private ServiceCallback mCallback;
1595        private boolean mBound;
1596        private boolean mReconnecting;
1597
1598        private ServiceState(ComponentName name, int userId) {
1599            mName = name;
1600            mConnection = new InputServiceConnection(name, userId);
1601            mIsHardware = hasHardwarePermission(mContext.getPackageManager(), mName);
1602        }
1603    }
1604
1605    private final class SessionState implements IBinder.DeathRecipient {
1606        private final TvInputInfo mInfo;
1607        private final ITvInputClient mClient;
1608        private final int mSeq;
1609        private final int mCallingUid;
1610        private final int mUserId;
1611        private final IBinder mSessionToken;
1612        private ITvInputSession mSession;
1613        private Uri mLogUri;
1614
1615        private SessionState(IBinder sessionToken, TvInputInfo info, ITvInputClient client, int seq,
1616                int callingUid, int userId) {
1617            mSessionToken = sessionToken;
1618            mInfo = info;
1619            mClient = client;
1620            mSeq = seq;
1621            mCallingUid = callingUid;
1622            mUserId = userId;
1623        }
1624
1625        @Override
1626        public void binderDied() {
1627            synchronized (mLock) {
1628                mSession = null;
1629                if (mClient != null) {
1630                    try {
1631                        mClient.onSessionReleased(mSeq);
1632                    } catch(RemoteException e) {
1633                        Slog.e(TAG, "error in onSessionReleased", e);
1634                    }
1635                }
1636                removeSessionStateLocked(mSessionToken, mUserId);
1637            }
1638        }
1639    }
1640
1641    private final class InputServiceConnection implements ServiceConnection {
1642        private final ComponentName mName;
1643        private final int mUserId;
1644
1645        private InputServiceConnection(ComponentName name, int userId) {
1646            mName = name;
1647            mUserId = userId;
1648        }
1649
1650        @Override
1651        public void onServiceConnected(ComponentName name, IBinder service) {
1652            if (DEBUG) {
1653                Slog.d(TAG, "onServiceConnected(name=" + name + ")");
1654            }
1655            synchronized (mLock) {
1656                UserState userState = getUserStateLocked(mUserId);
1657                ServiceState serviceState = userState.serviceStateMap.get(mName);
1658                serviceState.mService = ITvInputService.Stub.asInterface(service);
1659
1660                // Register a callback, if we need to.
1661                if (serviceState.mIsHardware && serviceState.mCallback == null) {
1662                    serviceState.mCallback = new ServiceCallback(mName, mUserId);
1663                    try {
1664                        serviceState.mService.registerCallback(serviceState.mCallback);
1665                    } catch (RemoteException e) {
1666                        Slog.e(TAG, "error in registerCallback", e);
1667                    }
1668                }
1669
1670                // And create sessions, if any.
1671                for (IBinder sessionToken : serviceState.mSessionTokens) {
1672                    createSessionInternalLocked(serviceState.mService, sessionToken, mUserId);
1673                }
1674
1675                for (TvInputState inputState : userState.inputMap.values()) {
1676                    if (inputState.mInfo.getComponent().equals(name)
1677                            && inputState.mState != INPUT_STATE_DISCONNECTED) {
1678                        notifyInputStateChangedLocked(userState, inputState.mInfo.getId(),
1679                                inputState.mState, null);
1680                    }
1681                }
1682
1683                if (serviceState.mIsHardware) {
1684                    List<TvInputHardwareInfo> hardwareInfoList =
1685                            mTvInputHardwareManager.getHardwareList();
1686                    for (TvInputHardwareInfo hardwareInfo : hardwareInfoList) {
1687                        try {
1688                            serviceState.mService.notifyHardwareAdded(hardwareInfo);
1689                        } catch (RemoteException e) {
1690                            Slog.e(TAG, "error in notifyHardwareAdded", e);
1691                        }
1692                    }
1693
1694                    List<HdmiCecDeviceInfo> cecDeviceInfoList =
1695                            mTvInputHardwareManager.getHdmiCecInputDeviceList();
1696                    for (HdmiCecDeviceInfo cecDeviceInfo : cecDeviceInfoList) {
1697                        try {
1698                            serviceState.mService.notifyHdmiCecDeviceAdded(cecDeviceInfo);
1699                        } catch (RemoteException e) {
1700                            Slog.e(TAG, "error in notifyHdmiCecDeviceAdded", e);
1701                        }
1702                    }
1703                }
1704            }
1705        }
1706
1707        @Override
1708        public void onServiceDisconnected(ComponentName name) {
1709            if (DEBUG) {
1710                Slog.d(TAG, "onServiceDisconnected(name=" + name + ")");
1711            }
1712            if (!mName.equals(name)) {
1713                throw new IllegalArgumentException("Mismatched ComponentName: "
1714                        + mName + " (expected), " + name + " (actual).");
1715            }
1716            synchronized (mLock) {
1717                UserState userState = getUserStateLocked(mUserId);
1718                ServiceState serviceState = userState.serviceStateMap.get(mName);
1719                if (serviceState != null) {
1720                    serviceState.mReconnecting = true;
1721                    serviceState.mBound = false;
1722                    serviceState.mService = null;
1723                    serviceState.mCallback = null;
1724
1725                    // Send null tokens for not finishing create session events.
1726                    for (IBinder sessionToken : serviceState.mSessionTokens) {
1727                        SessionState sessionState = userState.sessionStateMap.get(sessionToken);
1728                        if (sessionState.mSession == null) {
1729                            removeSessionStateLocked(sessionToken, sessionState.mUserId);
1730                            sendSessionTokenToClientLocked(sessionState.mClient,
1731                                    sessionState.mInfo.getId(), null, null, sessionState.mSeq);
1732                        }
1733                    }
1734
1735                    for (TvInputState inputState : userState.inputMap.values()) {
1736                        if (inputState.mInfo.getComponent().equals(name)) {
1737                            String inputId = inputState.mInfo.getId();
1738                            notifyInputStateChangedLocked(userState, inputId,
1739                                    INPUT_STATE_DISCONNECTED, null);
1740                            userState.wrappedInputMap.remove(inputId);
1741                        }
1742                    }
1743                    updateServiceConnectionLocked(mName, mUserId);
1744                }
1745            }
1746        }
1747    }
1748
1749    private final class ServiceCallback extends ITvInputServiceCallback.Stub {
1750        private final ComponentName mName;
1751        private final int mUserId;
1752
1753        ServiceCallback(ComponentName name, int userId) {
1754            mName = name;
1755            mUserId = userId;
1756        }
1757
1758        private void ensureHardwarePermission() {
1759            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
1760                    != PackageManager.PERMISSION_GRANTED) {
1761                throw new SecurityException("The caller does not have hardware permission");
1762            }
1763        }
1764
1765        private void ensureValidInput(TvInputInfo inputInfo) {
1766            if (inputInfo.getId() == null || !mName.equals(inputInfo.getComponent())) {
1767                throw new IllegalArgumentException("Invalid TvInputInfo");
1768            }
1769        }
1770
1771        private void addTvInputLocked(TvInputInfo inputInfo) {
1772            ServiceState serviceState = getServiceStateLocked(mName, mUserId);
1773            serviceState.mInputList.add(inputInfo);
1774            buildTvInputListLocked(mUserId);
1775        }
1776
1777        @Override
1778        public void addHardwareTvInput(int deviceId, TvInputInfo inputInfo) {
1779            ensureHardwarePermission();
1780            ensureValidInput(inputInfo);
1781            synchronized (mLock) {
1782                mTvInputHardwareManager.addHardwareTvInput(deviceId, inputInfo);
1783                addTvInputLocked(inputInfo);
1784            }
1785        }
1786
1787        @Override
1788        public void addHdmiCecTvInput(int logicalAddress, TvInputInfo inputInfo) {
1789            ensureHardwarePermission();
1790            ensureValidInput(inputInfo);
1791            synchronized (mLock) {
1792                mTvInputHardwareManager.addHdmiCecTvInput(logicalAddress, inputInfo);
1793                addTvInputLocked(inputInfo);
1794            }
1795        }
1796
1797        @Override
1798        public void removeTvInput(String inputId) {
1799            ensureHardwarePermission();
1800            synchronized (mLock) {
1801                ServiceState serviceState = getServiceStateLocked(mName, mUserId);
1802                boolean removed = false;
1803                for (Iterator<TvInputInfo> it = serviceState.mInputList.iterator();
1804                        it.hasNext(); ) {
1805                    if (it.next().getId().equals(inputId)) {
1806                        it.remove();
1807                        removed = true;
1808                        break;
1809                    }
1810                }
1811                if (removed) {
1812                    buildTvInputListLocked(mUserId);
1813                    mTvInputHardwareManager.removeTvInput(inputId);
1814                } else {
1815                    Slog.e(TAG, "TvInputInfo with inputId=" + inputId + " not found.");
1816                }
1817            }
1818        }
1819
1820        @Override
1821        public void setWrappedInputId(String inputId, String wrappedInputId) {
1822            synchronized (mLock) {
1823                if (!hasInputIdLocked(inputId)) {
1824                    return;
1825                }
1826                UserState userState = getUserStateLocked(mUserId);
1827                userState.wrappedInputMap.put(inputId, wrappedInputId);
1828            }
1829        }
1830
1831        private boolean hasInputIdLocked(String inputId) {
1832            ServiceState serviceState = getServiceStateLocked(mName, mUserId);
1833            for (TvInputInfo inputInfo : serviceState.mInputList) {
1834                if (inputInfo.getId().equals(inputId)) {
1835                    return true;
1836                }
1837            }
1838            return false;
1839        }
1840    }
1841
1842    private final class LogHandler extends Handler {
1843        private static final int MSG_OPEN_ENTRY = 1;
1844        private static final int MSG_UPDATE_ENTRY = 2;
1845        private static final int MSG_CLOSE_ENTRY = 3;
1846
1847        public LogHandler(Looper looper) {
1848            super(looper);
1849        }
1850
1851        @Override
1852        public void handleMessage(Message msg) {
1853            switch (msg.what) {
1854                case MSG_OPEN_ENTRY: {
1855                    SomeArgs args = (SomeArgs) msg.obj;
1856                    Uri uri = (Uri) args.arg1;
1857                    long channelId = (long) args.arg2;
1858                    long time = (long) args.arg3;
1859                    onOpenEntry(uri, channelId, time);
1860                    args.recycle();
1861                    return;
1862                }
1863                case MSG_UPDATE_ENTRY: {
1864                    SomeArgs args = (SomeArgs) msg.obj;
1865                    Uri uri = (Uri) args.arg1;
1866                    long channelId = (long) args.arg2;
1867                    long time = (long) args.arg3;
1868                    onUpdateEntry(uri, channelId, time);
1869                    args.recycle();
1870                    return;
1871                }
1872                case MSG_CLOSE_ENTRY: {
1873                    SomeArgs args = (SomeArgs) msg.obj;
1874                    Uri uri = (Uri) args.arg1;
1875                    long time = (long) args.arg2;
1876                    onCloseEntry(uri, time);
1877                    args.recycle();
1878                    return;
1879                }
1880                default: {
1881                    Slog.w(TAG, "Unhandled message code: " + msg.what);
1882                    return;
1883                }
1884            }
1885        }
1886
1887        private void onOpenEntry(Uri uri, long channelId, long watchStarttime) {
1888            String[] projection = {
1889                    TvContract.Programs.COLUMN_TITLE,
1890                    TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS,
1891                    TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS,
1892                    TvContract.Programs.COLUMN_SHORT_DESCRIPTION
1893            };
1894            String selection = TvContract.Programs.COLUMN_CHANNEL_ID + "=? AND "
1895                    + TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS + "<=? AND "
1896                    + TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS + ">?";
1897            String[] selectionArgs = {
1898                    String.valueOf(channelId),
1899                    String.valueOf(watchStarttime),
1900                    String.valueOf(watchStarttime)
1901            };
1902            String sortOrder = TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS + " ASC";
1903            Cursor cursor = null;
1904            try {
1905                cursor = mContentResolver.query(TvContract.Programs.CONTENT_URI, projection,
1906                        selection, selectionArgs, sortOrder);
1907                if (cursor != null && cursor.moveToNext()) {
1908                    ContentValues values = new ContentValues();
1909                    values.put(TvContract.WatchedPrograms.COLUMN_TITLE, cursor.getString(0));
1910                    values.put(TvContract.WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS,
1911                            cursor.getLong(1));
1912                    long endTime = cursor.getLong(2);
1913                    values.put(TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS, endTime);
1914                    values.put(TvContract.WatchedPrograms.COLUMN_DESCRIPTION, cursor.getString(3));
1915                    mContentResolver.update(uri, values, null, null);
1916
1917                    // Schedule an update when the current program ends.
1918                    SomeArgs args = SomeArgs.obtain();
1919                    args.arg1 = uri;
1920                    args.arg2 = channelId;
1921                    args.arg3 = endTime;
1922                    Message msg = obtainMessage(LogHandler.MSG_UPDATE_ENTRY, args);
1923                    sendMessageDelayed(msg, endTime - System.currentTimeMillis());
1924                }
1925            } finally {
1926                if (cursor != null) {
1927                    cursor.close();
1928                }
1929            }
1930        }
1931
1932        private void onUpdateEntry(Uri uri, long channelId, long time) {
1933            String[] projection = {
1934                    TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
1935                    TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS,
1936                    TvContract.WatchedPrograms.COLUMN_TITLE,
1937                    TvContract.WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS,
1938                    TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS,
1939                    TvContract.WatchedPrograms.COLUMN_DESCRIPTION
1940            };
1941            Cursor cursor = null;
1942            try {
1943                cursor = mContentResolver.query(uri, projection, null, null, null);
1944                if (cursor != null && cursor.moveToNext()) {
1945                    long watchStartTime = cursor.getLong(0);
1946                    long watchEndTime = cursor.getLong(1);
1947                    String title = cursor.getString(2);
1948                    long startTime = cursor.getLong(3);
1949                    long endTime = cursor.getLong(4);
1950                    String description = cursor.getString(5);
1951
1952                    // Do nothing if the current log entry is already closed.
1953                    if (watchEndTime > 0) {
1954                        return;
1955                    }
1956
1957                    // The current program has just ended. Create a (complete) log entry off the
1958                    // current entry.
1959                    ContentValues values = new ContentValues();
1960                    values.put(TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
1961                            watchStartTime);
1962                    values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, time);
1963                    values.put(TvContract.WatchedPrograms.COLUMN_CHANNEL_ID, channelId);
1964                    values.put(TvContract.WatchedPrograms.COLUMN_TITLE, title);
1965                    values.put(TvContract.WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS, startTime);
1966                    values.put(TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS, endTime);
1967                    values.put(TvContract.WatchedPrograms.COLUMN_DESCRIPTION, description);
1968                    mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
1969                }
1970            } finally {
1971                if (cursor != null) {
1972                    cursor.close();
1973                }
1974            }
1975            // Re-open the current log entry with the next program information.
1976            onOpenEntry(uri, channelId, time);
1977        }
1978
1979        private void onCloseEntry(Uri uri, long watchEndTime) {
1980            ContentValues values = new ContentValues();
1981            values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, watchEndTime);
1982            mContentResolver.update(uri, values, null, null);
1983        }
1984    }
1985
1986    final class HardwareListener implements TvInputHardwareManager.Listener {
1987        @Override
1988        public void onStateChanged(String inputId, int state) {
1989            synchronized (mLock) {
1990                setStateLocked(inputId, state, mCurrentUserId);
1991            }
1992        }
1993
1994        @Override
1995        public void onHardwareDeviceAdded(TvInputHardwareInfo info) {
1996            synchronized (mLock) {
1997                UserState userState = getUserStateLocked(mCurrentUserId);
1998                // Broadcast the event to all hardware inputs.
1999                for (ServiceState serviceState : userState.serviceStateMap.values()) {
2000                    if (!serviceState.mIsHardware || serviceState.mService == null) continue;
2001                    try {
2002                        serviceState.mService.notifyHardwareAdded(info);
2003                    } catch (RemoteException e) {
2004                        Slog.e(TAG, "error in notifyHardwareAdded", e);
2005                    }
2006                }
2007            }
2008        }
2009
2010        @Override
2011        public void onHardwareDeviceRemoved(TvInputHardwareInfo info) {
2012            synchronized (mLock) {
2013                UserState userState = getUserStateLocked(mCurrentUserId);
2014                // Broadcast the event to all hardware inputs.
2015                for (ServiceState serviceState : userState.serviceStateMap.values()) {
2016                    if (!serviceState.mIsHardware || serviceState.mService == null) continue;
2017                    try {
2018                        serviceState.mService.notifyHardwareRemoved(info);
2019                    } catch (RemoteException e) {
2020                        Slog.e(TAG, "error in notifyHardwareRemoved", e);
2021                    }
2022                }
2023            }
2024        }
2025
2026        @Override
2027        public void onHdmiCecDeviceAdded(HdmiCecDeviceInfo cecDeviceInfo) {
2028            synchronized (mLock) {
2029                UserState userState = getUserStateLocked(mCurrentUserId);
2030                // Broadcast the event to all hardware inputs.
2031                for (ServiceState serviceState : userState.serviceStateMap.values()) {
2032                    if (!serviceState.mIsHardware || serviceState.mService == null) continue;
2033                    try {
2034                        serviceState.mService.notifyHdmiCecDeviceAdded(cecDeviceInfo);
2035                    } catch (RemoteException e) {
2036                        Slog.e(TAG, "error in notifyHdmiCecDeviceAdded", e);
2037                    }
2038                }
2039            }
2040        }
2041
2042        @Override
2043        public void onHdmiCecDeviceRemoved(HdmiCecDeviceInfo cecDeviceInfo) {
2044            synchronized (mLock) {
2045                UserState userState = getUserStateLocked(mCurrentUserId);
2046                // Broadcast the event to all hardware inputs.
2047                for (ServiceState serviceState : userState.serviceStateMap.values()) {
2048                    if (!serviceState.mIsHardware || serviceState.mService == null) continue;
2049                    try {
2050                        serviceState.mService.notifyHdmiCecDeviceRemoved(cecDeviceInfo);
2051                    } catch (RemoteException e) {
2052                        Slog.e(TAG, "error in notifyHdmiCecDeviceRemoved", e);
2053                    }
2054                }
2055            }
2056        }
2057    }
2058}
2059