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