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