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