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