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