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