TvInputManagerService.java revision b06cb8870f0407f18bb1225065a93aba2a5de2bf
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                    sendSessionTokenToClientLocked(sessionState.client, sessionState.name,
281                            sessionToken, sessionState.seq, userId);
282                }
283            }
284        };
285
286        // Create a session. When failed, send a null token immediately.
287        try {
288            service.createSession(callback);
289        } catch (RemoteException e) {
290            Log.e(TAG, "error in createSession", e);
291            sendSessionTokenToClientLocked(sessionState.client, sessionState.name, null,
292                    sessionState.seq, userId);
293        }
294    }
295
296    private void sendSessionTokenToClientLocked(ITvInputClient client, ComponentName name,
297            IBinder sessionToken, int seq, int userId) {
298        try {
299            client.onSessionCreated(name, sessionToken, seq);
300        } catch (RemoteException exception) {
301            Log.e(TAG, "error in onSessionCreated", exception);
302        }
303
304        if (sessionToken == null) {
305            // This means that the session creation failed. We might want to disconnect the service.
306            updateServiceConnectionLocked(name, userId);
307        }
308    }
309
310    private final class BinderService extends ITvInputManager.Stub {
311        @Override
312        public List<TvInputInfo> getTvInputList(int userId) {
313            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
314                    Binder.getCallingUid(), userId, "getTvInputList");
315            final long identity = Binder.clearCallingIdentity();
316            try {
317                synchronized (mLock) {
318                    UserState userState = getUserStateLocked(resolvedUserId);
319                    return new ArrayList<TvInputInfo>(userState.inputList);
320                }
321            } finally {
322                Binder.restoreCallingIdentity(identity);
323            }
324        }
325
326        @Override
327        public boolean getAvailability(final ITvInputClient client, final ComponentName name,
328                int userId) {
329            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
330                    Binder.getCallingUid(), userId, "getAvailability");
331            final long identity = Binder.clearCallingIdentity();
332            try {
333                synchronized (mLock) {
334                    UserState userState = getUserStateLocked(resolvedUserId);
335                    ServiceState serviceState = userState.serviceStateMap.get(name);
336                    if (serviceState != null) {
337                        // We already know the status of this input service. Return the cached
338                        // status.
339                        return serviceState.available;
340                    }
341                }
342            } finally {
343                Binder.restoreCallingIdentity(identity);
344            }
345            return false;
346        }
347
348        @Override
349        public void registerCallback(final ITvInputClient client, final ComponentName name,
350                int userId) {
351            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
352                    Binder.getCallingUid(), userId, "registerCallback");
353            final long identity = Binder.clearCallingIdentity();
354            try {
355                synchronized (mLock) {
356                    // Create a new service callback and add it to the callback map of the current
357                    // service.
358                    UserState userState = getUserStateLocked(resolvedUserId);
359                    ServiceState serviceState = userState.serviceStateMap.get(name);
360                    if (serviceState == null) {
361                        serviceState = new ServiceState(name, resolvedUserId);
362                        userState.serviceStateMap.put(name, serviceState);
363                    }
364                    IBinder iBinder = client.asBinder();
365                    if (!serviceState.clients.contains(iBinder)) {
366                        serviceState.clients.add(iBinder);
367                    }
368                    if (serviceState.service != null) {
369                        if (serviceState.callback != null) {
370                            // We already handled.
371                            return;
372                        }
373                        serviceState.callback = new ServiceCallback(resolvedUserId);
374                        try {
375                            serviceState.service.registerCallback(serviceState.callback);
376                        } catch (RemoteException e) {
377                            Log.e(TAG, "error in registerCallback", e);
378                        }
379                    } else {
380                        updateServiceConnectionLocked(name, resolvedUserId);
381                    }
382                }
383            } finally {
384                Binder.restoreCallingIdentity(identity);
385            }
386        }
387
388        @Override
389        public void unregisterCallback(ITvInputClient client, ComponentName name, int userId) {
390            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
391                    Binder.getCallingUid(), userId, "unregisterCallback");
392            final long identity = Binder.clearCallingIdentity();
393            try {
394                synchronized (mLock) {
395                    UserState userState = getUserStateLocked(resolvedUserId);
396                    ServiceState serviceState = userState.serviceStateMap.get(name);
397                    if (serviceState == null) {
398                        return;
399                    }
400
401                    // Remove this client from the client list and unregister the callback.
402                    serviceState.clients.remove(client.asBinder());
403                    if (!serviceState.clients.isEmpty()) {
404                        // We have other clients who want to keep the callback. Do this later.
405                        return;
406                    }
407                    if (serviceState.service == null || serviceState.callback == null) {
408                        return;
409                    }
410                    try {
411                        serviceState.service.unregisterCallback(serviceState.callback);
412                    } catch (RemoteException e) {
413                        Log.e(TAG, "error in unregisterCallback", e);
414                    } finally {
415                        serviceState.callback = null;
416                        updateServiceConnectionLocked(name, resolvedUserId);
417                    }
418                }
419            } finally {
420                Binder.restoreCallingIdentity(identity);
421            }
422        }
423
424        @Override
425        public void createSession(final ITvInputClient client, final ComponentName name,
426                int seq, int userId) {
427            final int callingUid = Binder.getCallingUid();
428            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
429                    userId, "createSession");
430            final long identity = Binder.clearCallingIdentity();
431            try {
432                synchronized (mLock) {
433                    // Create a new session token and a session state.
434                    IBinder sessionToken = new Binder();
435                    SessionState sessionState = new SessionState(name, client, seq, callingUid);
436                    sessionState.session = null;
437
438                    // Add them to the global session state map of the current user.
439                    UserState userState = getUserStateLocked(resolvedUserId);
440                    userState.sessionStateMap.put(sessionToken, sessionState);
441
442                    // Also, add them to the session state map of the current service.
443                    ServiceState serviceState = userState.serviceStateMap.get(name);
444                    if (serviceState == null) {
445                        serviceState = new ServiceState(name, resolvedUserId);
446                        userState.serviceStateMap.put(name, serviceState);
447                    }
448                    serviceState.sessionStateMap.put(sessionToken, sessionState);
449
450                    if (serviceState.service != null) {
451                        createSessionInternalLocked(serviceState.service, sessionToken,
452                                sessionState, resolvedUserId);
453                    } else {
454                        updateServiceConnectionLocked(name, resolvedUserId);
455                    }
456                }
457            } finally {
458                Binder.restoreCallingIdentity(identity);
459            }
460        }
461
462        @Override
463        public void releaseSession(IBinder sessionToken, int userId) {
464            final int callingUid = Binder.getCallingUid();
465            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
466                    userId, "releaseSession");
467            final long identity = Binder.clearCallingIdentity();
468            try {
469                synchronized (mLock) {
470                    // Release the session.
471                    try {
472                        getSessionLocked(sessionToken, callingUid, resolvedUserId).release();
473                    } catch (RemoteException e) {
474                        Log.e(TAG, "error in release", e);
475                    }
476
477                    // Remove its state from the global session state map of the current user.
478                    UserState userState = getUserStateLocked(resolvedUserId);
479                    SessionState sessionState = userState.sessionStateMap.remove(sessionToken);
480
481                    // Also remove it from the session state map of the current service.
482                    ServiceState serviceState = userState.serviceStateMap.get(sessionState.name);
483                    if (serviceState != null) {
484                        serviceState.sessionStateMap.remove(sessionToken);
485                    }
486
487                    updateServiceConnectionLocked(sessionState.name, resolvedUserId);
488                }
489            } finally {
490                Binder.restoreCallingIdentity(identity);
491            }
492        }
493
494        @Override
495        public void setSurface(IBinder sessionToken, Surface surface, int userId) {
496            final int callingUid = Binder.getCallingUid();
497            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
498                    userId, "setSurface");
499            final long identity = Binder.clearCallingIdentity();
500            try {
501                synchronized (mLock) {
502                    try {
503                        getSessionLocked(sessionToken, callingUid, resolvedUserId).setSurface(
504                                surface);
505                    } catch (RemoteException e) {
506                        Log.e(TAG, "error in setSurface", e);
507                    }
508                }
509            } finally {
510                Binder.restoreCallingIdentity(identity);
511            }
512        }
513
514        @Override
515        public void setVolume(IBinder sessionToken, float volume, int userId) {
516            final int callingUid = Binder.getCallingUid();
517            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
518                    userId, "setVolume");
519            final long identity = Binder.clearCallingIdentity();
520            try {
521                synchronized (mLock) {
522                    try {
523                        getSessionLocked(sessionToken, callingUid, resolvedUserId).setVolume(
524                                volume);
525                    } catch (RemoteException e) {
526                        Log.e(TAG, "error in setVolume", e);
527                    }
528                }
529            } finally {
530                Binder.restoreCallingIdentity(identity);
531            }
532        }
533
534        @Override
535        public void tune(IBinder sessionToken, final Uri channelUri, int userId) {
536            final int callingUid = Binder.getCallingUid();
537            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
538                    userId, "tune");
539            final long identity = Binder.clearCallingIdentity();
540            try {
541                synchronized (mLock) {
542                    SessionState sessionState = getUserStateLocked(resolvedUserId)
543                            .sessionStateMap.get(sessionToken);
544                    final String serviceName = sessionState.name.getClassName();
545                    try {
546                        getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(channelUri);
547                    } catch (RemoteException e) {
548                        Log.e(TAG, "error in tune", e);
549                        return;
550                    }
551                }
552            } finally {
553                Binder.restoreCallingIdentity(identity);
554            }
555        }
556    }
557
558    private static final class UserState {
559        // A list of all known TV inputs on the system.
560        private final List<TvInputInfo> inputList = new ArrayList<TvInputInfo>();
561
562        // A mapping from the name of a TV input service to its state.
563        private final Map<ComponentName, ServiceState> serviceStateMap =
564                new HashMap<ComponentName, ServiceState>();
565
566        // A mapping from the token of a TV input session to its state.
567        private final Map<IBinder, SessionState> sessionStateMap =
568                new HashMap<IBinder, SessionState>();
569    }
570
571    private final class ServiceState {
572        private final List<IBinder> clients = new ArrayList<IBinder>();
573        private final ArrayMap<IBinder, SessionState> sessionStateMap = new ArrayMap<IBinder,
574                SessionState>();
575        private final ServiceConnection connection;
576
577        private ITvInputService service;
578        private ServiceCallback callback;
579        private boolean bound;
580        private boolean available;
581
582        private ServiceState(ComponentName name, int userId) {
583            this.connection = new InputServiceConnection(userId);
584        }
585    }
586
587    private static final class SessionState {
588        private final ComponentName name;
589        private final ITvInputClient client;
590        private final int seq;
591        private final int callingUid;
592
593        private ITvInputSession session;
594
595        private SessionState(ComponentName name, ITvInputClient client, int seq, int callingUid) {
596            this.name = name;
597            this.client = client;
598            this.seq = seq;
599            this.callingUid = callingUid;
600        }
601    }
602
603    private final class InputServiceConnection implements ServiceConnection {
604        private final int mUserId;
605
606        private InputServiceConnection(int userId) {
607            mUserId = userId;
608        }
609
610        @Override
611        public void onServiceConnected(ComponentName name, IBinder service) {
612            if (DEBUG) {
613                Log.d(TAG, "onServiceConnected(name=" + name.getClassName() + ")");
614            }
615            synchronized (mLock) {
616                ServiceState serviceState = getServiceStateLocked(name, mUserId);
617                serviceState.service = ITvInputService.Stub.asInterface(service);
618
619                // Register a callback, if we need to.
620                if (!serviceState.clients.isEmpty() && serviceState.callback == null) {
621                    serviceState.callback = new ServiceCallback(mUserId);
622                    try {
623                        serviceState.service.registerCallback(serviceState.callback);
624                    } catch (RemoteException e) {
625                        Log.e(TAG, "error in registerCallback", e);
626                    }
627                }
628
629                // And create sessions, if any.
630                for (Map.Entry<IBinder, SessionState> entry : serviceState.sessionStateMap
631                        .entrySet()) {
632                    createSessionInternalLocked(serviceState.service, entry.getKey(),
633                            entry.getValue(), mUserId);
634                }
635            }
636        }
637
638        @Override
639        public void onServiceDisconnected(ComponentName name) {
640            if (DEBUG) {
641                Log.d(TAG, "onServiceDisconnected(name=" + name.getClassName() + ")");
642            }
643        }
644    }
645
646    private final class ServiceCallback extends ITvInputServiceCallback.Stub {
647        private final int mUserId;
648
649        ServiceCallback(int userId) {
650            mUserId = userId;
651        }
652
653        @Override
654        public void onAvailabilityChanged(ComponentName name, boolean isAvailable)
655                throws RemoteException {
656            if (DEBUG) {
657                Log.d(TAG, "onAvailabilityChanged(name=" + name.getClassName() + ", isAvailable="
658                        + isAvailable + ")");
659            }
660            synchronized (mLock) {
661                ServiceState serviceState = getServiceStateLocked(name, mUserId);
662                serviceState.available = isAvailable;
663                for (IBinder iBinder : serviceState.clients) {
664                    ITvInputClient client = ITvInputClient.Stub.asInterface(iBinder);
665                    client.onAvailabilityChanged(name, isAvailable);
666                }
667            }
668        }
669    }
670}
671