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