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