1 /*
2  * Copyright 2024 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 
17 package com.android.server.audio;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.os.IBinder;
22 import android.os.IInterface;
23 import android.os.IServiceCallback;
24 import android.os.RemoteException;
25 import android.os.ServiceManager;
26 import android.util.Log;
27 
28 import java.util.Objects;
29 import java.util.Set;
30 import java.util.concurrent.ConcurrentHashMap;
31 import java.util.concurrent.Executor;
32 import java.util.concurrent.atomic.AtomicReference;
33 import java.util.function.Consumer;
34 import java.util.function.Function;
35 
36 /**
37  * Manages a remote service which can start and stop. Allows clients to add tasks to run when the
38  * remote service starts or dies.
39  *
40  * <p>Example usage should look something like:
41  *
42  * <pre>
43  *      var service = mServiceHolder.checkService();
44  *      if (service == null) handleFailure();
45  *      try {
46  *          service.foo();
47  *      } catch (RemoteException e) {
48  *          mServiceHolder.attemptClear(service.asBinder());
49  *          handleFailure();
50  *      }
51  * </pre>
52  */
53 public class ServiceHolder<I extends IInterface> implements IBinder.DeathRecipient {
54 
55     private final String mTag;
56     private final String mServiceName;
57     private final Function<? super IBinder, ? extends I> mCastFunction;
58     private final Executor mExecutor;
59     private final ServiceProviderFacade mServiceProvider;
60 
61     private final AtomicReference<I> mService = new AtomicReference();
62     private final Set<Consumer<I>> mOnStartTasks = ConcurrentHashMap.newKeySet();
63     private final Set<Consumer<I>> mOnDeathTasks = ConcurrentHashMap.newKeySet();
64 
65     private final IServiceCallback mServiceListener =
66             new IServiceCallback.Stub() {
67                 @Override
68                 public void onRegistration(String name, IBinder binder) {
69                     onServiceInited(binder);
70                 }
71             };
72 
73     // For test purposes
74     public static interface ServiceProviderFacade {
registerForNotifications(String name, IServiceCallback listener)75         public void registerForNotifications(String name, IServiceCallback listener);
76 
checkService(String name)77         public IBinder checkService(String name);
78 
waitForService(String name)79         public IBinder waitForService(String name);
80     }
81 
ServiceHolder( @onNull String serviceName, @NonNull Function<? super IBinder, ? extends I> castFunction, @NonNull Executor executor)82     public ServiceHolder(
83             @NonNull String serviceName,
84             @NonNull Function<? super IBinder, ? extends I> castFunction,
85             @NonNull Executor executor) {
86         this(
87                 serviceName,
88                 castFunction,
89                 executor,
90                 new ServiceProviderFacade() {
91                     @Override
92                     public void registerForNotifications(String name, IServiceCallback listener) {
93                         try {
94                             ServiceManager.registerForNotifications(name, listener);
95                         } catch (RemoteException e) {
96                             throw new IllegalStateException("ServiceManager died!!", e);
97                         }
98                     }
99 
100                     @Override
101                     public IBinder checkService(String name) {
102                         return ServiceManager.checkService(name);
103                     }
104 
105                     @Override
106                     public IBinder waitForService(String name) {
107                         return ServiceManager.waitForService(name);
108                     }
109                 });
110     }
111 
ServiceHolder( @onNull String serviceName, @NonNull Function<? super IBinder, ? extends I> castFunction, @NonNull Executor executor, @NonNull ServiceProviderFacade provider)112     public ServiceHolder(
113             @NonNull String serviceName,
114             @NonNull Function<? super IBinder, ? extends I> castFunction,
115             @NonNull Executor executor,
116             @NonNull ServiceProviderFacade provider) {
117         mServiceName = Objects.requireNonNull(serviceName);
118         mCastFunction = Objects.requireNonNull(castFunction);
119         mExecutor = Objects.requireNonNull(executor);
120         mServiceProvider = Objects.requireNonNull(provider);
121         mTag = "ServiceHolder: " + serviceName;
122         mServiceProvider.registerForNotifications(mServiceName, mServiceListener);
123     }
124 
125     /**
126      * Add tasks to run when service becomes available. Ran on the executor provided at
127      * construction. Note, for convenience, if the service is already connected, the task is
128      * immediately run.
129      */
registerOnStartTask(Consumer<I> task)130     public void registerOnStartTask(Consumer<I> task) {
131         mOnStartTasks.add(task);
132         I i;
133         if ((i = mService.get()) != null) {
134             mExecutor.execute(() -> task.accept(i));
135         }
136     }
137 
unregisterOnStartTask(Consumer<I> task)138     public void unregisterOnStartTask(Consumer<I> task) {
139         mOnStartTasks.remove(task);
140     }
141 
142     /**
143      * Add tasks to run when service goes down. Ran on the executor provided at construction. Should
144      * be called before getService to avoid dropping a death notification.
145      */
registerOnDeathTask(Consumer<I> task)146     public void registerOnDeathTask(Consumer<I> task) {
147         mOnDeathTasks.add(task);
148     }
149 
unregisterOnDeathTask(Consumer<I> task)150     public void unregisterOnDeathTask(Consumer<I> task) {
151         mOnDeathTasks.remove(task);
152     }
153 
154     @Override
binderDied(@onNull IBinder who)155     public void binderDied(@NonNull IBinder who) {
156         attemptClear(who);
157     }
158 
159     @Override
binderDied()160     public void binderDied() {
161         throw new AssertionError("Wrong binderDied called, this should never happen");
162     }
163 
164     /**
165      * Notify the holder that the service has gone done, usually in response to a RemoteException.
166      * Equivalent to receiving a binder death notification.
167      */
attemptClear(IBinder who)168     public void attemptClear(IBinder who) {
169         // Possibly prone to weird races, resulting in spurious dead/revive,
170         // but that should be fine.
171         var current = mService.get();
172         if (current != null
173                 && Objects.equals(current.asBinder(), who)
174                 && mService.compareAndSet(current, null)) {
175             who.unlinkToDeath(this, 0);
176             for (var r : mOnDeathTasks) {
177                 mExecutor.execute(() -> r.accept(current));
178             }
179         }
180     }
181 
182     /** Get the service, without blocking. Can trigger start tasks, on the provided executor. */
checkService()183     public @Nullable I checkService() {
184         var s = mService.get();
185         if (s != null) return s;
186         IBinder registered = mServiceProvider.checkService(mServiceName);
187         if (registered == null) return null;
188         return onServiceInited(registered);
189     }
190 
191     /** Get the service, but block. Can trigger start tasks, on the provided executor. */
waitForService()192     public @NonNull I waitForService() {
193         var s = mService.get();
194         return (s != null) ? s : onServiceInited(mServiceProvider.waitForService(mServiceName));
195     }
196 
197     /*
198      * Called when the native service is initialized.
199      */
onServiceInited(@onNull IBinder who)200     private @NonNull I onServiceInited(@NonNull IBinder who) {
201         var service = mCastFunction.apply(who);
202         Objects.requireNonNull(service);
203         if (!mService.compareAndSet(null, service)) {
204             return service;
205         }
206         // Even if the service has immediately died, we should perform these tasks for consistency
207         for (var r : mOnStartTasks) {
208             mExecutor.execute(() -> r.accept(service));
209         }
210         try {
211             who.linkToDeath(this, 0);
212         } catch (RemoteException e) {
213             Log.e(mTag, "Immediate service death. Service crash-looping");
214             attemptClear(who);
215         }
216         // This interface is non-null, but could represent a dead object
217         return service;
218     }
219 }
220