TvInputManagerService.java revision 187423c0bc4b27479bc8c23bd86969429094b296
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 onSessionEvent(String eventType, Bundle eventArgs) {
557                synchronized (mLock) {
558                    if (DEBUG) {
559                        Slog.d(TAG, "onEvent(what=" + eventType + ", data=" + eventArgs + ")");
560                    }
561                    if (sessionState.mSession == null || sessionState.mClient == null) {
562                        return;
563                    }
564                    try {
565                        sessionState.mClient.onSessionEvent(eventType, eventArgs,
566                                sessionState.mSeq);
567                    } catch (RemoteException e) {
568                        Slog.e(TAG, "error in onSessionEvent");
569                    }
570                }
571            }
572        };
573
574        // Create a session. When failed, send a null token immediately.
575        try {
576            service.createSession(channels[1], callback, sessionState.mInfo.getId());
577        } catch (RemoteException e) {
578            Slog.e(TAG, "error in createSession", e);
579            removeSessionStateLocked(sessionToken, userId);
580            sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mInfo.getId(), null,
581                    null, sessionState.mSeq);
582        }
583        channels[1].dispose();
584    }
585
586    private void sendSessionTokenToClientLocked(ITvInputClient client, String inputId,
587            IBinder sessionToken, InputChannel channel, int seq) {
588        try {
589            client.onSessionCreated(inputId, sessionToken, channel, seq);
590        } catch (RemoteException exception) {
591            Slog.e(TAG, "error in onSessionCreated", exception);
592        }
593    }
594
595    private void releaseSessionLocked(IBinder sessionToken, int callingUid, int userId) {
596        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
597        if (sessionState.mSession != null) {
598            try {
599                sessionState.mSession.release();
600            } catch (RemoteException e) {
601                Slog.w(TAG, "session is already disapeared", e);
602            }
603            sessionState.mSession = null;
604        }
605        removeSessionStateLocked(sessionToken, userId);
606    }
607
608    private void removeSessionStateLocked(IBinder sessionToken, int userId) {
609        // Remove the session state from the global session state map of the current user.
610        UserState userState = getUserStateLocked(userId);
611        SessionState sessionState = userState.sessionStateMap.remove(sessionToken);
612
613        // Close the open log entry, if any.
614        if (sessionState.mLogUri != null) {
615            SomeArgs args = SomeArgs.obtain();
616            args.arg1 = sessionState.mLogUri;
617            args.arg2 = System.currentTimeMillis();
618            mLogHandler.obtainMessage(LogHandler.MSG_CLOSE_ENTRY, args).sendToTarget();
619        }
620
621        // Also remove the session token from the session token list of the current client and
622        // service.
623        ClientState clientState = userState.clientStateMap.get(sessionState.mClient.asBinder());
624        if (clientState != null) {
625            clientState.mSessionTokens.remove(sessionToken);
626            if (clientState.isEmpty()) {
627                userState.clientStateMap.remove(sessionState.mClient.asBinder());
628            }
629        }
630
631        TvInputInfo info = sessionState.mInfo;
632        if (info != null) {
633            ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
634            if (serviceState != null) {
635                serviceState.mSessionTokens.remove(sessionToken);
636            }
637        }
638        updateServiceConnectionLocked(sessionState.mInfo.getComponent(), userId);
639    }
640
641    private void unregisterClientInternalLocked(IBinder clientToken, String inputId,
642            int userId) {
643        UserState userState = getUserStateLocked(userId);
644        ClientState clientState = userState.clientStateMap.get(clientToken);
645        if (clientState != null) {
646            clientState.mInputIds.remove(inputId);
647            if (clientState.isEmpty()) {
648                userState.clientStateMap.remove(clientToken);
649            }
650        }
651
652        TvInputInfo info = userState.inputMap.get(inputId).mInfo;
653        if (info == null) {
654            return;
655        }
656        ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
657        if (serviceState == null) {
658            return;
659        }
660
661        // Remove this client from the client list and unregister the callback.
662        serviceState.mClientTokens.remove(clientToken);
663        if (!serviceState.mClientTokens.isEmpty()) {
664            // We have other clients who want to keep the callback. Do this later.
665            return;
666        }
667        if (serviceState.mService == null || serviceState.mCallback == null) {
668            return;
669        }
670        try {
671            serviceState.mService.unregisterCallback(serviceState.mCallback);
672        } catch (RemoteException e) {
673            Slog.e(TAG, "error in unregisterCallback", e);
674        } finally {
675            serviceState.mCallback = null;
676            updateServiceConnectionLocked(info.getComponent(), userId);
677        }
678    }
679
680    private void notifyStateChangedLocked(UserState userState, String inputId,
681            int state, ITvInputManagerCallback targetCallback) {
682        if (DEBUG) {
683            Slog.d(TAG, "notifyStateChangedLocked: inputId = " + inputId
684                    + "; state = " + state);
685        }
686        if (targetCallback == null) {
687            for (ITvInputManagerCallback callback : userState.callbackSet) {
688                try {
689                    callback.onInputStateChanged(inputId, state);
690                } catch (RemoteException e) {
691                    Slog.e(TAG, "Failed to report state change to callback.");
692                }
693            }
694        } else {
695            try {
696                targetCallback.onInputStateChanged(inputId, state);
697            } catch (RemoteException e) {
698                Slog.e(TAG, "Failed to report state change to callback.");
699            }
700        }
701    }
702
703    private void setStateLocked(String inputId, int state, int userId) {
704        UserState userState = getUserStateLocked(userId);
705        TvInputState inputState = userState.inputMap.get(inputId);
706        ServiceState serviceState = userState.serviceStateMap.get(inputId);
707        int oldState = inputState.mState;
708        inputState.mState = state;
709        if (serviceState != null && serviceState.mService == null
710                && shouldMaintainConnection(serviceState)) {
711            // We don't notify state change while reconnecting. It should remain disconnected.
712            return;
713        }
714        if (oldState != state) {
715            notifyStateChangedLocked(userState, inputId, state, null);
716        }
717    }
718
719    private final class BinderService extends ITvInputManager.Stub {
720        @Override
721        public List<TvInputInfo> getTvInputList(int userId) {
722            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
723                    Binder.getCallingUid(), userId, "getTvInputList");
724            final long identity = Binder.clearCallingIdentity();
725            try {
726                synchronized (mLock) {
727                    UserState userState = getUserStateLocked(resolvedUserId);
728                    List<TvInputInfo> inputList = new ArrayList<TvInputInfo>();
729                    for (TvInputState state : userState.inputMap.values()) {
730                        inputList.add(state.mInfo);
731                    }
732                    return inputList;
733                }
734            } finally {
735                Binder.restoreCallingIdentity(identity);
736            }
737        }
738
739        @Override
740        public void registerCallback(final ITvInputManagerCallback callback, int userId) {
741            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
742                    Binder.getCallingUid(), userId, "registerCallback");
743            final long identity = Binder.clearCallingIdentity();
744            try {
745                synchronized (mLock) {
746                    UserState userState = getUserStateLocked(resolvedUserId);
747                    userState.callbackSet.add(callback);
748                    for (TvInputState state : userState.inputMap.values()) {
749                        notifyStateChangedLocked(userState, state.mInfo.getId(),
750                                state.mState, callback);
751                    }
752                }
753            } finally {
754                Binder.restoreCallingIdentity(identity);
755            }
756        }
757
758        @Override
759        public void unregisterCallback(ITvInputManagerCallback callback, int userId) {
760            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
761                    Binder.getCallingUid(), userId, "unregisterCallback");
762            final long identity = Binder.clearCallingIdentity();
763            try {
764                synchronized (mLock) {
765                    UserState userState = getUserStateLocked(resolvedUserId);
766                    userState.callbackSet.remove(callback);
767                }
768            } finally {
769                Binder.restoreCallingIdentity(identity);
770            }
771        }
772
773        @Override
774        public void createSession(final ITvInputClient client, final String inputId,
775                int seq, int userId) {
776            final int callingUid = Binder.getCallingUid();
777            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
778                    userId, "createSession");
779            final long identity = Binder.clearCallingIdentity();
780            try {
781                synchronized (mLock) {
782                    UserState userState = getUserStateLocked(resolvedUserId);
783                    TvInputInfo info = userState.inputMap.get(inputId).mInfo;
784                    ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
785                    if (serviceState == null) {
786                        serviceState = new ServiceState(info.getComponent(), resolvedUserId);
787                        userState.serviceStateMap.put(info.getComponent(), serviceState);
788                    }
789                    // Send a null token immediately while reconnecting.
790                    if (serviceState.mReconnecting == true) {
791                        sendSessionTokenToClientLocked(client, inputId, null, null, seq);
792                        return;
793                    }
794
795                    // Create a new session token and a session state.
796                    IBinder sessionToken = new Binder();
797                    SessionState sessionState = new SessionState(sessionToken, info, client,
798                            seq, callingUid, resolvedUserId);
799
800                    // Add them to the global session state map of the current user.
801                    userState.sessionStateMap.put(sessionToken, sessionState);
802
803                    // Also, add them to the session state map of the current service.
804                    serviceState.mSessionTokens.add(sessionToken);
805
806                    if (serviceState.mService != null) {
807                        createSessionInternalLocked(serviceState.mService, sessionToken,
808                                resolvedUserId);
809                    } else {
810                        updateServiceConnectionLocked(info.getComponent(), resolvedUserId);
811                    }
812                }
813            } finally {
814                Binder.restoreCallingIdentity(identity);
815            }
816        }
817
818        @Override
819        public void releaseSession(IBinder sessionToken, int userId) {
820            final int callingUid = Binder.getCallingUid();
821            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
822                    userId, "releaseSession");
823            final long identity = Binder.clearCallingIdentity();
824            try {
825                synchronized (mLock) {
826                    releaseSessionLocked(sessionToken, callingUid, resolvedUserId);
827                }
828            } finally {
829                Binder.restoreCallingIdentity(identity);
830            }
831        }
832
833        @Override
834        public void setSurface(IBinder sessionToken, Surface surface, int userId) {
835            final int callingUid = Binder.getCallingUid();
836            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
837                    userId, "setSurface");
838            final long identity = Binder.clearCallingIdentity();
839            try {
840                synchronized (mLock) {
841                    try {
842                        getSessionLocked(sessionToken, callingUid, resolvedUserId).setSurface(
843                                surface);
844                    } catch (RemoteException e) {
845                        Slog.e(TAG, "error in setSurface", e);
846                    }
847                }
848            } finally {
849                if (surface != null) {
850                    // surface is not used in TvInputManagerService.
851                    surface.release();
852                }
853                Binder.restoreCallingIdentity(identity);
854            }
855        }
856
857        @Override
858        public void setVolume(IBinder sessionToken, float volume, int userId) {
859            final int callingUid = Binder.getCallingUid();
860            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
861                    userId, "setVolume");
862            final long identity = Binder.clearCallingIdentity();
863            try {
864                synchronized (mLock) {
865                    try {
866                        getSessionLocked(sessionToken, callingUid, resolvedUserId).setVolume(
867                                volume);
868                    } catch (RemoteException e) {
869                        Slog.e(TAG, "error in setVolume", e);
870                    }
871                }
872            } finally {
873                Binder.restoreCallingIdentity(identity);
874            }
875        }
876
877        @Override
878        public void tune(IBinder sessionToken, final Uri channelUri, int userId) {
879            final int callingUid = Binder.getCallingUid();
880            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
881                    userId, "tune");
882            final long identity = Binder.clearCallingIdentity();
883            try {
884                synchronized (mLock) {
885                    try {
886                        getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(channelUri);
887
888                        long currentTime = System.currentTimeMillis();
889                        long channelId = ContentUris.parseId(channelUri);
890
891                        // Close the open log entry first, if any.
892                        UserState userState = getUserStateLocked(resolvedUserId);
893                        SessionState sessionState = userState.sessionStateMap.get(sessionToken);
894                        if (sessionState.mLogUri != null) {
895                            SomeArgs args = SomeArgs.obtain();
896                            args.arg1 = sessionState.mLogUri;
897                            args.arg2 = currentTime;
898                            mLogHandler.obtainMessage(LogHandler.MSG_CLOSE_ENTRY, args)
899                                    .sendToTarget();
900                        }
901
902                        // Create a log entry and fill it later.
903                        String packageName = sessionState.mInfo.getServiceInfo().packageName;
904                        ContentValues values = new ContentValues();
905                        values.put(TvContract.WatchedPrograms.COLUMN_PACKAGE_NAME, packageName);
906                        values.put(TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
907                                currentTime);
908                        values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, 0);
909                        values.put(TvContract.WatchedPrograms.COLUMN_CHANNEL_ID, channelId);
910
911                        sessionState.mLogUri = mContentResolver.insert(
912                                TvContract.WatchedPrograms.CONTENT_URI, values);
913                        SomeArgs args = SomeArgs.obtain();
914                        args.arg1 = sessionState.mLogUri;
915                        args.arg2 = ContentUris.parseId(channelUri);
916                        args.arg3 = currentTime;
917                        mLogHandler.obtainMessage(LogHandler.MSG_OPEN_ENTRY, args).sendToTarget();
918                    } catch (RemoteException e) {
919                        Slog.e(TAG, "error in tune", e);
920                        return;
921                    }
922                }
923            } finally {
924                Binder.restoreCallingIdentity(identity);
925            }
926        }
927
928        @Override
929        public void setCaptionEnabled(IBinder sessionToken, boolean enabled, int userId) {
930            final int callingUid = Binder.getCallingUid();
931            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
932                    userId, "setCaptionEnabled");
933            final long identity = Binder.clearCallingIdentity();
934            try {
935                synchronized (mLock) {
936                    try {
937                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
938                                .setCaptionEnabled(enabled);
939                    } catch (RemoteException e) {
940                        Slog.e(TAG, "error in setCaptionEnabled", e);
941                    }
942                }
943            } finally {
944                Binder.restoreCallingIdentity(identity);
945            }
946        }
947
948        @Override
949        public void selectTrack(IBinder sessionToken, TvTrackInfo track, int userId) {
950            final int callingUid = Binder.getCallingUid();
951            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
952                    userId, "selectTrack");
953            final long identity = Binder.clearCallingIdentity();
954            try {
955                synchronized (mLock) {
956                    try {
957                        getSessionLocked(sessionToken, callingUid, resolvedUserId).selectTrack(
958                                track);
959                    } catch (RemoteException e) {
960                        Slog.e(TAG, "error in selectTrack", e);
961                    }
962                }
963            } finally {
964                Binder.restoreCallingIdentity(identity);
965            }
966        }
967
968        @Override
969        public void unselectTrack(IBinder sessionToken, TvTrackInfo track, int userId) {
970            final int callingUid = Binder.getCallingUid();
971            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
972                    userId, "unselectTrack");
973            final long identity = Binder.clearCallingIdentity();
974            try {
975                synchronized (mLock) {
976                    try {
977                        getSessionLocked(sessionToken, callingUid, resolvedUserId).unselectTrack(
978                                track);
979                    } catch (RemoteException e) {
980                        Slog.e(TAG, "error in unselectTrack", e);
981                    }
982                }
983            } finally {
984                Binder.restoreCallingIdentity(identity);
985            }
986        }
987
988        @Override
989        public void createOverlayView(IBinder sessionToken, IBinder windowToken, Rect frame,
990                int userId) {
991            final int callingUid = Binder.getCallingUid();
992            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
993                    userId, "createOverlayView");
994            final long identity = Binder.clearCallingIdentity();
995            try {
996                synchronized (mLock) {
997                    try {
998                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
999                                .createOverlayView(windowToken, frame);
1000                    } catch (RemoteException e) {
1001                        Slog.e(TAG, "error in createOverlayView", e);
1002                    }
1003                }
1004            } finally {
1005                Binder.restoreCallingIdentity(identity);
1006            }
1007        }
1008
1009        @Override
1010        public void relayoutOverlayView(IBinder sessionToken, Rect frame, int userId) {
1011            final int callingUid = Binder.getCallingUid();
1012            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1013                    userId, "relayoutOverlayView");
1014            final long identity = Binder.clearCallingIdentity();
1015            try {
1016                synchronized (mLock) {
1017                    try {
1018                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
1019                                .relayoutOverlayView(frame);
1020                    } catch (RemoteException e) {
1021                        Slog.e(TAG, "error in relayoutOverlayView", e);
1022                    }
1023                }
1024            } finally {
1025                Binder.restoreCallingIdentity(identity);
1026            }
1027        }
1028
1029        @Override
1030        public void removeOverlayView(IBinder sessionToken, int userId) {
1031            final int callingUid = Binder.getCallingUid();
1032            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1033                    userId, "removeOverlayView");
1034            final long identity = Binder.clearCallingIdentity();
1035            try {
1036                synchronized (mLock) {
1037                    try {
1038                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
1039                                .removeOverlayView();
1040                    } catch (RemoteException e) {
1041                        Slog.e(TAG, "error in removeOverlayView", e);
1042                    }
1043                }
1044            } finally {
1045                Binder.restoreCallingIdentity(identity);
1046            }
1047        }
1048
1049        @Override
1050        public List<TvInputHardwareInfo> getHardwareList() throws RemoteException {
1051            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
1052                    != PackageManager.PERMISSION_GRANTED) {
1053                return null;
1054            }
1055
1056            final long identity = Binder.clearCallingIdentity();
1057            try {
1058                return mTvInputHardwareManager.getHardwareList();
1059            } finally {
1060                Binder.restoreCallingIdentity(identity);
1061            }
1062        }
1063
1064        @Override
1065        public void registerTvInputInfo(TvInputInfo info, int deviceId) {
1066            // TODO: Revisit to sort out deviceId ownership.
1067            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
1068                    != PackageManager.PERMISSION_GRANTED) {
1069                return;
1070            }
1071
1072            final long identity = Binder.clearCallingIdentity();
1073            try {
1074                mTvInputHardwareManager.registerTvInputInfo(info, deviceId);
1075            } finally {
1076                Binder.restoreCallingIdentity(identity);
1077            }
1078        }
1079
1080        @Override
1081        public ITvInputHardware acquireTvInputHardware(int deviceId,
1082                ITvInputHardwareCallback callback, TvInputInfo info, int userId)
1083                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            final int callingUid = Binder.getCallingUid();
1091            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1092                    userId, "acquireTvInputHardware");
1093            try {
1094                return mTvInputHardwareManager.acquireHardware(
1095                        deviceId, callback, info, callingUid, resolvedUserId);
1096            } finally {
1097                Binder.restoreCallingIdentity(identity);
1098            }
1099        }
1100
1101        @Override
1102        public void releaseTvInputHardware(int deviceId, ITvInputHardware hardware, int userId)
1103                throws RemoteException {
1104            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
1105                    != PackageManager.PERMISSION_GRANTED) {
1106                return;
1107            }
1108
1109            final long identity = Binder.clearCallingIdentity();
1110            final int callingUid = Binder.getCallingUid();
1111            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1112                    userId, "releaseTvInputHardware");
1113            try {
1114                mTvInputHardwareManager.releaseHardware(
1115                        deviceId, hardware, callingUid, resolvedUserId);
1116            } finally {
1117                Binder.restoreCallingIdentity(identity);
1118            }
1119        }
1120
1121        @Override
1122        @SuppressWarnings("resource")
1123        protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
1124            final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
1125            if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
1126                    != PackageManager.PERMISSION_GRANTED) {
1127                pw.println("Permission Denial: can't dump TvInputManager from pid="
1128                        + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
1129                return;
1130            }
1131
1132            synchronized (mLock) {
1133                pw.println("User Ids (Current user: " + mCurrentUserId + "):");
1134                pw.increaseIndent();
1135                for (int i = 0; i < mUserStates.size(); i++) {
1136                    int userId = mUserStates.keyAt(i);
1137                    pw.println(Integer.valueOf(userId));
1138                }
1139                pw.decreaseIndent();
1140
1141                for (int i = 0; i < mUserStates.size(); i++) {
1142                    int userId = mUserStates.keyAt(i);
1143                    UserState userState = getUserStateLocked(userId);
1144                    pw.println("UserState (" + userId + "):");
1145                    pw.increaseIndent();
1146
1147                    pw.println("inputMap: inputId -> TvInputState");
1148                    pw.increaseIndent();
1149                    for (TvInputState state : userState.inputMap.values()) {
1150                        pw.println(state.toString());
1151                    }
1152                    pw.decreaseIndent();
1153
1154                    pw.println("packageSet:");
1155                    pw.increaseIndent();
1156                    for (String packageName : userState.packageSet) {
1157                        pw.println(packageName);
1158                    }
1159                    pw.decreaseIndent();
1160
1161                    pw.println("clientStateMap: ITvInputClient -> ClientState");
1162                    pw.increaseIndent();
1163                    for (Map.Entry<IBinder, ClientState> entry :
1164                            userState.clientStateMap.entrySet()) {
1165                        ClientState client = entry.getValue();
1166                        pw.println(entry.getKey() + ": " + client);
1167
1168                        pw.increaseIndent();
1169
1170                        pw.println("mInputIds:");
1171                        pw.increaseIndent();
1172                        for (String inputId : client.mInputIds) {
1173                            pw.println(inputId);
1174                        }
1175                        pw.decreaseIndent();
1176
1177                        pw.println("mSessionTokens:");
1178                        pw.increaseIndent();
1179                        for (IBinder token : client.mSessionTokens) {
1180                            pw.println("" + token);
1181                        }
1182                        pw.decreaseIndent();
1183
1184                        pw.println("mClientTokens: " + client.mClientToken);
1185                        pw.println("mUserId: " + client.mUserId);
1186
1187                        pw.decreaseIndent();
1188                    }
1189                    pw.decreaseIndent();
1190
1191                    pw.println("serviceStateMap: ComponentName -> ServiceState");
1192                    pw.increaseIndent();
1193                    for (Map.Entry<ComponentName, ServiceState> entry :
1194                            userState.serviceStateMap.entrySet()) {
1195                        ServiceState service = entry.getValue();
1196                        pw.println(entry.getKey() + ": " + service);
1197
1198                        pw.increaseIndent();
1199
1200                        pw.println("mClientTokens:");
1201                        pw.increaseIndent();
1202                        for (IBinder token : service.mClientTokens) {
1203                            pw.println("" + token);
1204                        }
1205                        pw.decreaseIndent();
1206
1207                        pw.println("mSessionTokens:");
1208                        pw.increaseIndent();
1209                        for (IBinder token : service.mSessionTokens) {
1210                            pw.println("" + token);
1211                        }
1212                        pw.decreaseIndent();
1213
1214                        pw.println("mService: " + service.mService);
1215                        pw.println("mCallback: " + service.mCallback);
1216                        pw.println("mBound: " + service.mBound);
1217                        pw.println("mReconnecting: " + service.mReconnecting);
1218
1219                        pw.decreaseIndent();
1220                    }
1221                    pw.decreaseIndent();
1222
1223                    pw.println("sessionStateMap: ITvInputSession -> SessionState");
1224                    pw.increaseIndent();
1225                    for (Map.Entry<IBinder, SessionState> entry :
1226                            userState.sessionStateMap.entrySet()) {
1227                        SessionState session = entry.getValue();
1228                        pw.println(entry.getKey() + ": " + session);
1229
1230                        pw.increaseIndent();
1231                        pw.println("mInfo: " + session.mInfo);
1232                        pw.println("mClient: " + session.mClient);
1233                        pw.println("mSeq: " + session.mSeq);
1234                        pw.println("mCallingUid: " + session.mCallingUid);
1235                        pw.println("mUserId: " + session.mUserId);
1236                        pw.println("mSessionToken: " + session.mSessionToken);
1237                        pw.println("mSession: " + session.mSession);
1238                        pw.println("mLogUri: " + session.mLogUri);
1239                        pw.decreaseIndent();
1240                    }
1241                    pw.decreaseIndent();
1242
1243                    pw.println("callbackSet:");
1244                    pw.increaseIndent();
1245                    for (ITvInputManagerCallback callback : userState.callbackSet) {
1246                        pw.println(callback.toString());
1247                    }
1248                    pw.decreaseIndent();
1249
1250                    pw.decreaseIndent();
1251                }
1252            }
1253        }
1254    }
1255
1256    private static final class TvInputState {
1257        // A TvInputInfo object which represents the TV input.
1258        private TvInputInfo mInfo;
1259
1260        // The state of TV input. Connected by default.
1261        private int mState = INPUT_STATE_CONNECTED;
1262
1263        @Override
1264        public String toString() {
1265            return "mInfo: " + mInfo + "; mState: " + mState;
1266        }
1267    }
1268
1269    private static final class UserState {
1270        // A mapping from the TV input id to its TvInputState.
1271        private Map<String, TvInputState> inputMap = new HashMap<String, TvInputState>();
1272
1273        // A set of all TV input packages.
1274        private final Set<String> packageSet = new HashSet<String>();
1275
1276        // A mapping from the token of a client to its state.
1277        private final Map<IBinder, ClientState> clientStateMap =
1278                new HashMap<IBinder, ClientState>();
1279
1280        // A mapping from the name of a TV input service to its state.
1281        private final Map<ComponentName, ServiceState> serviceStateMap =
1282                new HashMap<ComponentName, ServiceState>();
1283
1284        // A mapping from the token of a TV input session to its state.
1285        private final Map<IBinder, SessionState> sessionStateMap =
1286                new HashMap<IBinder, SessionState>();
1287
1288        // A set of callbacks.
1289        private final Set<ITvInputManagerCallback> callbackSet =
1290                new HashSet<ITvInputManagerCallback>();
1291    }
1292
1293    private final class ClientState implements IBinder.DeathRecipient {
1294        private final List<String> mInputIds = new ArrayList<String>();
1295        private final List<IBinder> mSessionTokens = new ArrayList<IBinder>();
1296
1297        private IBinder mClientToken;
1298        private final int mUserId;
1299
1300        ClientState(IBinder clientToken, int userId) {
1301            mClientToken = clientToken;
1302            mUserId = userId;
1303        }
1304
1305        public boolean isEmpty() {
1306            return mInputIds.isEmpty() && mSessionTokens.isEmpty();
1307        }
1308
1309        @Override
1310        public void binderDied() {
1311            synchronized (mLock) {
1312                UserState userState = getUserStateLocked(mUserId);
1313                // DO NOT remove the client state of clientStateMap in this method. It will be
1314                // removed in releaseSessionLocked() or unregisterClientInternalLocked().
1315                ClientState clientState = userState.clientStateMap.get(mClientToken);
1316                if (clientState != null) {
1317                    while (clientState.mSessionTokens.size() > 0) {
1318                        releaseSessionLocked(
1319                                clientState.mSessionTokens.get(0), Process.SYSTEM_UID, mUserId);
1320                    }
1321                    while (clientState.mInputIds.size() > 0) {
1322                        unregisterClientInternalLocked(
1323                                mClientToken, clientState.mInputIds.get(0), mUserId);
1324                    }
1325                }
1326                mClientToken = null;
1327            }
1328        }
1329    }
1330
1331    private final class ServiceState {
1332        private final List<IBinder> mClientTokens = new ArrayList<IBinder>();
1333        private final List<IBinder> mSessionTokens = new ArrayList<IBinder>();
1334        private final ServiceConnection mConnection;
1335        private final ComponentName mName;
1336        private final boolean mIsHardware;
1337        private final List<TvInputInfo> mInputList = new ArrayList<TvInputInfo>();
1338
1339        private ITvInputService mService;
1340        private ServiceCallback mCallback;
1341        private boolean mBound;
1342        private boolean mReconnecting;
1343
1344        private ServiceState(ComponentName name, int userId) {
1345            mName = name;
1346            mConnection = new InputServiceConnection(name, userId);
1347            mIsHardware = hasHardwarePermission(mContext.getPackageManager(), mName);
1348        }
1349    }
1350
1351    private final class SessionState implements IBinder.DeathRecipient {
1352        private final TvInputInfo mInfo;
1353        private final ITvInputClient mClient;
1354        private final int mSeq;
1355        private final int mCallingUid;
1356        private final int mUserId;
1357        private final IBinder mSessionToken;
1358        private ITvInputSession mSession;
1359        private Uri mLogUri;
1360
1361        private SessionState(IBinder sessionToken, TvInputInfo info, ITvInputClient client, int seq,
1362                int callingUid, int userId) {
1363            mSessionToken = sessionToken;
1364            mInfo = info;
1365            mClient = client;
1366            mSeq = seq;
1367            mCallingUid = callingUid;
1368            mUserId = userId;
1369        }
1370
1371        @Override
1372        public void binderDied() {
1373            synchronized (mLock) {
1374                mSession = null;
1375                if (mClient != null) {
1376                    try {
1377                        mClient.onSessionReleased(mSeq);
1378                    } catch(RemoteException e) {
1379                        Slog.e(TAG, "error in onSessionReleased", e);
1380                    }
1381                }
1382                removeSessionStateLocked(mSessionToken, mUserId);
1383            }
1384        }
1385    }
1386
1387    private final class InputServiceConnection implements ServiceConnection {
1388        private final ComponentName mName;
1389        private final int mUserId;
1390
1391        private InputServiceConnection(ComponentName name, int userId) {
1392            mName = name;
1393            mUserId = userId;
1394        }
1395
1396        @Override
1397        public void onServiceConnected(ComponentName name, IBinder service) {
1398            if (DEBUG) {
1399                Slog.d(TAG, "onServiceConnected(name=" + name + ")");
1400            }
1401            synchronized (mLock) {
1402                List<TvInputHardwareInfo> hardwareInfoList =
1403                        mTvInputHardwareManager.getHardwareList();
1404                UserState userState = getUserStateLocked(mUserId);
1405                ServiceState serviceState = userState.serviceStateMap.get(mName);
1406                serviceState.mService = ITvInputService.Stub.asInterface(service);
1407
1408                // Register a callback, if we need to.
1409                if (serviceState.mIsHardware && serviceState.mCallback == null) {
1410                    serviceState.mCallback = new ServiceCallback(mName, mUserId);
1411                    try {
1412                        serviceState.mService.registerCallback(serviceState.mCallback);
1413                    } catch (RemoteException e) {
1414                        Slog.e(TAG, "error in registerCallback", e);
1415                    }
1416                }
1417
1418                // And create sessions, if any.
1419                for (IBinder sessionToken : serviceState.mSessionTokens) {
1420                    createSessionInternalLocked(serviceState.mService, sessionToken, mUserId);
1421                }
1422
1423                for (TvInputState inputState : userState.inputMap.values()) {
1424                    if (inputState.mInfo.getComponent().equals(name)
1425                            && inputState.mState != INPUT_STATE_DISCONNECTED) {
1426                        notifyStateChangedLocked(userState, inputState.mInfo.getId(),
1427                                inputState.mState, null);
1428                    }
1429                }
1430
1431                if (serviceState.mIsHardware) {
1432                    for (TvInputHardwareInfo hardwareInfo : hardwareInfoList) {
1433                        try {
1434                            serviceState.mService.notifyHardwareAdded(hardwareInfo);
1435                        } catch (RemoteException e) {
1436                            Slog.e(TAG, "error in notifyHardwareAdded", e);
1437                        }
1438                    }
1439
1440                    // TODO: Grab CEC devices and notify them to the service.
1441                }
1442            }
1443        }
1444
1445        @Override
1446        public void onServiceDisconnected(ComponentName name) {
1447            if (DEBUG) {
1448                Slog.d(TAG, "onServiceDisconnected(name=" + name + ")");
1449            }
1450            if (!mName.equals(name)) {
1451                throw new IllegalArgumentException("Mismatched ComponentName: "
1452                        + mName + " (expected), " + name + " (actual).");
1453            }
1454            synchronized (mLock) {
1455                UserState userState = getUserStateLocked(mUserId);
1456                ServiceState serviceState = userState.serviceStateMap.get(mName);
1457                if (serviceState != null) {
1458                    serviceState.mReconnecting = true;
1459                    serviceState.mBound = false;
1460                    serviceState.mService = null;
1461                    serviceState.mCallback = null;
1462
1463                    // Send null tokens for not finishing create session events.
1464                    for (IBinder sessionToken : serviceState.mSessionTokens) {
1465                        SessionState sessionState = userState.sessionStateMap.get(sessionToken);
1466                        if (sessionState.mSession == null) {
1467                            removeSessionStateLocked(sessionToken, sessionState.mUserId);
1468                            sendSessionTokenToClientLocked(sessionState.mClient,
1469                                    sessionState.mInfo.getId(), null, null, sessionState.mSeq);
1470                        }
1471                    }
1472
1473                    for (TvInputState inputState : userState.inputMap.values()) {
1474                        if (inputState.mInfo.getComponent().equals(name)) {
1475                            notifyStateChangedLocked(userState, inputState.mInfo.getId(),
1476                                    INPUT_STATE_DISCONNECTED, null);
1477                        }
1478                    }
1479                    updateServiceConnectionLocked(mName, mUserId);
1480                }
1481            }
1482        }
1483    }
1484
1485    private final class ServiceCallback extends ITvInputServiceCallback.Stub {
1486        private final ComponentName mName;
1487        private final int mUserId;
1488
1489        ServiceCallback(ComponentName name, int userId) {
1490            mName = name;
1491            mUserId = userId;
1492        }
1493
1494        @Override
1495        public void addTvInput(TvInputInfo inputInfo) {
1496            synchronized (mLock) {
1497                if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
1498                        != PackageManager.PERMISSION_GRANTED) {
1499                    Slog.w(TAG, "The caller does not have permission to add a TV input ("
1500                            + inputInfo + ").");
1501                    return;
1502                }
1503
1504                ServiceState serviceState = getServiceStateLocked(mName, mUserId);
1505                serviceState.mInputList.add(inputInfo);
1506                buildTvInputListLocked(mUserId);
1507            }
1508        }
1509
1510        @Override
1511        public void removeTvInput(String inputId) {
1512            synchronized (mLock) {
1513                if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
1514                        != PackageManager.PERMISSION_GRANTED) {
1515                    Slog.w(TAG, "The caller does not have permission to remove a TV input ("
1516                            + inputId + ").");
1517                    return;
1518                }
1519
1520                ServiceState serviceState = getServiceStateLocked(mName, mUserId);
1521                boolean removed = false;
1522                for (Iterator<TvInputInfo> it = serviceState.mInputList.iterator();
1523                        it.hasNext(); ) {
1524                    if (it.next().getId().equals(inputId)) {
1525                        it.remove();
1526                        removed = true;
1527                        break;
1528                    }
1529                }
1530                if (removed) {
1531                    buildTvInputListLocked(mUserId);
1532                } else {
1533                    Slog.e(TAG, "TvInputInfo with inputId=" + inputId + " not found.");
1534                }
1535            }
1536        }
1537    }
1538
1539    private final class LogHandler extends Handler {
1540        private static final int MSG_OPEN_ENTRY = 1;
1541        private static final int MSG_UPDATE_ENTRY = 2;
1542        private static final int MSG_CLOSE_ENTRY = 3;
1543
1544        public LogHandler(Looper looper) {
1545            super(looper);
1546        }
1547
1548        @Override
1549        public void handleMessage(Message msg) {
1550            switch (msg.what) {
1551                case MSG_OPEN_ENTRY: {
1552                    SomeArgs args = (SomeArgs) msg.obj;
1553                    Uri uri = (Uri) args.arg1;
1554                    long channelId = (long) args.arg2;
1555                    long time = (long) args.arg3;
1556                    onOpenEntry(uri, channelId, time);
1557                    args.recycle();
1558                    return;
1559                }
1560                case MSG_UPDATE_ENTRY: {
1561                    SomeArgs args = (SomeArgs) msg.obj;
1562                    Uri uri = (Uri) args.arg1;
1563                    long channelId = (long) args.arg2;
1564                    long time = (long) args.arg3;
1565                    onUpdateEntry(uri, channelId, time);
1566                    args.recycle();
1567                    return;
1568                }
1569                case MSG_CLOSE_ENTRY: {
1570                    SomeArgs args = (SomeArgs) msg.obj;
1571                    Uri uri = (Uri) args.arg1;
1572                    long time = (long) args.arg2;
1573                    onCloseEntry(uri, time);
1574                    args.recycle();
1575                    return;
1576                }
1577                default: {
1578                    Slog.w(TAG, "Unhandled message code: " + msg.what);
1579                    return;
1580                }
1581            }
1582        }
1583
1584        private void onOpenEntry(Uri uri, long channelId, long watchStarttime) {
1585            String[] projection = {
1586                    TvContract.Programs.COLUMN_TITLE,
1587                    TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS,
1588                    TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS,
1589                    TvContract.Programs.COLUMN_SHORT_DESCRIPTION
1590            };
1591            String selection = TvContract.Programs.COLUMN_CHANNEL_ID + "=? AND "
1592                    + TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS + "<=? AND "
1593                    + TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS + ">?";
1594            String[] selectionArgs = {
1595                    String.valueOf(channelId),
1596                    String.valueOf(watchStarttime),
1597                    String.valueOf(watchStarttime)
1598            };
1599            String sortOrder = TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS + " ASC";
1600            Cursor cursor = null;
1601            try {
1602                cursor = mContentResolver.query(TvContract.Programs.CONTENT_URI, projection,
1603                        selection, selectionArgs, sortOrder);
1604                if (cursor != null && cursor.moveToNext()) {
1605                    ContentValues values = new ContentValues();
1606                    values.put(TvContract.WatchedPrograms.COLUMN_TITLE, cursor.getString(0));
1607                    values.put(TvContract.WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS,
1608                            cursor.getLong(1));
1609                    long endTime = cursor.getLong(2);
1610                    values.put(TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS, endTime);
1611                    values.put(TvContract.WatchedPrograms.COLUMN_DESCRIPTION, cursor.getString(3));
1612                    mContentResolver.update(uri, values, null, null);
1613
1614                    // Schedule an update when the current program ends.
1615                    SomeArgs args = SomeArgs.obtain();
1616                    args.arg1 = uri;
1617                    args.arg2 = channelId;
1618                    args.arg3 = endTime;
1619                    Message msg = obtainMessage(LogHandler.MSG_UPDATE_ENTRY, args);
1620                    sendMessageDelayed(msg, endTime - System.currentTimeMillis());
1621                }
1622            } finally {
1623                if (cursor != null) {
1624                    cursor.close();
1625                }
1626            }
1627        }
1628
1629        private void onUpdateEntry(Uri uri, long channelId, long time) {
1630            String[] projection = {
1631                    TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
1632                    TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS,
1633                    TvContract.WatchedPrograms.COLUMN_TITLE,
1634                    TvContract.WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS,
1635                    TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS,
1636                    TvContract.WatchedPrograms.COLUMN_DESCRIPTION
1637            };
1638            Cursor cursor = null;
1639            try {
1640                cursor = mContentResolver.query(uri, projection, null, null, null);
1641                if (cursor != null && cursor.moveToNext()) {
1642                    long watchStartTime = cursor.getLong(0);
1643                    long watchEndTime = cursor.getLong(1);
1644                    String title = cursor.getString(2);
1645                    long startTime = cursor.getLong(3);
1646                    long endTime = cursor.getLong(4);
1647                    String description = cursor.getString(5);
1648
1649                    // Do nothing if the current log entry is already closed.
1650                    if (watchEndTime > 0) {
1651                        return;
1652                    }
1653
1654                    // The current program has just ended. Create a (complete) log entry off the
1655                    // current entry.
1656                    ContentValues values = new ContentValues();
1657                    values.put(TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
1658                            watchStartTime);
1659                    values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, time);
1660                    values.put(TvContract.WatchedPrograms.COLUMN_CHANNEL_ID, channelId);
1661                    values.put(TvContract.WatchedPrograms.COLUMN_TITLE, title);
1662                    values.put(TvContract.WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS, startTime);
1663                    values.put(TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS, endTime);
1664                    values.put(TvContract.WatchedPrograms.COLUMN_DESCRIPTION, description);
1665                    mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
1666                }
1667            } finally {
1668                if (cursor != null) {
1669                    cursor.close();
1670                }
1671            }
1672            // Re-open the current log entry with the next program information.
1673            onOpenEntry(uri, channelId, time);
1674        }
1675
1676        private void onCloseEntry(Uri uri, long watchEndTime) {
1677            ContentValues values = new ContentValues();
1678            values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, watchEndTime);
1679            mContentResolver.update(uri, values, null, null);
1680        }
1681    }
1682
1683    final class HardwareListener implements TvInputHardwareManager.Listener {
1684        @Override
1685        public void onStateChanged(String inputId, int state) {
1686            synchronized (mLock) {
1687                setStateLocked(inputId, state, mCurrentUserId);
1688            }
1689        }
1690
1691        @Override
1692        public void onHardwareDeviceAdded(TvInputHardwareInfo info) {
1693            synchronized (mLock) {
1694                UserState userState = getUserStateLocked(mCurrentUserId);
1695                // Broadcast the event to all hardware inputs.
1696                for (ServiceState serviceState : userState.serviceStateMap.values()) {
1697                    if (!serviceState.mIsHardware || serviceState.mService == null) continue;
1698                    try {
1699                        serviceState.mService.notifyHardwareAdded(info);
1700                    } catch (RemoteException e) {
1701                        Slog.e(TAG, "error in notifyHardwareAdded", e);
1702                    }
1703                }
1704            }
1705        }
1706
1707        @Override
1708        public void onHardwareDeviceRemoved(int deviceId) {
1709            synchronized (mLock) {
1710                UserState userState = getUserStateLocked(mCurrentUserId);
1711                // Broadcast the event to all hardware inputs.
1712                for (ServiceState serviceState : userState.serviceStateMap.values()) {
1713                    if (!serviceState.mIsHardware || serviceState.mService == null) continue;
1714                    try {
1715                        serviceState.mService.notifyHardwareRemoved(deviceId);
1716                    } catch (RemoteException e) {
1717                        Slog.e(TAG, "error in notifyHardwareRemoved", e);
1718                    }
1719                }
1720            }
1721        }
1722
1723        @Override
1724        public void onHdmiCecDeviceAdded(HdmiCecDeviceInfo cecDevice) {
1725            synchronized (mLock) {
1726                // TODO
1727            }
1728        }
1729
1730        @Override
1731        public void onHdmiCecDeviceRemoved(HdmiCecDeviceInfo cecDevice) {
1732            synchronized (mLock) {
1733                // TODO
1734            }
1735        }
1736    }
1737}
1738