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