TvInputManagerService.java revision 8d4ded0058de5c573ccf79c4596bf5eb1b14fad3
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        if (sessionState == null) {
717            return;
718        }
719
720        // Close the open log entry, if any.
721        if (sessionState.mLogUri != null) {
722            SomeArgs args = SomeArgs.obtain();
723            args.arg1 = sessionState.mLogUri;
724            args.arg2 = System.currentTimeMillis();
725            mLogHandler.obtainMessage(LogHandler.MSG_CLOSE_ENTRY, args).sendToTarget();
726        }
727
728        // Also remove the session token from the session token list of the current client and
729        // service.
730        ClientState clientState = userState.clientStateMap.get(sessionState.mClient.asBinder());
731        if (clientState != null) {
732            clientState.mSessionTokens.remove(sessionToken);
733            if (clientState.isEmpty()) {
734                userState.clientStateMap.remove(sessionState.mClient.asBinder());
735            }
736        }
737
738        TvInputInfo info = sessionState.mInfo;
739        if (info != null) {
740            ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
741            if (serviceState != null) {
742                serviceState.mSessionTokens.remove(sessionToken);
743            }
744        }
745        updateServiceConnectionLocked(sessionState.mInfo.getComponent(), userId);
746    }
747
748    private void notifyInputAddedLocked(UserState userState, String inputId) {
749        if (DEBUG) {
750            Slog.d(TAG, "notifyInputAdded: inputId = " + inputId);
751        }
752        for (ITvInputManagerCallback callback : userState.callbackSet) {
753            try {
754                callback.onInputAdded(inputId);
755            } catch (RemoteException e) {
756                Slog.e(TAG, "Failed to report added input to callback.");
757            }
758        }
759    }
760
761    private void notifyInputRemovedLocked(UserState userState, String inputId) {
762        if (DEBUG) {
763            Slog.d(TAG, "notifyInputRemovedLocked: inputId = " + inputId);
764        }
765        for (ITvInputManagerCallback callback : userState.callbackSet) {
766            try {
767                callback.onInputRemoved(inputId);
768            } catch (RemoteException e) {
769                Slog.e(TAG, "Failed to report removed input to callback.");
770            }
771        }
772    }
773
774    private void notifyInputStateChangedLocked(UserState userState, String inputId,
775            int state, ITvInputManagerCallback targetCallback) {
776        if (DEBUG) {
777            Slog.d(TAG, "notifyInputStateChangedLocked: inputId = " + inputId
778                    + "; state = " + state);
779        }
780        if (targetCallback == null) {
781            for (ITvInputManagerCallback callback : userState.callbackSet) {
782                try {
783                    callback.onInputStateChanged(inputId, state);
784                } catch (RemoteException e) {
785                    Slog.e(TAG, "Failed to report state change to callback.");
786                }
787            }
788        } else {
789            try {
790                targetCallback.onInputStateChanged(inputId, state);
791            } catch (RemoteException e) {
792                Slog.e(TAG, "Failed to report state change to callback.");
793            }
794        }
795    }
796
797    private void setStateLocked(String inputId, int state, int userId) {
798        UserState userState = getUserStateLocked(userId);
799        TvInputState inputState = userState.inputMap.get(inputId);
800        ServiceState serviceState = userState.serviceStateMap.get(inputState.mInfo.getComponent());
801        int oldState = inputState.mState;
802        inputState.mState = state;
803        if (serviceState != null && serviceState.mService == null
804                && shouldMaintainConnection(serviceState)) {
805            // We don't notify state change while reconnecting. It should remain disconnected.
806            return;
807        }
808        if (oldState != state) {
809            notifyInputStateChangedLocked(userState, inputId, state, null);
810        }
811    }
812
813    private final class BinderService extends ITvInputManager.Stub {
814        @Override
815        public List<TvInputInfo> getTvInputList(int userId) {
816            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
817                    Binder.getCallingUid(), userId, "getTvInputList");
818            final long identity = Binder.clearCallingIdentity();
819            try {
820                synchronized (mLock) {
821                    UserState userState = getUserStateLocked(resolvedUserId);
822                    List<TvInputInfo> inputList = new ArrayList<TvInputInfo>();
823                    for (TvInputState state : userState.inputMap.values()) {
824                        inputList.add(state.mInfo);
825                    }
826                    return inputList;
827                }
828            } finally {
829                Binder.restoreCallingIdentity(identity);
830            }
831        }
832
833        @Override
834        public TvInputInfo getTvInputInfo(String inputId, int userId) {
835            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
836                    Binder.getCallingUid(), userId, "getTvInputInfo");
837            final long identity = Binder.clearCallingIdentity();
838            try {
839                synchronized (mLock) {
840                    UserState userState = getUserStateLocked(resolvedUserId);
841                    TvInputState state = userState.inputMap.get(inputId);
842                    return state == null ? null : state.mInfo;
843                }
844            } finally {
845                Binder.restoreCallingIdentity(identity);
846            }
847        }
848
849        @Override
850        public List<Uri> getTvContentRatingSystemXmls(int userId) {
851            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
852                    Binder.getCallingUid(), userId, "getTvContentRatingSystemXmls");
853            final long identity = Binder.clearCallingIdentity();
854            try {
855                synchronized (mLock) {
856                    UserState userState = getUserStateLocked(resolvedUserId);
857                    List<Uri> ratingSystemXmlUriList = new ArrayList<Uri>();
858                    ratingSystemXmlUriList.addAll(userState.ratingSystemXmlUriSet);
859                    return ratingSystemXmlUriList;
860                }
861            } finally {
862                Binder.restoreCallingIdentity(identity);
863            }
864        }
865
866        @Override
867        public void registerCallback(final ITvInputManagerCallback callback, int userId) {
868            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
869                    Binder.getCallingUid(), userId, "registerCallback");
870            final long identity = Binder.clearCallingIdentity();
871            try {
872                synchronized (mLock) {
873                    UserState userState = getUserStateLocked(resolvedUserId);
874                    userState.callbackSet.add(callback);
875                    for (TvInputState state : userState.inputMap.values()) {
876                        notifyInputStateChangedLocked(userState, state.mInfo.getId(),
877                                state.mState, callback);
878                    }
879                }
880            } finally {
881                Binder.restoreCallingIdentity(identity);
882            }
883        }
884
885        @Override
886        public void unregisterCallback(ITvInputManagerCallback callback, int userId) {
887            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
888                    Binder.getCallingUid(), userId, "unregisterCallback");
889            final long identity = Binder.clearCallingIdentity();
890            try {
891                synchronized (mLock) {
892                    UserState userState = getUserStateLocked(resolvedUserId);
893                    userState.callbackSet.remove(callback);
894                }
895            } finally {
896                Binder.restoreCallingIdentity(identity);
897            }
898        }
899
900        @Override
901        public boolean isParentalControlsEnabled(int userId) {
902            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
903                    Binder.getCallingUid(), userId, "isParentalControlsEnabled");
904            final long identity = Binder.clearCallingIdentity();
905            try {
906                synchronized (mLock) {
907                    UserState userState = getUserStateLocked(resolvedUserId);
908                    return userState.persistentDataStore.isParentalControlsEnabled();
909                }
910            } finally {
911                Binder.restoreCallingIdentity(identity);
912            }
913        }
914
915        @Override
916        public void setParentalControlsEnabled(boolean enabled, int userId) {
917            ensureParentalControlsPermission();
918            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
919                    Binder.getCallingUid(), userId, "setParentalControlsEnabled");
920            final long identity = Binder.clearCallingIdentity();
921            try {
922                synchronized (mLock) {
923                    UserState userState = getUserStateLocked(resolvedUserId);
924                    userState.persistentDataStore.setParentalControlsEnabled(enabled);
925                }
926            } finally {
927                Binder.restoreCallingIdentity(identity);
928            }
929        }
930
931        @Override
932        public boolean isRatingBlocked(String rating, int userId) {
933            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
934                    Binder.getCallingUid(), userId, "isRatingBlocked");
935            final long identity = Binder.clearCallingIdentity();
936            try {
937                synchronized (mLock) {
938                    UserState userState = getUserStateLocked(resolvedUserId);
939                    return userState.persistentDataStore.isRatingBlocked(
940                            TvContentRating.unflattenFromString(rating));
941                }
942            } finally {
943                Binder.restoreCallingIdentity(identity);
944            }
945        }
946
947        @Override
948        public List<String> getBlockedRatings(int userId) {
949            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
950                    Binder.getCallingUid(), userId, "getBlockedRatings");
951            final long identity = Binder.clearCallingIdentity();
952            try {
953                synchronized (mLock) {
954                    UserState userState = getUserStateLocked(resolvedUserId);
955                    List<String> ratings = new ArrayList<String>();
956                    for (TvContentRating rating
957                            : userState.persistentDataStore.getBlockedRatings()) {
958                        ratings.add(rating.flattenToString());
959                    }
960                    return ratings;
961                }
962            } finally {
963                Binder.restoreCallingIdentity(identity);
964            }
965        }
966
967        @Override
968        public void addBlockedRating(String rating, int userId) {
969            ensureParentalControlsPermission();
970            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
971                    Binder.getCallingUid(), userId, "addBlockedRating");
972            final long identity = Binder.clearCallingIdentity();
973            try {
974                synchronized (mLock) {
975                    UserState userState = getUserStateLocked(resolvedUserId);
976                    userState.persistentDataStore.addBlockedRating(
977                            TvContentRating.unflattenFromString(rating));
978                }
979            } finally {
980                Binder.restoreCallingIdentity(identity);
981            }
982        }
983
984        @Override
985        public void removeBlockedRating(String rating, int userId) {
986            ensureParentalControlsPermission();
987            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
988                    Binder.getCallingUid(), userId, "removeBlockedRating");
989            final long identity = Binder.clearCallingIdentity();
990            try {
991                synchronized (mLock) {
992                    UserState userState = getUserStateLocked(resolvedUserId);
993                    userState.persistentDataStore.removeBlockedRating(
994                            TvContentRating.unflattenFromString(rating));
995                }
996            } finally {
997                Binder.restoreCallingIdentity(identity);
998            }
999        }
1000
1001        private void ensureParentalControlsPermission() {
1002            if (mContext.checkCallingPermission(
1003                    android.Manifest.permission.MODIFY_PARENTAL_CONTROLS)
1004                    != PackageManager.PERMISSION_GRANTED) {
1005                throw new SecurityException(
1006                        "The caller does not have parental controls permission");
1007            }
1008        }
1009
1010        @Override
1011        public void createSession(final ITvInputClient client, final String inputId,
1012                int seq, int userId) {
1013            final int callingUid = Binder.getCallingUid();
1014            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1015                    userId, "createSession");
1016            final long identity = Binder.clearCallingIdentity();
1017            try {
1018                synchronized (mLock) {
1019                    UserState userState = getUserStateLocked(resolvedUserId);
1020                    TvInputInfo info = userState.inputMap.get(inputId).mInfo;
1021                    ServiceState serviceState = userState.serviceStateMap.get(info.getComponent());
1022                    if (serviceState == null) {
1023                        serviceState = new ServiceState(info.getComponent(), resolvedUserId);
1024                        userState.serviceStateMap.put(info.getComponent(), serviceState);
1025                    }
1026                    // Send a null token immediately while reconnecting.
1027                    if (serviceState.mReconnecting == true) {
1028                        sendSessionTokenToClientLocked(client, inputId, null, null, seq);
1029                        return;
1030                    }
1031
1032                    // Create a new session token and a session state.
1033                    IBinder sessionToken = new Binder();
1034                    SessionState sessionState = new SessionState(sessionToken, info, client,
1035                            seq, callingUid, resolvedUserId);
1036
1037                    // Add them to the global session state map of the current user.
1038                    userState.sessionStateMap.put(sessionToken, sessionState);
1039
1040                    // Also, add them to the session state map of the current service.
1041                    serviceState.mSessionTokens.add(sessionToken);
1042
1043                    if (serviceState.mService != null) {
1044                        createSessionInternalLocked(serviceState.mService, sessionToken,
1045                                resolvedUserId);
1046                    } else {
1047                        updateServiceConnectionLocked(info.getComponent(), resolvedUserId);
1048                    }
1049                }
1050            } finally {
1051                Binder.restoreCallingIdentity(identity);
1052            }
1053        }
1054
1055        @Override
1056        public void releaseSession(IBinder sessionToken, int userId) {
1057            final int callingUid = Binder.getCallingUid();
1058            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1059                    userId, "releaseSession");
1060            final long identity = Binder.clearCallingIdentity();
1061            try {
1062                synchronized (mLock) {
1063                    releaseSessionLocked(sessionToken, callingUid, resolvedUserId);
1064                }
1065            } finally {
1066                Binder.restoreCallingIdentity(identity);
1067            }
1068        }
1069
1070        @Override
1071        public void setMainSession(IBinder sessionToken, int userId) {
1072            final int callingUid = Binder.getCallingUid();
1073            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1074                    userId, "setMainSession");
1075            final long identity = Binder.clearCallingIdentity();
1076            try {
1077                synchronized (mLock) {
1078                    UserState userState = getUserStateLocked(resolvedUserId);
1079                    if (userState.mainSessionToken == sessionToken) {
1080                        return;
1081                    }
1082
1083                    SessionState newMainSessionState = getSessionStateLocked(
1084                            sessionToken, callingUid, resolvedUserId);
1085                    if (newMainSessionState.mHardwareSessionToken != null) {
1086                        newMainSessionState = getSessionStateLocked(
1087                                newMainSessionState.mHardwareSessionToken,
1088                                Process.SYSTEM_UID, resolvedUserId);
1089                    }
1090                    ServiceState newMainServiceState = getServiceStateLocked(
1091                            newMainSessionState.mInfo.getComponent(), resolvedUserId);
1092                    ITvInputSession newMainSession = getSessionLocked(newMainSessionState);
1093
1094                    ServiceState oldMainServiceState = null;
1095                    ITvInputSession oldMainSession = null;
1096                    if (userState.mainSessionToken != null) {
1097                        SessionState oldMainSessionState = getSessionStateLocked(
1098                                userState.mainSessionToken, Process.SYSTEM_UID, resolvedUserId);
1099                        if (oldMainSessionState.mHardwareSessionToken != null) {
1100                            oldMainSessionState = getSessionStateLocked(
1101                                    oldMainSessionState.mHardwareSessionToken,
1102                                    Process.SYSTEM_UID, resolvedUserId);
1103                        }
1104                        oldMainServiceState = getServiceStateLocked(
1105                                oldMainSessionState.mInfo.getComponent(), resolvedUserId);
1106                        oldMainSession = getSessionLocked(oldMainSessionState);
1107                    }
1108
1109                    userState.mainSessionToken = sessionToken;
1110
1111                    // Inform the new main session first.
1112                    // See {@link TvInputService#onSetMainSession}.
1113                    if (newMainServiceState.mIsHardware) {
1114                        try {
1115                            newMainSession.setMainSession(true);
1116                        } catch (RemoteException e) {
1117                            Slog.e(TAG, "error in setMainSession", e);
1118                        }
1119                    }
1120                    if (oldMainSession != null && oldMainServiceState.mIsHardware) {
1121                        try {
1122                            oldMainSession.setMainSession(false);
1123                        } catch (RemoteException e) {
1124                            Slog.e(TAG, "error in setMainSession", e);
1125                        }
1126                    }
1127                }
1128            } finally {
1129                Binder.restoreCallingIdentity(identity);
1130            }
1131        }
1132
1133        @Override
1134        public void setSurface(IBinder sessionToken, Surface surface, int userId) {
1135            final int callingUid = Binder.getCallingUid();
1136            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1137                    userId, "setSurface");
1138            final long identity = Binder.clearCallingIdentity();
1139            try {
1140                synchronized (mLock) {
1141                    try {
1142                        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
1143                                resolvedUserId);
1144                        if (sessionState.mHardwareSessionToken == null) {
1145                            getSessionLocked(sessionState).setSurface(surface);
1146                        } else {
1147                            getSessionLocked(sessionState.mHardwareSessionToken,
1148                                    Process.SYSTEM_UID, resolvedUserId).setSurface(surface);
1149                        }
1150                    } catch (RemoteException e) {
1151                        Slog.e(TAG, "error in setSurface", e);
1152                    }
1153                }
1154            } finally {
1155                if (surface != null) {
1156                    // surface is not used in TvInputManagerService.
1157                    surface.release();
1158                }
1159                Binder.restoreCallingIdentity(identity);
1160            }
1161        }
1162
1163        @Override
1164        public void dispatchSurfaceChanged(IBinder sessionToken, int format, int width,
1165                int height, int userId) {
1166            final int callingUid = Binder.getCallingUid();
1167            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1168                    userId, "dispatchSurfaceChanged");
1169            final long identity = Binder.clearCallingIdentity();
1170            try {
1171                synchronized (mLock) {
1172                    try {
1173                        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
1174                                resolvedUserId);
1175                        getSessionLocked(sessionState).dispatchSurfaceChanged(format, width, height);
1176                        if (sessionState.mHardwareSessionToken != null) {
1177                            getSessionLocked(sessionState.mHardwareSessionToken, Process.SYSTEM_UID,
1178                                    resolvedUserId).dispatchSurfaceChanged(format, width, height);
1179                        }
1180                    } catch (RemoteException e) {
1181                        Slog.e(TAG, "error in dispatchSurfaceChanged", e);
1182                    }
1183                }
1184            } finally {
1185                Binder.restoreCallingIdentity(identity);
1186            }
1187        }
1188
1189        @Override
1190        public void setVolume(IBinder sessionToken, float volume, int userId) {
1191            final float REMOTE_VOLUME_ON = 1.0f;
1192            final float REMOTE_VOLUME_OFF = 0f;
1193            final int callingUid = Binder.getCallingUid();
1194            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1195                    userId, "setVolume");
1196            final long identity = Binder.clearCallingIdentity();
1197            try {
1198                synchronized (mLock) {
1199                    try {
1200                        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
1201                                resolvedUserId);
1202                        getSessionLocked(sessionState).setVolume(volume);
1203                        if (sessionState.mHardwareSessionToken != null) {
1204                            // Here, we let the hardware session know only whether volume is on or
1205                            // off to prevent that the volume is controlled in the both side.
1206                            getSessionLocked(sessionState.mHardwareSessionToken,
1207                                    Process.SYSTEM_UID, resolvedUserId).setVolume((volume > 0.0f)
1208                                            ? REMOTE_VOLUME_ON : REMOTE_VOLUME_OFF);
1209                        }
1210                    } catch (RemoteException e) {
1211                        Slog.e(TAG, "error in setVolume", e);
1212                    }
1213                }
1214            } finally {
1215                Binder.restoreCallingIdentity(identity);
1216            }
1217        }
1218
1219        @Override
1220        public void tune(IBinder sessionToken, final Uri channelUri, Bundle params, int userId) {
1221            final int callingUid = Binder.getCallingUid();
1222            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1223                    userId, "tune");
1224            final long identity = Binder.clearCallingIdentity();
1225            try {
1226                synchronized (mLock) {
1227                    try {
1228                        getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(
1229                                channelUri, params);
1230                        if (TvContract.isChannelUriForPassthroughTvInput(channelUri)) {
1231                            // Do not log the watch history for passthrough inputs.
1232                            return;
1233                        }
1234                        long currentTime = System.currentTimeMillis();
1235                        long channelId = ContentUris.parseId(channelUri);
1236
1237                        // Close the open log entry first, if any.
1238                        UserState userState = getUserStateLocked(resolvedUserId);
1239                        SessionState sessionState = userState.sessionStateMap.get(sessionToken);
1240                        if (sessionState.mLogUri != null) {
1241                            SomeArgs args = SomeArgs.obtain();
1242                            args.arg1 = sessionState.mLogUri;
1243                            args.arg2 = currentTime;
1244                            mLogHandler.obtainMessage(LogHandler.MSG_CLOSE_ENTRY, args)
1245                                    .sendToTarget();
1246                        }
1247
1248                        // Create a log entry and fill it later.
1249                        String packageName = sessionState.mInfo.getServiceInfo().packageName;
1250                        ContentValues values = new ContentValues();
1251                        values.put(TvContract.WatchedPrograms.COLUMN_PACKAGE_NAME, packageName);
1252                        values.put(TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
1253                                currentTime);
1254                        values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, 0);
1255                        values.put(TvContract.WatchedPrograms.COLUMN_CHANNEL_ID, channelId);
1256                        if (params != null) {
1257                            values.put(TvContract.WatchedPrograms.COLUMN_TUNE_PARAMS,
1258                                    encodeTuneParams(params));
1259                        }
1260
1261                        sessionState.mLogUri = mContentResolver.insert(
1262                                TvContract.WatchedPrograms.CONTENT_URI, values);
1263                        SomeArgs args = SomeArgs.obtain();
1264                        args.arg1 = sessionState.mLogUri;
1265                        args.arg2 = ContentUris.parseId(channelUri);
1266                        args.arg3 = currentTime;
1267                        args.arg4 = sessionState;
1268                        mLogHandler.obtainMessage(LogHandler.MSG_OPEN_ENTRY, args).sendToTarget();
1269                    } catch (RemoteException e) {
1270                        Slog.e(TAG, "error in tune", e);
1271                        return;
1272                    }
1273                }
1274            } finally {
1275                Binder.restoreCallingIdentity(identity);
1276            }
1277        }
1278
1279        @Override
1280        public void requestUnblockContent(
1281                IBinder sessionToken, String unblockedRating, int userId) {
1282            final int callingUid = Binder.getCallingUid();
1283            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1284                    userId, "unblockContent");
1285            final long identity = Binder.clearCallingIdentity();
1286            try {
1287                synchronized (mLock) {
1288                    try {
1289                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
1290                                .requestUnblockContent(unblockedRating);
1291                    } catch (RemoteException e) {
1292                        Slog.e(TAG, "error in unblockContent", e);
1293                    }
1294                }
1295            } finally {
1296                Binder.restoreCallingIdentity(identity);
1297            }
1298        }
1299
1300        @Override
1301        public void setCaptionEnabled(IBinder sessionToken, boolean enabled, int userId) {
1302            final int callingUid = Binder.getCallingUid();
1303            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1304                    userId, "setCaptionEnabled");
1305            final long identity = Binder.clearCallingIdentity();
1306            try {
1307                synchronized (mLock) {
1308                    try {
1309                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
1310                                .setCaptionEnabled(enabled);
1311                    } catch (RemoteException e) {
1312                        Slog.e(TAG, "error in setCaptionEnabled", e);
1313                    }
1314                }
1315            } finally {
1316                Binder.restoreCallingIdentity(identity);
1317            }
1318        }
1319
1320        @Override
1321        public void selectTrack(IBinder sessionToken, int type, String trackId, int userId) {
1322            final int callingUid = Binder.getCallingUid();
1323            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1324                    userId, "selectTrack");
1325            final long identity = Binder.clearCallingIdentity();
1326            try {
1327                synchronized (mLock) {
1328                    try {
1329                        getSessionLocked(sessionToken, callingUid, resolvedUserId).selectTrack(
1330                                type, trackId);
1331                    } catch (RemoteException e) {
1332                        Slog.e(TAG, "error in selectTrack", e);
1333                    }
1334                }
1335            } finally {
1336                Binder.restoreCallingIdentity(identity);
1337            }
1338        }
1339
1340        @Override
1341        public void sendAppPrivateCommand(IBinder sessionToken, String command, Bundle data,
1342                int userId) {
1343            final int callingUid = Binder.getCallingUid();
1344            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1345                    userId, "sendAppPrivateCommand");
1346            final long identity = Binder.clearCallingIdentity();
1347            try {
1348                synchronized (mLock) {
1349                    try {
1350                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
1351                                .appPrivateCommand(command, data);
1352                    } catch (RemoteException e) {
1353                        Slog.e(TAG, "error in sendAppPrivateCommand", e);
1354                    }
1355                }
1356            } finally {
1357                Binder.restoreCallingIdentity(identity);
1358            }
1359        }
1360
1361        @Override
1362        public void createOverlayView(IBinder sessionToken, IBinder windowToken, Rect frame,
1363                int userId) {
1364            final int callingUid = Binder.getCallingUid();
1365            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1366                    userId, "createOverlayView");
1367            final long identity = Binder.clearCallingIdentity();
1368            try {
1369                synchronized (mLock) {
1370                    try {
1371                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
1372                                .createOverlayView(windowToken, frame);
1373                    } catch (RemoteException e) {
1374                        Slog.e(TAG, "error in createOverlayView", e);
1375                    }
1376                }
1377            } finally {
1378                Binder.restoreCallingIdentity(identity);
1379            }
1380        }
1381
1382        @Override
1383        public void relayoutOverlayView(IBinder sessionToken, Rect frame, int userId) {
1384            final int callingUid = Binder.getCallingUid();
1385            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1386                    userId, "relayoutOverlayView");
1387            final long identity = Binder.clearCallingIdentity();
1388            try {
1389                synchronized (mLock) {
1390                    try {
1391                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
1392                                .relayoutOverlayView(frame);
1393                    } catch (RemoteException e) {
1394                        Slog.e(TAG, "error in relayoutOverlayView", e);
1395                    }
1396                }
1397            } finally {
1398                Binder.restoreCallingIdentity(identity);
1399            }
1400        }
1401
1402        @Override
1403        public void removeOverlayView(IBinder sessionToken, int userId) {
1404            final int callingUid = Binder.getCallingUid();
1405            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1406                    userId, "removeOverlayView");
1407            final long identity = Binder.clearCallingIdentity();
1408            try {
1409                synchronized (mLock) {
1410                    try {
1411                        getSessionLocked(sessionToken, callingUid, resolvedUserId)
1412                                .removeOverlayView();
1413                    } catch (RemoteException e) {
1414                        Slog.e(TAG, "error in removeOverlayView", e);
1415                    }
1416                }
1417            } finally {
1418                Binder.restoreCallingIdentity(identity);
1419            }
1420        }
1421
1422        @Override
1423        public List<TvInputHardwareInfo> getHardwareList() throws RemoteException {
1424            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
1425                    != PackageManager.PERMISSION_GRANTED) {
1426                return null;
1427            }
1428
1429            final long identity = Binder.clearCallingIdentity();
1430            try {
1431                return mTvInputHardwareManager.getHardwareList();
1432            } finally {
1433                Binder.restoreCallingIdentity(identity);
1434            }
1435        }
1436
1437        @Override
1438        public ITvInputHardware acquireTvInputHardware(int deviceId,
1439                ITvInputHardwareCallback callback, TvInputInfo info, int userId)
1440                throws RemoteException {
1441            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
1442                    != PackageManager.PERMISSION_GRANTED) {
1443                return null;
1444            }
1445
1446            final long identity = Binder.clearCallingIdentity();
1447            final int callingUid = Binder.getCallingUid();
1448            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1449                    userId, "acquireTvInputHardware");
1450            try {
1451                return mTvInputHardwareManager.acquireHardware(
1452                        deviceId, callback, info, callingUid, resolvedUserId);
1453            } finally {
1454                Binder.restoreCallingIdentity(identity);
1455            }
1456        }
1457
1458        @Override
1459        public void releaseTvInputHardware(int deviceId, ITvInputHardware hardware, int userId)
1460                throws RemoteException {
1461            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
1462                    != PackageManager.PERMISSION_GRANTED) {
1463                return;
1464            }
1465
1466            final long identity = Binder.clearCallingIdentity();
1467            final int callingUid = Binder.getCallingUid();
1468            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1469                    userId, "releaseTvInputHardware");
1470            try {
1471                mTvInputHardwareManager.releaseHardware(
1472                        deviceId, hardware, callingUid, resolvedUserId);
1473            } finally {
1474                Binder.restoreCallingIdentity(identity);
1475            }
1476        }
1477
1478        @Override
1479        public List<TvStreamConfig> getAvailableTvStreamConfigList(String inputId, int userId)
1480                throws RemoteException {
1481            if (mContext.checkCallingPermission(
1482                    android.Manifest.permission.CAPTURE_TV_INPUT)
1483                    != PackageManager.PERMISSION_GRANTED) {
1484                throw new SecurityException("Requires CAPTURE_TV_INPUT permission");
1485            }
1486
1487            final long identity = Binder.clearCallingIdentity();
1488            final int callingUid = Binder.getCallingUid();
1489            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1490                    userId, "getAvailableTvStreamConfigList");
1491            try {
1492                return mTvInputHardwareManager.getAvailableTvStreamConfigList(
1493                        inputId, callingUid, resolvedUserId);
1494            } finally {
1495                Binder.restoreCallingIdentity(identity);
1496            }
1497        }
1498
1499        @Override
1500        public boolean captureFrame(String inputId, Surface surface, TvStreamConfig config,
1501                int userId)
1502                throws RemoteException {
1503            if (mContext.checkCallingPermission(
1504                    android.Manifest.permission.CAPTURE_TV_INPUT)
1505                    != PackageManager.PERMISSION_GRANTED) {
1506                throw new SecurityException("Requires CAPTURE_TV_INPUT permission");
1507            }
1508
1509            final long identity = Binder.clearCallingIdentity();
1510            final int callingUid = Binder.getCallingUid();
1511            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1512                    userId, "captureFrame");
1513            try {
1514                String hardwareInputId = null;
1515                synchronized (mLock) {
1516                    UserState userState = getUserStateLocked(resolvedUserId);
1517                    if (userState.inputMap.get(inputId) == null) {
1518                        Slog.e(TAG, "Input not found for " + inputId);
1519                        return false;
1520                    }
1521                    for (SessionState sessionState : userState.sessionStateMap.values()) {
1522                        if (sessionState.mInfo.getId().equals(inputId)
1523                                && sessionState.mHardwareSessionToken != null) {
1524                            hardwareInputId = userState.sessionStateMap.get(
1525                                    sessionState.mHardwareSessionToken).mInfo.getId();
1526                            break;
1527                        }
1528                    }
1529                }
1530                return mTvInputHardwareManager.captureFrame(
1531                        (hardwareInputId != null) ? hardwareInputId : inputId,
1532                        surface, config, callingUid, resolvedUserId);
1533            } finally {
1534                Binder.restoreCallingIdentity(identity);
1535            }
1536        }
1537
1538        @Override
1539        public boolean isSingleSessionActive(int userId) throws RemoteException {
1540            final long identity = Binder.clearCallingIdentity();
1541            final int callingUid = Binder.getCallingUid();
1542            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
1543                    userId, "isSingleSessionActive");
1544            try {
1545                synchronized (mLock) {
1546                    UserState userState = getUserStateLocked(resolvedUserId);
1547                    if (userState.sessionStateMap.size() == 1) {
1548                        return true;
1549                    }
1550                    else if (userState.sessionStateMap.size() == 2) {
1551                        SessionState[] sessionStates = userState.sessionStateMap.values().toArray(
1552                                new SessionState[0]);
1553                        // Check if there is a wrapper input.
1554                        if (sessionStates[0].mHardwareSessionToken != null
1555                                || sessionStates[1].mHardwareSessionToken != null) {
1556                            return true;
1557                        }
1558                    }
1559                    return false;
1560                }
1561            } finally {
1562                Binder.restoreCallingIdentity(identity);
1563            }
1564        }
1565
1566        @Override
1567        @SuppressWarnings("resource")
1568        protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
1569            final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
1570            if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
1571                    != PackageManager.PERMISSION_GRANTED) {
1572                pw.println("Permission Denial: can't dump TvInputManager from pid="
1573                        + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
1574                return;
1575            }
1576
1577            synchronized (mLock) {
1578                pw.println("User Ids (Current user: " + mCurrentUserId + "):");
1579                pw.increaseIndent();
1580                for (int i = 0; i < mUserStates.size(); i++) {
1581                    int userId = mUserStates.keyAt(i);
1582                    pw.println(Integer.valueOf(userId));
1583                }
1584                pw.decreaseIndent();
1585
1586                for (int i = 0; i < mUserStates.size(); i++) {
1587                    int userId = mUserStates.keyAt(i);
1588                    UserState userState = getUserStateLocked(userId);
1589                    pw.println("UserState (" + userId + "):");
1590                    pw.increaseIndent();
1591
1592                    pw.println("inputMap: inputId -> TvInputState");
1593                    pw.increaseIndent();
1594                    for (Map.Entry<String, TvInputState> entry: userState.inputMap.entrySet()) {
1595                        pw.println(entry.getKey() + ": " + entry.getValue());
1596                    }
1597                    pw.decreaseIndent();
1598
1599                    pw.println("packageSet:");
1600                    pw.increaseIndent();
1601                    for (String packageName : userState.packageSet) {
1602                        pw.println(packageName);
1603                    }
1604                    pw.decreaseIndent();
1605
1606                    pw.println("clientStateMap: ITvInputClient -> ClientState");
1607                    pw.increaseIndent();
1608                    for (Map.Entry<IBinder, ClientState> entry :
1609                            userState.clientStateMap.entrySet()) {
1610                        ClientState client = entry.getValue();
1611                        pw.println(entry.getKey() + ": " + client);
1612
1613                        pw.increaseIndent();
1614
1615                        pw.println("mSessionTokens:");
1616                        pw.increaseIndent();
1617                        for (IBinder token : client.mSessionTokens) {
1618                            pw.println("" + token);
1619                        }
1620                        pw.decreaseIndent();
1621
1622                        pw.println("mClientTokens: " + client.mClientToken);
1623                        pw.println("mUserId: " + client.mUserId);
1624
1625                        pw.decreaseIndent();
1626                    }
1627                    pw.decreaseIndent();
1628
1629                    pw.println("serviceStateMap: ComponentName -> ServiceState");
1630                    pw.increaseIndent();
1631                    for (Map.Entry<ComponentName, ServiceState> entry :
1632                            userState.serviceStateMap.entrySet()) {
1633                        ServiceState service = entry.getValue();
1634                        pw.println(entry.getKey() + ": " + service);
1635
1636                        pw.increaseIndent();
1637
1638                        pw.println("mClientTokens:");
1639                        pw.increaseIndent();
1640                        for (IBinder token : service.mClientTokens) {
1641                            pw.println("" + token);
1642                        }
1643                        pw.decreaseIndent();
1644
1645                        pw.println("mSessionTokens:");
1646                        pw.increaseIndent();
1647                        for (IBinder token : service.mSessionTokens) {
1648                            pw.println("" + token);
1649                        }
1650                        pw.decreaseIndent();
1651
1652                        pw.println("mService: " + service.mService);
1653                        pw.println("mCallback: " + service.mCallback);
1654                        pw.println("mBound: " + service.mBound);
1655                        pw.println("mReconnecting: " + service.mReconnecting);
1656
1657                        pw.decreaseIndent();
1658                    }
1659                    pw.decreaseIndent();
1660
1661                    pw.println("sessionStateMap: ITvInputSession -> SessionState");
1662                    pw.increaseIndent();
1663                    for (Map.Entry<IBinder, SessionState> entry :
1664                            userState.sessionStateMap.entrySet()) {
1665                        SessionState session = entry.getValue();
1666                        pw.println(entry.getKey() + ": " + session);
1667
1668                        pw.increaseIndent();
1669                        pw.println("mInfo: " + session.mInfo);
1670                        pw.println("mClient: " + session.mClient);
1671                        pw.println("mSeq: " + session.mSeq);
1672                        pw.println("mCallingUid: " + session.mCallingUid);
1673                        pw.println("mUserId: " + session.mUserId);
1674                        pw.println("mSessionToken: " + session.mSessionToken);
1675                        pw.println("mSession: " + session.mSession);
1676                        pw.println("mLogUri: " + session.mLogUri);
1677                        pw.println("mHardwareSessionToken: " + session.mHardwareSessionToken);
1678                        pw.decreaseIndent();
1679                    }
1680                    pw.decreaseIndent();
1681
1682                    pw.println("callbackSet:");
1683                    pw.increaseIndent();
1684                    for (ITvInputManagerCallback callback : userState.callbackSet) {
1685                        pw.println(callback.toString());
1686                    }
1687                    pw.decreaseIndent();
1688
1689                    pw.println("mainSessionToken: " + userState.mainSessionToken);
1690                    pw.decreaseIndent();
1691                }
1692            }
1693        }
1694
1695        private String encodeTuneParams(Bundle tuneParams) {
1696            StringBuilder builder = new StringBuilder();
1697            Set<String> keySet = tuneParams.keySet();
1698            Iterator<String> it = keySet.iterator();
1699            while (it.hasNext()) {
1700                String key = it.next();
1701                Object value = tuneParams.get(key);
1702                if (value == null) {
1703                    continue;
1704                }
1705                builder.append(replaceEscapeCharacters(key));
1706                builder.append("=");
1707                builder.append(replaceEscapeCharacters(value.toString()));
1708                if (it.hasNext()) {
1709                    builder.append(", ");
1710                }
1711            }
1712            return builder.toString();
1713        }
1714
1715        private String replaceEscapeCharacters(String src) {
1716            final char ESCAPE_CHARACTER = '%';
1717            final String ENCODING_TARGET_CHARACTERS = "%=,";
1718            StringBuilder builder = new StringBuilder();
1719            for (char ch : src.toCharArray()) {
1720                if (ENCODING_TARGET_CHARACTERS.indexOf(ch) >= 0) {
1721                    builder.append(ESCAPE_CHARACTER);
1722                }
1723                builder.append(ch);
1724            }
1725            return builder.toString();
1726        }
1727    }
1728
1729    private static final class TvInputState {
1730        // A TvInputInfo object which represents the TV input.
1731        private TvInputInfo mInfo;
1732
1733        // The state of TV input. Connected by default.
1734        private int mState = INPUT_STATE_CONNECTED;
1735
1736        @Override
1737        public String toString() {
1738            return "mInfo: " + mInfo + "; mState: " + mState;
1739        }
1740    }
1741
1742    private static final class UserState {
1743        // A mapping from the TV input id to its TvInputState.
1744        private Map<String, TvInputState> inputMap = new HashMap<String, TvInputState>();
1745
1746        // A set of all TV input packages.
1747        private final Set<String> packageSet = new HashSet<String>();
1748
1749        // A set of all TV content rating system xml uris.
1750        private final Set<Uri> ratingSystemXmlUriSet = new HashSet<Uri>();
1751
1752        // A mapping from the token of a client to its state.
1753        private final Map<IBinder, ClientState> clientStateMap =
1754                new HashMap<IBinder, ClientState>();
1755
1756        // A mapping from the name of a TV input service to its state.
1757        private final Map<ComponentName, ServiceState> serviceStateMap =
1758                new HashMap<ComponentName, ServiceState>();
1759
1760        // A mapping from the token of a TV input session to its state.
1761        private final Map<IBinder, SessionState> sessionStateMap =
1762                new HashMap<IBinder, SessionState>();
1763
1764        // A set of callbacks.
1765        private final Set<ITvInputManagerCallback> callbackSet =
1766                new HashSet<ITvInputManagerCallback>();
1767
1768        // The token of a "main" TV input session.
1769        private IBinder mainSessionToken = null;
1770
1771        // Persistent data store for all internal settings maintained by the TV input manager
1772        // service.
1773        private final PersistentDataStore persistentDataStore;
1774
1775        private UserState(Context context, int userId) {
1776            persistentDataStore = new PersistentDataStore(context, userId);
1777        }
1778    }
1779
1780    private final class ClientState implements IBinder.DeathRecipient {
1781        private final List<IBinder> mSessionTokens = new ArrayList<IBinder>();
1782
1783        private IBinder mClientToken;
1784        private final int mUserId;
1785
1786        ClientState(IBinder clientToken, int userId) {
1787            mClientToken = clientToken;
1788            mUserId = userId;
1789        }
1790
1791        public boolean isEmpty() {
1792            return mSessionTokens.isEmpty();
1793        }
1794
1795        @Override
1796        public void binderDied() {
1797            synchronized (mLock) {
1798                UserState userState = getUserStateLocked(mUserId);
1799                // DO NOT remove the client state of clientStateMap in this method. It will be
1800                // removed in releaseSessionLocked().
1801                ClientState clientState = userState.clientStateMap.get(mClientToken);
1802                if (clientState != null) {
1803                    while (clientState.mSessionTokens.size() > 0) {
1804                        releaseSessionLocked(
1805                                clientState.mSessionTokens.get(0), Process.SYSTEM_UID, mUserId);
1806                    }
1807                }
1808                mClientToken = null;
1809            }
1810        }
1811    }
1812
1813    private final class ServiceState {
1814        private final List<IBinder> mClientTokens = new ArrayList<IBinder>();
1815        private final List<IBinder> mSessionTokens = new ArrayList<IBinder>();
1816        private final ServiceConnection mConnection;
1817        private final ComponentName mComponent;
1818        private final boolean mIsHardware;
1819        private final List<TvInputInfo> mInputList = new ArrayList<TvInputInfo>();
1820
1821        private ITvInputService mService;
1822        private ServiceCallback mCallback;
1823        private boolean mBound;
1824        private boolean mReconnecting;
1825
1826        private ServiceState(ComponentName component, int userId) {
1827            mComponent = component;
1828            mConnection = new InputServiceConnection(component, userId);
1829            mIsHardware = hasHardwarePermission(mContext.getPackageManager(), mComponent);
1830        }
1831    }
1832
1833    private final class SessionState implements IBinder.DeathRecipient {
1834        private final TvInputInfo mInfo;
1835        private final ITvInputClient mClient;
1836        private final int mSeq;
1837        private final int mCallingUid;
1838        private final int mUserId;
1839        private final IBinder mSessionToken;
1840        private ITvInputSession mSession;
1841        private Uri mLogUri;
1842        // Not null if this session represents an external device connected to a hardware TV input.
1843        private IBinder mHardwareSessionToken;
1844
1845        private SessionState(IBinder sessionToken, TvInputInfo info, ITvInputClient client,
1846                int seq, int callingUid, int userId) {
1847            mSessionToken = sessionToken;
1848            mInfo = info;
1849            mClient = client;
1850            mSeq = seq;
1851            mCallingUid = callingUid;
1852            mUserId = userId;
1853        }
1854
1855        @Override
1856        public void binderDied() {
1857            synchronized (mLock) {
1858                mSession = null;
1859                if (mClient != null) {
1860                    try {
1861                        mClient.onSessionReleased(mSeq);
1862                    } catch(RemoteException e) {
1863                        Slog.e(TAG, "error in onSessionReleased", e);
1864                    }
1865                }
1866                // If there are any other sessions based on this session, they should be released.
1867                UserState userState = getUserStateLocked(mUserId);
1868                for (SessionState sessionState : userState.sessionStateMap.values()) {
1869                    if (mSession != null && mSession == sessionState.mHardwareSessionToken) {
1870                        try {
1871                            sessionState.mSession.release();
1872                        } catch (RemoteException e) {
1873                            Slog.e(TAG, "error in release", e);
1874                        }
1875                        try {
1876                            sessionState.mClient.onSessionReleased(sessionState.mSeq);
1877                        } catch (RemoteException e) {
1878                            Slog.e(TAG, "error in onSessionReleased", e);
1879                        }
1880                    }
1881                }
1882                removeSessionStateLocked(mSessionToken, mUserId);
1883            }
1884        }
1885    }
1886
1887    private final class InputServiceConnection implements ServiceConnection {
1888        private final ComponentName mComponent;
1889        private final int mUserId;
1890
1891        private InputServiceConnection(ComponentName component, int userId) {
1892            mComponent = component;
1893            mUserId = userId;
1894        }
1895
1896        @Override
1897        public void onServiceConnected(ComponentName component, IBinder service) {
1898            if (DEBUG) {
1899                Slog.d(TAG, "onServiceConnected(component=" + component + ")");
1900            }
1901            synchronized (mLock) {
1902                UserState userState = getUserStateLocked(mUserId);
1903                ServiceState serviceState = userState.serviceStateMap.get(mComponent);
1904                serviceState.mService = ITvInputService.Stub.asInterface(service);
1905
1906                // Register a callback, if we need to.
1907                if (serviceState.mIsHardware && serviceState.mCallback == null) {
1908                    serviceState.mCallback = new ServiceCallback(mComponent, mUserId);
1909                    try {
1910                        serviceState.mService.registerCallback(serviceState.mCallback);
1911                    } catch (RemoteException e) {
1912                        Slog.e(TAG, "error in registerCallback", e);
1913                    }
1914                }
1915
1916                // And create sessions, if any.
1917                for (IBinder sessionToken : serviceState.mSessionTokens) {
1918                    createSessionInternalLocked(serviceState.mService, sessionToken, mUserId);
1919                }
1920
1921                for (TvInputState inputState : userState.inputMap.values()) {
1922                    if (inputState.mInfo.getComponent().equals(component)
1923                            && inputState.mState != INPUT_STATE_DISCONNECTED) {
1924                        notifyInputStateChangedLocked(userState, inputState.mInfo.getId(),
1925                                inputState.mState, null);
1926                    }
1927                }
1928
1929                if (serviceState.mIsHardware) {
1930                    List<TvInputHardwareInfo> hardwareInfoList =
1931                            mTvInputHardwareManager.getHardwareList();
1932                    for (TvInputHardwareInfo hardwareInfo : hardwareInfoList) {
1933                        try {
1934                            serviceState.mService.notifyHardwareAdded(hardwareInfo);
1935                        } catch (RemoteException e) {
1936                            Slog.e(TAG, "error in notifyHardwareAdded", e);
1937                        }
1938                    }
1939
1940                    List<HdmiDeviceInfo> deviceInfoList =
1941                            mTvInputHardwareManager.getHdmiDeviceList();
1942                    for (HdmiDeviceInfo deviceInfo : deviceInfoList) {
1943                        try {
1944                            serviceState.mService.notifyHdmiDeviceAdded(deviceInfo);
1945                        } catch (RemoteException e) {
1946                            Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
1947                        }
1948                    }
1949                }
1950            }
1951        }
1952
1953        @Override
1954        public void onServiceDisconnected(ComponentName component) {
1955            if (DEBUG) {
1956                Slog.d(TAG, "onServiceDisconnected(component=" + component + ")");
1957            }
1958            if (!mComponent.equals(component)) {
1959                throw new IllegalArgumentException("Mismatched ComponentName: "
1960                        + mComponent + " (expected), " + component + " (actual).");
1961            }
1962            synchronized (mLock) {
1963                UserState userState = getUserStateLocked(mUserId);
1964                ServiceState serviceState = userState.serviceStateMap.get(mComponent);
1965                if (serviceState != null) {
1966                    serviceState.mReconnecting = true;
1967                    serviceState.mBound = false;
1968                    serviceState.mService = null;
1969                    serviceState.mCallback = null;
1970
1971                    // Send null tokens for not finishing create session events.
1972                    for (IBinder sessionToken : serviceState.mSessionTokens) {
1973                        SessionState sessionState = userState.sessionStateMap.get(sessionToken);
1974                        if (sessionState.mSession == null) {
1975                            removeSessionStateLocked(sessionToken, sessionState.mUserId);
1976                            sendSessionTokenToClientLocked(sessionState.mClient,
1977                                    sessionState.mInfo.getId(), null, null, sessionState.mSeq);
1978                        }
1979                    }
1980
1981                    for (TvInputState inputState : userState.inputMap.values()) {
1982                        if (inputState.mInfo.getComponent().equals(component)) {
1983                            notifyInputStateChangedLocked(userState, inputState.mInfo.getId(),
1984                                    INPUT_STATE_DISCONNECTED, null);
1985                        }
1986                    }
1987                    updateServiceConnectionLocked(mComponent, mUserId);
1988                }
1989            }
1990        }
1991    }
1992
1993    private final class ServiceCallback extends ITvInputServiceCallback.Stub {
1994        private final ComponentName mComponent;
1995        private final int mUserId;
1996
1997        ServiceCallback(ComponentName component, int userId) {
1998            mComponent = component;
1999            mUserId = userId;
2000        }
2001
2002        private void ensureHardwarePermission() {
2003            if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
2004                    != PackageManager.PERMISSION_GRANTED) {
2005                throw new SecurityException("The caller does not have hardware permission");
2006            }
2007        }
2008
2009        private void ensureValidInput(TvInputInfo inputInfo) {
2010            if (inputInfo.getId() == null || !mComponent.equals(inputInfo.getComponent())) {
2011                throw new IllegalArgumentException("Invalid TvInputInfo");
2012            }
2013        }
2014
2015        private void addTvInputLocked(TvInputInfo inputInfo) {
2016            ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
2017            serviceState.mInputList.add(inputInfo);
2018            buildTvInputListLocked(mUserId);
2019        }
2020
2021        @Override
2022        public void addHardwareTvInput(int deviceId, TvInputInfo inputInfo) {
2023            ensureHardwarePermission();
2024            ensureValidInput(inputInfo);
2025            synchronized (mLock) {
2026                mTvInputHardwareManager.addHardwareTvInput(deviceId, inputInfo);
2027                addTvInputLocked(inputInfo);
2028            }
2029        }
2030
2031        @Override
2032        public void addHdmiTvInput(int logicalAddress, TvInputInfo inputInfo) {
2033            ensureHardwarePermission();
2034            ensureValidInput(inputInfo);
2035            synchronized (mLock) {
2036                mTvInputHardwareManager.addHdmiTvInput(logicalAddress, inputInfo);
2037                addTvInputLocked(inputInfo);
2038            }
2039        }
2040
2041        @Override
2042        public void removeTvInput(String inputId) {
2043            ensureHardwarePermission();
2044            synchronized (mLock) {
2045                ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
2046                boolean removed = false;
2047                for (Iterator<TvInputInfo> it = serviceState.mInputList.iterator();
2048                        it.hasNext(); ) {
2049                    if (it.next().getId().equals(inputId)) {
2050                        it.remove();
2051                        removed = true;
2052                        break;
2053                    }
2054                }
2055                if (removed) {
2056                    buildTvInputListLocked(mUserId);
2057                    mTvInputHardwareManager.removeTvInput(inputId);
2058                } else {
2059                    Slog.e(TAG, "TvInputInfo with inputId=" + inputId + " not found.");
2060                }
2061            }
2062        }
2063    }
2064
2065    private final class LogHandler extends Handler {
2066        private static final int MSG_OPEN_ENTRY = 1;
2067        private static final int MSG_UPDATE_ENTRY = 2;
2068        private static final int MSG_CLOSE_ENTRY = 3;
2069
2070        public LogHandler(Looper looper) {
2071            super(looper);
2072        }
2073
2074        @Override
2075        public void handleMessage(Message msg) {
2076            switch (msg.what) {
2077                case MSG_OPEN_ENTRY: {
2078                    SomeArgs args = (SomeArgs) msg.obj;
2079                    Uri uri = (Uri) args.arg1;
2080                    long channelId = (long) args.arg2;
2081                    long time = (long) args.arg3;
2082                    SessionState sessionState = (SessionState) args.arg4;
2083                    onOpenEntry(uri, channelId, time, sessionState);
2084                    args.recycle();
2085                    return;
2086                }
2087                case MSG_UPDATE_ENTRY: {
2088                    SomeArgs args = (SomeArgs) msg.obj;
2089                    Uri uri = (Uri) args.arg1;
2090                    long channelId = (long) args.arg2;
2091                    long time = (long) args.arg3;
2092                    SessionState sessionState = (SessionState) args.arg4;
2093                    onUpdateEntry(uri, channelId, time, sessionState);
2094                    args.recycle();
2095                    return;
2096                }
2097                case MSG_CLOSE_ENTRY: {
2098                    SomeArgs args = (SomeArgs) msg.obj;
2099                    Uri uri = (Uri) args.arg1;
2100                    long time = (long) args.arg2;
2101                    onCloseEntry(uri, time);
2102                    args.recycle();
2103                    return;
2104                }
2105                default: {
2106                    Slog.w(TAG, "Unhandled message code: " + msg.what);
2107                    return;
2108                }
2109            }
2110        }
2111
2112        private void onOpenEntry(Uri logUri, long channelId, long watchStarttime,
2113                SessionState sessionState) {
2114            if (!isChannelSearchable(channelId)) {
2115                // Do not log anything about non-searchable channels.
2116                synchronized (mLock) {
2117                    sessionState.mLogUri = null;
2118                }
2119                mContentResolver.delete(logUri, null, null);
2120                return;
2121            }
2122
2123            String[] projection = {
2124                    TvContract.Programs.COLUMN_TITLE,
2125                    TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS,
2126                    TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS,
2127                    TvContract.Programs.COLUMN_SHORT_DESCRIPTION
2128            };
2129            String selection = TvContract.Programs.COLUMN_CHANNEL_ID + "=? AND "
2130                    + TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS + "<=? AND "
2131                    + TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS + ">?";
2132            String[] selectionArgs = {
2133                    String.valueOf(channelId),
2134                    String.valueOf(watchStarttime),
2135                    String.valueOf(watchStarttime)
2136            };
2137            String sortOrder = TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS + " ASC";
2138            Cursor cursor = null;
2139            try {
2140                cursor = mContentResolver.query(TvContract.Programs.CONTENT_URI, projection,
2141                        selection, selectionArgs, sortOrder);
2142                if (cursor != null && cursor.moveToNext()) {
2143                    ContentValues values = new ContentValues();
2144                    values.put(TvContract.WatchedPrograms.COLUMN_TITLE, cursor.getString(0));
2145                    values.put(TvContract.WatchedPrograms.COLUMN_START_TIME_UTC_MILLIS,
2146                            cursor.getLong(1));
2147                    long endTime = cursor.getLong(2);
2148                    values.put(TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS, endTime);
2149                    values.put(TvContract.WatchedPrograms.COLUMN_DESCRIPTION, cursor.getString(3));
2150                    mContentResolver.update(logUri, values, null, null);
2151
2152                    // Schedule an update when the current program ends.
2153                    SomeArgs args = SomeArgs.obtain();
2154                    args.arg1 = logUri;
2155                    args.arg2 = channelId;
2156                    args.arg3 = endTime;
2157                    args.arg4 = sessionState;
2158                    Message msg = obtainMessage(LogHandler.MSG_UPDATE_ENTRY, args);
2159                    sendMessageDelayed(msg, endTime - System.currentTimeMillis());
2160                }
2161            } finally {
2162                if (cursor != null) {
2163                    cursor.close();
2164                }
2165            }
2166        }
2167
2168        private void onUpdateEntry(Uri uri, long channelId, long time, SessionState sessionState) {
2169            String[] projection = {
2170                    TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS
2171            };
2172            Cursor cursor = null;
2173            try {
2174                cursor = mContentResolver.query(uri, projection, null, null, null);
2175                if (cursor != null && cursor.moveToNext()) {
2176                    long watchEndTime = cursor.getLong(0);
2177                    // Do nothing if the current log entry is already closed.
2178                    if (watchEndTime > 0) {
2179                        return;
2180                    }
2181
2182                    // Update the watch end time for the current log entry.
2183                    ContentValues values = new ContentValues();
2184                    values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, time);
2185                    int c = mContentResolver.update(uri, values, null, null);
2186                } else {
2187                    // The record has been deleted.
2188                    synchronized (mLock) {
2189                        if (!uri.equals(sessionState.mLogUri)) {
2190                            // If the deleted record is not for the current channel, do not re-open
2191                            // a log entry for the next program.
2192                            return;
2193                        }
2194                    }
2195                }
2196                if (cursor != null) {
2197                    cursor.close();
2198                    cursor = null;
2199                }
2200
2201                // The current program has just ended. Create a new log entry for the next program.
2202                uri = ContentUris.withAppendedId(TvContract.Channels.CONTENT_URI, channelId);
2203                projection = new String[] {
2204                        TvContract.Channels.COLUMN_PACKAGE_NAME
2205                };
2206                cursor = mContentResolver.query(uri, projection, null, null, null);
2207                if (cursor != null && cursor.moveToNext()) {
2208                    ContentValues values = new ContentValues();
2209                    values.put(TvContract.WatchedPrograms.COLUMN_PACKAGE_NAME, cursor.getString(0));
2210                    values.put(TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS, time);
2211                    values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, 0);
2212                    values.put(TvContract.WatchedPrograms.COLUMN_CHANNEL_ID, channelId);
2213                    Uri newUri = mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI,
2214                            values);
2215
2216                    synchronized (mLock) {
2217                        sessionState.mLogUri = newUri;
2218                    }
2219
2220                    // Re-open the current log entry with the next program information.
2221                    onOpenEntry(newUri, channelId, time, sessionState);
2222                }
2223            } finally {
2224                if (cursor != null) {
2225                    cursor.close();
2226                }
2227            }
2228        }
2229
2230        private void onCloseEntry(Uri uri, long watchEndTime) {
2231            ContentValues values = new ContentValues();
2232            values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, watchEndTime);
2233            mContentResolver.update(uri, values, null, null);
2234        }
2235
2236        private boolean isChannelSearchable(long channelId) {
2237            String[] projection = { TvContract.Channels.COLUMN_SEARCHABLE };
2238            String selection = TvContract.Channels._ID + "=?";
2239            String[] selectionArgs = { String.valueOf(channelId) };
2240            Cursor cursor = null;
2241            try {
2242                cursor = mContentResolver.query(TvContract.Channels.CONTENT_URI, projection,
2243                        selection, selectionArgs, null);
2244                if (cursor != null && cursor.moveToNext()) {
2245                    return cursor.getLong(0) == 1 ? true : false;
2246                }
2247            } finally {
2248                if (cursor != null) {
2249                    cursor.close();
2250                }
2251            }
2252            // Unless explicitly specified non-searchable, by default the channel is searchable.
2253            return true;
2254        }
2255    }
2256
2257    final class HardwareListener implements TvInputHardwareManager.Listener {
2258        @Override
2259        public void onStateChanged(String inputId, int state) {
2260            synchronized (mLock) {
2261                setStateLocked(inputId, state, mCurrentUserId);
2262            }
2263        }
2264
2265        @Override
2266        public void onHardwareDeviceAdded(TvInputHardwareInfo info) {
2267            synchronized (mLock) {
2268                UserState userState = getUserStateLocked(mCurrentUserId);
2269                // Broadcast the event to all hardware inputs.
2270                for (ServiceState serviceState : userState.serviceStateMap.values()) {
2271                    if (!serviceState.mIsHardware || serviceState.mService == null) continue;
2272                    try {
2273                        serviceState.mService.notifyHardwareAdded(info);
2274                    } catch (RemoteException e) {
2275                        Slog.e(TAG, "error in notifyHardwareAdded", e);
2276                    }
2277                }
2278            }
2279        }
2280
2281        @Override
2282        public void onHardwareDeviceRemoved(TvInputHardwareInfo info) {
2283            synchronized (mLock) {
2284                UserState userState = getUserStateLocked(mCurrentUserId);
2285                // Broadcast the event to all hardware inputs.
2286                for (ServiceState serviceState : userState.serviceStateMap.values()) {
2287                    if (!serviceState.mIsHardware || serviceState.mService == null) continue;
2288                    try {
2289                        serviceState.mService.notifyHardwareRemoved(info);
2290                    } catch (RemoteException e) {
2291                        Slog.e(TAG, "error in notifyHardwareRemoved", e);
2292                    }
2293                }
2294            }
2295        }
2296
2297        @Override
2298        public void onHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) {
2299            synchronized (mLock) {
2300                UserState userState = getUserStateLocked(mCurrentUserId);
2301                // Broadcast the event to all hardware inputs.
2302                for (ServiceState serviceState : userState.serviceStateMap.values()) {
2303                    if (!serviceState.mIsHardware || serviceState.mService == null) continue;
2304                    try {
2305                        serviceState.mService.notifyHdmiDeviceAdded(deviceInfo);
2306                    } catch (RemoteException e) {
2307                        Slog.e(TAG, "error in notifyHdmiDeviceAdded", e);
2308                    }
2309                }
2310            }
2311        }
2312
2313        @Override
2314        public void onHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo) {
2315            synchronized (mLock) {
2316                UserState userState = getUserStateLocked(mCurrentUserId);
2317                // Broadcast the event to all hardware inputs.
2318                for (ServiceState serviceState : userState.serviceStateMap.values()) {
2319                    if (!serviceState.mIsHardware || serviceState.mService == null) continue;
2320                    try {
2321                        serviceState.mService.notifyHdmiDeviceRemoved(deviceInfo);
2322                    } catch (RemoteException e) {
2323                        Slog.e(TAG, "error in notifyHdmiDeviceRemoved", e);
2324                    }
2325                }
2326            }
2327        }
2328    }
2329}
2330