TvInputManagerService.java revision 969167dc05a6485a32d160895871cff46fd81884
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 setVolume(IBinder sessionToken, float volume, int userId) {
820            final int callingUid = Binder.getCallingUid();
821            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
822                    userId, "setVolume");
823            final long identity = Binder.clearCallingIdentity();
824            try {
825                synchronized (mLock) {
826                    try {
827                        getSessionLocked(sessionToken, callingUid, resolvedUserId).setVolume(
828                                volume);
829                    } catch (RemoteException e) {
830                        Slog.e(TAG, "error in setVolume", e);
831                    }
832                }
833            } finally {
834                Binder.restoreCallingIdentity(identity);
835            }
836        }
837
838        @Override
839        public void tune(IBinder sessionToken, final Uri channelUri, int userId) {
840            final int callingUid = Binder.getCallingUid();
841            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
842                    userId, "tune");
843            final long identity = Binder.clearCallingIdentity();
844            try {
845                synchronized (mLock) {
846                    try {
847                        getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(channelUri);
848
849                        long currentTime = System.currentTimeMillis();
850                        long channelId = ContentUris.parseId(channelUri);
851
852                        // Close the open log entry first, if any.
853                        UserState userState = getUserStateLocked(resolvedUserId);
854                        SessionState sessionState = userState.sessionStateMap.get(sessionToken);
855                        if (sessionState.mLogUri != null) {
856                            SomeArgs args = SomeArgs.obtain();
857                            args.arg1 = sessionState.mLogUri;
858                            args.arg2 = currentTime;
859                            mLogHandler.obtainMessage(LogHandler.MSG_CLOSE_ENTRY, args)
860                                    .sendToTarget();
861                        }
862
863                        // Create a log entry and fill it later.
864                        String packageName = userState.inputMap.get(sessionState.mInputId).mInfo
865                                .getServiceInfo().packageName;
866                        ContentValues values = new ContentValues();
867                        values.put(TvContract.WatchedPrograms.COLUMN_PACKAGE_NAME, packageName);
868                        values.put(TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
869                                currentTime);
870                        values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, 0);
871                        values.put(TvContract.WatchedPrograms.COLUMN_CHANNEL_ID, channelId);
872
873                        sessionState.mLogUri = mContentResolver.insert(
874                                TvContract.WatchedPrograms.CONTENT_URI, values);
875                        SomeArgs args = SomeArgs.obtain();
876                        args.arg1 = sessionState.mLogUri;
877                        args.arg2 = ContentUris.parseId(channelUri);
878                        args.arg3 = currentTime;
879                        mLogHandler.obtainMessage(LogHandler.MSG_OPEN_ENTRY, args).sendToTarget();
880                    } catch (RemoteException e) {
881                        Slog.e(TAG, "error in tune", e);
882                        return;
883                    }
884                }
885            } finally {
886                Binder.restoreCallingIdentity(identity);
887            }
888        }
889
890        @Override
891        public void setCaptionEnabled(IBinder sessionToken, boolean enabled, int userId) {
892            final int callingUid = Binder.getCallingUid();
893            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
894                    userId, "setCaptionEnabled");
895            final long identity = Binder.clearCallingIdentity();
896            try {
897                synchronized (mLock) {
898                    try {
899                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
900                                .setCaptionEnabled(enabled);
901                    } catch (RemoteException e) {
902                        Slog.e(TAG, "error in setCaptionEnabled", e);
903                    }
904                }
905            } finally {
906                Binder.restoreCallingIdentity(identity);
907            }
908        }
909
910        @Override
911        public void selectTrack(IBinder sessionToken, TvTrackInfo track, int userId) {
912            final int callingUid = Binder.getCallingUid();
913            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
914                    userId, "selectTrack");
915            final long identity = Binder.clearCallingIdentity();
916            try {
917                synchronized (mLock) {
918                    try {
919                        getSessionLocked(sessionToken, callingUid, resolvedUserId).selectTrack(
920                                track);
921                    } catch (RemoteException e) {
922                        Slog.e(TAG, "error in selectTrack", e);
923                    }
924                }
925            } finally {
926                Binder.restoreCallingIdentity(identity);
927            }
928        }
929
930        @Override
931        public void unselectTrack(IBinder sessionToken, TvTrackInfo track, int userId) {
932            final int callingUid = Binder.getCallingUid();
933            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
934                    userId, "unselectTrack");
935            final long identity = Binder.clearCallingIdentity();
936            try {
937                synchronized (mLock) {
938                    try {
939                        getSessionLocked(sessionToken, callingUid, resolvedUserId).unselectTrack(
940                                track);
941                    } catch (RemoteException e) {
942                        Slog.e(TAG, "error in unselectTrack", e);
943                    }
944                }
945            } finally {
946                Binder.restoreCallingIdentity(identity);
947            }
948        }
949
950        @Override
951        public void createOverlayView(IBinder sessionToken, IBinder windowToken, Rect frame,
952                int userId) {
953            final int callingUid = Binder.getCallingUid();
954            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
955                    userId, "createOverlayView");
956            final long identity = Binder.clearCallingIdentity();
957            try {
958                synchronized (mLock) {
959                    try {
960                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
961                                .createOverlayView(windowToken, frame);
962                    } catch (RemoteException e) {
963                        Slog.e(TAG, "error in createOverlayView", e);
964                    }
965                }
966            } finally {
967                Binder.restoreCallingIdentity(identity);
968            }
969        }
970
971        @Override
972        public void relayoutOverlayView(IBinder sessionToken, Rect frame, int userId) {
973            final int callingUid = Binder.getCallingUid();
974            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
975                    userId, "relayoutOverlayView");
976            final long identity = Binder.clearCallingIdentity();
977            try {
978                synchronized (mLock) {
979                    try {
980                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
981                                .relayoutOverlayView(frame);
982                    } catch (RemoteException e) {
983                        Slog.e(TAG, "error in relayoutOverlayView", e);
984                    }
985                }
986            } finally {
987                Binder.restoreCallingIdentity(identity);
988            }
989        }
990
991        @Override
992        public void removeOverlayView(IBinder sessionToken, int userId) {
993            final int callingUid = Binder.getCallingUid();
994            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
995                    userId, "removeOverlayView");
996            final long identity = Binder.clearCallingIdentity();
997            try {
998                synchronized (mLock) {
999                    try {
1000                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
1001                                .removeOverlayView();
1002                    } catch (RemoteException e) {
1003                        Slog.e(TAG, "error in removeOverlayView", e);
1004                    }
1005                }
1006            } finally {
1007                Binder.restoreCallingIdentity(identity);
1008            }
1009        }
1010
1011        @Override
1012        public List<TvInputHardwareInfo> getHardwareList() throws RemoteException {
1013            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
1014                    != PackageManager.PERMISSION_GRANTED) {
1015                return null;
1016            }
1017
1018            final long identity = Binder.clearCallingIdentity();
1019            try {
1020                return mTvInputHardwareManager.getHardwareList();
1021            } finally {
1022                Binder.restoreCallingIdentity(identity);
1023            }
1024        }
1025
1026        @Override
1027        public void registerTvInputInfo(TvInputInfo info, int deviceId) {
1028            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
1029                    != PackageManager.PERMISSION_GRANTED) {
1030                return;
1031            }
1032
1033            final long identity = Binder.clearCallingIdentity();
1034            try {
1035                mTvInputHardwareManager.registerTvInputInfo(info, deviceId);
1036            } finally {
1037                Binder.restoreCallingIdentity(identity);
1038            }
1039        }
1040
1041        @Override
1042        public ITvInputHardware acquireTvInputHardware(int deviceId,
1043                ITvInputHardwareCallback callback, TvInputInfo info, int userId)
1044                throws RemoteException {
1045            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
1046                    != PackageManager.PERMISSION_GRANTED) {
1047                return null;
1048            }
1049
1050            final long identity = Binder.clearCallingIdentity();
1051            final int callingUid = Binder.getCallingUid();
1052            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1053                    userId, "acquireTvInputHardware");
1054            try {
1055                return mTvInputHardwareManager.acquireHardware(
1056                        deviceId, callback, info, callingUid, resolvedUserId);
1057            } finally {
1058                Binder.restoreCallingIdentity(identity);
1059            }
1060        }
1061
1062        @Override
1063        public void releaseTvInputHardware(int deviceId, ITvInputHardware hardware, int userId)
1064                throws RemoteException {
1065            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
1066                    != PackageManager.PERMISSION_GRANTED) {
1067                return;
1068            }
1069
1070            final long identity = Binder.clearCallingIdentity();
1071            final int callingUid = Binder.getCallingUid();
1072            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1073                    userId, "releaseTvInputHardware");
1074            try {
1075                mTvInputHardwareManager.releaseHardware(
1076                        deviceId, hardware, callingUid, resolvedUserId);
1077            } finally {
1078                Binder.restoreCallingIdentity(identity);
1079            }
1080        }
1081
1082        @Override
1083        @SuppressWarnings("resource")
1084        protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
1085            final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
1086            if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
1087                    != PackageManager.PERMISSION_GRANTED) {
1088                pw.println("Permission Denial: can't dump TvInputManager from pid="
1089                        + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
1090                return;
1091            }
1092
1093            synchronized (mLock) {
1094                pw.println("User Ids (Current user: " + mCurrentUserId + "):");
1095                pw.increaseIndent();
1096                for (int i = 0; i < mUserStates.size(); i++) {
1097                    int userId = mUserStates.keyAt(i);
1098                    pw.println(Integer.valueOf(userId));
1099                }
1100                pw.decreaseIndent();
1101
1102                for (int i = 0; i < mUserStates.size(); i++) {
1103                    int userId = mUserStates.keyAt(i);
1104                    UserState userState = getUserStateLocked(userId);
1105                    pw.println("UserState (" + userId + "):");
1106                    pw.increaseIndent();
1107
1108                    pw.println("inputMap: inputId -> TvInputState");
1109                    pw.increaseIndent();
1110                    for (TvInputState state : userState.inputMap.values()) {
1111                        pw.println(state.toString());
1112                    }
1113                    pw.decreaseIndent();
1114
1115                    pw.println("packageSet:");
1116                    pw.increaseIndent();
1117                    for (String packageName : userState.packageSet) {
1118                        pw.println(packageName);
1119                    }
1120                    pw.decreaseIndent();
1121
1122                    pw.println("clientStateMap: ITvInputClient -> ClientState");
1123                    pw.increaseIndent();
1124                    for (Map.Entry<IBinder, ClientState> entry :
1125                            userState.clientStateMap.entrySet()) {
1126                        ClientState client = entry.getValue();
1127                        pw.println(entry.getKey() + ": " + client);
1128
1129                        pw.increaseIndent();
1130
1131                        pw.println("mInputIds:");
1132                        pw.increaseIndent();
1133                        for (String inputId : client.mInputIds) {
1134                            pw.println(inputId);
1135                        }
1136                        pw.decreaseIndent();
1137
1138                        pw.println("mSessionTokens:");
1139                        pw.increaseIndent();
1140                        for (IBinder token : client.mSessionTokens) {
1141                            pw.println("" + token);
1142                        }
1143                        pw.decreaseIndent();
1144
1145                        pw.println("mClientTokens: " + client.mClientToken);
1146                        pw.println("mUserId: " + client.mUserId);
1147
1148                        pw.decreaseIndent();
1149                    }
1150                    pw.decreaseIndent();
1151
1152                    pw.println("serviceStateMap: inputId -> ServiceState");
1153                    pw.increaseIndent();
1154                    for (Map.Entry<String, ServiceState> entry :
1155                            userState.serviceStateMap.entrySet()) {
1156                        ServiceState service = entry.getValue();
1157                        pw.println(entry.getKey() + ": " + service);
1158
1159                        pw.increaseIndent();
1160
1161                        pw.println("mClientTokens:");
1162                        pw.increaseIndent();
1163                        for (IBinder token : service.mClientTokens) {
1164                            pw.println("" + token);
1165                        }
1166                        pw.decreaseIndent();
1167
1168                        pw.println("mSessionTokens:");
1169                        pw.increaseIndent();
1170                        for (IBinder token : service.mSessionTokens) {
1171                            pw.println("" + token);
1172                        }
1173                        pw.decreaseIndent();
1174
1175                        pw.println("mService: " + service.mService);
1176                        pw.println("mCallback: " + service.mCallback);
1177                        pw.println("mBound: " + service.mBound);
1178                        pw.println("mReconnecting: " + service.mReconnecting);
1179
1180                        pw.decreaseIndent();
1181                    }
1182                    pw.decreaseIndent();
1183
1184                    pw.println("sessionStateMap: ITvInputSession -> SessionState");
1185                    pw.increaseIndent();
1186                    for (Map.Entry<IBinder, SessionState> entry :
1187                            userState.sessionStateMap.entrySet()) {
1188                        SessionState session = entry.getValue();
1189                        pw.println(entry.getKey() + ": " + session);
1190
1191                        pw.increaseIndent();
1192                        pw.println("mInputId: " + session.mInputId);
1193                        pw.println("mClient: " + session.mClient);
1194                        pw.println("mSeq: " + session.mSeq);
1195                        pw.println("mCallingUid: " + session.mCallingUid);
1196                        pw.println("mUserId: " + session.mUserId);
1197                        pw.println("mSessionToken: " + session.mSessionToken);
1198                        pw.println("mSession: " + session.mSession);
1199                        pw.println("mLogUri: " + session.mLogUri);
1200                        pw.decreaseIndent();
1201                    }
1202                    pw.decreaseIndent();
1203
1204                    pw.println("callbackSet:");
1205                    pw.increaseIndent();
1206                    for (ITvInputManagerCallback callback : userState.callbackSet) {
1207                        pw.println(callback.toString());
1208                    }
1209                    pw.decreaseIndent();
1210
1211                    pw.decreaseIndent();
1212                }
1213            }
1214        }
1215    }
1216
1217    private static final class TvInputState {
1218        // A TvInputInfo object which represents the TV input.
1219        private TvInputInfo mInfo;
1220
1221        // The state of TV input. Connected by default.
1222        private int mState = INPUT_STATE_CONNECTED;
1223
1224        @Override
1225        public String toString() {
1226            return "mInfo: " + mInfo + "; mState: " + mState;
1227        }
1228    }
1229
1230    private static final class UserState {
1231        // A mapping from the TV input id to its TvInputState.
1232        private Map<String, TvInputState> inputMap = new HashMap<String, TvInputState>();
1233
1234        // A set of all TV input packages.
1235        private final Set<String> packageSet = new HashSet<String>();
1236
1237        // A mapping from the token of a client to its state.
1238        private final Map<IBinder, ClientState> clientStateMap =
1239                new HashMap<IBinder, ClientState>();
1240
1241        // A mapping from the name of a TV input service to its state.
1242        private final Map<String, ServiceState> serviceStateMap =
1243                new HashMap<String, ServiceState>();
1244
1245        // A mapping from the token of a TV input session to its state.
1246        private final Map<IBinder, SessionState> sessionStateMap =
1247                new HashMap<IBinder, SessionState>();
1248
1249        // A set of callbacks.
1250        private final Set<ITvInputManagerCallback> callbackSet =
1251                new HashSet<ITvInputManagerCallback>();
1252    }
1253
1254    private final class ClientState implements IBinder.DeathRecipient {
1255        private final List<String> mInputIds = new ArrayList<String>();
1256        private final List<IBinder> mSessionTokens = new ArrayList<IBinder>();
1257
1258        private IBinder mClientToken;
1259        private final int mUserId;
1260
1261        ClientState(IBinder clientToken, int userId) {
1262            mClientToken = clientToken;
1263            mUserId = userId;
1264        }
1265
1266        public boolean isEmpty() {
1267            return mInputIds.isEmpty() && mSessionTokens.isEmpty();
1268        }
1269
1270        @Override
1271        public void binderDied() {
1272            synchronized (mLock) {
1273                UserState userState = getUserStateLocked(mUserId);
1274                // DO NOT remove the client state of clientStateMap in this method. It will be
1275                // removed in releaseSessionLocked() or unregisterClientInternalLocked().
1276                ClientState clientState = userState.clientStateMap.get(mClientToken);
1277                if (clientState != null) {
1278                    while (clientState.mSessionTokens.size() > 0) {
1279                        releaseSessionLocked(
1280                                clientState.mSessionTokens.get(0), Process.SYSTEM_UID, mUserId);
1281                    }
1282                    while (clientState.mInputIds.size() > 0) {
1283                        unregisterClientInternalLocked(
1284                                mClientToken, clientState.mInputIds.get(0), mUserId);
1285                    }
1286                }
1287                mClientToken = null;
1288            }
1289        }
1290    }
1291
1292    private final class ServiceState {
1293        private final List<IBinder> mClientTokens = new ArrayList<IBinder>();
1294        private final List<IBinder> mSessionTokens = new ArrayList<IBinder>();
1295        private final ServiceConnection mConnection;
1296        private final TvInputInfo mTvInputInfo;
1297
1298        private ITvInputService mService;
1299        private ServiceCallback mCallback;
1300        private boolean mBound;
1301        private boolean mReconnecting;
1302
1303        private ServiceState(TvInputInfo inputInfo, int userId) {
1304            mTvInputInfo = inputInfo;
1305            mConnection = new InputServiceConnection(inputInfo, userId);
1306        }
1307    }
1308
1309    private final class SessionState implements IBinder.DeathRecipient {
1310        private final String mInputId;
1311        private final ITvInputClient mClient;
1312        private final int mSeq;
1313        private final int mCallingUid;
1314        private final int mUserId;
1315        private final IBinder mSessionToken;
1316        private ITvInputSession mSession;
1317        private Uri mLogUri;
1318
1319        private SessionState(IBinder sessionToken, String inputId, ITvInputClient client, int seq,
1320                int callingUid, int userId) {
1321            mSessionToken = sessionToken;
1322            mInputId = inputId;
1323            mClient = client;
1324            mSeq = seq;
1325            mCallingUid = callingUid;
1326            mUserId = userId;
1327        }
1328
1329        @Override
1330        public void binderDied() {
1331            synchronized (mLock) {
1332                mSession = null;
1333                if (mClient != null) {
1334                    try {
1335                        mClient.onSessionReleased(mSeq);
1336                    } catch(RemoteException e) {
1337                        Slog.e(TAG, "error in onSessionReleased", e);
1338                    }
1339                }
1340                removeSessionStateLocked(mSessionToken, mUserId);
1341            }
1342        }
1343    }
1344
1345    private final class InputServiceConnection implements ServiceConnection {
1346        private final TvInputInfo mTvInputInfo;
1347        private final int mUserId;
1348
1349        private InputServiceConnection(TvInputInfo inputInfo, int userId) {
1350            mUserId = userId;
1351            mTvInputInfo = inputInfo;
1352        }
1353
1354        @Override
1355        public void onServiceConnected(ComponentName name, IBinder service) {
1356            String inputId = mTvInputInfo.getId();
1357            if (DEBUG) {
1358                Slog.d(TAG, "onServiceConnected(inputId=" + inputId + ")");
1359            }
1360            synchronized (mLock) {
1361                UserState userState = getUserStateLocked(mUserId);
1362                ServiceState serviceState = userState.serviceStateMap.get(inputId);
1363                serviceState.mService = ITvInputService.Stub.asInterface(service);
1364
1365                // Register a callback, if we need to.
1366                if (!serviceState.mClientTokens.isEmpty() && serviceState.mCallback == null) {
1367                    serviceState.mCallback = new ServiceCallback(mTvInputInfo.getId(), mUserId);
1368                    try {
1369                        serviceState.mService.registerCallback(serviceState.mCallback);
1370                    } catch (RemoteException e) {
1371                        Slog.e(TAG, "error in registerCallback", e);
1372                    }
1373                }
1374
1375                // And create sessions, if any.
1376                for (IBinder sessionToken : serviceState.mSessionTokens) {
1377                    createSessionInternalLocked(serviceState.mService, sessionToken, mUserId);
1378                }
1379
1380                TvInputState inputState = userState.inputMap.get(inputId);
1381                if (inputState != null && inputState.mState != INPUT_STATE_DISCONNECTED) {
1382                    notifyStateChangedLocked(userState, mTvInputInfo.getId(),
1383                            inputState.mState, null);
1384                }
1385            }
1386        }
1387
1388        @Override
1389        public void onServiceDisconnected(ComponentName name) {
1390            if (DEBUG) {
1391                Slog.d(TAG, "onServiceDisconnected(inputId=" + mTvInputInfo.getId() + ")");
1392            }
1393            if (!mTvInputInfo.getComponent().equals(name)) {
1394                throw new IllegalArgumentException("Mismatched ComponentName: "
1395                        + mTvInputInfo.getComponent() + " (expected), " + name + " (actual).");
1396            }
1397            synchronized (mLock) {
1398                UserState userState = getUserStateLocked(mUserId);
1399                ServiceState serviceState = userState.serviceStateMap.get(mTvInputInfo.getId());
1400                if (serviceState != null) {
1401                    serviceState.mReconnecting = true;
1402                    serviceState.mBound = false;
1403                    serviceState.mService = null;
1404                    serviceState.mCallback = null;
1405
1406                    // Send null tokens for not finishing create session events.
1407                    for (IBinder sessionToken : serviceState.mSessionTokens) {
1408                        SessionState sessionState = userState.sessionStateMap.get(sessionToken);
1409                        if (sessionState.mSession == null) {
1410                            removeSessionStateLocked(sessionToken, sessionState.mUserId);
1411                            sendSessionTokenToClientLocked(sessionState.mClient,
1412                                    sessionState.mInputId, null, null, sessionState.mSeq);
1413                        }
1414                    }
1415
1416                    notifyStateChangedLocked(userState, mTvInputInfo.getId(),
1417                            INPUT_STATE_DISCONNECTED, null);
1418                    updateServiceConnectionLocked(mTvInputInfo.getId(), mUserId);
1419                }
1420            }
1421        }
1422    }
1423
1424    private final class ServiceCallback extends ITvInputServiceCallback.Stub {
1425        private final String mInputId;
1426        private final int mUserId;
1427
1428        ServiceCallback(String inputId, int userId) {
1429            mInputId = inputId;
1430            mUserId = userId;
1431        }
1432
1433        @Override
1434        public void onInputStateChanged(int state) {
1435            if (DEBUG) {
1436                Slog.d(TAG, "onInputStateChanged(inputId=" + mInputId + ", state="
1437                        + state + ")");
1438            }
1439            synchronized (mLock) {
1440                setStateLocked(mInputId, state, mUserId);
1441            }
1442        }
1443    }
1444
1445    private final class LogHandler extends Handler {
1446        private static final int MSG_OPEN_ENTRY = 1;
1447        private static final int MSG_UPDATE_ENTRY = 2;
1448        private static final int MSG_CLOSE_ENTRY = 3;
1449
1450        public LogHandler(Looper looper) {
1451            super(looper);
1452        }
1453
1454        @Override
1455        public void handleMessage(Message msg) {
1456            switch (msg.what) {
1457                case MSG_OPEN_ENTRY: {
1458                    SomeArgs args = (SomeArgs) msg.obj;
1459                    Uri uri = (Uri) args.arg1;
1460                    long channelId = (long) args.arg2;
1461                    long time = (long) args.arg3;
1462                    onOpenEntry(uri, channelId, time);
1463                    args.recycle();
1464                    return;
1465                }
1466                case MSG_UPDATE_ENTRY: {
1467                    SomeArgs args = (SomeArgs) msg.obj;
1468                    Uri uri = (Uri) args.arg1;
1469                    long channelId = (long) args.arg2;
1470                    long time = (long) args.arg3;
1471                    onUpdateEntry(uri, channelId, time);
1472                    args.recycle();
1473                    return;
1474                }
1475                case MSG_CLOSE_ENTRY: {
1476                    SomeArgs args = (SomeArgs) msg.obj;
1477                    Uri uri = (Uri) args.arg1;
1478                    long time = (long) args.arg2;
1479                    onCloseEntry(uri, time);
1480                    args.recycle();
1481                    return;
1482                }
1483                default: {
1484                    Slog.w(TAG, "Unhandled message code: " + msg.what);
1485                    return;
1486                }
1487            }
1488        }
1489
1490        private void onOpenEntry(Uri uri, long channelId, long watchStarttime) {
1491            String[] projection = {
1492                    TvContract.Programs.COLUMN_TITLE,
1493                    TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS,
1494                    TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS,
1495                    TvContract.Programs.COLUMN_SHORT_DESCRIPTION
1496            };
1497            String selection = TvContract.Programs.COLUMN_CHANNEL_ID + "=? AND "
1498                    + TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS + "<=? AND "
1499                    + TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS + ">?";
1500            String[] selectionArgs = {
1501                    String.valueOf(channelId),
1502                    String.valueOf(watchStarttime),
1503                    String.valueOf(watchStarttime)
1504            };
1505            String sortOrder = TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS + " ASC";
1506            Cursor cursor = null;
1507            try {
1508                cursor = mContentResolver.query(TvContract.Programs.CONTENT_URI, projection,
1509                        selection, selectionArgs, sortOrder);
1510                if (cursor != null && cursor.moveToNext()) {
1511                    ContentValues values = new ContentValues();
1512                    values.put(TvContract.WatchedPrograms.COLUMN_TITLE, cursor.getString(0));
1513                    values.put(TvContract.WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS,
1514                            cursor.getLong(1));
1515                    long endTime = cursor.getLong(2);
1516                    values.put(TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS, endTime);
1517                    values.put(TvContract.WatchedPrograms.COLUMN_DESCRIPTION, cursor.getString(3));
1518                    mContentResolver.update(uri, values, null, null);
1519
1520                    // Schedule an update when the current program ends.
1521                    SomeArgs args = SomeArgs.obtain();
1522                    args.arg1 = uri;
1523                    args.arg2 = channelId;
1524                    args.arg3 = endTime;
1525                    Message msg = obtainMessage(LogHandler.MSG_UPDATE_ENTRY, args);
1526                    sendMessageDelayed(msg, endTime - System.currentTimeMillis());
1527                }
1528            } finally {
1529                if (cursor != null) {
1530                    cursor.close();
1531                }
1532            }
1533        }
1534
1535        private void onUpdateEntry(Uri uri, long channelId, long time) {
1536            String[] projection = {
1537                    TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
1538                    TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS,
1539                    TvContract.WatchedPrograms.COLUMN_TITLE,
1540                    TvContract.WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS,
1541                    TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS,
1542                    TvContract.WatchedPrograms.COLUMN_DESCRIPTION
1543            };
1544            Cursor cursor = null;
1545            try {
1546                cursor = mContentResolver.query(uri, projection, null, null, null);
1547                if (cursor != null && cursor.moveToNext()) {
1548                    long watchStartTime = cursor.getLong(0);
1549                    long watchEndTime = cursor.getLong(1);
1550                    String title = cursor.getString(2);
1551                    long startTime = cursor.getLong(3);
1552                    long endTime = cursor.getLong(4);
1553                    String description = cursor.getString(5);
1554
1555                    // Do nothing if the current log entry is already closed.
1556                    if (watchEndTime > 0) {
1557                        return;
1558                    }
1559
1560                    // The current program has just ended. Create a (complete) log entry off the
1561                    // current entry.
1562                    ContentValues values = new ContentValues();
1563                    values.put(TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
1564                            watchStartTime);
1565                    values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, time);
1566                    values.put(TvContract.WatchedPrograms.COLUMN_CHANNEL_ID, channelId);
1567                    values.put(TvContract.WatchedPrograms.COLUMN_TITLE, title);
1568                    values.put(TvContract.WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS, startTime);
1569                    values.put(TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS, endTime);
1570                    values.put(TvContract.WatchedPrograms.COLUMN_DESCRIPTION, description);
1571                    mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
1572                }
1573            } finally {
1574                if (cursor != null) {
1575                    cursor.close();
1576                }
1577            }
1578            // Re-open the current log entry with the next program information.
1579            onOpenEntry(uri, channelId, time);
1580        }
1581
1582        private void onCloseEntry(Uri uri, long watchEndTime) {
1583            ContentValues values = new ContentValues();
1584            values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, watchEndTime);
1585            mContentResolver.update(uri, values, null, null);
1586        }
1587    }
1588
1589    final class Client {
1590        public void setState(String inputId, int state) {
1591            synchronized (mLock) {
1592                setStateLocked(inputId, state, mCurrentUserId);
1593            }
1594        }
1595    }
1596}
1597