• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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