TvInputManagerService.java revision 783645e99f909ffc7a2d5d2fca9324cc0e9b7362
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 onVideoAvailable() {
541                synchronized (mLock) {
542                    if (DEBUG) {
543                        Slog.d(TAG, "onVideoAvailable()");
544                    }
545                    if (sessionState.mSession == null || sessionState.mClient == null) {
546                        return;
547                    }
548                    try {
549                        sessionState.mClient.onVideoAvailable(sessionState.mSeq);
550                    } catch (RemoteException e) {
551                        Slog.e(TAG, "error in onVideoAvailable");
552                    }
553                }
554            }
555
556            @Override
557            public void onVideoUnavailable(int reason) {
558                synchronized (mLock) {
559                    if (DEBUG) {
560                        Slog.d(TAG, "onVideoUnavailable(" + reason + ")");
561                    }
562                    if (sessionState.mSession == null || sessionState.mClient == null) {
563                        return;
564                    }
565                    try {
566                        sessionState.mClient.onVideoUnavailable(reason, sessionState.mSeq);
567                    } catch (RemoteException e) {
568                        Slog.e(TAG, "error in onVideoUnavailable");
569                    }
570                }
571            }
572
573            @Override
574            public void onContentAllowed() {
575                synchronized (mLock) {
576                    if (DEBUG) {
577                        Slog.d(TAG, "onContentAllowed()");
578                    }
579                    if (sessionState.mSession == null || sessionState.mClient == null) {
580                        return;
581                    }
582                    try {
583                        sessionState.mClient.onContentAllowed(sessionState.mSeq);
584                    } catch (RemoteException e) {
585                        Slog.e(TAG, "error in onContentAllowed");
586                    }
587                }
588            }
589
590            @Override
591            public void onContentBlocked(String rating) {
592                synchronized (mLock) {
593                    if (DEBUG) {
594                        Slog.d(TAG, "onContentBlocked()");
595                    }
596                    if (sessionState.mSession == null || sessionState.mClient == null) {
597                        return;
598                    }
599                    try {
600                        sessionState.mClient.onContentBlocked(rating, sessionState.mSeq);
601                    } catch (RemoteException e) {
602                        Slog.e(TAG, "error in onContentBlocked");
603                    }
604                }
605            }
606
607            @Override
608            public void onSessionEvent(String eventType, Bundle eventArgs) {
609                synchronized (mLock) {
610                    if (DEBUG) {
611                        Slog.d(TAG, "onEvent(what=" + eventType + ", data=" + eventArgs + ")");
612                    }
613                    if (sessionState.mSession == null || sessionState.mClient == null) {
614                        return;
615                    }
616                    try {
617                        sessionState.mClient.onSessionEvent(eventType, eventArgs,
618                                sessionState.mSeq);
619                    } catch (RemoteException e) {
620                        Slog.e(TAG, "error in onSessionEvent");
621                    }
622                }
623            }
624        };
625
626        // Create a session. When failed, send a null token immediately.
627        try {
628            service.createSession(channels[1], callback, sessionState.mInfo.getId());
629        } catch (RemoteException e) {
630            Slog.e(TAG, "error in createSession", e);
631            removeSessionStateLocked(sessionToken, userId);
632            sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mInfo.getId(), null,
633                    null, sessionState.mSeq);
634        }
635        channels[1].dispose();
636    }
637
638    private void sendSessionTokenToClientLocked(ITvInputClient client, String inputId,
639            IBinder sessionToken, InputChannel channel, int seq) {
640        try {
641            client.onSessionCreated(inputId, sessionToken, channel, seq);
642        } catch (RemoteException exception) {
643            Slog.e(TAG, "error in onSessionCreated", exception);
644        }
645    }
646
647    private void releaseSessionLocked(IBinder sessionToken, int callingUid, int userId) {
648        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
649        if (sessionState.mSession != null) {
650            try {
651                sessionState.mSession.release();
652            } catch (RemoteException e) {
653                Slog.w(TAG, "session is already disapeared", e);
654            }
655            sessionState.mSession = null;
656        }
657        removeSessionStateLocked(sessionToken, userId);
658    }
659
660    private void removeSessionStateLocked(IBinder sessionToken, int userId) {
661        UserState userState = getUserStateLocked(userId);
662        if (sessionToken == userState.mainSessionToken) {
663            userState.mainSessionToken = null;
664        }
665
666        // Remove the session state from the global session state map of the current user.
667        SessionState sessionState = userState.sessionStateMap.remove(sessionToken);
668
669        // Close the open log entry, if any.
670        if (sessionState.mLogUri != null) {
671            SomeArgs args = SomeArgs.obtain();
672            args.arg1 = sessionState.mLogUri;
673            args.arg2 = System.currentTimeMillis();
674            mLogHandler.obtainMessage(LogHandler.MSG_CLOSE_ENTRY, args).sendToTarget();
675        }
676
677        // Also remove the session token from the session token list of the current client and
678        // service.
679        ClientState clientState = userState.clientStateMap.get(sessionState.mClient.asBinder());
680        if (clientState != null) {
681            clientState.mSessionTokens.remove(sessionToken);
682            if (clientState.isEmpty()) {
683                userState.clientStateMap.remove(sessionState.mClient.asBinder());
684            }
685        }
686
687        TvInputInfo info = sessionState.mInfo;
688        if (info != null) {
689            ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
690            if (serviceState != null) {
691                serviceState.mSessionTokens.remove(sessionToken);
692            }
693        }
694        updateServiceConnectionLocked(sessionState.mInfo.getComponent(), userId);
695    }
696
697    private void notifyInputAddedLocked(UserState userState, String inputId) {
698        if (DEBUG) {
699            Slog.d(TAG, "notifyInputAdded: inputId = " + inputId);
700        }
701        for (ITvInputManagerCallback callback : userState.callbackSet) {
702            try {
703                callback.onInputAdded(inputId);
704            } catch (RemoteException e) {
705                Slog.e(TAG, "Failed to report added input to callback.");
706            }
707        }
708    }
709
710    private void notifyInputRemovedLocked(UserState userState, String inputId) {
711        if (DEBUG) {
712            Slog.d(TAG, "notifyInputRemovedLocked: inputId = " + inputId);
713        }
714        for (ITvInputManagerCallback callback : userState.callbackSet) {
715            try {
716                callback.onInputRemoved(inputId);
717            } catch (RemoteException e) {
718                Slog.e(TAG, "Failed to report removed input to callback.");
719            }
720        }
721    }
722
723    private void notifyInputStateChangedLocked(UserState userState, String inputId,
724            int state, ITvInputManagerCallback targetCallback) {
725        if (DEBUG) {
726            Slog.d(TAG, "notifyInputStateChangedLocked: inputId = " + inputId
727                    + "; state = " + state);
728        }
729        if (targetCallback == null) {
730            for (ITvInputManagerCallback callback : userState.callbackSet) {
731                try {
732                    callback.onInputStateChanged(inputId, state);
733                } catch (RemoteException e) {
734                    Slog.e(TAG, "Failed to report state change to callback.");
735                }
736            }
737        } else {
738            try {
739                targetCallback.onInputStateChanged(inputId, state);
740            } catch (RemoteException e) {
741                Slog.e(TAG, "Failed to report state change to callback.");
742            }
743        }
744    }
745
746    private void setStateLocked(String inputId, int state, int userId) {
747        UserState userState = getUserStateLocked(userId);
748        TvInputState inputState = userState.inputMap.get(inputId);
749        ServiceState serviceState = userState.serviceStateMap.get(inputState.mInfo.getComponent());
750        int oldState = inputState.mState;
751        inputState.mState = state;
752        if (serviceState != null && serviceState.mService == null
753                && shouldMaintainConnection(serviceState)) {
754            // We don't notify state change while reconnecting. It should remain disconnected.
755            return;
756        }
757        if (oldState != state) {
758            notifyInputStateChangedLocked(userState, inputId, state, null);
759        }
760    }
761
762    private final class BinderService extends ITvInputManager.Stub {
763        @Override
764        public List<TvInputInfo> getTvInputList(int userId) {
765            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
766                    Binder.getCallingUid(), userId, "getTvInputList");
767            final long identity = Binder.clearCallingIdentity();
768            try {
769                synchronized (mLock) {
770                    UserState userState = getUserStateLocked(resolvedUserId);
771                    List<TvInputInfo> inputList = new ArrayList<TvInputInfo>();
772                    for (TvInputState state : userState.inputMap.values()) {
773                        inputList.add(state.mInfo);
774                    }
775                    return inputList;
776                }
777            } finally {
778                Binder.restoreCallingIdentity(identity);
779            }
780        }
781
782        @Override
783        public TvInputInfo getTvInputInfo(String inputId, int userId) {
784            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
785                    Binder.getCallingUid(), userId, "getTvInputInfo");
786            final long identity = Binder.clearCallingIdentity();
787            try {
788                synchronized (mLock) {
789                    UserState userState = getUserStateLocked(resolvedUserId);
790                    TvInputState state = userState.inputMap.get(inputId);
791                    return state == null ? null : state.mInfo;
792                }
793            } finally {
794                Binder.restoreCallingIdentity(identity);
795            }
796        }
797
798        @Override
799        public void registerCallback(final ITvInputManagerCallback callback, int userId) {
800            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
801                    Binder.getCallingUid(), userId, "registerCallback");
802            final long identity = Binder.clearCallingIdentity();
803            try {
804                synchronized (mLock) {
805                    UserState userState = getUserStateLocked(resolvedUserId);
806                    userState.callbackSet.add(callback);
807                    for (TvInputState state : userState.inputMap.values()) {
808                        notifyInputStateChangedLocked(userState, state.mInfo.getId(),
809                                state.mState, callback);
810                    }
811                }
812            } finally {
813                Binder.restoreCallingIdentity(identity);
814            }
815        }
816
817        @Override
818        public void unregisterCallback(ITvInputManagerCallback callback, int userId) {
819            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
820                    Binder.getCallingUid(), userId, "unregisterCallback");
821            final long identity = Binder.clearCallingIdentity();
822            try {
823                synchronized (mLock) {
824                    UserState userState = getUserStateLocked(resolvedUserId);
825                    userState.callbackSet.remove(callback);
826                }
827            } finally {
828                Binder.restoreCallingIdentity(identity);
829            }
830        }
831
832        @Override
833        public boolean isParentalControlsEnabled(int userId) {
834            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
835                    Binder.getCallingUid(), userId, "isParentalControlsEnabled");
836            final long identity = Binder.clearCallingIdentity();
837            try {
838                synchronized (mLock) {
839                    UserState userState = getUserStateLocked(resolvedUserId);
840                    return userState.persistentDataStore.isParentalControlsEnabled();
841                }
842            } finally {
843                Binder.restoreCallingIdentity(identity);
844            }
845        }
846
847        @Override
848        public void setParentalControlsEnabled(boolean enabled, int userId) {
849            ensureParentalControlsPermission();
850            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
851                    Binder.getCallingUid(), userId, "setParentalControlsEnabled");
852            final long identity = Binder.clearCallingIdentity();
853            try {
854                synchronized (mLock) {
855                    UserState userState = getUserStateLocked(resolvedUserId);
856                    userState.persistentDataStore.setParentalControlsEnabled(enabled);
857                }
858            } finally {
859                Binder.restoreCallingIdentity(identity);
860            }
861        }
862
863        @Override
864        public boolean isRatingBlocked(String rating, int userId) {
865            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
866                    Binder.getCallingUid(), userId, "isRatingBlocked");
867            final long identity = Binder.clearCallingIdentity();
868            try {
869                synchronized (mLock) {
870                    UserState userState = getUserStateLocked(resolvedUserId);
871                    return userState.persistentDataStore.isRatingBlocked(
872                            TvContentRating.unflattenFromString(rating));
873                }
874            } finally {
875                Binder.restoreCallingIdentity(identity);
876            }
877        }
878
879        @Override
880        public List<String> getBlockedRatings(int userId) {
881            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
882                    Binder.getCallingUid(), userId, "getBlockedRatings");
883            final long identity = Binder.clearCallingIdentity();
884            try {
885                synchronized (mLock) {
886                    UserState userState = getUserStateLocked(resolvedUserId);
887                    List<String> ratings = new ArrayList<String>();
888                    for (TvContentRating rating
889                            : userState.persistentDataStore.getBlockedRatings()) {
890                        ratings.add(rating.flattenToString());
891                    }
892                    return ratings;
893                }
894            } finally {
895                Binder.restoreCallingIdentity(identity);
896            }
897        }
898
899        @Override
900        public void addBlockedRating(String rating, int userId) {
901            ensureParentalControlsPermission();
902            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
903                    Binder.getCallingUid(), userId, "addBlockedRating");
904            final long identity = Binder.clearCallingIdentity();
905            try {
906                synchronized (mLock) {
907                    UserState userState = getUserStateLocked(resolvedUserId);
908                    userState.persistentDataStore.addBlockedRating(
909                            TvContentRating.unflattenFromString(rating));
910                }
911            } finally {
912                Binder.restoreCallingIdentity(identity);
913            }
914        }
915
916        @Override
917        public void removeBlockedRating(String rating, int userId) {
918            ensureParentalControlsPermission();
919            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
920                    Binder.getCallingUid(), userId, "removeBlockedRating");
921            final long identity = Binder.clearCallingIdentity();
922            try {
923                synchronized (mLock) {
924                    UserState userState = getUserStateLocked(resolvedUserId);
925                    userState.persistentDataStore.removeBlockedRating(
926                            TvContentRating.unflattenFromString(rating));
927                }
928            } finally {
929                Binder.restoreCallingIdentity(identity);
930            }
931        }
932
933        private void ensureParentalControlsPermission() {
934            if (mContext.checkCallingPermission(
935                    android.Manifest.permission.MODIFY_PARENTAL_CONTROLS)
936                    != PackageManager.PERMISSION_GRANTED) {
937                throw new SecurityException(
938                        "The caller does not have parental controls permission");
939            }
940        }
941
942        @Override
943        public void createSession(final ITvInputClient client, final String inputId,
944                int seq, int userId) {
945            final int callingUid = Binder.getCallingUid();
946            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
947                    userId, "createSession");
948            final long identity = Binder.clearCallingIdentity();
949            try {
950                synchronized (mLock) {
951                    UserState userState = getUserStateLocked(resolvedUserId);
952                    TvInputInfo info = userState.inputMap.get(inputId).mInfo;
953                    ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
954                    if (serviceState == null) {
955                        serviceState = new ServiceState(info.getComponent(), resolvedUserId);
956                        userState.serviceStateMap.put(info.getComponent(), serviceState);
957                    }
958                    // Send a null token immediately while reconnecting.
959                    if (serviceState.mReconnecting == true) {
960                        sendSessionTokenToClientLocked(client, inputId, null, null, seq);
961                        return;
962                    }
963
964                    // Create a new session token and a session state.
965                    IBinder sessionToken = new Binder();
966                    SessionState sessionState = new SessionState(sessionToken, info, client,
967                            seq, callingUid, resolvedUserId);
968
969                    // Add them to the global session state map of the current user.
970                    userState.sessionStateMap.put(sessionToken, sessionState);
971
972                    // Also, add them to the session state map of the current service.
973                    serviceState.mSessionTokens.add(sessionToken);
974
975                    if (serviceState.mService != null) {
976                        createSessionInternalLocked(serviceState.mService, sessionToken,
977                                resolvedUserId);
978                    } else {
979                        updateServiceConnectionLocked(info.getComponent(), resolvedUserId);
980                    }
981                }
982            } finally {
983                Binder.restoreCallingIdentity(identity);
984            }
985        }
986
987        @Override
988        public void releaseSession(IBinder sessionToken, int userId) {
989            final int callingUid = Binder.getCallingUid();
990            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
991                    userId, "releaseSession");
992            final long identity = Binder.clearCallingIdentity();
993            try {
994                synchronized (mLock) {
995                    releaseSessionLocked(sessionToken, callingUid, resolvedUserId);
996                }
997            } finally {
998                Binder.restoreCallingIdentity(identity);
999            }
1000        }
1001
1002        @Override
1003        public void setMainSession(IBinder sessionToken, int userId) {
1004            final int callingUid = Binder.getCallingUid();
1005            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1006                    userId, "setMainSession");
1007            final long identity = Binder.clearCallingIdentity();
1008            try {
1009                synchronized (mLock) {
1010                    UserState userState = getUserStateLocked(resolvedUserId);
1011                    if (sessionToken == userState.mainSessionToken) {
1012                        return;
1013                    }
1014
1015                    SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
1016                            resolvedUserId);
1017                    ServiceState serviceState = getServiceStateLocked(
1018                            sessionState.mInfo.getComponent(), resolvedUserId);
1019                    ITvInputSession session = getSessionLocked(sessionState);
1020
1021                    ServiceState prevMainServiceState = null;
1022                    ITvInputSession prevMainSession = null;
1023                    if (userState.mainSessionToken != null) {
1024                        SessionState prevMainSessionState = getSessionStateLocked(
1025                                userState.mainSessionToken, Process.SYSTEM_UID, resolvedUserId);
1026                        prevMainServiceState = getServiceStateLocked(
1027                                prevMainSessionState.mInfo.getComponent(), resolvedUserId);
1028                        prevMainSession = getSessionLocked(prevMainSessionState);
1029                    }
1030
1031                    userState.mainSessionToken = sessionToken;
1032
1033                    // Inform the new main session first. See {@link TvInputService#onSetMain}.
1034                    if (serviceState.mIsHardware) {
1035                        try {
1036                            session.setMainSession(true);
1037                        } catch (RemoteException e) {
1038                            Slog.e(TAG, "error in setMainSession", e);
1039                        }
1040                    }
1041                    if (prevMainSession != null && prevMainServiceState.mIsHardware) {
1042                        try {
1043                            prevMainSession.setMainSession(false);
1044                        } catch (RemoteException e) {
1045                            Slog.e(TAG, "error in setMainSession", e);
1046                        }
1047                    }
1048                }
1049            } finally {
1050                Binder.restoreCallingIdentity(identity);
1051            }
1052        }
1053
1054        @Override
1055        public void setSurface(IBinder sessionToken, Surface surface, int userId) {
1056            final int callingUid = Binder.getCallingUid();
1057            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1058                    userId, "setSurface");
1059            final long identity = Binder.clearCallingIdentity();
1060            try {
1061                synchronized (mLock) {
1062                    try {
1063                        getSessionLocked(sessionToken, callingUid, resolvedUserId).setSurface(
1064                                surface);
1065                    } catch (RemoteException e) {
1066                        Slog.e(TAG, "error in setSurface", e);
1067                    }
1068                }
1069            } finally {
1070                if (surface != null) {
1071                    // surface is not used in TvInputManagerService.
1072                    surface.release();
1073                }
1074                Binder.restoreCallingIdentity(identity);
1075            }
1076        }
1077
1078        @Override
1079        public void dispatchSurfaceChanged(IBinder sessionToken, int format, int width,
1080                int height, int userId) {
1081            final int callingUid = Binder.getCallingUid();
1082            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1083                    userId, "dispatchSurfaceChanged");
1084            final long identity = Binder.clearCallingIdentity();
1085            try {
1086                synchronized (mLock) {
1087                    try {
1088                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
1089                                .dispatchSurfaceChanged(format, width, height);
1090                    } catch (RemoteException e) {
1091                        Slog.e(TAG, "error in dispatchSurfaceChanged", e);
1092                    }
1093                }
1094            } finally {
1095                Binder.restoreCallingIdentity(identity);
1096            }
1097        }
1098
1099        @Override
1100        public void setVolume(IBinder sessionToken, float volume, int userId) {
1101            final int callingUid = Binder.getCallingUid();
1102            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1103                    userId, "setVolume");
1104            final long identity = Binder.clearCallingIdentity();
1105            try {
1106                synchronized (mLock) {
1107                    try {
1108                        getSessionLocked(sessionToken, callingUid, resolvedUserId).setVolume(
1109                                volume);
1110                    } catch (RemoteException e) {
1111                        Slog.e(TAG, "error in setVolume", e);
1112                    }
1113                }
1114            } finally {
1115                Binder.restoreCallingIdentity(identity);
1116            }
1117        }
1118
1119        @Override
1120        public void tune(IBinder sessionToken, final Uri channelUri, int userId) {
1121            final int callingUid = Binder.getCallingUid();
1122            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1123                    userId, "tune");
1124            final long identity = Binder.clearCallingIdentity();
1125            try {
1126                synchronized (mLock) {
1127                    try {
1128                        getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(channelUri);
1129                        if (TvContract.isChannelUriForPassthroughTvInput(channelUri)) {
1130                            // Do not log the watch history for passthrough inputs.
1131                            return;
1132                        }
1133                        long currentTime = System.currentTimeMillis();
1134                        long channelId = ContentUris.parseId(channelUri);
1135
1136                        // Close the open log entry first, if any.
1137                        UserState userState = getUserStateLocked(resolvedUserId);
1138                        SessionState sessionState = userState.sessionStateMap.get(sessionToken);
1139                        if (sessionState.mLogUri != null) {
1140                            SomeArgs args = SomeArgs.obtain();
1141                            args.arg1 = sessionState.mLogUri;
1142                            args.arg2 = currentTime;
1143                            mLogHandler.obtainMessage(LogHandler.MSG_CLOSE_ENTRY, args)
1144                                    .sendToTarget();
1145                        }
1146
1147                        // Create a log entry and fill it later.
1148                        String packageName = sessionState.mInfo.getServiceInfo().packageName;
1149                        ContentValues values = new ContentValues();
1150                        values.put(TvContract.WatchedPrograms.COLUMN_PACKAGE_NAME, packageName);
1151                        values.put(TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
1152                                currentTime);
1153                        values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, 0);
1154                        values.put(TvContract.WatchedPrograms.COLUMN_CHANNEL_ID, channelId);
1155
1156                        sessionState.mLogUri = mContentResolver.insert(
1157                                TvContract.WatchedPrograms.CONTENT_URI, values);
1158                        SomeArgs args = SomeArgs.obtain();
1159                        args.arg1 = sessionState.mLogUri;
1160                        args.arg2 = ContentUris.parseId(channelUri);
1161                        args.arg3 = currentTime;
1162                        mLogHandler.obtainMessage(LogHandler.MSG_OPEN_ENTRY, args).sendToTarget();
1163                    } catch (RemoteException e) {
1164                        Slog.e(TAG, "error in tune", e);
1165                        return;
1166                    }
1167                }
1168            } finally {
1169                Binder.restoreCallingIdentity(identity);
1170            }
1171        }
1172
1173        @Override
1174        public void requestUnblockContent(
1175                IBinder sessionToken, String unblockedRating, int userId) {
1176            final int callingUid = Binder.getCallingUid();
1177            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1178                    userId, "unblockContent");
1179            final long identity = Binder.clearCallingIdentity();
1180            try {
1181                synchronized (mLock) {
1182                    try {
1183                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
1184                                .requestUnblockContent(unblockedRating);
1185                    } catch (RemoteException e) {
1186                        Slog.e(TAG, "error in unblockContent", e);
1187                    }
1188                }
1189            } finally {
1190                Binder.restoreCallingIdentity(identity);
1191            }
1192        }
1193
1194        @Override
1195        public void setCaptionEnabled(IBinder sessionToken, boolean enabled, int userId) {
1196            final int callingUid = Binder.getCallingUid();
1197            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1198                    userId, "setCaptionEnabled");
1199            final long identity = Binder.clearCallingIdentity();
1200            try {
1201                synchronized (mLock) {
1202                    try {
1203                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
1204                                .setCaptionEnabled(enabled);
1205                    } catch (RemoteException e) {
1206                        Slog.e(TAG, "error in setCaptionEnabled", e);
1207                    }
1208                }
1209            } finally {
1210                Binder.restoreCallingIdentity(identity);
1211            }
1212        }
1213
1214        @Override
1215        public void selectTrack(IBinder sessionToken, TvTrackInfo track, int userId) {
1216            final int callingUid = Binder.getCallingUid();
1217            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1218                    userId, "selectTrack");
1219            final long identity = Binder.clearCallingIdentity();
1220            try {
1221                synchronized (mLock) {
1222                    try {
1223                        getSessionLocked(sessionToken, callingUid, resolvedUserId).selectTrack(
1224                                track);
1225                    } catch (RemoteException e) {
1226                        Slog.e(TAG, "error in selectTrack", e);
1227                    }
1228                }
1229            } finally {
1230                Binder.restoreCallingIdentity(identity);
1231            }
1232        }
1233
1234        @Override
1235        public void unselectTrack(IBinder sessionToken, TvTrackInfo track, int userId) {
1236            final int callingUid = Binder.getCallingUid();
1237            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1238                    userId, "unselectTrack");
1239            final long identity = Binder.clearCallingIdentity();
1240            try {
1241                synchronized (mLock) {
1242                    try {
1243                        getSessionLocked(sessionToken, callingUid, resolvedUserId).unselectTrack(
1244                                track);
1245                    } catch (RemoteException e) {
1246                        Slog.e(TAG, "error in unselectTrack", e);
1247                    }
1248                }
1249            } finally {
1250                Binder.restoreCallingIdentity(identity);
1251            }
1252        }
1253
1254        @Override
1255        public void sendAppPrivateCommand(IBinder sessionToken, String command, Bundle data,
1256                int userId) {
1257            final int callingUid = Binder.getCallingUid();
1258            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1259                    userId, "sendAppPrivateCommand");
1260            final long identity = Binder.clearCallingIdentity();
1261            try {
1262                synchronized (mLock) {
1263                    try {
1264                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
1265                                .appPrivateCommand(command, data);
1266                    } catch (RemoteException e) {
1267                        Slog.e(TAG, "error in sendAppPrivateCommand", e);
1268                    }
1269                }
1270            } finally {
1271                Binder.restoreCallingIdentity(identity);
1272            }
1273        }
1274
1275        @Override
1276        public void createOverlayView(IBinder sessionToken, IBinder windowToken, Rect frame,
1277                int userId) {
1278            final int callingUid = Binder.getCallingUid();
1279            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1280                    userId, "createOverlayView");
1281            final long identity = Binder.clearCallingIdentity();
1282            try {
1283                synchronized (mLock) {
1284                    try {
1285                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
1286                                .createOverlayView(windowToken, frame);
1287                    } catch (RemoteException e) {
1288                        Slog.e(TAG, "error in createOverlayView", e);
1289                    }
1290                }
1291            } finally {
1292                Binder.restoreCallingIdentity(identity);
1293            }
1294        }
1295
1296        @Override
1297        public void relayoutOverlayView(IBinder sessionToken, Rect frame, int userId) {
1298            final int callingUid = Binder.getCallingUid();
1299            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1300                    userId, "relayoutOverlayView");
1301            final long identity = Binder.clearCallingIdentity();
1302            try {
1303                synchronized (mLock) {
1304                    try {
1305                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
1306                                .relayoutOverlayView(frame);
1307                    } catch (RemoteException e) {
1308                        Slog.e(TAG, "error in relayoutOverlayView", e);
1309                    }
1310                }
1311            } finally {
1312                Binder.restoreCallingIdentity(identity);
1313            }
1314        }
1315
1316        @Override
1317        public void removeOverlayView(IBinder sessionToken, int userId) {
1318            final int callingUid = Binder.getCallingUid();
1319            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1320                    userId, "removeOverlayView");
1321            final long identity = Binder.clearCallingIdentity();
1322            try {
1323                synchronized (mLock) {
1324                    try {
1325                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
1326                                .removeOverlayView();
1327                    } catch (RemoteException e) {
1328                        Slog.e(TAG, "error in removeOverlayView", e);
1329                    }
1330                }
1331            } finally {
1332                Binder.restoreCallingIdentity(identity);
1333            }
1334        }
1335
1336        @Override
1337        public List<TvInputHardwareInfo> getHardwareList() throws RemoteException {
1338            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
1339                    != PackageManager.PERMISSION_GRANTED) {
1340                return null;
1341            }
1342
1343            final long identity = Binder.clearCallingIdentity();
1344            try {
1345                return mTvInputHardwareManager.getHardwareList();
1346            } finally {
1347                Binder.restoreCallingIdentity(identity);
1348            }
1349        }
1350
1351        @Override
1352        public ITvInputHardware acquireTvInputHardware(int deviceId,
1353                ITvInputHardwareCallback callback, TvInputInfo info, int userId)
1354                throws RemoteException {
1355            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
1356                    != PackageManager.PERMISSION_GRANTED) {
1357                return null;
1358            }
1359
1360            final long identity = Binder.clearCallingIdentity();
1361            final int callingUid = Binder.getCallingUid();
1362            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1363                    userId, "acquireTvInputHardware");
1364            try {
1365                return mTvInputHardwareManager.acquireHardware(
1366                        deviceId, callback, info, callingUid, resolvedUserId);
1367            } finally {
1368                Binder.restoreCallingIdentity(identity);
1369            }
1370        }
1371
1372        @Override
1373        public void releaseTvInputHardware(int deviceId, ITvInputHardware hardware, int userId)
1374                throws RemoteException {
1375            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
1376                    != PackageManager.PERMISSION_GRANTED) {
1377                return;
1378            }
1379
1380            final long identity = Binder.clearCallingIdentity();
1381            final int callingUid = Binder.getCallingUid();
1382            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1383                    userId, "releaseTvInputHardware");
1384            try {
1385                mTvInputHardwareManager.releaseHardware(
1386                        deviceId, hardware, callingUid, resolvedUserId);
1387            } finally {
1388                Binder.restoreCallingIdentity(identity);
1389            }
1390        }
1391
1392        @Override
1393        public List<TvStreamConfig> getAvailableTvStreamConfigList(String inputId, int userId)
1394                throws RemoteException {
1395            if (mContext.checkCallingPermission(
1396                    android.Manifest.permission.CAPTURE_TV_INPUT)
1397                    != PackageManager.PERMISSION_GRANTED) {
1398                throw new SecurityException("Requires CAPTURE_TV_INPUT permission");
1399            }
1400
1401            final long identity = Binder.clearCallingIdentity();
1402            final int callingUid = Binder.getCallingUid();
1403            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1404                    userId, "getAvailableTvStreamConfigList");
1405            try {
1406                return mTvInputHardwareManager.getAvailableTvStreamConfigList(
1407                        inputId, callingUid, resolvedUserId);
1408            } finally {
1409                Binder.restoreCallingIdentity(identity);
1410            }
1411        }
1412
1413        @Override
1414        public boolean captureFrame(String inputId, Surface surface, TvStreamConfig config,
1415                int userId)
1416                throws RemoteException {
1417            if (mContext.checkCallingPermission(
1418                    android.Manifest.permission.CAPTURE_TV_INPUT)
1419                    != PackageManager.PERMISSION_GRANTED) {
1420                throw new SecurityException("Requires CAPTURE_TV_INPUT permission");
1421            }
1422
1423            final long identity = Binder.clearCallingIdentity();
1424            final int callingUid = Binder.getCallingUid();
1425            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1426                    userId, "captureFrame");
1427            try {
1428                final String wrappedInputId;
1429                synchronized (mLock) {
1430                    UserState userState = getUserStateLocked(resolvedUserId);
1431                    wrappedInputId = userState.wrappedInputMap.get(inputId);
1432                }
1433                return mTvInputHardwareManager.captureFrame(
1434                        (wrappedInputId != null) ? wrappedInputId : inputId,
1435                        surface, config, callingUid, resolvedUserId);
1436            } finally {
1437                Binder.restoreCallingIdentity(identity);
1438            }
1439        }
1440
1441        @Override
1442        @SuppressWarnings("resource")
1443        protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
1444            final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
1445            if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
1446                    != PackageManager.PERMISSION_GRANTED) {
1447                pw.println("Permission Denial: can't dump TvInputManager from pid="
1448                        + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
1449                return;
1450            }
1451
1452            synchronized (mLock) {
1453                pw.println("User Ids (Current user: " + mCurrentUserId + "):");
1454                pw.increaseIndent();
1455                for (int i = 0; i < mUserStates.size(); i++) {
1456                    int userId = mUserStates.keyAt(i);
1457                    pw.println(Integer.valueOf(userId));
1458                }
1459                pw.decreaseIndent();
1460
1461                for (int i = 0; i < mUserStates.size(); i++) {
1462                    int userId = mUserStates.keyAt(i);
1463                    UserState userState = getUserStateLocked(userId);
1464                    pw.println("UserState (" + userId + "):");
1465                    pw.increaseIndent();
1466
1467                    pw.println("inputMap: inputId -> TvInputState");
1468                    pw.increaseIndent();
1469                    for (Map.Entry<String, TvInputState> entry: userState.inputMap.entrySet()) {
1470                        pw.println(entry.getKey() + ": " + entry.getValue());
1471                    }
1472                    pw.decreaseIndent();
1473
1474                    pw.println("packageSet:");
1475                    pw.increaseIndent();
1476                    for (String packageName : userState.packageSet) {
1477                        pw.println(packageName);
1478                    }
1479                    pw.decreaseIndent();
1480
1481                    pw.println("clientStateMap: ITvInputClient -> ClientState");
1482                    pw.increaseIndent();
1483                    for (Map.Entry<IBinder, ClientState> entry :
1484                            userState.clientStateMap.entrySet()) {
1485                        ClientState client = entry.getValue();
1486                        pw.println(entry.getKey() + ": " + client);
1487
1488                        pw.increaseIndent();
1489
1490                        pw.println("mSessionTokens:");
1491                        pw.increaseIndent();
1492                        for (IBinder token : client.mSessionTokens) {
1493                            pw.println("" + token);
1494                        }
1495                        pw.decreaseIndent();
1496
1497                        pw.println("mClientTokens: " + client.mClientToken);
1498                        pw.println("mUserId: " + client.mUserId);
1499
1500                        pw.decreaseIndent();
1501                    }
1502                    pw.decreaseIndent();
1503
1504                    pw.println("serviceStateMap: ComponentName -> ServiceState");
1505                    pw.increaseIndent();
1506                    for (Map.Entry<ComponentName, ServiceState> entry :
1507                            userState.serviceStateMap.entrySet()) {
1508                        ServiceState service = entry.getValue();
1509                        pw.println(entry.getKey() + ": " + service);
1510
1511                        pw.increaseIndent();
1512
1513                        pw.println("mClientTokens:");
1514                        pw.increaseIndent();
1515                        for (IBinder token : service.mClientTokens) {
1516                            pw.println("" + token);
1517                        }
1518                        pw.decreaseIndent();
1519
1520                        pw.println("mSessionTokens:");
1521                        pw.increaseIndent();
1522                        for (IBinder token : service.mSessionTokens) {
1523                            pw.println("" + token);
1524                        }
1525                        pw.decreaseIndent();
1526
1527                        pw.println("mService: " + service.mService);
1528                        pw.println("mCallback: " + service.mCallback);
1529                        pw.println("mBound: " + service.mBound);
1530                        pw.println("mReconnecting: " + service.mReconnecting);
1531
1532                        pw.decreaseIndent();
1533                    }
1534                    pw.decreaseIndent();
1535
1536                    pw.println("sessionStateMap: ITvInputSession -> SessionState");
1537                    pw.increaseIndent();
1538                    for (Map.Entry<IBinder, SessionState> entry :
1539                            userState.sessionStateMap.entrySet()) {
1540                        SessionState session = entry.getValue();
1541                        pw.println(entry.getKey() + ": " + session);
1542
1543                        pw.increaseIndent();
1544                        pw.println("mInfo: " + session.mInfo);
1545                        pw.println("mClient: " + session.mClient);
1546                        pw.println("mSeq: " + session.mSeq);
1547                        pw.println("mCallingUid: " + session.mCallingUid);
1548                        pw.println("mUserId: " + session.mUserId);
1549                        pw.println("mSessionToken: " + session.mSessionToken);
1550                        pw.println("mSession: " + session.mSession);
1551                        pw.println("mLogUri: " + session.mLogUri);
1552                        pw.decreaseIndent();
1553                    }
1554                    pw.decreaseIndent();
1555
1556                    pw.println("callbackSet:");
1557                    pw.increaseIndent();
1558                    for (ITvInputManagerCallback callback : userState.callbackSet) {
1559                        pw.println(callback.toString());
1560                    }
1561                    pw.decreaseIndent();
1562
1563                    pw.decreaseIndent();
1564                }
1565            }
1566        }
1567    }
1568
1569    private static final class TvInputState {
1570        // A TvInputInfo object which represents the TV input.
1571        private TvInputInfo mInfo;
1572
1573        // The state of TV input. Connected by default.
1574        private int mState = INPUT_STATE_CONNECTED;
1575
1576        @Override
1577        public String toString() {
1578            return "mInfo: " + mInfo + "; mState: " + mState;
1579        }
1580    }
1581
1582    private static final class UserState {
1583        // A mapping from the TV input id to its TvInputState.
1584        private Map<String, TvInputState> inputMap = new HashMap<String, TvInputState>();
1585
1586        // A set of all TV input packages.
1587        private final Set<String> packageSet = new HashSet<String>();
1588
1589        // A mapping from the token of a client to its state.
1590        private final Map<IBinder, ClientState> clientStateMap =
1591                new HashMap<IBinder, ClientState>();
1592
1593        // A mapping from the name of a TV input service to its state.
1594        private final Map<ComponentName, ServiceState> serviceStateMap =
1595                new HashMap<ComponentName, ServiceState>();
1596
1597        // A mapping from the token of a TV input session to its state.
1598        private final Map<IBinder, SessionState> sessionStateMap =
1599                new HashMap<IBinder, SessionState>();
1600
1601        // A set of callbacks.
1602        private final Set<ITvInputManagerCallback> callbackSet =
1603                new HashSet<ITvInputManagerCallback>();
1604
1605        // A mapping from the TV input id to wrapped input id.
1606        private final Map<String, String> wrappedInputMap = new HashMap<String, String>();
1607
1608        // The token of a "main" TV input session.
1609        private IBinder mainSessionToken = null;
1610
1611        // Persistent data store for all internal settings maintained by the TV input manager
1612        // service.
1613        private final PersistentDataStore persistentDataStore;
1614
1615        private UserState(Context context, int userId) {
1616            persistentDataStore = new PersistentDataStore(context, userId);
1617        }
1618    }
1619
1620    private final class ClientState implements IBinder.DeathRecipient {
1621        private final List<IBinder> mSessionTokens = new ArrayList<IBinder>();
1622
1623        private IBinder mClientToken;
1624        private final int mUserId;
1625
1626        ClientState(IBinder clientToken, int userId) {
1627            mClientToken = clientToken;
1628            mUserId = userId;
1629        }
1630
1631        public boolean isEmpty() {
1632            return mSessionTokens.isEmpty();
1633        }
1634
1635        @Override
1636        public void binderDied() {
1637            synchronized (mLock) {
1638                UserState userState = getUserStateLocked(mUserId);
1639                // DO NOT remove the client state of clientStateMap in this method. It will be
1640                // removed in releaseSessionLocked().
1641                ClientState clientState = userState.clientStateMap.get(mClientToken);
1642                if (clientState != null) {
1643                    while (clientState.mSessionTokens.size() > 0) {
1644                        releaseSessionLocked(
1645                                clientState.mSessionTokens.get(0), Process.SYSTEM_UID, mUserId);
1646                    }
1647                }
1648                mClientToken = null;
1649            }
1650        }
1651    }
1652
1653    private final class ServiceState {
1654        private final List<IBinder> mClientTokens = new ArrayList<IBinder>();
1655        private final List<IBinder> mSessionTokens = new ArrayList<IBinder>();
1656        private final ServiceConnection mConnection;
1657        private final ComponentName mComponent;
1658        private final boolean mIsHardware;
1659        private final List<TvInputInfo> mInputList = new ArrayList<TvInputInfo>();
1660
1661        private ITvInputService mService;
1662        private ServiceCallback mCallback;
1663        private boolean mBound;
1664        private boolean mReconnecting;
1665
1666        private ServiceState(ComponentName component, int userId) {
1667            mComponent = component;
1668            mConnection = new InputServiceConnection(component, userId);
1669            mIsHardware = hasHardwarePermission(mContext.getPackageManager(), mComponent);
1670        }
1671    }
1672
1673    private final class SessionState implements IBinder.DeathRecipient {
1674        private final TvInputInfo mInfo;
1675        private final ITvInputClient mClient;
1676        private final int mSeq;
1677        private final int mCallingUid;
1678        private final int mUserId;
1679        private final IBinder mSessionToken;
1680        private ITvInputSession mSession;
1681        private Uri mLogUri;
1682
1683        private SessionState(IBinder sessionToken, TvInputInfo info, ITvInputClient client, int seq,
1684                int callingUid, int userId) {
1685            mSessionToken = sessionToken;
1686            mInfo = info;
1687            mClient = client;
1688            mSeq = seq;
1689            mCallingUid = callingUid;
1690            mUserId = userId;
1691        }
1692
1693        @Override
1694        public void binderDied() {
1695            synchronized (mLock) {
1696                mSession = null;
1697                if (mClient != null) {
1698                    try {
1699                        mClient.onSessionReleased(mSeq);
1700                    } catch(RemoteException e) {
1701                        Slog.e(TAG, "error in onSessionReleased", e);
1702                    }
1703                }
1704                removeSessionStateLocked(mSessionToken, mUserId);
1705            }
1706        }
1707    }
1708
1709    private final class InputServiceConnection implements ServiceConnection {
1710        private final ComponentName mComponent;
1711        private final int mUserId;
1712
1713        private InputServiceConnection(ComponentName component, int userId) {
1714            mComponent = component;
1715            mUserId = userId;
1716        }
1717
1718        @Override
1719        public void onServiceConnected(ComponentName component, IBinder service) {
1720            if (DEBUG) {
1721                Slog.d(TAG, "onServiceConnected(component=" + component + ")");
1722            }
1723            synchronized (mLock) {
1724                UserState userState = getUserStateLocked(mUserId);
1725                ServiceState serviceState = userState.serviceStateMap.get(mComponent);
1726                serviceState.mService = ITvInputService.Stub.asInterface(service);
1727
1728                // Register a callback, if we need to.
1729                if (serviceState.mIsHardware && serviceState.mCallback == null) {
1730                    serviceState.mCallback = new ServiceCallback(mComponent, mUserId);
1731                    try {
1732                        serviceState.mService.registerCallback(serviceState.mCallback);
1733                    } catch (RemoteException e) {
1734                        Slog.e(TAG, "error in registerCallback", e);
1735                    }
1736                }
1737
1738                // And create sessions, if any.
1739                for (IBinder sessionToken : serviceState.mSessionTokens) {
1740                    createSessionInternalLocked(serviceState.mService, sessionToken, mUserId);
1741                }
1742
1743                for (TvInputState inputState : userState.inputMap.values()) {
1744                    if (inputState.mInfo.getComponent().equals(component)
1745                            && inputState.mState != INPUT_STATE_DISCONNECTED) {
1746                        notifyInputStateChangedLocked(userState, inputState.mInfo.getId(),
1747                                inputState.mState, null);
1748                    }
1749                }
1750
1751                if (serviceState.mIsHardware) {
1752                    List<TvInputHardwareInfo> hardwareInfoList =
1753                            mTvInputHardwareManager.getHardwareList();
1754                    for (TvInputHardwareInfo hardwareInfo : hardwareInfoList) {
1755                        try {
1756                            serviceState.mService.notifyHardwareAdded(hardwareInfo);
1757                        } catch (RemoteException e) {
1758                            Slog.e(TAG, "error in notifyHardwareAdded", e);
1759                        }
1760                    }
1761
1762                    List<HdmiCecDeviceInfo> cecDeviceInfoList =
1763                            mTvInputHardwareManager.getHdmiCecInputDeviceList();
1764                    for (HdmiCecDeviceInfo cecDeviceInfo : cecDeviceInfoList) {
1765                        try {
1766                            serviceState.mService.notifyHdmiCecDeviceAdded(cecDeviceInfo);
1767                        } catch (RemoteException e) {
1768                            Slog.e(TAG, "error in notifyHdmiCecDeviceAdded", e);
1769                        }
1770                    }
1771                }
1772            }
1773        }
1774
1775        @Override
1776        public void onServiceDisconnected(ComponentName component) {
1777            if (DEBUG) {
1778                Slog.d(TAG, "onServiceDisconnected(component=" + component + ")");
1779            }
1780            if (!mComponent.equals(component)) {
1781                throw new IllegalArgumentException("Mismatched ComponentName: "
1782                        + mComponent + " (expected), " + component + " (actual).");
1783            }
1784            synchronized (mLock) {
1785                UserState userState = getUserStateLocked(mUserId);
1786                ServiceState serviceState = userState.serviceStateMap.get(mComponent);
1787                if (serviceState != null) {
1788                    serviceState.mReconnecting = true;
1789                    serviceState.mBound = false;
1790                    serviceState.mService = null;
1791                    serviceState.mCallback = null;
1792
1793                    // Send null tokens for not finishing create session events.
1794                    for (IBinder sessionToken : serviceState.mSessionTokens) {
1795                        SessionState sessionState = userState.sessionStateMap.get(sessionToken);
1796                        if (sessionState.mSession == null) {
1797                            removeSessionStateLocked(sessionToken, sessionState.mUserId);
1798                            sendSessionTokenToClientLocked(sessionState.mClient,
1799                                    sessionState.mInfo.getId(), null, null, sessionState.mSeq);
1800                        }
1801                    }
1802
1803                    for (TvInputState inputState : userState.inputMap.values()) {
1804                        if (inputState.mInfo.getComponent().equals(component)) {
1805                            String inputId = inputState.mInfo.getId();
1806                            notifyInputStateChangedLocked(userState, inputId,
1807                                    INPUT_STATE_DISCONNECTED, null);
1808                            userState.wrappedInputMap.remove(inputId);
1809                        }
1810                    }
1811                    updateServiceConnectionLocked(mComponent, mUserId);
1812                }
1813            }
1814        }
1815    }
1816
1817    private final class ServiceCallback extends ITvInputServiceCallback.Stub {
1818        private final ComponentName mComponent;
1819        private final int mUserId;
1820
1821        ServiceCallback(ComponentName component, int userId) {
1822            mComponent = component;
1823            mUserId = userId;
1824        }
1825
1826        private void ensureHardwarePermission() {
1827            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
1828                    != PackageManager.PERMISSION_GRANTED) {
1829                throw new SecurityException("The caller does not have hardware permission");
1830            }
1831        }
1832
1833        private void ensureValidInput(TvInputInfo inputInfo) {
1834            if (inputInfo.getId() == null || !mComponent.equals(inputInfo.getComponent())) {
1835                throw new IllegalArgumentException("Invalid TvInputInfo");
1836            }
1837        }
1838
1839        private void addTvInputLocked(TvInputInfo inputInfo) {
1840            ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
1841            serviceState.mInputList.add(inputInfo);
1842            buildTvInputListLocked(mUserId);
1843        }
1844
1845        @Override
1846        public void addHardwareTvInput(int deviceId, TvInputInfo inputInfo) {
1847            ensureHardwarePermission();
1848            ensureValidInput(inputInfo);
1849            synchronized (mLock) {
1850                mTvInputHardwareManager.addHardwareTvInput(deviceId, inputInfo);
1851                addTvInputLocked(inputInfo);
1852            }
1853        }
1854
1855        @Override
1856        public void addHdmiCecTvInput(int logicalAddress, TvInputInfo inputInfo) {
1857            ensureHardwarePermission();
1858            ensureValidInput(inputInfo);
1859            synchronized (mLock) {
1860                mTvInputHardwareManager.addHdmiCecTvInput(logicalAddress, inputInfo);
1861                addTvInputLocked(inputInfo);
1862            }
1863        }
1864
1865        @Override
1866        public void removeTvInput(String inputId) {
1867            ensureHardwarePermission();
1868            synchronized (mLock) {
1869                ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
1870                boolean removed = false;
1871                for (Iterator<TvInputInfo> it = serviceState.mInputList.iterator();
1872                        it.hasNext(); ) {
1873                    if (it.next().getId().equals(inputId)) {
1874                        it.remove();
1875                        removed = true;
1876                        break;
1877                    }
1878                }
1879                if (removed) {
1880                    buildTvInputListLocked(mUserId);
1881                    mTvInputHardwareManager.removeTvInput(inputId);
1882                } else {
1883                    Slog.e(TAG, "TvInputInfo with inputId=" + inputId + " not found.");
1884                }
1885            }
1886        }
1887
1888        @Override
1889        public void setWrappedInputId(String inputId, String wrappedInputId) {
1890            synchronized (mLock) {
1891                if (!hasInputIdLocked(inputId)) {
1892                    return;
1893                }
1894                UserState userState = getUserStateLocked(mUserId);
1895                userState.wrappedInputMap.put(inputId, wrappedInputId);
1896            }
1897        }
1898
1899        private boolean hasInputIdLocked(String inputId) {
1900            ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
1901            for (TvInputInfo inputInfo : serviceState.mInputList) {
1902                if (inputInfo.getId().equals(inputId)) {
1903                    return true;
1904                }
1905            }
1906            return false;
1907        }
1908    }
1909
1910    private final class LogHandler extends Handler {
1911        private static final int MSG_OPEN_ENTRY = 1;
1912        private static final int MSG_UPDATE_ENTRY = 2;
1913        private static final int MSG_CLOSE_ENTRY = 3;
1914
1915        public LogHandler(Looper looper) {
1916            super(looper);
1917        }
1918
1919        @Override
1920        public void handleMessage(Message msg) {
1921            switch (msg.what) {
1922                case MSG_OPEN_ENTRY: {
1923                    SomeArgs args = (SomeArgs) msg.obj;
1924                    Uri uri = (Uri) args.arg1;
1925                    long channelId = (long) args.arg2;
1926                    long time = (long) args.arg3;
1927                    onOpenEntry(uri, channelId, time);
1928                    args.recycle();
1929                    return;
1930                }
1931                case MSG_UPDATE_ENTRY: {
1932                    SomeArgs args = (SomeArgs) msg.obj;
1933                    Uri uri = (Uri) args.arg1;
1934                    long channelId = (long) args.arg2;
1935                    long time = (long) args.arg3;
1936                    onUpdateEntry(uri, channelId, time);
1937                    args.recycle();
1938                    return;
1939                }
1940                case MSG_CLOSE_ENTRY: {
1941                    SomeArgs args = (SomeArgs) msg.obj;
1942                    Uri uri = (Uri) args.arg1;
1943                    long time = (long) args.arg2;
1944                    onCloseEntry(uri, time);
1945                    args.recycle();
1946                    return;
1947                }
1948                default: {
1949                    Slog.w(TAG, "Unhandled message code: " + msg.what);
1950                    return;
1951                }
1952            }
1953        }
1954
1955        private void onOpenEntry(Uri uri, long channelId, long watchStarttime) {
1956            String[] projection = {
1957                    TvContract.Programs.COLUMN_TITLE,
1958                    TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS,
1959                    TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS,
1960                    TvContract.Programs.COLUMN_SHORT_DESCRIPTION
1961            };
1962            String selection = TvContract.Programs.COLUMN_CHANNEL_ID + "=? AND "
1963                    + TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS + "<=? AND "
1964                    + TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS + ">?";
1965            String[] selectionArgs = {
1966                    String.valueOf(channelId),
1967                    String.valueOf(watchStarttime),
1968                    String.valueOf(watchStarttime)
1969            };
1970            String sortOrder = TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS + " ASC";
1971            Cursor cursor = null;
1972            try {
1973                cursor = mContentResolver.query(TvContract.Programs.CONTENT_URI, projection,
1974                        selection, selectionArgs, sortOrder);
1975                if (cursor != null && cursor.moveToNext()) {
1976                    ContentValues values = new ContentValues();
1977                    values.put(TvContract.WatchedPrograms.COLUMN_TITLE, cursor.getString(0));
1978                    values.put(TvContract.WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS,
1979                            cursor.getLong(1));
1980                    long endTime = cursor.getLong(2);
1981                    values.put(TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS, endTime);
1982                    values.put(TvContract.WatchedPrograms.COLUMN_DESCRIPTION, cursor.getString(3));
1983                    mContentResolver.update(uri, values, null, null);
1984
1985                    // Schedule an update when the current program ends.
1986                    SomeArgs args = SomeArgs.obtain();
1987                    args.arg1 = uri;
1988                    args.arg2 = channelId;
1989                    args.arg3 = endTime;
1990                    Message msg = obtainMessage(LogHandler.MSG_UPDATE_ENTRY, args);
1991                    sendMessageDelayed(msg, endTime - System.currentTimeMillis());
1992                }
1993            } finally {
1994                if (cursor != null) {
1995                    cursor.close();
1996                }
1997            }
1998        }
1999
2000        private void onUpdateEntry(Uri uri, long channelId, long time) {
2001            String[] projection = {
2002                    TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
2003                    TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS,
2004                    TvContract.WatchedPrograms.COLUMN_TITLE,
2005                    TvContract.WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS,
2006                    TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS,
2007                    TvContract.WatchedPrograms.COLUMN_DESCRIPTION
2008            };
2009            Cursor cursor = null;
2010            try {
2011                cursor = mContentResolver.query(uri, projection, null, null, null);
2012                if (cursor != null && cursor.moveToNext()) {
2013                    long watchStartTime = cursor.getLong(0);
2014                    long watchEndTime = cursor.getLong(1);
2015                    String title = cursor.getString(2);
2016                    long startTime = cursor.getLong(3);
2017                    long endTime = cursor.getLong(4);
2018                    String description = cursor.getString(5);
2019
2020                    // Do nothing if the current log entry is already closed.
2021                    if (watchEndTime > 0) {
2022                        return;
2023                    }
2024
2025                    // The current program has just ended. Create a (complete) log entry off the
2026                    // current entry.
2027                    ContentValues values = new ContentValues();
2028                    values.put(TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
2029                            watchStartTime);
2030                    values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, time);
2031                    values.put(TvContract.WatchedPrograms.COLUMN_CHANNEL_ID, channelId);
2032                    values.put(TvContract.WatchedPrograms.COLUMN_TITLE, title);
2033                    values.put(TvContract.WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS, startTime);
2034                    values.put(TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS, endTime);
2035                    values.put(TvContract.WatchedPrograms.COLUMN_DESCRIPTION, description);
2036                    mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
2037                }
2038            } finally {
2039                if (cursor != null) {
2040                    cursor.close();
2041                }
2042            }
2043            // Re-open the current log entry with the next program information.
2044            onOpenEntry(uri, channelId, time);
2045        }
2046
2047        private void onCloseEntry(Uri uri, long watchEndTime) {
2048            ContentValues values = new ContentValues();
2049            values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, watchEndTime);
2050            mContentResolver.update(uri, values, null, null);
2051        }
2052    }
2053
2054    final class HardwareListener implements TvInputHardwareManager.Listener {
2055        @Override
2056        public void onStateChanged(String inputId, int state) {
2057            synchronized (mLock) {
2058                setStateLocked(inputId, state, mCurrentUserId);
2059            }
2060        }
2061
2062        @Override
2063        public void onHardwareDeviceAdded(TvInputHardwareInfo info) {
2064            synchronized (mLock) {
2065                UserState userState = getUserStateLocked(mCurrentUserId);
2066                // Broadcast the event to all hardware inputs.
2067                for (ServiceState serviceState : userState.serviceStateMap.values()) {
2068                    if (!serviceState.mIsHardware || serviceState.mService == null) continue;
2069                    try {
2070                        serviceState.mService.notifyHardwareAdded(info);
2071                    } catch (RemoteException e) {
2072                        Slog.e(TAG, "error in notifyHardwareAdded", e);
2073                    }
2074                }
2075            }
2076        }
2077
2078        @Override
2079        public void onHardwareDeviceRemoved(TvInputHardwareInfo info) {
2080            synchronized (mLock) {
2081                UserState userState = getUserStateLocked(mCurrentUserId);
2082                // Broadcast the event to all hardware inputs.
2083                for (ServiceState serviceState : userState.serviceStateMap.values()) {
2084                    if (!serviceState.mIsHardware || serviceState.mService == null) continue;
2085                    try {
2086                        serviceState.mService.notifyHardwareRemoved(info);
2087                    } catch (RemoteException e) {
2088                        Slog.e(TAG, "error in notifyHardwareRemoved", e);
2089                    }
2090                }
2091            }
2092        }
2093
2094        @Override
2095        public void onHdmiCecDeviceAdded(HdmiCecDeviceInfo cecDeviceInfo) {
2096            synchronized (mLock) {
2097                UserState userState = getUserStateLocked(mCurrentUserId);
2098                // Broadcast the event to all hardware inputs.
2099                for (ServiceState serviceState : userState.serviceStateMap.values()) {
2100                    if (!serviceState.mIsHardware || serviceState.mService == null) continue;
2101                    try {
2102                        serviceState.mService.notifyHdmiCecDeviceAdded(cecDeviceInfo);
2103                    } catch (RemoteException e) {
2104                        Slog.e(TAG, "error in notifyHdmiCecDeviceAdded", e);
2105                    }
2106                }
2107            }
2108        }
2109
2110        @Override
2111        public void onHdmiCecDeviceRemoved(HdmiCecDeviceInfo cecDeviceInfo) {
2112            synchronized (mLock) {
2113                UserState userState = getUserStateLocked(mCurrentUserId);
2114                // Broadcast the event to all hardware inputs.
2115                for (ServiceState serviceState : userState.serviceStateMap.values()) {
2116                    if (!serviceState.mIsHardware || serviceState.mService == null) continue;
2117                    try {
2118                        serviceState.mService.notifyHdmiCecDeviceRemoved(cecDeviceInfo);
2119                    } catch (RemoteException e) {
2120                        Slog.e(TAG, "error in notifyHdmiCecDeviceRemoved", e);
2121                    }
2122                }
2123            }
2124        }
2125    }
2126}
2127