Commit 84a39e04 authored by Liquan (Max) Gu's avatar Liquan (Max) Gu Committed by Commit Bot

[ExpandablePaymentHandler] Add content description to security icon

Before:
* The security icon on the toolbar did not have content description,
i.e, when the accessibility assistant did not speak when users focused
the security icon.
* The mediator uses static methods from these classes that make it hard
to test:
 - SecurityStatusIcon
 - SecurityStateModel
 - PageInfoController

After:
* The security icon has a content description. The description is the
same as the one used on custom tab toolbar.
* The static methods are invoked from the coordinator

Change:
* Add content description to security icon.
* Move the classes with static methods out of the mediator to make it
testable.

Bug: 1067659

Change-Id: Icd21f0578492e9b9b595cd06909049839c6eed85
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2140956
Commit-Queue: Liquan (Max) Gu <maxlg@chromium.org>
Reviewed-by: default avatarLiquan (Max) Gu <maxlg@chromium.org>
Reviewed-by: default avatarRouslan Solomakhin <rouslan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#759875}
parent 72d91e20
......@@ -187,6 +187,7 @@ chrome_junit_test_java_sources = [
"junit/src/org/chromium/chrome/browser/password_manager/settings/TimedCallbackDelayerTest.java",
"junit/src/org/chromium/chrome/browser/payments/AutofillContactTest.java",
"junit/src/org/chromium/chrome/browser/payments/AutofillContactUnitTest.java",
"junit/src/org/chromium/chrome/browser/payments/handler/toolbar/PaymentHandlerToolbarMediatorTest.java",
"junit/src/org/chromium/chrome/browser/photo_picker/FileEnumWorkerTaskTest.java",
"junit/src/org/chromium/chrome/browser/photo_picker/PickerBitmapViewTest.java",
"junit/src/org/chromium/chrome/browser/preferences/PrefServiceBridgeTest.java",
......
......@@ -6,10 +6,17 @@ package org.chromium.chrome.browser.payments.handler.toolbar;
import android.view.View;
import androidx.annotation.DrawableRes;
import androidx.annotation.VisibleForTesting;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.offlinepages.OfflinePageUtils;
import org.chromium.chrome.browser.page_info.ChromePageInfoControllerDelegate;
import org.chromium.chrome.browser.page_info.PageInfoController;
import org.chromium.chrome.browser.payments.handler.toolbar.PaymentHandlerToolbarMediator.PaymentHandlerToolbarMediatorDelegate;
import org.chromium.components.omnibox.SecurityStatusIcon;
import org.chromium.components.security_state.ConnectionSecurityLevel;
import org.chromium.components.security_state.SecurityStateModel;
import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.base.DeviceFormFactor;
import org.chromium.ui.modelutil.PropertyModel;
......@@ -22,11 +29,12 @@ import org.chromium.url.GURL;
* components and acts as the point of contact between them. Any code in this component that needs
* to interact with another component does that through this coordinator.
*/
public class PaymentHandlerToolbarCoordinator {
private Runnable mHider;
public class PaymentHandlerToolbarCoordinator implements PaymentHandlerToolbarMediatorDelegate {
private PaymentHandlerToolbarView mToolbarView;
private PaymentHandlerToolbarMediator mMediator;
private final WebContents mWebContents;
private final ChromeActivity mActivity;
private final boolean mIsSmallDevice;
/**
* Observer for the error of the payment handler toolbar.
......@@ -38,15 +46,19 @@ public class PaymentHandlerToolbarCoordinator {
/**
* Constructs the payment-handler toolbar component coordinator.
* @param context The main activity.
* @param webContents The {@link WebContents} of the payment handler app.
* @param context The main activity. Not allowed to be null.
* @param webContents The {@link WebContents} of the payment handler app. Not allowed to be
* null.
* @param url The url of the payment handler app, i.e., that of
* "PaymentRequestEvent.openWindow(url)".
* @param observer The observer of this toolbar.
* "PaymentRequestEvent.openWindow(url)". Not allowed to be null.
*/
public PaymentHandlerToolbarCoordinator(
ChromeActivity context, WebContents webContents, GURL url) {
assert context != null;
assert webContents != null;
assert url != null;
mWebContents = webContents;
mActivity = context;
PropertyModel model = new PropertyModel.Builder(PaymentHandlerToolbarProperties.ALL_KEYS)
.with(PaymentHandlerToolbarProperties.PROGRESS_VISIBLE, true)
.with(PaymentHandlerToolbarProperties.LOAD_PROGRESS,
......@@ -55,12 +67,12 @@ public class PaymentHandlerToolbarCoordinator {
ConnectionSecurityLevel.NONE)
.with(PaymentHandlerToolbarProperties.URL, url)
.build();
boolean isSmallDevice = !DeviceFormFactor.isNonMultiDisplayContextOnTablet(context);
mMediator = new PaymentHandlerToolbarMediator(model, context, webContents, isSmallDevice);
mIsSmallDevice = !DeviceFormFactor.isNonMultiDisplayContextOnTablet(context);
mMediator = new PaymentHandlerToolbarMediator(model, webContents, /*delegate=*/this);
mToolbarView =
new PaymentHandlerToolbarView(context, /*securityIconOnClickListener=*/mMediator);
webContents.addObserver(mMediator);
PropertyModelChangeProcessor changeProcessor = PropertyModelChangeProcessor.create(
PropertyModelChangeProcessor.create(
model, mToolbarView, PaymentHandlerToolbarViewBinder::bind);
}
......@@ -89,4 +101,38 @@ public class PaymentHandlerToolbarCoordinator {
public void clickSecurityIconForTest() {
mToolbarView.mSecurityIconView.performClick();
}
// Implement PaymentHandlerToolbarMediatorDelegate.
@Override
@ConnectionSecurityLevel
public int getSecurityLevel() {
return SecurityStateModel.getSecurityLevelForWebContents(mWebContents);
}
// Implement PaymentHandlerToolbarMediatorDelegate.
@Override
@DrawableRes
public int getSecurityIconResource(@ConnectionSecurityLevel int securityLevel) {
return SecurityStatusIcon.getSecurityIconResource(securityLevel,
SecurityStateModel.shouldShowDangerTriangleForWarningLevel(), mIsSmallDevice,
/*skipIconForNeutralState=*/true);
}
// Implement PaymentHandlerToolbarMediatorDelegate.
@Override
public String getSecurityIconContentDescription(@ConnectionSecurityLevel int securityLevel) {
int contentDescriptionRes =
SecurityStatusIcon.getSecurityIconContentDescriptionResourceId(securityLevel);
return mActivity.getResources().getString(contentDescriptionRes);
}
// Implement PaymentHandlerToolbarMediatorDelegate.
@Override
public void showPageInfoDialog() {
PageInfoController.show(mActivity, mWebContents, null,
PageInfoController.OpenedFromSource.TOOLBAR,
new ChromePageInfoControllerDelegate(mActivity, mWebContents,
/*offlinePageLoadUrlDelegate=*/
new OfflinePageUtils.WebContentsOfflinePageLoadUrlDelegate(mWebContents)));
}
}
......@@ -9,13 +9,7 @@ import android.view.View;
import androidx.annotation.DrawableRes;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.offlinepages.OfflinePageUtils;
import org.chromium.chrome.browser.page_info.ChromePageInfoControllerDelegate;
import org.chromium.chrome.browser.page_info.PageInfoController;
import org.chromium.components.omnibox.SecurityStatusIcon;
import org.chromium.components.security_state.ConnectionSecurityLevel;
import org.chromium.components.security_state.SecurityStateModel;
import org.chromium.content_public.browser.NavigationHandle;
import org.chromium.content_public.browser.WebContents;
import org.chromium.content_public.browser.WebContentsObserver;
......@@ -27,8 +21,6 @@ import org.chromium.ui.modelutil.PropertyModel;
*/
/* package */ class PaymentHandlerToolbarMediator
extends WebContentsObserver implements View.OnClickListener {
// Abbreviated for the length limit.
private static final String TAG = "PaymentHandlerTb";
/** The delay (four video frames - for 60Hz) after which the hide progress will be hidden. */
private static final long HIDE_PROGRESS_BAR_DELAY_MS = (1000 / 60) * 4;
/**
......@@ -42,24 +34,43 @@ import org.chromium.ui.modelutil.PropertyModel;
private Handler mHideProgressBarHandler;
/** Postfixed with "Ref" to distinguish from mWebContent in WebContentsObserver. */
private final WebContents mWebContentsRef;
private final boolean mIsSmallDevice;
private final ChromeActivity mChromeActivity;
private final PaymentHandlerToolbarMediatorDelegate mDelegate;
/** The delegate of PaymentHandlerToolbarMediator. */
/* package */ interface PaymentHandlerToolbarMediatorDelegate {
/** Get the security level of PaymentHandler's WebContents. */
int getSecurityLevel();
/**
* Get the security icon resource for a given security level.
* @param securityLevel The security level.
*/
@DrawableRes
int getSecurityIconResource(@ConnectionSecurityLevel int securityLevel);
/**
* Get the content description of the security icon for a given security level.
* @param securityLevel The security level.
*/
String getSecurityIconContentDescription(@ConnectionSecurityLevel int securityLevel);
/** Show the PageInfo dialog for the PaymentHandler's WebContents. */
void showPageInfoDialog();
}
/**
* Build a new mediator that handle events from outside the payment handler toolbar component.
* @param model The {@link PaymentHandlerToolbarProperties} that holds all the view state for
* the payment handler toolbar component.
* @param chromeActivity The {@link ChromeActivity}.
* @param webContents The web-contents that loads the payment app.
* @param isSmallDevice Whether the device screen is considered small.
* @param delegate The delegate of this class.
*/
/* package */ PaymentHandlerToolbarMediator(PropertyModel model, ChromeActivity chromeActivity,
WebContents webContents, boolean isSmallDevice) {
/* package */ PaymentHandlerToolbarMediator(PropertyModel model, WebContents webContents,
PaymentHandlerToolbarMediatorDelegate delegate) {
super(webContents);
mIsSmallDevice = isSmallDevice;
mWebContentsRef = webContents;
mModel = model;
mChromeActivity = chromeActivity;
mDelegate = delegate;
}
// WebContentsObserver:
......@@ -72,7 +83,6 @@ import org.chromium.ui.modelutil.PropertyModel;
mModel.set(PaymentHandlerToolbarProperties.PROGRESS_VISIBLE, false);
mHideProgressBarHandler = null;
}, HIDE_PROGRESS_BAR_DELAY_MS);
return;
}
@Override
......@@ -90,8 +100,7 @@ import org.chromium.ui.modelutil.PropertyModel;
@Override
public void didStartNavigation(NavigationHandle navigation) {
if (!navigation.isInMainFrame() || navigation.isSameDocument()) return;
mModel.set(PaymentHandlerToolbarProperties.SECURITY_ICON,
getSecurityIconResource(ConnectionSecurityLevel.NONE));
setSecurityState(ConnectionSecurityLevel.NONE);
}
@Override
......@@ -114,29 +123,23 @@ import org.chromium.ui.modelutil.PropertyModel;
Math.max(progress, MINIMUM_LOAD_PROGRESS));
}
@DrawableRes
private int getSecurityIconResource(@ConnectionSecurityLevel int securityLevel) {
return SecurityStatusIcon.getSecurityIconResource(securityLevel,
SecurityStateModel.shouldShowDangerTriangleForWarningLevel(), mIsSmallDevice,
/*skipIconForNeutralState=*/true);
private void setSecurityState(@ConnectionSecurityLevel int securityLevel) {
@DrawableRes
int iconRes = mDelegate.getSecurityIconResource(securityLevel);
mModel.set(PaymentHandlerToolbarProperties.SECURITY_ICON, iconRes);
String contentDescription = mDelegate.getSecurityIconContentDescription(securityLevel);
mModel.set(PaymentHandlerToolbarProperties.SECURITY_ICON_CONTENT_DESCRIPTION,
contentDescription);
}
@Override
public void didChangeVisibleSecurityState() {
int securityLevel = SecurityStateModel.getSecurityLevelForWebContents(mWebContentsRef);
mModel.set(PaymentHandlerToolbarProperties.SECURITY_ICON,
getSecurityIconResource(securityLevel));
setSecurityState(mDelegate.getSecurityLevel());
}
// (PaymentHandlerToolbarView security icon's) OnClickListener:
@Override
public void onClick(View view) {
if (mChromeActivity == null) return;
PageInfoController.show(mChromeActivity, mWebContentsRef, null,
PageInfoController.OpenedFromSource.TOOLBAR,
new ChromePageInfoControllerDelegate(mChromeActivity, mWebContentsRef,
/*offlinePageLoadUrlDelegate=*/
new OfflinePageUtils.WebContentsOfflinePageLoadUrlDelegate(
mWebContentsRef)));
mDelegate.showPageInfoDialog();
};
}
......@@ -27,8 +27,11 @@ import org.chromium.url.GURL;
/* package */ static final WritableIntPropertyKey SECURITY_ICON = new WritableIntPropertyKey();
/* package */ static final PropertyKey[] ALL_KEYS =
new PropertyKey[] {URL, TITLE, LOAD_PROGRESS, PROGRESS_VISIBLE, SECURITY_ICON};
/* package */ static final WritableObjectPropertyKey<String> SECURITY_ICON_CONTENT_DESCRIPTION =
new WritableObjectPropertyKey<>();
/* package */ static final PropertyKey[] ALL_KEYS = new PropertyKey[] {URL, TITLE,
LOAD_PROGRESS, PROGRESS_VISIBLE, SECURITY_ICON, SECURITY_ICON_CONTENT_DESCRIPTION};
// Prevent instantiation.
private PaymentHandlerToolbarProperties() {}
......
......@@ -32,8 +32,13 @@ import org.chromium.ui.modelutil.PropertyModel;
boolean visible = model.get(PaymentHandlerToolbarProperties.PROGRESS_VISIBLE);
view.mProgressBar.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
} else if (PaymentHandlerToolbarProperties.SECURITY_ICON == propertyKey) {
int securityIconResource = model.get(PaymentHandlerToolbarProperties.SECURITY_ICON);
view.mSecurityIconView.setImageResource(securityIconResource);
int iconRes = model.get(PaymentHandlerToolbarProperties.SECURITY_ICON);
view.mSecurityIconView.setImageResource(iconRes);
} else if (PaymentHandlerToolbarProperties.SECURITY_ICON_CONTENT_DESCRIPTION
== propertyKey) {
String description =
model.get(PaymentHandlerToolbarProperties.SECURITY_ICON_CONTENT_DESCRIPTION);
view.mSecurityIconView.setContentDescription(description);
}
}
}
// 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.
package org.chromium.chrome.browser.payments.handler.toolbar;
import android.view.View;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.mockito.quality.Strictness;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Feature;
import org.chromium.chrome.browser.payments.handler.toolbar.PaymentHandlerToolbarMediator.PaymentHandlerToolbarMediatorDelegate;
import org.chromium.components.security_state.ConnectionSecurityLevel;
import org.chromium.content_public.browser.NavigationHandle;
import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.modelutil.PropertyModel;
/**
* A test for PaymentHandlerToolbarMediator.
*/
@RunWith(BaseRobolectricTestRunner.class)
public class PaymentHandlerToolbarMediatorTest {
@Rule
public MockitoRule mMockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
@Mock
private WebContents mMockWebContents;
@Mock
private PaymentHandlerToolbarMediatorDelegate mMockDelegate;
private PropertyModel mModel;
private PaymentHandlerToolbarMediator mMediator;
@Before
public void setUp() {
mModel = new PropertyModel.Builder(PaymentHandlerToolbarProperties.ALL_KEYS).build();
mMediator = new PaymentHandlerToolbarMediator(mModel, mMockWebContents, mMockDelegate);
}
@Test
@Feature({"Payments"})
public void testDidChangeVisibleSecurityState() {
Mockito.doReturn(ConnectionSecurityLevel.NONE).when(mMockDelegate).getSecurityLevel();
Mockito.doReturn(123)
.when(mMockDelegate)
.getSecurityIconResource(ConnectionSecurityLevel.NONE);
Mockito.doReturn("this is content description.")
.when(mMockDelegate)
.getSecurityIconContentDescription(ConnectionSecurityLevel.NONE);
mMediator.didChangeVisibleSecurityState();
Assert.assertEquals(123, mModel.get(PaymentHandlerToolbarProperties.SECURITY_ICON));
Assert.assertEquals("this is content description.",
mModel.get(PaymentHandlerToolbarProperties.SECURITY_ICON_CONTENT_DESCRIPTION));
}
@Test
@Feature({"Payments"})
public void testDidStartNavigation() {
Mockito.doReturn(123).when(mMockDelegate).getSecurityIconResource(Mockito.anyInt());
Mockito.doReturn("this is content description.")
.when(mMockDelegate)
.getSecurityIconContentDescription(Mockito.anyInt());
NavigationHandle navigation = Mockito.mock(NavigationHandle.class);
Mockito.when(navigation.isInMainFrame()).thenReturn(true);
Mockito.when(navigation.isSameDocument()).thenReturn(false);
mMediator.didStartNavigation(navigation);
Assert.assertEquals(123, mModel.get(PaymentHandlerToolbarProperties.SECURITY_ICON));
Assert.assertEquals("this is content description.",
mModel.get(PaymentHandlerToolbarProperties.SECURITY_ICON_CONTENT_DESCRIPTION));
}
@Test
@Feature({"Payments"})
public void testClickSecurityIconToOpenPageInfoDialog() {
mMediator.onClick(Mockito.mock(View.class));
Mockito.verify(mMockDelegate).showPageInfoDialog();
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment