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