| /* |
| * 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. <b>It does not dispatch the back events</b>. |
| * <p> |
| * 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. |
| * <p> |
| * 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<Pair<OnBackInvokedCallback, Integer>> 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. |
| * <p> |
| * 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<OnBackInvokedCallback, Integer> 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<OnBackInvokedCallback, Integer> callback : mCallbacks) { |
| mActualDispatcher.unregisterOnBackInvokedCallback(callback.first); |
| } |
| } |
| |
| /** |
| * Resets this {@link ProxyOnBackInvokedDispatcher} so it loses track of the currently |
| * registered callbacks. |
| * <p> |
| * 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. |
| * <p> |
| * 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. |
| * <p> |
| * 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; |
| } |
| } |
| } |