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