summaryrefslogtreecommitdiff
path: root/chromium/components/infobars
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/components/infobars')
-rw-r--r--chromium/components/infobars/android/BUILD.gn70
-rw-r--r--chromium/components/infobars/android/DEPS3
-rw-r--r--chromium/components/infobars/android/OWNERS10
-rw-r--r--chromium/components/infobars/android/java/src/org/chromium/components/infobars/InfoBarControlLayout.java507
-rw-r--r--chromium/components/infobars/android/java/src/org/chromium/components/infobars/InfoBarControlLayoutTest.java187
-rw-r--r--chromium/components/infobars/android/java/src/org/chromium/components/infobars/InfoBarInteractionHandler.java31
-rw-r--r--chromium/components/infobars/android/java/src/org/chromium/components/infobars/InfoBarLayout.java573
-rw-r--r--chromium/components/infobars/android/java/src/org/chromium/components/infobars/InfoBarMessageView.java53
-rw-r--r--chromium/components/infobars/android/res/drawable-hdpi/infobar_shadow_left.9.pngbin0 -> 163 bytes
-rw-r--r--chromium/components/infobars/android/res/drawable-hdpi/infobar_shadow_top.pngbin0 -> 95 bytes
-rw-r--r--chromium/components/infobars/android/res/drawable-mdpi/infobar_shadow_left.9.pngbin0 -> 133 bytes
-rw-r--r--chromium/components/infobars/android/res/drawable-mdpi/infobar_shadow_top.pngbin0 -> 88 bytes
-rw-r--r--chromium/components/infobars/android/res/drawable-xhdpi/infobar_shadow_left.9.pngbin0 -> 187 bytes
-rw-r--r--chromium/components/infobars/android/res/drawable-xhdpi/infobar_shadow_top.pngbin0 -> 85 bytes
-rw-r--r--chromium/components/infobars/android/res/drawable-xxhdpi/infobar_shadow_left.9.pngbin0 -> 245 bytes
-rw-r--r--chromium/components/infobars/android/res/drawable-xxhdpi/infobar_shadow_top.pngbin0 -> 104 bytes
-rw-r--r--chromium/components/infobars/android/res/drawable-xxxhdpi/infobar_shadow_left.9.pngbin0 -> 289 bytes
-rw-r--r--chromium/components/infobars/android/res/drawable-xxxhdpi/infobar_shadow_top.pngbin0 -> 94 bytes
-rw-r--r--chromium/components/infobars/android/res/layout/infobar_control_icon_with_description.xml41
-rw-r--r--chromium/components/infobars/android/res/layout/infobar_control_message.xml13
-rw-r--r--chromium/components/infobars/android/res/layout/infobar_control_spinner.xml10
-rw-r--r--chromium/components/infobars/android/res/layout/infobar_control_spinner_drop_down.xml17
-rw-r--r--chromium/components/infobars/android/res/layout/infobar_control_spinner_view.xml25
-rw-r--r--chromium/components/infobars/android/res/layout/infobar_control_toggle.xml36
-rw-r--r--chromium/components/infobars/android/res/values/dimens.xml43
-rw-r--r--chromium/components/infobars/android/res/values/ids.xml10
-rw-r--r--chromium/components/infobars/content/BUILD.gn18
-rw-r--r--chromium/components/infobars/content/DEPS4
-rw-r--r--chromium/components/infobars/content/content_infobar_manager.cc115
-rw-r--r--chromium/components/infobars/content/content_infobar_manager.h88
-rw-r--r--chromium/components/infobars/core/infobar_delegate.cc3
-rw-r--r--chromium/components/infobars/core/infobar_delegate.h9
32 files changed, 1863 insertions, 3 deletions
diff --git a/chromium/components/infobars/android/BUILD.gn b/chromium/components/infobars/android/BUILD.gn
new file mode 100644
index 00000000000..70e8674eeba
--- /dev/null
+++ b/chromium/components/infobars/android/BUILD.gn
@@ -0,0 +1,70 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/android/rules.gni")
+
+android_resources("java_resources") {
+ sources = [
+ "res/drawable-hdpi/infobar_shadow_left.9.png",
+ "res/drawable-hdpi/infobar_shadow_top.png",
+ "res/drawable-mdpi/infobar_shadow_left.9.png",
+ "res/drawable-mdpi/infobar_shadow_top.png",
+ "res/drawable-xhdpi/infobar_shadow_left.9.png",
+ "res/drawable-xhdpi/infobar_shadow_top.png",
+ "res/drawable-xxhdpi/infobar_shadow_left.9.png",
+ "res/drawable-xxhdpi/infobar_shadow_top.png",
+ "res/drawable-xxxhdpi/infobar_shadow_left.9.png",
+ "res/drawable-xxxhdpi/infobar_shadow_top.png",
+ "res/layout/infobar_control_icon_with_description.xml",
+ "res/layout/infobar_control_message.xml",
+ "res/layout/infobar_control_spinner.xml",
+ "res/layout/infobar_control_spinner_drop_down.xml",
+ "res/layout/infobar_control_spinner_view.xml",
+ "res/layout/infobar_control_toggle.xml",
+ "res/values/dimens.xml",
+ "res/values/ids.xml",
+ ]
+ custom_package = "org.chromium.components.infobars"
+ deps = [
+ "//components/browser_ui/widget/android:java_resources",
+ "//components/strings",
+ "//ui/android:ui_java_resources",
+ ]
+}
+
+android_library("java") {
+ sources = [
+ "java/src/org/chromium/components/infobars/InfoBarControlLayout.java",
+ "java/src/org/chromium/components/infobars/InfoBarInteractionHandler.java",
+ "java/src/org/chromium/components/infobars/InfoBarLayout.java",
+ "java/src/org/chromium/components/infobars/InfoBarMessageView.java",
+ ]
+ deps = [
+ ":java_resources",
+ "//base:base_java",
+ "//components/browser_ui/widget/android:java",
+ "//third_party/android_deps:androidx_appcompat_appcompat_java",
+ "//third_party/android_deps:androidx_appcompat_appcompat_resources_java",
+ "//ui/android:ui_java",
+ ]
+}
+
+android_library("javatests") {
+ testonly = true
+ sources = [
+ "java/src/org/chromium/components/infobars/InfoBarControlLayoutTest.java",
+ ]
+ deps = [
+ ":java",
+ "//base:base_java",
+ "//base:base_java_test_support",
+ "//components/browser_ui/styles/android:java_resources",
+ "//components/browser_ui/widget/android:java",
+ "//third_party/android_deps:androidx_annotation_annotation_java",
+ "//third_party/android_deps:androidx_appcompat_appcompat_java",
+ "//third_party/android_support_test_runner:rules_java",
+ "//third_party/android_support_test_runner:runner_java",
+ "//third_party/junit:junit",
+ ]
+}
diff --git a/chromium/components/infobars/android/DEPS b/chromium/components/infobars/android/DEPS
new file mode 100644
index 00000000000..c08ee06f6bd
--- /dev/null
+++ b/chromium/components/infobars/android/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+components/browser_ui/widget/android",
+]
diff --git a/chromium/components/infobars/android/OWNERS b/chromium/components/infobars/android/OWNERS
new file mode 100644
index 00000000000..0c68d145004
--- /dev/null
+++ b/chromium/components/infobars/android/OWNERS
@@ -0,0 +1,10 @@
+# Primary:
+pavely@chromium.org
+
+# Secondary:
+dtrainor@chromium.org
+mdjones@chromium.org
+twellington@chromium.org
+
+# COMPONENT: UI>Browser>Mobile>Messages
+# OS: Android
diff --git a/chromium/components/infobars/android/java/src/org/chromium/components/infobars/InfoBarControlLayout.java b/chromium/components/infobars/android/java/src/org/chromium/components/infobars/InfoBarControlLayout.java
new file mode 100644
index 00000000000..7650b9af9c9
--- /dev/null
+++ b/chromium/components/infobars/android/java/src/org/chromium/components/infobars/InfoBarControlLayout.java
@@ -0,0 +1,507 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.components.infobars;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Paint;
+import android.text.method.LinkMovementMethod;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.appcompat.widget.SwitchCompat;
+
+import org.chromium.base.ApiCompatibilityUtils;
+import org.chromium.base.StrictModeContext;
+import org.chromium.components.browser_ui.widget.DualControlLayout;
+import org.chromium.components.browser_ui.widget.RadioButtonLayout;
+
+import java.util.List;
+
+/**
+ * Lays out a group of controls (e.g. switches, spinners, or additional text) for InfoBars that need
+ * more than the normal pair of buttons.
+ *
+ * This class works with the {@link InfoBarLayout} to define a standard set of controls with
+ * standardized spacings and text styling that gets laid out in grid form: https://crbug.com/543205
+ *
+ * Manually specified margins on the children managed by this layout are EXPLICITLY ignored to
+ * enforce a uniform margin between controls across all InfoBar types. Do NOT circumvent this
+ * restriction with creative layout definitions. If the layout algorithm doesn't work for your new
+ * InfoBar, convince Chrome for Android's UX team to amend the master spec and then change the
+ * layout algorithm to match.
+ *
+ * TODO(dfalcantara): The line spacing multiplier is applied to all lines in JB & KK, even if the
+ * TextView has only one line. This throws off vertical alignment. Find a
+ * solution that hopefully doesn't involve subclassing the TextView.
+ */
+public final class InfoBarControlLayout extends ViewGroup {
+ /**
+ * ArrayAdapter that automatically determines what size make its Views to accommodate all of
+ * its potential values.
+ * @param <T> Type of object that the ArrayAdapter stores.
+ */
+ public static final class InfoBarArrayAdapter<T> extends ArrayAdapter<T> {
+ private final String mLabel;
+ private int mMinWidthRequiredForValues;
+
+ public InfoBarArrayAdapter(Context context, String label) {
+ super(context, R.layout.infobar_control_spinner_drop_down);
+ mLabel = label;
+ }
+
+ public InfoBarArrayAdapter(Context context, T[] objects) {
+ super(context, R.layout.infobar_control_spinner_drop_down, objects);
+ mLabel = null;
+ }
+
+ @Override
+ public View getDropDownView(int position, View convertView, ViewGroup parent) {
+ TextView view;
+ if (convertView instanceof TextView) {
+ view = (TextView) convertView;
+ } else {
+ view = (TextView) inflateLayout(
+ getContext(), R.layout.infobar_control_spinner_drop_down, parent);
+ }
+
+ view.setText(getItem(position).toString());
+ return view;
+ }
+
+ @Override
+ public DualControlLayout getView(int position, View convertView, ViewGroup parent) {
+ DualControlLayout view;
+ if (convertView instanceof DualControlLayout) {
+ view = (DualControlLayout) convertView;
+ } else {
+ view = (DualControlLayout) inflateLayout(
+ getContext(), R.layout.infobar_control_spinner_view, parent);
+ }
+
+ // Set up the spinner label. The text it displays won't change.
+ TextView labelView = (TextView) view.getChildAt(0);
+ labelView.setText(mLabel);
+
+ // Because the values can be of different widths, the TextView may expand or shrink.
+ // Enforcing a minimum width prevents the layout from doing so as the user swaps values,
+ // preventing unwanted layout passes.
+ TextView valueView = (TextView) view.getChildAt(1);
+ valueView.setText(getItem(position).toString());
+ valueView.setMinimumWidth(mMinWidthRequiredForValues);
+
+ return view;
+ }
+
+ /**
+ * Computes and records the minimum width required to display any of the values without
+ * causing another layout pass when switching values.
+ */
+ int computeMinWidthRequiredForValues() {
+ DualControlLayout layout = getView(0, null, null);
+ TextView container = (TextView) layout.getChildAt(1);
+
+ Paint textPaint = container.getPaint();
+ float longestLanguageWidth = 0;
+ for (int i = 0; i < getCount(); i++) {
+ float width = textPaint.measureText(getItem(i).toString());
+ longestLanguageWidth = Math.max(longestLanguageWidth, width);
+ }
+
+ mMinWidthRequiredForValues = (int) Math.ceil(longestLanguageWidth);
+ return mMinWidthRequiredForValues;
+ }
+
+ /**
+ * Explicitly sets the minimum width required to display all of the values.
+ */
+ void setMinWidthRequiredForValues(int requiredWidth) {
+ mMinWidthRequiredForValues = requiredWidth;
+ }
+ }
+
+ /**
+ * Extends the regular LayoutParams by determining where a control should be located.
+ */
+ @VisibleForTesting
+ static final class ControlLayoutParams extends LayoutParams {
+ public int start;
+ public int top;
+ public int columnsRequired;
+ private boolean mMustBeFullWidth;
+
+ /**
+ * Stores values required for laying out this ViewGroup's children.
+ *
+ * This is set up as a private method to mitigate attempts at adding controls to the layout
+ * that aren't provided by the InfoBarControlLayout.
+ */
+ private ControlLayoutParams() {
+ super(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+ }
+ }
+
+ private final int mMarginBetweenRows;
+ private final int mMarginBetweenColumns;
+
+ /**
+ * Do not call this method directly; use {@link InfoBarLayout#addControlLayout()}.
+ */
+ public InfoBarControlLayout(Context context) {
+ this(context, null);
+ }
+
+ public InfoBarControlLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ Resources resources = context.getResources();
+ mMarginBetweenRows =
+ resources.getDimensionPixelSize(R.dimen.infobar_control_margin_between_rows);
+ mMarginBetweenColumns =
+ resources.getDimensionPixelSize(R.dimen.infobar_control_margin_between_columns);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int fullWidth = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.UNSPECIFIED
+ ? Integer.MAX_VALUE
+ : MeasureSpec.getSize(widthMeasureSpec);
+ int columnWidth = Math.max(0, (fullWidth - mMarginBetweenColumns) / 2);
+
+ int atMostFullWidthSpec = MeasureSpec.makeMeasureSpec(fullWidth, MeasureSpec.AT_MOST);
+ int exactlyFullWidthSpec = MeasureSpec.makeMeasureSpec(fullWidth, MeasureSpec.EXACTLY);
+ int exactlyColumnWidthSpec = MeasureSpec.makeMeasureSpec(columnWidth, MeasureSpec.EXACTLY);
+ int unspecifiedSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+
+ // Figure out how many columns each child requires.
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ measureChild(child, atMostFullWidthSpec, unspecifiedSpec);
+
+ if (child.getMeasuredWidth() <= columnWidth
+ && !getControlLayoutParams(child).mMustBeFullWidth) {
+ getControlLayoutParams(child).columnsRequired = 1;
+ } else {
+ getControlLayoutParams(child).columnsRequired = 2;
+ }
+ }
+
+ // Pack all the children as tightly into rows as possible without changing their ordering.
+ // Stretch out column-width controls if either it is the last control or the next one is
+ // a full-width control.
+ for (int i = 0; i < getChildCount(); i++) {
+ ControlLayoutParams lp = getControlLayoutParams(getChildAt(i));
+
+ if (i == getChildCount() - 1) {
+ lp.columnsRequired = 2;
+ } else {
+ ControlLayoutParams nextLp = getControlLayoutParams(getChildAt(i + 1));
+ if (lp.columnsRequired + nextLp.columnsRequired > 2) {
+ // This control is too big to place with the next child.
+ lp.columnsRequired = 2;
+ } else {
+ // This and the next control fit on the same line. Skip placing the next child.
+ i++;
+ }
+ }
+ }
+
+ // Measure all children, assuming they all have to fit within the width of the layout.
+ // Height is unconstrained.
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ ControlLayoutParams lp = getControlLayoutParams(child);
+ int spec = lp.columnsRequired == 1 ? exactlyColumnWidthSpec : exactlyFullWidthSpec;
+ measureChild(child, spec, unspecifiedSpec);
+ }
+
+ // Pack all the children as tightly into rows as possible without changing their ordering.
+ int layoutHeight = 0;
+ int nextChildStart = 0;
+ int nextChildTop = 0;
+ int currentRowHeight = 0;
+ int columnsAvailable = 2;
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ ControlLayoutParams lp = getControlLayoutParams(child);
+
+ // If there isn't enough room left for the control, move to the next row.
+ if (columnsAvailable < lp.columnsRequired) {
+ layoutHeight += currentRowHeight + mMarginBetweenRows;
+ nextChildStart = 0;
+ nextChildTop = layoutHeight;
+ currentRowHeight = 0;
+ columnsAvailable = 2;
+ }
+
+ lp.top = nextChildTop;
+ lp.start = nextChildStart;
+ currentRowHeight = Math.max(currentRowHeight, child.getMeasuredHeight());
+ columnsAvailable -= lp.columnsRequired;
+ nextChildStart += lp.columnsRequired * (columnWidth + mMarginBetweenColumns);
+ }
+
+ // Compute the ViewGroup's height, accounting for the final row's height.
+ layoutHeight += currentRowHeight;
+ setMeasuredDimension(resolveSize(fullWidth, widthMeasureSpec),
+ resolveSize(layoutHeight, heightMeasureSpec));
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ int width = right - left;
+ boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+
+ // Child positions were already determined during the measurement pass.
+ for (int childIndex = 0; childIndex < getChildCount(); childIndex++) {
+ View child = getChildAt(childIndex);
+ int childLeft = getControlLayoutParams(child).start;
+ if (isRtl) childLeft = width - childLeft - child.getMeasuredWidth();
+
+ int childTop = getControlLayoutParams(child).top;
+ int childRight = childLeft + child.getMeasuredWidth();
+ int childBottom = childTop + child.getMeasuredHeight();
+ child.layout(childLeft, childTop, childRight, childBottom);
+ }
+ }
+
+ @Override
+ protected LayoutParams generateDefaultLayoutParams() {
+ return new ControlLayoutParams();
+ }
+
+ @Override
+ public LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return generateDefaultLayoutParams();
+ }
+
+ @Override
+ protected LayoutParams generateLayoutParams(LayoutParams p) {
+ return generateDefaultLayoutParams();
+ }
+
+ /**
+ * Adds an icon with a descriptive message to the Title.
+ *
+ * -----------------------------------------------------
+ * | ICON | TITLE MESSAGE |
+ * -----------------------------------------------------
+ * If an icon is not provided, the ImageView that would normally show it is hidden.
+ *
+ * @param iconResourceId ID of the drawable to use for the icon.
+ * @param titleMessage Message to display on Infobar title.
+ */
+ public View addIconTitle(int iconResourceId, CharSequence titleMessage) {
+ LinearLayout layout = (LinearLayout) inflateLayout(
+ getContext(), R.layout.infobar_control_icon_with_description, this);
+ addView(layout, new ControlLayoutParams());
+
+ ImageView iconView = (ImageView) layout.findViewById(R.id.control_icon);
+ iconView.setImageResource(iconResourceId);
+
+ TextView titleView = (TextView) layout.findViewById(R.id.control_message);
+ titleView.setText(titleMessage);
+ titleView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
+ getContext().getResources().getDimension(R.dimen.infobar_text_size));
+
+ return layout;
+ }
+
+ /**
+ * Adds an icon with a descriptive message to the layout.
+ *
+ * -----------------------------------------------------
+ * | ICON | PRIMARY MESSAGE SECONDARY MESSAGE |
+ * -----------------------------------------------------
+ * If an icon is not provided, the ImageView that would normally show it is hidden.
+ *
+ * @param iconResourceId ID of the drawable to use for the icon.
+ * @param iconColorId ID of the tint color for the icon, or 0 for default.
+ * @param primaryMessage Message to display for the toggle.
+ * @param secondaryMessage Additional descriptive text for the toggle. May be null.
+ */
+ public View addIcon(int iconResourceId, int iconColorId, CharSequence primaryMessage,
+ CharSequence secondaryMessage) {
+ return addIcon(iconResourceId, iconColorId, primaryMessage, secondaryMessage,
+ R.dimen.infobar_text_size);
+ }
+
+ /**
+ * Adds an icon with a descriptive message to the layout.
+ *
+ * -----------------------------------------------------
+ * | ICON | PRIMARY MESSAGE SECONDARY MESSAGE |
+ * -----------------------------------------------------
+ * If an icon is not provided, the ImageView that would normally show it is hidden.
+ *
+ * @param iconResourceId ID of the drawable to use for the icon.
+ * @param iconColorId ID of the tint color for the icon, or 0 for default.
+ * @param primaryMessage Message to display for the toggle.
+ * @param secondaryMessage Additional descriptive text for the toggle. May be null.
+ * @param resourceId Size of resource id to be applied to primaryMessage
+ * and secondaryMessage.
+ */
+ public View addIcon(int iconResourceId, int iconColorId, CharSequence primaryMessage,
+ CharSequence secondaryMessage, int resourceId) {
+ LinearLayout layout = (LinearLayout) inflateLayout(
+ getContext(), R.layout.infobar_control_icon_with_description, this);
+ addView(layout, new ControlLayoutParams());
+
+ ImageView iconView = (ImageView) layout.findViewById(R.id.control_icon);
+ iconView.setImageResource(iconResourceId);
+ if (iconColorId != 0) {
+ iconView.setColorFilter(ApiCompatibilityUtils.getColor(getResources(), iconColorId));
+ }
+
+ // The primary message text is always displayed.
+ TextView primaryView = (TextView) layout.findViewById(R.id.control_message);
+ primaryView.setText(primaryMessage);
+ primaryView.setTextSize(
+ TypedValue.COMPLEX_UNIT_PX, getContext().getResources().getDimension(resourceId));
+
+ // The secondary message text is optional.
+ TextView secondaryView = (TextView) layout.findViewById(R.id.control_secondary_message);
+ if (secondaryMessage == null) {
+ layout.removeView(secondaryView);
+ } else {
+ secondaryView.setText(secondaryMessage);
+ secondaryView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
+ getContext().getResources().getDimension(resourceId));
+ }
+
+ return layout;
+ }
+
+ /**
+ * Creates a standard toggle switch and adds it to the layout.
+ *
+ * -------------------------------------------------
+ * | ICON | MESSAGE | TOGGLE |
+ * -------------------------------------------------
+ * If an icon is not provided, the ImageView that would normally show it is hidden.
+ *
+ * @param iconResourceId ID of the drawable to use for the icon, or 0 to hide the ImageView.
+ * @param iconColorId ID of the tint color for the icon, or 0 for default.
+ * @param toggleMessage Message to display for the toggle.
+ * @param toggleId ID to use for the toggle.
+ * @param isChecked Whether the toggle should start off checked.
+ */
+ public View addSwitch(int iconResourceId, int iconColorId, CharSequence toggleMessage,
+ int toggleId, boolean isChecked) {
+ LinearLayout switchLayout =
+ (LinearLayout) inflateLayout(getContext(), R.layout.infobar_control_toggle, this);
+ addView(switchLayout, new ControlLayoutParams());
+
+ ImageView iconView = (ImageView) switchLayout.findViewById(R.id.control_icon);
+ if (iconResourceId == 0) {
+ switchLayout.removeView(iconView);
+ } else {
+ iconView.setImageResource(iconResourceId);
+ if (iconColorId != 0) {
+ iconView.setColorFilter(
+ ApiCompatibilityUtils.getColor(getResources(), iconColorId));
+ }
+ }
+
+ TextView messageView = (TextView) switchLayout.findViewById(R.id.control_message);
+ messageView.setText(toggleMessage);
+
+ SwitchCompat switchView =
+ (SwitchCompat) switchLayout.findViewById(R.id.control_toggle_switch);
+ switchView.setId(toggleId);
+ switchView.setChecked(isChecked);
+
+ return switchLayout;
+ }
+
+ /**
+ * Creates a set of standard radio buttons and adds it to the layout.
+ *
+ * @param messages Messages to display for the options.
+ * @param tags Optional list of tags to attach to the buttons.
+ */
+ public RadioButtonLayout addRadioButtons(List<CharSequence> messages, @Nullable List<?> tags) {
+ ControlLayoutParams params = new ControlLayoutParams();
+ params.mMustBeFullWidth = true;
+
+ RadioButtonLayout radioLayout = new RadioButtonLayout(getContext());
+ radioLayout.addOptions(messages, tags);
+
+ addView(radioLayout, params);
+ return radioLayout;
+ }
+
+ /**
+ * Creates a standard spinner and adds it to the layout.
+ */
+ public <T> Spinner addSpinner(int spinnerId, ArrayAdapter<T> arrayAdapter) {
+ Spinner spinner =
+ (Spinner) inflateLayout(getContext(), R.layout.infobar_control_spinner, this);
+ spinner.setAdapter(arrayAdapter);
+ addView(spinner, new ControlLayoutParams());
+ spinner.setId(spinnerId);
+ return spinner;
+ }
+
+ /**
+ * Creates and adds a full-width control with additional text describing what an InfoBar is for.
+ */
+ public View addDescription(CharSequence message) {
+ ControlLayoutParams params = new ControlLayoutParams();
+ params.mMustBeFullWidth = true;
+
+ TextView descriptionView =
+ (TextView) inflateLayout(getContext(), R.layout.dialog_control_description, this);
+ addView(descriptionView, params);
+
+ descriptionView.setText(message);
+ descriptionView.setMovementMethod(LinkMovementMethod.getInstance());
+ return descriptionView;
+ }
+
+ /**
+ * Do NOT call this method directly from outside {@link InfoBarLayout#InfoBarLayout()}.
+ *
+ * Adds a full-width control showing the main InfoBar message. For other text, you should call
+ * {@link InfoBarControlLayout#addDescription(CharSequence)} instead.
+ */
+ TextView addMainMessage(CharSequence mainMessage) {
+ ControlLayoutParams params = new ControlLayoutParams();
+ params.mMustBeFullWidth = true;
+
+ TextView messageView =
+ (TextView) inflateLayout(getContext(), R.layout.infobar_control_message, this);
+ addView(messageView, params);
+
+ messageView.setText(mainMessage);
+ messageView.setMovementMethod(LinkMovementMethod.getInstance());
+ return messageView;
+ }
+
+ /**
+ * @return The {@link ControlLayoutParams} for the given child.
+ */
+ @VisibleForTesting
+ static ControlLayoutParams getControlLayoutParams(View child) {
+ return (ControlLayoutParams) child.getLayoutParams();
+ }
+
+ private static View inflateLayout(Context context, int layoutId, ViewGroup root) {
+ // LayoutInflater may trigger accessing the disk.
+ try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) {
+ return LayoutInflater.from(context).inflate(layoutId, root, false);
+ }
+ }
+}
diff --git a/chromium/components/infobars/android/java/src/org/chromium/components/infobars/InfoBarControlLayoutTest.java b/chromium/components/infobars/android/java/src/org/chromium/components/infobars/InfoBarControlLayoutTest.java
new file mode 100644
index 00000000000..e5925857b33
--- /dev/null
+++ b/chromium/components/infobars/android/java/src/org/chromium/components/infobars/InfoBarControlLayoutTest.java
@@ -0,0 +1,187 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.components.infobars;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.rule.UiThreadTestRule;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup.LayoutParams;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.BaseJUnit4ClassRunner;
+import org.chromium.components.infobars.InfoBarControlLayout.ControlLayoutParams;
+
+/**
+ * Tests for InfoBarControlLayout. This suite doesn't check for specific details, like margins
+ * paddings, and instead focuses on whether controls are placed correctly.
+ */
+@RunWith(BaseJUnit4ClassRunner.class)
+public class InfoBarControlLayoutTest {
+ private static final int SWITCH_ID_1 = 1;
+ private static final int SWITCH_ID_2 = 2;
+ private static final int SWITCH_ID_3 = 3;
+ private static final int SWITCH_ID_4 = 4;
+ private static final int SWITCH_ID_5 = 5;
+ private static final int INFOBAR_WIDTH = 3200;
+
+ private Context mContext;
+
+ @Rule
+ public UiThreadTestRule mRule = new UiThreadTestRule();
+
+ @Before
+ public void setUp() {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mContext.setTheme(R.style.Theme_BrowserUI);
+ }
+
+ /**
+ * A small control on the last line takes up the full width.
+ */
+ @Test
+ @SmallTest
+ @UiThreadTest
+ public void testOneSmallControlTakesFullWidth() {
+ InfoBarControlLayout layout = new InfoBarControlLayout(mContext);
+ layout.setLayoutParams(
+ new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
+ View smallSwitch = layout.addSwitch(0, 0, "A", SWITCH_ID_1, false);
+
+ // Trigger the measurement algorithm.
+ int parentWidthSpec = MeasureSpec.makeMeasureSpec(INFOBAR_WIDTH, MeasureSpec.AT_MOST);
+ int parentHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ layout.measure(parentWidthSpec, parentHeightSpec);
+
+ // Small control takes the full width of the layout because it's put on its own line.
+ ControlLayoutParams params = InfoBarControlLayout.getControlLayoutParams(smallSwitch);
+ Assert.assertEquals(0, params.top);
+ Assert.assertEquals(0, params.start);
+ Assert.assertEquals(2, params.columnsRequired);
+ Assert.assertEquals(INFOBAR_WIDTH, smallSwitch.getMeasuredWidth());
+ }
+
+ /**
+ * Tests the layout algorithm on a set of five controls, the second of which is a huge control
+ * and takes up the whole line. The other smaller controls try to pack themselves as tightly
+ * as possible, strecthing out if necessary for aesthetics, resulting in a layout like this:
+ *
+ * -------------------------
+ * | A (small) |
+ * -------------------------
+ * | B (big) |
+ * -------------------------
+ * | C (small) | D (small) |
+ * -------------------------
+ * | E (small) |
+ * -------------------------
+ */
+ @Test
+ @SmallTest
+ @UiThreadTest
+ public void testComplexSwitchLayout() {
+ // Add five controls to the layout.
+ InfoBarControlLayout layout = new InfoBarControlLayout(mContext);
+ layout.setLayoutParams(
+ new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
+
+ View switch1 = layout.addSwitch(0, 0, "A", SWITCH_ID_1, false);
+ View switch2 = layout.addSwitch(0, 0, "B", SWITCH_ID_2, false);
+ View switch3 = layout.addSwitch(0, 0, "C", SWITCH_ID_3, false);
+ View switch4 = layout.addSwitch(0, 0, "D", SWITCH_ID_4, false);
+ View switch5 = layout.addSwitch(0, 0, "E", SWITCH_ID_4, false);
+
+ // Make the second control require the full layout width.
+ switch2.setMinimumWidth(INFOBAR_WIDTH);
+
+ // Trigger the measurement algorithm.
+ int parentWidthSpec = MeasureSpec.makeMeasureSpec(INFOBAR_WIDTH, MeasureSpec.AT_MOST);
+ int parentHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ layout.measure(parentWidthSpec, parentHeightSpec);
+
+ ControlLayoutParams params1 = InfoBarControlLayout.getControlLayoutParams(switch1);
+ ControlLayoutParams params2 = InfoBarControlLayout.getControlLayoutParams(switch2);
+ ControlLayoutParams params3 = InfoBarControlLayout.getControlLayoutParams(switch3);
+ ControlLayoutParams params4 = InfoBarControlLayout.getControlLayoutParams(switch4);
+ ControlLayoutParams params5 = InfoBarControlLayout.getControlLayoutParams(switch5);
+
+ // Small control takes the full width of the layout because the next one doesn't fit.
+ Assert.assertEquals(0, params1.top);
+ Assert.assertEquals(0, params1.start);
+ Assert.assertEquals(2, params1.columnsRequired);
+ Assert.assertEquals(INFOBAR_WIDTH, switch1.getMeasuredWidth());
+
+ // Big control gets shunted onto the next row and takes up the whole space.
+ Assert.assertTrue(params2.top > switch1.getMeasuredHeight());
+ Assert.assertEquals(0, params2.start);
+ Assert.assertEquals(2, params2.columnsRequired);
+ Assert.assertEquals(INFOBAR_WIDTH, switch2.getMeasuredWidth());
+
+ // Small control gets placed onto the next line and takes only half the width.
+ int bottomOfSwitch2 = params2.top + switch2.getMeasuredHeight();
+ Assert.assertTrue(params3.top > bottomOfSwitch2);
+ Assert.assertEquals(0, params3.start);
+ Assert.assertEquals(1, params3.columnsRequired);
+ Assert.assertTrue(switch3.getMeasuredWidth() < INFOBAR_WIDTH);
+
+ // Small control gets placed next to the previous small control.
+ Assert.assertEquals(params3.top, params4.top);
+ Assert.assertTrue(params4.start > switch3.getMeasuredWidth());
+ Assert.assertEquals(1, params4.columnsRequired);
+ Assert.assertTrue(switch4.getMeasuredWidth() < INFOBAR_WIDTH);
+
+ // Last small control has no room left and gets put on its own line, taking the full width.
+ int bottomOfSwitch4 = params4.top + switch4.getMeasuredHeight();
+ Assert.assertTrue(params5.top > bottomOfSwitch4);
+ Assert.assertEquals(0, params5.start);
+ Assert.assertEquals(2, params5.columnsRequired);
+ Assert.assertEquals(INFOBAR_WIDTH, switch5.getMeasuredWidth());
+ }
+
+ /**
+ * Tests that the message is always the full width of the layout.
+ */
+ @Test
+ @SmallTest
+ @UiThreadTest
+ public void testFullWidthMessageControl() {
+ // Add two controls to the layout. The main message automatically requests the full width.
+ InfoBarControlLayout layout = new InfoBarControlLayout(mContext);
+ layout.setLayoutParams(
+ new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
+
+ View view1 = layout.addMainMessage("A");
+ View view2 = layout.addSwitch(0, 0, "B", SWITCH_ID_2, false);
+
+ // Trigger the measurement algorithm.
+ int parentWidthSpec = MeasureSpec.makeMeasureSpec(INFOBAR_WIDTH, MeasureSpec.AT_MOST);
+ int parentHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ layout.measure(parentWidthSpec, parentHeightSpec);
+
+ ControlLayoutParams params1 = InfoBarControlLayout.getControlLayoutParams(view1);
+ ControlLayoutParams params2 = InfoBarControlLayout.getControlLayoutParams(view2);
+
+ // Main message takes up the full space.
+ Assert.assertEquals(0, params1.top);
+ Assert.assertEquals(0, params1.start);
+ Assert.assertEquals(2, params1.columnsRequired);
+ Assert.assertEquals(INFOBAR_WIDTH, view1.getMeasuredWidth());
+
+ // Small control gets shunted onto the next row.
+ Assert.assertTrue(params2.top > view1.getMeasuredHeight());
+ Assert.assertEquals(0, params2.start);
+ Assert.assertEquals(2, params2.columnsRequired);
+ Assert.assertEquals(INFOBAR_WIDTH, view2.getMeasuredWidth());
+ }
+}
diff --git a/chromium/components/infobars/android/java/src/org/chromium/components/infobars/InfoBarInteractionHandler.java b/chromium/components/infobars/android/java/src/org/chromium/components/infobars/InfoBarInteractionHandler.java
new file mode 100644
index 00000000000..f0f84877429
--- /dev/null
+++ b/chromium/components/infobars/android/java/src/org/chromium/components/infobars/InfoBarInteractionHandler.java
@@ -0,0 +1,31 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.components.infobars;
+
+/**
+ * Functions needed to display an InfoBar UI.
+ */
+public interface InfoBarInteractionHandler {
+ /**
+ * Handles click on the infobar. It is invoked before one of the following functions.
+ */
+ public void onClick();
+
+ /**
+ * Takes some action related to the link being clicked.
+ */
+ public void onLinkClicked();
+
+ /**
+ * Takes some action related to the close button being clicked.
+ */
+ public void onCloseButtonClicked();
+
+ /**
+ * Performs some action related to either the primary or secondary button being pressed.
+ * @param isPrimaryButton True if the primary button was clicked, false otherwise.
+ */
+ public void onButtonClicked(boolean isPrimaryButton);
+}
diff --git a/chromium/components/infobars/android/java/src/org/chromium/components/infobars/InfoBarLayout.java b/chromium/components/infobars/android/java/src/org/chromium/components/infobars/InfoBarLayout.java
new file mode 100644
index 00000000000..d681631a84b
--- /dev/null
+++ b/chromium/components/infobars/android/java/src/org/chromium/components/infobars/InfoBarLayout.java
@@ -0,0 +1,573 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.components.infobars;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.text.SpannableString;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.ColorRes;
+import androidx.annotation.Nullable;
+import androidx.appcompat.content.res.AppCompatResources;
+
+import org.chromium.base.ApiCompatibilityUtils;
+import org.chromium.components.browser_ui.widget.DualControlLayout;
+import org.chromium.ui.UiUtils;
+import org.chromium.ui.text.NoUnderlineClickableSpan;
+import org.chromium.ui.widget.ButtonCompat;
+import org.chromium.ui.widget.ChromeImageButton;
+import org.chromium.ui.widget.ChromeImageView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Layout that arranges an infobar's views.
+ *
+ * An InfoBarLayout consists of:
+ * - A message describing why the infobar is being displayed.
+ * - A close button in the top right corner.
+ * - (optional) An icon representing the infobar's purpose in the top left corner.
+ * - (optional) Additional {@link InfoBarControlLayouts} for specialized controls (e.g. spinners).
+ * - (optional) One or two buttons with text at the bottom, or a button paired with an ImageView.
+ *
+ * When adding custom views, widths and heights defined in the LayoutParams will be ignored.
+ * Setting a minimum width using {@link View#setMinimumWidth()} will be obeyed.
+ *
+ * Logic for what happens when things are clicked should be implemented by the
+ * InfoBarInteractionHandler.
+ */
+public final class InfoBarLayout extends ViewGroup implements View.OnClickListener {
+ /**
+ * Parameters used for laying out children.
+ */
+ private static class LayoutParams extends ViewGroup.LayoutParams {
+ public int startMargin;
+ public int endMargin;
+ public int topMargin;
+ public int bottomMargin;
+
+ // Where this view will be laid out. Calculated in onMeasure() and used in onLayout().
+ public int start;
+ public int top;
+
+ LayoutParams(int startMargin, int topMargin, int endMargin, int bottomMargin) {
+ super(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+ this.startMargin = startMargin;
+ this.topMargin = topMargin;
+ this.endMargin = endMargin;
+ this.bottomMargin = bottomMargin;
+ }
+ }
+
+ private final int mSmallIconSize;
+ private final int mSmallIconMargin;
+ private final int mBigIconSize;
+ private final int mBigIconMargin;
+ private final int mMarginAboveButtonGroup;
+ private final int mMarginAboveControlGroups;
+ private final int mPadding;
+ private final int mMinWidth;
+
+ private final InfoBarInteractionHandler mInfoBar;
+ private final ImageButton mCloseButton;
+ private final InfoBarControlLayout mMessageLayout;
+ private final List<InfoBarControlLayout> mControlLayouts;
+
+ private TextView mMessageTextView;
+ private ImageView mIconView;
+ private DualControlLayout mButtonRowLayout;
+
+ private CharSequence mMessageMainText;
+ private String mMessageLinkText;
+ private int mMessageInlineLinkRangeStart;
+ private int mMessageInlineLinkRangeEnd;
+
+ /**
+ * Constructs a layout for the specified infobar. After calling this, be sure to set the
+ * message, the buttons, and/or the custom content using setMessage(), setButtons(), and
+ * setCustomContent().
+ * @param context The context used to render.
+ * @param infoBar InfoBarInteractionHandler that listens to events.
+ * @param iconResourceId ID of the icon to use for the infobar.
+ * @param iconTintId The {@link ColorRes} used as tint for {@code iconResourceId}.
+ * @param iconBitmap Bitmap for the icon to use, if the resource ID wasn't passed through.
+ * @param message The message to show in the infobar.
+ */
+ public InfoBarLayout(Context context, InfoBarInteractionHandler infoBar, int iconResourceId,
+ @ColorRes int iconTintId, Bitmap iconBitmap, CharSequence message) {
+ super(context);
+ mControlLayouts = new ArrayList<InfoBarControlLayout>();
+
+ mInfoBar = infoBar;
+
+ // Cache resource values.
+ Resources res = getResources();
+ mSmallIconSize = res.getDimensionPixelSize(R.dimen.infobar_small_icon_size);
+ mSmallIconMargin = res.getDimensionPixelSize(R.dimen.infobar_small_icon_margin);
+ mBigIconSize = res.getDimensionPixelSize(R.dimen.infobar_big_icon_size);
+ mBigIconMargin = res.getDimensionPixelSize(R.dimen.infobar_big_icon_margin);
+ mMarginAboveButtonGroup =
+ res.getDimensionPixelSize(R.dimen.infobar_margin_above_button_row);
+ mMarginAboveControlGroups =
+ res.getDimensionPixelSize(R.dimen.infobar_margin_above_control_groups);
+ mPadding = res.getDimensionPixelOffset(R.dimen.infobar_padding);
+ mMinWidth = res.getDimensionPixelSize(R.dimen.infobar_min_width);
+
+ // Set up the close button. Apply padding so it has a big touch target.
+ mCloseButton = createCloseButton(context);
+ mCloseButton.setOnClickListener(this);
+ mCloseButton.setPadding(mPadding, mPadding, mPadding, mPadding);
+ mCloseButton.setLayoutParams(new LayoutParams(0, -mPadding, -mPadding, -mPadding));
+
+ // Set up the icon, if necessary.
+ mIconView = createIconView(context, iconResourceId, iconTintId, iconBitmap);
+ if (mIconView != null) {
+ mIconView.setLayoutParams(new LayoutParams(0, 0, mSmallIconMargin, 0));
+ mIconView.getLayoutParams().width = mSmallIconSize;
+ mIconView.getLayoutParams().height = mSmallIconSize;
+ }
+
+ // Set up the message view.
+ mMessageMainText = message;
+ mMessageLayout = new InfoBarControlLayout(context);
+ mMessageTextView = mMessageLayout.addMainMessage(prepareMainMessageString());
+ }
+
+ /**
+ * Returns the {@link TextView} corresponding to the main infobar message.
+ * The returned view is a part of internal layout strucutre and shouldn't be accessed by InfoBar
+ * implementations.
+ */
+ public TextView getMessageTextView() {
+ return mMessageTextView;
+ }
+
+ /**
+ * Returns the {@link InfoBarControlLayout} containing the TextView showing the main infobar
+ * message and associated controls, which is sandwiched between its icon and close button.
+ * The returned view is a part of internal layout strucutre and shouldn't be accessed by InfoBar
+ * implementations.
+ */
+ public InfoBarControlLayout getMessageLayout() {
+ return mMessageLayout;
+ }
+
+ /**
+ * Sets the message to show on the infobar.
+ * TODO(dfalcantara): Do some magic here to determine if TextViews need to have line spacing
+ * manually added. Android changed when these values were applied between
+ * KK and L: https://crbug.com/543205
+ */
+ public void setMessage(CharSequence message) {
+ mMessageMainText = message;
+ mMessageTextView.setText(prepareMainMessageString());
+ }
+
+ /**
+ * Appends a link to the message, if an infobar requires one (e.g. "Learn more").
+ */
+ public void appendMessageLinkText(String linkText) {
+ mMessageLinkText = linkText;
+ mMessageTextView.setText(prepareMainMessageString());
+ }
+
+ /**
+ * Sets up the message to have an inline link, assuming an inclusive range.
+ * @param rangeStart Where the link starts.
+ * @param rangeEnd Where the link ends.
+ */
+ public void setInlineMessageLink(int rangeStart, int rangeEnd) {
+ mMessageInlineLinkRangeStart = rangeStart;
+ mMessageInlineLinkRangeEnd = rangeEnd;
+ mMessageTextView.setText(prepareMainMessageString());
+ }
+
+ /**
+ * Adds an {@link InfoBarControlLayout} to house additional infobar controls, like toggles and
+ * spinners.
+ */
+ public InfoBarControlLayout addControlLayout() {
+ InfoBarControlLayout controlLayout = new InfoBarControlLayout(getContext());
+ mControlLayouts.add(controlLayout);
+ return controlLayout;
+ }
+
+ /**
+ * Adds one or two buttons to the layout.
+ *
+ * @param primaryText Text for the primary button. If empty, no buttons are added at all.
+ * @param secondaryText Text for the secondary button, or null if there isn't a second button.
+ */
+ public void setButtons(String primaryText, String secondaryText) {
+ if (TextUtils.isEmpty(primaryText)) {
+ assert TextUtils.isEmpty(secondaryText);
+ return;
+ }
+
+ Button secondaryButton = null;
+ if (!TextUtils.isEmpty(secondaryText)) {
+ secondaryButton = DualControlLayout.createButtonForLayout(
+ getContext(), false, secondaryText, this);
+ }
+
+ setBottomViews(
+ primaryText, secondaryButton, DualControlLayout.DualControlLayoutAlignment.END);
+ }
+
+ /**
+ * Sets up the bottom-most part of the infobar with a primary button (e.g. OK) and a secondary
+ * View of your choice. Subclasses should be calling {@link #setButtons(String, String)}
+ * instead of this function in nearly all cases (that function calls this one).
+ *
+ * @param primaryText Text to display on the primary button. If empty, the bottom layout is not
+ * created.
+ * @param secondaryView View that is aligned with the primary button. May be null.
+ * @param alignment One of ALIGN_START, ALIGN_APART, or ALIGN_END from
+ * {@link DualControlLayout}.
+ */
+ public void setBottomViews(String primaryText, View secondaryView, int alignment) {
+ assert !TextUtils.isEmpty(primaryText);
+ Button primaryButton =
+ DualControlLayout.createButtonForLayout(getContext(), true, primaryText, this);
+
+ assert mButtonRowLayout == null;
+ mButtonRowLayout = new DualControlLayout(getContext(), null);
+ mButtonRowLayout.setAlignment(alignment);
+ mButtonRowLayout.setStackedMargin(getResources().getDimensionPixelSize(
+ R.dimen.infobar_margin_between_stacked_buttons));
+
+ mButtonRowLayout.addView(primaryButton);
+ if (secondaryView != null) mButtonRowLayout.addView(secondaryView);
+ }
+
+ /**
+ * Adjusts styling to account for the big icon layout.
+ */
+ public void setIsUsingBigIcon() {
+ if (mIconView == null) return;
+
+ LayoutParams lp = (LayoutParams) mIconView.getLayoutParams();
+ lp.width = mBigIconSize;
+ lp.height = mBigIconSize;
+ lp.endMargin = mBigIconMargin;
+
+ Resources res = getContext().getResources();
+ float textSize = res.getDimension(R.dimen.infobar_big_icon_message_size);
+ mMessageTextView.setTypeface(UiUtils.createRobotoMediumTypeface());
+ mMessageTextView.setMaxLines(1);
+ mMessageTextView.setEllipsize(TextUtils.TruncateAt.END);
+ mMessageTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
+ }
+
+ /**
+ * Returns the primary button, or null if it doesn't exist.
+ */
+ public ButtonCompat getPrimaryButton() {
+ return mButtonRowLayout == null
+ ? null
+ : (ButtonCompat) mButtonRowLayout.findViewById(R.id.button_primary);
+ }
+
+ /**
+ * Returns the icon, or null if it doesn't exist.
+ */
+ public ImageView getIcon() {
+ return mIconView;
+ }
+
+ /**
+ * Must be called after the message, buttons, and custom content have been set, and before the
+ * first call to onMeasure().
+ */
+ // TODO(crbug/1056346): onContentCreated is made public to allow access from InfoBar. Once
+ // InfoBar is modularized, restore access to package private.
+ public void onContentCreated() {
+ // Add the child views in the desired focus order.
+ if (mIconView != null) addView(mIconView);
+ addView(mMessageLayout);
+ for (View v : mControlLayouts) addView(v);
+ if (mButtonRowLayout != null) addView(mButtonRowLayout);
+ addView(mCloseButton);
+ }
+
+ @Override
+ protected LayoutParams generateDefaultLayoutParams() {
+ return new LayoutParams(0, 0, 0, 0);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ // Place all the views in the positions already determined during onMeasure().
+ int width = right - left;
+ boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ int childLeft = lp.start;
+ int childRight = lp.start + child.getMeasuredWidth();
+
+ if (isRtl) {
+ int tmp = width - childRight;
+ childRight = width - childLeft;
+ childLeft = tmp;
+ }
+
+ child.layout(childLeft, lp.top, childRight, lp.top + child.getMeasuredHeight());
+ }
+ }
+
+ /**
+ * Measures and determines where children should go.
+ *
+ * For current specs, see https://goto.google.com/infobar-spec
+ *
+ * All controls are padded from the infobar boundary by the same amount, but different types of
+ * control groups are bound by different widths and have different margins:
+ * --------------------------------------------------------------------------------
+ * | PADDING |
+ * | -------------------------------------------------------------------------- |
+ * | | ICON | MESSAGE LAYOUT | X | |
+ * | |------+ +---| |
+ * | | | | | |
+ * | | ------------------------------------------------------------------| |
+ * | | | CONTROL LAYOUT #1 | |
+ * | | ------------------------------------------------------------------| |
+ * | | | CONTROL LAYOUT #X | |
+ * | |------------------------------------------------------------------------| |
+ * | | BOTTOM ROW LAYOUT | |
+ * | -------------------------------------------------------------------------| |
+ * | |
+ * --------------------------------------------------------------------------------
+ */
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ assert getLayoutParams().height
+ == LayoutParams.WRAP_CONTENT : "InfoBar heights cannot be constrained.";
+
+ // Apply the padding that surrounds all the infobar controls.
+ final int layoutWidth = Math.max(MeasureSpec.getSize(widthMeasureSpec), mMinWidth);
+ final int paddedStart = mPadding;
+ final int paddedEnd = layoutWidth - mPadding;
+ int layoutBottom = mPadding;
+
+ // Measure and place the icon in the top-left corner.
+ int unspecifiedSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ if (mIconView != null) {
+ LayoutParams iconParams = getChildLayoutParams(mIconView);
+ measureChild(mIconView, unspecifiedSpec, unspecifiedSpec);
+ iconParams.start = paddedStart + iconParams.startMargin;
+ iconParams.top = layoutBottom + iconParams.topMargin;
+ }
+ final int iconWidth = getChildWidthWithMargins(mIconView);
+
+ // Measure and place the close button in the top-right corner of the layout.
+ LayoutParams closeParams = getChildLayoutParams(mCloseButton);
+ measureChild(mCloseButton, unspecifiedSpec, unspecifiedSpec);
+ closeParams.start = paddedEnd - closeParams.endMargin - mCloseButton.getMeasuredWidth();
+ closeParams.top = layoutBottom + closeParams.topMargin;
+
+ // Determine how much width is available for all the different control layouts; see the
+ // function JavaDoc above for details.
+ final int paddedWidth = paddedEnd - paddedStart;
+ final int controlLayoutWidth = paddedWidth - iconWidth;
+ final int messageWidth = controlLayoutWidth - getChildWidthWithMargins(mCloseButton);
+
+ // The message layout is sandwiched between the icon and the close button.
+ LayoutParams messageParams = getChildLayoutParams(mMessageLayout);
+ measureChildWithFixedWidth(mMessageLayout, messageWidth);
+ messageParams.start = paddedStart + iconWidth;
+ messageParams.top = layoutBottom;
+
+ // Control layouts are placed below the message layout and the close button. The icon is
+ // ignored for this particular calculation because the icon enforces a left margin on all of
+ // the control layouts and won't be overlapped.
+ layoutBottom += Math.max(
+ getChildHeightWithMargins(mMessageLayout), getChildHeightWithMargins(mCloseButton));
+
+ // The other control layouts are constrained only by the icon's width.
+ final int controlPaddedStart = paddedStart + iconWidth;
+ for (int i = 0; i < mControlLayouts.size(); i++) {
+ View child = mControlLayouts.get(i);
+ measureChildWithFixedWidth(child, controlLayoutWidth);
+
+ layoutBottom += mMarginAboveControlGroups;
+ getChildLayoutParams(child).start = controlPaddedStart;
+ getChildLayoutParams(child).top = layoutBottom;
+ layoutBottom += child.getMeasuredHeight();
+ }
+
+ // The button layout takes up the full width of the infobar and sits below everything else,
+ // including the icon.
+ layoutBottom = Math.max(layoutBottom, getChildHeightWithMargins(mIconView));
+ if (mButtonRowLayout != null) {
+ measureChildWithFixedWidth(mButtonRowLayout, paddedWidth);
+
+ layoutBottom += mMarginAboveButtonGroup;
+ getChildLayoutParams(mButtonRowLayout).start = paddedStart;
+ getChildLayoutParams(mButtonRowLayout).top = layoutBottom;
+ layoutBottom += mButtonRowLayout.getMeasuredHeight();
+ }
+
+ // Apply padding to the bottom of the infobar.
+ layoutBottom += mPadding;
+
+ setMeasuredDimension(resolveSize(layoutWidth, widthMeasureSpec),
+ resolveSize(layoutBottom, heightMeasureSpec));
+ }
+
+ private static int getChildWidthWithMargins(View view) {
+ if (view == null) return 0;
+ return view.getMeasuredWidth() + getChildLayoutParams(view).startMargin
+ + getChildLayoutParams(view).endMargin;
+ }
+
+ private static int getChildHeightWithMargins(View view) {
+ if (view == null) return 0;
+ return view.getMeasuredHeight() + getChildLayoutParams(view).topMargin
+ + getChildLayoutParams(view).bottomMargin;
+ }
+
+ private static LayoutParams getChildLayoutParams(View view) {
+ return (LayoutParams) view.getLayoutParams();
+ }
+
+ /**
+ * Measures a child for the given space, taking into account its margins.
+ */
+ private void measureChildWithFixedWidth(View child, int width) {
+ LayoutParams lp = getChildLayoutParams(child);
+ int availableWidth = width - lp.startMargin - lp.endMargin;
+ int widthSpec = MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.EXACTLY);
+ int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ child.measure(widthSpec, heightSpec);
+ }
+
+ /**
+ * Listens for View clicks.
+ * Classes that override this function MUST call this one.
+ * @param view View that was clicked on.
+ */
+ @Override
+ public void onClick(View view) {
+ mInfoBar.onClick();
+
+ if (view.getId() == R.id.infobar_close_button) {
+ mInfoBar.onCloseButtonClicked();
+ } else if (view.getId() == R.id.button_primary) {
+ mInfoBar.onButtonClicked(true);
+ } else if (view.getId() == R.id.button_secondary) {
+ mInfoBar.onButtonClicked(false);
+ }
+ }
+
+ /**
+ * Prepares text to be displayed as the infobar's main message, including setting up a
+ * clickable link if the infobar requires it.
+ */
+ private CharSequence prepareMainMessageString() {
+ SpannableStringBuilder fullString = new SpannableStringBuilder();
+
+ if (!TextUtils.isEmpty(mMessageMainText)) {
+ SpannableString spannedMessage = new SpannableString(mMessageMainText);
+
+ // If there's an inline link, apply the necessary span for it.
+ if (mMessageInlineLinkRangeEnd != 0) {
+ assert mMessageInlineLinkRangeStart < mMessageInlineLinkRangeEnd;
+ assert mMessageInlineLinkRangeEnd < mMessageMainText.length();
+
+ spannedMessage.setSpan(createClickableSpan(), mMessageInlineLinkRangeStart,
+ mMessageInlineLinkRangeEnd, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ }
+
+ fullString.append(spannedMessage);
+ }
+
+ // Concatenate the text to display for the link and make it clickable.
+ if (!TextUtils.isEmpty(mMessageLinkText)) {
+ if (fullString.length() > 0) fullString.append(" ");
+ int spanStart = fullString.length();
+
+ fullString.append(mMessageLinkText);
+ fullString.setSpan(createClickableSpan(), spanStart, fullString.length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+
+ return fullString;
+ }
+
+ private NoUnderlineClickableSpan createClickableSpan() {
+ return new NoUnderlineClickableSpan(getResources(), (view) -> mInfoBar.onLinkClicked());
+ }
+
+ /**
+ * Creates a View that holds an icon representing an infobar.
+ * @param context Context to grab resources from.
+ * @param iconResourceId ID of the icon to use for the infobar.
+ * @param iconTintId The {@link ColorRes} used as tint for {@code iconResourceId}.
+ * @param iconBitmap Bitmap for the icon to use, if the resource ID wasn't passed through.
+ * @return {@link ImageButton} that represents the icon.
+ */
+ @Nullable
+ public static ImageView createIconView(
+ Context context, int iconResourceId, @ColorRes int iconTintId, Bitmap iconBitmap) {
+ if (iconResourceId == 0 && iconBitmap == null) return null;
+
+ final ChromeImageView iconView = new ChromeImageView(context);
+ if (iconResourceId != 0) {
+ iconView.setImageDrawable(AppCompatResources.getDrawable(context, iconResourceId));
+ if (iconTintId != 0) {
+ ApiCompatibilityUtils.setImageTintList(
+ iconView, AppCompatResources.getColorStateList(context, iconTintId));
+ }
+ } else {
+ iconView.setImageBitmap(iconBitmap);
+ }
+
+ iconView.setFocusable(false);
+ iconView.setId(R.id.infobar_icon);
+ iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
+ return iconView;
+ }
+
+ /**
+ * Creates a close button that can be inserted into an infobar.
+ * @param context Context to grab resources from.
+ * @return {@link ImageButton} that represents a close button.
+ */
+ public static ImageButton createCloseButton(Context context) {
+ final ColorStateList tint =
+ AppCompatResources.getColorStateList(context, R.color.default_icon_color);
+ TypedArray a =
+ context.obtainStyledAttributes(new int[] {android.R.attr.selectableItemBackground});
+ Drawable closeButtonBackground = a.getDrawable(0);
+ a.recycle();
+
+ ChromeImageButton closeButton = new ChromeImageButton(context);
+ closeButton.setId(R.id.infobar_close_button);
+ closeButton.setImageResource(R.drawable.btn_close);
+ ApiCompatibilityUtils.setImageTintList(closeButton, tint);
+ closeButton.setBackground(closeButtonBackground);
+ closeButton.setContentDescription(context.getString(R.string.close));
+ closeButton.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
+
+ return closeButton;
+ }
+}
diff --git a/chromium/components/infobars/android/java/src/org/chromium/components/infobars/InfoBarMessageView.java b/chromium/components/infobars/android/java/src/org/chromium/components/infobars/InfoBarMessageView.java
new file mode 100644
index 00000000000..98a8d7ee7e2
--- /dev/null
+++ b/chromium/components/infobars/android/java/src/org/chromium/components/infobars/InfoBarMessageView.java
@@ -0,0 +1,53 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.components.infobars;
+
+import android.content.Context;
+import android.text.style.ClickableSpan;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+import org.chromium.ui.widget.TextViewWithClickableSpans;
+
+/**
+ * Handles the additional message view responsibilities needed for InfoBars.
+ * - Makes the full text view clickable if there is just a single link.
+ */
+public class InfoBarMessageView extends TextViewWithClickableSpans {
+ private boolean mExternalOnClickListenerSet;
+ private long mMotionEventDownTime;
+
+ public InfoBarMessageView(Context context) {
+ super(context);
+ }
+
+ public InfoBarMessageView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ boolean retVal = super.onTouchEvent(event);
+ if (!mExternalOnClickListenerSet && event.getActionMasked() == MotionEvent.ACTION_UP) {
+ long downDuration = event.getEventTime() - event.getDownTime();
+ boolean validClickEvent = downDuration >= ViewConfiguration.getTapTimeout()
+ && downDuration <= ViewConfiguration.getLongPressTimeout();
+
+ ClickableSpan[] spans = getClickableSpans();
+ if (validClickEvent && spans != null && spans.length == 1
+ && !touchIntersectsAnyClickableSpans(event)) {
+ spans[0].onClick(this);
+ }
+ }
+ return retVal;
+ }
+
+ @Override
+ public final void setOnClickListener(OnClickListener l) {
+ super.setOnClickListener(l);
+ if (l != null) mExternalOnClickListenerSet = true;
+ }
+}
diff --git a/chromium/components/infobars/android/res/drawable-hdpi/infobar_shadow_left.9.png b/chromium/components/infobars/android/res/drawable-hdpi/infobar_shadow_left.9.png
new file mode 100644
index 00000000000..d65598923df
--- /dev/null
+++ b/chromium/components/infobars/android/res/drawable-hdpi/infobar_shadow_left.9.png
Binary files differ
diff --git a/chromium/components/infobars/android/res/drawable-hdpi/infobar_shadow_top.png b/chromium/components/infobars/android/res/drawable-hdpi/infobar_shadow_top.png
new file mode 100644
index 00000000000..2baed48442c
--- /dev/null
+++ b/chromium/components/infobars/android/res/drawable-hdpi/infobar_shadow_top.png
Binary files differ
diff --git a/chromium/components/infobars/android/res/drawable-mdpi/infobar_shadow_left.9.png b/chromium/components/infobars/android/res/drawable-mdpi/infobar_shadow_left.9.png
new file mode 100644
index 00000000000..59af8e98339
--- /dev/null
+++ b/chromium/components/infobars/android/res/drawable-mdpi/infobar_shadow_left.9.png
Binary files differ
diff --git a/chromium/components/infobars/android/res/drawable-mdpi/infobar_shadow_top.png b/chromium/components/infobars/android/res/drawable-mdpi/infobar_shadow_top.png
new file mode 100644
index 00000000000..9dd1e33b27a
--- /dev/null
+++ b/chromium/components/infobars/android/res/drawable-mdpi/infobar_shadow_top.png
Binary files differ
diff --git a/chromium/components/infobars/android/res/drawable-xhdpi/infobar_shadow_left.9.png b/chromium/components/infobars/android/res/drawable-xhdpi/infobar_shadow_left.9.png
new file mode 100644
index 00000000000..674a728e9a4
--- /dev/null
+++ b/chromium/components/infobars/android/res/drawable-xhdpi/infobar_shadow_left.9.png
Binary files differ
diff --git a/chromium/components/infobars/android/res/drawable-xhdpi/infobar_shadow_top.png b/chromium/components/infobars/android/res/drawable-xhdpi/infobar_shadow_top.png
new file mode 100644
index 00000000000..766112897f5
--- /dev/null
+++ b/chromium/components/infobars/android/res/drawable-xhdpi/infobar_shadow_top.png
Binary files differ
diff --git a/chromium/components/infobars/android/res/drawable-xxhdpi/infobar_shadow_left.9.png b/chromium/components/infobars/android/res/drawable-xxhdpi/infobar_shadow_left.9.png
new file mode 100644
index 00000000000..4125483c89a
--- /dev/null
+++ b/chromium/components/infobars/android/res/drawable-xxhdpi/infobar_shadow_left.9.png
Binary files differ
diff --git a/chromium/components/infobars/android/res/drawable-xxhdpi/infobar_shadow_top.png b/chromium/components/infobars/android/res/drawable-xxhdpi/infobar_shadow_top.png
new file mode 100644
index 00000000000..34ae4b52120
--- /dev/null
+++ b/chromium/components/infobars/android/res/drawable-xxhdpi/infobar_shadow_top.png
Binary files differ
diff --git a/chromium/components/infobars/android/res/drawable-xxxhdpi/infobar_shadow_left.9.png b/chromium/components/infobars/android/res/drawable-xxxhdpi/infobar_shadow_left.9.png
new file mode 100644
index 00000000000..0f0cc52a2ae
--- /dev/null
+++ b/chromium/components/infobars/android/res/drawable-xxxhdpi/infobar_shadow_left.9.png
Binary files differ
diff --git a/chromium/components/infobars/android/res/drawable-xxxhdpi/infobar_shadow_top.png b/chromium/components/infobars/android/res/drawable-xxxhdpi/infobar_shadow_top.png
new file mode 100644
index 00000000000..63b926435bc
--- /dev/null
+++ b/chromium/components/infobars/android/res/drawable-xxxhdpi/infobar_shadow_top.png
Binary files differ
diff --git a/chromium/components/infobars/android/res/layout/infobar_control_icon_with_description.xml b/chromium/components/infobars/android/res/layout/infobar_control_icon_with_description.xml
new file mode 100644
index 00000000000..b9169aa9c33
--- /dev/null
+++ b/chromium/components/infobars/android/res/layout/infobar_control_icon_with_description.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2015 The Chromium Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file. -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="36dp"
+ android:gravity="center_vertical">
+
+ <ImageView
+ android:id="@+id/control_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/dual_control_margin_between_items"
+ android:scaleType="centerInside"
+ tools:ignore="ContentDescription" />
+
+ <org.chromium.components.browser_ui.widget.DualControlLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1" >
+
+ <TextView
+ android:id="@+id/control_message"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.TextLarge.Primary" />
+
+ <TextView
+ android:id="@+id/control_secondary_message"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.TextLarge.Secondary" />
+
+ </org.chromium.components.browser_ui.widget.DualControlLayout>
+
+</LinearLayout> \ No newline at end of file
diff --git a/chromium/components/infobars/android/res/layout/infobar_control_message.xml b/chromium/components/infobars/android/res/layout/infobar_control_message.xml
new file mode 100644
index 00000000000..53fc78601cc
--- /dev/null
+++ b/chromium/components/infobars/android/res/layout/infobar_control_message.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2015 The Chromium Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file. -->
+
+<org.chromium.components.infobars.InfoBarMessageView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/infobar_message"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textDirection="locale"
+ android:textAppearance="@style/TextAppearance.TextLarge.Primary"
+ android:textColorLink="@color/default_text_color_link" />
diff --git a/chromium/components/infobars/android/res/layout/infobar_control_spinner.xml b/chromium/components/infobars/android/res/layout/infobar_control_spinner.xml
new file mode 100644
index 00000000000..a03d009323c
--- /dev/null
+++ b/chromium/components/infobars/android/res/layout/infobar_control_spinner.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2015 The Chromium Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file. -->
+
+<androidx.appcompat.widget.AppCompatSpinner
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="48dp" />
diff --git a/chromium/components/infobars/android/res/layout/infobar_control_spinner_drop_down.xml b/chromium/components/infobars/android/res/layout/infobar_control_spinner_drop_down.xml
new file mode 100644
index 00000000000..1b5222c14b9
--- /dev/null
+++ b/chromium/components/infobars/android/res/layout/infobar_control_spinner_drop_down.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright 2016 The Chromium Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/control_spinner_drop_down"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="10dp"
+ android:background="?android:attr/selectableItemBackground"
+ android:ellipsize="end"
+ android:padding="10dp"
+ android:singleLine="true" />
diff --git a/chromium/components/infobars/android/res/layout/infobar_control_spinner_view.xml b/chromium/components/infobars/android/res/layout/infobar_control_spinner_view.xml
new file mode 100644
index 00000000000..660860aed5c
--- /dev/null
+++ b/chromium/components/infobars/android/res/layout/infobar_control_spinner_view.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright 2016 The Chromium Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file.
+-->
+
+<org.chromium.components.browser_ui.widget.DualControlLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" >
+
+ <!-- Label for the data. -->
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.TextLarge.Secondary" />
+
+ <!-- Shows the actively selected item. -->
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.TextLarge.Primary" />
+
+</org.chromium.components.browser_ui.widget.DualControlLayout> \ No newline at end of file
diff --git a/chromium/components/infobars/android/res/layout/infobar_control_toggle.xml b/chromium/components/infobars/android/res/layout/infobar_control_toggle.xml
new file mode 100644
index 00000000000..9cd7acaff2c
--- /dev/null
+++ b/chromium/components/infobars/android/res/layout/infobar_control_toggle.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2015 The Chromium Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file. -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="36dp"
+ android:gravity="center_vertical">
+
+ <ImageView
+ android:id="@+id/control_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/dual_control_margin_between_items"
+ android:scaleType="centerInside"
+ tools:ignore="ContentDescription" />
+
+ <TextView
+ android:id="@+id/control_message"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_marginEnd="@dimen/dual_control_margin_between_items"
+ android:gravity="center_vertical"
+ android:textAppearance="@style/TextAppearance.TextLarge.Primary" />
+
+ <androidx.appcompat.widget.SwitchCompat
+ android:id="@+id/control_toggle_switch"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
diff --git a/chromium/components/infobars/android/res/values/dimens.xml b/chromium/components/infobars/android/res/values/dimens.xml
new file mode 100644
index 00000000000..a4c09ffe336
--- /dev/null
+++ b/chromium/components/infobars/android/res/values/dimens.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2020 The Chromium Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file. -->
+<resources xmlns:tools="http://schemas.android.com/tools">
+ <!-- Infobar dimensions -->
+ <!-- Maximum width of an infobar. -->
+ <dimen name="infobar_max_width">600dp</dimen>
+ <!-- Width of side shadows on floating infobars. -->
+ <dimen name="infobar_shadow_width">8dp</dimen>
+ <!-- Height of top shadow on each infobar. -->
+ <dimen name="infobar_shadow_height">8dp</dimen>
+ <!-- Distance that a back infobar peeks out above the front infobar. -->
+ <dimen name="infobar_peeking_height">10dp</dimen>
+
+ <!-- Dimensions for compact infobars are a little shorter. -->
+ <dimen name="infobar_compact_size">56dp</dimen>
+ <dimen name="infobar_compact_message_vertical_padding">8dp</dimen>
+
+ <!-- Margin between items in the same control group. -->
+ <dimen name="infobar_control_margin_between_rows">8dp</dimen>
+ <dimen name="infobar_control_margin_between_columns">32dp</dimen>
+ <!-- Text size of the infobar message and other controls. -->
+ <dimen name="infobar_text_size">16sp</dimen>
+ <!-- Text size of the infobar message when a big icon is shown. -->
+ <dimen name="infobar_big_icon_message_size">20sp</dimen>
+ <!-- Margin between stacked buttons in an infobar. -->
+ <dimen name="infobar_margin_between_stacked_buttons">24dp</dimen>
+ <!-- Padding surrounding the infobar. -->
+ <dimen name="infobar_padding">16dp</dimen>
+ <!-- Minimum width of an infobar. -->
+ <dimen name="infobar_min_width">220dp</dimen>
+
+ <!-- Dimensions applied to InfoBars with differently sized icons. -->
+ <dimen name="infobar_small_icon_size">24dp</dimen>
+ <dimen name="infobar_small_icon_margin">8dp</dimen>
+ <dimen name="infobar_big_icon_size">48dp</dimen>
+ <dimen name="infobar_big_icon_margin">16dp</dimen>
+
+ <!-- Vertical margin applied between groups of controls. -->
+ <dimen name="infobar_margin_above_control_groups">24dp</dimen>
+ <dimen name="infobar_margin_above_button_row">32dp</dimen>
+</resources>
diff --git a/chromium/components/infobars/android/res/values/ids.xml b/chromium/components/infobars/android/res/values/ids.xml
new file mode 100644
index 00000000000..e9fdf836382
--- /dev/null
+++ b/chromium/components/infobars/android/res/values/ids.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2020 The Chromium Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file. -->
+<resources>
+ <!-- InfoBar constants -->
+ <item type="id" name="infobar_icon" />
+ <item type="id" name="infobar_close_button" />
+ <item type="id" name="infobar_message" />
+</resources>
diff --git a/chromium/components/infobars/content/BUILD.gn b/chromium/components/infobars/content/BUILD.gn
new file mode 100644
index 00000000000..6be0c41535c
--- /dev/null
+++ b/chromium/components/infobars/content/BUILD.gn
@@ -0,0 +1,18 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+static_library("content") {
+ sources = [
+ "content_infobar_manager.cc",
+ "content_infobar_manager.h",
+ ]
+
+ public_deps = [
+ "//base",
+ "//components/infobars/core",
+ "//content/public/browser",
+ "//content/public/common",
+ "//ui/base",
+ ]
+}
diff --git a/chromium/components/infobars/content/DEPS b/chromium/components/infobars/content/DEPS
new file mode 100644
index 00000000000..c24130ef510
--- /dev/null
+++ b/chromium/components/infobars/content/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ "+content/public/browser",
+ "+content/public/common",
+]
diff --git a/chromium/components/infobars/content/content_infobar_manager.cc b/chromium/components/infobars/content/content_infobar_manager.cc
new file mode 100644
index 00000000000..d93801b6c63
--- /dev/null
+++ b/chromium/components/infobars/content/content_infobar_manager.cc
@@ -0,0 +1,115 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/infobars/content/content_infobar_manager.h"
+
+#include "base/command_line.h"
+#include "components/infobars/core/confirm_infobar_delegate.h"
+#include "components/infobars/core/infobar.h"
+#include "content/public/browser/navigation_details.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/content_switches.h"
+#include "ui/base/page_transition_types.h"
+
+namespace infobars {
+
+// static
+InfoBarDelegate::NavigationDetails
+ContentInfoBarManager::NavigationDetailsFromLoadCommittedDetails(
+ const content::LoadCommittedDetails& details) {
+ InfoBarDelegate::NavigationDetails navigation_details;
+ navigation_details.entry_id = details.entry->GetUniqueID();
+ navigation_details.is_navigation_to_different_page =
+ details.is_navigation_to_different_page();
+ navigation_details.did_replace_entry = details.did_replace_entry;
+ const ui::PageTransition transition = details.entry->GetTransitionType();
+ navigation_details.is_reload =
+ ui::PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_RELOAD);
+ navigation_details.is_redirect = ui::PageTransitionIsRedirect(transition);
+ return navigation_details;
+}
+
+// static
+content::WebContents* ContentInfoBarManager::WebContentsFromInfoBar(
+ InfoBar* infobar) {
+ if (!infobar || !infobar->owner())
+ return nullptr;
+ ContentInfoBarManager* infobar_manager =
+ static_cast<ContentInfoBarManager*>(infobar->owner());
+ return infobar_manager->web_contents();
+}
+
+ContentInfoBarManager::ContentInfoBarManager(content::WebContents* web_contents)
+ : content::WebContentsObserver(web_contents), ignore_next_reload_(false) {
+ DCHECK(web_contents);
+ // Infobar animations cause viewport resizes. Disable them for automated
+ // tests, since they could lead to flakiness.
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableAutomation))
+ set_animations_enabled(false);
+}
+
+ContentInfoBarManager::~ContentInfoBarManager() {
+ ShutDown();
+}
+
+int ContentInfoBarManager::GetActiveEntryID() {
+ content::NavigationEntry* active_entry =
+ web_contents()->GetController().GetActiveEntry();
+ return active_entry ? active_entry->GetUniqueID() : 0;
+}
+
+std::unique_ptr<InfoBar> ContentInfoBarManager::CreateConfirmInfoBar(
+ std::unique_ptr<ConfirmInfoBarDelegate> delegate) {
+ NOTREACHED();
+ return nullptr;
+}
+
+void ContentInfoBarManager::RenderProcessGone(base::TerminationStatus status) {
+ RemoveAllInfoBars(true);
+}
+
+void ContentInfoBarManager::DidStartNavigation(
+ content::NavigationHandle* navigation_handle) {
+ if (!navigation_handle->IsInMainFrame() ||
+ navigation_handle->IsSameDocument()) {
+ return;
+ }
+
+ ignore_next_reload_ = false;
+}
+
+void ContentInfoBarManager::NavigationEntryCommitted(
+ const content::LoadCommittedDetails& load_details) {
+ const bool ignore =
+ ignore_next_reload_ &&
+ ui::PageTransitionCoreTypeIs(load_details.entry->GetTransitionType(),
+ ui::PAGE_TRANSITION_RELOAD);
+ ignore_next_reload_ = false;
+ if (!ignore)
+ OnNavigation(NavigationDetailsFromLoadCommittedDetails(load_details));
+}
+
+void ContentInfoBarManager::WebContentsDestroyed() {
+ // Subclasses may override this method to destroy this object, so don't do
+ // anything here.
+}
+
+void ContentInfoBarManager::OpenURL(const GURL& url,
+ WindowOpenDisposition disposition) {
+ // A normal user click on an infobar URL will result in a CURRENT_TAB
+ // disposition; turn that into a NEW_FOREGROUND_TAB so that we don't end up
+ // smashing the page the user is looking at.
+ web_contents()->OpenURL(
+ content::OpenURLParams(url, content::Referrer(),
+ (disposition == WindowOpenDisposition::CURRENT_TAB)
+ ? WindowOpenDisposition::NEW_FOREGROUND_TAB
+ : disposition,
+ ui::PAGE_TRANSITION_LINK, false));
+
+} // namespace infobars
+
+} // namespace infobars
diff --git a/chromium/components/infobars/content/content_infobar_manager.h b/chromium/components/infobars/content/content_infobar_manager.h
new file mode 100644
index 00000000000..f9c123445ed
--- /dev/null
+++ b/chromium/components/infobars/content/content_infobar_manager.h
@@ -0,0 +1,88 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_INFOBARS_CONTENT_CONTENT_INFOBAR_MANAGER_H_
+#define COMPONENTS_INFOBARS_CONTENT_CONTENT_INFOBAR_MANAGER_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/macros.h"
+#include "build/build_config.h"
+#include "components/infobars/core/infobar_manager.h"
+#include "content/public/browser/reload_type.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/browser/web_contents_user_data.h"
+#include "ui/base/window_open_disposition.h"
+
+namespace content {
+struct LoadCommittedDetails;
+class WebContents;
+} // namespace content
+
+namespace infobars {
+
+class InfoBar;
+
+// Associates a WebContents to an InfoBarManager.
+// It manages the infobar notifications and responds to navigation events.
+// By default the creation of confirm infobars is not supported. If embedders
+// wish to add such support, they should create a custom subclass of
+// ContentInfoBarManager that overrides CreateConfirmInfoBar().
+// This class is not itself a WebContentsUserData in order to support such
+// subclassing; it is expected that embedders will either have an instance of
+// this class as a member of their "Tab" objects or create a custom subclass
+// that is a WCUD.
+class ContentInfoBarManager : public InfoBarManager,
+ public content::WebContentsObserver {
+ public:
+ explicit ContentInfoBarManager(content::WebContents* web_contents);
+ ~ContentInfoBarManager() override;
+
+ static InfoBarDelegate::NavigationDetails
+ NavigationDetailsFromLoadCommittedDetails(
+ const content::LoadCommittedDetails& details);
+
+ // This function must only be called on infobars that are owned by a
+ // ContentInfoBarManager instance (or not owned at all, in which case this
+ // returns nullptr).
+ static content::WebContents* WebContentsFromInfoBar(InfoBar* infobar);
+
+ // Makes it so the next reload is ignored. That is, if the next commit is a
+ // reload then it is treated as if nothing happened and no infobars are
+ // attempted to be closed.
+ // This is useful for non-user triggered reloads that should not dismiss
+ // infobars. For example, instant may trigger a reload when the google URL
+ // changes.
+ void set_ignore_next_reload() { ignore_next_reload_ = true; }
+
+ // InfoBarManager:
+ // NOTE: By default this method is NOTREACHED() and returns nullptr.
+ // TODO(sdefresne): Change clients to invoke this on InfoBarManager
+ // and turn the method override private.
+ std::unique_ptr<InfoBar> CreateConfirmInfoBar(
+ std::unique_ptr<ConfirmInfoBarDelegate> delegate) override;
+ void OpenURL(const GURL& url, WindowOpenDisposition disposition) override;
+
+ private:
+ // InfoBarManager:
+ int GetActiveEntryID() override;
+
+ // content::WebContentsObserver:
+ void RenderProcessGone(base::TerminationStatus status) override;
+ void DidStartNavigation(
+ content::NavigationHandle* navigation_handle) override;
+ void NavigationEntryCommitted(
+ const content::LoadCommittedDetails& load_details) override;
+ void WebContentsDestroyed() override;
+
+ // See description in set_ignore_next_reload().
+ bool ignore_next_reload_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContentInfoBarManager);
+};
+
+} // namespace infobars
+
+#endif // COMPONENTS_INFOBARS_CONTENT_CONTENT_INFOBAR_MANAGER_H_
diff --git a/chromium/components/infobars/core/infobar_delegate.cc b/chromium/components/infobars/core/infobar_delegate.cc
index 4f829ad8be8..2c5e77a7ac7 100644
--- a/chromium/components/infobars/core/infobar_delegate.cc
+++ b/chromium/components/infobars/core/infobar_delegate.cc
@@ -100,7 +100,8 @@ HungRendererInfoBarDelegate* InfoBarDelegate::AsHungRendererInfoBarDelegate() {
return nullptr;
}
-PopupBlockedInfoBarDelegate* InfoBarDelegate::AsPopupBlockedInfoBarDelegate() {
+blocked_content::PopupBlockedInfoBarDelegate*
+InfoBarDelegate::AsPopupBlockedInfoBarDelegate() {
return nullptr;
}
diff --git a/chromium/components/infobars/core/infobar_delegate.h b/chromium/components/infobars/core/infobar_delegate.h
index cc6ac572da5..b1933ea71e7 100644
--- a/chromium/components/infobars/core/infobar_delegate.h
+++ b/chromium/components/infobars/core/infobar_delegate.h
@@ -13,9 +13,12 @@
class ConfirmInfoBarDelegate;
class HungRendererInfoBarDelegate;
-class PopupBlockedInfoBarDelegate;
class ThemeInstalledInfoBarDelegate;
+namespace blocked_content {
+class PopupBlockedInfoBarDelegate;
+}
+
#if defined(OS_ANDROID)
namespace offline_pages {
class OfflinePageInfoBarDelegate;
@@ -164,6 +167,7 @@ class InfoBarDelegate {
SYNC_ERROR_INFOBAR_DELEGATE_ANDROID = 97,
MIXED_CONTENT_DOWNLOAD_INFOBAR_DELEGATE_ANDROID = 98,
CONDITIONAL_TAB_STRIP_INFOBAR_ANDROID = 99,
+ LITE_MODE_HTTPS_IMAGE_COMPRESSION_INFOBAR_ANDROID = 100,
};
// Describes navigation events, used to decide whether infobars should be
@@ -256,7 +260,8 @@ class InfoBarDelegate {
// Type-checking downcast routines:
virtual ConfirmInfoBarDelegate* AsConfirmInfoBarDelegate();
virtual HungRendererInfoBarDelegate* AsHungRendererInfoBarDelegate();
- virtual PopupBlockedInfoBarDelegate* AsPopupBlockedInfoBarDelegate();
+ virtual blocked_content::PopupBlockedInfoBarDelegate*
+ AsPopupBlockedInfoBarDelegate();
virtual ThemeInstalledInfoBarDelegate* AsThemePreviewInfobarDelegate();
virtual translate::TranslateInfoBarDelegate* AsTranslateInfoBarDelegate();
#if defined(OS_ANDROID)