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 android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.os.Bundle; 22 import android.os.Handler; 23 import android.os.Parcel; 24 import android.os.Parcelable; 25 import android.os.RemoteException; 26 import android.os.ResultReceiver; 27 import android.util.Log; 28 import android.view.ViewRootImpl; 29 30 import java.util.ArrayList; 31 32 /** 33 * A {@link OnBackInvokedDispatcher} for IME that forwards {@link OnBackInvokedCallback} 34 * registrations from the IME process to the app process to be registered on the app window. 35 * <p> 36 * The app process creates and propagates an instance of {@link ImeOnBackInvokedDispatcher} 37 * to the IME to be set on the IME window's {@link WindowOnBackInvokedDispatcher}. 38 * <p> 39 * @see WindowOnBackInvokedDispatcher#setImeOnBackInvokedDispatcher 40 * 41 * @hide 42 */ 43 public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parcelable { 44 45 private static final String TAG = "ImeBackDispatcher"; 46 static final String RESULT_KEY_ID = "id"; 47 static final String RESULT_KEY_CALLBACK = "callback"; 48 static final String RESULT_KEY_PRIORITY = "priority"; 49 static final int RESULT_CODE_REGISTER = 0; 50 static final int RESULT_CODE_UNREGISTER = 1; 51 @NonNull 52 private final ResultReceiver mResultReceiver; 53 ImeOnBackInvokedDispatcher(Handler handler)54 public ImeOnBackInvokedDispatcher(Handler handler) { 55 mResultReceiver = new ResultReceiver(handler) { 56 @Override 57 public void onReceiveResult(int resultCode, Bundle resultData) { 58 WindowOnBackInvokedDispatcher dispatcher = getReceivingDispatcher(); 59 if (dispatcher != null) { 60 receive(resultCode, resultData, dispatcher); 61 } 62 } 63 }; 64 } 65 66 /** 67 * Override this method to return the {@link WindowOnBackInvokedDispatcher} of the window 68 * that should receive the forwarded callback. 69 */ 70 @Nullable getReceivingDispatcher()71 protected WindowOnBackInvokedDispatcher getReceivingDispatcher() { 72 return null; 73 } 74 ImeOnBackInvokedDispatcher(Parcel in)75 ImeOnBackInvokedDispatcher(Parcel in) { 76 mResultReceiver = in.readTypedObject(ResultReceiver.CREATOR); 77 } 78 79 @Override registerOnBackInvokedCallback( @nBackInvokedDispatcher.Priority int priority, @NonNull OnBackInvokedCallback callback)80 public void registerOnBackInvokedCallback( 81 @OnBackInvokedDispatcher.Priority int priority, 82 @NonNull OnBackInvokedCallback callback) { 83 final Bundle bundle = new Bundle(); 84 // Always invoke back for ime without checking the window focus. 85 // We use strong reference in the binder wrapper to avoid accidentally GC the callback. 86 // This is necessary because the callback is sent to and registered from 87 // the app process, which may treat the IME callback as weakly referenced. This will not 88 // cause a memory leak because the app side already clears the reference correctly. 89 final IOnBackInvokedCallback iCallback = 90 new WindowOnBackInvokedDispatcher.OnBackInvokedCallbackWrapper( 91 callback, false /* useWeakRef */); 92 bundle.putBinder(RESULT_KEY_CALLBACK, iCallback.asBinder()); 93 bundle.putInt(RESULT_KEY_PRIORITY, priority); 94 bundle.putInt(RESULT_KEY_ID, callback.hashCode()); 95 mResultReceiver.send(RESULT_CODE_REGISTER, bundle); 96 } 97 98 @Override unregisterOnBackInvokedCallback( @onNull OnBackInvokedCallback callback)99 public void unregisterOnBackInvokedCallback( 100 @NonNull OnBackInvokedCallback callback) { 101 Bundle bundle = new Bundle(); 102 bundle.putInt(RESULT_KEY_ID, callback.hashCode()); 103 mResultReceiver.send(RESULT_CODE_UNREGISTER, bundle); 104 } 105 106 @Override describeContents()107 public int describeContents() { 108 return 0; 109 } 110 111 @Override writeToParcel(@onNull Parcel dest, int flags)112 public void writeToParcel(@NonNull Parcel dest, int flags) { 113 dest.writeTypedObject(mResultReceiver, flags); 114 } 115 116 @NonNull 117 public static final Parcelable.Creator<ImeOnBackInvokedDispatcher> CREATOR = 118 new Parcelable.Creator<ImeOnBackInvokedDispatcher>() { 119 public ImeOnBackInvokedDispatcher createFromParcel(Parcel in) { 120 return new ImeOnBackInvokedDispatcher(in); 121 } 122 public ImeOnBackInvokedDispatcher[] newArray(int size) { 123 return new ImeOnBackInvokedDispatcher[size]; 124 } 125 }; 126 127 private final ArrayList<ImeOnBackInvokedCallback> mImeCallbacks = new ArrayList<>(); 128 receive( int resultCode, Bundle resultData, @NonNull WindowOnBackInvokedDispatcher receivingDispatcher)129 private void receive( 130 int resultCode, Bundle resultData, 131 @NonNull WindowOnBackInvokedDispatcher receivingDispatcher) { 132 final int callbackId = resultData.getInt(RESULT_KEY_ID); 133 if (resultCode == RESULT_CODE_REGISTER) { 134 int priority = resultData.getInt(RESULT_KEY_PRIORITY); 135 final IOnBackInvokedCallback callback = IOnBackInvokedCallback.Stub.asInterface( 136 resultData.getBinder(RESULT_KEY_CALLBACK)); 137 registerReceivedCallback( 138 callback, priority, callbackId, receivingDispatcher); 139 } else if (resultCode == RESULT_CODE_UNREGISTER) { 140 unregisterReceivedCallback(callbackId, receivingDispatcher); 141 } 142 } 143 registerReceivedCallback( @onNull IOnBackInvokedCallback iCallback, @OnBackInvokedDispatcher.Priority int priority, int callbackId, @NonNull WindowOnBackInvokedDispatcher receivingDispatcher)144 private void registerReceivedCallback( 145 @NonNull IOnBackInvokedCallback iCallback, 146 @OnBackInvokedDispatcher.Priority int priority, 147 int callbackId, 148 @NonNull WindowOnBackInvokedDispatcher receivingDispatcher) { 149 final ImeOnBackInvokedCallback imeCallback = 150 new ImeOnBackInvokedCallback(iCallback, callbackId, priority); 151 mImeCallbacks.add(imeCallback); 152 receivingDispatcher.registerOnBackInvokedCallbackUnchecked(imeCallback, priority); 153 } 154 unregisterReceivedCallback( int callbackId, OnBackInvokedDispatcher receivingDispatcher)155 private void unregisterReceivedCallback( 156 int callbackId, OnBackInvokedDispatcher receivingDispatcher) { 157 ImeOnBackInvokedCallback callback = null; 158 for (ImeOnBackInvokedCallback imeCallback : mImeCallbacks) { 159 if (imeCallback.getId() == callbackId) { 160 callback = imeCallback; 161 break; 162 } 163 } 164 if (callback == null) { 165 Log.e(TAG, "Ime callback not found. Ignoring unregisterReceivedCallback. " 166 + "callbackId: " + callbackId); 167 return; 168 } 169 receivingDispatcher.unregisterOnBackInvokedCallback(callback); 170 mImeCallbacks.remove(callback); 171 } 172 173 /** Clears all registered callbacks on the instance. */ clear()174 public void clear() { 175 // Unregister previously registered callbacks if there's any. 176 if (getReceivingDispatcher() != null) { 177 for (ImeOnBackInvokedCallback callback : mImeCallbacks) { 178 getReceivingDispatcher().unregisterOnBackInvokedCallback(callback); 179 } 180 } 181 mImeCallbacks.clear(); 182 } 183 184 static class ImeOnBackInvokedCallback implements OnBackInvokedCallback { 185 @NonNull 186 private final IOnBackInvokedCallback mIOnBackInvokedCallback; 187 /** 188 * The hashcode of the callback instance in the IME process, used as a unique id to 189 * identify the callback when it's passed between processes. 190 */ 191 private final int mId; 192 private final int mPriority; 193 ImeOnBackInvokedCallback(@onNull IOnBackInvokedCallback iCallback, int id, @Priority int priority)194 ImeOnBackInvokedCallback(@NonNull IOnBackInvokedCallback iCallback, int id, 195 @Priority int priority) { 196 mIOnBackInvokedCallback = iCallback; 197 mId = id; 198 mPriority = priority; 199 } 200 201 @Override onBackInvoked()202 public void onBackInvoked() { 203 try { 204 if (mIOnBackInvokedCallback != null) { 205 mIOnBackInvokedCallback.onBackInvoked(); 206 } 207 } catch (RemoteException e) { 208 Log.e(TAG, "Exception when invoking forwarded callback. e: ", e); 209 } 210 } 211 getId()212 private int getId() { 213 return mId; 214 } 215 getIOnBackInvokedCallback()216 IOnBackInvokedCallback getIOnBackInvokedCallback() { 217 return mIOnBackInvokedCallback; 218 } 219 220 @Override toString()221 public String toString() { 222 return "ImeCallback=ImeOnBackInvokedCallback@" + mId 223 + " Callback=" + mIOnBackInvokedCallback; 224 } 225 } 226 227 /** 228 * Transfers {@link ImeOnBackInvokedCallback}s registered on one {@link ViewRootImpl} to 229 * another {@link ViewRootImpl} on focus change. 230 * 231 * @param previous the previously focused {@link ViewRootImpl}. 232 * @param current the currently focused {@link ViewRootImpl}. 233 */ switchRootView(ViewRootImpl previous, ViewRootImpl current)234 public void switchRootView(ViewRootImpl previous, ViewRootImpl current) { 235 for (ImeOnBackInvokedCallback imeCallback : mImeCallbacks) { 236 if (previous != null) { 237 previous.getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(imeCallback); 238 } 239 if (current != null) { 240 current.getOnBackInvokedDispatcher().registerOnBackInvokedCallbackUnchecked( 241 imeCallback, imeCallback.mPriority); 242 } 243 } 244 } 245 } 246