chromium/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressHelper.java

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.back_press;

import androidx.activity.OnBackPressedCallback;
import androidx.activity.OnBackPressedDispatcher;
import androidx.lifecycle.LifecycleOwner;

import org.chromium.chrome.browser.back_press.SecondaryActivityBackPressUma.SecondaryActivity;
import org.chromium.components.browser_ui.widget.gesture.BackPressHandler;

/**
 * Helper class for back press event handling via {@link OnBackPressedDisptacher}. This is
 * a recommended way over {@link Activity#onBackPressed}. Refer to the Android developer's guide
 * {@link https://developer.android.com/guide/navigation/navigation-custom-back}.
 */
public final class BackPressHelper {
    /**
     * @deprecated Handles back press event. #onBackPressed is deprecated starting from U. Prefer
     * {@link BackPressHandler} whenever possible.
     */
    public interface ObsoleteBackPressedHandler {
        /**
         * @return {@code true} if the event was consumed. {@code false} otherwise.
         */
        boolean onBackPressed();
    }

    /**
     * @param lifecycleOwner {@link LifecycleOwner} managing the back press logic's lifecycle.
     * @param dispatcher {@link OnBackPressedDispatcher} that holds other callbacks.
     * @param handler {@link ObsoleteBackPressedHandler} implementing the caller's back press.
     * @param activity {@link SecondaryActivity} handling the back press.
     * handler.
     * @deprecated Create a {@link BackPressHelper} that can handle a chain of handlers. Prefer
     * {@link #create(LifecycleOwner, OnBackPressedDispatcher, BackPressHandler, int)} whenever
     * possible.
     */
    @Deprecated
    public static void create(
            LifecycleOwner lifecycleOwner,
            OnBackPressedDispatcher dispatcher,
            ObsoleteBackPressedHandler handler,
            @SecondaryActivity int activity) {
        new BackPressHelper(lifecycleOwner, dispatcher, handler, activity);
    }

    /**
     * Register a {@link BackPressHandler} on a given {@link  OnBackPressedDispatcher}.
     *
     * @param lifecycleOwner {@link LifecycleOwner} managing the back press logic's lifecycle.
     * @param dispatcher {@link OnBackPressedDispatcher} that holds other callbacks.
     * @param handler {@link BackPressHandler} observing back press state and consuming back press.
     * @param activity {@link SecondaryActivity} handling the back press.
     */
    public static void create(
            LifecycleOwner lifecycleOwner,
            OnBackPressedDispatcher dispatcher,
            BackPressHandler handler,
            @SecondaryActivity int activity) {
        var callback =
                new OnBackPressedCallback(/* enabled= */ false) {
                    @Override
                    public void handleOnBackPressed() {
                        SecondaryActivityBackPressUma.record(activity);
                        handler.handleBackPress();
                    }
                };
        // Update it now since ObservableSupplier posts updates asynchronously.
        if (handler.getHandleBackPressChangedSupplier().get() != null) {
            callback.setEnabled(handler.getHandleBackPressChangedSupplier().get());
        }
        handler.getHandleBackPressChangedSupplier().addObserver(callback::setEnabled);
        dispatcher.addCallback(lifecycleOwner, callback);
    }

    /**
     * Register a list of {@link BackPressHandler} on a given {@link OnBackPressedDispatcher}. The
     * first handler has the top priority and the last one has the least. TODO(crbug.com/40252517):
     * consider introducing a lightweight {@link
     * org.chromium.chrome.browser.back_press.BackPressManager} if too many handlers should be
     * registered.
     *
     * @param lifecycleOwner {@link LifecycleOwner} managing the back press logic's lifecycle.
     * @param dispatcher {@link OnBackPressedDispatcher} that holds other callbacks.
     * @param handlers {@link BackPressHandler} observing back press state and consuming back press.
     * @param activity {@link SecondaryActivity} handling the back press.
     */
    public static void create(
            LifecycleOwner lifecycleOwner,
            OnBackPressedDispatcher dispatcher,
            BackPressHandler[] handlers,
            @SecondaryActivity int activity) {
        // OnBackPressedDispatcher triggers handlers in a reversed order.
        for (int i = handlers.length - 1; i >= 0; i--) {
            create(lifecycleOwner, dispatcher, handlers[i], activity);
        }
    }

    /**
     * Let the back press event be processed by next {@link OnBackPressedCallback}. A callback
     * needs to be in enabled state only when it plans to handle back press event. If the callback
     * cannot handle it for whatever reason, it can call this method to give the next callback
     * or a fallback runnable an opportunity to process it. If the callback needs to receive
     * back press events in the future again, it should enable itself after this call.
     * @param dispatcher {@link OnBackPressedDispatcher} holding other callbacks.
     * @param callback {@link OnBackPressedCallback} which just received the event but ended up
     *        not handling it.
     */
    public static void onBackPressed(
            OnBackPressedDispatcher dispatcher, OnBackPressedCallback callback) {
        callback.setEnabled(false);
        dispatcher.onBackPressed();
    }

    private BackPressHelper(
            LifecycleOwner lifecycleOwner,
            OnBackPressedDispatcher dispatcher,
            ObsoleteBackPressedHandler handler,
            @SecondaryActivity int activity) {
        dispatcher.addCallback(
                lifecycleOwner,
                new OnBackPressedCallback(true) {
                    @Override
                    public void handleOnBackPressed() {
                        if (handler.onBackPressed()) {
                            SecondaryActivityBackPressUma.record(activity);
                        } else {
                            onBackPressed(dispatcher, this);
                        }
                    }
                });
    }
}