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