• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  *      &#064;Override
72  *      public void onCancel(IBinder token) { remoteIFooService.onCancelToken(token); }
73  *
74  *      &#064;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  *    &#064;Override
91  *    public void doCancellableOperation(..., IBinder csToken) {
92  *      CancellationSignal cs = mCancellationSignalReceiver.unbeam(csToken))
93  *      // ...
94  *    }
95  *
96  *    &#064;Override
97  *    public void onCancelToken(..., IBinder csToken) {
98  *      mCancellationSignalReceiver.cancelToken(csToken))
99  *    }
100  *
101  *    &#064;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