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