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