TvInputManagerService.java revision 1f213914c45c23c653f721690da2ce0718e63139
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 android.app.ActivityManager;
20import android.content.BroadcastReceiver;
21import android.content.ComponentName;
22import android.content.ContentProviderOperation;
23import android.content.ContentProviderResult;
24import android.content.ContentResolver;
25import android.content.ContentUris;
26import android.content.ContentValues;
27import android.content.Context;
28import android.content.Intent;
29import android.content.IntentFilter;
30import android.content.OperationApplicationException;
31import android.content.ServiceConnection;
32import android.content.pm.PackageManager;
33import android.content.pm.ResolveInfo;
34import android.content.pm.ServiceInfo;
35import android.database.Cursor;
36import android.graphics.Rect;
37import android.media.tv.ITvInputClient;
38import android.media.tv.ITvInputHardware;
39import android.media.tv.ITvInputHardwareCallback;
40import android.media.tv.ITvInputManager;
41import android.media.tv.ITvInputService;
42import android.media.tv.ITvInputServiceCallback;
43import android.media.tv.ITvInputSession;
44import android.media.tv.ITvInputSessionCallback;
45import android.media.tv.TvContract;
46import android.media.tv.TvInputHardwareInfo;
47import android.media.tv.TvInputInfo;
48import android.media.tv.TvInputService;
49import android.media.tv.TvTrackInfo;
50import android.net.Uri;
51import android.os.Binder;
52import android.os.Bundle;
53import android.os.Handler;
54import android.os.IBinder;
55import android.os.Looper;
56import android.os.Message;
57import android.os.Process;
58import android.os.RemoteException;
59import android.os.UserHandle;
60import android.util.Slog;
61import android.util.SparseArray;
62import android.view.InputChannel;
63import android.view.Surface;
64
65import com.android.internal.content.PackageMonitor;
66import com.android.internal.os.SomeArgs;
67import com.android.internal.util.IndentingPrintWriter;
68import com.android.server.IoThread;
69import com.android.server.SystemService;
70
71import org.xmlpull.v1.XmlPullParserException;
72
73import java.io.FileDescriptor;
74import java.io.IOException;
75import java.io.PrintWriter;
76import java.util.ArrayList;
77import java.util.HashMap;
78import java.util.HashSet;
79import java.util.List;
80import java.util.Map;
81import java.util.Set;
82
83/** This class provides a system service that manages television inputs. */
84public final class TvInputManagerService extends SystemService {
85    // STOPSHIP: Turn debugging off.
86    private static final boolean DEBUG = true;
87    private static final String TAG = "TvInputManagerService";
88
89    private final Context mContext;
90    private final TvInputHardwareManager mTvInputHardwareManager;
91
92    private final ContentResolver mContentResolver;
93
94    // A global lock.
95    private final Object mLock = new Object();
96
97    // ID of the current user.
98    private int mCurrentUserId = UserHandle.USER_OWNER;
99
100    // A map from user id to UserState.
101    private final SparseArray<UserState> mUserStates = new SparseArray<UserState>();
102
103    private final Handler mLogHandler;
104
105    public TvInputManagerService(Context context) {
106        super(context);
107
108        mContext = context;
109        mContentResolver = context.getContentResolver();
110        mLogHandler = new LogHandler(IoThread.get().getLooper());
111
112        mTvInputHardwareManager = new TvInputHardwareManager(context);
113
114        synchronized (mLock) {
115            mUserStates.put(mCurrentUserId, new UserState());
116        }
117    }
118
119    @Override
120    public void onStart() {
121        publishBinderService(Context.TV_INPUT_SERVICE, new BinderService());
122    }
123
124    @Override
125    public void onBootPhase(int phase) {
126        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
127            registerBroadcastReceivers();
128            synchronized (mLock) {
129                buildTvInputListLocked(mCurrentUserId);
130            }
131        }
132    }
133
134    private void registerBroadcastReceivers() {
135        PackageMonitor monitor = new PackageMonitor() {
136            @Override
137            public void onSomePackagesChanged() {
138                synchronized (mLock) {
139                    buildTvInputListLocked(mCurrentUserId);
140                }
141            }
142
143            @Override
144            public void onPackageRemoved(String packageName, int uid) {
145                synchronized (mLock) {
146                    UserState userState = getUserStateLocked(mCurrentUserId);
147                    if (!userState.packageList.contains(packageName)) {
148                        // Not a TV input package.
149                        return;
150                    }
151                }
152
153                ArrayList<ContentProviderOperation> operations =
154                        new ArrayList<ContentProviderOperation>();
155
156                String selection = TvContract.BaseTvColumns.COLUMN_PACKAGE_NAME + "=?";
157                String[] selectionArgs = { packageName };
158
159                operations.add(ContentProviderOperation.newDelete(TvContract.Channels.CONTENT_URI)
160                        .withSelection(selection, selectionArgs).build());
161                operations.add(ContentProviderOperation.newDelete(TvContract.Programs.CONTENT_URI)
162                        .withSelection(selection, selectionArgs).build());
163                operations.add(ContentProviderOperation
164                        .newDelete(TvContract.WatchedPrograms.CONTENT_URI)
165                        .withSelection(selection, selectionArgs).build());
166
167                ContentProviderResult[] results = null;
168                try {
169                    results = mContentResolver.applyBatch(TvContract.AUTHORITY, operations);
170                } catch (RemoteException | OperationApplicationException e) {
171                    Slog.e(TAG, "error in applyBatch" + e);
172                }
173
174                if (DEBUG) {
175                    Slog.d(TAG, "onPackageRemoved(packageName=" + packageName + ", uid=" + uid
176                            + ")");
177                    Slog.d(TAG, "results=" + results);
178                }
179            }
180        };
181        monitor.register(mContext, null, UserHandle.ALL, true);
182
183        IntentFilter intentFilter = new IntentFilter();
184        intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
185        intentFilter.addAction(Intent.ACTION_USER_REMOVED);
186        mContext.registerReceiverAsUser(new BroadcastReceiver() {
187            @Override
188            public void onReceive(Context context, Intent intent) {
189                String action = intent.getAction();
190                if (Intent.ACTION_USER_SWITCHED.equals(action)) {
191                    switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
192                } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
193                    removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
194                }
195            }
196        }, UserHandle.ALL, intentFilter, null, null);
197    }
198
199    private void buildTvInputListLocked(int userId) {
200        UserState userState = getUserStateLocked(userId);
201        userState.inputMap.clear();
202        userState.packageList.clear();
203
204        if (DEBUG) Slog.d(TAG, "buildTvInputList");
205        PackageManager pm = mContext.getPackageManager();
206        List<ResolveInfo> services = pm.queryIntentServices(
207                new Intent(TvInputService.SERVICE_INTERFACE),
208                PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
209        for (ResolveInfo ri : services) {
210            ServiceInfo si = ri.serviceInfo;
211            if (!android.Manifest.permission.BIND_TV_INPUT.equals(si.permission)) {
212                Slog.w(TAG, "Skipping TV input " + si.name + ": it does not require the permission "
213                        + android.Manifest.permission.BIND_TV_INPUT);
214                continue;
215            }
216            try {
217                TvInputInfo info = TvInputInfo.createTvInputInfo(mContext, ri);
218                if (DEBUG) Slog.d(TAG, "add " + info.getId());
219                userState.inputMap.put(info.getId(), info);
220                userState.packageList.add(si.packageName);
221
222                // Reconnect the service if existing input is updated.
223                updateServiceConnectionLocked(info.getId(), userId);
224            } catch (IOException | XmlPullParserException e) {
225                Slog.e(TAG, "Can't load TV input " + si.name, e);
226            }
227        }
228    }
229
230    private void switchUser(int userId) {
231        synchronized (mLock) {
232            if (mCurrentUserId == userId) {
233                return;
234            }
235            // final int oldUserId = mCurrentUserId;
236            // TODO: Release services and sessions in the old user state, if needed.
237            mCurrentUserId = userId;
238
239            UserState userState = mUserStates.get(userId);
240            if (userState == null) {
241                userState = new UserState();
242            }
243            mUserStates.put(userId, userState);
244            buildTvInputListLocked(userId);
245        }
246    }
247
248    private void removeUser(int userId) {
249        synchronized (mLock) {
250            UserState userState = mUserStates.get(userId);
251            if (userState == null) {
252                return;
253            }
254            // Release created sessions.
255            for (SessionState state : userState.sessionStateMap.values()) {
256                if (state.mSession != null) {
257                    try {
258                        state.mSession.release();
259                    } catch (RemoteException e) {
260                        Slog.e(TAG, "error in release", e);
261                    }
262                }
263            }
264            userState.sessionStateMap.clear();
265
266            // Unregister all callbacks and unbind all services.
267            for (ServiceState serviceState : userState.serviceStateMap.values()) {
268                if (serviceState.mCallback != null) {
269                    try {
270                        serviceState.mService.unregisterCallback(serviceState.mCallback);
271                    } catch (RemoteException e) {
272                        Slog.e(TAG, "error in unregisterCallback", e);
273                    }
274                }
275                serviceState.mClientTokens.clear();
276                mContext.unbindService(serviceState.mConnection);
277            }
278            userState.serviceStateMap.clear();
279
280            userState.clientStateMap.clear();
281
282            mUserStates.remove(userId);
283        }
284    }
285
286    private UserState getUserStateLocked(int userId) {
287        UserState userState = mUserStates.get(userId);
288        if (userState == null) {
289            throw new IllegalStateException("User state not found for user ID " + userId);
290        }
291        return userState;
292    }
293
294    private ServiceState getServiceStateLocked(String inputId, int userId) {
295        UserState userState = getUserStateLocked(userId);
296        ServiceState serviceState = userState.serviceStateMap.get(inputId);
297        if (serviceState == null) {
298            throw new IllegalStateException("Service state not found for " + inputId + " (userId="
299                    + userId + ")");
300        }
301        return serviceState;
302    }
303
304    private SessionState getSessionStateLocked(IBinder sessionToken, int callingUid, int userId) {
305        UserState userState = getUserStateLocked(userId);
306        SessionState sessionState = userState.sessionStateMap.get(sessionToken);
307        if (sessionState == null) {
308            throw new IllegalArgumentException("Session state not found for token " + sessionToken);
309        }
310        // Only the application that requested this session or the system can access it.
311        if (callingUid != Process.SYSTEM_UID && callingUid != sessionState.mCallingUid) {
312            throw new SecurityException("Illegal access to the session with token " + sessionToken
313                    + " from uid " + callingUid);
314        }
315        return sessionState;
316    }
317
318    private ITvInputSession getSessionLocked(IBinder sessionToken, int callingUid, int userId) {
319        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
320        ITvInputSession session = sessionState.mSession;
321        if (session == null) {
322            throw new IllegalStateException("Session not yet created for token " + sessionToken);
323        }
324        return session;
325    }
326
327    private int resolveCallingUserId(int callingPid, int callingUid, int requestedUserId,
328            String methodName) {
329        return ActivityManager.handleIncomingUser(callingPid, callingUid, requestedUserId, false,
330                false, methodName, null);
331    }
332
333    private void updateServiceConnectionLocked(String inputId, int userId) {
334        UserState userState = getUserStateLocked(userId);
335        ServiceState serviceState = userState.serviceStateMap.get(inputId);
336        if (serviceState == null) {
337            return;
338        }
339        if (serviceState.mReconnecting) {
340            if (!serviceState.mSessionTokens.isEmpty()) {
341                // wait until all the sessions are removed.
342                return;
343            }
344            serviceState.mReconnecting = false;
345        }
346        boolean isStateEmpty = serviceState.mClientTokens.isEmpty()
347                && serviceState.mSessionTokens.isEmpty();
348        if (serviceState.mService == null && !isStateEmpty && userId == mCurrentUserId) {
349            // This means that the service is not yet connected but its state indicates that we
350            // have pending requests. Then, connect the service.
351            if (serviceState.mBound) {
352                // We have already bound to the service so we don't try to bind again until after we
353                // unbind later on.
354                return;
355            }
356            if (DEBUG) {
357                Slog.d(TAG, "bindServiceAsUser(inputId=" + inputId + ", userId=" + userId
358                        + ")");
359            }
360
361            Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(
362                    userState.inputMap.get(inputId).getComponent());
363            // Binding service may fail if the service is updating.
364            // In that case, the connection will be revived in buildTvInputListLocked called by
365            // onSomePackagesChanged.
366            serviceState.mBound = mContext.bindServiceAsUser(
367                    i, serviceState.mConnection, Context.BIND_AUTO_CREATE, new UserHandle(userId));
368        } else if (serviceState.mService != null && isStateEmpty) {
369            // This means that the service is already connected but its state indicates that we have
370            // nothing to do with it. Then, disconnect the service.
371            if (DEBUG) {
372                Slog.d(TAG, "unbindService(inputId=" + inputId + ")");
373            }
374            mContext.unbindService(serviceState.mConnection);
375            userState.serviceStateMap.remove(inputId);
376        }
377    }
378
379    private ClientState createClientStateLocked(IBinder clientToken, int userId) {
380        UserState userState = getUserStateLocked(userId);
381        ClientState clientState = new ClientState(clientToken, userId);
382        try {
383            clientToken.linkToDeath(clientState, 0);
384        } catch (RemoteException e) {
385            Slog.e(TAG, "Client is already died.");
386        }
387        userState.clientStateMap.put(clientToken, clientState);
388        return clientState;
389    }
390
391    private void createSessionInternalLocked(ITvInputService service, final IBinder sessionToken,
392            final int userId) {
393        final UserState userState = getUserStateLocked(userId);
394        final SessionState sessionState = userState.sessionStateMap.get(sessionToken);
395        if (DEBUG) {
396            Slog.d(TAG, "createSessionInternalLocked(inputId=" + sessionState.mInputId + ")");
397        }
398
399        final InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString());
400
401        // Set up a callback to send the session token.
402        ITvInputSessionCallback callback = new ITvInputSessionCallback.Stub() {
403            @Override
404            public void onSessionCreated(ITvInputSession session) {
405                if (DEBUG) {
406                    Slog.d(TAG, "onSessionCreated(inputId=" + sessionState.mInputId + ")");
407                }
408                synchronized (mLock) {
409                    sessionState.mSession = session;
410                    if (session == null) {
411                        removeSessionStateLocked(sessionToken, userId);
412                        sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mInputId,
413                                null, null, sessionState.mSeq);
414                    } else {
415                        try {
416                            session.asBinder().linkToDeath(sessionState, 0);
417                        } catch (RemoteException e) {
418                            Slog.e(TAG, "Session is already died.");
419                        }
420
421                        IBinder clientToken = sessionState.mClient.asBinder();
422                        ClientState clientState = userState.clientStateMap.get(clientToken);
423                        if (clientState == null) {
424                            clientState = createClientStateLocked(clientToken, userId);
425                        }
426                        clientState.mSessionTokens.add(sessionState.mSessionToken);
427
428                        sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mInputId,
429                                sessionToken, channels[0], sessionState.mSeq);
430                    }
431                    channels[0].dispose();
432                }
433            }
434
435            @Override
436            public void onChannelRetuned(Uri channelUri) {
437                synchronized (mLock) {
438                    if (DEBUG) {
439                        Slog.d(TAG, "onChannelRetuned(" + channelUri + ")");
440                    }
441                    if (sessionState.mSession == null || sessionState.mClient == null) {
442                        return;
443                    }
444                    try {
445                        // TODO: Consider adding this channel change in the watch log. When we do
446                        // that, how we can protect the watch log from malicious tv inputs should
447                        // be addressed. e.g. add a field which represents where the channel change
448                        // originated from.
449                        sessionState.mClient.onChannelRetuned(channelUri, sessionState.mSeq);
450                    } catch (RemoteException e) {
451                        Slog.e(TAG, "error in onChannelRetuned");
452                    }
453                }
454            }
455
456            @Override
457            public void onTrackInfoChanged(List<TvTrackInfo> tracks) {
458                synchronized (mLock) {
459                    if (DEBUG) {
460                        Slog.d(TAG, "onTrackInfoChanged(" + tracks + ")");
461                    }
462                    if (sessionState.mSession == null || sessionState.mClient == null) {
463                        return;
464                    }
465                    try {
466                        sessionState.mClient.onTrackInfoChanged(tracks,
467                                sessionState.mSeq);
468                    } catch (RemoteException e) {
469                        Slog.e(TAG, "error in onTrackInfoChanged");
470                    }
471                }
472            }
473
474            @Override
475            public void onSessionEvent(String eventType, Bundle eventArgs) {
476                synchronized (mLock) {
477                    if (DEBUG) {
478                        Slog.d(TAG, "onEvent(what=" + eventType + ", data=" + eventArgs + ")");
479                    }
480                    if (sessionState.mSession == null || sessionState.mClient == null) {
481                        return;
482                    }
483                    try {
484                        sessionState.mClient.onSessionEvent(eventType, eventArgs,
485                                sessionState.mSeq);
486                    } catch (RemoteException e) {
487                        Slog.e(TAG, "error in onSessionEvent");
488                    }
489                }
490            }
491        };
492
493        // Create a session. When failed, send a null token immediately.
494        try {
495            service.createSession(channels[1], callback);
496        } catch (RemoteException e) {
497            Slog.e(TAG, "error in createSession", e);
498            removeSessionStateLocked(sessionToken, userId);
499            sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mInputId, null, null,
500                    sessionState.mSeq);
501        }
502        channels[1].dispose();
503    }
504
505    private void sendSessionTokenToClientLocked(ITvInputClient client, String inputId,
506            IBinder sessionToken, InputChannel channel, int seq) {
507        try {
508            client.onSessionCreated(inputId, sessionToken, channel, seq);
509        } catch (RemoteException exception) {
510            Slog.e(TAG, "error in onSessionCreated", exception);
511        }
512    }
513
514    private void releaseSessionLocked(IBinder sessionToken, int callingUid, int userId) {
515        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
516        if (sessionState.mSession != null) {
517            try {
518                sessionState.mSession.release();
519            } catch (RemoteException e) {
520                Slog.w(TAG, "session is already disapeared", e);
521            }
522            sessionState.mSession = null;
523        }
524        removeSessionStateLocked(sessionToken, userId);
525    }
526
527    private void removeSessionStateLocked(IBinder sessionToken, int userId) {
528        // Remove the session state from the global session state map of the current user.
529        UserState userState = getUserStateLocked(userId);
530        SessionState sessionState = userState.sessionStateMap.remove(sessionToken);
531
532        // Close the open log entry, if any.
533        if (sessionState.mLogUri != null) {
534            SomeArgs args = SomeArgs.obtain();
535            args.arg1 = sessionState.mLogUri;
536            args.arg2 = System.currentTimeMillis();
537            mLogHandler.obtainMessage(LogHandler.MSG_CLOSE_ENTRY, args).sendToTarget();
538        }
539
540        // Also remove the session token from the session token list of the current client and
541        // service.
542        ClientState clientState = userState.clientStateMap.get(sessionState.mClient.asBinder());
543        if (clientState != null) {
544            clientState.mSessionTokens.remove(sessionToken);
545            if (clientState.isEmpty()) {
546                userState.clientStateMap.remove(sessionState.mClient.asBinder());
547            }
548        }
549
550        ServiceState serviceState = userState.serviceStateMap.get(sessionState.mInputId);
551        if (serviceState != null) {
552            serviceState.mSessionTokens.remove(sessionToken);
553        }
554        updateServiceConnectionLocked(sessionState.mInputId, userId);
555    }
556
557    private void unregisterCallbackInternalLocked(IBinder clientToken, String inputId,
558            int userId) {
559        UserState userState = getUserStateLocked(userId);
560        ClientState clientState = userState.clientStateMap.get(clientToken);
561        if (clientState != null) {
562            clientState.mInputIds.remove(inputId);
563            if (clientState.isEmpty()) {
564                userState.clientStateMap.remove(clientToken);
565            }
566        }
567
568        ServiceState serviceState = userState.serviceStateMap.get(inputId);
569        if (serviceState == null) {
570            return;
571        }
572
573        // Remove this client from the client list and unregister the callback.
574        serviceState.mClientTokens.remove(clientToken);
575        if (!serviceState.mClientTokens.isEmpty()) {
576            // We have other clients who want to keep the callback. Do this later.
577            return;
578        }
579        if (serviceState.mService == null || serviceState.mCallback == null) {
580            return;
581        }
582        try {
583            serviceState.mService.unregisterCallback(serviceState.mCallback);
584        } catch (RemoteException e) {
585            Slog.e(TAG, "error in unregisterCallback", e);
586        } finally {
587            serviceState.mCallback = null;
588            updateServiceConnectionLocked(inputId, userId);
589        }
590    }
591
592    private void broadcastServiceAvailabilityChangedLocked(ServiceState serviceState) {
593        for (IBinder clientToken : serviceState.mClientTokens) {
594            try {
595                ITvInputClient.Stub.asInterface(clientToken).onAvailabilityChanged(
596                        serviceState.mTvInputInfo.getId(), serviceState.mAvailable);
597            } catch (RemoteException e) {
598                Slog.e(TAG, "error in onAvailabilityChanged", e);
599            }
600        }
601    }
602
603    private final class BinderService extends ITvInputManager.Stub {
604        @Override
605        public List<TvInputInfo> getTvInputList(int userId) {
606            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
607                    Binder.getCallingUid(), userId, "getTvInputList");
608            final long identity = Binder.clearCallingIdentity();
609            try {
610                synchronized (mLock) {
611                    UserState userState = getUserStateLocked(resolvedUserId);
612                    return new ArrayList<TvInputInfo>(userState.inputMap.values());
613                }
614            } finally {
615                Binder.restoreCallingIdentity(identity);
616            }
617        }
618
619        @Override
620        public boolean getAvailability(final ITvInputClient client, final String inputId,
621                int userId) {
622            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
623                    Binder.getCallingUid(), userId, "getAvailability");
624            final long identity = Binder.clearCallingIdentity();
625            try {
626                synchronized (mLock) {
627                    UserState userState = getUserStateLocked(resolvedUserId);
628                    ServiceState serviceState = userState.serviceStateMap.get(inputId);
629                    if (serviceState != null) {
630                        // We already know the status of this input service. Return the cached
631                        // status.
632                        return serviceState.mAvailable;
633                    }
634                }
635            } finally {
636                Binder.restoreCallingIdentity(identity);
637            }
638            // STOPSHIP: Redesign the API around the availability change. For now, the service
639            // will be always available.
640            return true;
641        }
642
643        @Override
644        public void registerCallback(final ITvInputClient client, final String inputId,
645                int userId) {
646            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
647                    Binder.getCallingUid(), userId, "registerCallback");
648            final long identity = Binder.clearCallingIdentity();
649            try {
650                synchronized (mLock) {
651                    // Create a new service callback and add it to the callback map of the current
652                    // service.
653                    UserState userState = getUserStateLocked(resolvedUserId);
654                    ServiceState serviceState = userState.serviceStateMap.get(inputId);
655                    if (serviceState == null) {
656                        serviceState = new ServiceState(
657                                userState.inputMap.get(inputId), resolvedUserId);
658                        userState.serviceStateMap.put(inputId, serviceState);
659                    }
660                    IBinder clientToken = client.asBinder();
661                    if (!serviceState.mClientTokens.contains(clientToken)) {
662                        serviceState.mClientTokens.add(clientToken);
663                    }
664
665                    ClientState clientState = userState.clientStateMap.get(clientToken);
666                    if (clientState == null) {
667                        clientState = createClientStateLocked(clientToken, resolvedUserId);
668                    }
669                    if (!clientState.mInputIds.contains(inputId)) {
670                        clientState.mInputIds.add(inputId);
671                    }
672
673                    if (serviceState.mService != null) {
674                        if (serviceState.mCallback != null) {
675                            // We already handled.
676                            return;
677                        }
678                        serviceState.mCallback = new ServiceCallback(resolvedUserId);
679                        try {
680                            serviceState.mService.registerCallback(serviceState.mCallback);
681                        } catch (RemoteException e) {
682                            Slog.e(TAG, "error in registerCallback", e);
683                        }
684                    } else {
685                        updateServiceConnectionLocked(inputId, resolvedUserId);
686                    }
687                }
688            } finally {
689                Binder.restoreCallingIdentity(identity);
690            }
691        }
692
693        @Override
694        public void unregisterCallback(ITvInputClient client, String inputId, int userId) {
695            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
696                    Binder.getCallingUid(), userId, "unregisterCallback");
697            final long identity = Binder.clearCallingIdentity();
698            try {
699                synchronized (mLock) {
700                    unregisterCallbackInternalLocked(client.asBinder(), inputId, resolvedUserId);
701                }
702            } finally {
703                Binder.restoreCallingIdentity(identity);
704            }
705        }
706
707        @Override
708        public void createSession(final ITvInputClient client, final String inputId,
709                int seq, int userId) {
710            final int callingUid = Binder.getCallingUid();
711            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
712                    userId, "createSession");
713            final long identity = Binder.clearCallingIdentity();
714            try {
715                synchronized (mLock) {
716                    UserState userState = getUserStateLocked(resolvedUserId);
717                    ServiceState serviceState = userState.serviceStateMap.get(inputId);
718                    if (serviceState == null) {
719                        serviceState = new ServiceState(
720                                userState.inputMap.get(inputId), resolvedUserId);
721                        userState.serviceStateMap.put(inputId, serviceState);
722                    }
723                    // Send a null token immediately while reconnecting.
724                    if (serviceState.mReconnecting == true) {
725                        sendSessionTokenToClientLocked(client, inputId, null, null, seq);
726                        return;
727                    }
728
729                    // Create a new session token and a session state.
730                    IBinder sessionToken = new Binder();
731                    SessionState sessionState = new SessionState(sessionToken, inputId, client,
732                            seq, callingUid, resolvedUserId);
733
734                    // Add them to the global session state map of the current user.
735                    userState.sessionStateMap.put(sessionToken, sessionState);
736
737                    // Also, add them to the session state map of the current service.
738                    serviceState.mSessionTokens.add(sessionToken);
739
740                    if (serviceState.mService != null) {
741                        createSessionInternalLocked(serviceState.mService, sessionToken,
742                                resolvedUserId);
743                    } else {
744                        updateServiceConnectionLocked(inputId, resolvedUserId);
745                    }
746                }
747            } finally {
748                Binder.restoreCallingIdentity(identity);
749            }
750        }
751
752        @Override
753        public void releaseSession(IBinder sessionToken, int userId) {
754            final int callingUid = Binder.getCallingUid();
755            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
756                    userId, "releaseSession");
757            final long identity = Binder.clearCallingIdentity();
758            try {
759                synchronized (mLock) {
760                    releaseSessionLocked(sessionToken, callingUid, resolvedUserId);
761                }
762            } finally {
763                Binder.restoreCallingIdentity(identity);
764            }
765        }
766
767        @Override
768        public void setSurface(IBinder sessionToken, Surface surface, int userId) {
769            final int callingUid = Binder.getCallingUid();
770            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
771                    userId, "setSurface");
772            final long identity = Binder.clearCallingIdentity();
773            try {
774                synchronized (mLock) {
775                    try {
776                        getSessionLocked(sessionToken, callingUid, resolvedUserId).setSurface(
777                                surface);
778                    } catch (RemoteException e) {
779                        Slog.e(TAG, "error in setSurface", e);
780                    }
781                }
782            } finally {
783                if (surface != null) {
784                    // surface is not used in TvInputManagerService.
785                    surface.release();
786                }
787                Binder.restoreCallingIdentity(identity);
788            }
789        }
790
791        @Override
792        public void setVolume(IBinder sessionToken, float volume, int userId) {
793            final int callingUid = Binder.getCallingUid();
794            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
795                    userId, "setVolume");
796            final long identity = Binder.clearCallingIdentity();
797            try {
798                synchronized (mLock) {
799                    try {
800                        getSessionLocked(sessionToken, callingUid, resolvedUserId).setVolume(
801                                volume);
802                    } catch (RemoteException e) {
803                        Slog.e(TAG, "error in setVolume", e);
804                    }
805                }
806            } finally {
807                Binder.restoreCallingIdentity(identity);
808            }
809        }
810
811        @Override
812        public void tune(IBinder sessionToken, final Uri channelUri, int userId) {
813            final int callingUid = Binder.getCallingUid();
814            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
815                    userId, "tune");
816            final long identity = Binder.clearCallingIdentity();
817            try {
818                synchronized (mLock) {
819                    try {
820                        getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(channelUri);
821
822                        long currentTime = System.currentTimeMillis();
823                        long channelId = ContentUris.parseId(channelUri);
824
825                        // Close the open log entry first, if any.
826                        UserState userState = getUserStateLocked(resolvedUserId);
827                        SessionState sessionState = userState.sessionStateMap.get(sessionToken);
828                        if (sessionState.mLogUri != null) {
829                            SomeArgs args = SomeArgs.obtain();
830                            args.arg1 = sessionState.mLogUri;
831                            args.arg2 = currentTime;
832                            mLogHandler.obtainMessage(LogHandler.MSG_CLOSE_ENTRY, args)
833                                    .sendToTarget();
834                        }
835
836                        // Create a log entry and fill it later.
837                        String packageName = userState.inputMap.get(sessionState.mInputId)
838                                .getServiceInfo().packageName;
839                        ContentValues values = new ContentValues();
840                        values.put(TvContract.WatchedPrograms.COLUMN_PACKAGE_NAME, packageName);
841                        values.put(TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
842                                currentTime);
843                        values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, 0);
844                        values.put(TvContract.WatchedPrograms.COLUMN_CHANNEL_ID, channelId);
845
846                        sessionState.mLogUri = mContentResolver.insert(
847                                TvContract.WatchedPrograms.CONTENT_URI, values);
848                        SomeArgs args = SomeArgs.obtain();
849                        args.arg1 = sessionState.mLogUri;
850                        args.arg2 = ContentUris.parseId(channelUri);
851                        args.arg3 = currentTime;
852                        mLogHandler.obtainMessage(LogHandler.MSG_OPEN_ENTRY, args).sendToTarget();
853                    } catch (RemoteException e) {
854                        Slog.e(TAG, "error in tune", e);
855                        return;
856                    }
857                }
858            } finally {
859                Binder.restoreCallingIdentity(identity);
860            }
861        }
862
863        @Override
864        public void selectTrack(IBinder sessionToken, TvTrackInfo track, int userId) {
865            final int callingUid = Binder.getCallingUid();
866            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
867                    userId, "selectTrack");
868            final long identity = Binder.clearCallingIdentity();
869            try {
870                synchronized (mLock) {
871                    try {
872                        getSessionLocked(sessionToken, callingUid, resolvedUserId).selectTrack(
873                                track);
874                    } catch (RemoteException e) {
875                        Slog.e(TAG, "error in selectTrack", e);
876                    }
877                }
878            } finally {
879                Binder.restoreCallingIdentity(identity);
880            }
881        }
882
883        @Override
884        public void unselectTrack(IBinder sessionToken, TvTrackInfo track, int userId) {
885            final int callingUid = Binder.getCallingUid();
886            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
887                    userId, "unselectTrack");
888            final long identity = Binder.clearCallingIdentity();
889            try {
890                synchronized (mLock) {
891                    try {
892                        getSessionLocked(sessionToken, callingUid, resolvedUserId).unselectTrack(
893                                track);
894                    } catch (RemoteException e) {
895                        Slog.e(TAG, "error in unselectTrack", e);
896                    }
897                }
898            } finally {
899                Binder.restoreCallingIdentity(identity);
900            }
901        }
902
903        @Override
904        public void createOverlayView(IBinder sessionToken, IBinder windowToken, Rect frame,
905                int userId) {
906            final int callingUid = Binder.getCallingUid();
907            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
908                    userId, "createOverlayView");
909            final long identity = Binder.clearCallingIdentity();
910            try {
911                synchronized (mLock) {
912                    try {
913                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
914                                .createOverlayView(windowToken, frame);
915                    } catch (RemoteException e) {
916                        Slog.e(TAG, "error in createOverlayView", e);
917                    }
918                }
919            } finally {
920                Binder.restoreCallingIdentity(identity);
921            }
922        }
923
924        @Override
925        public void relayoutOverlayView(IBinder sessionToken, Rect frame, int userId) {
926            final int callingUid = Binder.getCallingUid();
927            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
928                    userId, "relayoutOverlayView");
929            final long identity = Binder.clearCallingIdentity();
930            try {
931                synchronized (mLock) {
932                    try {
933                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
934                                .relayoutOverlayView(frame);
935                    } catch (RemoteException e) {
936                        Slog.e(TAG, "error in relayoutOverlayView", e);
937                    }
938                }
939            } finally {
940                Binder.restoreCallingIdentity(identity);
941            }
942        }
943
944        @Override
945        public void removeOverlayView(IBinder sessionToken, int userId) {
946            final int callingUid = Binder.getCallingUid();
947            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
948                    userId, "removeOverlayView");
949            final long identity = Binder.clearCallingIdentity();
950            try {
951                synchronized (mLock) {
952                    try {
953                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
954                                .removeOverlayView();
955                    } catch (RemoteException e) {
956                        Slog.e(TAG, "error in removeOverlayView", e);
957                    }
958                }
959            } finally {
960                Binder.restoreCallingIdentity(identity);
961            }
962        }
963
964        @Override
965        public List<TvInputHardwareInfo> getHardwareList() throws RemoteException {
966            if (mContext.checkCallingPermission(
967                    android.Manifest.permission.TV_INPUT_HARDWARE)
968                    != PackageManager.PERMISSION_GRANTED) {
969                return null;
970            }
971
972            final long identity = Binder.clearCallingIdentity();
973            try {
974                return mTvInputHardwareManager.getHardwareList();
975            } finally {
976                Binder.restoreCallingIdentity(identity);
977            }
978        }
979
980        @Override
981        public ITvInputHardware acquireTvInputHardware(int deviceId,
982                ITvInputHardwareCallback callback, int userId) throws RemoteException {
983            if (mContext.checkCallingPermission(
984                    android.Manifest.permission.TV_INPUT_HARDWARE)
985                    != PackageManager.PERMISSION_GRANTED) {
986                return null;
987            }
988
989            final long identity = Binder.clearCallingIdentity();
990            final int callingUid = Binder.getCallingUid();
991            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
992                    userId, "acquireTvInputHardware");
993            try {
994                return mTvInputHardwareManager.acquireHardware(
995                        deviceId, callback, callingUid, resolvedUserId);
996            } finally {
997                Binder.restoreCallingIdentity(identity);
998            }
999        }
1000
1001        @Override
1002        public void releaseTvInputHardware(int deviceId, ITvInputHardware hardware, int userId)
1003                throws RemoteException {
1004            if (mContext.checkCallingPermission(
1005                    android.Manifest.permission.TV_INPUT_HARDWARE)
1006                    != PackageManager.PERMISSION_GRANTED) {
1007                return;
1008            }
1009
1010            final long identity = Binder.clearCallingIdentity();
1011            final int callingUid = Binder.getCallingUid();
1012            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1013                    userId, "releaseTvInputHardware");
1014            try {
1015                mTvInputHardwareManager.releaseHardware(
1016                        deviceId, hardware, callingUid, resolvedUserId);
1017            } finally {
1018                Binder.restoreCallingIdentity(identity);
1019            }
1020        }
1021
1022        @Override
1023        protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
1024            final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
1025            if (mContext.checkCallingOrSelfPermission(
1026                    android.Manifest.permission.DUMP)
1027                    != PackageManager.PERMISSION_GRANTED) {
1028                pw.println("Permission Denial: can't dump TvInputManager " +
1029                        "from from pid=" + Binder.getCallingPid() + ", uid=" +
1030                        Binder.getCallingUid());
1031                return;
1032            }
1033
1034            synchronized (mLock) {
1035                pw.println("User Ids (Current user: " + mCurrentUserId + "):");
1036                pw.increaseIndent();
1037                for (int i = 0; i < mUserStates.size(); i++) {
1038                    int userId = mUserStates.keyAt(i);
1039                    pw.println(Integer.valueOf(userId));
1040                }
1041                pw.decreaseIndent();
1042
1043                for (int i = 0; i < mUserStates.size(); i++) {
1044                    int userId = mUserStates.keyAt(i);
1045                    UserState userState = getUserStateLocked(userId);
1046
1047                    pw.println("UserState (" + userId + "):");
1048                    pw.increaseIndent();
1049
1050                    pw.println("inputMap: inputId -> TvInputInfo");
1051                    pw.increaseIndent();
1052                    for (TvInputInfo info : userState.inputMap.values()) {
1053                        pw.println(info.toString());
1054                    }
1055                    pw.decreaseIndent();
1056
1057                    pw.println("packageList:");
1058                    pw.increaseIndent();
1059                    for (String packageName : userState.packageList) {
1060                        pw.println(packageName);
1061                    }
1062                    pw.decreaseIndent();
1063
1064                    pw.println("clientStateMap: ITvInputClient -> ClientState");
1065                    pw.increaseIndent();
1066                    for (Map.Entry<IBinder, ClientState> entry :
1067                            userState.clientStateMap.entrySet()) {
1068                        ClientState client = entry.getValue();
1069                        pw.println(entry.getKey() + ": " + client);
1070
1071                        pw.increaseIndent();
1072
1073                        pw.println("mInputIds:");
1074                        pw.increaseIndent();
1075                        for (String inputId : client.mInputIds) {
1076                            pw.println(inputId);
1077                        }
1078                        pw.decreaseIndent();
1079
1080                        pw.println("mSessionTokens:");
1081                        pw.increaseIndent();
1082                        for (IBinder token : client.mSessionTokens) {
1083                            pw.println("" + token);
1084                        }
1085                        pw.decreaseIndent();
1086
1087                        pw.println("mClientTokens: " + client.mClientToken);
1088                        pw.println("mUserId: " + client.mUserId);
1089
1090                        pw.decreaseIndent();
1091                    }
1092                    pw.decreaseIndent();
1093
1094                    pw.println("serviceStateMap: inputId -> ServiceState");
1095                    pw.increaseIndent();
1096                    for (Map.Entry<String, ServiceState> entry :
1097                            userState.serviceStateMap.entrySet()) {
1098                        ServiceState service = entry.getValue();
1099                        pw.println(entry.getKey() + ": " + service);
1100
1101                        pw.increaseIndent();
1102
1103                        pw.println("mClientTokens:");
1104                        pw.increaseIndent();
1105                        for (IBinder token : service.mClientTokens) {
1106                            pw.println("" + token);
1107                        }
1108                        pw.decreaseIndent();
1109
1110                        pw.println("mSessionTokens:");
1111                        pw.increaseIndent();
1112                        for (IBinder token : service.mSessionTokens) {
1113                            pw.println("" + token);
1114                        }
1115                        pw.decreaseIndent();
1116
1117                        pw.println("mService: " + service.mService);
1118                        pw.println("mCallback: " + service.mCallback);
1119                        pw.println("mBound: " + service.mBound);
1120                        pw.println("mAvailable: " + service.mAvailable);
1121                        pw.println("mReconnecting: " + service.mReconnecting);
1122
1123                        pw.decreaseIndent();
1124                    }
1125                    pw.decreaseIndent();
1126
1127                    pw.println("sessionStateMap: ITvInputSession -> SessionState");
1128                    pw.increaseIndent();
1129                    for (Map.Entry<IBinder, SessionState> entry :
1130                            userState.sessionStateMap.entrySet()) {
1131                        SessionState session = entry.getValue();
1132                        pw.println(entry.getKey() + ": " + session);
1133
1134                        pw.increaseIndent();
1135                        pw.println("mInputId: " + session.mInputId);
1136                        pw.println("mClient: " + session.mClient);
1137                        pw.println("mSeq: " + session.mSeq);
1138                        pw.println("mCallingUid: " + session.mCallingUid);
1139                        pw.println("mUserId: " + session.mUserId);
1140                        pw.println("mSessionToken: " + session.mSessionToken);
1141                        pw.println("mSession: " + session.mSession);
1142                        pw.println("mLogUri: " + session.mLogUri);
1143                        pw.decreaseIndent();
1144                    }
1145                    pw.decreaseIndent();
1146
1147                    pw.decreaseIndent();
1148                }
1149            }
1150        }
1151    }
1152
1153    private static final class UserState {
1154        // A mapping from the TV input id to its TvInputInfo.
1155        private final Map<String, TvInputInfo> inputMap = new HashMap<String,TvInputInfo>();
1156
1157        // A list of all TV input packages.
1158        private final Set<String> packageList = new HashSet<String>();
1159
1160        // A mapping from the token of a client to its state.
1161        private final Map<IBinder, ClientState> clientStateMap =
1162                new HashMap<IBinder, ClientState>();
1163
1164        // A mapping from the name of a TV input service to its state.
1165        private final Map<String, ServiceState> serviceStateMap =
1166                new HashMap<String, ServiceState>();
1167
1168        // A mapping from the token of a TV input session to its state.
1169        private final Map<IBinder, SessionState> sessionStateMap =
1170                new HashMap<IBinder, SessionState>();
1171    }
1172
1173    private final class ClientState implements IBinder.DeathRecipient {
1174        private final List<String> mInputIds = new ArrayList<String>();
1175        private final List<IBinder> mSessionTokens = new ArrayList<IBinder>();
1176
1177        private IBinder mClientToken;
1178        private final int mUserId;
1179
1180        ClientState(IBinder clientToken, int userId) {
1181            mClientToken = clientToken;
1182            mUserId = userId;
1183        }
1184
1185        public boolean isEmpty() {
1186            return mInputIds.isEmpty() && mSessionTokens.isEmpty();
1187        }
1188
1189        @Override
1190        public void binderDied() {
1191            synchronized (mLock) {
1192                UserState userState = getUserStateLocked(mUserId);
1193                // DO NOT remove the client state of clientStateMap in this method. It will be
1194                // removed in releaseSessionLocked() or unregisterCallbackInternalLocked().
1195                ClientState clientState = userState.clientStateMap.get(mClientToken);
1196                if (clientState != null) {
1197                    while (clientState.mSessionTokens.size() > 0) {
1198                        releaseSessionLocked(
1199                                clientState.mSessionTokens.get(0), Process.SYSTEM_UID, mUserId);
1200                    }
1201                    while (clientState.mInputIds.size() > 0) {
1202                        unregisterCallbackInternalLocked(
1203                                mClientToken, clientState.mInputIds.get(0), mUserId);
1204                    }
1205                }
1206                mClientToken = null;
1207            }
1208        }
1209    }
1210
1211    private final class ServiceState {
1212        private final List<IBinder> mClientTokens = new ArrayList<IBinder>();
1213        private final List<IBinder> mSessionTokens = new ArrayList<IBinder>();
1214        private final ServiceConnection mConnection;
1215        private final TvInputInfo mTvInputInfo;
1216
1217        private ITvInputService mService;
1218        private ServiceCallback mCallback;
1219        private boolean mBound;
1220        private boolean mAvailable;
1221        private boolean mReconnecting;
1222
1223        private ServiceState(TvInputInfo inputInfo, int userId) {
1224            mTvInputInfo = inputInfo;
1225            mConnection = new InputServiceConnection(inputInfo, userId);
1226        }
1227    }
1228
1229    private final class SessionState implements IBinder.DeathRecipient {
1230        private final String mInputId;
1231        private final ITvInputClient mClient;
1232        private final int mSeq;
1233        private final int mCallingUid;
1234        private final int mUserId;
1235        private final IBinder mSessionToken;
1236        private ITvInputSession mSession;
1237        private Uri mLogUri;
1238
1239        private SessionState(IBinder sessionToken, String inputId, ITvInputClient client, int seq,
1240                int callingUid, int userId) {
1241            mSessionToken = sessionToken;
1242            mInputId = inputId;
1243            mClient = client;
1244            mSeq = seq;
1245            mCallingUid = callingUid;
1246            mUserId = userId;
1247        }
1248
1249        @Override
1250        public void binderDied() {
1251            synchronized (mLock) {
1252                mSession = null;
1253                if (mClient != null) {
1254                    try {
1255                        mClient.onSessionReleased(mSeq);
1256                    } catch(RemoteException e) {
1257                        Slog.e(TAG, "error in onSessionReleased", e);
1258                    }
1259                }
1260                removeSessionStateLocked(mSessionToken, mUserId);
1261            }
1262        }
1263    }
1264
1265    private final class InputServiceConnection implements ServiceConnection {
1266        private final TvInputInfo mTvInputInfo;
1267        private final int mUserId;
1268
1269        private InputServiceConnection(TvInputInfo inputInfo, int userId) {
1270            mUserId = userId;
1271            mTvInputInfo = inputInfo;
1272        }
1273
1274        @Override
1275        public void onServiceConnected(ComponentName name, IBinder service) {
1276            if (DEBUG) {
1277                Slog.d(TAG, "onServiceConnected(inputId=" + mTvInputInfo.getId() + ")");
1278            }
1279            synchronized (mLock) {
1280                ServiceState serviceState = getServiceStateLocked(mTvInputInfo.getId(), mUserId);
1281                serviceState.mService = ITvInputService.Stub.asInterface(service);
1282
1283                // Register a callback, if we need to.
1284                if (!serviceState.mClientTokens.isEmpty() && serviceState.mCallback == null) {
1285                    serviceState.mCallback = new ServiceCallback(mUserId);
1286                    try {
1287                        serviceState.mService.registerCallback(serviceState.mCallback);
1288                    } catch (RemoteException e) {
1289                        Slog.e(TAG, "error in registerCallback", e);
1290                    }
1291                }
1292
1293                // And create sessions, if any.
1294                for (IBinder sessionToken : serviceState.mSessionTokens) {
1295                    createSessionInternalLocked(serviceState.mService, sessionToken, mUserId);
1296                }
1297            }
1298        }
1299
1300        @Override
1301        public void onServiceDisconnected(ComponentName name) {
1302            if (DEBUG) {
1303                Slog.d(TAG, "onServiceDisconnected(inputId=" + mTvInputInfo.getId() + ")");
1304            }
1305            if (!mTvInputInfo.getComponent().equals(name)) {
1306                throw new IllegalArgumentException("Mismatched ComponentName: "
1307                        + mTvInputInfo.getComponent() + " (expected), " + name + " (actual).");
1308            }
1309            synchronized (mLock) {
1310                UserState userState = getUserStateLocked(mUserId);
1311                ServiceState serviceState = userState.serviceStateMap.get(mTvInputInfo.getId());
1312                if (serviceState != null) {
1313                    serviceState.mReconnecting = true;
1314                    serviceState.mBound = false;
1315                    serviceState.mService = null;
1316                    serviceState.mCallback = null;
1317
1318                    // Send null tokens for not finishing create session events.
1319                    for (IBinder sessionToken : serviceState.mSessionTokens) {
1320                        SessionState sessionState = userState.sessionStateMap.get(sessionToken);
1321                        if (sessionState.mSession == null) {
1322                            removeSessionStateLocked(sessionToken, sessionState.mUserId);
1323                            sendSessionTokenToClientLocked(sessionState.mClient,
1324                                    sessionState.mInputId, null, null, sessionState.mSeq);
1325                        }
1326                    }
1327
1328                    if (serviceState.mAvailable) {
1329                        serviceState.mAvailable = false;
1330                        broadcastServiceAvailabilityChangedLocked(serviceState);
1331                    }
1332                    updateServiceConnectionLocked(mTvInputInfo.getId(), mUserId);
1333                }
1334            }
1335        }
1336    }
1337
1338    private final class ServiceCallback extends ITvInputServiceCallback.Stub {
1339        private final int mUserId;
1340
1341        ServiceCallback(int userId) {
1342            mUserId = userId;
1343        }
1344
1345        @Override
1346        public void onAvailabilityChanged(String inputId, boolean isAvailable) {
1347            if (DEBUG) {
1348                Slog.d(TAG, "onAvailabilityChanged(inputId=" + inputId + ", isAvailable="
1349                        + isAvailable + ")");
1350            }
1351            synchronized (mLock) {
1352                ServiceState serviceState = getServiceStateLocked(inputId, mUserId);
1353                if (serviceState.mAvailable != isAvailable) {
1354                    serviceState.mAvailable = isAvailable;
1355                    broadcastServiceAvailabilityChangedLocked(serviceState);
1356                }
1357            }
1358        }
1359    }
1360
1361    private final class LogHandler extends Handler {
1362        private static final int MSG_OPEN_ENTRY = 1;
1363        private static final int MSG_UPDATE_ENTRY = 2;
1364        private static final int MSG_CLOSE_ENTRY = 3;
1365
1366        public LogHandler(Looper looper) {
1367            super(looper);
1368        }
1369
1370        @Override
1371        public void handleMessage(Message msg) {
1372            switch (msg.what) {
1373                case MSG_OPEN_ENTRY: {
1374                    SomeArgs args = (SomeArgs) msg.obj;
1375                    Uri uri = (Uri) args.arg1;
1376                    long channelId = (long) args.arg2;
1377                    long time = (long) args.arg3;
1378                    onOpenEntry(uri, channelId, time);
1379                    args.recycle();
1380                    return;
1381                }
1382                case MSG_UPDATE_ENTRY: {
1383                    SomeArgs args = (SomeArgs) msg.obj;
1384                    Uri uri = (Uri) args.arg1;
1385                    long channelId = (long) args.arg2;
1386                    long time = (long) args.arg3;
1387                    onUpdateEntry(uri, channelId, time);
1388                    args.recycle();
1389                    return;
1390                }
1391                case MSG_CLOSE_ENTRY: {
1392                    SomeArgs args = (SomeArgs) msg.obj;
1393                    Uri uri = (Uri) args.arg1;
1394                    long time = (long) args.arg2;
1395                    onCloseEntry(uri, time);
1396                    args.recycle();
1397                    return;
1398                }
1399                default: {
1400                    Slog.w(TAG, "Unhandled message code: " + msg.what);
1401                    return;
1402                }
1403            }
1404        }
1405
1406        private void onOpenEntry(Uri uri, long channelId, long watchStarttime) {
1407            String[] projection = {
1408                    TvContract.Programs.COLUMN_TITLE,
1409                    TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS,
1410                    TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS,
1411                    TvContract.Programs.COLUMN_SHORT_DESCRIPTION
1412            };
1413            String selection = TvContract.Programs.COLUMN_CHANNEL_ID + "=? AND "
1414                    + TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS + "<=? AND "
1415                    + TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS + ">?";
1416            String[] selectionArgs = {
1417                    String.valueOf(channelId),
1418                    String.valueOf(watchStarttime),
1419                    String.valueOf(watchStarttime)
1420            };
1421            String sortOrder = TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS + " ASC";
1422            Cursor cursor = null;
1423            try {
1424                cursor = mContentResolver.query(TvContract.Programs.CONTENT_URI, projection,
1425                        selection, selectionArgs, sortOrder);
1426                if (cursor != null && cursor.moveToNext()) {
1427                    ContentValues values = new ContentValues();
1428                    values.put(TvContract.WatchedPrograms.COLUMN_TITLE, cursor.getString(0));
1429                    values.put(TvContract.WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS,
1430                            cursor.getLong(1));
1431                    long endTime = cursor.getLong(2);
1432                    values.put(TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS, endTime);
1433                    values.put(TvContract.WatchedPrograms.COLUMN_DESCRIPTION, cursor.getString(3));
1434                    mContentResolver.update(uri, values, null, null);
1435
1436                    // Schedule an update when the current program ends.
1437                    SomeArgs args = SomeArgs.obtain();
1438                    args.arg1 = uri;
1439                    args.arg2 = channelId;
1440                    args.arg3 = endTime;
1441                    Message msg = obtainMessage(LogHandler.MSG_UPDATE_ENTRY, args);
1442                    sendMessageDelayed(msg, endTime - System.currentTimeMillis());
1443                }
1444            } finally {
1445                if (cursor != null) {
1446                    cursor.close();
1447                }
1448            }
1449        }
1450
1451        private void onUpdateEntry(Uri uri, long channelId, long time) {
1452            String[] projection = {
1453                    TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
1454                    TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS,
1455                    TvContract.WatchedPrograms.COLUMN_TITLE,
1456                    TvContract.WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS,
1457                    TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS,
1458                    TvContract.WatchedPrograms.COLUMN_DESCRIPTION
1459            };
1460            Cursor cursor = null;
1461            try {
1462                cursor = mContentResolver.query(uri, projection, null, null, null);
1463                if (cursor != null && cursor.moveToNext()) {
1464                    long watchStartTime = cursor.getLong(0);
1465                    long watchEndTime = cursor.getLong(1);
1466                    String title = cursor.getString(2);
1467                    long startTime = cursor.getLong(3);
1468                    long endTime = cursor.getLong(4);
1469                    String description = cursor.getString(5);
1470
1471                    // Do nothing if the current log entry is already closed.
1472                    if (watchEndTime > 0) {
1473                        return;
1474                    }
1475
1476                    // The current program has just ended. Create a (complete) log entry off the
1477                    // current entry.
1478                    ContentValues values = new ContentValues();
1479                    values.put(TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
1480                            watchStartTime);
1481                    values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, time);
1482                    values.put(TvContract.WatchedPrograms.COLUMN_CHANNEL_ID, channelId);
1483                    values.put(TvContract.WatchedPrograms.COLUMN_TITLE, title);
1484                    values.put(TvContract.WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS, startTime);
1485                    values.put(TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS, endTime);
1486                    values.put(TvContract.WatchedPrograms.COLUMN_DESCRIPTION, description);
1487                    mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
1488                }
1489            } finally {
1490                if (cursor != null) {
1491                    cursor.close();
1492                }
1493            }
1494            // Re-open the current log entry with the next program information.
1495            onOpenEntry(uri, channelId, time);
1496        }
1497
1498        private void onCloseEntry(Uri uri, long watchEndTime) {
1499            ContentValues values = new ContentValues();
1500            values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, watchEndTime);
1501            mContentResolver.update(uri, values, null, null);
1502        }
1503    }
1504}
1505