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