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