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