• 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.window;
18 
19 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
20 import static com.android.window.flags.Flags.predictiveBackTimestampApi;
21 
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.os.Bundle;
25 import android.os.Handler;
26 import android.os.Parcel;
27 import android.os.Parcelable;
28 import android.os.RemoteException;
29 import android.os.ResultReceiver;
30 import android.util.Log;
31 import android.util.Pair;
32 import android.view.ViewRootImpl;
33 
34 import com.android.internal.annotations.VisibleForTesting;
35 
36 import java.util.ArrayDeque;
37 import java.util.ArrayList;
38 import java.util.function.Consumer;
39 
40 /**
41  * A {@link OnBackInvokedDispatcher} for IME that forwards {@link OnBackInvokedCallback}
42  * registrations from the IME process to the app process to be registered on the app window.
43  * <p>
44  * The app process creates and propagates an instance of {@link ImeOnBackInvokedDispatcher}
45  * to the IME to be set on the IME window's {@link WindowOnBackInvokedDispatcher}.
46  * <p>
47  * @see WindowOnBackInvokedDispatcher#setImeOnBackInvokedDispatcher
48  *
49  * @hide
50  */
51 public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parcelable {
52 
53     private static final String TAG = "ImeBackDispatcher";
54     static final String RESULT_KEY_ID = "id";
55     static final String RESULT_KEY_CALLBACK = "callback";
56     static final String RESULT_KEY_PRIORITY = "priority";
57     static final int RESULT_CODE_REGISTER = 0;
58     static final int RESULT_CODE_UNREGISTER = 1;
59     @NonNull
60     private final ResultReceiver mResultReceiver;
61     // The handler to run callbacks on. This should be on the same thread
62     // the ViewRootImpl holding IME's WindowOnBackInvokedDispatcher is created on.
63     private Handler mHandler;
64     private final ArrayDeque<Pair<Integer, Bundle>> mQueuedReceive = new ArrayDeque<>();
ImeOnBackInvokedDispatcher(Handler handler)65     public ImeOnBackInvokedDispatcher(Handler handler) {
66         mResultReceiver = new ResultReceiver(handler) {
67             @Override
68             public void onReceiveResult(int resultCode, Bundle resultData) {
69                 WindowOnBackInvokedDispatcher dispatcher = getReceivingDispatcher();
70                 if (dispatcher != null) {
71                     receive(resultCode, resultData, dispatcher);
72                 } else {
73                     mQueuedReceive.add(new Pair<>(resultCode, resultData));
74                 }
75             }
76         };
77     }
78 
79     /** Set receiving dispatcher to consume queued receiving events. */
updateReceivingDispatcher(@onNull WindowOnBackInvokedDispatcher dispatcher)80     public void updateReceivingDispatcher(@NonNull WindowOnBackInvokedDispatcher dispatcher) {
81         while (!mQueuedReceive.isEmpty()) {
82             final Pair<Integer, Bundle> queuedMessage = mQueuedReceive.poll();
83             receive(queuedMessage.first, queuedMessage.second, dispatcher);
84         }
85     }
86 
87 
setHandler(@onNull Handler handler)88     void setHandler(@NonNull Handler handler) {
89         mHandler = handler;
90     }
91 
92     /**
93      * Override this method to return the {@link WindowOnBackInvokedDispatcher} of the window
94      * that should receive the forwarded callback.
95      */
96     @Nullable
getReceivingDispatcher()97     protected WindowOnBackInvokedDispatcher getReceivingDispatcher() {
98         return null;
99     }
100 
ImeOnBackInvokedDispatcher(Parcel in)101     ImeOnBackInvokedDispatcher(Parcel in) {
102         mResultReceiver = in.readTypedObject(ResultReceiver.CREATOR);
103     }
104 
105     @Override
registerOnBackInvokedCallback( @nBackInvokedDispatcher.Priority int priority, @NonNull OnBackInvokedCallback callback)106     public void registerOnBackInvokedCallback(
107             @OnBackInvokedDispatcher.Priority int priority,
108             @NonNull OnBackInvokedCallback callback) {
109         final Bundle bundle = new Bundle();
110         // Always invoke back for ime without checking the window focus.
111         // We use strong reference in the binder wrapper to avoid accidentally GC the callback.
112         // This is necessary because the callback is sent to and registered from
113         // the app process, which may treat the IME callback as weakly referenced. This will not
114         // cause a memory leak because the app side already clears the reference correctly.
115         final IOnBackInvokedCallback iCallback = new ImeOnBackInvokedCallbackWrapper(callback);
116         bundle.putBinder(RESULT_KEY_CALLBACK, iCallback.asBinder());
117         bundle.putInt(RESULT_KEY_PRIORITY, priority);
118         bundle.putInt(RESULT_KEY_ID, callback.hashCode());
119         mResultReceiver.send(RESULT_CODE_REGISTER, bundle);
120     }
121 
122     @Override
unregisterOnBackInvokedCallback( @onNull OnBackInvokedCallback callback)123     public void unregisterOnBackInvokedCallback(
124             @NonNull OnBackInvokedCallback callback) {
125         Bundle bundle = new Bundle();
126         bundle.putInt(RESULT_KEY_ID, callback.hashCode());
127         mResultReceiver.send(RESULT_CODE_UNREGISTER, bundle);
128     }
129 
130     @Override
describeContents()131     public int describeContents() {
132         return 0;
133     }
134 
135     @Override
writeToParcel(@onNull Parcel dest, int flags)136     public void writeToParcel(@NonNull Parcel dest, int flags) {
137         dest.writeTypedObject(mResultReceiver, flags);
138     }
139 
140     @NonNull
141     public static final Parcelable.Creator<ImeOnBackInvokedDispatcher> CREATOR =
142             new Parcelable.Creator<ImeOnBackInvokedDispatcher>() {
143                 public ImeOnBackInvokedDispatcher createFromParcel(Parcel in) {
144                     return new ImeOnBackInvokedDispatcher(in);
145                 }
146                 public ImeOnBackInvokedDispatcher[] newArray(int size) {
147                     return new ImeOnBackInvokedDispatcher[size];
148                 }
149             };
150 
151     private final ArrayList<ImeOnBackInvokedCallback> mImeCallbacks = new ArrayList<>();
152 
receive( int resultCode, Bundle resultData, @NonNull WindowOnBackInvokedDispatcher receivingDispatcher)153     private void receive(
154             int resultCode, Bundle resultData,
155             @NonNull WindowOnBackInvokedDispatcher receivingDispatcher) {
156         if (resultCode == RESULT_CODE_REGISTER) {
157             final int callbackId = resultData.getInt(RESULT_KEY_ID);
158             int priority = resultData.getInt(RESULT_KEY_PRIORITY);
159             final IOnBackInvokedCallback callback = IOnBackInvokedCallback.Stub.asInterface(
160                     resultData.getBinder(RESULT_KEY_CALLBACK));
161             registerReceivedCallback(callback, priority, callbackId, receivingDispatcher);
162         } else if (resultCode == RESULT_CODE_UNREGISTER) {
163             final int callbackId = resultData.getInt(RESULT_KEY_ID);
164             unregisterReceivedCallback(callbackId, receivingDispatcher);
165         }
166     }
167 
registerReceivedCallback( @onNull IOnBackInvokedCallback iCallback, @OnBackInvokedDispatcher.Priority int priority, int callbackId, @NonNull WindowOnBackInvokedDispatcher receivingDispatcher)168     private void registerReceivedCallback(
169             @NonNull IOnBackInvokedCallback iCallback,
170             @OnBackInvokedDispatcher.Priority int priority,
171             int callbackId,
172             @NonNull WindowOnBackInvokedDispatcher receivingDispatcher) {
173         final ImeOnBackInvokedCallback imeCallback;
174         if (priority == PRIORITY_SYSTEM) {
175             // A callback registration with PRIORITY_SYSTEM indicates that a predictive back
176             // animation can be played on the IME. Therefore register the
177             // DefaultImeOnBackInvokedCallback with the receiving dispatcher and override the
178             // priority to PRIORITY_DEFAULT.
179             priority = PRIORITY_DEFAULT;
180             imeCallback = new DefaultImeOnBackAnimationCallback(iCallback, callbackId, priority);
181         } else {
182             imeCallback = new ImeOnBackInvokedCallback(iCallback, callbackId, priority);
183         }
184         mImeCallbacks.add(imeCallback);
185         receivingDispatcher.registerOnBackInvokedCallbackUnchecked(imeCallback, priority);
186     }
187 
unregisterReceivedCallback( int callbackId, OnBackInvokedDispatcher receivingDispatcher)188     private void unregisterReceivedCallback(
189             int callbackId, OnBackInvokedDispatcher receivingDispatcher) {
190         ImeOnBackInvokedCallback callback = null;
191         for (ImeOnBackInvokedCallback imeCallback : mImeCallbacks) {
192             if (imeCallback.getId() == callbackId) {
193                 callback = imeCallback;
194                 break;
195             }
196         }
197         if (callback == null) {
198             Log.e(TAG, "Ime callback not found. Ignoring unregisterReceivedCallback. "
199                     + "callbackId: " + callbackId);
200             return;
201         }
202         receivingDispatcher.unregisterOnBackInvokedCallback(callback);
203         mImeCallbacks.remove(callback);
204     }
205 
206     /**
207      * Unregisters all callbacks on the receiving dispatcher but keeps a reference of the callbacks
208      * in case the clearance is reverted in
209      * {@link ImeOnBackInvokedDispatcher#undoPreliminaryClear()}.
210      */
preliminaryClear()211     public void preliminaryClear() {
212         // Unregister previously registered callbacks if there's any.
213         if (getReceivingDispatcher() != null) {
214             for (ImeOnBackInvokedCallback callback : mImeCallbacks) {
215                 getReceivingDispatcher().unregisterOnBackInvokedCallback(callback);
216             }
217         }
218     }
219 
220     /**
221      * Reregisters all callbacks on the receiving dispatcher that have previously been cleared by
222      * calling {@link ImeOnBackInvokedDispatcher#preliminaryClear()}. This can happen if an IME hide
223      * animation is interrupted causing the IME to reappear.
224      */
undoPreliminaryClear()225     public void undoPreliminaryClear() {
226         if (getReceivingDispatcher() != null) {
227             for (ImeOnBackInvokedCallback callback : mImeCallbacks) {
228                 getReceivingDispatcher().registerOnBackInvokedCallbackUnchecked(callback,
229                         callback.mPriority);
230             }
231         }
232     }
233 
234     /** Clears all registered callbacks on the instance. */
clear()235     public void clear() {
236         // Unregister previously registered callbacks if there's any.
237         if (getReceivingDispatcher() != null) {
238             for (ImeOnBackInvokedCallback callback : mImeCallbacks) {
239                 getReceivingDispatcher().unregisterOnBackInvokedCallback(callback);
240             }
241         }
242         mImeCallbacks.clear();
243         mQueuedReceive.clear();
244     }
245 
246     @VisibleForTesting(visibility = PACKAGE)
247     public static class ImeOnBackInvokedCallback implements OnBackAnimationCallback {
248         @NonNull
249         private final IOnBackInvokedCallback mIOnBackInvokedCallback;
250         /**
251          * The hashcode of the callback instance in the IME process, used as a unique id to
252          * identify the callback when it's passed between processes.
253          */
254         private final int mId;
255         private final int mPriority;
256 
ImeOnBackInvokedCallback(@onNull IOnBackInvokedCallback iCallback, int id, @Priority int priority)257         ImeOnBackInvokedCallback(@NonNull IOnBackInvokedCallback iCallback, int id,
258                 @Priority int priority) {
259             mIOnBackInvokedCallback = iCallback;
260             mId = id;
261             mPriority = priority;
262         }
263 
264         @Override
onBackStarted(@onNull BackEvent backEvent)265         public void onBackStarted(@NonNull BackEvent backEvent) {
266             try {
267                 long frameTime = 0;
268                 if (predictiveBackTimestampApi()) {
269                     frameTime = backEvent.getFrameTimeMillis();
270                 }
271                 mIOnBackInvokedCallback.onBackStarted(
272                         new BackMotionEvent(backEvent.getTouchX(), backEvent.getTouchY(), frameTime,
273                                 backEvent.getProgress(), false, backEvent.getSwipeEdge(),
274                                 null));
275             } catch (RemoteException e) {
276                 Log.e(TAG, "Exception when invoking forwarded callback. e: ", e);
277             }
278         }
279 
280         @Override
onBackProgressed(@onNull BackEvent backEvent)281         public void onBackProgressed(@NonNull BackEvent backEvent) {
282             try {
283                 long frameTime = 0;
284                 if (predictiveBackTimestampApi()) {
285                     frameTime = backEvent.getFrameTimeMillis();
286                 }
287                 mIOnBackInvokedCallback.onBackProgressed(
288                         new BackMotionEvent(backEvent.getTouchX(), backEvent.getTouchY(), frameTime,
289                                 backEvent.getProgress(), false, backEvent.getSwipeEdge(),
290                                 null));
291             } catch (RemoteException e) {
292                 Log.e(TAG, "Exception when invoking forwarded callback. e: ", e);
293             }
294         }
295 
296         @Override
onBackInvoked()297         public void onBackInvoked() {
298             try {
299                 mIOnBackInvokedCallback.onBackInvoked();
300             } catch (RemoteException e) {
301                 Log.e(TAG, "Exception when invoking forwarded callback. e: ", e);
302             }
303         }
304 
305         @Override
onBackCancelled()306         public void onBackCancelled() {
307             try {
308                 mIOnBackInvokedCallback.onBackCancelled();
309             } catch (RemoteException e) {
310                 Log.e(TAG, "Exception when invoking forwarded callback. e: ", e);
311             }
312         }
313 
getId()314         private int getId() {
315             return mId;
316         }
317 
318         @Override
toString()319         public String toString() {
320             return "ImeCallback=ImeOnBackInvokedCallback@" + mId
321                     + " Callback=" + mIOnBackInvokedCallback;
322         }
323     }
324 
325     /**
326      * Subclass of ImeOnBackInvokedCallback indicating that a predictive IME back animation may be
327      * played instead of invoking the callback.
328      */
329     @VisibleForTesting(visibility = PACKAGE)
330     public static class DefaultImeOnBackAnimationCallback extends ImeOnBackInvokedCallback {
DefaultImeOnBackAnimationCallback(@onNull IOnBackInvokedCallback iCallback, int id, int priority)331         DefaultImeOnBackAnimationCallback(@NonNull IOnBackInvokedCallback iCallback, int id,
332                 int priority) {
333             super(iCallback, id, priority);
334         }
335     }
336 
337     /**
338      * Transfers {@link ImeOnBackInvokedCallback}s registered on one {@link ViewRootImpl} to
339      * another {@link ViewRootImpl} on focus change.
340      *
341      * @param previous the previously focused {@link ViewRootImpl}.
342      * @param current the currently focused {@link ViewRootImpl}.
343      */
switchRootView(ViewRootImpl previous, ViewRootImpl current)344     public void switchRootView(ViewRootImpl previous, ViewRootImpl current) {
345         for (ImeOnBackInvokedCallback imeCallback : mImeCallbacks) {
346             if (previous != null) {
347                 previous.getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(imeCallback);
348             }
349             if (current != null) {
350                 current.getOnBackInvokedDispatcher().registerOnBackInvokedCallbackUnchecked(
351                         imeCallback, imeCallback.mPriority);
352             }
353         }
354     }
355 
356     /**
357      * Wrapper class that wraps an OnBackInvokedCallback. This is used when a callback is sent from
358      * the IME process to the app process.
359      */
360     private class ImeOnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub {
361 
362         private final OnBackInvokedCallback mCallback;
363 
ImeOnBackInvokedCallbackWrapper(@onNull OnBackInvokedCallback callback)364         ImeOnBackInvokedCallbackWrapper(@NonNull OnBackInvokedCallback callback) {
365             mCallback = callback;
366         }
367 
368         @Override
onBackStarted(BackMotionEvent backMotionEvent)369         public void onBackStarted(BackMotionEvent backMotionEvent) {
370             maybeRunOnAnimationCallback((animationCallback) -> animationCallback.onBackStarted(
371                     BackEvent.fromBackMotionEvent(backMotionEvent)));
372         }
373 
374         @Override
onBackProgressed(BackMotionEvent backMotionEvent)375         public void onBackProgressed(BackMotionEvent backMotionEvent) {
376             maybeRunOnAnimationCallback((animationCallback) -> animationCallback.onBackProgressed(
377                     BackEvent.fromBackMotionEvent(backMotionEvent)));
378         }
379 
380         @Override
onBackCancelled()381         public void onBackCancelled() {
382             maybeRunOnAnimationCallback(OnBackAnimationCallback::onBackCancelled);
383         }
384 
385         @Override
onBackInvoked()386         public void onBackInvoked() {
387             mHandler.post(mCallback::onBackInvoked);
388         }
389 
390         @Override
setTriggerBack(boolean triggerBack)391         public void setTriggerBack(boolean triggerBack) {
392             // no-op
393         }
394 
395         @Override
setHandoffHandler(IBackAnimationHandoffHandler handoffHandler)396         public void setHandoffHandler(IBackAnimationHandoffHandler handoffHandler) {
397             // no-op
398         }
399 
maybeRunOnAnimationCallback(Consumer<OnBackAnimationCallback> block)400         private void maybeRunOnAnimationCallback(Consumer<OnBackAnimationCallback> block) {
401             if (mCallback instanceof OnBackAnimationCallback) {
402                 mHandler.post(() -> block.accept((OnBackAnimationCallback) mCallback));
403             }
404         }
405     }
406 }
407