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