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

// Copyright 2020 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.all_passwords_bottom_sheet;

import static org.chromium.chrome.browser.keyboard_accessory.all_passwords_bottom_sheet.AllPasswordsBottomSheetProperties.VISIBLE;

import org.chromium.components.browser_ui.bottomsheet.BottomSheetController.StateChangeReason;
import org.chromium.components.embedder_support.util.UrlUtilities;
import org.chromium.ui.modelutil.ListModel;
import org.chromium.ui.modelutil.MVCListAdapter.ListItem;
import org.chromium.ui.modelutil.PropertyModel;

import java.util.Collections;
import java.util.List;
import java.util.Locale;

/**
 * Contains the logic for the AllPasswordsBottomSheet. It sets the state of the model and reacts to
 * events like clicks.
 */
class AllPasswordsBottomSheetMediator {
    private AllPasswordsBottomSheetCoordinator.Delegate mDelegate;
    private PropertyModel mModel;
    private ListModel<ListItem> mListModel;
    private List<Credential> mCredentials;
    private boolean mIsPasswordField;

    void initialize(
            AllPasswordsBottomSheetCoordinator.Delegate delegate,
            PropertyModel model,
            ListModel<ListItem> listModel) {
        assert delegate != null;
        mDelegate = delegate;
        mModel = model;
        mListModel = listModel;
    }

    void showCredentials(List<Credential> credentials, boolean isPasswordField) {
        assert credentials != null;
        Collections.sort(credentials, AllPasswordsBottomSheetMediator::compareCredentials);

        mCredentials = credentials;
        mIsPasswordField = isPasswordField;

        mListModel.clear();

        for (Credential credential : mCredentials) {
            if (credential.getPassword().isEmpty() && isPasswordField) continue;
            final PropertyModel model =
                    AllPasswordsBottomSheetProperties.CredentialProperties.createCredentialModel(
                            credential, this::onCredentialSelected, mIsPasswordField);
            mListModel.add(
                    new ListItem(AllPasswordsBottomSheetProperties.ItemType.CREDENTIAL, model));
        }
        mModel.set(VISIBLE, true);
    }

    /**
     * Filters the credentials list based on the passed text and adds the resulting credentials to
     * the model.
     *
     * @param newText the text used to filter the credentials.
     */
    void onQueryTextChange(String newText) {
        mListModel.clear();

        for (Credential credential : mCredentials) {
            if ((credential.getPassword().isEmpty() && mIsPasswordField)
                    || shouldBeFiltered(newText, credential)) {
                continue;
            }
            final PropertyModel model =
                    AllPasswordsBottomSheetProperties.CredentialProperties.createCredentialModel(
                            credential, this::onCredentialSelected, mIsPasswordField);
            mListModel.add(
                    new ListItem(AllPasswordsBottomSheetProperties.ItemType.CREDENTIAL, model));
        }
    }

    /**
     * Returns true if no substring in the passed credential matches the searchQuery ignoring the
     * characters case.
     * @param searchQuery the text to check if passed credential has it.
     * @param credential its username and origin will be checked for matching string.
     * @return Returns whether the entry with the passed credential should be filtered.
     */
    private boolean shouldBeFiltered(final String searchQuery, final Credential credential) {
        return searchQuery != null
                && !credential
                        .getOriginUrl()
                        .toLowerCase(Locale.ENGLISH)
                        .contains(searchQuery.toLowerCase(Locale.ENGLISH))
                && !credential
                        .getUsername()
                        .toLowerCase(Locale.getDefault())
                        .contains(searchQuery.toLowerCase(Locale.getDefault()));
    }

    void onCredentialSelected(CredentialFillRequest credentialFillRequest) {
        mModel.set(VISIBLE, false);
        mDelegate.onCredentialSelected(credentialFillRequest);
    }

    void onDismissed(@StateChangeReason Integer reason) {
        if (!mModel.get(VISIBLE)) return; // Dismiss only if not dismissed yet.
        mModel.set(VISIBLE, false);
        mDelegate.onDismissed();
    }

    private static int compareCredentials(Credential credential1, Credential credential2) {
        String displayOrigin1 =
                credential1.isAndroidCredential()
                        ? credential1.getAppDisplayName().toLowerCase(Locale.ENGLISH)
                        : UrlUtilities.getDomainAndRegistry(credential1.getOriginUrl(), false);
        String displayOrigin2 =
                credential2.isAndroidCredential()
                        ? credential2.getAppDisplayName().toLowerCase(Locale.ENGLISH)
                        : UrlUtilities.getDomainAndRegistry(credential2.getOriginUrl(), false);
        return displayOrigin1.compareTo(displayOrigin2);
    }
}