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