chromium/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingState.java

// Copyright 2019 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.keyboard_accessory;

import android.util.SparseArray;

import androidx.annotation.Nullable;

import org.chromium.chrome.browser.keyboard_accessory.data.CachedProviderAdapter;
import org.chromium.chrome.browser.keyboard_accessory.data.ConditionalProviderAdapter;
import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData;
import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData.AccessorySheetData;
import org.chromium.chrome.browser.keyboard_accessory.data.PropertyProvider;
import org.chromium.chrome.browser.keyboard_accessory.data.Provider;
import org.chromium.content_public.browser.WebContents;
import org.chromium.content_public.browser.WebContentsObserver;

import java.util.ArrayList;

/**
 * This class holds all data that is necessary to restore the state of the Keyboard accessory
 * and its sheet for the {@link WebContents} it is attached to.
 */
class ManualFillingState {
    private static final int[] TAB_ORDER = {
        AccessoryTabType.PASSWORDS, AccessoryTabType.CREDIT_CARDS, AccessoryTabType.ADDRESSES,
    };
    private final WebContents mWebContents;
    private final SparseArray<Provider<AccessorySheetData>> mSheetDataProviders =
            new SparseArray<>();
    private final SparseArray<KeyboardAccessoryData.Tab> mAvailableTabs = new SparseArray<>();
    private @Nullable ManualFillingComponent.UpdateAccessorySheetDelegate mUpdater;
    private @Nullable CachedProviderAdapter<KeyboardAccessoryData.Action[]> mActionsProvider;
    private boolean mWebContentsShowing;

    private class Observer extends WebContentsObserver {
        public Observer(WebContents webContents) {
            super(webContents);
        }

        @Override
        public void wasShown() {
            super.wasShown();
            mWebContentsShowing = true;
            if (mActionsProvider != null) mActionsProvider.notifyAboutCachedItems();
        }

        @Override
        public void wasHidden() {
            super.wasHidden();
            mWebContentsShowing = false;
        }
    }

    private final WebContentsObserver mWebContentsObserver;

    /**
     * Creates a new set of user data that is bound to and observing the given web contents.
     * @param webContents Some {@link WebContents} which are assumed to be shown right now.
     */
    ManualFillingState(@Nullable WebContents webContents) {
        if (webContents == null || webContents.isDestroyed()) {
            mWebContents = null;
            mWebContentsObserver = null;
            return;
        }
        mWebContents = webContents;
        mWebContentsShowing = true;
        mWebContentsObserver = new Observer(mWebContents);
        mWebContents.addObserver(mWebContentsObserver);
    }

    /**
     * Repeats the latest data that known {@link CachedProviderAdapter}s cached to all {@link
     * Provider.Observer}s.
     */
    void notifyObservers() {
        if (mActionsProvider != null) mActionsProvider.notifyAboutCachedItems();
    }

    void setSheetUpdater(ManualFillingComponent.UpdateAccessorySheetDelegate delegate) {
        mUpdater = delegate;
    }

    void requestRecentSheets() {
        for (@AccessoryTabType int type : TAB_ORDER) {
            if (mAvailableTabs.get(type, null) != null && mUpdater != null) {
                mUpdater.requestSheet(type);
            }
        }
    }

    KeyboardAccessoryData.Tab[] getTabs() {
        ArrayList<KeyboardAccessoryData.Tab> tabs = new ArrayList<>();
        for (@AccessoryTabType int type : TAB_ORDER) {
            KeyboardAccessoryData.Tab tab = mAvailableTabs.get(type, null);
            if (tab != null) tabs.add(mAvailableTabs.get(type));
        }
        return tabs.toArray(new KeyboardAccessoryData.Tab[0]);
    }

    void destroy() {
        if (mWebContents != null) mWebContents.removeObserver(mWebContentsObserver);
        mActionsProvider = null;
        mSheetDataProviders.clear();
        mWebContentsShowing = false;
    }

    /**
     * Wraps the given ActionProvider in a {@link CachedProviderAdapter} and stores it.
     *
     * @param provider A {@link PropertyProvider} providing actions.
     * @param defaultActions A default set of actions to prepopulate the adapter's cache.
     */
    void wrapActionsProvider(
            PropertyProvider<KeyboardAccessoryData.Action[]> provider,
            KeyboardAccessoryData.Action[] defaultActions) {
        mActionsProvider =
                new CachedProviderAdapter<>(
                        provider, defaultActions, this::onAdapterReceivedNewData);
    }

    /**
     * Returns the wrapped provider set with {@link #wrapActionsProvider}.
     * @return A {@link CachedProviderAdapter} wrapping a {@link PropertyProvider}.
     */
    Provider<KeyboardAccessoryData.Action[]> getActionsProvider() {
        return mActionsProvider;
    }

    /**
     * Wraps the given provider for sheet data in a {@link ConditionalProviderAdapter} and stores
     * it.
     *
     * @param provider A {@link PropertyProvider} providing sheet data.
     */
    void wrapSheetDataProvider(
            @AccessoryTabType int tabType, PropertyProvider<AccessorySheetData> provider) {
        mSheetDataProviders.put(
                tabType, new ConditionalProviderAdapter<>(provider, () -> mWebContentsShowing));
    }

    /**
     * Returns the wrapped provider set with {@link #wrapSheetDataProvider}.
     *
     * @return A {@link CachedProviderAdapter} wrapping a {@link PropertyProvider}.
     */
    @Nullable
    Provider<AccessorySheetData> getSheetDataProvider(@AccessoryTabType int tabType) {
        return mSheetDataProviders.get(tabType);
    }

    /**
     * Makes a tab available to the state. If there is already a tab of the same state, this fails.
     *
     * @param tab The @{@link KeyboardAccessoryData.Tab} to track.
     * @return True iff the tab was added. False if the a tab of that type was already added.
     */
    boolean addAvailableTab(KeyboardAccessoryData.Tab tab) {
        if (mAvailableTabs.get(tab.getRecordingType(), null) != null) return false;
        mAvailableTabs.put(tab.getRecordingType(), tab);
        return true;
    }

    private void onAdapterReceivedNewData(CachedProviderAdapter adapter) {
        if (mWebContentsShowing) adapter.notifyAboutCachedItems();
    }

    WebContentsObserver getWebContentsObserverForTesting() {
        return mWebContentsObserver;
    }
}