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