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