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