chromium/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedStreamViewResizer.java

// Copyright 2021 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.feed;

import android.app.Activity;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
import android.view.View;
import android.view.ViewGroup;

import org.chromium.base.BuildInfo;
import org.chromium.base.FeatureList;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.components.browser_ui.widget.displaystyle.HorizontalDisplayStyle;
import org.chromium.components.browser_ui.widget.displaystyle.UiConfig;
import org.chromium.components.browser_ui.widget.displaystyle.ViewResizer;
import org.chromium.ui.base.DeviceFormFactor;

/**
 * Updates the paddings used to display the feed stream when switching to landscape mode. Due to the
 * fact that the search bar is floating at the top, the entire feed stream needs to shrink a little
 * bit in order to have large image or video preview fit in the viewport.
 */
public class FeedStreamViewResizer extends ViewResizer {
    // The aspect ratio of large images or video previews, computed based on 1280:720.
    private static final float FEED_IMAGE_OR_VIDEO_ASPECT_RATIO = 1.778f;

    private final View mView;
    private final Activity mActivity;

    /**
     * @param activity The activity displays the view.
     * @param view The view that will have its padding resized.
     * @param config The UiConfig object to subscribe to.
     * @param defaultPaddingPixels Padding to use in {@link HorizontalDisplayStyle#REGULAR}.
     * @param minWidePaddingPixels Minimum lateral padding to use in {@link
     *         HorizontalDisplayStyle#WIDE}.
     */
    public FeedStreamViewResizer(
            Activity activity,
            View view,
            UiConfig config,
            int defaultPaddingPixels,
            int minWidePaddingPixels) {
        super(view, config, defaultPaddingPixels, minWidePaddingPixels);
        mView = view;
        mActivity = activity;
    }

    /**
     * Convenience method to create a new ViewResizer and immediately attach it to a {@link
     * UiConfig}. If the {@link UiConfig} can outlive the view, the regular constructor should be
     * used, so it can be detached to avoid memory leaks.
     * @param activity The activity displays the view.
     * @param view The view that will have its padding resized.
     * @param config The UiConfig object to subscribe to.
     * @return The {@link ViewResizer} that is created and attached.
     */
    public static FeedStreamViewResizer createAndAttach(
            Activity activity, View view, UiConfig config) {
        Resources resources = activity.getResources();
        int defaultPaddingPixels =
                FeatureList.isNativeInitialized()
                                && ChromeFeatureList.isEnabled(ChromeFeatureList.FEED_CONTAINMENT)
                        ? resources.getDimensionPixelSize(R.dimen.feed_containment_margin)
                        : resources.getDimensionPixelSize(
                                R.dimen.content_suggestions_card_modern_margin);
        int minWidePaddingPixels =
                resources.getDimensionPixelSize(R.dimen.ntp_wide_card_lateral_margins);

        FeedStreamViewResizer viewResizer =
                new FeedStreamViewResizer(
                        activity, view, config, defaultPaddingPixels, minWidePaddingPixels);
        viewResizer.attach();
        return viewResizer;
    }

    /**
     * In landscape mode, the entire large image or video preview cannot fit in the viewport because
     * the floating search bar at the top reduces the user's visible area. To deal with this, we
     * add the left and right paddings to all items in the RecyclerView in order to shrink all card
     * images a little bit so that they can fit in the viewport.
     */
    @Override
    protected int computePadding() {
        if (DeviceFormFactor.isNonMultiDisplayContextOnTablet(mUiConfig.getContext())
                && mUiConfig.getCurrentDisplayStyle().isWide()) {
            return computePaddingWide();
        } else {
            return computePaddingNarrow();
        }
    }

    private int computePaddingNarrow() {
        int padding = super.computePadding();
        Resources resources = mUiConfig.getContext().getResources();
        if (resources.getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE
                || mActivity.isInMultiWindowMode()) {
            return padding;
        }
        float dpToPx = resources.getDisplayMetrics().density;
        float screenWidth = getScreenWidth();
        float screenHeight = resources.getConfiguration().screenHeightDp * dpToPx;
        float useableHeight = screenHeight - statusBarHeight() - toolbarHeight();
        int customPadding =
                (int) ((screenWidth - useableHeight * FEED_IMAGE_OR_VIDEO_ASPECT_RATIO) / 2);
        return Math.max(customPadding, padding);
    }

    private int computePaddingWide() {
        float screenWidth = getScreenWidth();
        // (a) Once the width of the body reaches breakpoint,
        // adjust margin sizes while keeping the body width constant.
        Resources resources = mActivity.getResources();
        int breakpointWidth =
                resources.getDimensionPixelSize(R.dimen.ntp_wide_card_width_breakpoint);
        int customPadding = (int) ((screenWidth - breakpointWidth) / 2);
        // (b) Once the margins reach max, adjust the body size while keeping margins constant.
        customPadding =
                Math.min(
                        customPadding,
                        resources.getDimensionPixelSize(R.dimen.ntp_wide_card_lateral_margins_max));
        // (c) Once the body reaches max width, adjust the margin widths while keeping the body
        // constant.
        int maxWidth = resources.getDimensionPixelSize(R.dimen.ntp_wide_card_width_max);
        customPadding = Math.max(customPadding, (int) (screenWidth - maxWidth) / 2);
        // (d) Return max of computed padding and min allowed margin.
        return Math.max(customPadding, getMinWidePaddingPixels());
    }

    private float getScreenWidth() {
        Resources resources = mUiConfig.getContext().getResources();
        float screenWidth;
        if (BuildInfo.getInstance().isAutomotive && mView != null) {
            screenWidth = mView.getMeasuredWidth();
        } else {
            float dpToPx = resources.getDisplayMetrics().density;
            screenWidth = resources.getConfiguration().screenWidthDp * dpToPx;
        }
        return screenWidth;
    }

    private int toolbarHeight() {
        ViewGroup contentContainer = mActivity.findViewById(android.R.id.content);
        if (contentContainer == null) {
            return 0;
        }
        View toolbarView = contentContainer.findViewById(R.id.toolbar_container);
        if (toolbarView == null) {
            return 0;
        }
        return toolbarView.getHeight();
    }

    private int statusBarHeight() {
        Rect visibleContentRect = new Rect();
        mActivity.getWindow().getDecorView().getWindowVisibleDisplayFrame(visibleContentRect);
        return visibleContentRect.top;
    }
}