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