TvInputManagerService.java revision 3957091ba8f08c02b5e781098cb955a5f697a1ff
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            // Release created sessions.
155            UserState userState = getUserStateLocked(userId);
156            for (SessionState state : userState.sessionStateMap.values()) {
157                if (state.session != null) {
158                    try {
159                        state.session.release();
160                    } catch (RemoteException e) {
161                        Log.e(TAG, "error in release", e);
162                    }
163                }
164            }
165            userState.sessionStateMap.clear();
166
167            // Unregister all callbacks and unbind all services.
168            for (ServiceState serviceState : userState.serviceStateMap.values()) {
169                if (serviceState.callback != null) {
170                    try {
171                        serviceState.service.unregisterCallback(serviceState.callback);
172                    } catch (RemoteException e) {
173                        Log.e(TAG, "error in unregisterCallback", e);
174                    }
175                }
176                serviceState.clients.clear();
177                mContext.unbindService(serviceState.connection);
178            }
179            userState.serviceStateMap.clear();
180
181            mUserStates.remove(userId);
182        }
183    }
184
185    private UserState getUserStateLocked(int userId) {
186        UserState userState = mUserStates.get(userId);
187        if (userState == null) {
188            throw new IllegalStateException("User state not found for user ID " + userId);
189        }
190        return userState;
191    }
192
193    private ServiceState getServiceStateLocked(ComponentName name, int userId) {
194        UserState userState = getUserStateLocked(userId);
195        ServiceState serviceState = userState.serviceStateMap.get(name);
196        if (serviceState == null) {
197            throw new IllegalStateException("Service state not found for " + name + " (userId=" +
198                    userId + ")");
199        }
200        return serviceState;
201    }
202
203    private ITvInputSession getSessionLocked(IBinder sessionToken, int callingUid, int userId) {
204        UserState userState = getUserStateLocked(userId);
205        SessionState sessionState = userState.sessionStateMap.get(sessionToken);
206        if (sessionState == null) {
207            throw new IllegalArgumentException("Session state not found for token " + sessionToken);
208        }
209        // Only the application that requested this session or the system can access it.
210        if (callingUid != Process.SYSTEM_UID && callingUid != sessionState.callingUid) {
211            throw new SecurityException("Illegal access to the session with token " + sessionToken
212                    + " from uid " + callingUid);
213        }
214        ITvInputSession session = sessionState.session;
215        if (session == null) {
216            throw new IllegalStateException("Session not yet created for token " + sessionToken);
217        }
218        return session;
219    }
220
221    private int resolveCallingUserId(int callingPid, int callingUid, int requestedUserId,
222            String methodName) {
223        return ActivityManager.handleIncomingUser(callingPid, callingUid, requestedUserId, false,
224                false, methodName, null);
225    }
226
227    private void updateServiceConnectionLocked(ComponentName name, int userId) {
228        UserState userState = getUserStateLocked(userId);
229        ServiceState serviceState = userState.serviceStateMap.get(name);
230        if (serviceState == null) {
231            return;
232        }
233        boolean isStateEmpty = serviceState.clients.size() == 0
234                && serviceState.sessionStateMap.size() == 0;
235        if (serviceState.service == null && !isStateEmpty && userId == mCurrentUserId) {
236            // This means that the service is not yet connected but its state indicates that we
237            // have pending requests. Then, connect the service.
238            if (serviceState.bound) {
239                // We have already bound to the service so we don't try to bind again until after we
240                // unbind later on.
241                return;
242            }
243            if (DEBUG) {
244                Log.i(TAG, "bindServiceAsUser(name=" + name.getClassName() + ", userId=" + userId
245                        + ")");
246            }
247            Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(name);
248            mContext.bindServiceAsUser(i, serviceState.connection, Context.BIND_AUTO_CREATE,
249                    new UserHandle(userId));
250            serviceState.bound = true;
251        } else if (serviceState.service != null && isStateEmpty) {
252            // This means that the service is already connected but its state indicates that we have
253            // nothing to do with it. Then, disconnect the service.
254            if (DEBUG) {
255                Log.i(TAG, "unbindService(name=" + name.getClassName() + ")");
256            }
257            mContext.unbindService(serviceState.connection);
258            userState.serviceStateMap.remove(name);
259        }
260    }
261
262    private void createSessionInternalLocked(ITvInputService service, final IBinder sessionToken,
263            final SessionState sessionState, final int userId) {
264        if (DEBUG) {
265            Log.d(TAG, "createSessionInternalLocked(name=" + sessionState.name.getClassName()
266                    + ")");
267        }
268        // Set up a callback to send the session token.
269        ITvInputSessionCallback callback = new ITvInputSessionCallback.Stub() {
270            @Override
271            public void onSessionCreated(ITvInputSession session) {
272                if (DEBUG) {
273                    Log.d(TAG, "onSessionCreated(name=" + sessionState.name.getClassName() + ")");
274                }
275                synchronized (mLock) {
276                    sessionState.session = session;
277                    sendSessionTokenToClientLocked(sessionState.client, sessionState.name,
278                            sessionToken, sessionState.seq, userId);
279                }
280            }
281        };
282
283        // Create a session. When failed, send a null token immediately.
284        try {
285            service.createSession(callback);
286        } catch (RemoteException e) {
287            Log.e(TAG, "error in createSession", e);
288            sendSessionTokenToClientLocked(sessionState.client, sessionState.name, null,
289                    sessionState.seq, userId);
290        }
291    }
292
293    private void sendSessionTokenToClientLocked(ITvInputClient client, ComponentName name,
294            IBinder sessionToken, int seq, int userId) {
295        try {
296            client.onSessionCreated(name, sessionToken, seq);
297        } catch (RemoteException exception) {
298            Log.e(TAG, "error in onSessionCreated", exception);
299        }
300
301        if (sessionToken == null) {
302            // This means that the session creation failed. We might want to disconnect the service.
303            updateServiceConnectionLocked(name, userId);
304        }
305    }
306
307    private final class BinderService extends ITvInputManager.Stub {
308        @Override
309        public List<TvInputInfo> getTvInputList(int userId) {
310            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
311                    Binder.getCallingUid(), userId, "getTvInputList");
312            final long identity = Binder.clearCallingIdentity();
313            try {
314                synchronized (mLock) {
315                    UserState userState = getUserStateLocked(resolvedUserId);
316                    return new ArrayList<TvInputInfo>(userState.inputList);
317                }
318            } finally {
319                Binder.restoreCallingIdentity(identity);
320            }
321        }
322
323        @Override
324        public boolean getAvailability(final ITvInputClient client, final ComponentName name,
325                int userId) {
326            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
327                    Binder.getCallingUid(), userId, "getAvailability");
328            final long identity = Binder.clearCallingIdentity();
329            try {
330                synchronized (mLock) {
331                    UserState userState = getUserStateLocked(resolvedUserId);
332                    ServiceState serviceState = userState.serviceStateMap.get(name);
333                    if (serviceState != null) {
334                        // We already know the status of this input service. Return the cached
335                        // status.
336                        return serviceState.available;
337                    }
338                }
339            } finally {
340                Binder.restoreCallingIdentity(identity);
341            }
342            return false;
343        }
344
345        @Override
346        public void registerCallback(final ITvInputClient client, final ComponentName name,
347                int userId) {
348            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
349                    Binder.getCallingUid(), userId, "registerCallback");
350            final long identity = Binder.clearCallingIdentity();
351            try {
352                synchronized (mLock) {
353                    // Create a new service callback and add it to the callback map of the current
354                    // service.
355                    UserState userState = getUserStateLocked(resolvedUserId);
356                    ServiceState serviceState = userState.serviceStateMap.get(name);
357                    if (serviceState == null) {
358                        serviceState = new ServiceState(name, resolvedUserId);
359                        userState.serviceStateMap.put(name, serviceState);
360                    }
361                    IBinder iBinder = client.asBinder();
362                    if (!serviceState.clients.contains(iBinder)) {
363                        serviceState.clients.add(iBinder);
364                    }
365                    if (serviceState.service != null) {
366                        if (serviceState.callback != null) {
367                            // We already handled.
368                            return;
369                        }
370                        serviceState.callback = new ServiceCallback(resolvedUserId);
371                        try {
372                            serviceState.service.registerCallback(serviceState.callback);
373                        } catch (RemoteException e) {
374                            Log.e(TAG, "error in registerCallback", e);
375                        }
376                    } else {
377                        updateServiceConnectionLocked(name, resolvedUserId);
378                    }
379                }
380            } finally {
381                Binder.restoreCallingIdentity(identity);
382            }
383        }
384
385        @Override
386        public void unregisterCallback(ITvInputClient client, ComponentName name, int userId) {
387            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
388                    Binder.getCallingUid(), userId, "unregisterCallback");
389            final long identity = Binder.clearCallingIdentity();
390            try {
391                synchronized (mLock) {
392                    UserState userState = getUserStateLocked(resolvedUserId);
393                    ServiceState serviceState = userState.serviceStateMap.get(name);
394                    if (serviceState == null) {
395                        return;
396                    }
397
398                    // Remove this client from the client list and unregister the callback.
399                    serviceState.clients.remove(client.asBinder());
400                    if (!serviceState.clients.isEmpty()) {
401                        // We have other clients who want to keep the callback. Do this later.
402                        return;
403                    }
404                    if (serviceState.service == null || serviceState.callback == null) {
405                        return;
406                    }
407                    try {
408                        serviceState.service.unregisterCallback(serviceState.callback);
409                    } catch (RemoteException e) {
410                        Log.e(TAG, "error in unregisterCallback", e);
411                    } finally {
412                        serviceState.callback = null;
413                        updateServiceConnectionLocked(name, resolvedUserId);
414                    }
415                }
416            } finally {
417                Binder.restoreCallingIdentity(identity);
418            }
419        }
420
421        @Override
422        public void createSession(final ITvInputClient client, final ComponentName name,
423                int seq, int userId) {
424            final int callingUid = Binder.getCallingUid();
425            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
426                    userId, "createSession");
427            final long identity = Binder.clearCallingIdentity();
428            try {
429                synchronized (mLock) {
430                    // Create a new session token and a session state.
431                    IBinder sessionToken = new Binder();
432                    SessionState sessionState = new SessionState(name, client, seq, callingUid);
433                    sessionState.session = null;
434
435                    // Add them to the global session state map of the current user.
436                    UserState userState = getUserStateLocked(resolvedUserId);
437                    userState.sessionStateMap.put(sessionToken, sessionState);
438
439                    // Also, add them to the session state map of the current service.
440                    ServiceState serviceState = userState.serviceStateMap.get(name);
441                    if (serviceState == null) {
442                        serviceState = new ServiceState(name, resolvedUserId);
443                        userState.serviceStateMap.put(name, serviceState);
444                    }
445                    serviceState.sessionStateMap.put(sessionToken, sessionState);
446
447                    if (serviceState.service != null) {
448                        createSessionInternalLocked(serviceState.service, sessionToken,
449                                sessionState, resolvedUserId);
450                    } else {
451                        updateServiceConnectionLocked(name, resolvedUserId);
452                    }
453                }
454            } finally {
455                Binder.restoreCallingIdentity(identity);
456            }
457        }
458
459        @Override
460        public void releaseSession(IBinder sessionToken, int userId) {
461            final int callingUid = Binder.getCallingUid();
462            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
463                    userId, "releaseSession");
464            final long identity = Binder.clearCallingIdentity();
465            try {
466                synchronized (mLock) {
467                    // Release the session.
468                    try {
469                        getSessionLocked(sessionToken, callingUid, resolvedUserId).release();
470                    } catch (RemoteException e) {
471                        Log.e(TAG, "error in release", e);
472                    }
473
474                    // Remove its state from the global session state map of the current user.
475                    UserState userState = getUserStateLocked(resolvedUserId);
476                    SessionState sessionState = userState.sessionStateMap.remove(sessionToken);
477
478                    // Also remove it from the session state map of the current service.
479                    ServiceState serviceState = userState.serviceStateMap.get(sessionState.name);
480                    if (serviceState != null) {
481                        serviceState.sessionStateMap.remove(sessionToken);
482                    }
483
484                    updateServiceConnectionLocked(sessionState.name, resolvedUserId);
485                }
486            } finally {
487                Binder.restoreCallingIdentity(identity);
488            }
489        }
490
491        @Override
492        public void setSurface(IBinder sessionToken, Surface surface, int userId) {
493            final int callingUid = Binder.getCallingUid();
494            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
495                    userId, "setSurface");
496            final long identity = Binder.clearCallingIdentity();
497            try {
498                synchronized (mLock) {
499                    try {
500                        getSessionLocked(sessionToken, callingUid, resolvedUserId).setSurface(
501                                surface);
502                    } catch (RemoteException e) {
503                        Log.e(TAG, "error in setSurface", e);
504                    }
505                }
506            } finally {
507                Binder.restoreCallingIdentity(identity);
508            }
509        }
510
511        @Override
512        public void setVolume(IBinder sessionToken, float volume, int userId) {
513            final int callingUid = Binder.getCallingUid();
514            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
515                    userId, "setVolume");
516            final long identity = Binder.clearCallingIdentity();
517            try {
518                synchronized (mLock) {
519                    try {
520                        getSessionLocked(sessionToken, callingUid, resolvedUserId).setVolume(
521                                volume);
522                    } catch (RemoteException e) {
523                        Log.e(TAG, "error in setVolume", e);
524                    }
525                }
526            } finally {
527                Binder.restoreCallingIdentity(identity);
528            }
529        }
530
531        @Override
532        public void tune(IBinder sessionToken, final Uri channelUri, int userId) {
533            final int callingUid = Binder.getCallingUid();
534            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
535                    userId, "tune");
536            final long identity = Binder.clearCallingIdentity();
537            try {
538                synchronized (mLock) {
539                    SessionState sessionState = getUserStateLocked(resolvedUserId)
540                            .sessionStateMap.get(sessionToken);
541                    final String serviceName = sessionState.name.getClassName();
542                    try {
543                        getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(channelUri);
544                    } catch (RemoteException e) {
545                        Log.e(TAG, "error in tune", e);
546                        return;
547                    }
548                }
549            } finally {
550                Binder.restoreCallingIdentity(identity);
551            }
552        }
553    }
554
555    private static final class UserState {
556        // A list of all known TV inputs on the system.
557        private final List<TvInputInfo> inputList = new ArrayList<TvInputInfo>();
558
559        // A mapping from the name of a TV input service to its state.
560        private final Map<ComponentName, ServiceState> serviceStateMap =
561                new HashMap<ComponentName, ServiceState>();
562
563        // A mapping from the token of a TV input session to its state.
564        private final Map<IBinder, SessionState> sessionStateMap =
565                new HashMap<IBinder, SessionState>();
566    }
567
568    private final class ServiceState {
569        private final List<IBinder> clients = new ArrayList<IBinder>();
570        private final ArrayMap<IBinder, SessionState> sessionStateMap = new ArrayMap<IBinder,
571                SessionState>();
572        private final ServiceConnection connection;
573
574        private ITvInputService service;
575        private ServiceCallback callback;
576        private boolean bound;
577        private boolean available;
578
579        private ServiceState(ComponentName name, int userId) {
580            this.connection = new InputServiceConnection(userId);
581        }
582    }
583
584    private static final class SessionState {
585        private final ComponentName name;
586        private final ITvInputClient client;
587        private final int seq;
588        private final int callingUid;
589
590        private ITvInputSession session;
591
592        private SessionState(ComponentName name, ITvInputClient client, int seq, int callingUid) {
593            this.name = name;
594            this.client = client;
595            this.seq = seq;
596            this.callingUid = callingUid;
597        }
598    }
599
600    private final class InputServiceConnection implements ServiceConnection {
601        private final int mUserId;
602
603        private InputServiceConnection(int userId) {
604            mUserId = userId;
605        }
606
607        @Override
608        public void onServiceConnected(ComponentName name, IBinder service) {
609            if (DEBUG) {
610                Log.d(TAG, "onServiceConnected(name=" + name.getClassName() + ")");
611            }
612            synchronized (mLock) {
613                ServiceState serviceState = getServiceStateLocked(name, mUserId);
614                serviceState.service = ITvInputService.Stub.asInterface(service);
615
616                // Register a callback, if we need to.
617                if (!serviceState.clients.isEmpty() && serviceState.callback == null) {
618                    serviceState.callback = new ServiceCallback(mUserId);
619                    try {
620                        serviceState.service.registerCallback(serviceState.callback);
621                    } catch (RemoteException e) {
622                        Log.e(TAG, "error in registerCallback", e);
623                    }
624                }
625
626                // And create sessions, if any.
627                for (Map.Entry<IBinder, SessionState> entry : serviceState.sessionStateMap
628                        .entrySet()) {
629                    createSessionInternalLocked(serviceState.service, entry.getKey(),
630                            entry.getValue(), mUserId);
631                }
632            }
633        }
634
635        @Override
636        public void onServiceDisconnected(ComponentName name) {
637            if (DEBUG) {
638                Log.d(TAG, "onServiceDisconnected(name=" + name.getClassName() + ")");
639            }
640        }
641    }
642
643    private final class ServiceCallback extends ITvInputServiceCallback.Stub {
644        private final int mUserId;
645
646        ServiceCallback(int userId) {
647            mUserId = userId;
648        }
649
650        @Override
651        public void onAvailabilityChanged(ComponentName name, boolean isAvailable)
652                throws RemoteException {
653            if (DEBUG) {
654                Log.d(TAG, "onAvailabilityChanged(name=" + name.getClassName() + ", isAvailable="
655                        + isAvailable + ")");
656            }
657            synchronized (mLock) {
658                ServiceState serviceState = getServiceStateLocked(name, mUserId);
659                serviceState.available = isAvailable;
660                for (IBinder iBinder : serviceState.clients) {
661                    ITvInputClient client = ITvInputClient.Stub.asInterface(iBinder);
662                    client.onAvailabilityChanged(name, isAvailable);
663                }
664            }
665        }
666    }
667}
668