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