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