TvInputManagerService.java revision fd5b72f1ed2ee74a4204eef65f560fc82f0b62fe
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 android.app.ActivityManager;
20import android.content.BroadcastReceiver;
21import android.content.ComponentName;
22import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.content.ServiceConnection;
26import android.content.pm.PackageManager;
27import android.content.pm.ResolveInfo;
28import android.content.pm.ServiceInfo;
29import android.net.Uri;
30import android.os.Binder;
31import android.os.IBinder;
32import android.os.Process;
33import android.os.RemoteException;
34import android.os.UserHandle;
35import android.tv.ITvInputClient;
36import android.tv.ITvInputManager;
37import android.tv.ITvInputService;
38import android.tv.ITvInputServiceCallback;
39import android.tv.ITvInputSession;
40import android.tv.ITvInputSessionCallback;
41import android.tv.TvInputInfo;
42import android.tv.TvInputService;
43import android.util.ArrayMap;
44import android.util.Log;
45import android.util.SparseArray;
46import android.view.Surface;
47
48import com.android.internal.content.PackageMonitor;
49import com.android.server.SystemService;
50
51import java.util.ArrayList;
52import java.util.HashMap;
53import java.util.List;
54import java.util.Map;
55
56/** This class provides a system service that manages television inputs. */
57public final class TvInputManagerService extends SystemService {
58    // STOPSHIP: Turn debugging off.
59    private static final boolean DEBUG = true;
60    private static final String TAG = "TvInputManagerService";
61
62    private final Context mContext;
63
64    // A global lock.
65    private final Object mLock = new Object();
66
67    // ID of the current user.
68    private int mCurrentUserId = UserHandle.USER_OWNER;
69
70    // A map from user id to UserState.
71    private final SparseArray<UserState> mUserStates = new SparseArray<UserState>();
72
73    public TvInputManagerService(Context context) {
74        super(context);
75        mContext = context;
76        registerBroadcastReceivers();
77        synchronized (mLock) {
78            mUserStates.put(mCurrentUserId, new UserState());
79            buildTvInputListLocked(mCurrentUserId);
80        }
81    }
82
83    @Override
84    public void onStart() {
85        publishBinderService(Context.TV_INPUT_SERVICE, new BinderService());
86    }
87
88    private void registerBroadcastReceivers() {
89        PackageMonitor monitor = new PackageMonitor() {
90            @Override
91            public void onSomePackagesChanged() {
92                synchronized (mLock) {
93                    buildTvInputListLocked(mCurrentUserId);
94                }
95            }
96        };
97        monitor.register(mContext, null, UserHandle.ALL, true);
98
99        IntentFilter intentFilter = new IntentFilter();
100        intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
101        intentFilter.addAction(Intent.ACTION_USER_REMOVED);
102        mContext.registerReceiverAsUser(new BroadcastReceiver() {
103            @Override
104            public void onReceive(Context context, Intent intent) {
105                String action = intent.getAction();
106                if (Intent.ACTION_USER_SWITCHED.equals(action)) {
107                    switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
108                } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
109                    removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
110                }
111            }
112        }, UserHandle.ALL, intentFilter, null, null);
113    }
114
115    private void buildTvInputListLocked(int userId) {
116        UserState userState = getUserStateLocked(userId);
117        userState.inputList.clear();
118
119        PackageManager pm = mContext.getPackageManager();
120        List<ResolveInfo> services = pm.queryIntentServices(
121                new Intent(TvInputService.SERVICE_INTERFACE), PackageManager.GET_SERVICES);
122        for (ResolveInfo ri : services) {
123            ServiceInfo si = ri.serviceInfo;
124            if (!android.Manifest.permission.BIND_TV_INPUT.equals(si.permission)) {
125                Log.w(TAG, "Skipping TV input " + si.name + ": it does not require the permission "
126                        + android.Manifest.permission.BIND_TV_INPUT);
127                continue;
128            }
129            TvInputInfo info = new TvInputInfo(ri);
130            userState.inputList.add(info);
131        }
132    }
133
134    private void switchUser(int userId) {
135        synchronized (mLock) {
136            if (mCurrentUserId == userId) {
137                return;
138            }
139            // final int oldUserId = mCurrentUserId;
140            // TODO: Release services and sessions in the old user state, if needed.
141            mCurrentUserId = userId;
142
143            UserState userState = mUserStates.get(userId);
144            if (userState == null) {
145                userState = new UserState();
146            }
147            mUserStates.put(userId, userState);
148            buildTvInputListLocked(userId);
149        }
150    }
151
152    private void removeUser(int userId) {
153        synchronized (mLock) {
154            UserState userState = mUserStates.get(userId);
155            if (userState == null) {
156                return;
157            }
158            // Release created sessions.
159            for (SessionState state : userState.sessionStateMap.values()) {
160                if (state.session != null) {
161                    try {
162                        state.session.release();
163                    } catch (RemoteException e) {
164                        Log.e(TAG, "error in release", e);
165                    }
166                }
167            }
168            userState.sessionStateMap.clear();
169
170            // Unregister all callbacks and unbind all services.
171            for (ServiceState serviceState : userState.serviceStateMap.values()) {
172                if (serviceState.callback != null) {
173                    try {
174                        serviceState.service.unregisterCallback(serviceState.callback);
175                    } catch (RemoteException e) {
176                        Log.e(TAG, "error in unregisterCallback", e);
177                    }
178                }
179                serviceState.clients.clear();
180                mContext.unbindService(serviceState.connection);
181            }
182            userState.serviceStateMap.clear();
183
184            mUserStates.remove(userId);
185        }
186    }
187
188    private UserState getUserStateLocked(int userId) {
189        UserState userState = mUserStates.get(userId);
190        if (userState == null) {
191            throw new IllegalStateException("User state not found for user ID " + userId);
192        }
193        return userState;
194    }
195
196    private ServiceState getServiceStateLocked(ComponentName name, int userId) {
197        UserState userState = getUserStateLocked(userId);
198        ServiceState serviceState = userState.serviceStateMap.get(name);
199        if (serviceState == null) {
200            throw new IllegalStateException("Service state not found for " + name + " (userId=" +
201                    userId + ")");
202        }
203        return serviceState;
204    }
205
206    private ITvInputSession getSessionLocked(IBinder sessionToken, int callingUid, int userId) {
207        UserState userState = getUserStateLocked(userId);
208        SessionState sessionState = userState.sessionStateMap.get(sessionToken);
209        if (sessionState == null) {
210            throw new IllegalArgumentException("Session state not found for token " + sessionToken);
211        }
212        // Only the application that requested this session or the system can access it.
213        if (callingUid != Process.SYSTEM_UID && callingUid != sessionState.callingUid) {
214            throw new SecurityException("Illegal access to the session with token " + sessionToken
215                    + " from uid " + callingUid);
216        }
217        ITvInputSession session = sessionState.session;
218        if (session == null) {
219            throw new IllegalStateException("Session not yet created for token " + sessionToken);
220        }
221        return session;
222    }
223
224    private int resolveCallingUserId(int callingPid, int callingUid, int requestedUserId,
225            String methodName) {
226        return ActivityManager.handleIncomingUser(callingPid, callingUid, requestedUserId, false,
227                false, methodName, null);
228    }
229
230    private void updateServiceConnectionLocked(ComponentName name, int userId) {
231        UserState userState = getUserStateLocked(userId);
232        ServiceState serviceState = userState.serviceStateMap.get(name);
233        if (serviceState == null) {
234            return;
235        }
236        boolean isStateEmpty = serviceState.clients.size() == 0
237                && serviceState.sessionStateMap.size() == 0;
238        if (serviceState.service == null && !isStateEmpty && userId == mCurrentUserId) {
239            // This means that the service is not yet connected but its state indicates that we
240            // have pending requests. Then, connect the service.
241            if (serviceState.bound) {
242                // We have already bound to the service so we don't try to bind again until after we
243                // unbind later on.
244                return;
245            }
246            if (DEBUG) {
247                Log.i(TAG, "bindServiceAsUser(name=" + name.getClassName() + ", userId=" + userId
248                        + ")");
249            }
250            Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(name);
251            mContext.bindServiceAsUser(i, serviceState.connection, Context.BIND_AUTO_CREATE,
252                    new UserHandle(userId));
253            serviceState.bound = true;
254        } else if (serviceState.service != null && isStateEmpty) {
255            // This means that the service is already connected but its state indicates that we have
256            // nothing to do with it. Then, disconnect the service.
257            if (DEBUG) {
258                Log.i(TAG, "unbindService(name=" + name.getClassName() + ")");
259            }
260            mContext.unbindService(serviceState.connection);
261            userState.serviceStateMap.remove(name);
262        }
263    }
264
265    private void createSessionInternalLocked(ITvInputService service, final IBinder sessionToken,
266            final SessionState sessionState, final int userId) {
267        if (DEBUG) {
268            Log.d(TAG, "createSessionInternalLocked(name=" + sessionState.name.getClassName()
269                    + ")");
270        }
271        // Set up a callback to send the session token.
272        ITvInputSessionCallback callback = new ITvInputSessionCallback.Stub() {
273            @Override
274            public void onSessionCreated(ITvInputSession session) {
275                if (DEBUG) {
276                    Log.d(TAG, "onSessionCreated(name=" + sessionState.name.getClassName() + ")");
277                }
278                synchronized (mLock) {
279                    sessionState.session = session;
280                    if (session == null) {
281                        removeSessionStateLocked(sessionToken, userId);
282                        sendSessionTokenToClientLocked(sessionState.client, sessionState.name, null,
283                                sessionState.seq, userId);
284                    } else {
285                        sendSessionTokenToClientLocked(sessionState.client, sessionState.name,
286                                sessionToken, sessionState.seq, userId);
287                    }
288                }
289            }
290        };
291
292        // Create a session. When failed, send a null token immediately.
293        try {
294            service.createSession(callback);
295        } catch (RemoteException e) {
296            Log.e(TAG, "error in createSession", e);
297            removeSessionStateLocked(sessionToken, userId);
298            sendSessionTokenToClientLocked(sessionState.client, sessionState.name, null,
299                    sessionState.seq, userId);
300        }
301    }
302
303    private void sendSessionTokenToClientLocked(ITvInputClient client, ComponentName name,
304            IBinder sessionToken, int seq, int userId) {
305        try {
306            client.onSessionCreated(name, sessionToken, seq);
307        } catch (RemoteException exception) {
308            Log.e(TAG, "error in onSessionCreated", exception);
309        }
310
311        if (sessionToken == null) {
312            // This means that the session creation failed. We might want to disconnect the service.
313            updateServiceConnectionLocked(name, userId);
314        }
315    }
316
317    private void removeSessionStateLocked(IBinder sessionToken, int userId) {
318        // Remove the session state from the global session state map of the current user.
319        UserState userState = getUserStateLocked(userId);
320        SessionState sessionState = userState.sessionStateMap.remove(sessionToken);
321
322        // Also remove the session state from the session state map of the current service.
323        ServiceState serviceState = userState.serviceStateMap.get(sessionState.name);
324        if (serviceState != null) {
325            serviceState.sessionStateMap.remove(sessionToken);
326        }
327        updateServiceConnectionLocked(sessionState.name, userId);
328    }
329
330    private final class BinderService extends ITvInputManager.Stub {
331        @Override
332        public List<TvInputInfo> getTvInputList(int userId) {
333            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
334                    Binder.getCallingUid(), userId, "getTvInputList");
335            final long identity = Binder.clearCallingIdentity();
336            try {
337                synchronized (mLock) {
338                    UserState userState = getUserStateLocked(resolvedUserId);
339                    return new ArrayList<TvInputInfo>(userState.inputList);
340                }
341            } finally {
342                Binder.restoreCallingIdentity(identity);
343            }
344        }
345
346        @Override
347        public boolean getAvailability(final ITvInputClient client, final ComponentName name,
348                int userId) {
349            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
350                    Binder.getCallingUid(), userId, "getAvailability");
351            final long identity = Binder.clearCallingIdentity();
352            try {
353                synchronized (mLock) {
354                    UserState userState = getUserStateLocked(resolvedUserId);
355                    ServiceState serviceState = userState.serviceStateMap.get(name);
356                    if (serviceState != null) {
357                        // We already know the status of this input service. Return the cached
358                        // status.
359                        return serviceState.available;
360                    }
361                }
362            } finally {
363                Binder.restoreCallingIdentity(identity);
364            }
365            return false;
366        }
367
368        @Override
369        public void registerCallback(final ITvInputClient client, final ComponentName name,
370                int userId) {
371            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
372                    Binder.getCallingUid(), userId, "registerCallback");
373            final long identity = Binder.clearCallingIdentity();
374            try {
375                synchronized (mLock) {
376                    // Create a new service callback and add it to the callback map of the current
377                    // service.
378                    UserState userState = getUserStateLocked(resolvedUserId);
379                    ServiceState serviceState = userState.serviceStateMap.get(name);
380                    if (serviceState == null) {
381                        serviceState = new ServiceState(name, resolvedUserId);
382                        userState.serviceStateMap.put(name, serviceState);
383                    }
384                    IBinder iBinder = client.asBinder();
385                    if (!serviceState.clients.contains(iBinder)) {
386                        serviceState.clients.add(iBinder);
387                    }
388                    if (serviceState.service != null) {
389                        if (serviceState.callback != null) {
390                            // We already handled.
391                            return;
392                        }
393                        serviceState.callback = new ServiceCallback(resolvedUserId);
394                        try {
395                            serviceState.service.registerCallback(serviceState.callback);
396                        } catch (RemoteException e) {
397                            Log.e(TAG, "error in registerCallback", e);
398                        }
399                    } else {
400                        updateServiceConnectionLocked(name, resolvedUserId);
401                    }
402                }
403            } finally {
404                Binder.restoreCallingIdentity(identity);
405            }
406        }
407
408        @Override
409        public void unregisterCallback(ITvInputClient client, ComponentName name, int userId) {
410            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
411                    Binder.getCallingUid(), userId, "unregisterCallback");
412            final long identity = Binder.clearCallingIdentity();
413            try {
414                synchronized (mLock) {
415                    UserState userState = getUserStateLocked(resolvedUserId);
416                    ServiceState serviceState = userState.serviceStateMap.get(name);
417                    if (serviceState == null) {
418                        return;
419                    }
420
421                    // Remove this client from the client list and unregister the callback.
422                    serviceState.clients.remove(client.asBinder());
423                    if (!serviceState.clients.isEmpty()) {
424                        // We have other clients who want to keep the callback. Do this later.
425                        return;
426                    }
427                    if (serviceState.service == null || serviceState.callback == null) {
428                        return;
429                    }
430                    try {
431                        serviceState.service.unregisterCallback(serviceState.callback);
432                    } catch (RemoteException e) {
433                        Log.e(TAG, "error in unregisterCallback", e);
434                    } finally {
435                        serviceState.callback = null;
436                        updateServiceConnectionLocked(name, resolvedUserId);
437                    }
438                }
439            } finally {
440                Binder.restoreCallingIdentity(identity);
441            }
442        }
443
444        @Override
445        public void createSession(final ITvInputClient client, final ComponentName name,
446                int seq, int userId) {
447            final int callingUid = Binder.getCallingUid();
448            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
449                    userId, "createSession");
450            final long identity = Binder.clearCallingIdentity();
451            try {
452                synchronized (mLock) {
453                    // Create a new session token and a session state.
454                    IBinder sessionToken = new Binder();
455                    SessionState sessionState = new SessionState(name, client, seq, callingUid);
456                    sessionState.session = null;
457
458                    // Add them to the global session state map of the current user.
459                    UserState userState = getUserStateLocked(resolvedUserId);
460                    userState.sessionStateMap.put(sessionToken, sessionState);
461
462                    // Also, add them to the session state map of the current service.
463                    ServiceState serviceState = userState.serviceStateMap.get(name);
464                    if (serviceState == null) {
465                        serviceState = new ServiceState(name, resolvedUserId);
466                        userState.serviceStateMap.put(name, serviceState);
467                    }
468                    serviceState.sessionStateMap.put(sessionToken, sessionState);
469
470                    if (serviceState.service != null) {
471                        createSessionInternalLocked(serviceState.service, sessionToken,
472                                sessionState, resolvedUserId);
473                    } else {
474                        updateServiceConnectionLocked(name, resolvedUserId);
475                    }
476                }
477            } finally {
478                Binder.restoreCallingIdentity(identity);
479            }
480        }
481
482        @Override
483        public void releaseSession(IBinder sessionToken, int userId) {
484            final int callingUid = Binder.getCallingUid();
485            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
486                    userId, "releaseSession");
487            final long identity = Binder.clearCallingIdentity();
488            try {
489                synchronized (mLock) {
490                    // Release the session.
491                    try {
492                        getSessionLocked(sessionToken, callingUid, resolvedUserId).release();
493                    } catch (RemoteException e) {
494                        Log.e(TAG, "error in release", e);
495                    }
496
497                    removeSessionStateLocked(sessionToken, resolvedUserId);
498                }
499            } finally {
500                Binder.restoreCallingIdentity(identity);
501            }
502        }
503
504        @Override
505        public void setSurface(IBinder sessionToken, Surface surface, int userId) {
506            final int callingUid = Binder.getCallingUid();
507            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
508                    userId, "setSurface");
509            final long identity = Binder.clearCallingIdentity();
510            try {
511                synchronized (mLock) {
512                    try {
513                        getSessionLocked(sessionToken, callingUid, resolvedUserId).setSurface(
514                                surface);
515                    } catch (RemoteException e) {
516                        Log.e(TAG, "error in setSurface", e);
517                    }
518                }
519            } finally {
520                Binder.restoreCallingIdentity(identity);
521            }
522        }
523
524        @Override
525        public void setVolume(IBinder sessionToken, float volume, int userId) {
526            final int callingUid = Binder.getCallingUid();
527            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
528                    userId, "setVolume");
529            final long identity = Binder.clearCallingIdentity();
530            try {
531                synchronized (mLock) {
532                    try {
533                        getSessionLocked(sessionToken, callingUid, resolvedUserId).setVolume(
534                                volume);
535                    } catch (RemoteException e) {
536                        Log.e(TAG, "error in setVolume", e);
537                    }
538                }
539            } finally {
540                Binder.restoreCallingIdentity(identity);
541            }
542        }
543
544        @Override
545        public void tune(IBinder sessionToken, final Uri channelUri, int userId) {
546            final int callingUid = Binder.getCallingUid();
547            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
548                    userId, "tune");
549            final long identity = Binder.clearCallingIdentity();
550            try {
551                synchronized (mLock) {
552                    SessionState sessionState = getUserStateLocked(resolvedUserId)
553                            .sessionStateMap.get(sessionToken);
554                    final String serviceName = sessionState.name.getClassName();
555                    try {
556                        getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(channelUri);
557                    } catch (RemoteException e) {
558                        Log.e(TAG, "error in tune", e);
559                        return;
560                    }
561                }
562            } finally {
563                Binder.restoreCallingIdentity(identity);
564            }
565        }
566    }
567
568    private static final class UserState {
569        // A list of all known TV inputs on the system.
570        private final List<TvInputInfo> inputList = new ArrayList<TvInputInfo>();
571
572        // A mapping from the name of a TV input service to its state.
573        private final Map<ComponentName, ServiceState> serviceStateMap =
574                new HashMap<ComponentName, ServiceState>();
575
576        // A mapping from the token of a TV input session to its state.
577        private final Map<IBinder, SessionState> sessionStateMap =
578                new HashMap<IBinder, SessionState>();
579    }
580
581    private final class ServiceState {
582        private final List<IBinder> clients = new ArrayList<IBinder>();
583        private final ArrayMap<IBinder, SessionState> sessionStateMap = new ArrayMap<IBinder,
584                SessionState>();
585        private final ServiceConnection connection;
586
587        private ITvInputService service;
588        private ServiceCallback callback;
589        private boolean bound;
590        private boolean available;
591
592        private ServiceState(ComponentName name, int userId) {
593            this.connection = new InputServiceConnection(userId);
594        }
595    }
596
597    private static final class SessionState {
598        private final ComponentName name;
599        private final ITvInputClient client;
600        private final int seq;
601        private final int callingUid;
602
603        private ITvInputSession session;
604
605        private SessionState(ComponentName name, ITvInputClient client, int seq, int callingUid) {
606            this.name = name;
607            this.client = client;
608            this.seq = seq;
609            this.callingUid = callingUid;
610        }
611    }
612
613    private final class InputServiceConnection implements ServiceConnection {
614        private final int mUserId;
615
616        private InputServiceConnection(int userId) {
617            mUserId = userId;
618        }
619
620        @Override
621        public void onServiceConnected(ComponentName name, IBinder service) {
622            if (DEBUG) {
623                Log.d(TAG, "onServiceConnected(name=" + name.getClassName() + ")");
624            }
625            synchronized (mLock) {
626                ServiceState serviceState = getServiceStateLocked(name, mUserId);
627                serviceState.service = ITvInputService.Stub.asInterface(service);
628
629                // Register a callback, if we need to.
630                if (!serviceState.clients.isEmpty() && serviceState.callback == null) {
631                    serviceState.callback = new ServiceCallback(mUserId);
632                    try {
633                        serviceState.service.registerCallback(serviceState.callback);
634                    } catch (RemoteException e) {
635                        Log.e(TAG, "error in registerCallback", e);
636                    }
637                }
638
639                // And create sessions, if any.
640                for (Map.Entry<IBinder, SessionState> entry : serviceState.sessionStateMap
641                        .entrySet()) {
642                    createSessionInternalLocked(serviceState.service, entry.getKey(),
643                            entry.getValue(), mUserId);
644                }
645            }
646        }
647
648        @Override
649        public void onServiceDisconnected(ComponentName name) {
650            if (DEBUG) {
651                Log.d(TAG, "onServiceDisconnected(name=" + name.getClassName() + ")");
652            }
653        }
654    }
655
656    private final class ServiceCallback extends ITvInputServiceCallback.Stub {
657        private final int mUserId;
658
659        ServiceCallback(int userId) {
660            mUserId = userId;
661        }
662
663        @Override
664        public void onAvailabilityChanged(ComponentName name, boolean isAvailable)
665                throws RemoteException {
666            if (DEBUG) {
667                Log.d(TAG, "onAvailabilityChanged(name=" + name.getClassName() + ", isAvailable="
668                        + isAvailable + ")");
669            }
670            synchronized (mLock) {
671                ServiceState serviceState = getServiceStateLocked(name, mUserId);
672                serviceState.available = isAvailable;
673                for (IBinder iBinder : serviceState.clients) {
674                    ITvInputClient client = ITvInputClient.Stub.asInterface(iBinder);
675                    client.onAvailabilityChanged(name, isAvailable);
676                }
677            }
678        }
679    }
680}
681