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