1 /* 2 * Copyright (C) 2022 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 android.os; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.SuppressLint; 22 import android.system.SystemCleaner; 23 import android.util.Pair; 24 import android.view.inputmethod.CancellableHandwritingGesture; 25 import android.view.inputmethod.HandwritingGesture; 26 27 import java.lang.ref.Cleaner; 28 import java.lang.ref.Reference; 29 import java.util.ArrayList; 30 import java.util.HashMap; 31 32 /** 33 * A transport for {@link CancellationSignal}, but unlike 34 * {@link CancellationSignal#createTransport()} doesn't require pre-creating the transport in the 35 * target process. Instead, cancellation is forwarded over the same IPC surface as the cancellable 36 * request. 37 * 38 * <p><strong>Important:</strong> For this to work, the following invariants must be held up: 39 * <ul> 40 * <li>A call to beam() <strong>MUST</strong> result in a call to close() on the result 41 * (otherwise, the token will be leaked and cancellation isn't propagated), and that call 42 * must happen after the call using the 43 * token is sent (otherwise, any concurrent cancellation may be lost). It is strongly 44 * recommended to use try-with-resources on the token. 45 * <li>The cancel(), forget() and cancellable operations transporting the token must either 46 * all be oneway on the same binder, or all be non-oneway to guarantee proper ordering. 47 * <li>A {@link CancellationSignal} <strong>SHOULD</strong> be used only once, as there 48 * can only be a single {@link android.os.CancellationSignal.OnCancelListener OnCancelListener}. 49 * 50 * </ul> 51 * <p>Caveats: 52 * <ul> 53 * <li>Cancellation is only ever dispatched after the token is closed, and thus after the 54 * call performing the cancellable operation (if the invariants are followed). The operation 55 * must therefore not block the incoming binder thread, or cancellation won't be possible. 56 * <li>Consequently, in the unlikely event that the sender dies right after beaming an already 57 * cancelled {@link CancellationSignal}, the cancellation may be lost (unlike with 58 * {@link CancellationSignal#createTransport()}). 59 * <li>The forwarding OnCancelListener is set in the implied finally phase of try-with-resources 60 * / when closing the token. If the receiver is in the same process, and the signal is 61 * already cancelled, this may invoke the target's OnCancelListener during that phase. 62 * </ul> 63 * 64 * 65 * <p>Usage: 66 * <pre> 67 * // Sender: 68 * 69 * class FooManager { 70 * var mCancellationSignalSender = new CancellationSignalBeamer.Sender() { 71 * @Override 72 * public void onCancel(IBinder token) { remoteIFooService.onCancelToken(token); } 73 * 74 * @Override 75 * public void onForget(IBinder token) { remoteIFooService.onForgetToken(token); } 76 * }; 77 * 78 * public void doCancellableOperation(..., CancellationSignal cs) { 79 * try (var csToken = mCancellationSignalSender.beam(cs)) { 80 * remoteIFooService.doCancellableOperation(..., csToken); 81 * } 82 * } 83 * } 84 * 85 * // Receiver: 86 * 87 * class FooManagerService extends IFooService.Stub { 88 * var mCancellationSignalReceiver = new CancellationSignalBeamer.Receiver(); 89 * 90 * @Override 91 * public void doCancellableOperation(..., IBinder csToken) { 92 * CancellationSignal cs = mCancellationSignalReceiver.unbeam(csToken)) 93 * // ... 94 * } 95 * 96 * @Override 97 * public void onCancelToken(..., IBinder csToken) { 98 * mCancellationSignalReceiver.cancelToken(csToken)) 99 * } 100 * 101 * @Override 102 * public void onForgetToken(..., IBinder csToken) { 103 * mCancellationSignalReceiver.forgetToken(csToken)) 104 * } 105 * } 106 * 107 * </pre> 108 * 109 * @hide 110 */ 111 public class CancellationSignalBeamer { 112 113 static final Cleaner sCleaner = SystemCleaner.cleaner(); 114 115 /** The sending side of an {@link CancellationSignalBeamer} */ 116 public abstract static class Sender { 117 118 /** 119 * Beams a {@link CancellationSignal} through an existing Binder interface. 120 * 121 * @param cs the {@code CancellationSignal} to beam, or {@code null}. 122 * @return an {@link IBinder} token. MUST be {@link CloseableToken#close}d <em>after</em> 123 * the binder call transporting it to the remote process, best with 124 * try-with-resources. {@code null} if {@code cs} was {@code null}. 125 */ 126 // TODO(b/254888024): @MustBeClosed 127 @Nullable beam(@ullable CancellationSignal cs)128 public CloseableToken beam(@Nullable CancellationSignal cs) { 129 if (cs == null) { 130 return null; 131 } 132 return new Token(this, cs); 133 } 134 135 /** 136 * A {@link #beam}ed {@link CancellationSignal} was closed. 137 * 138 * MUST be forwarded to {@link Receiver#cancel} with proper ordering. See 139 * {@link CancellationSignalBeamer} for details. 140 */ onCancel(@onNull IBinder token)141 public abstract void onCancel(@NonNull IBinder token); 142 143 /** 144 * A {@link #beam}ed {@link CancellationSignal} was GC'd. 145 * 146 * MUST be forwarded to {@link Receiver#forget} with proper ordering. See 147 * {@link CancellationSignalBeamer} for details. 148 */ onForget(@onNull IBinder token)149 public abstract void onForget(@NonNull IBinder token); 150 151 private static final ThreadLocal<Pair<Sender, ArrayList<CloseableToken>>> sScope = 152 new ThreadLocal<>(); 153 154 /** 155 * Beams a {@link CancellationSignal} through an existing Binder interface. 156 * @param gesture {@link HandwritingGesture} that supports 157 * {@link CancellableHandwritingGesture cancellation} requesting cancellation token. 158 * @return {@link IBinder} token. MUST be {@link MustClose#close}d <em>after</em> 159 * the binder call transporting it to the remote process, best with 160 * try-with-resources. {@code null} if {@code cs} was {@code null} or if 161 * {@link HandwritingGesture} isn't {@link CancellableHandwritingGesture cancellable}. 162 */ 163 @NonNull beamScopeIfNeeded(@onNull HandwritingGesture gesture)164 public MustClose beamScopeIfNeeded(@NonNull HandwritingGesture gesture) { 165 if (!(gesture instanceof CancellableHandwritingGesture)) { 166 return null; 167 } 168 sScope.set(Pair.create(this, new ArrayList<>())); 169 return () -> { 170 var tokens = sScope.get().second; 171 sScope.remove(); 172 for (int i = tokens.size() - 1; i >= 0; i--) { 173 if (tokens.get(i) != null) { 174 tokens.get(i).close(); 175 } 176 } 177 }; 178 } 179 180 /** 181 * An {@link AutoCloseable} interface with {@link AutoCloseable#close()} callback. 182 */ 183 public interface MustClose extends AutoCloseable { 184 @Override close()185 void close(); 186 } 187 188 /** 189 * Beams a {@link CancellationSignal} token from existing scope created by previous call to 190 * {@link #beamScopeIfNeeded()} 191 * @param cs {@link CancellationSignal} for which token should be returned. 192 * @return {@link IBinder} token. 193 */ 194 @NonNull beamFromScope(@onNull CancellationSignal cs)195 public static IBinder beamFromScope(@NonNull CancellationSignal cs) { 196 var state = sScope.get(); 197 if (state != null) { 198 var token = state.first.beam(cs); 199 state.second.add(token); 200 return token; 201 } 202 return null; 203 } 204 205 private static class Token extends Binder implements CloseableToken, Runnable { 206 207 private final Sender mSender; 208 private Preparer mPreparer; 209 Token(Sender sender, CancellationSignal signal)210 private Token(Sender sender, CancellationSignal signal) { 211 mSender = sender; 212 mPreparer = new Preparer(sender, signal, this); 213 } 214 215 @Override close()216 public void close() { 217 Preparer preparer = mPreparer; 218 mPreparer = null; 219 if (preparer != null) { 220 preparer.setup(); 221 } 222 } 223 224 @Override run()225 public void run() { 226 mSender.onForget(this); 227 } 228 229 private static class Preparer implements CancellationSignal.OnCancelListener { 230 private final Sender mSender; 231 private final CancellationSignal mSignal; 232 private final Token mToken; 233 Preparer(Sender sender, CancellationSignal signal, Token token)234 private Preparer(Sender sender, CancellationSignal signal, Token token) { 235 mSender = sender; 236 mSignal = signal; 237 mToken = token; 238 } 239 setup()240 void setup() { 241 sCleaner.register(this, mToken); 242 mSignal.setOnCancelListener(this); 243 } 244 245 @Override onCancel()246 public void onCancel() { 247 try { 248 mSender.onCancel(mToken); 249 } finally { 250 // Make sure we dispatch onCancel before the cleaner can run. 251 Reference.reachabilityFence(this); 252 } 253 } 254 } 255 } 256 257 /** 258 * A {@link #beam}ed {@link CancellationSignal} ready for sending over Binder. 259 * 260 * MUST be closed <em>after</em> it is sent over binder, ideally through try-with-resources. 261 */ 262 public interface CloseableToken extends IBinder, MustClose { 263 @Override close()264 void close(); // No throws 265 } 266 } 267 268 /** The receiving side of a {@link CancellationSignalBeamer}. */ 269 public static class Receiver implements IBinder.DeathRecipient { 270 private final HashMap<IBinder, CancellationSignal> mTokenMap = new HashMap<>(); 271 private final boolean mCancelOnSenderDeath; 272 273 /** 274 * Constructs a new {@code Receiver}. 275 * 276 * @param cancelOnSenderDeath if true, {@link CancellationSignal}s obtained from 277 * {@link #unbeam} are automatically {@link #cancel}led if the sender token 278 * {@link Binder#linkToDeath dies}; otherwise they are simnply dropped. Note: if the 279 * sending process drops all references to the {@link CancellationSignal} before 280 * process death, the cancellation is not guaranteed. 281 */ Receiver(boolean cancelOnSenderDeath)282 public Receiver(boolean cancelOnSenderDeath) { 283 mCancelOnSenderDeath = cancelOnSenderDeath; 284 } 285 286 /** 287 * Unbeams a token that was obtained via {@link Sender#beam} and turns it back into a 288 * {@link CancellationSignal}. 289 * 290 * A subsequent call to {@link #cancel} with the same token will cancel the returned 291 * {@code CancellationSignal}. 292 * 293 * @param token a token that was obtained from {@link Sender}, possibly in a remote process. 294 * @return a {@link CancellationSignal} linked to the given token. 295 */ 296 @Nullable 297 @SuppressLint("VisiblySynchronized") unbeam(@ullable IBinder token)298 public CancellationSignal unbeam(@Nullable IBinder token) { 299 if (token == null) { 300 return null; 301 } 302 synchronized (this) { 303 CancellationSignal cs = mTokenMap.get(token); 304 if (cs != null) { 305 return cs; 306 } 307 308 cs = new CancellationSignal(); 309 mTokenMap.put(token, cs); 310 try { 311 token.linkToDeath(this, 0); 312 } catch (RemoteException e) { 313 dead(token); 314 } 315 return cs; 316 } 317 } 318 319 /** 320 * Forgets state associated with the given token (if any). 321 * 322 * Subsequent calls to {@link #cancel} or binder death notifications on the token will not 323 * have any effect. 324 * 325 * This MUST be invoked when forwarding {@link Sender#onForget}, otherwise the token and 326 * {@link CancellationSignal} will leak if the token was ever {@link #unbeam}ed. 327 * 328 * Optionally, the receiving service logic may also invoke this if it can guarantee that 329 * the unbeamed CancellationSignal isn't needed anymore (i.e. the cancellable operation 330 * using the CancellationSignal has been fully completed). 331 * 332 * @param token the token to forget. No-op if {@code null}. 333 */ 334 @SuppressLint("VisiblySynchronized") forget(@ullable IBinder token)335 public void forget(@Nullable IBinder token) { 336 synchronized (this) { 337 if (mTokenMap.remove(token) != null) { 338 token.unlinkToDeath(this, 0); 339 } 340 } 341 } 342 343 /** 344 * Cancels the {@link CancellationSignal} associated with the given token (if any). 345 * 346 * This MUST be invoked when forwarding {@link Sender#onCancel}, otherwise the token and 347 * {@link CancellationSignal} will leak if the token was ever {@link #unbeam}ed. 348 * 349 * Optionally, the receiving service logic may also invoke this if it can guarantee that 350 * the unbeamed CancellationSignal isn't needed anymore (i.e. the cancellable operation 351 * using the CancellationSignal has been fully completed). 352 * 353 * @param token the token to forget. No-op if {@code null}. 354 */ 355 @SuppressLint("VisiblySynchronized") cancel(@ullable IBinder token)356 public void cancel(@Nullable IBinder token) { 357 CancellationSignal cs; 358 synchronized (this) { 359 cs = mTokenMap.get(token); 360 if (cs != null) { 361 forget(token); 362 } else { 363 return; 364 } 365 } 366 cs.cancel(); 367 } 368 dead(@onNull IBinder token)369 private void dead(@NonNull IBinder token) { 370 if (mCancelOnSenderDeath) { 371 cancel(token); 372 } else { 373 forget(token); 374 } 375 } 376 377 @Override binderDied(@onNull IBinder who)378 public void binderDied(@NonNull IBinder who) { 379 dead(who); 380 } 381 382 @Override binderDied()383 public void binderDied() { 384 throw new RuntimeException("unreachable"); 385 } 386 } 387 } 388