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