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