TvInputManagerService.java revision b375805f3b1672e68d1511565af4700e5fa8491d
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 static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
20import static android.media.tv.TvInputManager.INPUT_STATE_DISCONNECTED;
21
22import android.app.ActivityManager;
23import android.content.BroadcastReceiver;
24import android.content.ComponentName;
25import android.content.ContentProviderOperation;
26import android.content.ContentProviderResult;
27import android.content.ContentResolver;
28import android.content.ContentUris;
29import android.content.ContentValues;
30import android.content.Context;
31import android.content.Intent;
32import android.content.IntentFilter;
33import android.content.OperationApplicationException;
34import android.content.ServiceConnection;
35import android.content.pm.PackageManager;
36import android.content.pm.ResolveInfo;
37import android.content.pm.ServiceInfo;
38import android.database.Cursor;
39import android.graphics.Rect;
40import android.hardware.hdmi.HdmiCecDeviceInfo;
41import android.media.tv.ITvInputClient;
42import android.media.tv.ITvInputHardware;
43import android.media.tv.ITvInputHardwareCallback;
44import android.media.tv.ITvInputManager;
45import android.media.tv.ITvInputManagerCallback;
46import android.media.tv.ITvInputService;
47import android.media.tv.ITvInputServiceCallback;
48import android.media.tv.ITvInputSession;
49import android.media.tv.ITvInputSessionCallback;
50import android.media.tv.TvContract;
51import android.media.tv.TvInputHardwareInfo;
52import android.media.tv.TvInputInfo;
53import android.media.tv.TvInputService;
54import android.media.tv.TvTrackInfo;
55import android.net.Uri;
56import android.os.Binder;
57import android.os.Bundle;
58import android.os.Handler;
59import android.os.IBinder;
60import android.os.Looper;
61import android.os.Message;
62import android.os.Process;
63import android.os.RemoteException;
64import android.os.UserHandle;
65import android.util.Slog;
66import android.util.SparseArray;
67import android.view.InputChannel;
68import android.view.Surface;
69
70import com.android.internal.content.PackageMonitor;
71import com.android.internal.os.SomeArgs;
72import com.android.internal.util.IndentingPrintWriter;
73import com.android.server.IoThread;
74import com.android.server.SystemService;
75
76import org.xmlpull.v1.XmlPullParserException;
77
78import java.io.FileDescriptor;
79import java.io.IOException;
80import java.io.PrintWriter;
81import java.util.ArrayList;
82import java.util.HashMap;
83import java.util.HashSet;
84import java.util.Iterator;
85import java.util.List;
86import java.util.Map;
87import java.util.Set;
88
89/** This class provides a system service that manages television inputs. */
90public final class TvInputManagerService extends SystemService {
91    // STOPSHIP: Turn debugging off.
92    private static final boolean DEBUG = true;
93    private static final String TAG = "TvInputManagerService";
94
95    private final Context mContext;
96    private final TvInputHardwareManager mTvInputHardwareManager;
97
98    private final ContentResolver mContentResolver;
99
100    // A global lock.
101    private final Object mLock = new Object();
102
103    // ID of the current user.
104    private int mCurrentUserId = UserHandle.USER_OWNER;
105
106    // A map from user id to UserState.
107    private final SparseArray<UserState> mUserStates = new SparseArray<UserState>();
108
109    private final Handler mLogHandler;
110
111    public TvInputManagerService(Context context) {
112        super(context);
113
114        mContext = context;
115        mContentResolver = context.getContentResolver();
116        mLogHandler = new LogHandler(IoThread.get().getLooper());
117
118        mTvInputHardwareManager = new TvInputHardwareManager(context, new HardwareListener());
119
120        synchronized (mLock) {
121            mUserStates.put(mCurrentUserId, new UserState());
122        }
123    }
124
125    @Override
126    public void onStart() {
127        publishBinderService(Context.TV_INPUT_SERVICE, new BinderService());
128    }
129
130    @Override
131    public void onBootPhase(int phase) {
132        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
133            registerBroadcastReceivers();
134        } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
135            synchronized (mLock) {
136                buildTvInputListLocked(mCurrentUserId);
137            }
138        }
139        mTvInputHardwareManager.onBootPhase(phase);
140    }
141
142    private void registerBroadcastReceivers() {
143        PackageMonitor monitor = new PackageMonitor() {
144            @Override
145            public void onSomePackagesChanged() {
146                synchronized (mLock) {
147                    buildTvInputListLocked(mCurrentUserId);
148                }
149            }
150
151            @Override
152            public void onPackageRemoved(String packageName, int uid) {
153                synchronized (mLock) {
154                    UserState userState = getUserStateLocked(mCurrentUserId);
155                    if (!userState.packageSet.contains(packageName)) {
156                        // Not a TV input package.
157                        return;
158                    }
159                }
160
161                ArrayList<ContentProviderOperation> operations =
162                        new ArrayList<ContentProviderOperation>();
163
164                String selection = TvContract.BaseTvColumns.COLUMN_PACKAGE_NAME + "=?";
165                String[] selectionArgs = { packageName };
166
167                operations.add(ContentProviderOperation.newDelete(TvContract.Channels.CONTENT_URI)
168                        .withSelection(selection, selectionArgs).build());
169                operations.add(ContentProviderOperation.newDelete(TvContract.Programs.CONTENT_URI)
170                        .withSelection(selection, selectionArgs).build());
171                operations.add(ContentProviderOperation
172                        .newDelete(TvContract.WatchedPrograms.CONTENT_URI)
173                        .withSelection(selection, selectionArgs).build());
174
175                ContentProviderResult[] results = null;
176                try {
177                    results = mContentResolver.applyBatch(TvContract.AUTHORITY, operations);
178                } catch (RemoteException | OperationApplicationException e) {
179                    Slog.e(TAG, "error in applyBatch" + e);
180                }
181
182                if (DEBUG) {
183                    Slog.d(TAG, "onPackageRemoved(packageName=" + packageName + ", uid=" + uid
184                            + ")");
185                    Slog.d(TAG, "results=" + results);
186                }
187            }
188        };
189        monitor.register(mContext, null, UserHandle.ALL, true);
190
191        IntentFilter intentFilter = new IntentFilter();
192        intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
193        intentFilter.addAction(Intent.ACTION_USER_REMOVED);
194        mContext.registerReceiverAsUser(new BroadcastReceiver() {
195            @Override
196            public void onReceive(Context context, Intent intent) {
197                String action = intent.getAction();
198                if (Intent.ACTION_USER_SWITCHED.equals(action)) {
199                    switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
200                } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
201                    removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
202                }
203            }
204        }, UserHandle.ALL, intentFilter, null, null);
205    }
206
207    private static boolean hasHardwarePermission(PackageManager pm, ComponentName name) {
208        return pm.checkPermission(android.Manifest.permission.TV_INPUT_HARDWARE,
209                name.getPackageName()) == PackageManager.PERMISSION_GRANTED;
210    }
211
212    private void buildTvInputListLocked(int userId) {
213        UserState userState = getUserStateLocked(userId);
214
215        Map<String, TvInputState> oldInputMap = userState.inputMap;
216        userState.inputMap = new HashMap<String, TvInputState>();
217
218        userState.packageSet.clear();
219
220        if (DEBUG) Slog.d(TAG, "buildTvInputList");
221        PackageManager pm = mContext.getPackageManager();
222        List<ResolveInfo> services = pm.queryIntentServices(
223                new Intent(TvInputService.SERVICE_INTERFACE),
224                PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
225        List<TvInputInfo> infoList = new ArrayList<TvInputInfo>();
226        for (ResolveInfo ri : services) {
227            ServiceInfo si = ri.serviceInfo;
228            if (!android.Manifest.permission.BIND_TV_INPUT.equals(si.permission)) {
229                Slog.w(TAG, "Skipping TV input " + si.name + ": it does not require the permission "
230                        + android.Manifest.permission.BIND_TV_INPUT);
231                continue;
232            }
233            try {
234                infoList.clear();
235                ComponentName service = new ComponentName(si.packageName, si.name);
236                if (hasHardwarePermission(pm, service)) {
237                    ServiceState serviceState = userState.serviceStateMap.get(service);
238                    if (serviceState == null) {
239                        // We see this hardware TV input service for the first time; we need to
240                        // prepare the ServiceState object so that we can connect to the service and
241                        // let it add TvInputInfo objects to mInputList if there's any.
242                        serviceState = new ServiceState(service, userId);
243                        userState.serviceStateMap.put(service, serviceState);
244                    } else {
245                        infoList.addAll(serviceState.mInputList);
246                    }
247                } else {
248                    infoList.add(TvInputInfo.createTvInputInfo(mContext, ri));
249                }
250
251                for (TvInputInfo info : infoList) {
252                    if (DEBUG) Slog.d(TAG, "add " + info.getId());
253                    TvInputState state = oldInputMap.get(info.getId());
254                    if (state == null) {
255                        state = new TvInputState();
256                    }
257                    userState.inputMap.put(info.getId(), state);
258                    state.mInfo = info;
259                }
260
261                // Reconnect the service if existing input is updated.
262                updateServiceConnectionLocked(service, userId);
263
264                userState.packageSet.add(si.packageName);
265            } catch (IOException | XmlPullParserException e) {
266                Slog.e(TAG, "Can't load TV input " + si.name, e);
267            }
268        }
269        oldInputMap.clear();
270    }
271
272    private void switchUser(int userId) {
273        synchronized (mLock) {
274            if (mCurrentUserId == userId) {
275                return;
276            }
277            // final int oldUserId = mCurrentUserId;
278            // TODO: Release services and sessions in the old user state, if needed.
279            mCurrentUserId = userId;
280
281            UserState userState = mUserStates.get(userId);
282            if (userState == null) {
283                userState = new UserState();
284            }
285            mUserStates.put(userId, userState);
286            buildTvInputListLocked(userId);
287        }
288    }
289
290    private void removeUser(int userId) {
291        synchronized (mLock) {
292            UserState userState = mUserStates.get(userId);
293            if (userState == null) {
294                return;
295            }
296            // Release created sessions.
297            for (SessionState state : userState.sessionStateMap.values()) {
298                if (state.mSession != null) {
299                    try {
300                        state.mSession.release();
301                    } catch (RemoteException e) {
302                        Slog.e(TAG, "error in release", e);
303                    }
304                }
305            }
306            userState.sessionStateMap.clear();
307
308            // Unregister all callbacks and unbind all services.
309            for (ServiceState serviceState : userState.serviceStateMap.values()) {
310                if (serviceState.mCallback != null) {
311                    try {
312                        serviceState.mService.unregisterCallback(serviceState.mCallback);
313                    } catch (RemoteException e) {
314                        Slog.e(TAG, "error in unregisterCallback", e);
315                    }
316                }
317                serviceState.mClientTokens.clear();
318                mContext.unbindService(serviceState.mConnection);
319            }
320            userState.serviceStateMap.clear();
321
322            userState.clientStateMap.clear();
323
324            mUserStates.remove(userId);
325        }
326    }
327
328    private UserState getUserStateLocked(int userId) {
329        UserState userState = mUserStates.get(userId);
330        if (userState == null) {
331            throw new IllegalStateException("User state not found for user ID " + userId);
332        }
333        return userState;
334    }
335
336    private ServiceState getServiceStateLocked(ComponentName name, int userId) {
337        UserState userState = getUserStateLocked(userId);
338        ServiceState serviceState = userState.serviceStateMap.get(name);
339        if (serviceState == null) {
340            throw new IllegalStateException("Service state not found for " + name + " (userId="
341                    + userId + ")");
342        }
343        return serviceState;
344    }
345
346    private SessionState getSessionStateLocked(IBinder sessionToken, int callingUid, int userId) {
347        UserState userState = getUserStateLocked(userId);
348        SessionState sessionState = userState.sessionStateMap.get(sessionToken);
349        if (sessionState == null) {
350            throw new IllegalArgumentException("Session state not found for token " + sessionToken);
351        }
352        // Only the application that requested this session or the system can access it.
353        if (callingUid != Process.SYSTEM_UID && callingUid != sessionState.mCallingUid) {
354            throw new SecurityException("Illegal access to the session with token " + sessionToken
355                    + " from uid " + callingUid);
356        }
357        return sessionState;
358    }
359
360    private ITvInputSession getSessionLocked(IBinder sessionToken, int callingUid, int userId) {
361        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
362        ITvInputSession session = sessionState.mSession;
363        if (session == null) {
364            throw new IllegalStateException("Session not yet created for token " + sessionToken);
365        }
366        return session;
367    }
368
369    private int resolveCallingUserId(int callingPid, int callingUid, int requestedUserId,
370            String methodName) {
371        return ActivityManager.handleIncomingUser(callingPid, callingUid, requestedUserId, false,
372                false, methodName, null);
373    }
374
375    private static boolean shouldMaintainConnection(ServiceState serviceState) {
376        return !serviceState.mClientTokens.isEmpty()
377                || !serviceState.mSessionTokens.isEmpty()
378                || serviceState.mIsHardware;
379        // TODO: Find a way to maintain connection only when necessary.
380    }
381
382    private void updateServiceConnectionLocked(ComponentName service, int userId) {
383        UserState userState = getUserStateLocked(userId);
384        ServiceState serviceState = userState.serviceStateMap.get(service);
385        if (serviceState == null) {
386            return;
387        }
388        if (serviceState.mReconnecting) {
389            if (!serviceState.mSessionTokens.isEmpty()) {
390                // wait until all the sessions are removed.
391                return;
392            }
393            serviceState.mReconnecting = false;
394        }
395        boolean maintainConnection = shouldMaintainConnection(serviceState);
396        if (serviceState.mService == null && maintainConnection && userId == mCurrentUserId) {
397            // This means that the service is not yet connected but its state indicates that we
398            // have pending requests. Then, connect the service.
399            if (serviceState.mBound) {
400                // We have already bound to the service so we don't try to bind again until after we
401                // unbind later on.
402                return;
403            }
404            if (DEBUG) {
405                Slog.d(TAG, "bindServiceAsUser(service=" + service + ", userId=" + userId + ")");
406            }
407
408            Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(service);
409            // Binding service may fail if the service is updating.
410            // In that case, the connection will be revived in buildTvInputListLocked called by
411            // onSomePackagesChanged.
412            serviceState.mBound = mContext.bindServiceAsUser(
413                    i, serviceState.mConnection, Context.BIND_AUTO_CREATE, new UserHandle(userId));
414        } else if (serviceState.mService != null && !maintainConnection) {
415            // This means that the service is already connected but its state indicates that we have
416            // nothing to do with it. Then, disconnect the service.
417            if (DEBUG) {
418                Slog.d(TAG, "unbindService(service=" + service + ")");
419            }
420            mContext.unbindService(serviceState.mConnection);
421            userState.serviceStateMap.remove(service);
422        }
423    }
424
425    private ClientState createClientStateLocked(IBinder clientToken, int userId) {
426        UserState userState = getUserStateLocked(userId);
427        ClientState clientState = new ClientState(clientToken, userId);
428        try {
429            clientToken.linkToDeath(clientState, 0);
430        } catch (RemoteException e) {
431            Slog.e(TAG, "Client is already died.");
432        }
433        userState.clientStateMap.put(clientToken, clientState);
434        return clientState;
435    }
436
437    private void createSessionInternalLocked(ITvInputService service, final IBinder sessionToken,
438            final int userId) {
439        final UserState userState = getUserStateLocked(userId);
440        final SessionState sessionState = userState.sessionStateMap.get(sessionToken);
441        if (DEBUG) {
442            Slog.d(TAG, "createSessionInternalLocked(inputId=" + sessionState.mInfo.getId() + ")");
443        }
444
445        final InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString());
446
447        // Set up a callback to send the session token.
448        ITvInputSessionCallback callback = new ITvInputSessionCallback.Stub() {
449            @Override
450            public void onSessionCreated(ITvInputSession session) {
451                if (DEBUG) {
452                    Slog.d(TAG, "onSessionCreated(inputId=" + sessionState.mInfo.getId() + ")");
453                }
454                synchronized (mLock) {
455                    sessionState.mSession = session;
456                    if (session == null) {
457                        removeSessionStateLocked(sessionToken, userId);
458                        sendSessionTokenToClientLocked(sessionState.mClient,
459                                sessionState.mInfo.getId(), null, null, sessionState.mSeq);
460                    } else {
461                        try {
462                            session.asBinder().linkToDeath(sessionState, 0);
463                        } catch (RemoteException e) {
464                            Slog.e(TAG, "Session is already died.");
465                        }
466
467                        IBinder clientToken = sessionState.mClient.asBinder();
468                        ClientState clientState = userState.clientStateMap.get(clientToken);
469                        if (clientState == null) {
470                            clientState = createClientStateLocked(clientToken, userId);
471                        }
472                        clientState.mSessionTokens.add(sessionState.mSessionToken);
473
474                        sendSessionTokenToClientLocked(sessionState.mClient,
475                                sessionState.mInfo.getId(), sessionToken, channels[0],
476                                sessionState.mSeq);
477                    }
478                    channels[0].dispose();
479                }
480            }
481
482            @Override
483            public void onChannelRetuned(Uri channelUri) {
484                synchronized (mLock) {
485                    if (DEBUG) {
486                        Slog.d(TAG, "onChannelRetuned(" + channelUri + ")");
487                    }
488                    if (sessionState.mSession == null || sessionState.mClient == null) {
489                        return;
490                    }
491                    try {
492                        // TODO: Consider adding this channel change in the watch log. When we do
493                        // that, how we can protect the watch log from malicious tv inputs should
494                        // be addressed. e.g. add a field which represents where the channel change
495                        // originated from.
496                        sessionState.mClient.onChannelRetuned(channelUri, sessionState.mSeq);
497                    } catch (RemoteException e) {
498                        Slog.e(TAG, "error in onChannelRetuned");
499                    }
500                }
501            }
502
503            @Override
504            public void onTrackInfoChanged(List<TvTrackInfo> tracks) {
505                synchronized (mLock) {
506                    if (DEBUG) {
507                        Slog.d(TAG, "onTrackInfoChanged(" + tracks + ")");
508                    }
509                    if (sessionState.mSession == null || sessionState.mClient == null) {
510                        return;
511                    }
512                    try {
513                        sessionState.mClient.onTrackInfoChanged(tracks,
514                                sessionState.mSeq);
515                    } catch (RemoteException e) {
516                        Slog.e(TAG, "error in onTrackInfoChanged");
517                    }
518                }
519            }
520
521            @Override
522            public void onVideoAvailable() {
523                synchronized (mLock) {
524                    if (DEBUG) {
525                        Slog.d(TAG, "onVideoAvailable()");
526                    }
527                    if (sessionState.mSession == null || sessionState.mClient == null) {
528                        return;
529                    }
530                    try {
531                        sessionState.mClient.onVideoAvailable(sessionState.mSeq);
532                    } catch (RemoteException e) {
533                        Slog.e(TAG, "error in onVideoAvailable");
534                    }
535                }
536            }
537
538            @Override
539            public void onVideoUnavailable(int reason) {
540                synchronized (mLock) {
541                    if (DEBUG) {
542                        Slog.d(TAG, "onVideoUnavailable(" + reason + ")");
543                    }
544                    if (sessionState.mSession == null || sessionState.mClient == null) {
545                        return;
546                    }
547                    try {
548                        sessionState.mClient.onVideoUnavailable(reason, sessionState.mSeq);
549                    } catch (RemoteException e) {
550                        Slog.e(TAG, "error in onVideoUnavailable");
551                    }
552                }
553            }
554
555            @Override
556            public void onContentBlocked(String rating) {
557                synchronized (mLock) {
558                    if (DEBUG) {
559                        Slog.d(TAG, "onContentBlocked()");
560                    }
561                    if (sessionState.mSession == null || sessionState.mClient == null) {
562                        return;
563                    }
564                    try {
565                        sessionState.mClient.onContentBlocked(rating, sessionState.mSeq);
566                    } catch (RemoteException e) {
567                        Slog.e(TAG, "error in onContentBlocked");
568                    }
569                }
570            }
571
572            @Override
573            public void onSessionEvent(String eventType, Bundle eventArgs) {
574                synchronized (mLock) {
575                    if (DEBUG) {
576                        Slog.d(TAG, "onEvent(what=" + eventType + ", data=" + eventArgs + ")");
577                    }
578                    if (sessionState.mSession == null || sessionState.mClient == null) {
579                        return;
580                    }
581                    try {
582                        sessionState.mClient.onSessionEvent(eventType, eventArgs,
583                                sessionState.mSeq);
584                    } catch (RemoteException e) {
585                        Slog.e(TAG, "error in onSessionEvent");
586                    }
587                }
588            }
589        };
590
591        // Create a session. When failed, send a null token immediately.
592        try {
593            service.createSession(channels[1], callback, sessionState.mInfo.getId());
594        } catch (RemoteException e) {
595            Slog.e(TAG, "error in createSession", e);
596            removeSessionStateLocked(sessionToken, userId);
597            sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mInfo.getId(), null,
598                    null, sessionState.mSeq);
599        }
600        channels[1].dispose();
601    }
602
603    private void sendSessionTokenToClientLocked(ITvInputClient client, String inputId,
604            IBinder sessionToken, InputChannel channel, int seq) {
605        try {
606            client.onSessionCreated(inputId, sessionToken, channel, seq);
607        } catch (RemoteException exception) {
608            Slog.e(TAG, "error in onSessionCreated", exception);
609        }
610    }
611
612    private void releaseSessionLocked(IBinder sessionToken, int callingUid, int userId) {
613        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
614        if (sessionState.mSession != null) {
615            try {
616                sessionState.mSession.release();
617            } catch (RemoteException e) {
618                Slog.w(TAG, "session is already disapeared", e);
619            }
620            sessionState.mSession = null;
621        }
622        removeSessionStateLocked(sessionToken, userId);
623    }
624
625    private void removeSessionStateLocked(IBinder sessionToken, int userId) {
626        // Remove the session state from the global session state map of the current user.
627        UserState userState = getUserStateLocked(userId);
628        SessionState sessionState = userState.sessionStateMap.remove(sessionToken);
629
630        // Close the open log entry, if any.
631        if (sessionState.mLogUri != null) {
632            SomeArgs args = SomeArgs.obtain();
633            args.arg1 = sessionState.mLogUri;
634            args.arg2 = System.currentTimeMillis();
635            mLogHandler.obtainMessage(LogHandler.MSG_CLOSE_ENTRY, args).sendToTarget();
636        }
637
638        // Also remove the session token from the session token list of the current client and
639        // service.
640        ClientState clientState = userState.clientStateMap.get(sessionState.mClient.asBinder());
641        if (clientState != null) {
642            clientState.mSessionTokens.remove(sessionToken);
643            if (clientState.isEmpty()) {
644                userState.clientStateMap.remove(sessionState.mClient.asBinder());
645            }
646        }
647
648        TvInputInfo info = sessionState.mInfo;
649        if (info != null) {
650            ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
651            if (serviceState != null) {
652                serviceState.mSessionTokens.remove(sessionToken);
653            }
654        }
655        updateServiceConnectionLocked(sessionState.mInfo.getComponent(), userId);
656    }
657
658    private void unregisterClientInternalLocked(IBinder clientToken, String inputId,
659            int userId) {
660        UserState userState = getUserStateLocked(userId);
661        ClientState clientState = userState.clientStateMap.get(clientToken);
662        if (clientState != null) {
663            clientState.mInputIds.remove(inputId);
664            if (clientState.isEmpty()) {
665                userState.clientStateMap.remove(clientToken);
666            }
667        }
668
669        TvInputInfo info = userState.inputMap.get(inputId).mInfo;
670        if (info == null) {
671            return;
672        }
673        ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
674        if (serviceState == null) {
675            return;
676        }
677
678        // Remove this client from the client list and unregister the callback.
679        serviceState.mClientTokens.remove(clientToken);
680        if (!serviceState.mClientTokens.isEmpty()) {
681            // We have other clients who want to keep the callback. Do this later.
682            return;
683        }
684        if (serviceState.mService == null || serviceState.mCallback == null) {
685            return;
686        }
687        try {
688            serviceState.mService.unregisterCallback(serviceState.mCallback);
689        } catch (RemoteException e) {
690            Slog.e(TAG, "error in unregisterCallback", e);
691        } finally {
692            serviceState.mCallback = null;
693            updateServiceConnectionLocked(info.getComponent(), userId);
694        }
695    }
696
697    private void notifyStateChangedLocked(UserState userState, String inputId,
698            int state, ITvInputManagerCallback targetCallback) {
699        if (DEBUG) {
700            Slog.d(TAG, "notifyStateChangedLocked: inputId = " + inputId
701                    + "; state = " + state);
702        }
703        if (targetCallback == null) {
704            for (ITvInputManagerCallback callback : userState.callbackSet) {
705                try {
706                    callback.onInputStateChanged(inputId, state);
707                } catch (RemoteException e) {
708                    Slog.e(TAG, "Failed to report state change to callback.");
709                }
710            }
711        } else {
712            try {
713                targetCallback.onInputStateChanged(inputId, state);
714            } catch (RemoteException e) {
715                Slog.e(TAG, "Failed to report state change to callback.");
716            }
717        }
718    }
719
720    private void setStateLocked(String inputId, int state, int userId) {
721        UserState userState = getUserStateLocked(userId);
722        TvInputState inputState = userState.inputMap.get(inputId);
723        ServiceState serviceState = userState.serviceStateMap.get(inputId);
724        int oldState = inputState.mState;
725        inputState.mState = state;
726        if (serviceState != null && serviceState.mService == null
727                && shouldMaintainConnection(serviceState)) {
728            // We don't notify state change while reconnecting. It should remain disconnected.
729            return;
730        }
731        if (oldState != state) {
732            notifyStateChangedLocked(userState, inputId, state, null);
733        }
734    }
735
736    private final class BinderService extends ITvInputManager.Stub {
737        @Override
738        public List<TvInputInfo> getTvInputList(int userId) {
739            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
740                    Binder.getCallingUid(), userId, "getTvInputList");
741            final long identity = Binder.clearCallingIdentity();
742            try {
743                synchronized (mLock) {
744                    UserState userState = getUserStateLocked(resolvedUserId);
745                    List<TvInputInfo> inputList = new ArrayList<TvInputInfo>();
746                    for (TvInputState state : userState.inputMap.values()) {
747                        inputList.add(state.mInfo);
748                    }
749                    return inputList;
750                }
751            } finally {
752                Binder.restoreCallingIdentity(identity);
753            }
754        }
755
756        @Override
757        public TvInputInfo getTvInputInfo(String inputId, int userId) {
758            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
759                    Binder.getCallingUid(), userId, "getTvInputInfo");
760            final long identity = Binder.clearCallingIdentity();
761            try {
762                synchronized (mLock) {
763                    UserState userState = getUserStateLocked(resolvedUserId);
764                    TvInputState state = userState.inputMap.get(inputId);
765                    return state == null ? null : state.mInfo;
766                }
767            } finally {
768                Binder.restoreCallingIdentity(identity);
769            }
770        }
771
772        @Override
773        public void registerCallback(final ITvInputManagerCallback callback, int userId) {
774            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
775                    Binder.getCallingUid(), userId, "registerCallback");
776            final long identity = Binder.clearCallingIdentity();
777            try {
778                synchronized (mLock) {
779                    UserState userState = getUserStateLocked(resolvedUserId);
780                    userState.callbackSet.add(callback);
781                    for (TvInputState state : userState.inputMap.values()) {
782                        notifyStateChangedLocked(userState, state.mInfo.getId(),
783                                state.mState, callback);
784                    }
785                }
786            } finally {
787                Binder.restoreCallingIdentity(identity);
788            }
789        }
790
791        @Override
792        public void unregisterCallback(ITvInputManagerCallback callback, int userId) {
793            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
794                    Binder.getCallingUid(), userId, "unregisterCallback");
795            final long identity = Binder.clearCallingIdentity();
796            try {
797                synchronized (mLock) {
798                    UserState userState = getUserStateLocked(resolvedUserId);
799                    userState.callbackSet.remove(callback);
800                }
801            } finally {
802                Binder.restoreCallingIdentity(identity);
803            }
804        }
805
806        @Override
807        public void createSession(final ITvInputClient client, final String inputId,
808                int seq, int userId) {
809            final int callingUid = Binder.getCallingUid();
810            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
811                    userId, "createSession");
812            final long identity = Binder.clearCallingIdentity();
813            try {
814                synchronized (mLock) {
815                    UserState userState = getUserStateLocked(resolvedUserId);
816                    TvInputInfo info = userState.inputMap.get(inputId).mInfo;
817                    ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
818                    if (serviceState == null) {
819                        serviceState = new ServiceState(info.getComponent(), resolvedUserId);
820                        userState.serviceStateMap.put(info.getComponent(), serviceState);
821                    }
822                    // Send a null token immediately while reconnecting.
823                    if (serviceState.mReconnecting == true) {
824                        sendSessionTokenToClientLocked(client, inputId, null, null, seq);
825                        return;
826                    }
827
828                    // Create a new session token and a session state.
829                    IBinder sessionToken = new Binder();
830                    SessionState sessionState = new SessionState(sessionToken, info, client,
831                            seq, callingUid, resolvedUserId);
832
833                    // Add them to the global session state map of the current user.
834                    userState.sessionStateMap.put(sessionToken, sessionState);
835
836                    // Also, add them to the session state map of the current service.
837                    serviceState.mSessionTokens.add(sessionToken);
838
839                    if (serviceState.mService != null) {
840                        createSessionInternalLocked(serviceState.mService, sessionToken,
841                                resolvedUserId);
842                    } else {
843                        updateServiceConnectionLocked(info.getComponent(), resolvedUserId);
844                    }
845                }
846            } finally {
847                Binder.restoreCallingIdentity(identity);
848            }
849        }
850
851        @Override
852        public void releaseSession(IBinder sessionToken, int userId) {
853            final int callingUid = Binder.getCallingUid();
854            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
855                    userId, "releaseSession");
856            final long identity = Binder.clearCallingIdentity();
857            try {
858                synchronized (mLock) {
859                    releaseSessionLocked(sessionToken, callingUid, resolvedUserId);
860                }
861            } finally {
862                Binder.restoreCallingIdentity(identity);
863            }
864        }
865
866        @Override
867        public void setSurface(IBinder sessionToken, Surface surface, int userId) {
868            final int callingUid = Binder.getCallingUid();
869            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
870                    userId, "setSurface");
871            final long identity = Binder.clearCallingIdentity();
872            try {
873                synchronized (mLock) {
874                    try {
875                        getSessionLocked(sessionToken, callingUid, resolvedUserId).setSurface(
876                                surface);
877                    } catch (RemoteException e) {
878                        Slog.e(TAG, "error in setSurface", e);
879                    }
880                }
881            } finally {
882                if (surface != null) {
883                    // surface is not used in TvInputManagerService.
884                    surface.release();
885                }
886                Binder.restoreCallingIdentity(identity);
887            }
888        }
889
890        @Override
891        public void setVolume(IBinder sessionToken, float volume, int userId) {
892            final int callingUid = Binder.getCallingUid();
893            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
894                    userId, "setVolume");
895            final long identity = Binder.clearCallingIdentity();
896            try {
897                synchronized (mLock) {
898                    try {
899                        getSessionLocked(sessionToken, callingUid, resolvedUserId).setVolume(
900                                volume);
901                    } catch (RemoteException e) {
902                        Slog.e(TAG, "error in setVolume", e);
903                    }
904                }
905            } finally {
906                Binder.restoreCallingIdentity(identity);
907            }
908        }
909
910        @Override
911        public void tune(IBinder sessionToken, final Uri channelUri, int userId) {
912            final int callingUid = Binder.getCallingUid();
913            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
914                    userId, "tune");
915            final long identity = Binder.clearCallingIdentity();
916            try {
917                synchronized (mLock) {
918                    try {
919                        getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(channelUri);
920
921                        long currentTime = System.currentTimeMillis();
922                        long channelId = ContentUris.parseId(channelUri);
923
924                        // Close the open log entry first, if any.
925                        UserState userState = getUserStateLocked(resolvedUserId);
926                        SessionState sessionState = userState.sessionStateMap.get(sessionToken);
927                        if (sessionState.mLogUri != null) {
928                            SomeArgs args = SomeArgs.obtain();
929                            args.arg1 = sessionState.mLogUri;
930                            args.arg2 = currentTime;
931                            mLogHandler.obtainMessage(LogHandler.MSG_CLOSE_ENTRY, args)
932                                    .sendToTarget();
933                        }
934
935                        // Create a log entry and fill it later.
936                        String packageName = sessionState.mInfo.getServiceInfo().packageName;
937                        ContentValues values = new ContentValues();
938                        values.put(TvContract.WatchedPrograms.COLUMN_PACKAGE_NAME, packageName);
939                        values.put(TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
940                                currentTime);
941                        values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, 0);
942                        values.put(TvContract.WatchedPrograms.COLUMN_CHANNEL_ID, channelId);
943
944                        sessionState.mLogUri = mContentResolver.insert(
945                                TvContract.WatchedPrograms.CONTENT_URI, values);
946                        SomeArgs args = SomeArgs.obtain();
947                        args.arg1 = sessionState.mLogUri;
948                        args.arg2 = ContentUris.parseId(channelUri);
949                        args.arg3 = currentTime;
950                        mLogHandler.obtainMessage(LogHandler.MSG_OPEN_ENTRY, args).sendToTarget();
951                    } catch (RemoteException e) {
952                        Slog.e(TAG, "error in tune", e);
953                        return;
954                    }
955                }
956            } finally {
957                Binder.restoreCallingIdentity(identity);
958            }
959        }
960
961        @Override
962        public void setCaptionEnabled(IBinder sessionToken, boolean enabled, int userId) {
963            final int callingUid = Binder.getCallingUid();
964            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
965                    userId, "setCaptionEnabled");
966            final long identity = Binder.clearCallingIdentity();
967            try {
968                synchronized (mLock) {
969                    try {
970                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
971                                .setCaptionEnabled(enabled);
972                    } catch (RemoteException e) {
973                        Slog.e(TAG, "error in setCaptionEnabled", e);
974                    }
975                }
976            } finally {
977                Binder.restoreCallingIdentity(identity);
978            }
979        }
980
981        @Override
982        public void selectTrack(IBinder sessionToken, TvTrackInfo track, int userId) {
983            final int callingUid = Binder.getCallingUid();
984            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
985                    userId, "selectTrack");
986            final long identity = Binder.clearCallingIdentity();
987            try {
988                synchronized (mLock) {
989                    try {
990                        getSessionLocked(sessionToken, callingUid, resolvedUserId).selectTrack(
991                                track);
992                    } catch (RemoteException e) {
993                        Slog.e(TAG, "error in selectTrack", e);
994                    }
995                }
996            } finally {
997                Binder.restoreCallingIdentity(identity);
998            }
999        }
1000
1001        @Override
1002        public void unselectTrack(IBinder sessionToken, TvTrackInfo track, int userId) {
1003            final int callingUid = Binder.getCallingUid();
1004            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1005                    userId, "unselectTrack");
1006            final long identity = Binder.clearCallingIdentity();
1007            try {
1008                synchronized (mLock) {
1009                    try {
1010                        getSessionLocked(sessionToken, callingUid, resolvedUserId).unselectTrack(
1011                                track);
1012                    } catch (RemoteException e) {
1013                        Slog.e(TAG, "error in unselectTrack", e);
1014                    }
1015                }
1016            } finally {
1017                Binder.restoreCallingIdentity(identity);
1018            }
1019        }
1020
1021        @Override
1022        public void createOverlayView(IBinder sessionToken, IBinder windowToken, Rect frame,
1023                int userId) {
1024            final int callingUid = Binder.getCallingUid();
1025            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1026                    userId, "createOverlayView");
1027            final long identity = Binder.clearCallingIdentity();
1028            try {
1029                synchronized (mLock) {
1030                    try {
1031                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
1032                                .createOverlayView(windowToken, frame);
1033                    } catch (RemoteException e) {
1034                        Slog.e(TAG, "error in createOverlayView", e);
1035                    }
1036                }
1037            } finally {
1038                Binder.restoreCallingIdentity(identity);
1039            }
1040        }
1041
1042        @Override
1043        public void relayoutOverlayView(IBinder sessionToken, Rect frame, int userId) {
1044            final int callingUid = Binder.getCallingUid();
1045            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1046                    userId, "relayoutOverlayView");
1047            final long identity = Binder.clearCallingIdentity();
1048            try {
1049                synchronized (mLock) {
1050                    try {
1051                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
1052                                .relayoutOverlayView(frame);
1053                    } catch (RemoteException e) {
1054                        Slog.e(TAG, "error in relayoutOverlayView", e);
1055                    }
1056                }
1057            } finally {
1058                Binder.restoreCallingIdentity(identity);
1059            }
1060        }
1061
1062        @Override
1063        public void removeOverlayView(IBinder sessionToken, int userId) {
1064            final int callingUid = Binder.getCallingUid();
1065            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1066                    userId, "removeOverlayView");
1067            final long identity = Binder.clearCallingIdentity();
1068            try {
1069                synchronized (mLock) {
1070                    try {
1071                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
1072                                .removeOverlayView();
1073                    } catch (RemoteException e) {
1074                        Slog.e(TAG, "error in removeOverlayView", e);
1075                    }
1076                }
1077            } finally {
1078                Binder.restoreCallingIdentity(identity);
1079            }
1080        }
1081
1082        @Override
1083        public List<TvInputHardwareInfo> getHardwareList() throws RemoteException {
1084            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
1085                    != PackageManager.PERMISSION_GRANTED) {
1086                return null;
1087            }
1088
1089            final long identity = Binder.clearCallingIdentity();
1090            try {
1091                return mTvInputHardwareManager.getHardwareList();
1092            } finally {
1093                Binder.restoreCallingIdentity(identity);
1094            }
1095        }
1096
1097        @Override
1098        public void registerTvInputInfo(TvInputInfo info, int deviceId) {
1099            // TODO: Revisit to sort out deviceId ownership.
1100            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
1101                    != PackageManager.PERMISSION_GRANTED) {
1102                return;
1103            }
1104
1105            final long identity = Binder.clearCallingIdentity();
1106            try {
1107                mTvInputHardwareManager.registerTvInputInfo(info, deviceId);
1108            } finally {
1109                Binder.restoreCallingIdentity(identity);
1110            }
1111        }
1112
1113        @Override
1114        public ITvInputHardware acquireTvInputHardware(int deviceId,
1115                ITvInputHardwareCallback callback, TvInputInfo info, int userId)
1116                throws RemoteException {
1117            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
1118                    != PackageManager.PERMISSION_GRANTED) {
1119                return null;
1120            }
1121
1122            final long identity = Binder.clearCallingIdentity();
1123            final int callingUid = Binder.getCallingUid();
1124            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1125                    userId, "acquireTvInputHardware");
1126            try {
1127                return mTvInputHardwareManager.acquireHardware(
1128                        deviceId, callback, info, callingUid, resolvedUserId);
1129            } finally {
1130                Binder.restoreCallingIdentity(identity);
1131            }
1132        }
1133
1134        @Override
1135        public void releaseTvInputHardware(int deviceId, ITvInputHardware hardware, int userId)
1136                throws RemoteException {
1137            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
1138                    != PackageManager.PERMISSION_GRANTED) {
1139                return;
1140            }
1141
1142            final long identity = Binder.clearCallingIdentity();
1143            final int callingUid = Binder.getCallingUid();
1144            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1145                    userId, "releaseTvInputHardware");
1146            try {
1147                mTvInputHardwareManager.releaseHardware(
1148                        deviceId, hardware, callingUid, resolvedUserId);
1149            } finally {
1150                Binder.restoreCallingIdentity(identity);
1151            }
1152        }
1153
1154        @Override
1155        @SuppressWarnings("resource")
1156        protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
1157            final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
1158            if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
1159                    != PackageManager.PERMISSION_GRANTED) {
1160                pw.println("Permission Denial: can't dump TvInputManager from pid="
1161                        + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
1162                return;
1163            }
1164
1165            synchronized (mLock) {
1166                pw.println("User Ids (Current user: " + mCurrentUserId + "):");
1167                pw.increaseIndent();
1168                for (int i = 0; i < mUserStates.size(); i++) {
1169                    int userId = mUserStates.keyAt(i);
1170                    pw.println(Integer.valueOf(userId));
1171                }
1172                pw.decreaseIndent();
1173
1174                for (int i = 0; i < mUserStates.size(); i++) {
1175                    int userId = mUserStates.keyAt(i);
1176                    UserState userState = getUserStateLocked(userId);
1177                    pw.println("UserState (" + userId + "):");
1178                    pw.increaseIndent();
1179
1180                    pw.println("inputMap: inputId -> TvInputState");
1181                    pw.increaseIndent();
1182                    for (TvInputState state : userState.inputMap.values()) {
1183                        pw.println(state.toString());
1184                    }
1185                    pw.decreaseIndent();
1186
1187                    pw.println("packageSet:");
1188                    pw.increaseIndent();
1189                    for (String packageName : userState.packageSet) {
1190                        pw.println(packageName);
1191                    }
1192                    pw.decreaseIndent();
1193
1194                    pw.println("clientStateMap: ITvInputClient -> ClientState");
1195                    pw.increaseIndent();
1196                    for (Map.Entry<IBinder, ClientState> entry :
1197                            userState.clientStateMap.entrySet()) {
1198                        ClientState client = entry.getValue();
1199                        pw.println(entry.getKey() + ": " + client);
1200
1201                        pw.increaseIndent();
1202
1203                        pw.println("mInputIds:");
1204                        pw.increaseIndent();
1205                        for (String inputId : client.mInputIds) {
1206                            pw.println(inputId);
1207                        }
1208                        pw.decreaseIndent();
1209
1210                        pw.println("mSessionTokens:");
1211                        pw.increaseIndent();
1212                        for (IBinder token : client.mSessionTokens) {
1213                            pw.println("" + token);
1214                        }
1215                        pw.decreaseIndent();
1216
1217                        pw.println("mClientTokens: " + client.mClientToken);
1218                        pw.println("mUserId: " + client.mUserId);
1219
1220                        pw.decreaseIndent();
1221                    }
1222                    pw.decreaseIndent();
1223
1224                    pw.println("serviceStateMap: ComponentName -> ServiceState");
1225                    pw.increaseIndent();
1226                    for (Map.Entry<ComponentName, ServiceState> entry :
1227                            userState.serviceStateMap.entrySet()) {
1228                        ServiceState service = entry.getValue();
1229                        pw.println(entry.getKey() + ": " + service);
1230
1231                        pw.increaseIndent();
1232
1233                        pw.println("mClientTokens:");
1234                        pw.increaseIndent();
1235                        for (IBinder token : service.mClientTokens) {
1236                            pw.println("" + token);
1237                        }
1238                        pw.decreaseIndent();
1239
1240                        pw.println("mSessionTokens:");
1241                        pw.increaseIndent();
1242                        for (IBinder token : service.mSessionTokens) {
1243                            pw.println("" + token);
1244                        }
1245                        pw.decreaseIndent();
1246
1247                        pw.println("mService: " + service.mService);
1248                        pw.println("mCallback: " + service.mCallback);
1249                        pw.println("mBound: " + service.mBound);
1250                        pw.println("mReconnecting: " + service.mReconnecting);
1251
1252                        pw.decreaseIndent();
1253                    }
1254                    pw.decreaseIndent();
1255
1256                    pw.println("sessionStateMap: ITvInputSession -> SessionState");
1257                    pw.increaseIndent();
1258                    for (Map.Entry<IBinder, SessionState> entry :
1259                            userState.sessionStateMap.entrySet()) {
1260                        SessionState session = entry.getValue();
1261                        pw.println(entry.getKey() + ": " + session);
1262
1263                        pw.increaseIndent();
1264                        pw.println("mInfo: " + session.mInfo);
1265                        pw.println("mClient: " + session.mClient);
1266                        pw.println("mSeq: " + session.mSeq);
1267                        pw.println("mCallingUid: " + session.mCallingUid);
1268                        pw.println("mUserId: " + session.mUserId);
1269                        pw.println("mSessionToken: " + session.mSessionToken);
1270                        pw.println("mSession: " + session.mSession);
1271                        pw.println("mLogUri: " + session.mLogUri);
1272                        pw.decreaseIndent();
1273                    }
1274                    pw.decreaseIndent();
1275
1276                    pw.println("callbackSet:");
1277                    pw.increaseIndent();
1278                    for (ITvInputManagerCallback callback : userState.callbackSet) {
1279                        pw.println(callback.toString());
1280                    }
1281                    pw.decreaseIndent();
1282
1283                    pw.decreaseIndent();
1284                }
1285            }
1286        }
1287    }
1288
1289    private static final class TvInputState {
1290        // A TvInputInfo object which represents the TV input.
1291        private TvInputInfo mInfo;
1292
1293        // The state of TV input. Connected by default.
1294        private int mState = INPUT_STATE_CONNECTED;
1295
1296        @Override
1297        public String toString() {
1298            return "mInfo: " + mInfo + "; mState: " + mState;
1299        }
1300    }
1301
1302    private static final class UserState {
1303        // A mapping from the TV input id to its TvInputState.
1304        private Map<String, TvInputState> inputMap = new HashMap<String, TvInputState>();
1305
1306        // A set of all TV input packages.
1307        private final Set<String> packageSet = new HashSet<String>();
1308
1309        // A mapping from the token of a client to its state.
1310        private final Map<IBinder, ClientState> clientStateMap =
1311                new HashMap<IBinder, ClientState>();
1312
1313        // A mapping from the name of a TV input service to its state.
1314        private final Map<ComponentName, ServiceState> serviceStateMap =
1315                new HashMap<ComponentName, ServiceState>();
1316
1317        // A mapping from the token of a TV input session to its state.
1318        private final Map<IBinder, SessionState> sessionStateMap =
1319                new HashMap<IBinder, SessionState>();
1320
1321        // A set of callbacks.
1322        private final Set<ITvInputManagerCallback> callbackSet =
1323                new HashSet<ITvInputManagerCallback>();
1324    }
1325
1326    private final class ClientState implements IBinder.DeathRecipient {
1327        private final List<String> mInputIds = new ArrayList<String>();
1328        private final List<IBinder> mSessionTokens = new ArrayList<IBinder>();
1329
1330        private IBinder mClientToken;
1331        private final int mUserId;
1332
1333        ClientState(IBinder clientToken, int userId) {
1334            mClientToken = clientToken;
1335            mUserId = userId;
1336        }
1337
1338        public boolean isEmpty() {
1339            return mInputIds.isEmpty() && mSessionTokens.isEmpty();
1340        }
1341
1342        @Override
1343        public void binderDied() {
1344            synchronized (mLock) {
1345                UserState userState = getUserStateLocked(mUserId);
1346                // DO NOT remove the client state of clientStateMap in this method. It will be
1347                // removed in releaseSessionLocked() or unregisterClientInternalLocked().
1348                ClientState clientState = userState.clientStateMap.get(mClientToken);
1349                if (clientState != null) {
1350                    while (clientState.mSessionTokens.size() > 0) {
1351                        releaseSessionLocked(
1352                                clientState.mSessionTokens.get(0), Process.SYSTEM_UID, mUserId);
1353                    }
1354                    while (clientState.mInputIds.size() > 0) {
1355                        unregisterClientInternalLocked(
1356                                mClientToken, clientState.mInputIds.get(0), mUserId);
1357                    }
1358                }
1359                mClientToken = null;
1360            }
1361        }
1362    }
1363
1364    private final class ServiceState {
1365        private final List<IBinder> mClientTokens = new ArrayList<IBinder>();
1366        private final List<IBinder> mSessionTokens = new ArrayList<IBinder>();
1367        private final ServiceConnection mConnection;
1368        private final ComponentName mName;
1369        private final boolean mIsHardware;
1370        private final List<TvInputInfo> mInputList = new ArrayList<TvInputInfo>();
1371
1372        private ITvInputService mService;
1373        private ServiceCallback mCallback;
1374        private boolean mBound;
1375        private boolean mReconnecting;
1376
1377        private ServiceState(ComponentName name, int userId) {
1378            mName = name;
1379            mConnection = new InputServiceConnection(name, userId);
1380            mIsHardware = hasHardwarePermission(mContext.getPackageManager(), mName);
1381        }
1382    }
1383
1384    private final class SessionState implements IBinder.DeathRecipient {
1385        private final TvInputInfo mInfo;
1386        private final ITvInputClient mClient;
1387        private final int mSeq;
1388        private final int mCallingUid;
1389        private final int mUserId;
1390        private final IBinder mSessionToken;
1391        private ITvInputSession mSession;
1392        private Uri mLogUri;
1393
1394        private SessionState(IBinder sessionToken, TvInputInfo info, ITvInputClient client, int seq,
1395                int callingUid, int userId) {
1396            mSessionToken = sessionToken;
1397            mInfo = info;
1398            mClient = client;
1399            mSeq = seq;
1400            mCallingUid = callingUid;
1401            mUserId = userId;
1402        }
1403
1404        @Override
1405        public void binderDied() {
1406            synchronized (mLock) {
1407                mSession = null;
1408                if (mClient != null) {
1409                    try {
1410                        mClient.onSessionReleased(mSeq);
1411                    } catch(RemoteException e) {
1412                        Slog.e(TAG, "error in onSessionReleased", e);
1413                    }
1414                }
1415                removeSessionStateLocked(mSessionToken, mUserId);
1416            }
1417        }
1418    }
1419
1420    private final class InputServiceConnection implements ServiceConnection {
1421        private final ComponentName mName;
1422        private final int mUserId;
1423
1424        private InputServiceConnection(ComponentName name, int userId) {
1425            mName = name;
1426            mUserId = userId;
1427        }
1428
1429        @Override
1430        public void onServiceConnected(ComponentName name, IBinder service) {
1431            if (DEBUG) {
1432                Slog.d(TAG, "onServiceConnected(name=" + name + ")");
1433            }
1434            synchronized (mLock) {
1435                List<TvInputHardwareInfo> hardwareInfoList =
1436                        mTvInputHardwareManager.getHardwareList();
1437                UserState userState = getUserStateLocked(mUserId);
1438                ServiceState serviceState = userState.serviceStateMap.get(mName);
1439                serviceState.mService = ITvInputService.Stub.asInterface(service);
1440
1441                // Register a callback, if we need to.
1442                if (serviceState.mIsHardware && serviceState.mCallback == null) {
1443                    serviceState.mCallback = new ServiceCallback(mName, mUserId);
1444                    try {
1445                        serviceState.mService.registerCallback(serviceState.mCallback);
1446                    } catch (RemoteException e) {
1447                        Slog.e(TAG, "error in registerCallback", e);
1448                    }
1449                }
1450
1451                // And create sessions, if any.
1452                for (IBinder sessionToken : serviceState.mSessionTokens) {
1453                    createSessionInternalLocked(serviceState.mService, sessionToken, mUserId);
1454                }
1455
1456                for (TvInputState inputState : userState.inputMap.values()) {
1457                    if (inputState.mInfo.getComponent().equals(name)
1458                            && inputState.mState != INPUT_STATE_DISCONNECTED) {
1459                        notifyStateChangedLocked(userState, inputState.mInfo.getId(),
1460                                inputState.mState, null);
1461                    }
1462                }
1463
1464                if (serviceState.mIsHardware) {
1465                    for (TvInputHardwareInfo hardwareInfo : hardwareInfoList) {
1466                        try {
1467                            serviceState.mService.notifyHardwareAdded(hardwareInfo);
1468                        } catch (RemoteException e) {
1469                            Slog.e(TAG, "error in notifyHardwareAdded", e);
1470                        }
1471                    }
1472
1473                    // TODO: Grab CEC devices and notify them to the service.
1474                }
1475            }
1476        }
1477
1478        @Override
1479        public void onServiceDisconnected(ComponentName name) {
1480            if (DEBUG) {
1481                Slog.d(TAG, "onServiceDisconnected(name=" + name + ")");
1482            }
1483            if (!mName.equals(name)) {
1484                throw new IllegalArgumentException("Mismatched ComponentName: "
1485                        + mName + " (expected), " + name + " (actual).");
1486            }
1487            synchronized (mLock) {
1488                UserState userState = getUserStateLocked(mUserId);
1489                ServiceState serviceState = userState.serviceStateMap.get(mName);
1490                if (serviceState != null) {
1491                    serviceState.mReconnecting = true;
1492                    serviceState.mBound = false;
1493                    serviceState.mService = null;
1494                    serviceState.mCallback = null;
1495
1496                    // Send null tokens for not finishing create session events.
1497                    for (IBinder sessionToken : serviceState.mSessionTokens) {
1498                        SessionState sessionState = userState.sessionStateMap.get(sessionToken);
1499                        if (sessionState.mSession == null) {
1500                            removeSessionStateLocked(sessionToken, sessionState.mUserId);
1501                            sendSessionTokenToClientLocked(sessionState.mClient,
1502                                    sessionState.mInfo.getId(), null, null, sessionState.mSeq);
1503                        }
1504                    }
1505
1506                    for (TvInputState inputState : userState.inputMap.values()) {
1507                        if (inputState.mInfo.getComponent().equals(name)) {
1508                            notifyStateChangedLocked(userState, inputState.mInfo.getId(),
1509                                    INPUT_STATE_DISCONNECTED, null);
1510                        }
1511                    }
1512                    updateServiceConnectionLocked(mName, mUserId);
1513                }
1514            }
1515        }
1516    }
1517
1518    private final class ServiceCallback extends ITvInputServiceCallback.Stub {
1519        private final ComponentName mName;
1520        private final int mUserId;
1521
1522        ServiceCallback(ComponentName name, int userId) {
1523            mName = name;
1524            mUserId = userId;
1525        }
1526
1527        @Override
1528        public void addTvInput(TvInputInfo inputInfo) {
1529            synchronized (mLock) {
1530                if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
1531                        != PackageManager.PERMISSION_GRANTED) {
1532                    Slog.w(TAG, "The caller does not have permission to add a TV input ("
1533                            + inputInfo + ").");
1534                    return;
1535                }
1536
1537                ServiceState serviceState = getServiceStateLocked(mName, mUserId);
1538                serviceState.mInputList.add(inputInfo);
1539                buildTvInputListLocked(mUserId);
1540            }
1541        }
1542
1543        @Override
1544        public void removeTvInput(String inputId) {
1545            synchronized (mLock) {
1546                if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
1547                        != PackageManager.PERMISSION_GRANTED) {
1548                    Slog.w(TAG, "The caller does not have permission to remove a TV input ("
1549                            + inputId + ").");
1550                    return;
1551                }
1552
1553                ServiceState serviceState = getServiceStateLocked(mName, mUserId);
1554                boolean removed = false;
1555                for (Iterator<TvInputInfo> it = serviceState.mInputList.iterator();
1556                        it.hasNext(); ) {
1557                    if (it.next().getId().equals(inputId)) {
1558                        it.remove();
1559                        removed = true;
1560                        break;
1561                    }
1562                }
1563                if (removed) {
1564                    buildTvInputListLocked(mUserId);
1565                } else {
1566                    Slog.e(TAG, "TvInputInfo with inputId=" + inputId + " not found.");
1567                }
1568            }
1569        }
1570    }
1571
1572    private final class LogHandler extends Handler {
1573        private static final int MSG_OPEN_ENTRY = 1;
1574        private static final int MSG_UPDATE_ENTRY = 2;
1575        private static final int MSG_CLOSE_ENTRY = 3;
1576
1577        public LogHandler(Looper looper) {
1578            super(looper);
1579        }
1580
1581        @Override
1582        public void handleMessage(Message msg) {
1583            switch (msg.what) {
1584                case MSG_OPEN_ENTRY: {
1585                    SomeArgs args = (SomeArgs) msg.obj;
1586                    Uri uri = (Uri) args.arg1;
1587                    long channelId = (long) args.arg2;
1588                    long time = (long) args.arg3;
1589                    onOpenEntry(uri, channelId, time);
1590                    args.recycle();
1591                    return;
1592                }
1593                case MSG_UPDATE_ENTRY: {
1594                    SomeArgs args = (SomeArgs) msg.obj;
1595                    Uri uri = (Uri) args.arg1;
1596                    long channelId = (long) args.arg2;
1597                    long time = (long) args.arg3;
1598                    onUpdateEntry(uri, channelId, time);
1599                    args.recycle();
1600                    return;
1601                }
1602                case MSG_CLOSE_ENTRY: {
1603                    SomeArgs args = (SomeArgs) msg.obj;
1604                    Uri uri = (Uri) args.arg1;
1605                    long time = (long) args.arg2;
1606                    onCloseEntry(uri, time);
1607                    args.recycle();
1608                    return;
1609                }
1610                default: {
1611                    Slog.w(TAG, "Unhandled message code: " + msg.what);
1612                    return;
1613                }
1614            }
1615        }
1616
1617        private void onOpenEntry(Uri uri, long channelId, long watchStarttime) {
1618            String[] projection = {
1619                    TvContract.Programs.COLUMN_TITLE,
1620                    TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS,
1621                    TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS,
1622                    TvContract.Programs.COLUMN_SHORT_DESCRIPTION
1623            };
1624            String selection = TvContract.Programs.COLUMN_CHANNEL_ID + "=? AND "
1625                    + TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS + "<=? AND "
1626                    + TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS + ">?";
1627            String[] selectionArgs = {
1628                    String.valueOf(channelId),
1629                    String.valueOf(watchStarttime),
1630                    String.valueOf(watchStarttime)
1631            };
1632            String sortOrder = TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS + " ASC";
1633            Cursor cursor = null;
1634            try {
1635                cursor = mContentResolver.query(TvContract.Programs.CONTENT_URI, projection,
1636                        selection, selectionArgs, sortOrder);
1637                if (cursor != null && cursor.moveToNext()) {
1638                    ContentValues values = new ContentValues();
1639                    values.put(TvContract.WatchedPrograms.COLUMN_TITLE, cursor.getString(0));
1640                    values.put(TvContract.WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS,
1641                            cursor.getLong(1));
1642                    long endTime = cursor.getLong(2);
1643                    values.put(TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS, endTime);
1644                    values.put(TvContract.WatchedPrograms.COLUMN_DESCRIPTION, cursor.getString(3));
1645                    mContentResolver.update(uri, values, null, null);
1646
1647                    // Schedule an update when the current program ends.
1648                    SomeArgs args = SomeArgs.obtain();
1649                    args.arg1 = uri;
1650                    args.arg2 = channelId;
1651                    args.arg3 = endTime;
1652                    Message msg = obtainMessage(LogHandler.MSG_UPDATE_ENTRY, args);
1653                    sendMessageDelayed(msg, endTime - System.currentTimeMillis());
1654                }
1655            } finally {
1656                if (cursor != null) {
1657                    cursor.close();
1658                }
1659            }
1660        }
1661
1662        private void onUpdateEntry(Uri uri, long channelId, long time) {
1663            String[] projection = {
1664                    TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
1665                    TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS,
1666                    TvContract.WatchedPrograms.COLUMN_TITLE,
1667                    TvContract.WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS,
1668                    TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS,
1669                    TvContract.WatchedPrograms.COLUMN_DESCRIPTION
1670            };
1671            Cursor cursor = null;
1672            try {
1673                cursor = mContentResolver.query(uri, projection, null, null, null);
1674                if (cursor != null && cursor.moveToNext()) {
1675                    long watchStartTime = cursor.getLong(0);
1676                    long watchEndTime = cursor.getLong(1);
1677                    String title = cursor.getString(2);
1678                    long startTime = cursor.getLong(3);
1679                    long endTime = cursor.getLong(4);
1680                    String description = cursor.getString(5);
1681
1682                    // Do nothing if the current log entry is already closed.
1683                    if (watchEndTime > 0) {
1684                        return;
1685                    }
1686
1687                    // The current program has just ended. Create a (complete) log entry off the
1688                    // current entry.
1689                    ContentValues values = new ContentValues();
1690                    values.put(TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
1691                            watchStartTime);
1692                    values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, time);
1693                    values.put(TvContract.WatchedPrograms.COLUMN_CHANNEL_ID, channelId);
1694                    values.put(TvContract.WatchedPrograms.COLUMN_TITLE, title);
1695                    values.put(TvContract.WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS, startTime);
1696                    values.put(TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS, endTime);
1697                    values.put(TvContract.WatchedPrograms.COLUMN_DESCRIPTION, description);
1698                    mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
1699                }
1700            } finally {
1701                if (cursor != null) {
1702                    cursor.close();
1703                }
1704            }
1705            // Re-open the current log entry with the next program information.
1706            onOpenEntry(uri, channelId, time);
1707        }
1708
1709        private void onCloseEntry(Uri uri, long watchEndTime) {
1710            ContentValues values = new ContentValues();
1711            values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, watchEndTime);
1712            mContentResolver.update(uri, values, null, null);
1713        }
1714    }
1715
1716    final class HardwareListener implements TvInputHardwareManager.Listener {
1717        @Override
1718        public void onStateChanged(String inputId, int state) {
1719            synchronized (mLock) {
1720                setStateLocked(inputId, state, mCurrentUserId);
1721            }
1722        }
1723
1724        @Override
1725        public void onHardwareDeviceAdded(TvInputHardwareInfo info) {
1726            synchronized (mLock) {
1727                UserState userState = getUserStateLocked(mCurrentUserId);
1728                // Broadcast the event to all hardware inputs.
1729                for (ServiceState serviceState : userState.serviceStateMap.values()) {
1730                    if (!serviceState.mIsHardware || serviceState.mService == null) continue;
1731                    try {
1732                        serviceState.mService.notifyHardwareAdded(info);
1733                    } catch (RemoteException e) {
1734                        Slog.e(TAG, "error in notifyHardwareAdded", e);
1735                    }
1736                }
1737            }
1738        }
1739
1740        @Override
1741        public void onHardwareDeviceRemoved(int deviceId) {
1742            synchronized (mLock) {
1743                UserState userState = getUserStateLocked(mCurrentUserId);
1744                // Broadcast the event to all hardware inputs.
1745                for (ServiceState serviceState : userState.serviceStateMap.values()) {
1746                    if (!serviceState.mIsHardware || serviceState.mService == null) continue;
1747                    try {
1748                        serviceState.mService.notifyHardwareRemoved(deviceId);
1749                    } catch (RemoteException e) {
1750                        Slog.e(TAG, "error in notifyHardwareRemoved", e);
1751                    }
1752                }
1753            }
1754        }
1755
1756        @Override
1757        public void onHdmiCecDeviceAdded(HdmiCecDeviceInfo cecDevice) {
1758            synchronized (mLock) {
1759                // TODO
1760            }
1761        }
1762
1763        @Override
1764        public void onHdmiCecDeviceRemoved(HdmiCecDeviceInfo cecDevice) {
1765            synchronized (mLock) {
1766                // TODO
1767            }
1768        }
1769    }
1770}
1771