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