chromium/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/BoundedLinearLayout.java

// Copyright 2015 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.components.browser_ui.widget;

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.widget.LinearLayout;

import org.chromium.ui.base.ViewUtils;

/**
 * A LinearLayout that can be constrained to a maximum size or percentage of the screen size.
 *
 * Example:
 *   <org.chromium.components.browser_ui.widget.BoundedLinearLayout
 *       xmlns:android="http://schemas.android.com/apk/res/android"
 *       xmlns:app="http://schemas.android.com/apk/res-auto"
 *       android:layout_width="match_parent"
 *       android:layout_height="match_parent"
 *       app:maxWidthLandscape="@dimen/modal_dialog_landscape_max_width"
 * app:maxWidthPortrait="@dimen/modal_dialog_portrait_max_width">
 *     ...
 */
public class BoundedLinearLayout extends LinearLayout {
    private static final int NOT_SPECIFIED = -1;

    private TypedValue mMaxWidthLandscape = new TypedValue();
    private TypedValue mMaxWidthPortrait = new TypedValue();

    private final int mMaxHeight;

    private boolean mIgnoreWidthConstraints;
    private boolean mIgnoreHeightConstraints;

    /** Constructor for inflating from XML. */
    public BoundedLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.BoundedLinearLayout);
        int maxHeight =
                a.getDimensionPixelSize(R.styleable.BoundedLinearLayout_maxHeight, NOT_SPECIFIED);

        a.getValue(R.styleable.BoundedLinearLayout_maxWidthLandscape, mMaxWidthLandscape);
        a.getValue(R.styleable.BoundedLinearLayout_maxWidthPortrait, mMaxWidthPortrait);

        a.recycle();

        mMaxHeight = maxHeight <= 0 ? NOT_SPECIFIED : maxHeight;
    }

    /**
     * @param ignoreWidthConstraints A boolean indicating whether we should ignore width constraints
     *         to support a full-screen type view.
     * @param ignoreHeightConstraint A boolean indicating whether we should ignore the maxHeight
     *        constraint to support a full-screen type view.
     */
    public void setIgnoreConstraints(
            boolean ignoreWidthConstraints, boolean ignoreHeightConstraint) {
        mIgnoreWidthConstraints = ignoreWidthConstraints;
        mIgnoreHeightConstraints = ignoreHeightConstraint;
        ViewUtils.requestLayout(this, "BoundedLinearLayout.setIgnoreConstraints");
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
        final boolean isPortrait = metrics.widthPixels < metrics.heightPixels;

        // Limit the width.
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        final TypedValue tv = isPortrait ? mMaxWidthPortrait : mMaxWidthLandscape;
        if (tv.type != TypedValue.TYPE_NULL && !isWidthConstraintsIgnored()) {
            int maxWidthPixel = NOT_SPECIFIED;
            if (tv.type == TypedValue.TYPE_DIMENSION) {
                maxWidthPixel = (int) tv.getDimension(metrics);
            } else if (tv.type == TypedValue.TYPE_FRACTION) {
                maxWidthPixel = (int) tv.getFraction(metrics.widthPixels, metrics.widthPixels);
            }

            if (widthSize > maxWidthPixel && maxWidthPixel > 0) {
                widthMeasureSpec = makeMeasureSpec(widthMeasureSpec, maxWidthPixel);
            }
        }

        // Limit the height.
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        if (mMaxHeight != NOT_SPECIFIED
                && heightSize > mMaxHeight
                && !areHeightConstraintsIgnored()) {
            heightMeasureSpec = makeMeasureSpec(heightMeasureSpec, mMaxHeight);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    private int makeMeasureSpec(int measureSpec, int maxPixel) {
        int mode = MeasureSpec.getMode(measureSpec);
        if (mode == MeasureSpec.UNSPECIFIED) mode = MeasureSpec.AT_MOST;
        return MeasureSpec.makeMeasureSpec(maxPixel, mode);
    }

    /** When true, {@code app:maxWidthPortrait} and {@code app:maxWidthLandscape} are ignored. */
    private boolean isWidthConstraintsIgnored() {
        return mIgnoreWidthConstraints;
    }

    /** When true, {@code app:maxHeight} is ignored. */
    private boolean areHeightConstraintsIgnored() {
        return mIgnoreHeightConstraints;
    }
}