Commit f598b314 authored by Nohemi Fernandez's avatar Nohemi Fernandez Committed by Commit Bot

[iOS] Add a new option to non-managed account Sign-Out.

Re-enables the 'ClearSyncData' feature reverted in change 2020323.
Fixes the DCHECK errors due to incomplete URLs in tests and uses
the ScopedFeatureList to enable the experimental flag that is
compatible with both EarlGrey 1 and 2.

Bug: 1005509
Change-Id: Ib300eb8d89e125e9bfa26045b41f59209228ac85
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2019362Reviewed-by: default avatarSylvain Defresne <sdefresne@chromium.org>
Commit-Queue: Nohemi Fernandez <fernandex@chromium.org>
Cr-Commit-Position: refs/heads/master@{#735382}
parent 09878ba9
...@@ -662,6 +662,9 @@ locale. The strings in this file are specific to iOS. ...@@ -662,6 +662,9 @@ locale. The strings in this file are specific to iOS.
<message name="IDS_IOS_DISCONNECT_DIALOG_CONTINUE_BUTTON_MOBILE" desc="The title of the continue button on the disconnect dialog [Length: 20em]."> <message name="IDS_IOS_DISCONNECT_DIALOG_CONTINUE_BUTTON_MOBILE" desc="The title of the continue button on the disconnect dialog [Length: 20em].">
Sign Out Sign Out
</message> </message>
<message name="IDS_IOS_DISCONNECT_DIALOG_CONTINUE_AND_CLEAR_MOBILE" desc="The title of the continue and clear browsing data button on the disconnect dialog [iOS only].">
Sign Out and Clear Synced Data
</message>
<message name="IDS_IOS_DISCONNECT_DIALOG_INFO_MOBILE" desc="The information text on the disconnect dialog [Length: 400em]."> <message name="IDS_IOS_DISCONNECT_DIALOG_INFO_MOBILE" desc="The information text on the disconnect dialog [Length: 400em].">
Changes to your bookmarks, history, passwords, and other settings will no longer be synced to your Google Account. However, your existing data will remain stored in your Google Account. Changes to your bookmarks, history, passwords, and other settings will no longer be synced to your Google Account. However, your existing data will remain stored in your Google Account.
</message> </message>
......
...@@ -11,6 +11,12 @@ ...@@ -11,6 +11,12 @@
@class FakeChromeIdentity; @class FakeChromeIdentity;
typedef NS_ENUM(NSInteger, SignOutConfirmation) {
SignOutConfirmationManagedUser,
SignOutConfirmationNonManagedUser,
SignOutConfirmationNonManagedUserWithClearedData,
};
// Methods used for the EarlGrey tests, related to UI. // Methods used for the EarlGrey tests, related to UI.
@interface SigninEarlGreyUI : NSObject @interface SigninEarlGreyUI : NSObject
...@@ -57,9 +63,9 @@ ...@@ -57,9 +63,9 @@
// Checks that the sign-in promo view is not visible. // Checks that the sign-in promo view is not visible.
+ (void)checkSigninPromoNotVisible; + (void)checkSigninPromoNotVisible;
// Signs out from the current identity. if |isManagedAccount| is true, the // Taps the appropriate action label on the sign-out dialog for the given
// confirmed managed dialog is confirmed while signing out. // |signOutConfirmation| profile and signs out from the current identity.
+ (void)signOutWithManagedAccount:(BOOL)isManagedAccount; + (void)signOutWithSignOutConfirmation:(SignOutConfirmation)signOutConfirmation;
@end @end
......
...@@ -189,16 +189,31 @@ using chrome_test_util::UnifiedConsentAddAccountButton; ...@@ -189,16 +189,31 @@ using chrome_test_util::UnifiedConsentAddAccountButton;
assertWithMatcher:grey_nil()]; assertWithMatcher:grey_nil()];
} }
+ (void)signOutWithManagedAccount:(BOOL)isManagedAccount { + (void)signOutWithSignOutConfirmation:
(SignOutConfirmation)signOutConfirmation {
[ChromeEarlGreyUI openSettingsMenu]; [ChromeEarlGreyUI openSettingsMenu];
[ChromeEarlGreyUI tapSettingsMenuButton:SettingsAccountButton()]; [ChromeEarlGreyUI tapSettingsMenuButton:SettingsAccountButton()];
[ChromeEarlGreyUI tapAccountsMenuButton:SignOutAccountsButton()]; [ChromeEarlGreyUI tapAccountsMenuButton:SignOutAccountsButton()];
int confirmationLabelID = 0; int confirmationLabelID = 0;
if (isManagedAccount) { switch (signOutConfirmation) {
confirmationLabelID = IDS_IOS_MANAGED_DISCONNECT_DIALOG_ACCEPT_UNITY; case SignOutConfirmationManagedUser: {
} else { confirmationLabelID = IDS_IOS_MANAGED_DISCONNECT_DIALOG_ACCEPT_UNITY;
confirmationLabelID = IDS_IOS_DISCONNECT_DIALOG_CONTINUE_BUTTON_MOBILE; break;
}
case SignOutConfirmationNonManagedUser: {
confirmationLabelID = IDS_IOS_DISCONNECT_DIALOG_CONTINUE_BUTTON_MOBILE;
break;
}
case SignOutConfirmationNonManagedUserWithClearedData: {
confirmationLabelID = IDS_IOS_DISCONNECT_DIALOG_CONTINUE_AND_CLEAR_MOBILE;
break;
}
default: {
NOTREACHED();
break;
}
} }
id<GREYMatcher> confirmationButtonMatcher = [ChromeMatchersAppInterface id<GREYMatcher> confirmationButtonMatcher = [ChromeMatchersAppInterface
buttonWithAccessibilityLabelID:confirmationLabelID]; buttonWithAccessibilityLabelID:confirmationLabelID];
[[EarlGrey selectElementWithMatcher:confirmationButtonMatcher] [[EarlGrey selectElementWithMatcher:confirmationButtonMatcher]
......
...@@ -109,9 +109,12 @@ source_set("eg_tests") { ...@@ -109,9 +109,12 @@ source_set("eg_tests") {
deps = [ deps = [
":constants", ":constants",
":test_support", ":test_support",
"//base/test:test_support",
"//ios/chrome/app/strings", "//ios/chrome/app/strings",
"//ios/chrome/browser/tabs", "//ios/chrome/browser/tabs",
"//ios/chrome/browser/ui:feature_flags",
"//ios/chrome/browser/ui/authentication:eg_test_support", "//ios/chrome/browser/ui/authentication:eg_test_support",
"//ios/chrome/browser/ui/bookmarks:eg_test_support",
"//ios/chrome/test/app:test_support", "//ios/chrome/test/app:test_support",
"//ios/chrome/test/earl_grey:test_support", "//ios/chrome/test/earl_grey:test_support",
"//ios/public/provider/chrome/browser/signin:fake_chrome_identity", "//ios/public/provider/chrome/browser/signin:fake_chrome_identity",
...@@ -179,8 +182,11 @@ source_set("eg2_tests") { ...@@ -179,8 +182,11 @@ source_set("eg2_tests") {
deps = [ deps = [
":eg_test_support+eg2", ":eg_test_support+eg2",
"//base", "//base",
"//base/test:test_support",
"//ios/chrome/app/strings", "//ios/chrome/app/strings",
"//ios/chrome/browser/ui:feature_flags",
"//ios/chrome/browser/ui/authentication:eg_test_support+eg2", "//ios/chrome/browser/ui/authentication:eg_test_support+eg2",
"//ios/chrome/browser/ui/bookmarks:eg_test_support+eg2",
"//ios/chrome/browser/ui/settings/google_services:constants", "//ios/chrome/browser/ui/settings/google_services:constants",
"//ios/chrome/test/earl_grey:eg_test_support+eg2", "//ios/chrome/test/earl_grey:eg_test_support+eg2",
"//ios/public/provider/chrome/browser/signin:fake_chrome_identity", "//ios/public/provider/chrome/browser/signin:fake_chrome_identity",
......
...@@ -2,13 +2,22 @@ ...@@ -2,13 +2,22 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#import "base/test/scoped_feature_list.h"
#import "ios/chrome/browser/ui/authentication/signin_earl_grey_ui.h" #import "ios/chrome/browser/ui/authentication/signin_earl_grey_ui.h"
#import "ios/chrome/browser/ui/authentication/signin_earlgrey_utils.h" #import "ios/chrome/browser/ui/authentication/signin_earlgrey_utils.h"
#import "ios/chrome/browser/ui/authentication/signin_earlgrey_utils_app_interface.h"
#import "ios/chrome/browser/ui/bookmarks/bookmark_earl_grey.h"
#import "ios/chrome/browser/ui/bookmarks/bookmark_earl_grey_ui.h"
#import "ios/chrome/browser/ui/ui_feature_flags.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
#import "ios/chrome/test/earl_grey/chrome_earl_grey_ui.h" #import "ios/chrome/test/earl_grey/chrome_earl_grey_ui.h"
#import "ios/chrome/test/earl_grey/chrome_matchers.h" #import "ios/chrome/test/earl_grey/chrome_matchers.h"
#import "ios/chrome/test/earl_grey/chrome_test_case.h" #import "ios/chrome/test/earl_grey/chrome_test_case.h"
#import "ios/public/provider/chrome/browser/signin/fake_chrome_identity.h" #import "ios/public/provider/chrome/browser/signin/fake_chrome_identity.h"
#import "ios/testing/earl_grey/app_launch_manager.h"
#import "ios/testing/earl_grey/earl_grey_test.h" #import "ios/testing/earl_grey/earl_grey_test.h"
#include "ui/base/l10n/l10n_util.h"
#if !defined(__has_feature) || !__has_feature(objc_arc) #if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support." #error "This file requires ARC support."
...@@ -22,18 +31,48 @@ using chrome_test_util::PrimarySignInButton; ...@@ -22,18 +31,48 @@ using chrome_test_util::PrimarySignInButton;
namespace { namespace {
// Constant for timeout while waiting for asynchronous sync operations.
const NSTimeInterval kSyncOperationTimeout = 10.0;
// Returns a matcher for a button that matches the userEmail in the given // Returns a matcher for a button that matches the userEmail in the given
// |fakeIdentity|. // |fakeIdentity|.
id<GREYMatcher> ButtonWithFakeIdentity(FakeChromeIdentity* fakeIdentity) { id<GREYMatcher> ButtonWithFakeIdentity(FakeChromeIdentity* fakeIdentity) {
return ButtonWithAccessibilityLabel(fakeIdentity.userEmail); return ButtonWithAccessibilityLabel(fakeIdentity.userEmail);
} }
id<GREYMatcher> NoBookmarksLabel() {
return grey_text(l10n_util::GetNSString(IDS_IOS_BOOKMARK_NO_BOOKMARKS_LABEL));
}
} }
// Integration tests using the Account Settings screen. // Integration tests using the Account Settings screen.
@interface AccountCollectionsTestCase : ChromeTestCase @interface AccountCollectionsTestCase : ChromeTestCase
@end @end
@implementation AccountCollectionsTestCase @implementation AccountCollectionsTestCase {
base::test::ScopedFeatureList _featureList;
}
- (void)tearDown {
[ChromeEarlGrey waitForBookmarksToFinishLoading];
[ChromeEarlGrey clearBookmarks];
[BookmarkEarlGrey clearBookmarksPositionCache];
[ChromeEarlGrey clearSyncServerData];
[super tearDown];
}
- (void)setUp {
_featureList.InitAndEnableFeature(kClearSyncedData);
[super setUp];
[ChromeEarlGrey waitForBookmarksToFinishLoading];
[ChromeEarlGrey clearBookmarks];
GREYAssertEqual(
[ChromeEarlGrey numberOfSyncEntitiesWithType:syncer::BOOKMARKS], 0,
@"No bookmarks should exist before tests start.");
}
// Tests that the Sync and Account Settings screen are correctly popped if the // Tests that the Sync and Account Settings screen are correctly popped if the
// signed in account is removed. // signed in account is removed.
...@@ -138,6 +177,58 @@ id<GREYMatcher> ButtonWithFakeIdentity(FakeChromeIdentity* fakeIdentity) { ...@@ -138,6 +177,58 @@ id<GREYMatcher> ButtonWithFakeIdentity(FakeChromeIdentity* fakeIdentity) {
performAction:grey_tap()]; performAction:grey_tap()];
} }
// Tests that selecting sign-out from a non-managed account keeps the user's
// synced data.
- (void)testSignOutFromNonManagedAccountKeepsData {
FakeChromeIdentity* fakeIdentity = [SigninEarlGreyUtils fakeIdentity1];
// Sign In |fakeIdentity|.
[SigninEarlGreyUI signinWithFakeIdentity:fakeIdentity];
// Add a bookmark after sync is initialized.
[ChromeEarlGrey waitForSyncInitialized:YES syncTimeout:kSyncOperationTimeout];
[ChromeEarlGrey waitForBookmarksToFinishLoading];
[SigninEarlGreyUtilsAppInterface addBookmark:@"http://youtube.com"
withTitle:@"cats"];
[SigninEarlGreyUI
signOutWithSignOutConfirmation:SignOutConfirmationNonManagedUser];
// Open the Bookmarks screen on the Tools menu.
[BookmarkEarlGreyUI openBookmarks];
[BookmarkEarlGreyUI openMobileBookmarks];
// Assert that the 'cats' bookmark is displayed.
[[EarlGrey selectElementWithMatcher:grey_text(@"cats")]
assertWithMatcher:grey_notNil()];
}
// Tests that selecting sign-out and clear data from a non-managed user account
// clears the user's synced data.
- (void)testSignOutAndClearDataFromNonManagedAccountClearsData {
FakeChromeIdentity* fakeIdentity = [SigninEarlGreyUtils fakeIdentity1];
// Sign In |fakeIdentity|.
[SigninEarlGreyUI signinWithFakeIdentity:fakeIdentity];
// Add a bookmark after sync is initialized.
[ChromeEarlGrey waitForSyncInitialized:YES syncTimeout:kSyncOperationTimeout];
[ChromeEarlGrey waitForBookmarksToFinishLoading];
[SigninEarlGreyUtilsAppInterface addBookmark:@"http://youtube.com"
withTitle:@"cats"];
[SigninEarlGreyUI signOutWithSignOutConfirmation:
SignOutConfirmationNonManagedUserWithClearedData];
// Open the Bookmarks screen on the Tools menu.
[BookmarkEarlGreyUI openBookmarks];
[BookmarkEarlGreyUI openMobileBookmarks];
// Assert that there are no bookmarks.
[[EarlGrey selectElementWithMatcher:NoBookmarksLabel()]
assertWithMatcher:grey_notNil()];
}
// Tests that the user isn't signed out and the UI is correct when the // Tests that the user isn't signed out and the UI is correct when the
// disconnect is cancelled in the Account Settings screen. // disconnect is cancelled in the Account Settings screen.
- (void)testSignInDisconnectCancelled { - (void)testSignInDisconnectCancelled {
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#import "ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller.h" #import "ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller.h"
#import "base/mac/foundation_util.h" #import "base/mac/foundation_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h" #include "base/metrics/user_metrics.h"
#include "base/strings/sys_string_conversions.h" #include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
...@@ -347,11 +348,11 @@ typedef NS_ENUM(NSInteger, ItemType) { ...@@ -347,11 +348,11 @@ typedef NS_ENUM(NSInteger, ItemType) {
return; return;
} }
NSString* title = l10n_util::GetNSString(IDS_IOS_DISCONNECT_DIALOG_TITLE); NSString* title = nil;
NSString* message = NSString* message = nil;
l10n_util::GetNSString(IDS_IOS_DISCONNECT_DIALOG_INFO_MOBILE); NSString* continueButtonTitle = nil;
NSString* continueButtonTitle = NSString* clearDataButtonTitle = nil;
l10n_util::GetNSString(IDS_IOS_DISCONNECT_DIALOG_CONTINUE_BUTTON_MOBILE);
if ([self authService] -> IsAuthenticatedIdentityManaged()) { if ([self authService] -> IsAuthenticatedIdentityManaged()) {
signin::IdentityManager* identityManager = signin::IdentityManager* identityManager =
IdentityManagerFactory::GetForBrowserState(_browser->GetBrowserState()); IdentityManagerFactory::GetForBrowserState(_browser->GetBrowserState());
...@@ -361,6 +362,7 @@ typedef NS_ENUM(NSInteger, ItemType) { ...@@ -361,6 +362,7 @@ typedef NS_ENUM(NSInteger, ItemType) {
std::string hosted_domain = accountInfo.has_value() std::string hosted_domain = accountInfo.has_value()
? accountInfo.value().hosted_domain ? accountInfo.value().hosted_domain
: std::string(); : std::string();
title = title =
l10n_util::GetNSString(IDS_IOS_MANAGED_DISCONNECT_DIALOG_TITLE_UNITY); l10n_util::GetNSString(IDS_IOS_MANAGED_DISCONNECT_DIALOG_TITLE_UNITY);
message = message =
...@@ -374,7 +376,10 @@ typedef NS_ENUM(NSInteger, ItemType) { ...@@ -374,7 +376,10 @@ typedef NS_ENUM(NSInteger, ItemType) {
l10n_util::GetNSString(IDS_IOS_DISCONNECT_DIALOG_INFO_MOBILE_UNITY); l10n_util::GetNSString(IDS_IOS_DISCONNECT_DIALOG_INFO_MOBILE_UNITY);
continueButtonTitle = l10n_util::GetNSString( continueButtonTitle = l10n_util::GetNSString(
IDS_IOS_DISCONNECT_DIALOG_CONTINUE_BUTTON_MOBILE); IDS_IOS_DISCONNECT_DIALOG_CONTINUE_BUTTON_MOBILE);
clearDataButtonTitle = l10n_util::GetNSString(
IDS_IOS_DISCONNECT_DIALOG_CONTINUE_AND_CLEAR_MOBILE);
} }
_alertCoordinator = _alertCoordinator =
[[AlertCoordinator alloc] initWithBaseViewController:self [[AlertCoordinator alloc] initWithBaseViewController:self
title:title title:title
...@@ -384,28 +389,41 @@ typedef NS_ENUM(NSInteger, ItemType) { ...@@ -384,28 +389,41 @@ typedef NS_ENUM(NSInteger, ItemType) {
action:nil action:nil
style:UIAlertActionStyleCancel]; style:UIAlertActionStyleCancel];
__weak AccountsTableViewController* weakSelf = self; __weak AccountsTableViewController* weakSelf = self;
[_alertCoordinator addItemWithTitle:continueButtonTitle [_alertCoordinator
action:^{ addItemWithTitle:continueButtonTitle
[weakSelf handleDisconnect]; action:^{
} [weakSelf handleDisconnectWithForceClearSyncData:NO];
style:UIAlertActionStyleDefault]; }
style:UIAlertActionStyleDefault];
if (base::FeatureList::IsEnabled(kClearSyncedData)) {
[_alertCoordinator
addItemWithTitle:clearDataButtonTitle
action:^{
[weakSelf handleDisconnectWithForceClearSyncData:YES];
}
style:UIAlertActionStyleDestructive];
}
[_alertCoordinator start]; [_alertCoordinator start];
} }
- (void)handleDisconnect { - (void)handleDisconnectWithForceClearSyncData:(BOOL)forceClearSyncData {
AuthenticationService* authService = [self authService]; AuthenticationService* authService = [self authService];
if (authService->IsAuthenticated()) { if (authService->IsAuthenticated()) {
_authenticationOperationInProgress = YES; _authenticationOperationInProgress = YES;
[self preventUserInteraction]; [self preventUserInteraction];
authService->SignOut( authService->SignOut(
signin_metrics::USER_CLICKED_SIGNOUT_SETTINGS, signin_metrics::USER_CLICKED_SIGNOUT_SETTINGS, forceClearSyncData, ^{
/*force_clear_browsing_data=*/false, ^{
[self allowUserInteraction]; [self allowUserInteraction];
_authenticationOperationInProgress = NO; _authenticationOperationInProgress = NO;
[base::mac::ObjCCastStrict<SettingsNavigationController>( [base::mac::ObjCCastStrict<SettingsNavigationController>(
self.navigationController) self.navigationController)
popViewControllerOrCloseSettingsAnimated:YES]; popViewControllerOrCloseSettingsAnimated:YES];
}); });
if (base::FeatureList::IsEnabled(kClearSyncedData)) {
UMA_HISTOGRAM_BOOLEAN("Signin.UserRequestedWipeDataOnSignout",
forceClearSyncData);
}
} }
} }
......
...@@ -96,7 +96,8 @@ void ChooseImportOrKeepDataSepareteDialog(id<GREYMatcher> choiceButtonMatcher) { ...@@ -96,7 +96,8 @@ void ChooseImportOrKeepDataSepareteDialog(id<GREYMatcher> choiceButtonMatcher) {
// Sign in to |fakeIdentity1|. // Sign in to |fakeIdentity1|.
[SigninEarlGreyUI signinWithFakeIdentity:fakeIdentity1]; [SigninEarlGreyUI signinWithFakeIdentity:fakeIdentity1];
[SigninEarlGreyUI signOutWithManagedAccount:NO]; [SigninEarlGreyUI
signOutWithSignOutConfirmation:SignOutConfirmationNonManagedUser];
// Sign in with |fakeIdentity2|. // Sign in with |fakeIdentity2|.
[ChromeEarlGreyUI openSettingsMenu]; [ChromeEarlGreyUI openSettingsMenu];
...@@ -178,7 +179,8 @@ void ChooseImportOrKeepDataSepareteDialog(id<GREYMatcher> choiceButtonMatcher) { ...@@ -178,7 +179,8 @@ void ChooseImportOrKeepDataSepareteDialog(id<GREYMatcher> choiceButtonMatcher) {
[SigninEarlGreyUI signinWithFakeIdentity:fakeIdentity]; [SigninEarlGreyUI signinWithFakeIdentity:fakeIdentity];
// Sign out. // Sign out.
[SigninEarlGreyUI signOutWithManagedAccount:NO]; [SigninEarlGreyUI
signOutWithSignOutConfirmation:SignOutConfirmationNonManagedUser];
} }
// Tests that signing out of a managed account from the Settings works // Tests that signing out of a managed account from the Settings works
...@@ -190,7 +192,8 @@ void ChooseImportOrKeepDataSepareteDialog(id<GREYMatcher> choiceButtonMatcher) { ...@@ -190,7 +192,8 @@ void ChooseImportOrKeepDataSepareteDialog(id<GREYMatcher> choiceButtonMatcher) {
[SigninEarlGreyUI signinWithFakeIdentity:fakeIdentity isManagedAccount:YES]; [SigninEarlGreyUI signinWithFakeIdentity:fakeIdentity isManagedAccount:YES];
// Sign out. // Sign out.
[SigninEarlGreyUI signOutWithManagedAccount:YES]; [SigninEarlGreyUI
signOutWithSignOutConfirmation:SignOutConfirmationManagedUser];
// Check that there is no signed in user. // Check that there is no signed in user.
[SigninEarlGreyUtils checkSignedOut]; [SigninEarlGreyUtils checkSignedOut];
...@@ -270,8 +273,8 @@ void ChooseImportOrKeepDataSepareteDialog(id<GREYMatcher> choiceButtonMatcher) { ...@@ -270,8 +273,8 @@ void ChooseImportOrKeepDataSepareteDialog(id<GREYMatcher> choiceButtonMatcher) {
[SigninEarlGreyUI signinWithFakeIdentity:fakeIdentity2]; [SigninEarlGreyUI signinWithFakeIdentity:fakeIdentity2];
// Sign out. // Sign out.
[SigninEarlGreyUI signOutWithManagedAccount:NO]; [SigninEarlGreyUI
signOutWithSignOutConfirmation:SignOutConfirmationNonManagedUser];
// Sign in with |fakeIdentity1|. // Sign in with |fakeIdentity1|.
[ChromeEarlGreyUI openSettingsMenu]; [ChromeEarlGreyUI openSettingsMenu];
[[EarlGrey selectElementWithMatcher:SecondarySignInButton()] [[EarlGrey selectElementWithMatcher:SecondarySignInButton()]
......
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