/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.window; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.util.Log; import android.util.Pair; import android.window.WindowOnBackInvokedDispatcher.Checker; import java.util.ArrayList; import java.util.List; /** * {@link OnBackInvokedDispatcher} only used to hold callbacks while an actual * dispatcher becomes available. It does not dispatch the back events. *

* Once the actual {@link OnBackInvokedDispatcher} becomes available, * {@link #setActualDispatcher(OnBackInvokedDispatcher)} needs to * be called and this {@link ProxyOnBackInvokedDispatcher} will pass the callback registrations * onto it. *

* This dispatcher will continue to keep track of callback registrations and when a dispatcher is * removed or set it will unregister the callbacks from the old one and register them on the new * one unless {@link #reset()} is called before. * * @hide */ public class ProxyOnBackInvokedDispatcher implements OnBackInvokedDispatcher { /** * List of pair representing an {@link OnBackInvokedCallback} and its associated priority. * * @see OnBackInvokedDispatcher#registerOnBackInvokedCallback(int, OnBackInvokedCallback) */ private final List> mCallbacks = new ArrayList<>(); private final Object mLock = new Object(); private OnBackInvokedDispatcher mActualDispatcher = null; private ImeOnBackInvokedDispatcher mImeDispatcher; private final Checker mChecker; public ProxyOnBackInvokedDispatcher(@NonNull Context context) { mChecker = new Checker(context); } @Override public void registerOnBackInvokedCallback( int priority, @NonNull OnBackInvokedCallback callback) { if (DEBUG) { Log.v(TAG, String.format("Proxy register %s. mActualDispatcher=%s", callback, mActualDispatcher)); } if (mChecker.checkApplicationCallbackRegistration(priority, callback)) { registerOnBackInvokedCallbackUnchecked(callback, priority); } } @Override public void registerSystemOnBackInvokedCallback(@NonNull OnBackInvokedCallback callback) { registerOnBackInvokedCallbackUnchecked(callback, PRIORITY_SYSTEM); } @Override public void unregisterOnBackInvokedCallback( @NonNull OnBackInvokedCallback callback) { if (DEBUG) { Log.v(TAG, String.format("Proxy unregister %s. Actual=%s", callback, mActualDispatcher)); } synchronized (mLock) { mCallbacks.removeIf((p) -> p.first.equals(callback)); if (mActualDispatcher != null) { mActualDispatcher.unregisterOnBackInvokedCallback(callback); } } } private void registerOnBackInvokedCallbackUnchecked( @NonNull OnBackInvokedCallback callback, int priority) { synchronized (mLock) { mCallbacks.add(Pair.create(callback, priority)); if (mActualDispatcher != null) { if (priority <= PRIORITY_SYSTEM) { mActualDispatcher.registerSystemOnBackInvokedCallback(callback); } else { mActualDispatcher.registerOnBackInvokedCallback(priority, callback); } } } } /** * Transfers all the pending callbacks to the provided dispatcher. *

* The callbacks are registered on the dispatcher in the same order as they were added on this * proxy dispatcher. */ private void transferCallbacksToDispatcher() { if (mActualDispatcher == null) { return; } if (DEBUG) { Log.v(TAG, String.format("Proxy transferring %d callbacks to %s", mCallbacks.size(), mActualDispatcher)); } if (mImeDispatcher != null) { mActualDispatcher.setImeOnBackInvokedDispatcher(mImeDispatcher); } for (Pair callbackPair : mCallbacks) { int priority = callbackPair.second; if (priority >= PRIORITY_DEFAULT) { mActualDispatcher.registerOnBackInvokedCallback(priority, callbackPair.first); } else { mActualDispatcher.registerSystemOnBackInvokedCallback(callbackPair.first); } } mCallbacks.clear(); mImeDispatcher = null; } private void clearCallbacksOnDispatcher() { if (mActualDispatcher == null) { return; } for (Pair callback : mCallbacks) { mActualDispatcher.unregisterOnBackInvokedCallback(callback.first); } } /** * Resets this {@link ProxyOnBackInvokedDispatcher} so it loses track of the currently * registered callbacks. *

* Using this method means that when setting a new {@link OnBackInvokedDispatcher}, the * callbacks registered on the old one won't be removed from it and won't be registered on * the new one. */ public void reset() { if (DEBUG) { Log.v(TAG, "Proxy: reset callbacks"); } synchronized (mLock) { mCallbacks.clear(); mImeDispatcher = null; } } /** * Sets the actual {@link OnBackInvokedDispatcher} onto which the callbacks will be registered. *

* If any dispatcher was already present, all the callbacks that were added via this * {@link ProxyOnBackInvokedDispatcher} will be unregistered from the old one and registered * on the new one if it is not null. *

* If you do not wish for the previously registered callbacks to be reassigned to the new * dispatcher, {@link #reset} must be called beforehand. */ public void setActualDispatcher(@Nullable OnBackInvokedDispatcher actualDispatcher) { if (DEBUG) { Log.v(TAG, String.format("Proxy setActual %s. Current %s", actualDispatcher, mActualDispatcher)); } synchronized (mLock) { if (actualDispatcher == mActualDispatcher) { return; } clearCallbacksOnDispatcher(); mActualDispatcher = actualDispatcher; transferCallbacksToDispatcher(); } } @Override public void setImeOnBackInvokedDispatcher( @NonNull ImeOnBackInvokedDispatcher imeDispatcher) { if (mActualDispatcher != null) { mActualDispatcher.setImeOnBackInvokedDispatcher(imeDispatcher); } else { mImeDispatcher = imeDispatcher; } } }