TvInputManagerService.java revision 31dc634be3610b062fbcc4afa02607ce8f4125f5
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.Log;
53import android.util.Slog;
54import android.util.SparseArray;
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.inputList.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.inputList.add(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.session != null) {
183                    try {
184                        state.session.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.callback != null) {
195                    try {
196                        serviceState.service.unregisterCallback(serviceState.callback);
197                    } catch (RemoteException e) {
198                        Slog.e(TAG, "error in unregisterCallback", e);
199                    }
200                }
201                serviceState.clients.clear();
202                mContext.unbindService(serviceState.connection);
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(ComponentName name, int userId) {
219        UserState userState = getUserStateLocked(userId);
220        ServiceState serviceState = userState.serviceStateMap.get(name);
221        if (serviceState == null) {
222            throw new IllegalStateException("Service state not found for " + name + " (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.callingUid) {
236            throw new SecurityException("Illegal access to the session with token " + sessionToken
237                    + " from uid " + callingUid);
238        }
239        ITvInputSession session = sessionState.session;
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(ComponentName name, int userId) {
253        UserState userState = getUserStateLocked(userId);
254        ServiceState serviceState = userState.serviceStateMap.get(name);
255        if (serviceState == null) {
256            return;
257        }
258        boolean isStateEmpty = serviceState.clients.isEmpty()
259                && serviceState.sessionTokens.isEmpty();
260        if (serviceState.service == 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.bound) {
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(name=" + name.getClassName() + ", userId=" + userId
270                        + ")");
271            }
272            Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(name);
273            mContext.bindServiceAsUser(i, serviceState.connection, Context.BIND_AUTO_CREATE,
274                    new UserHandle(userId));
275            serviceState.bound = true;
276        } else if (serviceState.service != null && isStateEmpty) {
277            // This means that the service is already connected but its state indicates that we have
278            // nothing to do with it. Then, disconnect the service.
279            if (DEBUG) {
280                Slog.d(TAG, "unbindService(name=" + name.getClassName() + ")");
281            }
282            mContext.unbindService(serviceState.connection);
283            userState.serviceStateMap.remove(name);
284        }
285    }
286
287    private void createSessionInternalLocked(ITvInputService service, final IBinder sessionToken,
288            final int userId) {
289        final SessionState sessionState =
290                getUserStateLocked(userId).sessionStateMap.get(sessionToken);
291        if (DEBUG) {
292            Slog.d(TAG, "createSessionInternalLocked(name=" + sessionState.name.getClassName()
293                    + ")");
294        }
295        // Set up a callback to send the session token.
296        ITvInputSessionCallback callback = new ITvInputSessionCallback.Stub() {
297            @Override
298            public void onSessionCreated(ITvInputSession session) {
299                if (DEBUG) {
300                    Slog.d(TAG, "onSessionCreated(name=" + sessionState.name.getClassName() + ")");
301                }
302                synchronized (mLock) {
303                    sessionState.session = session;
304                    if (session == null) {
305                        removeSessionStateLocked(sessionToken, userId);
306                        sendSessionTokenToClientLocked(sessionState.client, sessionState.name, null,
307                                sessionState.seq, userId);
308                    } else {
309                        sendSessionTokenToClientLocked(sessionState.client, sessionState.name,
310                                sessionToken, sessionState.seq, userId);
311                    }
312                }
313            }
314        };
315
316        // Create a session. When failed, send a null token immediately.
317        try {
318            service.createSession(callback);
319        } catch (RemoteException e) {
320            Slog.e(TAG, "error in createSession", e);
321            removeSessionStateLocked(sessionToken, userId);
322            sendSessionTokenToClientLocked(sessionState.client, sessionState.name, null,
323                    sessionState.seq, userId);
324        }
325    }
326
327    private void sendSessionTokenToClientLocked(ITvInputClient client, ComponentName name,
328            IBinder sessionToken, int seq, int userId) {
329        try {
330            client.onSessionCreated(name, sessionToken, seq);
331        } catch (RemoteException exception) {
332            Slog.e(TAG, "error in onSessionCreated", exception);
333        }
334
335        if (sessionToken == null) {
336            // This means that the session creation failed. We might want to disconnect the service.
337            updateServiceConnectionLocked(name, userId);
338        }
339    }
340
341    private void removeSessionStateLocked(IBinder sessionToken, int userId) {
342        // Remove the session state from the global session state map of the current user.
343        UserState userState = getUserStateLocked(userId);
344        SessionState sessionState = userState.sessionStateMap.remove(sessionToken);
345
346        // Close the open log entry, if any.
347        if (sessionState.logUri != null) {
348            SomeArgs args = SomeArgs.obtain();
349            args.arg1 = sessionState.logUri;
350            args.arg2 = System.currentTimeMillis();
351            mLogHandler.obtainMessage(LogHandler.MSG_CLOSE_ENTRY, args).sendToTarget();
352        }
353
354        // Also remove the session token from the session token list of the current service.
355        ServiceState serviceState = userState.serviceStateMap.get(sessionState.name);
356        if (serviceState != null) {
357            serviceState.sessionTokens.remove(sessionToken);
358        }
359        updateServiceConnectionLocked(sessionState.name, userId);
360    }
361
362    private final class BinderService extends ITvInputManager.Stub {
363        @Override
364        public List<TvInputInfo> getTvInputList(int userId) {
365            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
366                    Binder.getCallingUid(), userId, "getTvInputList");
367            final long identity = Binder.clearCallingIdentity();
368            try {
369                synchronized (mLock) {
370                    UserState userState = getUserStateLocked(resolvedUserId);
371                    return new ArrayList<TvInputInfo>(userState.inputList);
372                }
373            } finally {
374                Binder.restoreCallingIdentity(identity);
375            }
376        }
377
378        @Override
379        public boolean getAvailability(final ITvInputClient client, final ComponentName name,
380                int userId) {
381            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
382                    Binder.getCallingUid(), userId, "getAvailability");
383            final long identity = Binder.clearCallingIdentity();
384            try {
385                synchronized (mLock) {
386                    UserState userState = getUserStateLocked(resolvedUserId);
387                    ServiceState serviceState = userState.serviceStateMap.get(name);
388                    if (serviceState != null) {
389                        // We already know the status of this input service. Return the cached
390                        // status.
391                        return serviceState.available;
392                    }
393                }
394            } finally {
395                Binder.restoreCallingIdentity(identity);
396            }
397            return false;
398        }
399
400        @Override
401        public void registerCallback(final ITvInputClient client, final ComponentName name,
402                int userId) {
403            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
404                    Binder.getCallingUid(), userId, "registerCallback");
405            final long identity = Binder.clearCallingIdentity();
406            try {
407                synchronized (mLock) {
408                    // Create a new service callback and add it to the callback map of the current
409                    // service.
410                    UserState userState = getUserStateLocked(resolvedUserId);
411                    ServiceState serviceState = userState.serviceStateMap.get(name);
412                    if (serviceState == null) {
413                        serviceState = new ServiceState(resolvedUserId);
414                        userState.serviceStateMap.put(name, serviceState);
415                    }
416                    IBinder iBinder = client.asBinder();
417                    if (!serviceState.clients.contains(iBinder)) {
418                        serviceState.clients.add(iBinder);
419                    }
420                    if (serviceState.service != null) {
421                        if (serviceState.callback != null) {
422                            // We already handled.
423                            return;
424                        }
425                        serviceState.callback = new ServiceCallback(resolvedUserId);
426                        try {
427                            serviceState.service.registerCallback(serviceState.callback);
428                        } catch (RemoteException e) {
429                            Slog.e(TAG, "error in registerCallback", e);
430                        }
431                    } else {
432                        updateServiceConnectionLocked(name, resolvedUserId);
433                    }
434                }
435            } finally {
436                Binder.restoreCallingIdentity(identity);
437            }
438        }
439
440        @Override
441        public void unregisterCallback(ITvInputClient client, ComponentName name, int userId) {
442            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
443                    Binder.getCallingUid(), userId, "unregisterCallback");
444            final long identity = Binder.clearCallingIdentity();
445            try {
446                synchronized (mLock) {
447                    UserState userState = getUserStateLocked(resolvedUserId);
448                    ServiceState serviceState = userState.serviceStateMap.get(name);
449                    if (serviceState == null) {
450                        return;
451                    }
452
453                    // Remove this client from the client list and unregister the callback.
454                    serviceState.clients.remove(client.asBinder());
455                    if (!serviceState.clients.isEmpty()) {
456                        // We have other clients who want to keep the callback. Do this later.
457                        return;
458                    }
459                    if (serviceState.service == null || serviceState.callback == null) {
460                        return;
461                    }
462                    try {
463                        serviceState.service.unregisterCallback(serviceState.callback);
464                    } catch (RemoteException e) {
465                        Slog.e(TAG, "error in unregisterCallback", e);
466                    } finally {
467                        serviceState.callback = null;
468                        updateServiceConnectionLocked(name, resolvedUserId);
469                    }
470                }
471            } finally {
472                Binder.restoreCallingIdentity(identity);
473            }
474        }
475
476        @Override
477        public void createSession(final ITvInputClient client, final ComponentName name,
478                int seq, int userId) {
479            final int callingUid = Binder.getCallingUid();
480            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
481                    userId, "createSession");
482            final long identity = Binder.clearCallingIdentity();
483            try {
484                synchronized (mLock) {
485                    // Create a new session token and a session state.
486                    IBinder sessionToken = new Binder();
487                    SessionState sessionState = new SessionState(name, client, seq, callingUid);
488                    sessionState.session = null;
489
490                    // Add them to the global session state map of the current user.
491                    UserState userState = getUserStateLocked(resolvedUserId);
492                    userState.sessionStateMap.put(sessionToken, sessionState);
493
494                    // Also, add them to the session state map of the current service.
495                    ServiceState serviceState = userState.serviceStateMap.get(name);
496                    if (serviceState == null) {
497                        serviceState = new ServiceState(resolvedUserId);
498                        userState.serviceStateMap.put(name, serviceState);
499                    }
500                    serviceState.sessionTokens.add(sessionToken);
501
502                    if (serviceState.service != null) {
503                        createSessionInternalLocked(serviceState.service, sessionToken,
504                                resolvedUserId);
505                    } else {
506                        updateServiceConnectionLocked(name, resolvedUserId);
507                    }
508                }
509            } finally {
510                Binder.restoreCallingIdentity(identity);
511            }
512        }
513
514        @Override
515        public void releaseSession(IBinder sessionToken, int userId) {
516            final int callingUid = Binder.getCallingUid();
517            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
518                    userId, "releaseSession");
519            final long identity = Binder.clearCallingIdentity();
520            try {
521                synchronized (mLock) {
522                    // Release the session.
523                    try {
524                        getSessionLocked(sessionToken, callingUid, resolvedUserId).release();
525                    } catch (RemoteException e) {
526                        Slog.e(TAG, "error in release", e);
527                    }
528
529                    removeSessionStateLocked(sessionToken, resolvedUserId);
530                }
531            } finally {
532                Binder.restoreCallingIdentity(identity);
533            }
534        }
535
536        @Override
537        public void setSurface(IBinder sessionToken, Surface surface, int userId) {
538            final int callingUid = Binder.getCallingUid();
539            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
540                    userId, "setSurface");
541            final long identity = Binder.clearCallingIdentity();
542            try {
543                synchronized (mLock) {
544                    try {
545                        getSessionLocked(sessionToken, callingUid, resolvedUserId).setSurface(
546                                surface);
547                    } catch (RemoteException e) {
548                        Slog.e(TAG, "error in setSurface", e);
549                    }
550                }
551            } finally {
552                Binder.restoreCallingIdentity(identity);
553            }
554        }
555
556        @Override
557        public void setVolume(IBinder sessionToken, float volume, int userId) {
558            final int callingUid = Binder.getCallingUid();
559            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
560                    userId, "setVolume");
561            final long identity = Binder.clearCallingIdentity();
562            try {
563                synchronized (mLock) {
564                    try {
565                        getSessionLocked(sessionToken, callingUid, resolvedUserId).setVolume(
566                                volume);
567                    } catch (RemoteException e) {
568                        Slog.e(TAG, "error in setVolume", e);
569                    }
570                }
571            } finally {
572                Binder.restoreCallingIdentity(identity);
573            }
574        }
575
576        @Override
577        public void tune(IBinder sessionToken, final Uri channelUri, int userId) {
578            final int callingUid = Binder.getCallingUid();
579            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
580                    userId, "tune");
581            final long identity = Binder.clearCallingIdentity();
582            try {
583                synchronized (mLock) {
584                    try {
585                        getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(channelUri);
586
587                        long currentTime = System.currentTimeMillis();
588                        long channelId = ContentUris.parseId(channelUri);
589
590                        // Close the open log entry first, if any.
591                        UserState userState = getUserStateLocked(resolvedUserId);
592                        SessionState sessionState = userState.sessionStateMap.get(sessionToken);
593                        if (sessionState.logUri != null) {
594                            SomeArgs args = SomeArgs.obtain();
595                            args.arg1 = sessionState.logUri;
596                            args.arg2 = currentTime;
597                            mLogHandler.obtainMessage(LogHandler.MSG_CLOSE_ENTRY, args)
598                                    .sendToTarget();
599                        }
600
601                        // Create a log entry and fill it later.
602                        ContentValues values = new ContentValues();
603                        values.put(TvContract.WatchedPrograms.WATCH_START_TIME_UTC_MILLIS,
604                                currentTime);
605                        values.put(TvContract.WatchedPrograms.WATCH_END_TIME_UTC_MILLIS, 0);
606                        values.put(TvContract.WatchedPrograms.CHANNEL_ID, channelId);
607
608                        sessionState.logUri = mContentResolver.insert(
609                                TvContract.WatchedPrograms.CONTENT_URI, values);
610                        SomeArgs args = SomeArgs.obtain();
611                        args.arg1 = sessionState.logUri;
612                        args.arg2 = ContentUris.parseId(channelUri);
613                        args.arg3 = currentTime;
614                        mLogHandler.obtainMessage(LogHandler.MSG_OPEN_ENTRY, args).sendToTarget();
615                    } catch (RemoteException e) {
616                        Slog.e(TAG, "error in tune", e);
617                        return;
618                    }
619                }
620            } finally {
621                Binder.restoreCallingIdentity(identity);
622            }
623        }
624
625        @Override
626        public void createOverlayView(IBinder sessionToken, IBinder windowToken, Rect frame,
627                int userId) {
628            final int callingUid = Binder.getCallingUid();
629            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
630                    userId, "createOverlayView");
631            final long identity = Binder.clearCallingIdentity();
632            try {
633                synchronized (mLock) {
634                    try {
635                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
636                                .createOverlayView(windowToken, frame);
637                    } catch (RemoteException e) {
638                        Slog.e(TAG, "error in createOverlayView", e);
639                    }
640                }
641            } finally {
642                Binder.restoreCallingIdentity(identity);
643            }
644        }
645
646        @Override
647        public void relayoutOverlayView(IBinder sessionToken, Rect frame, int userId) {
648            final int callingUid = Binder.getCallingUid();
649            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
650                    userId, "relayoutOverlayView");
651            final long identity = Binder.clearCallingIdentity();
652            try {
653                synchronized (mLock) {
654                    try {
655                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
656                                .relayoutOverlayView(frame);
657                    } catch (RemoteException e) {
658                        Slog.e(TAG, "error in relayoutOverlayView", e);
659                    }
660                }
661            } finally {
662                Binder.restoreCallingIdentity(identity);
663            }
664        }
665
666        @Override
667        public void removeOverlayView(IBinder sessionToken, int userId) {
668            final int callingUid = Binder.getCallingUid();
669            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
670                    userId, "removeOverlayView");
671            final long identity = Binder.clearCallingIdentity();
672            try {
673                synchronized (mLock) {
674                    try {
675                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
676                                .removeOverlayView();
677                    } catch (RemoteException e) {
678                        Slog.e(TAG, "error in removeOverlayView", e);
679                    }
680                }
681            } finally {
682                Binder.restoreCallingIdentity(identity);
683            }
684        }
685    }
686
687    private static final class UserState {
688        // A list of all known TV inputs on the system.
689        private final List<TvInputInfo> inputList = new ArrayList<TvInputInfo>();
690
691        // A mapping from the name of a TV input service to its state.
692        private final Map<ComponentName, ServiceState> serviceStateMap =
693                new HashMap<ComponentName, ServiceState>();
694
695        // A mapping from the token of a TV input session to its state.
696        private final Map<IBinder, SessionState> sessionStateMap =
697                new HashMap<IBinder, SessionState>();
698    }
699
700    private final class ServiceState {
701        private final List<IBinder> clients = new ArrayList<IBinder>();
702        private final List<IBinder> sessionTokens = new ArrayList<IBinder>();
703        private final ServiceConnection connection;
704
705        private ITvInputService service;
706        private ServiceCallback callback;
707        private boolean bound;
708        private boolean available;
709
710        private ServiceState(int userId) {
711            this.connection = new InputServiceConnection(userId);
712        }
713    }
714
715    private static final class SessionState {
716        private final ComponentName name;
717        private final ITvInputClient client;
718        private final int seq;
719        private final int callingUid;
720
721        private ITvInputSession session;
722        private Uri logUri;
723
724        private SessionState(ComponentName name, ITvInputClient client, int seq, int callingUid) {
725            this.name = name;
726            this.client = client;
727            this.seq = seq;
728            this.callingUid = callingUid;
729        }
730    }
731
732    private final class InputServiceConnection implements ServiceConnection {
733        private final int mUserId;
734
735        private InputServiceConnection(int userId) {
736            mUserId = userId;
737        }
738
739        @Override
740        public void onServiceConnected(ComponentName name, IBinder service) {
741            if (DEBUG) {
742                Slog.d(TAG, "onServiceConnected(name=" + name.getClassName() + ")");
743            }
744            synchronized (mLock) {
745                ServiceState serviceState = getServiceStateLocked(name, mUserId);
746                serviceState.service = ITvInputService.Stub.asInterface(service);
747
748                // Register a callback, if we need to.
749                if (!serviceState.clients.isEmpty() && serviceState.callback == null) {
750                    serviceState.callback = new ServiceCallback(mUserId);
751                    try {
752                        serviceState.service.registerCallback(serviceState.callback);
753                    } catch (RemoteException e) {
754                        Slog.e(TAG, "error in registerCallback", e);
755                    }
756                }
757
758                // And create sessions, if any.
759                for (IBinder sessionToken : serviceState.sessionTokens) {
760                    createSessionInternalLocked(serviceState.service, sessionToken, mUserId);
761                }
762            }
763        }
764
765        @Override
766        public void onServiceDisconnected(ComponentName name) {
767            if (DEBUG) {
768                Slog.d(TAG, "onServiceDisconnected(name=" + name.getClassName() + ")");
769            }
770        }
771    }
772
773    private final class ServiceCallback extends ITvInputServiceCallback.Stub {
774        private final int mUserId;
775
776        ServiceCallback(int userId) {
777            mUserId = userId;
778        }
779
780        @Override
781        public void onAvailabilityChanged(ComponentName name, boolean isAvailable)
782                throws RemoteException {
783            if (DEBUG) {
784                Slog.d(TAG, "onAvailabilityChanged(name=" + name.getClassName() + ", isAvailable="
785                        + isAvailable + ")");
786            }
787            synchronized (mLock) {
788                ServiceState serviceState = getServiceStateLocked(name, mUserId);
789                serviceState.available = isAvailable;
790                for (IBinder iBinder : serviceState.clients) {
791                    ITvInputClient client = ITvInputClient.Stub.asInterface(iBinder);
792                    client.onAvailabilityChanged(name, isAvailable);
793                }
794            }
795        }
796    }
797
798    private final class LogHandler extends Handler {
799        private static final int MSG_OPEN_ENTRY = 1;
800        private static final int MSG_UPDATE_ENTRY = 2;
801        private static final int MSG_CLOSE_ENTRY = 3;
802
803        public LogHandler(Looper looper) {
804            super(looper);
805        }
806
807        @Override
808        public void handleMessage(Message msg) {
809            switch (msg.what) {
810                case MSG_OPEN_ENTRY: {
811                    SomeArgs args = (SomeArgs) msg.obj;
812                    Uri uri = (Uri) args.arg1;
813                    long channelId = (long) args.arg2;
814                    long time = (long) args.arg3;
815                    onOpenEntry(uri, channelId, time);
816                    args.recycle();
817                    return;
818                }
819                case MSG_UPDATE_ENTRY: {
820                    SomeArgs args = (SomeArgs) msg.obj;
821                    Uri uri = (Uri) args.arg1;
822                    long channelId = (long) args.arg2;
823                    long time = (long) args.arg3;
824                    onUpdateEntry(uri, channelId, time);
825                    args.recycle();
826                    return;
827                }
828                case MSG_CLOSE_ENTRY: {
829                    SomeArgs args = (SomeArgs) msg.obj;
830                    Uri uri = (Uri) args.arg1;
831                    long time = (long) args.arg2;
832                    onCloseEntry(uri, time);
833                    args.recycle();
834                    return;
835                }
836                default: {
837                    Log.w(TAG, "Unhandled message code: " + msg.what);
838                    return;
839                }
840            }
841        }
842
843        private void onOpenEntry(Uri uri, long channelId, long watchStarttime) {
844            String[] projection = {
845                    TvContract.Programs.TITLE,
846                    TvContract.Programs.START_TIME_UTC_MILLIS,
847                    TvContract.Programs.END_TIME_UTC_MILLIS,
848                    TvContract.Programs.DESCRIPTION
849            };
850            String selection = TvContract.Programs.CHANNEL_ID + "=? AND "
851                    + TvContract.Programs.START_TIME_UTC_MILLIS + "<=? AND "
852                    + TvContract.Programs.END_TIME_UTC_MILLIS + ">?";
853            String[] selectionArgs = {
854                    String.valueOf(channelId),
855                    String.valueOf(watchStarttime),
856                    String.valueOf(watchStarttime)
857            };
858            String sortOrder = TvContract.Programs.START_TIME_UTC_MILLIS + " ASC";
859            Cursor cursor = null;
860            try {
861                cursor = mContentResolver.query(TvContract.Programs.CONTENT_URI, projection,
862                        selection, selectionArgs, sortOrder);
863                if (cursor != null && cursor.moveToNext()) {
864                    ContentValues values = new ContentValues();
865                    values.put(TvContract.WatchedPrograms.TITLE, cursor.getString(0));
866                    values.put(TvContract.WatchedPrograms.START_TIME_UTC_MILLIS, cursor.getLong(1));
867                    long endTime = cursor.getLong(2);
868                    values.put(TvContract.WatchedPrograms.END_TIME_UTC_MILLIS, endTime);
869                    values.put(TvContract.WatchedPrograms.DESCRIPTION, cursor.getString(3));
870                    mContentResolver.update(uri, values, null, null);
871
872                    // Schedule an update when the current program ends.
873                    SomeArgs args = SomeArgs.obtain();
874                    args.arg1 = uri;
875                    args.arg2 = channelId;
876                    args.arg3 = endTime;
877                    Message msg = obtainMessage(LogHandler.MSG_UPDATE_ENTRY, args);
878                    sendMessageDelayed(msg, endTime - System.currentTimeMillis());
879                }
880            } finally {
881                if (cursor != null) {
882                    cursor.close();
883                }
884            }
885        }
886
887        private void onUpdateEntry(Uri uri, long channelId, long time) {
888            String[] projection = {
889                    TvContract.WatchedPrograms.WATCH_START_TIME_UTC_MILLIS,
890                    TvContract.WatchedPrograms.WATCH_END_TIME_UTC_MILLIS,
891                    TvContract.WatchedPrograms.TITLE,
892                    TvContract.WatchedPrograms.START_TIME_UTC_MILLIS,
893                    TvContract.WatchedPrograms.END_TIME_UTC_MILLIS,
894                    TvContract.WatchedPrograms.DESCRIPTION
895            };
896            Cursor cursor = null;
897            try {
898                cursor = mContentResolver.query(uri, projection, null, null, null);
899                if (cursor != null && cursor.moveToNext()) {
900                    long watchStartTime = cursor.getLong(0);
901                    long watchEndTime = cursor.getLong(1);
902                    String title = cursor.getString(2);
903                    long startTime = cursor.getLong(3);
904                    long endTime = cursor.getLong(4);
905                    String description = cursor.getString(5);
906
907                    // Do nothing if the current log entry is already closed.
908                    if (watchEndTime > 0) {
909                        return;
910                    }
911
912                    // The current program has just ended. Create a (complete) log entry off the
913                    // current entry.
914                    ContentValues values = new ContentValues();
915                    values.put(TvContract.WatchedPrograms.WATCH_START_TIME_UTC_MILLIS,
916                            watchStartTime);
917                    values.put(TvContract.WatchedPrograms.WATCH_END_TIME_UTC_MILLIS, time);
918                    values.put(TvContract.WatchedPrograms.CHANNEL_ID, channelId);
919                    values.put(TvContract.WatchedPrograms.TITLE, title);
920                    values.put(TvContract.WatchedPrograms.START_TIME_UTC_MILLIS, startTime);
921                    values.put(TvContract.WatchedPrograms.END_TIME_UTC_MILLIS, endTime);
922                    values.put(TvContract.WatchedPrograms.DESCRIPTION, description);
923                    mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
924                }
925            } finally {
926                if (cursor != null) {
927                    cursor.close();
928                }
929            }
930            // Re-open the current log entry with the next program information.
931            onOpenEntry(uri, channelId, time);
932        }
933
934        private void onCloseEntry(Uri uri, long watchEndTime) {
935            ContentValues values = new ContentValues();
936            values.put(TvContract.WatchedPrograms.WATCH_END_TIME_UTC_MILLIS, watchEndTime);
937            mContentResolver.update(uri, values, null, null);
938        }
939    }
940}
941