chromium/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/NavigationSheetMediator.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.gesturenav;

import static org.chromium.chrome.browser.gesturenav.NavigationSheetCoordinator.NAVIGATION_LIST_ITEM_TYPE_ID;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.view.View;

import org.chromium.chrome.R;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.ui.favicon.FaviconHelper;
import org.chromium.chrome.browser.ui.favicon.FaviconUtils;
import org.chromium.components.browser_ui.widget.RoundedIconGenerator;
import org.chromium.components.browser_ui.widget.TintedDrawable;
import org.chromium.components.embedder_support.util.UrlConstants;
import org.chromium.components.embedder_support.util.UrlUtilities;
import org.chromium.content_public.browser.NavigationEntry;
import org.chromium.content_public.browser.NavigationHistory;
import org.chromium.ui.modelutil.MVCListAdapter.ListItem;
import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModel.WritableObjectPropertyKey;
import org.chromium.url.GURL;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

/** Mediator class for navigation sheet. */
class NavigationSheetMediator {
    private final ClickListener mClickListener;
    private final FaviconHelper mFaviconHelper;
    private final RoundedIconGenerator mIconGenerator;
    private final int mFaviconSize;
    private final ModelList mModelList;
    private final Drawable mHistoryIcon;
    private final Drawable mDefaultIcon;
    private final Drawable mIncognitoIcon;
    private final String mNewTabText;
    private final String mNewIncognitoTabText;
    private final Profile mProfile;

    private NavigationHistory mHistory;

    /** Performs an action when a navigation item is clicked. */
    interface ClickListener {
        /**
         * @param index Index from {@link NavigationEntry#getIndex()}.
         * @param position Position of the clicked item in the list, starting from 0.
         */
        void click(int position, int index);
    }

    static class ItemProperties {
        /** The favicon for the list item. */
        public static final WritableObjectPropertyKey<Drawable> ICON =
                new WritableObjectPropertyKey<>();

        /** The text shown next to the favicon. */
        public static final WritableObjectPropertyKey<String> LABEL =
                new WritableObjectPropertyKey<>();

        /** {@link View#OnClickListener} to execute when each item is clicked. */
        public static final WritableObjectPropertyKey<View.OnClickListener> CLICK_LISTENER =
                new WritableObjectPropertyKey<>();

        public static final PropertyKey[] ALL_KEYS = {ICON, LABEL, CLICK_LISTENER};
    }

    NavigationSheetMediator(
            Context context, ModelList modelList, Profile profile, ClickListener listener) {
        mModelList = modelList;
        mClickListener = listener;
        mProfile = profile;
        mFaviconHelper = new FaviconHelper();
        mIconGenerator = FaviconUtils.createCircularIconGenerator(context);
        mFaviconSize = context.getResources().getDimensionPixelSize(R.dimen.default_favicon_size);
        mHistoryIcon =
                TintedDrawable.constructTintedDrawable(
                        context,
                        R.drawable.ic_history_googblue_24dp,
                        R.color.default_icon_color_tint_list);
        mDefaultIcon =
                TintedDrawable.constructTintedDrawable(
                        context, R.drawable.ic_chrome, R.color.default_icon_color_tint_list);
        mIncognitoIcon =
                TintedDrawable.constructTintedDrawable(
                        context, R.drawable.incognito_small, R.color.default_icon_color_tint_list);
        mNewTabText = context.getResources().getString(R.string.menu_new_tab);
        mNewIncognitoTabText = context.getResources().getString(R.string.menu_new_incognito_tab);
    }

    /**
     * Populate the sheet with the navigation history.
     * @param history {@link NavigationHistory} object.
     */
    void populateEntries(NavigationHistory history) {
        mHistory = history;
        Set<GURL> requestedUrls = new HashSet<>();
        for (int i = 0; i < mHistory.getEntryCount(); i++) {
            PropertyModel model = new PropertyModel(Arrays.asList(ItemProperties.ALL_KEYS));
            NavigationEntry entry = mHistory.getEntryAtIndex(i);
            model.set(ItemProperties.LABEL, getEntryText(entry));
            final int position = i;
            model.set(
                    ItemProperties.CLICK_LISTENER,
                    (view) -> {
                        mClickListener.click(position, entry.getIndex());
                    });
            mModelList.add(new ListItem(NAVIGATION_LIST_ITEM_TYPE_ID, model));
            if (entry.getFavicon() != null) continue;
            final GURL pageUrl = entry.getUrl();
            if (!requestedUrls.contains(pageUrl)) {
                FaviconHelper.FaviconImageCallback imageCallback =
                        (bitmap, iconUrl) -> onFaviconAvailable(pageUrl, bitmap);
                if (!pageUrl.getSpec().equals(UrlConstants.HISTORY_URL)) {
                    mFaviconHelper.getLocalFaviconImageForURL(
                            mProfile, pageUrl, mFaviconSize, imageCallback);
                    requestedUrls.add(pageUrl);
                } else {
                    mModelList.get(i).model.set(ItemProperties.ICON, mHistoryIcon);
                }
            }
        }
    }

    /** Remove the property model. */
    void clear() {
        mModelList.clear();
    }

    /**
     * Called when favicon data requested by {@link #initializeFavicons()} is retrieved.
     * @param pageUrl the page for which the favicon was retrieved.
     * @param favicon the favicon data.
     */
    private void onFaviconAvailable(GURL pageUrl, Bitmap favicon) {
        // This callback can come after the sheet is hidden (which clears modelList).
        // Do nothing if that happens.
        if (mModelList.size() == 0) return;
        for (int i = 0; i < mHistory.getEntryCount(); i++) {
            if (pageUrl.equals(mHistory.getEntryAtIndex(i).getUrl())) {
                Drawable drawable;
                if (favicon == null) {
                    drawable =
                            UrlUtilities.isNtpUrl(pageUrl)
                                    ? getNtpIcon()
                                    : new BitmapDrawable(
                                            mIconGenerator.generateIconForUrl(pageUrl));
                } else {
                    drawable = new BitmapDrawable(favicon);
                }
                mModelList.get(i).model.set(ItemProperties.ICON, drawable);
            }
        }
    }

    private String getEntryText(NavigationEntry entry) {
        String entryText = entry.getTitle();
        if (UrlUtilities.isNtpUrl(entry.getUrl())) entryText = getNtpText();
        if (TextUtils.isEmpty(entryText)) entryText = entry.getVirtualUrl().getSpec();
        if (TextUtils.isEmpty(entryText)) entryText = entry.getUrl().getSpec();
        return entryText;
    }

    private Drawable getNtpIcon() {
        return mProfile.isOffTheRecord() ? mIncognitoIcon : mDefaultIcon;
    }

    private String getNtpText() {
        return mProfile.isOffTheRecord() ? mNewIncognitoTabText : mNewTabText;
    }
}