TvInputManagerService.java revision e7bb7d6bb2257c24076f5a4b9f536f90a6637f58
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 onVideoSizeChanged(int width, int height) throws RemoteException {
354                synchronized (mLock) {
355                    if (DEBUG) {
356                        Slog.d(TAG, "onVideoSizeChanged(" + width + ", " + height + ")");
357                    }
358                    if (sessionState.mSession == null || sessionState.mClient == null) {
359                        return;
360                    }
361                    try {
362                        sessionState.mClient.onVideoSizeChanged(width, height, sessionState.mSeq);
363                    } catch (RemoteException e) {
364                        Slog.e(TAG, "error in onSessionEvent");
365                    }
366                }
367            }
368
369            @Override
370            public void onSessionEvent(String eventType, Bundle eventArgs) {
371                synchronized (mLock) {
372                    if (DEBUG) {
373                        Slog.d(TAG, "onEvent(what=" + eventType + ", data=" + eventArgs + ")");
374                    }
375                    if (sessionState.mSession == null || sessionState.mClient == null) {
376                        return;
377                    }
378                    try {
379                        sessionState.mClient.onSessionEvent(eventType, eventArgs,
380                                sessionState.mSeq);
381                    } catch (RemoteException e) {
382                        Slog.e(TAG, "error in onSessionEvent");
383                    }
384                }
385            }
386        };
387
388        // Create a session. When failed, send a null token immediately.
389        try {
390            service.createSession(channels[1], callback);
391        } catch (RemoteException e) {
392            Slog.e(TAG, "error in createSession", e);
393            removeSessionStateLocked(sessionToken, userId);
394            sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mInputId, null, null,
395                    sessionState.mSeq, userId);
396        }
397        channels[1].dispose();
398    }
399
400    private void sendSessionTokenToClientLocked(ITvInputClient client, String inputId,
401            IBinder sessionToken, InputChannel channel, int seq, int userId) {
402        try {
403            client.onSessionCreated(inputId, sessionToken, channel, seq);
404        } catch (RemoteException exception) {
405            Slog.e(TAG, "error in onSessionCreated", exception);
406        }
407    }
408
409    private void releaseSessionLocked(IBinder sessionToken, int callingUid, int userId) {
410        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
411        if (sessionState.mSession != null) {
412            try {
413                sessionState.mSession.release();
414            } catch (RemoteException e) {
415                Slog.w(TAG, "session is already disapeared", e);
416            }
417            sessionState.mSession = null;
418        }
419        removeSessionStateLocked(sessionToken, userId);
420    }
421
422    private void removeSessionStateLocked(IBinder sessionToken, int userId) {
423        // Remove the session state from the global session state map of the current user.
424        UserState userState = getUserStateLocked(userId);
425        SessionState sessionState = userState.sessionStateMap.remove(sessionToken);
426
427        // Close the open log entry, if any.
428        if (sessionState.mLogUri != null) {
429            SomeArgs args = SomeArgs.obtain();
430            args.arg1 = sessionState.mLogUri;
431            args.arg2 = System.currentTimeMillis();
432            mLogHandler.obtainMessage(LogHandler.MSG_CLOSE_ENTRY, args).sendToTarget();
433        }
434
435        // Also remove the session token from the session token list of the current service.
436        ServiceState serviceState = userState.serviceStateMap.get(sessionState.mInputId);
437        if (serviceState != null) {
438            serviceState.mSessionTokens.remove(sessionToken);
439        }
440        updateServiceConnectionLocked(sessionState.mInputId, userId);
441    }
442
443    private void broadcastServiceAvailabilityChangedLocked(ServiceState serviceState) {
444        for (IBinder iBinder : serviceState.mClients) {
445            ITvInputClient client = ITvInputClient.Stub.asInterface(iBinder);
446            try {
447                client.onAvailabilityChanged(
448                        serviceState.mTvInputInfo.getId(), serviceState.mAvailable);
449            } catch (RemoteException e) {
450                Slog.e(TAG, "error in onAvailabilityChanged", e);
451            }
452        }
453    }
454
455    private final class BinderService extends ITvInputManager.Stub {
456        @Override
457        public List<TvInputInfo> getTvInputList(int userId) {
458            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
459                    Binder.getCallingUid(), userId, "getTvInputList");
460            final long identity = Binder.clearCallingIdentity();
461            try {
462                synchronized (mLock) {
463                    UserState userState = getUserStateLocked(resolvedUserId);
464                    return new ArrayList<TvInputInfo>(userState.inputMap.values());
465                }
466            } finally {
467                Binder.restoreCallingIdentity(identity);
468            }
469        }
470
471        @Override
472        public boolean getAvailability(final ITvInputClient client, final String inputId,
473                int userId) {
474            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
475                    Binder.getCallingUid(), userId, "getAvailability");
476            final long identity = Binder.clearCallingIdentity();
477            try {
478                synchronized (mLock) {
479                    UserState userState = getUserStateLocked(resolvedUserId);
480                    ServiceState serviceState = userState.serviceStateMap.get(inputId);
481                    if (serviceState != null) {
482                        // We already know the status of this input service. Return the cached
483                        // status.
484                        return serviceState.mAvailable;
485                    }
486                }
487            } finally {
488                Binder.restoreCallingIdentity(identity);
489            }
490            return false;
491        }
492
493        @Override
494        public void registerCallback(final ITvInputClient client, final String inputId,
495                int userId) {
496            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
497                    Binder.getCallingUid(), userId, "registerCallback");
498            final long identity = Binder.clearCallingIdentity();
499            try {
500                synchronized (mLock) {
501                    // Create a new service callback and add it to the callback map of the current
502                    // service.
503                    UserState userState = getUserStateLocked(resolvedUserId);
504                    ServiceState serviceState = userState.serviceStateMap.get(inputId);
505                    if (serviceState == null) {
506                        serviceState = new ServiceState(
507                                userState.inputMap.get(inputId), resolvedUserId);
508                        userState.serviceStateMap.put(inputId, serviceState);
509                    }
510                    IBinder iBinder = client.asBinder();
511                    if (!serviceState.mClients.contains(iBinder)) {
512                        serviceState.mClients.add(iBinder);
513                    }
514                    if (serviceState.mService != null) {
515                        if (serviceState.mCallback != null) {
516                            // We already handled.
517                            return;
518                        }
519                        serviceState.mCallback = new ServiceCallback(resolvedUserId);
520                        try {
521                            serviceState.mService.registerCallback(serviceState.mCallback);
522                        } catch (RemoteException e) {
523                            Slog.e(TAG, "error in registerCallback", e);
524                        }
525                    } else {
526                        updateServiceConnectionLocked(inputId, resolvedUserId);
527                    }
528                }
529            } finally {
530                Binder.restoreCallingIdentity(identity);
531            }
532        }
533
534        @Override
535        public void unregisterCallback(ITvInputClient client, String inputId, int userId) {
536            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
537                    Binder.getCallingUid(), userId, "unregisterCallback");
538            final long identity = Binder.clearCallingIdentity();
539            try {
540                synchronized (mLock) {
541                    UserState userState = getUserStateLocked(resolvedUserId);
542                    ServiceState serviceState = userState.serviceStateMap.get(inputId);
543                    if (serviceState == null) {
544                        return;
545                    }
546
547                    // Remove this client from the client list and unregister the callback.
548                    serviceState.mClients.remove(client.asBinder());
549                    if (!serviceState.mClients.isEmpty()) {
550                        // We have other clients who want to keep the callback. Do this later.
551                        return;
552                    }
553                    if (serviceState.mService == null || serviceState.mCallback == null) {
554                        return;
555                    }
556                    try {
557                        serviceState.mService.unregisterCallback(serviceState.mCallback);
558                    } catch (RemoteException e) {
559                        Slog.e(TAG, "error in unregisterCallback", e);
560                    } finally {
561                        serviceState.mCallback = null;
562                        updateServiceConnectionLocked(inputId, resolvedUserId);
563                    }
564                }
565            } finally {
566                Binder.restoreCallingIdentity(identity);
567            }
568        }
569
570        @Override
571        public void createSession(final ITvInputClient client, final String inputId,
572                int seq, int userId) {
573            final int callingUid = Binder.getCallingUid();
574            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
575                    userId, "createSession");
576            final long identity = Binder.clearCallingIdentity();
577            try {
578                synchronized (mLock) {
579                    UserState userState = getUserStateLocked(resolvedUserId);
580                    ServiceState serviceState = userState.serviceStateMap.get(inputId);
581                    if (serviceState == null) {
582                        serviceState = new ServiceState(
583                                userState.inputMap.get(inputId), resolvedUserId);
584                        userState.serviceStateMap.put(inputId, serviceState);
585                    }
586                    // Send a null token immediately while reconnecting.
587                    if (serviceState.mReconnecting == true) {
588                        sendSessionTokenToClientLocked(client, inputId, null, null, seq, userId);
589                        return;
590                    }
591
592                    // Create a new session token and a session state.
593                    IBinder sessionToken = new Binder();
594                    SessionState sessionState = new SessionState(
595                            sessionToken, inputId, client, seq, callingUid, resolvedUserId);
596
597                    // Add them to the global session state map of the current user.
598                    userState.sessionStateMap.put(sessionToken, sessionState);
599
600                    // Also, add them to the session state map of the current service.
601                    serviceState.mSessionTokens.add(sessionToken);
602
603                    if (serviceState.mService != null) {
604                        createSessionInternalLocked(serviceState.mService, sessionToken,
605                                resolvedUserId);
606                    } else {
607                        updateServiceConnectionLocked(inputId, resolvedUserId);
608                    }
609                }
610            } finally {
611                Binder.restoreCallingIdentity(identity);
612            }
613        }
614
615        @Override
616        public void releaseSession(IBinder sessionToken, int userId) {
617            final int callingUid = Binder.getCallingUid();
618            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
619                    userId, "releaseSession");
620            final long identity = Binder.clearCallingIdentity();
621            try {
622                synchronized (mLock) {
623                    releaseSessionLocked(sessionToken, callingUid, resolvedUserId);
624                }
625            } finally {
626                Binder.restoreCallingIdentity(identity);
627            }
628        }
629
630        @Override
631        public void setSurface(IBinder sessionToken, Surface surface, int userId) {
632            final int callingUid = Binder.getCallingUid();
633            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
634                    userId, "setSurface");
635            final long identity = Binder.clearCallingIdentity();
636            try {
637                synchronized (mLock) {
638                    try {
639                        getSessionLocked(sessionToken, callingUid, resolvedUserId).setSurface(
640                                surface);
641                    } catch (RemoteException e) {
642                        Slog.e(TAG, "error in setSurface", e);
643                    }
644                }
645            } finally {
646                if (surface != null) {
647                    // surface is not used in TvInputManagerService.
648                    surface.release();
649                }
650                Binder.restoreCallingIdentity(identity);
651            }
652        }
653
654        @Override
655        public void setVolume(IBinder sessionToken, float volume, int userId) {
656            final int callingUid = Binder.getCallingUid();
657            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
658                    userId, "setVolume");
659            final long identity = Binder.clearCallingIdentity();
660            try {
661                synchronized (mLock) {
662                    try {
663                        getSessionLocked(sessionToken, callingUid, resolvedUserId).setVolume(
664                                volume);
665                    } catch (RemoteException e) {
666                        Slog.e(TAG, "error in setVolume", e);
667                    }
668                }
669            } finally {
670                Binder.restoreCallingIdentity(identity);
671            }
672        }
673
674        @Override
675        public void tune(IBinder sessionToken, final Uri channelUri, int userId) {
676            final int callingUid = Binder.getCallingUid();
677            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
678                    userId, "tune");
679            final long identity = Binder.clearCallingIdentity();
680            try {
681                synchronized (mLock) {
682                    try {
683                        getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(channelUri);
684
685                        long currentTime = System.currentTimeMillis();
686                        long channelId = ContentUris.parseId(channelUri);
687
688                        // Close the open log entry first, if any.
689                        UserState userState = getUserStateLocked(resolvedUserId);
690                        SessionState sessionState = userState.sessionStateMap.get(sessionToken);
691                        if (sessionState.mLogUri != null) {
692                            SomeArgs args = SomeArgs.obtain();
693                            args.arg1 = sessionState.mLogUri;
694                            args.arg2 = currentTime;
695                            mLogHandler.obtainMessage(LogHandler.MSG_CLOSE_ENTRY, args)
696                                    .sendToTarget();
697                        }
698
699                        // Create a log entry and fill it later.
700                        ContentValues values = new ContentValues();
701                        values.put(TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
702                                currentTime);
703                        values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, 0);
704                        values.put(TvContract.WatchedPrograms.COLUMN_CHANNEL_ID, channelId);
705
706                        sessionState.mLogUri = mContentResolver.insert(
707                                TvContract.WatchedPrograms.CONTENT_URI, values);
708                        SomeArgs args = SomeArgs.obtain();
709                        args.arg1 = sessionState.mLogUri;
710                        args.arg2 = ContentUris.parseId(channelUri);
711                        args.arg3 = currentTime;
712                        mLogHandler.obtainMessage(LogHandler.MSG_OPEN_ENTRY, args).sendToTarget();
713                    } catch (RemoteException e) {
714                        Slog.e(TAG, "error in tune", e);
715                        return;
716                    }
717                }
718            } finally {
719                Binder.restoreCallingIdentity(identity);
720            }
721        }
722
723        @Override
724        public void createOverlayView(IBinder sessionToken, IBinder windowToken, Rect frame,
725                int userId) {
726            final int callingUid = Binder.getCallingUid();
727            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
728                    userId, "createOverlayView");
729            final long identity = Binder.clearCallingIdentity();
730            try {
731                synchronized (mLock) {
732                    try {
733                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
734                                .createOverlayView(windowToken, frame);
735                    } catch (RemoteException e) {
736                        Slog.e(TAG, "error in createOverlayView", e);
737                    }
738                }
739            } finally {
740                Binder.restoreCallingIdentity(identity);
741            }
742        }
743
744        @Override
745        public void relayoutOverlayView(IBinder sessionToken, Rect frame, int userId) {
746            final int callingUid = Binder.getCallingUid();
747            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
748                    userId, "relayoutOverlayView");
749            final long identity = Binder.clearCallingIdentity();
750            try {
751                synchronized (mLock) {
752                    try {
753                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
754                                .relayoutOverlayView(frame);
755                    } catch (RemoteException e) {
756                        Slog.e(TAG, "error in relayoutOverlayView", e);
757                    }
758                }
759            } finally {
760                Binder.restoreCallingIdentity(identity);
761            }
762        }
763
764        @Override
765        public void removeOverlayView(IBinder sessionToken, int userId) {
766            final int callingUid = Binder.getCallingUid();
767            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
768                    userId, "removeOverlayView");
769            final long identity = Binder.clearCallingIdentity();
770            try {
771                synchronized (mLock) {
772                    try {
773                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
774                                .removeOverlayView();
775                    } catch (RemoteException e) {
776                        Slog.e(TAG, "error in removeOverlayView", e);
777                    }
778                }
779            } finally {
780                Binder.restoreCallingIdentity(identity);
781            }
782        }
783
784        @Override
785        public List<TvInputHardwareInfo> getHardwareList() throws RemoteException {
786            if (mContext.checkCallingPermission(
787                    android.Manifest.permission.TV_INPUT_HARDWARE)
788                    != PackageManager.PERMISSION_GRANTED) {
789                return null;
790            }
791
792            final long identity = Binder.clearCallingIdentity();
793            try {
794                return mTvInputHardwareManager.getHardwareList();
795            } finally {
796                Binder.restoreCallingIdentity(identity);
797            }
798        }
799
800        @Override
801        public ITvInputHardware acquireTvInputHardware(int deviceId,
802                ITvInputHardwareCallback callback, int userId) throws RemoteException {
803            if (mContext.checkCallingPermission(
804                    android.Manifest.permission.TV_INPUT_HARDWARE)
805                    != PackageManager.PERMISSION_GRANTED) {
806                return null;
807            }
808
809            final long identity = Binder.clearCallingIdentity();
810            final int callingUid = Binder.getCallingUid();
811            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
812                    userId, "acquireTvInputHardware");
813            try {
814                return mTvInputHardwareManager.acquireHardware(
815                        deviceId, callback, callingUid, resolvedUserId);
816            } finally {
817                Binder.restoreCallingIdentity(identity);
818            }
819        }
820
821        @Override
822        public void releaseTvInputHardware(int deviceId, ITvInputHardware hardware, int userId)
823                throws RemoteException {
824            if (mContext.checkCallingPermission(
825                    android.Manifest.permission.TV_INPUT_HARDWARE)
826                    != PackageManager.PERMISSION_GRANTED) {
827                return;
828            }
829
830            final long identity = Binder.clearCallingIdentity();
831            final int callingUid = Binder.getCallingUid();
832            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
833                    userId, "releaseTvInputHardware");
834            try {
835                mTvInputHardwareManager.releaseHardware(
836                        deviceId, hardware, callingUid, resolvedUserId);
837            } finally {
838                Binder.restoreCallingIdentity(identity);
839            }
840        }
841    }
842
843    private static final class UserState {
844        // A mapping from the TV input id to its TvInputInfo.
845        private final Map<String, TvInputInfo> inputMap = new HashMap<String,TvInputInfo>();
846
847        // A mapping from the name of a TV input service to its state.
848        private final Map<String, ServiceState> serviceStateMap =
849                new HashMap<String, ServiceState>();
850
851        // A mapping from the token of a TV input session to its state.
852        private final Map<IBinder, SessionState> sessionStateMap =
853                new HashMap<IBinder, SessionState>();
854    }
855
856    private final class ServiceState {
857        // TODO: need to implement DeathRecipient for clients.
858        private final List<IBinder> mClients = new ArrayList<IBinder>();
859        private final List<IBinder> mSessionTokens = new ArrayList<IBinder>();
860        private final ServiceConnection mConnection;
861        private final TvInputInfo mTvInputInfo;
862
863        private ITvInputService mService;
864        private ServiceCallback mCallback;
865        private boolean mBound;
866        private boolean mAvailable;
867        private boolean mReconnecting;
868
869        private ServiceState(TvInputInfo inputInfo, int userId) {
870            mTvInputInfo = inputInfo;
871            mConnection = new InputServiceConnection(inputInfo, userId);
872        }
873    }
874
875    private final class SessionState implements IBinder.DeathRecipient {
876        private final String mInputId;
877        private final ITvInputClient mClient;
878        private final int mSeq;
879        private final int mCallingUid;
880        private final int mUserId;
881        private final IBinder mToken;
882        private ITvInputSession mSession;
883        private Uri mLogUri;
884
885        private SessionState(IBinder token, String inputId, ITvInputClient client, int seq,
886                int callingUid, int userId) {
887            mToken = token;
888            mInputId = inputId;
889            mClient = client;
890            mSeq = seq;
891            mCallingUid = callingUid;
892            mUserId = userId;
893        }
894
895        @Override
896        public void binderDied() {
897            synchronized (mLock) {
898                mSession = null;
899                if (mClient != null) {
900                    try {
901                        mClient.onSessionReleased(mSeq);
902                    } catch(RemoteException e) {
903                        Slog.e(TAG, "error in onSessionReleased", e);
904                    }
905                }
906                removeSessionStateLocked(mToken, mUserId);
907            }
908        }
909    }
910
911    private final class InputServiceConnection implements ServiceConnection {
912        private final TvInputInfo mTvInputInfo;
913        private final int mUserId;
914
915        private InputServiceConnection(TvInputInfo inputInfo, int userId) {
916            mUserId = userId;
917            mTvInputInfo = inputInfo;
918        }
919
920        @Override
921        public void onServiceConnected(ComponentName name, IBinder service) {
922            if (DEBUG) {
923                Slog.d(TAG, "onServiceConnected(inputId=" + mTvInputInfo.getId() + ")");
924            }
925            synchronized (mLock) {
926                ServiceState serviceState = getServiceStateLocked(mTvInputInfo.getId(), mUserId);
927                serviceState.mService = ITvInputService.Stub.asInterface(service);
928
929                // Register a callback, if we need to.
930                if (!serviceState.mClients.isEmpty() && serviceState.mCallback == null) {
931                    serviceState.mCallback = new ServiceCallback(mUserId);
932                    try {
933                        serviceState.mService.registerCallback(serviceState.mCallback);
934                    } catch (RemoteException e) {
935                        Slog.e(TAG, "error in registerCallback", e);
936                    }
937                }
938
939                // And create sessions, if any.
940                for (IBinder sessionToken : serviceState.mSessionTokens) {
941                    createSessionInternalLocked(serviceState.mService, sessionToken, mUserId);
942                }
943            }
944        }
945
946        @Override
947        public void onServiceDisconnected(ComponentName name) {
948            if (DEBUG) {
949                Slog.d(TAG, "onServiceDisconnected(inputId=" + mTvInputInfo.getId() + ")");
950            }
951            if (!mTvInputInfo.getComponent().equals(name)) {
952                throw new IllegalArgumentException("Mismatched ComponentName: "
953                        + mTvInputInfo.getComponent() + " (expected), " + name + " (actual).");
954            }
955            synchronized (mLock) {
956                UserState userState = getUserStateLocked(mUserId);
957                ServiceState serviceState = userState.serviceStateMap.get(mTvInputInfo.getId());
958                if (serviceState != null) {
959                    serviceState.mReconnecting = true;
960                    serviceState.mBound = false;
961                    serviceState.mService = null;
962                    serviceState.mCallback = null;
963
964                    // Send null tokens for not finishing create session events.
965                    for (IBinder sessionToken : serviceState.mSessionTokens) {
966                        SessionState sessionState = userState.sessionStateMap.get(sessionToken);
967                        if (sessionState.mSession == null) {
968                            removeSessionStateLocked(sessionToken, sessionState.mUserId);
969                            sendSessionTokenToClientLocked(sessionState.mClient,
970                                    sessionState.mInputId, null, null, sessionState.mSeq,
971                                    sessionState.mUserId);
972                        }
973                    }
974
975                    if (serviceState.mAvailable) {
976                        serviceState.mAvailable = false;
977                        broadcastServiceAvailabilityChangedLocked(serviceState);
978                    }
979                    updateServiceConnectionLocked(mTvInputInfo.getId(), mUserId);
980                }
981            }
982        }
983    }
984
985    private final class ServiceCallback extends ITvInputServiceCallback.Stub {
986        private final int mUserId;
987
988        ServiceCallback(int userId) {
989            mUserId = userId;
990        }
991
992        @Override
993        public void onAvailabilityChanged(String inputId, boolean isAvailable) {
994            if (DEBUG) {
995                Slog.d(TAG, "onAvailabilityChanged(inputId=" + inputId + ", isAvailable="
996                        + isAvailable + ")");
997            }
998            synchronized (mLock) {
999                ServiceState serviceState = getServiceStateLocked(inputId, mUserId);
1000                if (serviceState.mAvailable != isAvailable) {
1001                    serviceState.mAvailable = isAvailable;
1002                    broadcastServiceAvailabilityChangedLocked(serviceState);
1003                }
1004            }
1005        }
1006    }
1007
1008    private final class LogHandler extends Handler {
1009        private static final int MSG_OPEN_ENTRY = 1;
1010        private static final int MSG_UPDATE_ENTRY = 2;
1011        private static final int MSG_CLOSE_ENTRY = 3;
1012
1013        public LogHandler(Looper looper) {
1014            super(looper);
1015        }
1016
1017        @Override
1018        public void handleMessage(Message msg) {
1019            switch (msg.what) {
1020                case MSG_OPEN_ENTRY: {
1021                    SomeArgs args = (SomeArgs) msg.obj;
1022                    Uri uri = (Uri) args.arg1;
1023                    long channelId = (long) args.arg2;
1024                    long time = (long) args.arg3;
1025                    onOpenEntry(uri, channelId, time);
1026                    args.recycle();
1027                    return;
1028                }
1029                case MSG_UPDATE_ENTRY: {
1030                    SomeArgs args = (SomeArgs) msg.obj;
1031                    Uri uri = (Uri) args.arg1;
1032                    long channelId = (long) args.arg2;
1033                    long time = (long) args.arg3;
1034                    onUpdateEntry(uri, channelId, time);
1035                    args.recycle();
1036                    return;
1037                }
1038                case MSG_CLOSE_ENTRY: {
1039                    SomeArgs args = (SomeArgs) msg.obj;
1040                    Uri uri = (Uri) args.arg1;
1041                    long time = (long) args.arg2;
1042                    onCloseEntry(uri, time);
1043                    args.recycle();
1044                    return;
1045                }
1046                default: {
1047                    Slog.w(TAG, "Unhandled message code: " + msg.what);
1048                    return;
1049                }
1050            }
1051        }
1052
1053        private void onOpenEntry(Uri uri, long channelId, long watchStarttime) {
1054            String[] projection = {
1055                    TvContract.Programs.COLUMN_TITLE,
1056                    TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS,
1057                    TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS,
1058                    TvContract.Programs.COLUMN_DESCRIPTION
1059            };
1060            String selection = TvContract.Programs.COLUMN_CHANNEL_ID + "=? AND "
1061                    + TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS + "<=? AND "
1062                    + TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS + ">?";
1063            String[] selectionArgs = {
1064                    String.valueOf(channelId),
1065                    String.valueOf(watchStarttime),
1066                    String.valueOf(watchStarttime)
1067            };
1068            String sortOrder = TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS + " ASC";
1069            Cursor cursor = null;
1070            try {
1071                cursor = mContentResolver.query(TvContract.Programs.CONTENT_URI, projection,
1072                        selection, selectionArgs, sortOrder);
1073                if (cursor != null && cursor.moveToNext()) {
1074                    ContentValues values = new ContentValues();
1075                    values.put(TvContract.WatchedPrograms.COLUMN_TITLE, cursor.getString(0));
1076                    values.put(TvContract.WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS,
1077                            cursor.getLong(1));
1078                    long endTime = cursor.getLong(2);
1079                    values.put(TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS, endTime);
1080                    values.put(TvContract.WatchedPrograms.COLUMN_DESCRIPTION, cursor.getString(3));
1081                    mContentResolver.update(uri, values, null, null);
1082
1083                    // Schedule an update when the current program ends.
1084                    SomeArgs args = SomeArgs.obtain();
1085                    args.arg1 = uri;
1086                    args.arg2 = channelId;
1087                    args.arg3 = endTime;
1088                    Message msg = obtainMessage(LogHandler.MSG_UPDATE_ENTRY, args);
1089                    sendMessageDelayed(msg, endTime - System.currentTimeMillis());
1090                }
1091            } finally {
1092                if (cursor != null) {
1093                    cursor.close();
1094                }
1095            }
1096        }
1097
1098        private void onUpdateEntry(Uri uri, long channelId, long time) {
1099            String[] projection = {
1100                    TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
1101                    TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS,
1102                    TvContract.WatchedPrograms.COLUMN_TITLE,
1103                    TvContract.WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS,
1104                    TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS,
1105                    TvContract.WatchedPrograms.COLUMN_DESCRIPTION
1106            };
1107            Cursor cursor = null;
1108            try {
1109                cursor = mContentResolver.query(uri, projection, null, null, null);
1110                if (cursor != null && cursor.moveToNext()) {
1111                    long watchStartTime = cursor.getLong(0);
1112                    long watchEndTime = cursor.getLong(1);
1113                    String title = cursor.getString(2);
1114                    long startTime = cursor.getLong(3);
1115                    long endTime = cursor.getLong(4);
1116                    String description = cursor.getString(5);
1117
1118                    // Do nothing if the current log entry is already closed.
1119                    if (watchEndTime > 0) {
1120                        return;
1121                    }
1122
1123                    // The current program has just ended. Create a (complete) log entry off the
1124                    // current entry.
1125                    ContentValues values = new ContentValues();
1126                    values.put(TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
1127                            watchStartTime);
1128                    values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, time);
1129                    values.put(TvContract.WatchedPrograms.COLUMN_CHANNEL_ID, channelId);
1130                    values.put(TvContract.WatchedPrograms.COLUMN_TITLE, title);
1131                    values.put(TvContract.WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS, startTime);
1132                    values.put(TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS, endTime);
1133                    values.put(TvContract.WatchedPrograms.COLUMN_DESCRIPTION, description);
1134                    mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
1135                }
1136            } finally {
1137                if (cursor != null) {
1138                    cursor.close();
1139                }
1140            }
1141            // Re-open the current log entry with the next program information.
1142            onOpenEntry(uri, channelId, time);
1143        }
1144
1145        private void onCloseEntry(Uri uri, long watchEndTime) {
1146            ContentValues values = new ContentValues();
1147            values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, watchEndTime);
1148            mContentResolver.update(uri, values, null, null);
1149        }
1150    }
1151}
1152