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