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