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