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