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.content.Context;
22 import android.util.Log;
23 import android.util.Pair;
24 import android.window.WindowOnBackInvokedDispatcher.Checker;
25 
26 import java.util.ArrayList;
27 import java.util.List;
28 
29 /**
30  * {@link OnBackInvokedDispatcher} only used to hold callbacks while an actual
31  * dispatcher becomes available. <b>It does not dispatch the back events</b>.
32  * <p>
33  * Once the actual {@link OnBackInvokedDispatcher} becomes available,
34  * {@link #setActualDispatcher(OnBackInvokedDispatcher)} needs to
35  * be called and this {@link ProxyOnBackInvokedDispatcher} will pass the callback registrations
36  * onto it.
37  * <p>
38  * This dispatcher will continue to keep track of callback registrations and when a dispatcher is
39  * removed or set it will unregister the callbacks from the old one and register them on the new
40  * one unless {@link #reset()} is called before.
41  *
42  * @hide
43  */
44 public class ProxyOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
45 
46     /**
47      * List of pair representing an {@link OnBackInvokedCallback} and its associated priority.
48      *
49      * @see OnBackInvokedDispatcher#registerOnBackInvokedCallback(int, OnBackInvokedCallback)
50      */
51     private final List<Pair<OnBackInvokedCallback, Integer>> mCallbacks = new ArrayList<>();
52     private final Object mLock = new Object();
53     private OnBackInvokedDispatcher mActualDispatcher = null;
54     private ImeOnBackInvokedDispatcher mImeDispatcher;
55     private final Checker mChecker;
56 
ProxyOnBackInvokedDispatcher(@onNull Context context)57     public ProxyOnBackInvokedDispatcher(@NonNull Context context) {
58         mChecker = new Checker(context);
59     }
60 
61     @Override
registerOnBackInvokedCallback( int priority, @NonNull OnBackInvokedCallback callback)62     public void registerOnBackInvokedCallback(
63             int priority, @NonNull OnBackInvokedCallback callback) {
64         if (DEBUG) {
65             Log.v(TAG, String.format("Proxy register %s. mActualDispatcher=%s", callback,
66                     mActualDispatcher));
67         }
68         if (mChecker.checkApplicationCallbackRegistration(priority, callback)) {
69             registerOnBackInvokedCallbackUnchecked(callback, priority);
70         }
71     }
72 
73     @Override
registerSystemOnBackInvokedCallback(@onNull OnBackInvokedCallback callback)74     public void registerSystemOnBackInvokedCallback(@NonNull OnBackInvokedCallback callback) {
75         registerOnBackInvokedCallbackUnchecked(callback, PRIORITY_SYSTEM);
76     }
77 
78     @Override
unregisterOnBackInvokedCallback( @onNull OnBackInvokedCallback callback)79     public void unregisterOnBackInvokedCallback(
80             @NonNull OnBackInvokedCallback callback) {
81         if (DEBUG) {
82             Log.v(TAG, String.format("Proxy unregister %s. Actual=%s", callback,
83                     mActualDispatcher));
84         }
85         synchronized (mLock) {
86             mCallbacks.removeIf((p) -> p.first.equals(callback));
87             if (mActualDispatcher != null) {
88                 mActualDispatcher.unregisterOnBackInvokedCallback(callback);
89             }
90         }
91     }
92 
registerOnBackInvokedCallbackUnchecked( @onNull OnBackInvokedCallback callback, int priority)93     private void registerOnBackInvokedCallbackUnchecked(
94             @NonNull OnBackInvokedCallback callback, int priority) {
95         synchronized (mLock) {
96             mCallbacks.add(Pair.create(callback, priority));
97             if (mActualDispatcher != null) {
98                 if (priority <= PRIORITY_SYSTEM) {
99                     mActualDispatcher.registerSystemOnBackInvokedCallback(callback);
100                 } else {
101                     mActualDispatcher.registerOnBackInvokedCallback(priority, callback);
102                 }
103             }
104         }
105     }
106 
107     /**
108      * Transfers all the pending callbacks to the provided dispatcher.
109      * <p>
110      * The callbacks are registered on the dispatcher in the same order as they were added on this
111      * proxy dispatcher.
112      */
transferCallbacksToDispatcher()113     private void transferCallbacksToDispatcher() {
114         if (mActualDispatcher == null) {
115             return;
116         }
117         if (DEBUG) {
118             Log.v(TAG, String.format("Proxy transferring %d callbacks to %s", mCallbacks.size(),
119                     mActualDispatcher));
120         }
121         if (mImeDispatcher != null) {
122             mActualDispatcher.setImeOnBackInvokedDispatcher(mImeDispatcher);
123         }
124         for (Pair<OnBackInvokedCallback, Integer> callbackPair : mCallbacks) {
125             int priority = callbackPair.second;
126             if (priority >= PRIORITY_DEFAULT) {
127                 mActualDispatcher.registerOnBackInvokedCallback(priority, callbackPair.first);
128             } else {
129                 mActualDispatcher.registerSystemOnBackInvokedCallback(callbackPair.first);
130             }
131         }
132         mCallbacks.clear();
133         mImeDispatcher = null;
134     }
135 
clearCallbacksOnDispatcher()136     private void clearCallbacksOnDispatcher() {
137         if (mActualDispatcher == null) {
138             return;
139         }
140         for (Pair<OnBackInvokedCallback, Integer> callback : mCallbacks) {
141             mActualDispatcher.unregisterOnBackInvokedCallback(callback.first);
142         }
143     }
144 
145     /**
146      * Resets this {@link ProxyOnBackInvokedDispatcher} so it loses track of the currently
147      * registered callbacks.
148      * <p>
149      * Using this method means that when setting a new {@link OnBackInvokedDispatcher}, the
150      * callbacks registered on the old one won't be removed from it and won't be registered on
151      * the new one.
152      */
reset()153     public void reset() {
154         if (DEBUG) {
155             Log.v(TAG, "Proxy: reset callbacks");
156         }
157         synchronized (mLock) {
158             mCallbacks.clear();
159             mImeDispatcher = null;
160         }
161     }
162 
163     /**
164      * Sets the actual {@link OnBackInvokedDispatcher} onto which the callbacks will be registered.
165      * <p>
166      * If any dispatcher was already present, all the callbacks that were added via this
167      * {@link ProxyOnBackInvokedDispatcher} will be unregistered from the old one and registered
168      * on the new one if it is not null.
169      * <p>
170      * If you do not wish for the previously registered callbacks to be reassigned to the new
171      * dispatcher, {@link #reset} must be called beforehand.
172      */
setActualDispatcher(@ullable OnBackInvokedDispatcher actualDispatcher)173     public void setActualDispatcher(@Nullable OnBackInvokedDispatcher actualDispatcher) {
174         if (DEBUG) {
175             Log.v(TAG, String.format("Proxy setActual %s. Current %s",
176                             actualDispatcher, mActualDispatcher));
177         }
178         synchronized (mLock) {
179             if (actualDispatcher == mActualDispatcher) {
180                 return;
181             }
182             clearCallbacksOnDispatcher();
183             mActualDispatcher = actualDispatcher;
184             transferCallbacksToDispatcher();
185         }
186     }
187 
188     @Override
setImeOnBackInvokedDispatcher( @onNull ImeOnBackInvokedDispatcher imeDispatcher)189     public void setImeOnBackInvokedDispatcher(
190             @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
191         if (mActualDispatcher != null) {
192             mActualDispatcher.setImeOnBackInvokedDispatcher(imeDispatcher);
193         } else {
194             mImeDispatcher = imeDispatcher;
195         }
196     }
197 }
198