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