TvInputManagerService.java revision d6672b51c5e07ec376a61057cfbb6bb7491a76b3
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.ContentResolver;
23import android.content.ContentUris;
24import android.content.ContentValues;
25import android.content.Context;
26import android.content.Intent;
27import android.content.IntentFilter;
28import android.content.ServiceConnection;
29import android.content.pm.PackageManager;
30import android.content.pm.ResolveInfo;
31import android.content.pm.ServiceInfo;
32import android.database.Cursor;
33import android.graphics.Rect;
34import android.net.Uri;
35import android.os.Binder;
36import android.os.Handler;
37import android.os.IBinder;
38import android.os.Looper;
39import android.os.Message;
40import android.os.Process;
41import android.os.RemoteException;
42import android.os.UserHandle;
43import android.provider.TvContract;
44import android.tv.ITvInputClient;
45import android.tv.ITvInputManager;
46import android.tv.ITvInputService;
47import android.tv.ITvInputServiceCallback;
48import android.tv.ITvInputSession;
49import android.tv.ITvInputSessionCallback;
50import android.tv.TvInputInfo;
51import android.tv.TvInputService;
52import android.util.Slog;
53import android.util.SparseArray;
54import android.view.InputChannel;
55import android.view.Surface;
56
57import com.android.internal.content.PackageMonitor;
58import com.android.internal.os.SomeArgs;
59import com.android.server.IoThread;
60import com.android.server.SystemService;
61
62import java.util.ArrayList;
63import java.util.HashMap;
64import java.util.List;
65import java.util.Map;
66
67/** This class provides a system service that manages television inputs. */
68public final class TvInputManagerService extends SystemService {
69    // STOPSHIP: Turn debugging off.
70    private static final boolean DEBUG = true;
71    private static final String TAG = "TvInputManagerService";
72
73    private final Context mContext;
74
75    private final ContentResolver mContentResolver;
76
77    // A global lock.
78    private final Object mLock = new Object();
79
80    // ID of the current user.
81    private int mCurrentUserId = UserHandle.USER_OWNER;
82
83    // A map from user id to UserState.
84    private final SparseArray<UserState> mUserStates = new SparseArray<UserState>();
85
86    private final Handler mLogHandler;
87
88    public TvInputManagerService(Context context) {
89        super(context);
90
91        mContext = context;
92        mContentResolver = context.getContentResolver();
93        mLogHandler = new LogHandler(IoThread.get().getLooper());
94
95        registerBroadcastReceivers();
96
97        synchronized (mLock) {
98            mUserStates.put(mCurrentUserId, new UserState());
99            buildTvInputListLocked(mCurrentUserId);
100        }
101    }
102
103    @Override
104    public void onStart() {
105        publishBinderService(Context.TV_INPUT_SERVICE, new BinderService());
106    }
107
108    private void registerBroadcastReceivers() {
109        PackageMonitor monitor = new PackageMonitor() {
110            @Override
111            public void onSomePackagesChanged() {
112                synchronized (mLock) {
113                    buildTvInputListLocked(mCurrentUserId);
114                }
115            }
116        };
117        monitor.register(mContext, null, UserHandle.ALL, true);
118
119        IntentFilter intentFilter = new IntentFilter();
120        intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
121        intentFilter.addAction(Intent.ACTION_USER_REMOVED);
122        mContext.registerReceiverAsUser(new BroadcastReceiver() {
123            @Override
124            public void onReceive(Context context, Intent intent) {
125                String action = intent.getAction();
126                if (Intent.ACTION_USER_SWITCHED.equals(action)) {
127                    switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
128                } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
129                    removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
130                }
131            }
132        }, UserHandle.ALL, intentFilter, null, null);
133    }
134
135    private void buildTvInputListLocked(int userId) {
136        UserState userState = getUserStateLocked(userId);
137        userState.inputMap.clear();
138
139        if (DEBUG) Slog.d(TAG, "buildTvInputList");
140        PackageManager pm = mContext.getPackageManager();
141        List<ResolveInfo> services = pm.queryIntentServices(
142                new Intent(TvInputService.SERVICE_INTERFACE), PackageManager.GET_SERVICES);
143        for (ResolveInfo ri : services) {
144            ServiceInfo si = ri.serviceInfo;
145            if (!android.Manifest.permission.BIND_TV_INPUT.equals(si.permission)) {
146                Slog.w(TAG, "Skipping TV input " + si.name + ": it does not require the permission "
147                        + android.Manifest.permission.BIND_TV_INPUT);
148                continue;
149            }
150            TvInputInfo info = new TvInputInfo(ri);
151            if (DEBUG) Slog.d(TAG, "add " + info.getId());
152            userState.inputMap.put(info.getId(), info);
153        }
154    }
155
156    private void switchUser(int userId) {
157        synchronized (mLock) {
158            if (mCurrentUserId == userId) {
159                return;
160            }
161            // final int oldUserId = mCurrentUserId;
162            // TODO: Release services and sessions in the old user state, if needed.
163            mCurrentUserId = userId;
164
165            UserState userState = mUserStates.get(userId);
166            if (userState == null) {
167                userState = new UserState();
168            }
169            mUserStates.put(userId, userState);
170            buildTvInputListLocked(userId);
171        }
172    }
173
174    private void removeUser(int userId) {
175        synchronized (mLock) {
176            UserState userState = mUserStates.get(userId);
177            if (userState == null) {
178                return;
179            }
180            // Release created sessions.
181            for (SessionState state : userState.sessionStateMap.values()) {
182                if (state.mSession != null) {
183                    try {
184                        state.mSession.release();
185                    } catch (RemoteException e) {
186                        Slog.e(TAG, "error in release", e);
187                    }
188                }
189            }
190            userState.sessionStateMap.clear();
191
192            // Unregister all callbacks and unbind all services.
193            for (ServiceState serviceState : userState.serviceStateMap.values()) {
194                if (serviceState.mCallback != null) {
195                    try {
196                        serviceState.mService.unregisterCallback(serviceState.mCallback);
197                    } catch (RemoteException e) {
198                        Slog.e(TAG, "error in unregisterCallback", e);
199                    }
200                }
201                serviceState.mClients.clear();
202                mContext.unbindService(serviceState.mConnection);
203            }
204            userState.serviceStateMap.clear();
205
206            mUserStates.remove(userId);
207        }
208    }
209
210    private UserState getUserStateLocked(int userId) {
211        UserState userState = mUserStates.get(userId);
212        if (userState == null) {
213            throw new IllegalStateException("User state not found for user ID " + userId);
214        }
215        return userState;
216    }
217
218    private ServiceState getServiceStateLocked(String inputId, int userId) {
219        UserState userState = getUserStateLocked(userId);
220        ServiceState serviceState = userState.serviceStateMap.get(inputId);
221        if (serviceState == null) {
222            throw new IllegalStateException("Service state not found for " + inputId + " (userId="
223                    + userId + ")");
224        }
225        return serviceState;
226    }
227
228    private ITvInputSession getSessionLocked(IBinder sessionToken, int callingUid, int userId) {
229        UserState userState = getUserStateLocked(userId);
230        SessionState sessionState = userState.sessionStateMap.get(sessionToken);
231        if (sessionState == null) {
232            throw new IllegalArgumentException("Session state not found for token " + sessionToken);
233        }
234        // Only the application that requested this session or the system can access it.
235        if (callingUid != Process.SYSTEM_UID && callingUid != sessionState.mCallingUid) {
236            throw new SecurityException("Illegal access to the session with token " + sessionToken
237                    + " from uid " + callingUid);
238        }
239        ITvInputSession session = sessionState.mSession;
240        if (session == null) {
241            throw new IllegalStateException("Session not yet created for token " + sessionToken);
242        }
243        return session;
244    }
245
246    private int resolveCallingUserId(int callingPid, int callingUid, int requestedUserId,
247            String methodName) {
248        return ActivityManager.handleIncomingUser(callingPid, callingUid, requestedUserId, false,
249                false, methodName, null);
250    }
251
252    private void updateServiceConnectionLocked(String inputId, int userId) {
253        UserState userState = getUserStateLocked(userId);
254        ServiceState serviceState = userState.serviceStateMap.get(inputId);
255        if (serviceState == null) {
256            return;
257        }
258        boolean isStateEmpty = serviceState.mClients.isEmpty()
259                && serviceState.mSessionTokens.isEmpty();
260        if (serviceState.mService == null && !isStateEmpty && userId == mCurrentUserId) {
261            // This means that the service is not yet connected but its state indicates that we
262            // have pending requests. Then, connect the service.
263            if (serviceState.mBound) {
264                // We have already bound to the service so we don't try to bind again until after we
265                // unbind later on.
266                return;
267            }
268            if (DEBUG) {
269                Slog.d(TAG, "bindServiceAsUser(inputId=" + inputId + ", userId=" + userId
270                        + ")");
271            }
272
273            Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(
274                    userState.inputMap.get(inputId).getComponent());
275            mContext.bindServiceAsUser(i, serviceState.mConnection, Context.BIND_AUTO_CREATE,
276                    new UserHandle(userId));
277            serviceState.mBound = true;
278        } else if (serviceState.mService != null && isStateEmpty) {
279            // This means that the service is already connected but its state indicates that we have
280            // nothing to do with it. Then, disconnect the service.
281            if (DEBUG) {
282                Slog.d(TAG, "unbindService(inputId=" + inputId + ")");
283            }
284            mContext.unbindService(serviceState.mConnection);
285            userState.serviceStateMap.remove(inputId);
286        }
287    }
288
289    private void createSessionInternalLocked(ITvInputService service, final IBinder sessionToken,
290            final int userId) {
291        final SessionState sessionState =
292                getUserStateLocked(userId).sessionStateMap.get(sessionToken);
293        if (DEBUG) {
294            Slog.d(TAG, "createSessionInternalLocked(inputId=" + sessionState.mInputId + ")");
295        }
296
297        final InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString());
298
299        // Set up a callback to send the session token.
300        ITvInputSessionCallback callback = new ITvInputSessionCallback.Stub() {
301            @Override
302            public void onSessionCreated(ITvInputSession session) {
303                if (DEBUG) {
304                    Slog.d(TAG, "onSessionCreated(inputId=" + sessionState.mInputId + ")");
305                }
306                synchronized (mLock) {
307                    sessionState.mSession = session;
308                    if (session == null) {
309                        removeSessionStateLocked(sessionToken, userId);
310                        sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mInputId, null,
311                                null, sessionState.mSeq, userId);
312                    } else {
313                        sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mInputId,
314                                sessionToken, channels[0], sessionState.mSeq, userId);
315                    }
316                    channels[0].dispose();
317                }
318            }
319        };
320
321        // Create a session. When failed, send a null token immediately.
322        try {
323            service.createSession(channels[1], callback);
324        } catch (RemoteException e) {
325            Slog.e(TAG, "error in createSession", e);
326            removeSessionStateLocked(sessionToken, userId);
327            sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mInputId, null, null,
328                    sessionState.mSeq, userId);
329        }
330        channels[1].dispose();
331    }
332
333    private void sendSessionTokenToClientLocked(ITvInputClient client, String inputId,
334            IBinder sessionToken, InputChannel channel, int seq, int userId) {
335        try {
336            client.onSessionCreated(inputId, sessionToken, channel, seq);
337        } catch (RemoteException exception) {
338            Slog.e(TAG, "error in onSessionCreated", exception);
339        }
340
341        if (sessionToken == null) {
342            // This means that the session creation failed. We might want to disconnect the service.
343            updateServiceConnectionLocked(inputId, userId);
344        }
345    }
346
347    private void removeSessionStateLocked(IBinder sessionToken, int userId) {
348        // Remove the session state from the global session state map of the current user.
349        UserState userState = getUserStateLocked(userId);
350        SessionState sessionState = userState.sessionStateMap.remove(sessionToken);
351
352        // Close the open log entry, if any.
353        if (sessionState.mLogUri != null) {
354            SomeArgs args = SomeArgs.obtain();
355            args.arg1 = sessionState.mLogUri;
356            args.arg2 = System.currentTimeMillis();
357            mLogHandler.obtainMessage(LogHandler.MSG_CLOSE_ENTRY, args).sendToTarget();
358        }
359
360        // Also remove the session token from the session token list of the current service.
361        ServiceState serviceState = userState.serviceStateMap.get(sessionState.mInputId);
362        if (serviceState != null) {
363            serviceState.mSessionTokens.remove(sessionToken);
364        }
365        updateServiceConnectionLocked(sessionState.mInputId, userId);
366    }
367
368    private final class BinderService extends ITvInputManager.Stub {
369        @Override
370        public List<TvInputInfo> getTvInputList(int userId) {
371            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
372                    Binder.getCallingUid(), userId, "getTvInputList");
373            final long identity = Binder.clearCallingIdentity();
374            try {
375                synchronized (mLock) {
376                    UserState userState = getUserStateLocked(resolvedUserId);
377                    return new ArrayList<TvInputInfo>(userState.inputMap.values());
378                }
379            } finally {
380                Binder.restoreCallingIdentity(identity);
381            }
382        }
383
384        @Override
385        public boolean getAvailability(final ITvInputClient client, final String inputId,
386                int userId) {
387            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
388                    Binder.getCallingUid(), userId, "getAvailability");
389            final long identity = Binder.clearCallingIdentity();
390            try {
391                synchronized (mLock) {
392                    UserState userState = getUserStateLocked(resolvedUserId);
393                    ServiceState serviceState = userState.serviceStateMap.get(inputId);
394                    if (serviceState != null) {
395                        // We already know the status of this input service. Return the cached
396                        // status.
397                        return serviceState.mAvailable;
398                    }
399                }
400            } finally {
401                Binder.restoreCallingIdentity(identity);
402            }
403            return false;
404        }
405
406        @Override
407        public void registerCallback(final ITvInputClient client, final String inputId,
408                int userId) {
409            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
410                    Binder.getCallingUid(), userId, "registerCallback");
411            final long identity = Binder.clearCallingIdentity();
412            try {
413                synchronized (mLock) {
414                    // Create a new service callback and add it to the callback map of the current
415                    // service.
416                    UserState userState = getUserStateLocked(resolvedUserId);
417                    ServiceState serviceState = userState.serviceStateMap.get(inputId);
418                    if (serviceState == null) {
419                        serviceState = new ServiceState(
420                                userState.inputMap.get(inputId), resolvedUserId);
421                        userState.serviceStateMap.put(inputId, serviceState);
422                    }
423                    IBinder iBinder = client.asBinder();
424                    if (!serviceState.mClients.contains(iBinder)) {
425                        serviceState.mClients.add(iBinder);
426                    }
427                    if (serviceState.mService != null) {
428                        if (serviceState.mCallback != null) {
429                            // We already handled.
430                            return;
431                        }
432                        serviceState.mCallback = new ServiceCallback(resolvedUserId);
433                        try {
434                            serviceState.mService.registerCallback(serviceState.mCallback);
435                        } catch (RemoteException e) {
436                            Slog.e(TAG, "error in registerCallback", e);
437                        }
438                    } else {
439                        updateServiceConnectionLocked(inputId, resolvedUserId);
440                    }
441                }
442            } finally {
443                Binder.restoreCallingIdentity(identity);
444            }
445        }
446
447        @Override
448        public void unregisterCallback(ITvInputClient client, String inputId, int userId) {
449            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
450                    Binder.getCallingUid(), userId, "unregisterCallback");
451            final long identity = Binder.clearCallingIdentity();
452            try {
453                synchronized (mLock) {
454                    UserState userState = getUserStateLocked(resolvedUserId);
455                    ServiceState serviceState = userState.serviceStateMap.get(inputId);
456                    if (serviceState == null) {
457                        return;
458                    }
459
460                    // Remove this client from the client list and unregister the callback.
461                    serviceState.mClients.remove(client.asBinder());
462                    if (!serviceState.mClients.isEmpty()) {
463                        // We have other clients who want to keep the callback. Do this later.
464                        return;
465                    }
466                    if (serviceState.mService == null || serviceState.mCallback == null) {
467                        return;
468                    }
469                    try {
470                        serviceState.mService.unregisterCallback(serviceState.mCallback);
471                    } catch (RemoteException e) {
472                        Slog.e(TAG, "error in unregisterCallback", e);
473                    } finally {
474                        serviceState.mCallback = null;
475                        updateServiceConnectionLocked(inputId, resolvedUserId);
476                    }
477                }
478            } finally {
479                Binder.restoreCallingIdentity(identity);
480            }
481        }
482
483        @Override
484        public void createSession(final ITvInputClient client, final String inputId,
485                int seq, int userId) {
486            final int callingUid = Binder.getCallingUid();
487            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
488                    userId, "createSession");
489            final long identity = Binder.clearCallingIdentity();
490            try {
491                synchronized (mLock) {
492                    // Create a new session token and a session state.
493                    IBinder sessionToken = new Binder();
494                    SessionState sessionState = new SessionState(inputId, client, seq, callingUid);
495                    sessionState.mSession = null;
496
497                    // Add them to the global session state map of the current user.
498                    UserState userState = getUserStateLocked(resolvedUserId);
499                    userState.sessionStateMap.put(sessionToken, sessionState);
500
501                    // Also, add them to the session state map of the current service.
502                    ServiceState serviceState = userState.serviceStateMap.get(inputId);
503                    if (serviceState == null) {
504                        serviceState = new ServiceState(
505                                userState.inputMap.get(inputId), resolvedUserId);
506                        userState.serviceStateMap.put(inputId, serviceState);
507                    }
508                    serviceState.mSessionTokens.add(sessionToken);
509
510                    if (serviceState.mService != null) {
511                        createSessionInternalLocked(serviceState.mService, sessionToken,
512                                resolvedUserId);
513                    } else {
514                        updateServiceConnectionLocked(inputId, resolvedUserId);
515                    }
516                }
517            } finally {
518                Binder.restoreCallingIdentity(identity);
519            }
520        }
521
522        @Override
523        public void releaseSession(IBinder sessionToken, int userId) {
524            final int callingUid = Binder.getCallingUid();
525            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
526                    userId, "releaseSession");
527            final long identity = Binder.clearCallingIdentity();
528            try {
529                synchronized (mLock) {
530                    // Release the session.
531                    try {
532                        getSessionLocked(sessionToken, callingUid, resolvedUserId).release();
533                    } catch (RemoteException e) {
534                        Slog.e(TAG, "error in release", e);
535                    }
536
537                    removeSessionStateLocked(sessionToken, resolvedUserId);
538                }
539            } finally {
540                Binder.restoreCallingIdentity(identity);
541            }
542        }
543
544        @Override
545        public void setSurface(IBinder sessionToken, Surface surface, int userId) {
546            final int callingUid = Binder.getCallingUid();
547            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
548                    userId, "setSurface");
549            final long identity = Binder.clearCallingIdentity();
550            try {
551                synchronized (mLock) {
552                    try {
553                        getSessionLocked(sessionToken, callingUid, resolvedUserId).setSurface(
554                                surface);
555                    } catch (RemoteException e) {
556                        Slog.e(TAG, "error in setSurface", e);
557                    }
558                }
559            } finally {
560                if (surface != null) {
561                    // surface is not used in TvInputManagerService.
562                    surface.release();
563                }
564                Binder.restoreCallingIdentity(identity);
565            }
566        }
567
568        @Override
569        public void setVolume(IBinder sessionToken, float volume, int userId) {
570            final int callingUid = Binder.getCallingUid();
571            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
572                    userId, "setVolume");
573            final long identity = Binder.clearCallingIdentity();
574            try {
575                synchronized (mLock) {
576                    try {
577                        getSessionLocked(sessionToken, callingUid, resolvedUserId).setVolume(
578                                volume);
579                    } catch (RemoteException e) {
580                        Slog.e(TAG, "error in setVolume", e);
581                    }
582                }
583            } finally {
584                Binder.restoreCallingIdentity(identity);
585            }
586        }
587
588        @Override
589        public void tune(IBinder sessionToken, final Uri channelUri, int userId) {
590            final int callingUid = Binder.getCallingUid();
591            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
592                    userId, "tune");
593            final long identity = Binder.clearCallingIdentity();
594            try {
595                synchronized (mLock) {
596                    try {
597                        getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(channelUri);
598
599                        long currentTime = System.currentTimeMillis();
600                        long channelId = ContentUris.parseId(channelUri);
601
602                        // Close the open log entry first, if any.
603                        UserState userState = getUserStateLocked(resolvedUserId);
604                        SessionState sessionState = userState.sessionStateMap.get(sessionToken);
605                        if (sessionState.mLogUri != null) {
606                            SomeArgs args = SomeArgs.obtain();
607                            args.arg1 = sessionState.mLogUri;
608                            args.arg2 = currentTime;
609                            mLogHandler.obtainMessage(LogHandler.MSG_CLOSE_ENTRY, args)
610                                    .sendToTarget();
611                        }
612
613                        // Create a log entry and fill it later.
614                        ContentValues values = new ContentValues();
615                        values.put(TvContract.WatchedPrograms.WATCH_START_TIME_UTC_MILLIS,
616                                currentTime);
617                        values.put(TvContract.WatchedPrograms.WATCH_END_TIME_UTC_MILLIS, 0);
618                        values.put(TvContract.WatchedPrograms.CHANNEL_ID, channelId);
619
620                        sessionState.mLogUri = mContentResolver.insert(
621                                TvContract.WatchedPrograms.CONTENT_URI, values);
622                        SomeArgs args = SomeArgs.obtain();
623                        args.arg1 = sessionState.mLogUri;
624                        args.arg2 = ContentUris.parseId(channelUri);
625                        args.arg3 = currentTime;
626                        mLogHandler.obtainMessage(LogHandler.MSG_OPEN_ENTRY, args).sendToTarget();
627                    } catch (RemoteException e) {
628                        Slog.e(TAG, "error in tune", e);
629                        return;
630                    }
631                }
632            } finally {
633                Binder.restoreCallingIdentity(identity);
634            }
635        }
636
637        @Override
638        public void createOverlayView(IBinder sessionToken, IBinder windowToken, Rect frame,
639                int userId) {
640            final int callingUid = Binder.getCallingUid();
641            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
642                    userId, "createOverlayView");
643            final long identity = Binder.clearCallingIdentity();
644            try {
645                synchronized (mLock) {
646                    try {
647                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
648                                .createOverlayView(windowToken, frame);
649                    } catch (RemoteException e) {
650                        Slog.e(TAG, "error in createOverlayView", e);
651                    }
652                }
653            } finally {
654                Binder.restoreCallingIdentity(identity);
655            }
656        }
657
658        @Override
659        public void relayoutOverlayView(IBinder sessionToken, Rect frame, int userId) {
660            final int callingUid = Binder.getCallingUid();
661            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
662                    userId, "relayoutOverlayView");
663            final long identity = Binder.clearCallingIdentity();
664            try {
665                synchronized (mLock) {
666                    try {
667                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
668                                .relayoutOverlayView(frame);
669                    } catch (RemoteException e) {
670                        Slog.e(TAG, "error in relayoutOverlayView", e);
671                    }
672                }
673            } finally {
674                Binder.restoreCallingIdentity(identity);
675            }
676        }
677
678        @Override
679        public void removeOverlayView(IBinder sessionToken, int userId) {
680            final int callingUid = Binder.getCallingUid();
681            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
682                    userId, "removeOverlayView");
683            final long identity = Binder.clearCallingIdentity();
684            try {
685                synchronized (mLock) {
686                    try {
687                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
688                                .removeOverlayView();
689                    } catch (RemoteException e) {
690                        Slog.e(TAG, "error in removeOverlayView", e);
691                    }
692                }
693            } finally {
694                Binder.restoreCallingIdentity(identity);
695            }
696        }
697    }
698
699    private static final class UserState {
700        // A mapping from the TV input id to its TvInputInfo.
701        private final Map<String, TvInputInfo> inputMap = new HashMap<String,TvInputInfo>();
702
703        // A mapping from the name of a TV input service to its state.
704        private final Map<String, ServiceState> serviceStateMap =
705                new HashMap<String, ServiceState>();
706
707        // A mapping from the token of a TV input session to its state.
708        private final Map<IBinder, SessionState> sessionStateMap =
709                new HashMap<IBinder, SessionState>();
710    }
711
712    private final class ServiceState {
713        private final List<IBinder> mClients = new ArrayList<IBinder>();
714        private final List<IBinder> mSessionTokens = new ArrayList<IBinder>();
715        private final ServiceConnection mConnection;
716
717        private ITvInputService mService;
718        private ServiceCallback mCallback;
719        private boolean mBound;
720        private boolean mAvailable;
721
722        private ServiceState(TvInputInfo inputInfo, int userId) {
723            this.mConnection = new InputServiceConnection(inputInfo, userId);
724        }
725    }
726
727    private static final class SessionState {
728        private final String mInputId;
729        private final ITvInputClient mClient;
730        private final int mSeq;
731        private final int mCallingUid;
732
733        private ITvInputSession mSession;
734        private Uri mLogUri;
735
736        private SessionState(String inputId, ITvInputClient client, int seq, int callingUid) {
737            this.mInputId = inputId;
738            this.mClient = client;
739            this.mSeq = seq;
740            this.mCallingUid = callingUid;
741        }
742    }
743
744    private final class InputServiceConnection implements ServiceConnection {
745        private final TvInputInfo mTvInputInfo;
746        private final int mUserId;
747
748        private InputServiceConnection(TvInputInfo inputInfo, int userId) {
749            mUserId = userId;
750            mTvInputInfo = inputInfo;
751        }
752
753        @Override
754        public void onServiceConnected(ComponentName name, IBinder service) {
755            if (DEBUG) {
756                Slog.d(TAG, "onServiceConnected(inputId=" + mTvInputInfo.getId() + ")");
757            }
758            synchronized (mLock) {
759                ServiceState serviceState = getServiceStateLocked(mTvInputInfo.getId(), mUserId);
760                serviceState.mService = ITvInputService.Stub.asInterface(service);
761
762                // Register a callback, if we need to.
763                if (!serviceState.mClients.isEmpty() && serviceState.mCallback == null) {
764                    serviceState.mCallback = new ServiceCallback(mUserId);
765                    try {
766                        serviceState.mService.registerCallback(serviceState.mCallback);
767                    } catch (RemoteException e) {
768                        Slog.e(TAG, "error in registerCallback", e);
769                    }
770                }
771
772                // And create sessions, if any.
773                for (IBinder sessionToken : serviceState.mSessionTokens) {
774                    createSessionInternalLocked(serviceState.mService, sessionToken, mUserId);
775                }
776            }
777        }
778
779        @Override
780        public void onServiceDisconnected(ComponentName name) {
781            if (DEBUG) {
782                Slog.d(TAG, "onServiceDisconnected(inputId=" + mTvInputInfo.getId() + ")");
783            }
784        }
785    }
786
787    private final class ServiceCallback extends ITvInputServiceCallback.Stub {
788        private final int mUserId;
789
790        ServiceCallback(int userId) {
791            mUserId = userId;
792        }
793
794        @Override
795        public void onAvailabilityChanged(String inputId, boolean isAvailable)
796                throws RemoteException {
797            if (DEBUG) {
798                Slog.d(TAG, "onAvailabilityChanged(inputId=" + inputId + ", isAvailable="
799                        + isAvailable + ")");
800            }
801            synchronized (mLock) {
802                ServiceState serviceState = getServiceStateLocked(inputId, mUserId);
803                serviceState.mAvailable = isAvailable;
804                for (IBinder iBinder : serviceState.mClients) {
805                    ITvInputClient client = ITvInputClient.Stub.asInterface(iBinder);
806                    client.onAvailabilityChanged(inputId, isAvailable);
807                }
808            }
809        }
810    }
811
812    private final class LogHandler extends Handler {
813        private static final int MSG_OPEN_ENTRY = 1;
814        private static final int MSG_UPDATE_ENTRY = 2;
815        private static final int MSG_CLOSE_ENTRY = 3;
816
817        public LogHandler(Looper looper) {
818            super(looper);
819        }
820
821        @Override
822        public void handleMessage(Message msg) {
823            switch (msg.what) {
824                case MSG_OPEN_ENTRY: {
825                    SomeArgs args = (SomeArgs) msg.obj;
826                    Uri uri = (Uri) args.arg1;
827                    long channelId = (long) args.arg2;
828                    long time = (long) args.arg3;
829                    onOpenEntry(uri, channelId, time);
830                    args.recycle();
831                    return;
832                }
833                case MSG_UPDATE_ENTRY: {
834                    SomeArgs args = (SomeArgs) msg.obj;
835                    Uri uri = (Uri) args.arg1;
836                    long channelId = (long) args.arg2;
837                    long time = (long) args.arg3;
838                    onUpdateEntry(uri, channelId, time);
839                    args.recycle();
840                    return;
841                }
842                case MSG_CLOSE_ENTRY: {
843                    SomeArgs args = (SomeArgs) msg.obj;
844                    Uri uri = (Uri) args.arg1;
845                    long time = (long) args.arg2;
846                    onCloseEntry(uri, time);
847                    args.recycle();
848                    return;
849                }
850                default: {
851                    Slog.w(TAG, "Unhandled message code: " + msg.what);
852                    return;
853                }
854            }
855        }
856
857        private void onOpenEntry(Uri uri, long channelId, long watchStarttime) {
858            String[] projection = {
859                    TvContract.Programs.TITLE,
860                    TvContract.Programs.START_TIME_UTC_MILLIS,
861                    TvContract.Programs.END_TIME_UTC_MILLIS,
862                    TvContract.Programs.DESCRIPTION
863            };
864            String selection = TvContract.Programs.CHANNEL_ID + "=? AND "
865                    + TvContract.Programs.START_TIME_UTC_MILLIS + "<=? AND "
866                    + TvContract.Programs.END_TIME_UTC_MILLIS + ">?";
867            String[] selectionArgs = {
868                    String.valueOf(channelId),
869                    String.valueOf(watchStarttime),
870                    String.valueOf(watchStarttime)
871            };
872            String sortOrder = TvContract.Programs.START_TIME_UTC_MILLIS + " ASC";
873            Cursor cursor = null;
874            try {
875                cursor = mContentResolver.query(TvContract.Programs.CONTENT_URI, projection,
876                        selection, selectionArgs, sortOrder);
877                if (cursor != null && cursor.moveToNext()) {
878                    ContentValues values = new ContentValues();
879                    values.put(TvContract.WatchedPrograms.TITLE, cursor.getString(0));
880                    values.put(TvContract.WatchedPrograms.START_TIME_UTC_MILLIS, cursor.getLong(1));
881                    long endTime = cursor.getLong(2);
882                    values.put(TvContract.WatchedPrograms.END_TIME_UTC_MILLIS, endTime);
883                    values.put(TvContract.WatchedPrograms.DESCRIPTION, cursor.getString(3));
884                    mContentResolver.update(uri, values, null, null);
885
886                    // Schedule an update when the current program ends.
887                    SomeArgs args = SomeArgs.obtain();
888                    args.arg1 = uri;
889                    args.arg2 = channelId;
890                    args.arg3 = endTime;
891                    Message msg = obtainMessage(LogHandler.MSG_UPDATE_ENTRY, args);
892                    sendMessageDelayed(msg, endTime - System.currentTimeMillis());
893                }
894            } finally {
895                if (cursor != null) {
896                    cursor.close();
897                }
898            }
899        }
900
901        private void onUpdateEntry(Uri uri, long channelId, long time) {
902            String[] projection = {
903                    TvContract.WatchedPrograms.WATCH_START_TIME_UTC_MILLIS,
904                    TvContract.WatchedPrograms.WATCH_END_TIME_UTC_MILLIS,
905                    TvContract.WatchedPrograms.TITLE,
906                    TvContract.WatchedPrograms.START_TIME_UTC_MILLIS,
907                    TvContract.WatchedPrograms.END_TIME_UTC_MILLIS,
908                    TvContract.WatchedPrograms.DESCRIPTION
909            };
910            Cursor cursor = null;
911            try {
912                cursor = mContentResolver.query(uri, projection, null, null, null);
913                if (cursor != null && cursor.moveToNext()) {
914                    long watchStartTime = cursor.getLong(0);
915                    long watchEndTime = cursor.getLong(1);
916                    String title = cursor.getString(2);
917                    long startTime = cursor.getLong(3);
918                    long endTime = cursor.getLong(4);
919                    String description = cursor.getString(5);
920
921                    // Do nothing if the current log entry is already closed.
922                    if (watchEndTime > 0) {
923                        return;
924                    }
925
926                    // The current program has just ended. Create a (complete) log entry off the
927                    // current entry.
928                    ContentValues values = new ContentValues();
929                    values.put(TvContract.WatchedPrograms.WATCH_START_TIME_UTC_MILLIS,
930                            watchStartTime);
931                    values.put(TvContract.WatchedPrograms.WATCH_END_TIME_UTC_MILLIS, time);
932                    values.put(TvContract.WatchedPrograms.CHANNEL_ID, channelId);
933                    values.put(TvContract.WatchedPrograms.TITLE, title);
934                    values.put(TvContract.WatchedPrograms.START_TIME_UTC_MILLIS, startTime);
935                    values.put(TvContract.WatchedPrograms.END_TIME_UTC_MILLIS, endTime);
936                    values.put(TvContract.WatchedPrograms.DESCRIPTION, description);
937                    mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
938                }
939            } finally {
940                if (cursor != null) {
941                    cursor.close();
942                }
943            }
944            // Re-open the current log entry with the next program information.
945            onOpenEntry(uri, channelId, time);
946        }
947
948        private void onCloseEntry(Uri uri, long watchEndTime) {
949            ContentValues values = new ContentValues();
950            values.put(TvContract.WatchedPrograms.WATCH_END_TIME_UTC_MILLIS, watchEndTime);
951            mContentResolver.update(uri, values, null, null);
952        }
953    }
954}
955