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