TvInputManagerService.java revision e14c3f4fc42e2dc83cf4aba711c5ff52d8bbe3ea
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 onSessionEvent(String eventType, Bundle eventArgs) {
489                synchronized (mLock) {
490                    if (DEBUG) {
491                        Slog.d(TAG, "onEvent(what=" + eventType + ", data=" + eventArgs + ")");
492                    }
493                    if (sessionState.mSession == null || sessionState.mClient == null) {
494                        return;
495                    }
496                    try {
497                        sessionState.mClient.onSessionEvent(eventType, eventArgs,
498                                sessionState.mSeq);
499                    } catch (RemoteException e) {
500                        Slog.e(TAG, "error in onSessionEvent");
501                    }
502                }
503            }
504        };
505
506        // Create a session. When failed, send a null token immediately.
507        try {
508            service.createSession(channels[1], callback);
509        } catch (RemoteException e) {
510            Slog.e(TAG, "error in createSession", e);
511            removeSessionStateLocked(sessionToken, userId);
512            sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mInputId, null, null,
513                    sessionState.mSeq);
514        }
515        channels[1].dispose();
516    }
517
518    private void sendSessionTokenToClientLocked(ITvInputClient client, String inputId,
519            IBinder sessionToken, InputChannel channel, int seq) {
520        try {
521            client.onSessionCreated(inputId, sessionToken, channel, seq);
522        } catch (RemoteException exception) {
523            Slog.e(TAG, "error in onSessionCreated", exception);
524        }
525    }
526
527    private void releaseSessionLocked(IBinder sessionToken, int callingUid, int userId) {
528        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
529        if (sessionState.mSession != null) {
530            try {
531                sessionState.mSession.release();
532            } catch (RemoteException e) {
533                Slog.w(TAG, "session is already disapeared", e);
534            }
535            sessionState.mSession = null;
536        }
537        removeSessionStateLocked(sessionToken, userId);
538    }
539
540    private void removeSessionStateLocked(IBinder sessionToken, int userId) {
541        // Remove the session state from the global session state map of the current user.
542        UserState userState = getUserStateLocked(userId);
543        SessionState sessionState = userState.sessionStateMap.remove(sessionToken);
544
545        // Close the open log entry, if any.
546        if (sessionState.mLogUri != null) {
547            SomeArgs args = SomeArgs.obtain();
548            args.arg1 = sessionState.mLogUri;
549            args.arg2 = System.currentTimeMillis();
550            mLogHandler.obtainMessage(LogHandler.MSG_CLOSE_ENTRY, args).sendToTarget();
551        }
552
553        // Also remove the session token from the session token list of the current client and
554        // service.
555        ClientState clientState = userState.clientStateMap.get(sessionState.mClient.asBinder());
556        if (clientState != null) {
557            clientState.mSessionTokens.remove(sessionToken);
558            if (clientState.isEmpty()) {
559                userState.clientStateMap.remove(sessionState.mClient.asBinder());
560            }
561        }
562
563        ServiceState serviceState = userState.serviceStateMap.get(sessionState.mInputId);
564        if (serviceState != null) {
565            serviceState.mSessionTokens.remove(sessionToken);
566        }
567        updateServiceConnectionLocked(sessionState.mInputId, userId);
568    }
569
570    private void unregisterCallbackInternalLocked(IBinder clientToken, String inputId,
571            int userId) {
572        UserState userState = getUserStateLocked(userId);
573        ClientState clientState = userState.clientStateMap.get(clientToken);
574        if (clientState != null) {
575            clientState.mInputIds.remove(inputId);
576            if (clientState.isEmpty()) {
577                userState.clientStateMap.remove(clientToken);
578            }
579        }
580
581        ServiceState serviceState = userState.serviceStateMap.get(inputId);
582        if (serviceState == null) {
583            return;
584        }
585
586        // Remove this client from the client list and unregister the callback.
587        serviceState.mClientTokens.remove(clientToken);
588        if (!serviceState.mClientTokens.isEmpty()) {
589            // We have other clients who want to keep the callback. Do this later.
590            return;
591        }
592        if (serviceState.mService == null || serviceState.mCallback == null) {
593            return;
594        }
595        try {
596            serviceState.mService.unregisterCallback(serviceState.mCallback);
597        } catch (RemoteException e) {
598            Slog.e(TAG, "error in unregisterCallback", e);
599        } finally {
600            serviceState.mCallback = null;
601            updateServiceConnectionLocked(inputId, userId);
602        }
603    }
604
605    private void broadcastServiceAvailabilityChangedLocked(ServiceState serviceState) {
606        for (IBinder clientToken : serviceState.mClientTokens) {
607            try {
608                ITvInputClient.Stub.asInterface(clientToken).onAvailabilityChanged(
609                        serviceState.mTvInputInfo.getId(), serviceState.mAvailable);
610            } catch (RemoteException e) {
611                Slog.e(TAG, "error in onAvailabilityChanged", e);
612            }
613        }
614    }
615
616    private final class BinderService extends ITvInputManager.Stub {
617        @Override
618        public List<TvInputInfo> getTvInputList(int userId) {
619            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
620                    Binder.getCallingUid(), userId, "getTvInputList");
621            final long identity = Binder.clearCallingIdentity();
622            try {
623                synchronized (mLock) {
624                    UserState userState = getUserStateLocked(resolvedUserId);
625                    return new ArrayList<TvInputInfo>(userState.inputMap.values());
626                }
627            } finally {
628                Binder.restoreCallingIdentity(identity);
629            }
630        }
631
632        @Override
633        public boolean getAvailability(final ITvInputClient client, final String inputId,
634                int userId) {
635            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
636                    Binder.getCallingUid(), userId, "getAvailability");
637            final long identity = Binder.clearCallingIdentity();
638            try {
639                synchronized (mLock) {
640                    UserState userState = getUserStateLocked(resolvedUserId);
641                    ServiceState serviceState = userState.serviceStateMap.get(inputId);
642                    if (serviceState != null) {
643                        // We already know the status of this input service. Return the cached
644                        // status.
645                        return serviceState.mAvailable;
646                    }
647                }
648            } finally {
649                Binder.restoreCallingIdentity(identity);
650            }
651            // STOPSHIP: Redesign the API around the availability change. For now, the service
652            // will be always available.
653            return true;
654        }
655
656        @Override
657        public void registerCallback(final ITvInputClient client, final String inputId,
658                int userId) {
659            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
660                    Binder.getCallingUid(), userId, "registerCallback");
661            final long identity = Binder.clearCallingIdentity();
662            try {
663                synchronized (mLock) {
664                    // Create a new service callback and add it to the callback map of the current
665                    // service.
666                    UserState userState = getUserStateLocked(resolvedUserId);
667                    ServiceState serviceState = userState.serviceStateMap.get(inputId);
668                    if (serviceState == null) {
669                        serviceState = new ServiceState(
670                                userState.inputMap.get(inputId), resolvedUserId);
671                        userState.serviceStateMap.put(inputId, serviceState);
672                    }
673                    IBinder clientToken = client.asBinder();
674                    if (!serviceState.mClientTokens.contains(clientToken)) {
675                        serviceState.mClientTokens.add(clientToken);
676                    }
677
678                    ClientState clientState = userState.clientStateMap.get(clientToken);
679                    if (clientState == null) {
680                        clientState = createClientStateLocked(clientToken, resolvedUserId);
681                    }
682                    if (!clientState.mInputIds.contains(inputId)) {
683                        clientState.mInputIds.add(inputId);
684                    }
685
686                    if (serviceState.mService != null) {
687                        if (serviceState.mCallback != null) {
688                            // We already handled.
689                            return;
690                        }
691                        serviceState.mCallback = new ServiceCallback(resolvedUserId);
692                        try {
693                            serviceState.mService.registerCallback(serviceState.mCallback);
694                        } catch (RemoteException e) {
695                            Slog.e(TAG, "error in registerCallback", e);
696                        }
697                    } else {
698                        updateServiceConnectionLocked(inputId, resolvedUserId);
699                    }
700                }
701            } finally {
702                Binder.restoreCallingIdentity(identity);
703            }
704        }
705
706        @Override
707        public void unregisterCallback(ITvInputClient client, String inputId, int userId) {
708            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
709                    Binder.getCallingUid(), userId, "unregisterCallback");
710            final long identity = Binder.clearCallingIdentity();
711            try {
712                synchronized (mLock) {
713                    unregisterCallbackInternalLocked(client.asBinder(), inputId, resolvedUserId);
714                }
715            } finally {
716                Binder.restoreCallingIdentity(identity);
717            }
718        }
719
720        @Override
721        public void createSession(final ITvInputClient client, final String inputId,
722                int seq, int userId) {
723            final int callingUid = Binder.getCallingUid();
724            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
725                    userId, "createSession");
726            final long identity = Binder.clearCallingIdentity();
727            try {
728                synchronized (mLock) {
729                    UserState userState = getUserStateLocked(resolvedUserId);
730                    ServiceState serviceState = userState.serviceStateMap.get(inputId);
731                    if (serviceState == null) {
732                        serviceState = new ServiceState(
733                                userState.inputMap.get(inputId), resolvedUserId);
734                        userState.serviceStateMap.put(inputId, serviceState);
735                    }
736                    // Send a null token immediately while reconnecting.
737                    if (serviceState.mReconnecting == true) {
738                        sendSessionTokenToClientLocked(client, inputId, null, null, seq);
739                        return;
740                    }
741
742                    // Create a new session token and a session state.
743                    IBinder sessionToken = new Binder();
744                    SessionState sessionState = new SessionState(sessionToken, inputId, client,
745                            seq, callingUid, resolvedUserId);
746
747                    // Add them to the global session state map of the current user.
748                    userState.sessionStateMap.put(sessionToken, sessionState);
749
750                    // Also, add them to the session state map of the current service.
751                    serviceState.mSessionTokens.add(sessionToken);
752
753                    if (serviceState.mService != null) {
754                        createSessionInternalLocked(serviceState.mService, sessionToken,
755                                resolvedUserId);
756                    } else {
757                        updateServiceConnectionLocked(inputId, resolvedUserId);
758                    }
759                }
760            } finally {
761                Binder.restoreCallingIdentity(identity);
762            }
763        }
764
765        @Override
766        public void releaseSession(IBinder sessionToken, int userId) {
767            final int callingUid = Binder.getCallingUid();
768            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
769                    userId, "releaseSession");
770            final long identity = Binder.clearCallingIdentity();
771            try {
772                synchronized (mLock) {
773                    releaseSessionLocked(sessionToken, callingUid, resolvedUserId);
774                }
775            } finally {
776                Binder.restoreCallingIdentity(identity);
777            }
778        }
779
780        @Override
781        public void setSurface(IBinder sessionToken, Surface surface, int userId) {
782            final int callingUid = Binder.getCallingUid();
783            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
784                    userId, "setSurface");
785            final long identity = Binder.clearCallingIdentity();
786            try {
787                synchronized (mLock) {
788                    try {
789                        getSessionLocked(sessionToken, callingUid, resolvedUserId).setSurface(
790                                surface);
791                    } catch (RemoteException e) {
792                        Slog.e(TAG, "error in setSurface", e);
793                    }
794                }
795            } finally {
796                if (surface != null) {
797                    // surface is not used in TvInputManagerService.
798                    surface.release();
799                }
800                Binder.restoreCallingIdentity(identity);
801            }
802        }
803
804        @Override
805        public void setVolume(IBinder sessionToken, float volume, int userId) {
806            final int callingUid = Binder.getCallingUid();
807            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
808                    userId, "setVolume");
809            final long identity = Binder.clearCallingIdentity();
810            try {
811                synchronized (mLock) {
812                    try {
813                        getSessionLocked(sessionToken, callingUid, resolvedUserId).setVolume(
814                                volume);
815                    } catch (RemoteException e) {
816                        Slog.e(TAG, "error in setVolume", e);
817                    }
818                }
819            } finally {
820                Binder.restoreCallingIdentity(identity);
821            }
822        }
823
824        @Override
825        public void tune(IBinder sessionToken, final Uri channelUri, int userId) {
826            final int callingUid = Binder.getCallingUid();
827            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
828                    userId, "tune");
829            final long identity = Binder.clearCallingIdentity();
830            try {
831                synchronized (mLock) {
832                    try {
833                        getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(channelUri);
834
835                        long currentTime = System.currentTimeMillis();
836                        long channelId = ContentUris.parseId(channelUri);
837
838                        // Close the open log entry first, if any.
839                        UserState userState = getUserStateLocked(resolvedUserId);
840                        SessionState sessionState = userState.sessionStateMap.get(sessionToken);
841                        if (sessionState.mLogUri != null) {
842                            SomeArgs args = SomeArgs.obtain();
843                            args.arg1 = sessionState.mLogUri;
844                            args.arg2 = currentTime;
845                            mLogHandler.obtainMessage(LogHandler.MSG_CLOSE_ENTRY, args)
846                                    .sendToTarget();
847                        }
848
849                        // Create a log entry and fill it later.
850                        String packageName = userState.inputMap.get(sessionState.mInputId)
851                                .getServiceInfo().packageName;
852                        ContentValues values = new ContentValues();
853                        values.put(TvContract.WatchedPrograms.COLUMN_PACKAGE_NAME, packageName);
854                        values.put(TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
855                                currentTime);
856                        values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, 0);
857                        values.put(TvContract.WatchedPrograms.COLUMN_CHANNEL_ID, channelId);
858
859                        sessionState.mLogUri = mContentResolver.insert(
860                                TvContract.WatchedPrograms.CONTENT_URI, values);
861                        SomeArgs args = SomeArgs.obtain();
862                        args.arg1 = sessionState.mLogUri;
863                        args.arg2 = ContentUris.parseId(channelUri);
864                        args.arg3 = currentTime;
865                        mLogHandler.obtainMessage(LogHandler.MSG_OPEN_ENTRY, args).sendToTarget();
866                    } catch (RemoteException e) {
867                        Slog.e(TAG, "error in tune", e);
868                        return;
869                    }
870                }
871            } finally {
872                Binder.restoreCallingIdentity(identity);
873            }
874        }
875
876        @Override
877        public void createOverlayView(IBinder sessionToken, IBinder windowToken, Rect frame,
878                int userId) {
879            final int callingUid = Binder.getCallingUid();
880            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
881                    userId, "createOverlayView");
882            final long identity = Binder.clearCallingIdentity();
883            try {
884                synchronized (mLock) {
885                    try {
886                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
887                                .createOverlayView(windowToken, frame);
888                    } catch (RemoteException e) {
889                        Slog.e(TAG, "error in createOverlayView", e);
890                    }
891                }
892            } finally {
893                Binder.restoreCallingIdentity(identity);
894            }
895        }
896
897        @Override
898        public void relayoutOverlayView(IBinder sessionToken, Rect frame, int userId) {
899            final int callingUid = Binder.getCallingUid();
900            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
901                    userId, "relayoutOverlayView");
902            final long identity = Binder.clearCallingIdentity();
903            try {
904                synchronized (mLock) {
905                    try {
906                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
907                                .relayoutOverlayView(frame);
908                    } catch (RemoteException e) {
909                        Slog.e(TAG, "error in relayoutOverlayView", e);
910                    }
911                }
912            } finally {
913                Binder.restoreCallingIdentity(identity);
914            }
915        }
916
917        @Override
918        public void removeOverlayView(IBinder sessionToken, int userId) {
919            final int callingUid = Binder.getCallingUid();
920            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
921                    userId, "removeOverlayView");
922            final long identity = Binder.clearCallingIdentity();
923            try {
924                synchronized (mLock) {
925                    try {
926                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
927                                .removeOverlayView();
928                    } catch (RemoteException e) {
929                        Slog.e(TAG, "error in removeOverlayView", e);
930                    }
931                }
932            } finally {
933                Binder.restoreCallingIdentity(identity);
934            }
935        }
936
937        @Override
938        public List<TvInputHardwareInfo> getHardwareList() throws RemoteException {
939            if (mContext.checkCallingPermission(
940                    android.Manifest.permission.TV_INPUT_HARDWARE)
941                    != PackageManager.PERMISSION_GRANTED) {
942                return null;
943            }
944
945            final long identity = Binder.clearCallingIdentity();
946            try {
947                return mTvInputHardwareManager.getHardwareList();
948            } finally {
949                Binder.restoreCallingIdentity(identity);
950            }
951        }
952
953        @Override
954        public ITvInputHardware acquireTvInputHardware(int deviceId,
955                ITvInputHardwareCallback callback, int userId) throws RemoteException {
956            if (mContext.checkCallingPermission(
957                    android.Manifest.permission.TV_INPUT_HARDWARE)
958                    != PackageManager.PERMISSION_GRANTED) {
959                return null;
960            }
961
962            final long identity = Binder.clearCallingIdentity();
963            final int callingUid = Binder.getCallingUid();
964            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
965                    userId, "acquireTvInputHardware");
966            try {
967                return mTvInputHardwareManager.acquireHardware(
968                        deviceId, callback, callingUid, resolvedUserId);
969            } finally {
970                Binder.restoreCallingIdentity(identity);
971            }
972        }
973
974        @Override
975        public void releaseTvInputHardware(int deviceId, ITvInputHardware hardware, int userId)
976                throws RemoteException {
977            if (mContext.checkCallingPermission(
978                    android.Manifest.permission.TV_INPUT_HARDWARE)
979                    != PackageManager.PERMISSION_GRANTED) {
980                return;
981            }
982
983            final long identity = Binder.clearCallingIdentity();
984            final int callingUid = Binder.getCallingUid();
985            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
986                    userId, "releaseTvInputHardware");
987            try {
988                mTvInputHardwareManager.releaseHardware(
989                        deviceId, hardware, callingUid, resolvedUserId);
990            } finally {
991                Binder.restoreCallingIdentity(identity);
992            }
993        }
994
995        @Override
996        protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
997            final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
998            if (mContext.checkCallingOrSelfPermission(
999                    android.Manifest.permission.DUMP)
1000                    != PackageManager.PERMISSION_GRANTED) {
1001                pw.println("Permission Denial: can't dump TvInputManager " +
1002                        "from from pid=" + Binder.getCallingPid() + ", uid=" +
1003                        Binder.getCallingUid());
1004                return;
1005            }
1006
1007            synchronized (mLock) {
1008                pw.println("User Ids (Current user: " + mCurrentUserId + "):");
1009                pw.increaseIndent();
1010                for (int i = 0; i < mUserStates.size(); i++) {
1011                    int userId = mUserStates.keyAt(i);
1012                    pw.println(Integer.valueOf(userId));
1013                }
1014                pw.decreaseIndent();
1015
1016                for (int i = 0; i < mUserStates.size(); i++) {
1017                    int userId = mUserStates.keyAt(i);
1018                    UserState userState = getUserStateLocked(userId);
1019
1020                    pw.println("UserState (" + userId + "):");
1021                    pw.increaseIndent();
1022
1023                    pw.println("inputMap: inputId -> TvInputInfo");
1024                    pw.increaseIndent();
1025                    for (TvInputInfo info : userState.inputMap.values()) {
1026                        pw.println(info.toString());
1027                    }
1028                    pw.decreaseIndent();
1029
1030                    pw.println("packageList:");
1031                    pw.increaseIndent();
1032                    for (String packageName : userState.packageList) {
1033                        pw.println(packageName);
1034                    }
1035                    pw.decreaseIndent();
1036
1037                    pw.println("clientStateMap: ITvInputClient -> ClientState");
1038                    pw.increaseIndent();
1039                    for (Map.Entry<IBinder, ClientState> entry :
1040                            userState.clientStateMap.entrySet()) {
1041                        ClientState client = entry.getValue();
1042                        pw.println(entry.getKey() + ": " + client);
1043
1044                        pw.increaseIndent();
1045
1046                        pw.println("mInputIds:");
1047                        pw.increaseIndent();
1048                        for (String inputId : client.mInputIds) {
1049                            pw.println(inputId);
1050                        }
1051                        pw.decreaseIndent();
1052
1053                        pw.println("mSessionTokens:");
1054                        pw.increaseIndent();
1055                        for (IBinder token : client.mSessionTokens) {
1056                            pw.println("" + token);
1057                        }
1058                        pw.decreaseIndent();
1059
1060                        pw.println("mClientTokens: " + client.mClientToken);
1061                        pw.println("mUserId: " + client.mUserId);
1062
1063                        pw.decreaseIndent();
1064                    }
1065                    pw.decreaseIndent();
1066
1067                    pw.println("serviceStateMap: inputId -> ServiceState");
1068                    pw.increaseIndent();
1069                    for (Map.Entry<String, ServiceState> entry :
1070                            userState.serviceStateMap.entrySet()) {
1071                        ServiceState service = entry.getValue();
1072                        pw.println(entry.getKey() + ": " + service);
1073
1074                        pw.increaseIndent();
1075
1076                        pw.println("mClientTokens:");
1077                        pw.increaseIndent();
1078                        for (IBinder token : service.mClientTokens) {
1079                            pw.println("" + token);
1080                        }
1081                        pw.decreaseIndent();
1082
1083                        pw.println("mSessionTokens:");
1084                        pw.increaseIndent();
1085                        for (IBinder token : service.mSessionTokens) {
1086                            pw.println("" + token);
1087                        }
1088                        pw.decreaseIndent();
1089
1090                        pw.println("mService: " + service.mService);
1091                        pw.println("mCallback: " + service.mCallback);
1092                        pw.println("mBound: " + service.mBound);
1093                        pw.println("mAvailable: " + service.mAvailable);
1094                        pw.println("mReconnecting: " + service.mReconnecting);
1095
1096                        pw.decreaseIndent();
1097                    }
1098                    pw.decreaseIndent();
1099
1100                    pw.println("sessionStateMap: ITvInputSession -> SessionState");
1101                    pw.increaseIndent();
1102                    for (Map.Entry<IBinder, SessionState> entry :
1103                            userState.sessionStateMap.entrySet()) {
1104                        SessionState session = entry.getValue();
1105                        pw.println(entry.getKey() + ": " + session);
1106
1107                        pw.increaseIndent();
1108                        pw.println("mInputId: " + session.mInputId);
1109                        pw.println("mClient: " + session.mClient);
1110                        pw.println("mSeq: " + session.mSeq);
1111                        pw.println("mCallingUid: " + session.mCallingUid);
1112                        pw.println("mUserId: " + session.mUserId);
1113                        pw.println("mSessionToken: " + session.mSessionToken);
1114                        pw.println("mSession: " + session.mSession);
1115                        pw.println("mLogUri: " + session.mLogUri);
1116                        pw.decreaseIndent();
1117                    }
1118                    pw.decreaseIndent();
1119
1120                    pw.decreaseIndent();
1121                }
1122            }
1123        }
1124    }
1125
1126    private static final class UserState {
1127        // A mapping from the TV input id to its TvInputInfo.
1128        private final Map<String, TvInputInfo> inputMap = new HashMap<String,TvInputInfo>();
1129
1130        // A list of all TV input packages.
1131        private final Set<String> packageList = new HashSet<String>();
1132
1133        // A mapping from the token of a client to its state.
1134        private final Map<IBinder, ClientState> clientStateMap =
1135                new HashMap<IBinder, ClientState>();
1136
1137        // A mapping from the name of a TV input service to its state.
1138        private final Map<String, ServiceState> serviceStateMap =
1139                new HashMap<String, ServiceState>();
1140
1141        // A mapping from the token of a TV input session to its state.
1142        private final Map<IBinder, SessionState> sessionStateMap =
1143                new HashMap<IBinder, SessionState>();
1144    }
1145
1146    private final class ClientState implements IBinder.DeathRecipient {
1147        private final List<String> mInputIds = new ArrayList<String>();
1148        private final List<IBinder> mSessionTokens = new ArrayList<IBinder>();
1149
1150        private IBinder mClientToken;
1151        private final int mUserId;
1152
1153        ClientState(IBinder clientToken, int userId) {
1154            mClientToken = clientToken;
1155            mUserId = userId;
1156        }
1157
1158        public boolean isEmpty() {
1159            return mInputIds.isEmpty() && mSessionTokens.isEmpty();
1160        }
1161
1162        @Override
1163        public void binderDied() {
1164            synchronized (mLock) {
1165                UserState userState = getUserStateLocked(mUserId);
1166                // DO NOT remove the client state of clientStateMap in this method. It will be
1167                // removed in releaseSessionLocked() or unregisterCallbackInternalLocked().
1168                ClientState clientState = userState.clientStateMap.get(mClientToken);
1169                if (clientState != null) {
1170                    while (clientState.mSessionTokens.size() > 0) {
1171                        releaseSessionLocked(
1172                                clientState.mSessionTokens.get(0), Process.SYSTEM_UID, mUserId);
1173                    }
1174                    while (clientState.mInputIds.size() > 0) {
1175                        unregisterCallbackInternalLocked(
1176                                mClientToken, clientState.mInputIds.get(0), mUserId);
1177                    }
1178                }
1179                mClientToken = null;
1180            }
1181        }
1182    }
1183
1184    private final class ServiceState {
1185        private final List<IBinder> mClientTokens = new ArrayList<IBinder>();
1186        private final List<IBinder> mSessionTokens = new ArrayList<IBinder>();
1187        private final ServiceConnection mConnection;
1188        private final TvInputInfo mTvInputInfo;
1189
1190        private ITvInputService mService;
1191        private ServiceCallback mCallback;
1192        private boolean mBound;
1193        private boolean mAvailable;
1194        private boolean mReconnecting;
1195
1196        private ServiceState(TvInputInfo inputInfo, int userId) {
1197            mTvInputInfo = inputInfo;
1198            mConnection = new InputServiceConnection(inputInfo, userId);
1199        }
1200    }
1201
1202    private final class SessionState implements IBinder.DeathRecipient {
1203        private final String mInputId;
1204        private final ITvInputClient mClient;
1205        private final int mSeq;
1206        private final int mCallingUid;
1207        private final int mUserId;
1208        private final IBinder mSessionToken;
1209        private ITvInputSession mSession;
1210        private Uri mLogUri;
1211
1212        private SessionState(IBinder sessionToken, String inputId, ITvInputClient client, int seq,
1213                int callingUid, int userId) {
1214            mSessionToken = sessionToken;
1215            mInputId = inputId;
1216            mClient = client;
1217            mSeq = seq;
1218            mCallingUid = callingUid;
1219            mUserId = userId;
1220        }
1221
1222        @Override
1223        public void binderDied() {
1224            synchronized (mLock) {
1225                mSession = null;
1226                if (mClient != null) {
1227                    try {
1228                        mClient.onSessionReleased(mSeq);
1229                    } catch(RemoteException e) {
1230                        Slog.e(TAG, "error in onSessionReleased", e);
1231                    }
1232                }
1233                removeSessionStateLocked(mSessionToken, mUserId);
1234            }
1235        }
1236    }
1237
1238    private final class InputServiceConnection implements ServiceConnection {
1239        private final TvInputInfo mTvInputInfo;
1240        private final int mUserId;
1241
1242        private InputServiceConnection(TvInputInfo inputInfo, int userId) {
1243            mUserId = userId;
1244            mTvInputInfo = inputInfo;
1245        }
1246
1247        @Override
1248        public void onServiceConnected(ComponentName name, IBinder service) {
1249            if (DEBUG) {
1250                Slog.d(TAG, "onServiceConnected(inputId=" + mTvInputInfo.getId() + ")");
1251            }
1252            synchronized (mLock) {
1253                ServiceState serviceState = getServiceStateLocked(mTvInputInfo.getId(), mUserId);
1254                serviceState.mService = ITvInputService.Stub.asInterface(service);
1255
1256                // Register a callback, if we need to.
1257                if (!serviceState.mClientTokens.isEmpty() && serviceState.mCallback == null) {
1258                    serviceState.mCallback = new ServiceCallback(mUserId);
1259                    try {
1260                        serviceState.mService.registerCallback(serviceState.mCallback);
1261                    } catch (RemoteException e) {
1262                        Slog.e(TAG, "error in registerCallback", e);
1263                    }
1264                }
1265
1266                // And create sessions, if any.
1267                for (IBinder sessionToken : serviceState.mSessionTokens) {
1268                    createSessionInternalLocked(serviceState.mService, sessionToken, mUserId);
1269                }
1270            }
1271        }
1272
1273        @Override
1274        public void onServiceDisconnected(ComponentName name) {
1275            if (DEBUG) {
1276                Slog.d(TAG, "onServiceDisconnected(inputId=" + mTvInputInfo.getId() + ")");
1277            }
1278            if (!mTvInputInfo.getComponent().equals(name)) {
1279                throw new IllegalArgumentException("Mismatched ComponentName: "
1280                        + mTvInputInfo.getComponent() + " (expected), " + name + " (actual).");
1281            }
1282            synchronized (mLock) {
1283                UserState userState = getUserStateLocked(mUserId);
1284                ServiceState serviceState = userState.serviceStateMap.get(mTvInputInfo.getId());
1285                if (serviceState != null) {
1286                    serviceState.mReconnecting = true;
1287                    serviceState.mBound = false;
1288                    serviceState.mService = null;
1289                    serviceState.mCallback = null;
1290
1291                    // Send null tokens for not finishing create session events.
1292                    for (IBinder sessionToken : serviceState.mSessionTokens) {
1293                        SessionState sessionState = userState.sessionStateMap.get(sessionToken);
1294                        if (sessionState.mSession == null) {
1295                            removeSessionStateLocked(sessionToken, sessionState.mUserId);
1296                            sendSessionTokenToClientLocked(sessionState.mClient,
1297                                    sessionState.mInputId, null, null, sessionState.mSeq);
1298                        }
1299                    }
1300
1301                    if (serviceState.mAvailable) {
1302                        serviceState.mAvailable = false;
1303                        broadcastServiceAvailabilityChangedLocked(serviceState);
1304                    }
1305                    updateServiceConnectionLocked(mTvInputInfo.getId(), mUserId);
1306                }
1307            }
1308        }
1309    }
1310
1311    private final class ServiceCallback extends ITvInputServiceCallback.Stub {
1312        private final int mUserId;
1313
1314        ServiceCallback(int userId) {
1315            mUserId = userId;
1316        }
1317
1318        @Override
1319        public void onAvailabilityChanged(String inputId, boolean isAvailable) {
1320            if (DEBUG) {
1321                Slog.d(TAG, "onAvailabilityChanged(inputId=" + inputId + ", isAvailable="
1322                        + isAvailable + ")");
1323            }
1324            synchronized (mLock) {
1325                ServiceState serviceState = getServiceStateLocked(inputId, mUserId);
1326                if (serviceState.mAvailable != isAvailable) {
1327                    serviceState.mAvailable = isAvailable;
1328                    broadcastServiceAvailabilityChangedLocked(serviceState);
1329                }
1330            }
1331        }
1332    }
1333
1334    private final class LogHandler extends Handler {
1335        private static final int MSG_OPEN_ENTRY = 1;
1336        private static final int MSG_UPDATE_ENTRY = 2;
1337        private static final int MSG_CLOSE_ENTRY = 3;
1338
1339        public LogHandler(Looper looper) {
1340            super(looper);
1341        }
1342
1343        @Override
1344        public void handleMessage(Message msg) {
1345            switch (msg.what) {
1346                case MSG_OPEN_ENTRY: {
1347                    SomeArgs args = (SomeArgs) msg.obj;
1348                    Uri uri = (Uri) args.arg1;
1349                    long channelId = (long) args.arg2;
1350                    long time = (long) args.arg3;
1351                    onOpenEntry(uri, channelId, time);
1352                    args.recycle();
1353                    return;
1354                }
1355                case MSG_UPDATE_ENTRY: {
1356                    SomeArgs args = (SomeArgs) msg.obj;
1357                    Uri uri = (Uri) args.arg1;
1358                    long channelId = (long) args.arg2;
1359                    long time = (long) args.arg3;
1360                    onUpdateEntry(uri, channelId, time);
1361                    args.recycle();
1362                    return;
1363                }
1364                case MSG_CLOSE_ENTRY: {
1365                    SomeArgs args = (SomeArgs) msg.obj;
1366                    Uri uri = (Uri) args.arg1;
1367                    long time = (long) args.arg2;
1368                    onCloseEntry(uri, time);
1369                    args.recycle();
1370                    return;
1371                }
1372                default: {
1373                    Slog.w(TAG, "Unhandled message code: " + msg.what);
1374                    return;
1375                }
1376            }
1377        }
1378
1379        private void onOpenEntry(Uri uri, long channelId, long watchStarttime) {
1380            String[] projection = {
1381                    TvContract.Programs.COLUMN_TITLE,
1382                    TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS,
1383                    TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS,
1384                    TvContract.Programs.COLUMN_SHORT_DESCRIPTION
1385            };
1386            String selection = TvContract.Programs.COLUMN_CHANNEL_ID + "=? AND "
1387                    + TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS + "<=? AND "
1388                    + TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS + ">?";
1389            String[] selectionArgs = {
1390                    String.valueOf(channelId),
1391                    String.valueOf(watchStarttime),
1392                    String.valueOf(watchStarttime)
1393            };
1394            String sortOrder = TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS + " ASC";
1395            Cursor cursor = null;
1396            try {
1397                cursor = mContentResolver.query(TvContract.Programs.CONTENT_URI, projection,
1398                        selection, selectionArgs, sortOrder);
1399                if (cursor != null && cursor.moveToNext()) {
1400                    ContentValues values = new ContentValues();
1401                    values.put(TvContract.WatchedPrograms.COLUMN_TITLE, cursor.getString(0));
1402                    values.put(TvContract.WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS,
1403                            cursor.getLong(1));
1404                    long endTime = cursor.getLong(2);
1405                    values.put(TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS, endTime);
1406                    values.put(TvContract.WatchedPrograms.COLUMN_DESCRIPTION, cursor.getString(3));
1407                    mContentResolver.update(uri, values, null, null);
1408
1409                    // Schedule an update when the current program ends.
1410                    SomeArgs args = SomeArgs.obtain();
1411                    args.arg1 = uri;
1412                    args.arg2 = channelId;
1413                    args.arg3 = endTime;
1414                    Message msg = obtainMessage(LogHandler.MSG_UPDATE_ENTRY, args);
1415                    sendMessageDelayed(msg, endTime - System.currentTimeMillis());
1416                }
1417            } finally {
1418                if (cursor != null) {
1419                    cursor.close();
1420                }
1421            }
1422        }
1423
1424        private void onUpdateEntry(Uri uri, long channelId, long time) {
1425            String[] projection = {
1426                    TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
1427                    TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS,
1428                    TvContract.WatchedPrograms.COLUMN_TITLE,
1429                    TvContract.WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS,
1430                    TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS,
1431                    TvContract.WatchedPrograms.COLUMN_DESCRIPTION
1432            };
1433            Cursor cursor = null;
1434            try {
1435                cursor = mContentResolver.query(uri, projection, null, null, null);
1436                if (cursor != null && cursor.moveToNext()) {
1437                    long watchStartTime = cursor.getLong(0);
1438                    long watchEndTime = cursor.getLong(1);
1439                    String title = cursor.getString(2);
1440                    long startTime = cursor.getLong(3);
1441                    long endTime = cursor.getLong(4);
1442                    String description = cursor.getString(5);
1443
1444                    // Do nothing if the current log entry is already closed.
1445                    if (watchEndTime > 0) {
1446                        return;
1447                    }
1448
1449                    // The current program has just ended. Create a (complete) log entry off the
1450                    // current entry.
1451                    ContentValues values = new ContentValues();
1452                    values.put(TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
1453                            watchStartTime);
1454                    values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, time);
1455                    values.put(TvContract.WatchedPrograms.COLUMN_CHANNEL_ID, channelId);
1456                    values.put(TvContract.WatchedPrograms.COLUMN_TITLE, title);
1457                    values.put(TvContract.WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS, startTime);
1458                    values.put(TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS, endTime);
1459                    values.put(TvContract.WatchedPrograms.COLUMN_DESCRIPTION, description);
1460                    mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
1461                }
1462            } finally {
1463                if (cursor != null) {
1464                    cursor.close();
1465                }
1466            }
1467            // Re-open the current log entry with the next program information.
1468            onOpenEntry(uri, channelId, time);
1469        }
1470
1471        private void onCloseEntry(Uri uri, long watchEndTime) {
1472            ContentValues values = new ContentValues();
1473            values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, watchEndTime);
1474            mContentResolver.update(uri, values, null, null);
1475        }
1476    }
1477}
1478