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