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