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