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 final IOnBackInvokedCallback iCallback = 86 new WindowOnBackInvokedDispatcher.OnBackInvokedCallbackWrapper(callback); 87 bundle.putBinder(RESULT_KEY_CALLBACK, iCallback.asBinder()); 88 bundle.putInt(RESULT_KEY_PRIORITY, priority); 89 bundle.putInt(RESULT_KEY_ID, callback.hashCode()); 90 mResultReceiver.send(RESULT_CODE_REGISTER, bundle); 91 } 92 93 @Override unregisterOnBackInvokedCallback( @onNull OnBackInvokedCallback callback)94 public void unregisterOnBackInvokedCallback( 95 @NonNull OnBackInvokedCallback callback) { 96 Bundle bundle = new Bundle(); 97 bundle.putInt(RESULT_KEY_ID, callback.hashCode()); 98 mResultReceiver.send(RESULT_CODE_UNREGISTER, bundle); 99 } 100 101 @Override describeContents()102 public int describeContents() { 103 return 0; 104 } 105 106 @Override writeToParcel(@onNull Parcel dest, int flags)107 public void writeToParcel(@NonNull Parcel dest, int flags) { 108 dest.writeTypedObject(mResultReceiver, flags); 109 } 110 111 @NonNull 112 public static final Parcelable.Creator<ImeOnBackInvokedDispatcher> CREATOR = 113 new Parcelable.Creator<ImeOnBackInvokedDispatcher>() { 114 public ImeOnBackInvokedDispatcher createFromParcel(Parcel in) { 115 return new ImeOnBackInvokedDispatcher(in); 116 } 117 public ImeOnBackInvokedDispatcher[] newArray(int size) { 118 return new ImeOnBackInvokedDispatcher[size]; 119 } 120 }; 121 122 private final ArrayList<ImeOnBackInvokedCallback> mImeCallbacks = new ArrayList<>(); 123 receive( int resultCode, Bundle resultData, @NonNull WindowOnBackInvokedDispatcher receivingDispatcher)124 private void receive( 125 int resultCode, Bundle resultData, 126 @NonNull WindowOnBackInvokedDispatcher receivingDispatcher) { 127 final int callbackId = resultData.getInt(RESULT_KEY_ID); 128 if (resultCode == RESULT_CODE_REGISTER) { 129 int priority = resultData.getInt(RESULT_KEY_PRIORITY); 130 final IOnBackInvokedCallback callback = IOnBackInvokedCallback.Stub.asInterface( 131 resultData.getBinder(RESULT_KEY_CALLBACK)); 132 registerReceivedCallback( 133 callback, priority, callbackId, receivingDispatcher); 134 } else if (resultCode == RESULT_CODE_UNREGISTER) { 135 unregisterReceivedCallback(callbackId, receivingDispatcher); 136 } 137 } 138 registerReceivedCallback( @onNull IOnBackInvokedCallback iCallback, @OnBackInvokedDispatcher.Priority int priority, int callbackId, @NonNull WindowOnBackInvokedDispatcher receivingDispatcher)139 private void registerReceivedCallback( 140 @NonNull IOnBackInvokedCallback iCallback, 141 @OnBackInvokedDispatcher.Priority int priority, 142 int callbackId, 143 @NonNull WindowOnBackInvokedDispatcher receivingDispatcher) { 144 final ImeOnBackInvokedCallback imeCallback = 145 new ImeOnBackInvokedCallback(iCallback, callbackId, priority); 146 mImeCallbacks.add(imeCallback); 147 receivingDispatcher.registerOnBackInvokedCallbackUnchecked(imeCallback, priority); 148 } 149 unregisterReceivedCallback( int callbackId, OnBackInvokedDispatcher receivingDispatcher)150 private void unregisterReceivedCallback( 151 int callbackId, OnBackInvokedDispatcher receivingDispatcher) { 152 ImeOnBackInvokedCallback callback = null; 153 for (ImeOnBackInvokedCallback imeCallback : mImeCallbacks) { 154 if (imeCallback.getId() == callbackId) { 155 callback = imeCallback; 156 break; 157 } 158 } 159 if (callback == null) { 160 Log.e(TAG, "Ime callback not found. Ignoring unregisterReceivedCallback. " 161 + "callbackId: " + callbackId); 162 return; 163 } 164 receivingDispatcher.unregisterOnBackInvokedCallback(callback); 165 mImeCallbacks.remove(callback); 166 } 167 168 /** Clears all registered callbacks on the instance. */ clear()169 public void clear() { 170 // Unregister previously registered callbacks if there's any. 171 if (getReceivingDispatcher() != null) { 172 for (ImeOnBackInvokedCallback callback : mImeCallbacks) { 173 getReceivingDispatcher().unregisterOnBackInvokedCallback(callback); 174 } 175 } 176 mImeCallbacks.clear(); 177 } 178 179 static class ImeOnBackInvokedCallback implements OnBackInvokedCallback { 180 @NonNull 181 private final IOnBackInvokedCallback mIOnBackInvokedCallback; 182 /** 183 * The hashcode of the callback instance in the IME process, used as a unique id to 184 * identify the callback when it's passed between processes. 185 */ 186 private final int mId; 187 private final int mPriority; 188 ImeOnBackInvokedCallback(@onNull IOnBackInvokedCallback iCallback, int id, @Priority int priority)189 ImeOnBackInvokedCallback(@NonNull IOnBackInvokedCallback iCallback, int id, 190 @Priority int priority) { 191 mIOnBackInvokedCallback = iCallback; 192 mId = id; 193 mPriority = priority; 194 } 195 196 @Override onBackInvoked()197 public void onBackInvoked() { 198 try { 199 if (mIOnBackInvokedCallback != null) { 200 mIOnBackInvokedCallback.onBackInvoked(); 201 } 202 } catch (RemoteException e) { 203 Log.e(TAG, "Exception when invoking forwarded callback. e: ", e); 204 } 205 } 206 getId()207 private int getId() { 208 return mId; 209 } 210 getIOnBackInvokedCallback()211 IOnBackInvokedCallback getIOnBackInvokedCallback() { 212 return mIOnBackInvokedCallback; 213 } 214 215 @Override toString()216 public String toString() { 217 return "ImeCallback=ImeOnBackInvokedCallback@" + mId 218 + " Callback=" + mIOnBackInvokedCallback; 219 } 220 } 221 222 /** 223 * Transfers {@link ImeOnBackInvokedCallback}s registered on one {@link ViewRootImpl} to 224 * another {@link ViewRootImpl} on focus change. 225 * 226 * @param previous the previously focused {@link ViewRootImpl}. 227 * @param current the currently focused {@link ViewRootImpl}. 228 */ switchRootView(ViewRootImpl previous, ViewRootImpl current)229 public void switchRootView(ViewRootImpl previous, ViewRootImpl current) { 230 for (ImeOnBackInvokedCallback imeCallback : mImeCallbacks) { 231 if (previous != null) { 232 previous.getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(imeCallback); 233 } 234 if (current != null) { 235 current.getOnBackInvokedDispatcher().registerOnBackInvokedCallbackUnchecked( 236 imeCallback, imeCallback.mPriority); 237 } 238 } 239 } 240 } 241