TvInputManagerService.java revision 70aa0c6db3cf89adafe3e1946e3713c18b0de8f0
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 SessionState getSessionStateLocked(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        return sessionState;
240    }
241
242    private ITvInputSession getSessionLocked(IBinder sessionToken, int callingUid, int userId) {
243        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
244        ITvInputSession session = sessionState.mSession;
245        if (session == null) {
246            throw new IllegalStateException("Session not yet created for token " + sessionToken);
247        }
248        return session;
249    }
250
251    private int resolveCallingUserId(int callingPid, int callingUid, int requestedUserId,
252            String methodName) {
253        return ActivityManager.handleIncomingUser(callingPid, callingUid, requestedUserId, false,
254                false, methodName, null);
255    }
256
257    private void updateServiceConnectionLocked(String inputId, int userId) {
258        UserState userState = getUserStateLocked(userId);
259        ServiceState serviceState = userState.serviceStateMap.get(inputId);
260        if (serviceState == null) {
261            return;
262        }
263        if (serviceState.mReconnecting) {
264            if (!serviceState.mSessionTokens.isEmpty()) {
265                // wait until all the sessions are removed.
266                return;
267            }
268            serviceState.mReconnecting = false;
269        }
270        boolean isStateEmpty = serviceState.mClients.isEmpty()
271                && serviceState.mSessionTokens.isEmpty();
272        if (serviceState.mService == null && !isStateEmpty && userId == mCurrentUserId) {
273            // This means that the service is not yet connected but its state indicates that we
274            // have pending requests. Then, connect the service.
275            if (serviceState.mBound) {
276                // We have already bound to the service so we don't try to bind again until after we
277                // unbind later on.
278                return;
279            }
280            if (DEBUG) {
281                Slog.d(TAG, "bindServiceAsUser(inputId=" + inputId + ", userId=" + userId
282                        + ")");
283            }
284
285            Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(
286                    userState.inputMap.get(inputId).getComponent());
287            mContext.bindServiceAsUser(i, serviceState.mConnection, Context.BIND_AUTO_CREATE,
288                    new UserHandle(userId));
289            serviceState.mBound = true;
290        } else if (serviceState.mService != null && isStateEmpty) {
291            // This means that the service is already connected but its state indicates that we have
292            // nothing to do with it. Then, disconnect the service.
293            if (DEBUG) {
294                Slog.d(TAG, "unbindService(inputId=" + inputId + ")");
295            }
296            mContext.unbindService(serviceState.mConnection);
297            userState.serviceStateMap.remove(inputId);
298        }
299    }
300
301    private void createSessionInternalLocked(ITvInputService service, final IBinder sessionToken,
302            final int userId) {
303        final SessionState sessionState =
304                getUserStateLocked(userId).sessionStateMap.get(sessionToken);
305        if (DEBUG) {
306            Slog.d(TAG, "createSessionInternalLocked(inputId=" + sessionState.mInputId + ")");
307        }
308
309        final InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString());
310
311        // Set up a callback to send the session token.
312        ITvInputSessionCallback callback = new ITvInputSessionCallback.Stub() {
313            @Override
314            public void onSessionCreated(ITvInputSession session) {
315                if (DEBUG) {
316                    Slog.d(TAG, "onSessionCreated(inputId=" + sessionState.mInputId + ")");
317                }
318                synchronized (mLock) {
319                    sessionState.mSession = session;
320                    if (session == null) {
321                        removeSessionStateLocked(sessionToken, userId);
322                        sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mInputId,
323                                null, null, sessionState.mSeq, userId);
324                    } else {
325                        try {
326                            session.asBinder().linkToDeath(sessionState, 0);
327                        } catch (RemoteException e) {
328                            Slog.e(TAG, "Session is already died.");
329                        }
330                        sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mInputId,
331                                sessionToken, channels[0], sessionState.mSeq, userId);
332                    }
333                    channels[0].dispose();
334                }
335            }
336        };
337
338        // Create a session. When failed, send a null token immediately.
339        try {
340            service.createSession(channels[1], callback);
341        } catch (RemoteException e) {
342            Slog.e(TAG, "error in createSession", e);
343            removeSessionStateLocked(sessionToken, userId);
344            sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mInputId, null, null,
345                    sessionState.mSeq, userId);
346        }
347        channels[1].dispose();
348    }
349
350    private void sendSessionTokenToClientLocked(ITvInputClient client, String inputId,
351            IBinder sessionToken, InputChannel channel, int seq, int userId) {
352        try {
353            client.onSessionCreated(inputId, sessionToken, channel, seq);
354        } catch (RemoteException exception) {
355            Slog.e(TAG, "error in onSessionCreated", exception);
356        }
357    }
358
359    private void releaseSessionLocked(IBinder sessionToken, int callingUid, int userId) {
360        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
361        if (sessionState.mSession != null) {
362            try {
363                sessionState.mSession.release();
364            } catch (RemoteException e) {
365                Slog.w(TAG, "session is already disapeared", e);
366            }
367            sessionState.mSession = null;
368        }
369        removeSessionStateLocked(sessionToken, userId);
370    }
371
372    private void removeSessionStateLocked(IBinder sessionToken, int userId) {
373        // Remove the session state from the global session state map of the current user.
374        UserState userState = getUserStateLocked(userId);
375        SessionState sessionState = userState.sessionStateMap.remove(sessionToken);
376
377        // Close the open log entry, if any.
378        if (sessionState.mLogUri != null) {
379            SomeArgs args = SomeArgs.obtain();
380            args.arg1 = sessionState.mLogUri;
381            args.arg2 = System.currentTimeMillis();
382            mLogHandler.obtainMessage(LogHandler.MSG_CLOSE_ENTRY, args).sendToTarget();
383        }
384
385        // Also remove the session token from the session token list of the current service.
386        ServiceState serviceState = userState.serviceStateMap.get(sessionState.mInputId);
387        if (serviceState != null) {
388            serviceState.mSessionTokens.remove(sessionToken);
389        }
390        updateServiceConnectionLocked(sessionState.mInputId, userId);
391    }
392
393    private void broadcastServiceAvailabilityChangedLocked(ServiceState serviceState) {
394        for (IBinder iBinder : serviceState.mClients) {
395            ITvInputClient client = ITvInputClient.Stub.asInterface(iBinder);
396            try {
397                client.onAvailabilityChanged(
398                        serviceState.mTvInputInfo.getId(), serviceState.mAvailable);
399            } catch (RemoteException e) {
400                Slog.e(TAG, "error in onAvailabilityChanged", e);
401            }
402        }
403    }
404
405    private final class BinderService extends ITvInputManager.Stub {
406        @Override
407        public List<TvInputInfo> getTvInputList(int userId) {
408            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
409                    Binder.getCallingUid(), userId, "getTvInputList");
410            final long identity = Binder.clearCallingIdentity();
411            try {
412                synchronized (mLock) {
413                    UserState userState = getUserStateLocked(resolvedUserId);
414                    return new ArrayList<TvInputInfo>(userState.inputMap.values());
415                }
416            } finally {
417                Binder.restoreCallingIdentity(identity);
418            }
419        }
420
421        @Override
422        public boolean getAvailability(final ITvInputClient client, final String inputId,
423                int userId) {
424            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
425                    Binder.getCallingUid(), userId, "getAvailability");
426            final long identity = Binder.clearCallingIdentity();
427            try {
428                synchronized (mLock) {
429                    UserState userState = getUserStateLocked(resolvedUserId);
430                    ServiceState serviceState = userState.serviceStateMap.get(inputId);
431                    if (serviceState != null) {
432                        // We already know the status of this input service. Return the cached
433                        // status.
434                        return serviceState.mAvailable;
435                    }
436                }
437            } finally {
438                Binder.restoreCallingIdentity(identity);
439            }
440            return false;
441        }
442
443        @Override
444        public void registerCallback(final ITvInputClient client, final String inputId,
445                int userId) {
446            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
447                    Binder.getCallingUid(), userId, "registerCallback");
448            final long identity = Binder.clearCallingIdentity();
449            try {
450                synchronized (mLock) {
451                    // Create a new service callback and add it to the callback map of the current
452                    // service.
453                    UserState userState = getUserStateLocked(resolvedUserId);
454                    ServiceState serviceState = userState.serviceStateMap.get(inputId);
455                    if (serviceState == null) {
456                        serviceState = new ServiceState(
457                                userState.inputMap.get(inputId), resolvedUserId);
458                        userState.serviceStateMap.put(inputId, serviceState);
459                    }
460                    IBinder iBinder = client.asBinder();
461                    if (!serviceState.mClients.contains(iBinder)) {
462                        serviceState.mClients.add(iBinder);
463                    }
464                    if (serviceState.mService != null) {
465                        if (serviceState.mCallback != null) {
466                            // We already handled.
467                            return;
468                        }
469                        serviceState.mCallback = new ServiceCallback(resolvedUserId);
470                        try {
471                            serviceState.mService.registerCallback(serviceState.mCallback);
472                        } catch (RemoteException e) {
473                            Slog.e(TAG, "error in registerCallback", e);
474                        }
475                    } else {
476                        updateServiceConnectionLocked(inputId, resolvedUserId);
477                    }
478                }
479            } finally {
480                Binder.restoreCallingIdentity(identity);
481            }
482        }
483
484        @Override
485        public void unregisterCallback(ITvInputClient client, String inputId, int userId) {
486            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
487                    Binder.getCallingUid(), userId, "unregisterCallback");
488            final long identity = Binder.clearCallingIdentity();
489            try {
490                synchronized (mLock) {
491                    UserState userState = getUserStateLocked(resolvedUserId);
492                    ServiceState serviceState = userState.serviceStateMap.get(inputId);
493                    if (serviceState == null) {
494                        return;
495                    }
496
497                    // Remove this client from the client list and unregister the callback.
498                    serviceState.mClients.remove(client.asBinder());
499                    if (!serviceState.mClients.isEmpty()) {
500                        // We have other clients who want to keep the callback. Do this later.
501                        return;
502                    }
503                    if (serviceState.mService == null || serviceState.mCallback == null) {
504                        return;
505                    }
506                    try {
507                        serviceState.mService.unregisterCallback(serviceState.mCallback);
508                    } catch (RemoteException e) {
509                        Slog.e(TAG, "error in unregisterCallback", e);
510                    } finally {
511                        serviceState.mCallback = null;
512                        updateServiceConnectionLocked(inputId, resolvedUserId);
513                    }
514                }
515            } finally {
516                Binder.restoreCallingIdentity(identity);
517            }
518        }
519
520        @Override
521        public void createSession(final ITvInputClient client, final String inputId,
522                int seq, int userId) {
523            final int callingUid = Binder.getCallingUid();
524            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
525                    userId, "createSession");
526            final long identity = Binder.clearCallingIdentity();
527            try {
528                synchronized (mLock) {
529                    UserState userState = getUserStateLocked(resolvedUserId);
530                    ServiceState serviceState = userState.serviceStateMap.get(inputId);
531                    if (serviceState == null) {
532                        serviceState = new ServiceState(
533                                userState.inputMap.get(inputId), resolvedUserId);
534                        userState.serviceStateMap.put(inputId, serviceState);
535                    }
536                    // Send a null token immediately while reconnecting.
537                    if (serviceState.mReconnecting == true) {
538                        sendSessionTokenToClientLocked(client, inputId, null, null, seq, userId);
539                        return;
540                    }
541
542                    // Create a new session token and a session state.
543                    IBinder sessionToken = new Binder();
544                    SessionState sessionState = new SessionState(
545                            sessionToken, inputId, client, seq, callingUid, resolvedUserId);
546
547                    // Add them to the global session state map of the current user.
548                    userState.sessionStateMap.put(sessionToken, sessionState);
549
550                    // Also, add them to the session state map of the current service.
551                    serviceState.mSessionTokens.add(sessionToken);
552
553                    if (serviceState.mService != null) {
554                        createSessionInternalLocked(serviceState.mService, sessionToken,
555                                resolvedUserId);
556                    } else {
557                        updateServiceConnectionLocked(inputId, resolvedUserId);
558                    }
559                }
560            } finally {
561                Binder.restoreCallingIdentity(identity);
562            }
563        }
564
565        @Override
566        public void releaseSession(IBinder sessionToken, int userId) {
567            final int callingUid = Binder.getCallingUid();
568            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
569                    userId, "releaseSession");
570            final long identity = Binder.clearCallingIdentity();
571            try {
572                synchronized (mLock) {
573                    releaseSessionLocked(sessionToken, callingUid, resolvedUserId);
574                }
575            } finally {
576                Binder.restoreCallingIdentity(identity);
577            }
578        }
579
580        @Override
581        public void setSurface(IBinder sessionToken, Surface surface, int userId) {
582            final int callingUid = Binder.getCallingUid();
583            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
584                    userId, "setSurface");
585            final long identity = Binder.clearCallingIdentity();
586            try {
587                synchronized (mLock) {
588                    try {
589                        getSessionLocked(sessionToken, callingUid, resolvedUserId).setSurface(
590                                surface);
591                    } catch (RemoteException e) {
592                        Slog.e(TAG, "error in setSurface", e);
593                    }
594                }
595            } finally {
596                if (surface != null) {
597                    // surface is not used in TvInputManagerService.
598                    surface.release();
599                }
600                Binder.restoreCallingIdentity(identity);
601            }
602        }
603
604        @Override
605        public void setVolume(IBinder sessionToken, float volume, int userId) {
606            final int callingUid = Binder.getCallingUid();
607            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
608                    userId, "setVolume");
609            final long identity = Binder.clearCallingIdentity();
610            try {
611                synchronized (mLock) {
612                    try {
613                        getSessionLocked(sessionToken, callingUid, resolvedUserId).setVolume(
614                                volume);
615                    } catch (RemoteException e) {
616                        Slog.e(TAG, "error in setVolume", e);
617                    }
618                }
619            } finally {
620                Binder.restoreCallingIdentity(identity);
621            }
622        }
623
624        @Override
625        public void tune(IBinder sessionToken, final Uri channelUri, int userId) {
626            final int callingUid = Binder.getCallingUid();
627            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
628                    userId, "tune");
629            final long identity = Binder.clearCallingIdentity();
630            try {
631                synchronized (mLock) {
632                    try {
633                        getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(channelUri);
634
635                        long currentTime = System.currentTimeMillis();
636                        long channelId = ContentUris.parseId(channelUri);
637
638                        // Close the open log entry first, if any.
639                        UserState userState = getUserStateLocked(resolvedUserId);
640                        SessionState sessionState = userState.sessionStateMap.get(sessionToken);
641                        if (sessionState.mLogUri != null) {
642                            SomeArgs args = SomeArgs.obtain();
643                            args.arg1 = sessionState.mLogUri;
644                            args.arg2 = currentTime;
645                            mLogHandler.obtainMessage(LogHandler.MSG_CLOSE_ENTRY, args)
646                                    .sendToTarget();
647                        }
648
649                        // Create a log entry and fill it later.
650                        ContentValues values = new ContentValues();
651                        values.put(TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
652                                currentTime);
653                        values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, 0);
654                        values.put(TvContract.WatchedPrograms.COLUMN_CHANNEL_ID, channelId);
655
656                        sessionState.mLogUri = mContentResolver.insert(
657                                TvContract.WatchedPrograms.CONTENT_URI, values);
658                        SomeArgs args = SomeArgs.obtain();
659                        args.arg1 = sessionState.mLogUri;
660                        args.arg2 = ContentUris.parseId(channelUri);
661                        args.arg3 = currentTime;
662                        mLogHandler.obtainMessage(LogHandler.MSG_OPEN_ENTRY, args).sendToTarget();
663                    } catch (RemoteException e) {
664                        Slog.e(TAG, "error in tune", e);
665                        return;
666                    }
667                }
668            } finally {
669                Binder.restoreCallingIdentity(identity);
670            }
671        }
672
673        @Override
674        public void createOverlayView(IBinder sessionToken, IBinder windowToken, Rect frame,
675                int userId) {
676            final int callingUid = Binder.getCallingUid();
677            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
678                    userId, "createOverlayView");
679            final long identity = Binder.clearCallingIdentity();
680            try {
681                synchronized (mLock) {
682                    try {
683                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
684                                .createOverlayView(windowToken, frame);
685                    } catch (RemoteException e) {
686                        Slog.e(TAG, "error in createOverlayView", e);
687                    }
688                }
689            } finally {
690                Binder.restoreCallingIdentity(identity);
691            }
692        }
693
694        @Override
695        public void relayoutOverlayView(IBinder sessionToken, Rect frame, int userId) {
696            final int callingUid = Binder.getCallingUid();
697            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
698                    userId, "relayoutOverlayView");
699            final long identity = Binder.clearCallingIdentity();
700            try {
701                synchronized (mLock) {
702                    try {
703                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
704                                .relayoutOverlayView(frame);
705                    } catch (RemoteException e) {
706                        Slog.e(TAG, "error in relayoutOverlayView", e);
707                    }
708                }
709            } finally {
710                Binder.restoreCallingIdentity(identity);
711            }
712        }
713
714        @Override
715        public void removeOverlayView(IBinder sessionToken, int userId) {
716            final int callingUid = Binder.getCallingUid();
717            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
718                    userId, "removeOverlayView");
719            final long identity = Binder.clearCallingIdentity();
720            try {
721                synchronized (mLock) {
722                    try {
723                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
724                                .removeOverlayView();
725                    } catch (RemoteException e) {
726                        Slog.e(TAG, "error in removeOverlayView", e);
727                    }
728                }
729            } finally {
730                Binder.restoreCallingIdentity(identity);
731            }
732        }
733    }
734
735    private static final class UserState {
736        // A mapping from the TV input id to its TvInputInfo.
737        private final Map<String, TvInputInfo> inputMap = new HashMap<String,TvInputInfo>();
738
739        // A mapping from the name of a TV input service to its state.
740        private final Map<String, ServiceState> serviceStateMap =
741                new HashMap<String, ServiceState>();
742
743        // A mapping from the token of a TV input session to its state.
744        private final Map<IBinder, SessionState> sessionStateMap =
745                new HashMap<IBinder, SessionState>();
746    }
747
748    private final class ServiceState {
749        // TODO: need to implement DeathRecipient for clients.
750        private final List<IBinder> mClients = new ArrayList<IBinder>();
751        private final List<IBinder> mSessionTokens = new ArrayList<IBinder>();
752        private final ServiceConnection mConnection;
753        private final TvInputInfo mTvInputInfo;
754
755        private ITvInputService mService;
756        private ServiceCallback mCallback;
757        private boolean mBound;
758        private boolean mAvailable;
759        private boolean mReconnecting;
760
761        private ServiceState(TvInputInfo inputInfo, int userId) {
762            mTvInputInfo = inputInfo;
763            mConnection = new InputServiceConnection(inputInfo, userId);
764        }
765    }
766
767    private final class SessionState implements IBinder.DeathRecipient {
768        private final String mInputId;
769        private final ITvInputClient mClient;
770        private final int mSeq;
771        private final int mCallingUid;
772        private final int mUserId;
773        private final IBinder mToken;
774        private ITvInputSession mSession;
775        private Uri mLogUri;
776
777        private SessionState(IBinder token, String inputId, ITvInputClient client, int seq,
778                int callingUid, int userId) {
779            mToken = token;
780            mInputId = inputId;
781            mClient = client;
782            mSeq = seq;
783            mCallingUid = callingUid;
784            mUserId = userId;
785        }
786
787        @Override
788        public void binderDied() {
789            synchronized (mLock) {
790                mSession = null;
791                if (mClient != null) {
792                    try {
793                        mClient.onSessionReleased(mSeq);
794                    } catch(RemoteException e) {
795                        Slog.e(TAG, "error in onSessionReleased", e);
796                    }
797                }
798                removeSessionStateLocked(mToken, mUserId);
799            }
800        }
801    }
802
803    private final class InputServiceConnection implements ServiceConnection {
804        private final TvInputInfo mTvInputInfo;
805        private final int mUserId;
806
807        private InputServiceConnection(TvInputInfo inputInfo, int userId) {
808            mUserId = userId;
809            mTvInputInfo = inputInfo;
810        }
811
812        @Override
813        public void onServiceConnected(ComponentName name, IBinder service) {
814            if (DEBUG) {
815                Slog.d(TAG, "onServiceConnected(inputId=" + mTvInputInfo.getId() + ")");
816            }
817            synchronized (mLock) {
818                ServiceState serviceState = getServiceStateLocked(mTvInputInfo.getId(), mUserId);
819                serviceState.mService = ITvInputService.Stub.asInterface(service);
820
821                // Register a callback, if we need to.
822                if (!serviceState.mClients.isEmpty() && serviceState.mCallback == null) {
823                    serviceState.mCallback = new ServiceCallback(mUserId);
824                    try {
825                        serviceState.mService.registerCallback(serviceState.mCallback);
826                    } catch (RemoteException e) {
827                        Slog.e(TAG, "error in registerCallback", e);
828                    }
829                }
830
831                // And create sessions, if any.
832                for (IBinder sessionToken : serviceState.mSessionTokens) {
833                    createSessionInternalLocked(serviceState.mService, sessionToken, mUserId);
834                }
835            }
836        }
837
838        @Override
839        public void onServiceDisconnected(ComponentName name) {
840            if (DEBUG) {
841                Slog.d(TAG, "onServiceDisconnected(inputId=" + mTvInputInfo.getId() + ")");
842            }
843            if (!mTvInputInfo.getComponent().equals(name)) {
844                throw new IllegalArgumentException("Mismatched ComponentName: "
845                        + mTvInputInfo.getComponent() + " (expected), " + name + " (actual).");
846            }
847            synchronized (mLock) {
848                UserState userState = getUserStateLocked(mUserId);
849                ServiceState serviceState = userState.serviceStateMap.get(mTvInputInfo.getId());
850                if (serviceState != null) {
851                    serviceState.mReconnecting = true;
852                    serviceState.mBound = false;
853                    serviceState.mService = null;
854                    serviceState.mCallback = null;
855
856                    // Send null tokens for not finishing create session events.
857                    for (IBinder sessionToken : serviceState.mSessionTokens) {
858                        SessionState sessionState = userState.sessionStateMap.get(sessionToken);
859                        if (sessionState.mSession == null) {
860                            removeSessionStateLocked(sessionToken, sessionState.mUserId);
861                            sendSessionTokenToClientLocked(sessionState.mClient,
862                                    sessionState.mInputId, null, null, sessionState.mSeq,
863                                    sessionState.mUserId);
864                        }
865                    }
866
867                    if (serviceState.mAvailable) {
868                        serviceState.mAvailable = false;
869                        broadcastServiceAvailabilityChangedLocked(serviceState);
870                    }
871                    updateServiceConnectionLocked(mTvInputInfo.getId(), mUserId);
872                }
873            }
874        }
875    }
876
877    private final class ServiceCallback extends ITvInputServiceCallback.Stub {
878        private final int mUserId;
879
880        ServiceCallback(int userId) {
881            mUserId = userId;
882        }
883
884        @Override
885        public void onAvailabilityChanged(String inputId, boolean isAvailable) {
886            if (DEBUG) {
887                Slog.d(TAG, "onAvailabilityChanged(inputId=" + inputId + ", isAvailable="
888                        + isAvailable + ")");
889            }
890            synchronized (mLock) {
891                ServiceState serviceState = getServiceStateLocked(inputId, mUserId);
892                if (serviceState.mAvailable != isAvailable) {
893                    serviceState.mAvailable = isAvailable;
894                    broadcastServiceAvailabilityChangedLocked(serviceState);
895                }
896            }
897        }
898    }
899
900    private final class LogHandler extends Handler {
901        private static final int MSG_OPEN_ENTRY = 1;
902        private static final int MSG_UPDATE_ENTRY = 2;
903        private static final int MSG_CLOSE_ENTRY = 3;
904
905        public LogHandler(Looper looper) {
906            super(looper);
907        }
908
909        @Override
910        public void handleMessage(Message msg) {
911            switch (msg.what) {
912                case MSG_OPEN_ENTRY: {
913                    SomeArgs args = (SomeArgs) msg.obj;
914                    Uri uri = (Uri) args.arg1;
915                    long channelId = (long) args.arg2;
916                    long time = (long) args.arg3;
917                    onOpenEntry(uri, channelId, time);
918                    args.recycle();
919                    return;
920                }
921                case MSG_UPDATE_ENTRY: {
922                    SomeArgs args = (SomeArgs) msg.obj;
923                    Uri uri = (Uri) args.arg1;
924                    long channelId = (long) args.arg2;
925                    long time = (long) args.arg3;
926                    onUpdateEntry(uri, channelId, time);
927                    args.recycle();
928                    return;
929                }
930                case MSG_CLOSE_ENTRY: {
931                    SomeArgs args = (SomeArgs) msg.obj;
932                    Uri uri = (Uri) args.arg1;
933                    long time = (long) args.arg2;
934                    onCloseEntry(uri, time);
935                    args.recycle();
936                    return;
937                }
938                default: {
939                    Slog.w(TAG, "Unhandled message code: " + msg.what);
940                    return;
941                }
942            }
943        }
944
945        private void onOpenEntry(Uri uri, long channelId, long watchStarttime) {
946            String[] projection = {
947                    TvContract.Programs.COLUMN_TITLE,
948                    TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS,
949                    TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS,
950                    TvContract.Programs.COLUMN_DESCRIPTION
951            };
952            String selection = TvContract.Programs.COLUMN_CHANNEL_ID + "=? AND "
953                    + TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS + "<=? AND "
954                    + TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS + ">?";
955            String[] selectionArgs = {
956                    String.valueOf(channelId),
957                    String.valueOf(watchStarttime),
958                    String.valueOf(watchStarttime)
959            };
960            String sortOrder = TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS + " ASC";
961            Cursor cursor = null;
962            try {
963                cursor = mContentResolver.query(TvContract.Programs.CONTENT_URI, projection,
964                        selection, selectionArgs, sortOrder);
965                if (cursor != null && cursor.moveToNext()) {
966                    ContentValues values = new ContentValues();
967                    values.put(TvContract.WatchedPrograms.COLUMN_TITLE, cursor.getString(0));
968                    values.put(TvContract.WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS,
969                            cursor.getLong(1));
970                    long endTime = cursor.getLong(2);
971                    values.put(TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS, endTime);
972                    values.put(TvContract.WatchedPrograms.COLUMN_DESCRIPTION, cursor.getString(3));
973                    mContentResolver.update(uri, values, null, null);
974
975                    // Schedule an update when the current program ends.
976                    SomeArgs args = SomeArgs.obtain();
977                    args.arg1 = uri;
978                    args.arg2 = channelId;
979                    args.arg3 = endTime;
980                    Message msg = obtainMessage(LogHandler.MSG_UPDATE_ENTRY, args);
981                    sendMessageDelayed(msg, endTime - System.currentTimeMillis());
982                }
983            } finally {
984                if (cursor != null) {
985                    cursor.close();
986                }
987            }
988        }
989
990        private void onUpdateEntry(Uri uri, long channelId, long time) {
991            String[] projection = {
992                    TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
993                    TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS,
994                    TvContract.WatchedPrograms.COLUMN_TITLE,
995                    TvContract.WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS,
996                    TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS,
997                    TvContract.WatchedPrograms.COLUMN_DESCRIPTION
998            };
999            Cursor cursor = null;
1000            try {
1001                cursor = mContentResolver.query(uri, projection, null, null, null);
1002                if (cursor != null && cursor.moveToNext()) {
1003                    long watchStartTime = cursor.getLong(0);
1004                    long watchEndTime = cursor.getLong(1);
1005                    String title = cursor.getString(2);
1006                    long startTime = cursor.getLong(3);
1007                    long endTime = cursor.getLong(4);
1008                    String description = cursor.getString(5);
1009
1010                    // Do nothing if the current log entry is already closed.
1011                    if (watchEndTime > 0) {
1012                        return;
1013                    }
1014
1015                    // The current program has just ended. Create a (complete) log entry off the
1016                    // current entry.
1017                    ContentValues values = new ContentValues();
1018                    values.put(TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
1019                            watchStartTime);
1020                    values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, time);
1021                    values.put(TvContract.WatchedPrograms.COLUMN_CHANNEL_ID, channelId);
1022                    values.put(TvContract.WatchedPrograms.COLUMN_TITLE, title);
1023                    values.put(TvContract.WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS, startTime);
1024                    values.put(TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS, endTime);
1025                    values.put(TvContract.WatchedPrograms.COLUMN_DESCRIPTION, description);
1026                    mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
1027                }
1028            } finally {
1029                if (cursor != null) {
1030                    cursor.close();
1031                }
1032            }
1033            // Re-open the current log entry with the next program information.
1034            onOpenEntry(uri, channelId, time);
1035        }
1036
1037        private void onCloseEntry(Uri uri, long watchEndTime) {
1038            ContentValues values = new ContentValues();
1039            values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, watchEndTime);
1040            mContentResolver.update(uri, values, null, null);
1041        }
1042    }
1043}
1044