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