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