0

[FedCM] Split button mode and widget mode UI on Android

This patch changes the view initialized depending on the RP mode passed.
The new view's layout is account_selection_button_mode_sheet.xml. Note
that this is a copy of account_selection_sheet.xml with an additional
drag handlebar at the top, the UI will be changed more in future
patches. This patch is mainly to split the UI to use different layouts
for different RP modes and set up test suites.

In case it helps, there is a design section in this document briefly
outlining what should be changed.
https://docs.google.com/document/d/1ZRVb6-5_S4APS4joLzYUu2YOQDLnWCxXH3WwGiEpJ1Y/edit?resourcekey=0-eJQ6WHnMBH37PjMssVC1jg&tab=t.0#heading=h.82v445ybapt

Screenshot of layout account_selection_button_mode_sheet.xml
https://issues.chromium.org/action/issues/327273595/attachments/57246729?download=false

Bug: 327273595
Change-Id: I54deea9c1743d4f4b0b5f684fdf0d1b62457033b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5618272
Reviewed-by: Nicolás Peña <npm@chromium.org>
Code-Coverage: findit-for-me@appspot.gserviceaccount.com <findit-for-me@appspot.gserviceaccount.com>
Commit-Queue: Zachary Tan <tanzachary@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1321205}
This commit is contained in:
Zachary Tan
2024-06-28 22:06:24 +00:00
committed by Chromium LUCI CQ
parent 2550b3d4f7
commit 78a8a52f44
9 changed files with 326 additions and 83 deletions

@ -10,6 +10,7 @@
#include "base/android/jni_string.h"
#include "chrome/browser/ui/webid/account_selection_view.h"
#include "content/public/browser/identity_request_dialog_controller.h"
#include "third_party/blink/public/mojom/webid/federated_auth_request.mojom-shared.h"
#include "third_party/blink/public/mojom/webid/federated_auth_request.mojom.h"
#include "ui/android/color_utils_android.h"
#include "ui/android/window_android.h"
@ -169,8 +170,7 @@ bool AccountSelectionViewAndroid::Show(
Account::SignInMode sign_in_mode,
blink::mojom::RpMode rp_mode,
const std::optional<content::IdentityProviderData>& new_account_idp) {
// TODO(crbug.com/41491333): Use rp_mode for button flows on Android.
if (!MaybeCreateJavaObject()) {
if (!MaybeCreateJavaObject(rp_mode)) {
// It's possible that the constructor cannot access the bottom sheet clank
// component. That case may be temporary but we can't let users in a
// waiting state so report that AccountSelectionView is dismissed instead.
@ -213,8 +213,11 @@ bool AccountSelectionViewAndroid::ShowFailureDialog(
blink::mojom::RpContext rp_context,
blink::mojom::RpMode rp_mode,
const content::IdentityProviderMetadata& idp_metadata) {
// TODO(crbug.com/41491333): Use rp_mode for button flows on Android.
if (!MaybeCreateJavaObject()) {
// ShowFailureDialog is never called in button mode.
// TODO(crbug.com/347736746): Remove rp_mode from this method.
CHECK(rp_mode == blink::mojom::RpMode::kWidget);
if (!MaybeCreateJavaObject(rp_mode)) {
// It's possible that the constructor cannot access the bottom sheet clank
// component. That case may be temporary but we can't let users in a
// waiting state so report that AccountSelectionView is dismissed instead.
@ -241,8 +244,9 @@ bool AccountSelectionViewAndroid::ShowErrorDialog(
blink::mojom::RpMode rp_mode,
const content::IdentityProviderMetadata& idp_metadata,
const std::optional<TokenError>& error) {
// TODO(crbug.com/41491333): Use rp_mode for button flows on Android.
if (!MaybeCreateJavaObject()) {
// TODO(crbug.com/347117752): Implement button mode error dialog.
if (rp_mode == blink::mojom::RpMode::kButton ||
!MaybeCreateJavaObject(rp_mode)) {
// It's possible that the constructor cannot access the bottom sheet clank
// component. That case may be temporary but we can't let users in a
// waiting state so report that AccountSelectionView is dismissed instead.
@ -267,8 +271,15 @@ bool AccountSelectionViewAndroid::ShowLoadingDialog(
const std::string& idp_for_display,
blink::mojom::RpContext rp_context,
blink::mojom::RpMode rp_mode) {
// TODO(crbug.com/327273595): Prototype button flow on Android.
return false;
if (!MaybeCreateJavaObject(rp_mode)) {
// It's possible that the constructor cannot access the bottom sheet clank
// component. That case may be temporary but we can't let users in a
// waiting state so report that AccountSelectionView is dismissed instead.
delegate_->OnDismiss(DismissReason::kOther);
return false;
}
// TODO(crbug.com/327273595): Add button flow loading dialog on Android.
return true;
}
std::string AccountSelectionViewAndroid::GetTitle() const {
@ -358,7 +369,8 @@ void AccountSelectionViewAndroid::OnAccountsDisplayed(JNIEnv* env) {
delegate_->OnAccountsDisplayed();
}
bool AccountSelectionViewAndroid::MaybeCreateJavaObject() {
bool AccountSelectionViewAndroid::MaybeCreateJavaObject(
blink::mojom::RpMode rp_mode) {
if (delegate_->GetNativeView() == nullptr ||
delegate_->GetNativeView()->GetWindowAndroid() == nullptr) {
return false; // No window attached (yet or anymore).
@ -366,10 +378,12 @@ bool AccountSelectionViewAndroid::MaybeCreateJavaObject() {
if (java_object_internal_) {
return true;
}
JNIEnv* env = AttachCurrentThread();
java_object_internal_ = Java_AccountSelectionBridge_create(
AttachCurrentThread(), reinterpret_cast<intptr_t>(this),
env, reinterpret_cast<intptr_t>(this),
delegate_->GetWebContents()->GetJavaWebContents(),
delegate_->GetNativeView()->GetWindowAndroid()->GetJavaObject());
delegate_->GetNativeView()->GetWindowAndroid()->GetJavaObject(),
static_cast<jint>(rp_mode));
return !!java_object_internal_;
}

@ -10,6 +10,7 @@
#include "base/functional/callback.h"
#include "chrome/browser/ui/webid/account_selection_view.h"
#include "content/public/browser/web_contents.h"
#include "third_party/blink/public/mojom/webid/federated_auth_request.mojom-shared.h"
using TokenError = content::IdentityCredentialTokenError;
@ -71,7 +72,8 @@ class AccountSelectionViewAndroid : public AccountSelectionView {
private:
// Returns either true if the java counterpart of this bridge is initialized
// successfully or false if the creation failed.
bool MaybeCreateJavaObject();
bool MaybeCreateJavaObject(
blink::mojom::RpMode rp_mode = blink::mojom::RpMode::kWidget);
base::android::ScopedJavaGlobalRef<jobject> java_object_internal_;
};

@ -38,6 +38,7 @@ android_library("java") {
"//third_party/androidx:androidx_annotation_annotation_java",
"//third_party/androidx:androidx_browser_browser_java",
"//third_party/androidx:androidx_recyclerview_recyclerview_java",
"//third_party/blink/public/mojom:mojom_platform_java",
"//third_party/jni_zero:jni_zero_java",
"//ui/android:ui_java",
"//url:gurl_java",
@ -64,6 +65,7 @@ android_resources("java_resources") {
]
sources = [
"java/res/layout/account_selection_account_item.xml",
"java/res/layout/account_selection_button_mode_sheet.xml",
"java/res/layout/account_selection_continue_button.xml",
"java/res/layout/account_selection_data_sharing_consent_item.xml",
"java/res/layout/account_selection_header_item.xml",
@ -78,6 +80,7 @@ android_resources("java_resources") {
robolectric_library("junit") {
resources_package = "org.chromium.chrome.browser.ui.android.webid"
sources = [
"java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionButtonModeViewTest.java",
"java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionControllerTest.java",
"java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionViewTest.java",
]
@ -101,6 +104,7 @@ robolectric_library("junit") {
"//third_party/androidx:androidx_recyclerview_recyclerview_java",
"//third_party/androidx:androidx_test_core_java",
"//third_party/androidx:androidx_test_ext_junit_java",
"//third_party/blink/public/mojom:mojom_platform_java",
"//third_party/junit",
"//third_party/mockito:mockito_java",
"//ui/android:ui_full_java",
@ -144,6 +148,7 @@ android_library("javatests") {
"//third_party/androidx:androidx_recyclerview_recyclerview_java",
"//third_party/androidx:androidx_test_monitor_java",
"//third_party/androidx:androidx_test_runner_java",
"//third_party/blink/public/mojom:mojom_platform_java",
"//third_party/hamcrest:hamcrest_core_java",
"//third_party/hamcrest:hamcrest_library_java",
"//third_party/junit",

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 The Chromium Authors
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:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:paddingBottom="8dp"
android:orientation="vertical">
<ImageView
android:id="@+id/drag_handlebar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:importantForAccessibility="no"
app:srcCompat="@drawable/drag_handlebar" />
<include layout="@layout/account_selection_header_item"
android:id="@+id/header_view_item" />
<FrameLayout
android:id="@+id/sheet_item_list_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/sheet_item_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:divider="@null"
tools:listitem="@layout/account_selection_account_item"/>
</FrameLayout>
<include layout="@layout/idp_signin_text_item"
android:id="@+id/idp_signin"
android:visibility="gone"/>
<LinearLayout
android:id="@+id/error_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/account_selection_sheet_horizontal_margin"
android:orientation="vertical"
android:layoutDirection="locale"
android:textDirection="locale"
android:visibility="gone">
<include layout="@layout/error_summary_text_item"
android:id="@+id/error_summary"/>
<include layout="@layout/error_description_text_item"
android:id="@+id/error_description"/>
</LinearLayout>
<include layout="@layout/account_selection_continue_button"
android:id="@+id/account_selection_continue_btn"
android:visibility="gone"/>
<include layout="@layout/account_selection_data_sharing_consent_item"
android:id="@+id/user_data_sharing_consent"
android:visibility="gone"/>
</LinearLayout>

@ -12,6 +12,7 @@ import org.jni_zero.CalledByNative;
import org.jni_zero.NativeMethods;
import org.chromium.base.ContextUtils;
import org.chromium.blink.mojom.RpMode;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabUtils;
import org.chromium.chrome.browser.ui.android.webid.data.Account;
@ -46,10 +47,12 @@ class AccountSelectionBridge implements AccountSelectionComponent.Delegate {
long nativeView,
Tab tab,
WindowAndroid windowAndroid,
BottomSheetController bottomSheetController) {
BottomSheetController bottomSheetController,
@RpMode.EnumType int rpMode) {
mNativeView = nativeView;
mAccountSelectionComponent =
new AccountSelectionCoordinator(tab, windowAndroid, bottomSheetController, this);
new AccountSelectionCoordinator(
tab, windowAndroid, bottomSheetController, rpMode, this);
}
@CalledByNative
@ -72,12 +75,16 @@ class AccountSelectionBridge implements AccountSelectionComponent.Delegate {
@CalledByNative
private static @Nullable AccountSelectionBridge create(
long nativeView, WebContents webContents, WindowAndroid windowAndroid) {
long nativeView,
WebContents webContents,
WindowAndroid windowAndroid,
@RpMode.EnumType int rpMode) {
BottomSheetController bottomSheetController =
BottomSheetControllerProvider.from(windowAndroid);
if (bottomSheetController == null) return null;
Tab tab = TabUtils.fromWebContents(webContents);
return new AccountSelectionBridge(nativeView, tab, windowAndroid, bottomSheetController);
return new AccountSelectionBridge(
nativeView, tab, windowAndroid, bottomSheetController, rpMode);
}
@CalledByNative

@ -0,0 +1,70 @@
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.ui.android.webid;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import android.view.View;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.MockitoAnnotations;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.blink.mojom.RpMode;
import org.chromium.ui.base.TestActivity;
import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
import org.chromium.ui.modelutil.PropertyModel;
/**
* View tests for the Account Selection Button Mode component ensure that model changes are
* reflected in the sheet.
*/
@RunWith(BaseRobolectricTestRunner.class)
public class AccountSelectionButtonModeViewTest {
@Rule
public ActivityScenarioRule<TestActivity> mActivityScenarioRule =
new ActivityScenarioRule<>(TestActivity.class);
private PropertyModel mModel;
private ModelList mSheetAccountItems;
private View mContentView;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mActivityScenarioRule
.getScenario()
.onActivity(
activity -> {
mModel =
new PropertyModel.Builder(
AccountSelectionProperties.ItemProperties
.ALL_KEYS)
.build();
mSheetAccountItems = new ModelList();
mContentView =
AccountSelectionCoordinator.setupContentView(
activity,
mModel,
mSheetAccountItems,
/* rpMode= */ RpMode.BUTTON);
activity.setContentView(mContentView);
});
}
@Test
public void testDragHandlebarShown() {
assertEquals(View.VISIBLE, mContentView.getVisibility());
View handlebar = mContentView.findViewById(R.id.drag_handlebar);
assertTrue(handlebar.isShown());
}
}

@ -25,6 +25,7 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import org.chromium.base.IntentUtils;
import org.chromium.blink.mojom.RpMode;
import org.chromium.chrome.browser.IntentHandler;
import org.chromium.chrome.browser.LaunchIntentDispatcher;
import org.chromium.chrome.browser.app.ChromeActivity;
@ -82,6 +83,7 @@ public class AccountSelectionCoordinator
Tab tab,
WindowAndroid windowAndroid,
BottomSheetController sheetController,
@RpMode.EnumType int rpMode,
AccountSelectionComponent.Delegate delegate) {
mBottomSheetController = sheetController;
mWindowAndroid = windowAndroid;
@ -93,7 +95,7 @@ public class AccountSelectionCoordinator
.build();
// Construct view and its related adaptor to be displayed in the bottom sheet.
ModelList sheetItems = new ModelList();
View contentView = setupContentView(context, model, sheetItems);
View contentView = setupContentView(context, model, sheetItems, rpMode);
mSheetItemListView = contentView.findViewById(R.id.sheet_item_list);
// Setup the bottom sheet content view.
@ -124,11 +126,18 @@ public class AccountSelectionCoordinator
avatarSize);
}
static View setupContentView(Context context, PropertyModel model, ModelList sheetItems) {
static View setupContentView(
Context context,
PropertyModel model,
ModelList sheetItems,
@RpMode.EnumType int rpMode) {
int accountSelectionSheetLayout =
rpMode == RpMode.BUTTON
? R.layout.account_selection_button_mode_sheet
: R.layout.account_selection_sheet;
View contentView =
(LinearLayout)
LayoutInflater.from(context)
.inflate(R.layout.account_selection_sheet, null);
LayoutInflater.from(context).inflate(accountSelectionSheetLayout, null);
PropertyModelChangeProcessor.create(
model, contentView, AccountSelectionViewBinder::bindContentView);

@ -49,12 +49,16 @@ import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.chromium.base.test.params.ParameterAnnotations;
import org.chromium.base.test.params.ParameterSet;
import org.chromium.base.test.params.ParameterizedRunner;
import org.chromium.base.test.util.ApplicationTestUtils;
import org.chromium.base.test.util.Batch;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Criteria;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.base.test.util.ScalableTimeout;
import org.chromium.blink.mojom.RpMode;
import org.chromium.chrome.browser.IntentHandler;
import org.chromium.chrome.browser.customtabs.CustomTabActivity;
import org.chromium.chrome.browser.flags.ChromeSwitches;
@ -62,7 +66,7 @@ import org.chromium.chrome.browser.ui.android.webid.data.Account;
import org.chromium.chrome.browser.ui.android.webid.data.ClientIdMetadata;
import org.chromium.chrome.browser.ui.android.webid.data.IdentityCredentialTokenError;
import org.chromium.chrome.browser.ui.android.webid.data.IdentityProviderMetadata;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.components.browser_ui.bottomsheet.BottomSheetContent;
import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
@ -74,15 +78,28 @@ import org.chromium.url.GURL;
import org.chromium.url.JUnitTestGURLs;
import java.util.Arrays;
import java.util.List;
/**
* Integration tests for the Account Selection component check that the calls to the Account
* Selection API end up rendering a View.
* Selection API end up rendering a View. This class is parameterized to run all tests for each RP
* mode.
*/
@RunWith(ChromeJUnit4ClassRunner.class)
@RunWith(ParameterizedRunner.class)
@ParameterAnnotations.UseRunnerDelegate(ChromeJUnit4RunnerDelegate.class)
@Batch(Batch.PER_CLASS)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
public class AccountSelectionIntegrationTest {
@ParameterAnnotations.ClassParameter
private static List<ParameterSet> sClassParams =
Arrays.asList(
new ParameterSet().value(RpMode.WIDGET).name("widget"),
new ParameterSet().value(RpMode.BUTTON).name("button"));
public AccountSelectionIntegrationTest(@RpMode.EnumType int rpMode) {
mRpMode = rpMode;
}
private static final String EXAMPLE_ETLD_PLUS_ONE = "example.com";
private static final String TEST_ETLD_PLUS_ONE_1 = "one.com";
private static final String TEST_ETLD_PLUS_ONE_2 = "two.com";
@ -127,6 +144,7 @@ public class AccountSelectionIntegrationTest {
private String mTestUrlTermsOfService;
private String mTestUrlPrivacyPolicy;
private ClientIdMetadata mClientIdMetadata;
private @RpMode.EnumType int mRpMode;
@Before
public void setUp() throws InterruptedException {
@ -142,6 +160,7 @@ public class AccountSelectionIntegrationTest {
mActivityTestRule.getActivity().getActivityTab(),
mActivityTestRule.getActivity().getWindowAndroid(),
mBottomSheetController,
mRpMode,
mMockBridge);
});
@ -374,6 +393,7 @@ public class AccountSelectionIntegrationTest {
activity.getActivityTab(),
activity.getWindowAndroid(),
customTabController,
mRpMode,
mMockBridge);
customTabComponent.closeModalDialog();
});

@ -30,12 +30,16 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.ParameterizedRobolectricTestRunner;
import org.robolectric.ParameterizedRobolectricTestRunner.Parameter;
import org.robolectric.ParameterizedRobolectricTestRunner.Parameters;
import org.robolectric.shadows.ShadowLooper;
import org.chromium.base.Callback;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.BaseRobolectricTestRule;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.base.test.util.ScalableTimeout;
import org.chromium.blink.mojom.RpMode;
import org.chromium.chrome.browser.ui.android.webid.AccountSelectionProperties.AccountProperties;
import org.chromium.chrome.browser.ui.android.webid.AccountSelectionProperties.ContinueButtonProperties;
import org.chromium.chrome.browser.ui.android.webid.AccountSelectionProperties.DataSharingConsentProperties;
@ -53,47 +57,48 @@ import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.widget.ButtonCompat;
import org.chromium.url.GURL;
import org.chromium.url.JUnitTestGURLs;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
/**
* View tests for the Account Selection component ensure that model changes are reflected in the
* sheet.
* sheet. This class is parameterized to run all tests for each RP mode.
*/
@RunWith(BaseRobolectricTestRunner.class)
@RunWith(ParameterizedRobolectricTestRunner.class)
public class AccountSelectionViewTest {
@Parameter(0)
public @RpMode.EnumType int mRpMode;
@Parameters
public static Collection<Object> data() {
return Arrays.asList(new Object[] {RpMode.WIDGET, RpMode.BUTTON});
}
@Rule(order = -2)
public BaseRobolectricTestRule mBaseRule = new BaseRobolectricTestRule();
// Note that these are not actual ETLD+1 values, but this is irrelevant for the purposes of this
// test.
private static final String TEST_IDP_ETLD_PLUS_ONE = JUnitTestGURLs.EXAMPLE_URL.getSpec();
private static final String TEST_RP_ETLD_PLUS_ONE = JUnitTestGURLs.URL_1.getSpec();
private static final GURL TEST_PROFILE_PIC = JUnitTestGURLs.EXAMPLE_URL;
private static final GURL TEST_CONFIG_URL = JUnitTestGURLs.URL_1;
private static final GURL TEST_LOGIN_URL = JUnitTestGURLs.URL_2;
private static final Account ANA =
new Account("Ana", "ana@email.example", "Ana Doe", "Ana", TEST_PROFILE_PIC, null, true);
private static final Account NO_ONE =
new Account("", "", "No Subject", "", TEST_PROFILE_PIC, null, true);
private static final Account BOB =
new Account("Bob", "", "Bob", "", TEST_PROFILE_PIC, null, true);
private static final GURL TEST_ERROR_URL = JUnitTestGURLs.URL_1;
private static final GURL TEST_EMPTY_ERROR_URL = new GURL("");
private static final String TEST_IDP_ETLD_PLUS_ONE = "https://idp.com";
private static final String TEST_RP_ETLD_PLUS_ONE = "https://rp.com";
// Android chrome strings may have link tags which needs to be removed before comparing with the
// actual text on the dialog.
private static final String LINK_TAG_REGEX = "<[^>]*>";
private static final IdentityProviderMetadata TEST_IDP_METADATA =
new IdentityProviderMetadata(
Color.BLUE,
Color.GREEN,
"https://icon-url.example",
TEST_CONFIG_URL,
TEST_LOGIN_URL,
false);
// Constants but can only be initialized after parameterized test runner setup.
private GURL mTestProfilePicUrl;
private GURL mTestConfigUrl;
private GURL mTestLoginUrl;
private GURL mTestErrorUrl;
private GURL mTestEmptyErrorUrl;
private Account mAnaAccount;
private Account mNoOneAccount;
private Account mBobAccount;
private IdentityProviderMetadata mTestIdpMetadata;
private class RpContext {
public String mValue;
@ -178,7 +183,7 @@ public class AccountSelectionViewTest {
private final String appendExtraDescription(String code, GURL url) {
String initialDescription = mCodeToDescription.get(code);
if (AccountSelectionViewBinder.GENERIC.equals(code)) {
if (TEST_EMPTY_ERROR_URL.equals(url)) {
if (mTestEmptyErrorUrl.equals(url)) {
return initialDescription;
}
return initialDescription
@ -188,7 +193,7 @@ public class AccountSelectionViewTest {
.replaceAll(LINK_TAG_REGEX, "");
}
if (TEST_EMPTY_ERROR_URL.equals(url)) {
if (mTestEmptyErrorUrl.equals(url)) {
return initialDescription
+ " "
+ mResources.getString(
@ -224,6 +229,49 @@ public class AccountSelectionViewTest {
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mTestProfilePicUrl = new GURL("https://profile-picture.com");
mTestConfigUrl = new GURL("https://idp.com/fedcm.json");
mTestLoginUrl = new GURL("https://idp.com/login");
mTestErrorUrl = new GURL("https://idp.com/error");
mTestEmptyErrorUrl = new GURL("");
mAnaAccount =
new Account(
"Ana",
"ana@email.example",
"Ana Doe",
"Ana",
mTestProfilePicUrl,
/* pictureBitmap= */ null,
/* isSignIn= */ true);
mNoOneAccount =
new Account(
"",
"",
"No Subject",
"",
mTestProfilePicUrl,
/* pictureBitmap= */ null,
/* isSignIn= */ true);
mBobAccount =
new Account(
"Bob",
"",
"Bob",
"",
mTestProfilePicUrl,
/* pictureBitmap= */ null,
/* isSignIn= */ true);
mTestIdpMetadata =
new IdentityProviderMetadata(
Color.BLUE,
Color.GREEN,
"https://icon-url.example",
mTestConfigUrl,
mTestLoginUrl,
false);
mActivityScenarioRule
.getScenario()
.onActivity(
@ -236,7 +284,7 @@ public class AccountSelectionViewTest {
mSheetAccountItems = new ModelList();
mContentView =
AccountSelectionCoordinator.setupContentView(
activity, mModel, mSheetAccountItems);
activity, mModel, mSheetAccountItems, mRpMode);
activity.setContentView(mContentView);
mResources = activity.getResources();
});
@ -341,22 +389,25 @@ public class AccountSelectionViewTest {
@Test
public void testAccountsChangedByModel() {
mSheetAccountItems.addAll(
asList(buildAccountItem(ANA), buildAccountItem(NO_ONE), buildAccountItem(BOB)));
asList(
buildAccountItem(mAnaAccount),
buildAccountItem(mNoOneAccount),
buildAccountItem(mBobAccount)));
ShadowLooper.shadowMainLooper().idle();
assertEquals(View.VISIBLE, mContentView.getVisibility());
assertEquals("Incorrect account count", 3, getAccounts().getChildCount());
assertEquals("Incorrect name", ANA.getName(), getAccountNameAt(0).getText());
assertEquals("Incorrect email", ANA.getEmail(), getAccountEmailAt(0).getText());
assertEquals("Incorrect name", NO_ONE.getName(), getAccountNameAt(1).getText());
assertEquals("Incorrect email", NO_ONE.getEmail(), getAccountEmailAt(1).getText());
assertEquals("Incorrect name", BOB.getName(), getAccountNameAt(2).getText());
assertEquals("Incorrect email", BOB.getEmail(), getAccountEmailAt(2).getText());
assertEquals("Incorrect name", mAnaAccount.getName(), getAccountNameAt(0).getText());
assertEquals("Incorrect email", mAnaAccount.getEmail(), getAccountEmailAt(0).getText());
assertEquals("Incorrect name", mNoOneAccount.getName(), getAccountNameAt(1).getText());
assertEquals("Incorrect email", mNoOneAccount.getEmail(), getAccountEmailAt(1).getText());
assertEquals("Incorrect name", mBobAccount.getName(), getAccountNameAt(2).getText());
assertEquals("Incorrect email", mBobAccount.getEmail(), getAccountEmailAt(2).getText());
}
@Test
public void testAccountsAreClickable() {
mSheetAccountItems.addAll(Collections.singletonList(buildAccountItem(ANA)));
mSheetAccountItems.addAll(Collections.singletonList(buildAccountItem(mAnaAccount)));
ShadowLooper.shadowMainLooper().idle();
assertEquals(View.VISIBLE, mContentView.getVisibility());
@ -365,7 +416,7 @@ public class AccountSelectionViewTest {
getAccounts().getChildAt(0).performClick();
waitForEvent(mAccountCallback).onResult(eq(ANA));
waitForEvent(mAccountCallback).onResult(eq(mAnaAccount));
}
@Test
@ -376,14 +427,14 @@ public class AccountSelectionViewTest {
new MVCListAdapter.ListItem(
AccountSelectionProperties.ITEM_TYPE_ACCOUNT,
new PropertyModel.Builder(AccountProperties.ALL_KEYS)
.with(AccountProperties.ACCOUNT, ANA)
.with(AccountProperties.ACCOUNT, mAnaAccount)
.with(AccountProperties.ON_CLICK_LISTENER, null)
.build()));
ShadowLooper.shadowMainLooper().idle();
mModel.set(
ItemProperties.CONTINUE_BUTTON,
buildContinueButton(ANA, TEST_IDP_METADATA, HeaderType.SIGN_IN));
buildContinueButton(mAnaAccount, mTestIdpMetadata, HeaderType.SIGN_IN));
assertEquals(View.VISIBLE, mContentView.getVisibility());
assertNotNull(getAccounts().getChildAt(0));
@ -392,7 +443,7 @@ public class AccountSelectionViewTest {
assertTrue(continueButton.isShown());
continueButton.performClick();
waitForEvent(mAccountCallback).onResult(eq(ANA));
waitForEvent(mAccountCallback).onResult(eq(mAnaAccount));
}
@Test
@ -427,13 +478,13 @@ public class AccountSelectionViewTest {
expectedTextColor,
/* brandBackgroundColor= */ Color.GREEN,
"https://icon-url.example",
TEST_CONFIG_URL,
TEST_LOGIN_URL,
mTestConfigUrl,
mTestLoginUrl,
false);
mModel.set(
ItemProperties.CONTINUE_BUTTON,
buildContinueButton(ANA, idpMetadata, HeaderType.SIGN_IN));
buildContinueButton(mAnaAccount, idpMetadata, HeaderType.SIGN_IN));
assertEquals(View.VISIBLE, mContentView.getVisibility());
@ -513,7 +564,7 @@ public class AccountSelectionViewTest {
mModel.set(
ItemProperties.CONTINUE_BUTTON,
buildContinueButton(null, TEST_IDP_METADATA, HeaderType.SIGN_IN_TO_IDP_STATIC));
buildContinueButton(null, mTestIdpMetadata, HeaderType.SIGN_IN_TO_IDP_STATIC));
ButtonCompat continueButton =
mContentView.findViewById(R.id.account_selection_continue_btn);
assertTrue(continueButton.isShown());
@ -527,23 +578,21 @@ public class AccountSelectionViewTest {
public void testErrorDisplayed() {
final TokenError[] mErrors =
new TokenError[] {
new TokenError(AccountSelectionViewBinder.GENERIC, TEST_EMPTY_ERROR_URL),
new TokenError(AccountSelectionViewBinder.GENERIC, TEST_ERROR_URL),
new TokenError(AccountSelectionViewBinder.GENERIC, mTestEmptyErrorUrl),
new TokenError(AccountSelectionViewBinder.GENERIC, mTestErrorUrl),
new TokenError(AccountSelectionViewBinder.INVALID_REQUEST, mTestEmptyErrorUrl),
new TokenError(AccountSelectionViewBinder.INVALID_REQUEST, mTestErrorUrl),
new TokenError(
AccountSelectionViewBinder.INVALID_REQUEST, TEST_EMPTY_ERROR_URL),
new TokenError(AccountSelectionViewBinder.INVALID_REQUEST, TEST_ERROR_URL),
AccountSelectionViewBinder.UNAUTHORIZED_CLIENT, mTestEmptyErrorUrl),
new TokenError(AccountSelectionViewBinder.UNAUTHORIZED_CLIENT, mTestErrorUrl),
new TokenError(AccountSelectionViewBinder.ACCESS_DENIED, mTestEmptyErrorUrl),
new TokenError(AccountSelectionViewBinder.ACCESS_DENIED, mTestErrorUrl),
new TokenError(
AccountSelectionViewBinder.UNAUTHORIZED_CLIENT, TEST_EMPTY_ERROR_URL),
new TokenError(AccountSelectionViewBinder.UNAUTHORIZED_CLIENT, TEST_ERROR_URL),
new TokenError(AccountSelectionViewBinder.ACCESS_DENIED, TEST_EMPTY_ERROR_URL),
new TokenError(AccountSelectionViewBinder.ACCESS_DENIED, TEST_ERROR_URL),
AccountSelectionViewBinder.TEMPORARILY_UNAVAILABLE, mTestEmptyErrorUrl),
new TokenError(
AccountSelectionViewBinder.TEMPORARILY_UNAVAILABLE,
TEST_EMPTY_ERROR_URL),
new TokenError(
AccountSelectionViewBinder.TEMPORARILY_UNAVAILABLE, TEST_ERROR_URL),
new TokenError(AccountSelectionViewBinder.SERVER_ERROR, TEST_EMPTY_ERROR_URL),
new TokenError(AccountSelectionViewBinder.SERVER_ERROR, TEST_ERROR_URL)
AccountSelectionViewBinder.TEMPORARILY_UNAVAILABLE, mTestErrorUrl),
new TokenError(AccountSelectionViewBinder.SERVER_ERROR, mTestEmptyErrorUrl),
new TokenError(AccountSelectionViewBinder.SERVER_ERROR, mTestErrorUrl)
};
for (TokenError error : mErrors) {