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