Skip to content

Commit

Permalink
[ios][userpolicy] User policy prompt half sheet
Browse files Browse the repository at this point in the history
The prompt is used to notify the user that from now on their
administrator may push policies to their browser when they use a
managed account.

The user is offered 2 options:
 - Continue
 - Sign Out and Clear Data

The User Policy feature isn't yet released. There is still UX work to
be done on the prompt, but this initial iteration is a good start.
The layout will probably remain as is and the text is subject to
change.

User Policy isn't a policy in itself where it adds a new provider to
get policies using a managed account.

Mock: http://shortn/_YimzhZuC57
Implementation: http://shortn/_QYMgdC3T59
Demo Video: http://shortn/_Csim1j4PoY

Bug: 1410811
Change-Id: Idcc1df3cafd0164309c315d70503236a0b3342de
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4198119
Commit-Queue: Vincent Boisselle <vincb@google.com>
Reviewed-by: Mark Cogan <marq@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1099282}
  • Loading branch information
vincb2 authored and Chromium LUCI CQ committed Jan 31, 2023
1 parent ec046cb commit 8bdb2f9
Show file tree
Hide file tree
Showing 20 changed files with 553 additions and 99 deletions.
1 change: 1 addition & 0 deletions ios/chrome/browser/policy/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ source_set("eg2_tests") {
"//ios/chrome/browser/ui/settings/privacy:privacy_constants",
"//ios/chrome/browser/ui/toolbar/public:constants",
"//ios/chrome/browser/url:constants",
"//ios/chrome/common/ui/confirmation_alert",
"//ios/chrome/common/ui/table_view:cells_constants",
"//ios/chrome/test/earl_grey:eg_test_support+eg2",
"//ios/chrome/test/earl_grey:switches",
Expand Down
11 changes: 9 additions & 2 deletions ios/chrome/browser/policy/user_policy_egtest.mm
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#import "ios/chrome/browser/policy/policy_app_interface.h"
#import "ios/chrome/browser/signin/fake_system_identity.h"
#import "ios/chrome/browser/ui/authentication/signin_earl_grey_ui_test_util.h"
#import "ios/chrome/common/ui/confirmation_alert/confirmation_alert_view_controller.h"
#import "ios/chrome/grit/ios_chromium_strings.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
Expand Down Expand Up @@ -137,16 +138,22 @@ void WaitOnUserPolicy(base::TimeDelta timeout) {
}

void VerifyTheNotificationUI() {
// Swipe up to make sure that all the text content in the prompt is visible.
[[EarlGrey
selectElementWithMatcher:
grey_accessibilityID(kConfirmationAlertTitleAccessibilityIdentifier)]
performAction:grey_swipeFastInDirection(kGREYDirectionUp)];

NSString* title =
l10n_util::GetNSString(IDS_IOS_USER_POLICY_NOTIFICATION_TITLE);
NSString* subtitle = l10n_util::GetNSStringF(
IDS_IOS_USER_POLICY_NOTIFICATION_SUBTITLE,
base::UTF8ToUTF16(std::string(policy::SignatureProvider::kTestDomain1)));

// Verify the notification UI.
[[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(title)]
[[EarlGrey selectElementWithMatcher:grey_text(title)]
assertWithMatcher:grey_sufficientlyVisible()];
[[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(subtitle)]
[[EarlGrey selectElementWithMatcher:grey_text(subtitle)]
assertWithMatcher:grey_sufficientlyVisible()];
}

Expand Down
4 changes: 1 addition & 3 deletions ios/chrome/browser/ui/policy/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,12 @@ source_set("user_policy_scene_agent") {
"//ios/chrome/browser/policy:policy_util",
"//ios/chrome/browser/prefs:pref_names",
"//ios/chrome/browser/signin",
"//ios/chrome/browser/signin:system_identity",
"//ios/chrome/browser/ui/alert_coordinator",
"//ios/chrome/browser/ui/authentication",
"//ios/chrome/browser/ui/authentication/signin",
"//ios/chrome/browser/ui/commands",
"//ios/chrome/browser/ui/main:browser_interface_provider",
"//ios/chrome/browser/ui/main:observing_scene_agent",
"//ios/chrome/browser/ui/main:scene_ui_provider",
"//ios/chrome/browser/ui/policy/user_policy",
"//ios/chrome/browser/ui/scoped_ui_blocker",
"//ui/base",
]
Expand Down
36 changes: 36 additions & 0 deletions ios/chrome/browser/ui/policy/user_policy/BUILD.gn
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Copyright 2023 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

source_set("user_policy") {
configs += [ "//build/config/compiler:enable_arc" ]
sources = [
"user_policy_prompt_coordinator.h",
"user_policy_prompt_coordinator.mm",
"user_policy_prompt_coordinator_delegate.h",
"user_policy_prompt_mediator.h",
"user_policy_prompt_mediator.mm",
"user_policy_prompt_presenter.h",
"user_policy_prompt_view_controller.h",
"user_policy_prompt_view_controller.mm",
]
deps = [
"//base",
"//components/signin/public/base",
"//ios/chrome/app/strings",
"//ios/chrome/browser/main:public",
"//ios/chrome/browser/policy",
"//ios/chrome/browser/policy/resources:enterprise_grey_icon_large",
"//ios/chrome/browser/signin",
"//ios/chrome/browser/ui/authentication",
"//ios/chrome/browser/ui/authentication/signin",
"//ios/chrome/browser/ui/authentication/signin:signin_headers",
"//ios/chrome/browser/ui/commands",
"//ios/chrome/browser/ui/coordinators:chrome_coordinators",
"//ios/chrome/browser/ui/elements:activity_overlay",
"//ios/chrome/common/ui/colors",
"//ios/chrome/common/ui/confirmation_alert",
"//ui/base",
]
frameworks = [ "UIKit.framework" ]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef IOS_CHROME_BROWSER_UI_POLICY_USER_POLICY_USER_POLICY_PROMPT_COORDINATOR_H_
#define IOS_CHROME_BROWSER_UI_POLICY_USER_POLICY_USER_POLICY_PROMPT_COORDINATOR_H_

#import "ios/chrome/browser/ui/coordinators/chrome_coordinator.h"

@protocol UserPolicyPromptCoordinatorDelegate;

@interface UserPolicyPromptCoordinator : ChromeCoordinator

// Delegate for the coordinator. Can be a parent coordinator that owns this
// coordinator or a scene agent.
@property(nonatomic, weak) id<UserPolicyPromptCoordinatorDelegate> delegate;

@end

#endif // IOS_CHROME_BROWSER_UI_POLICY_USER_POLICY_USER_POLICY_PROMPT_COORDINATOR_H_
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "ios/chrome/browser/ui/policy/user_policy/user_policy_prompt_coordinator.h"

#import "base/strings/sys_string_conversions.h"
#import "ios/chrome/browser/main/browser.h"
#import "ios/chrome/browser/signin/authentication_service.h"
#import "ios/chrome/browser/signin/authentication_service_factory.h"
#import "ios/chrome/browser/ui/authentication/authentication_ui_util.h"
#import "ios/chrome/browser/ui/elements/activity_overlay_coordinator.h"
#import "ios/chrome/browser/ui/policy/user_policy/user_policy_prompt_coordinator_delegate.h"
#import "ios/chrome/browser/ui/policy/user_policy/user_policy_prompt_mediator.h"
#import "ios/chrome/browser/ui/policy/user_policy/user_policy_prompt_presenter.h"
#import "ios/chrome/browser/ui/policy/user_policy/user_policy_prompt_view_controller.h"
#import "ios/chrome/common/ui/confirmation_alert/confirmation_alert_action_handler.h"

#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif

class ChromeBrowserState;

namespace {
constexpr CGFloat kHalfSheetCornerRadius = 20;
} // namespace

@interface UserPolicyPromptCoordinator () <UserPolicyPromptPresenter>

// View controller for the User Policy prompt.
@property(nonatomic, strong)
UserPolicyPromptViewController* presentedViewController;

// Mediator for the User Policy prompt.
@property(nonatomic, strong) UserPolicyPromptMediator* mediator;

// Child coordinator than handles the activity overlay shown on top of the view
// when there is an ongoing activity.
@property(nonatomic, strong)
ActivityOverlayCoordinator* activityOverlayCoordinator;

@end

@implementation UserPolicyPromptCoordinator

#pragma mark - Internal

// Returns the domain of the administrator hosting the primary account.
// Returns an empty string if the account isn't managed OR isn't syncing.
- (NSString*)managedDomain {
return base::SysUTF16ToNSString(HostedDomainForPrimaryAccount(self.browser));
}

// Returns the AuthenticationService of the browser.
- (AuthenticationService*)authService {
ChromeBrowserState* browserState = self.browser->GetBrowserState();
DCHECK(browserState);
AuthenticationService* authService =
AuthenticationServiceFactory::GetForBrowserState(browserState);
return authService;
}

#pragma mark - ChromeCoordinator

- (void)start {
self.mediator =
[[UserPolicyPromptMediator alloc] initWithPresenter:self
authService:[self authService]];

self.presentedViewController = [[UserPolicyPromptViewController alloc]
initWithManagedDomain:[self managedDomain]];
self.presentedViewController.actionHandler = self.mediator;
self.presentedViewController.presentationController.delegate = self.mediator;

if (@available(iOS 15, *)) {
self.presentedViewController.modalPresentationStyle =
UIModalPresentationPageSheet;
UISheetPresentationController* presentationController =
self.presentedViewController.sheetPresentationController;
presentationController.prefersEdgeAttachedInCompactHeight = YES;
presentationController.detents = @[
UISheetPresentationControllerDetent.mediumDetent,
UISheetPresentationControllerDetent.largeDetent
];
presentationController.preferredCornerRadius = kHalfSheetCornerRadius;
} else {
self.presentedViewController.modalPresentationStyle =
UIModalPresentationFormSheet;
}

self.presentedViewController.modalInPresentation = YES;

[self.baseViewController presentViewController:self.presentedViewController
animated:YES
completion:nil];
}

- (void)stop {
[self hideActivityOverlay];
if (self.presentedViewController) {
[self.baseViewController.presentedViewController
dismissViewControllerAnimated:YES
completion:nil];
self.presentedViewController = nil;
}
}

#pragma mark - UserPolicyPromptPresenter

- (void)stopPresenting {
[self.delegate didCompletePresentation:self];
}

- (void)showActivityOverlay {
self.activityOverlayCoordinator = [[ActivityOverlayCoordinator alloc]
initWithBaseViewController:self.presentedViewController
browser:self.browser];
[self.activityOverlayCoordinator start];
}

- (void)hideActivityOverlay {
if (self.activityOverlayCoordinator) {
[self.activityOverlayCoordinator stop];
self.activityOverlayCoordinator = nil;
}
}

@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef IOS_CHROME_BROWSER_UI_POLICY_USER_POLICY_USER_POLICY_PROMPT_COORDINATOR_DELEGATE_H_
#define IOS_CHROME_BROWSER_UI_POLICY_USER_POLICY_USER_POLICY_PROMPT_COORDINATOR_DELEGATE_H_

@protocol UserPolicyPromptCoordinatorDelegate <NSObject>

// Called when the presentation did complete. Usually called when the action the
// user did on the prompt is completed (e.g. after sign out).
- (void)didCompletePresentation:(UserPolicyPromptCoordinator*)coordinator;

@end

#endif // IOS_CHROME_BROWSER_UI_POLICY_USER_POLICY_USER_POLICY_PROMPT_COORDINATOR_DELEGATE_H_
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef IOS_CHROME_BROWSER_UI_POLICY_USER_POLICY_USER_POLICY_PROMPT_MEDIATOR_H_
#define IOS_CHROME_BROWSER_UI_POLICY_USER_POLICY_USER_POLICY_PROMPT_MEDIATOR_H_

#import <UIKit/UIKit.h>

#import "ios/chrome/common/ui/confirmation_alert/confirmation_alert_action_handler.h"

@protocol UserPolicyPromptPresenter;
class AuthenticationService;

@interface UserPolicyPromptMediator
: NSObject <ConfirmationAlertActionHandler,
UIAdaptivePresentationControllerDelegate>

- (instancetype)initWithPresenter:(id<UserPolicyPromptPresenter>)presenter
authService:(AuthenticationService*)authService
NS_DESIGNATED_INITIALIZER;

- (instancetype)init NS_UNAVAILABLE;

@end

#endif // IOS_CHROME_BROWSER_UI_POLICY_USER_POLICY_USER_POLICY_PROMPT_MEDIATOR_H_
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "ios/chrome/browser/ui/policy/user_policy/user_policy_prompt_mediator.h"

#import "components/signin/public/base/signin_metrics.h"
#import "ios/chrome/browser/signin/authentication_service.h"
#import "ios/chrome/browser/ui/policy/user_policy/user_policy_prompt_presenter.h"

#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif

@interface UserPolicyPromptMediator ()

// Presenter of the User Policy prompt view.
@property(nonatomic, weak, readonly) id<UserPolicyPromptPresenter> presenter;

// AuthenticationService for handling authentication (e.g. sign out).
@property(nonatomic, assign, readonly) AuthenticationService* authService;

@end

@implementation UserPolicyPromptMediator

- (instancetype)initWithPresenter:(id<UserPolicyPromptPresenter>)presenter
authService:(AuthenticationService*)authService {
if (self = [super init]) {
_presenter = presenter;
_authService = authService;
}
return self;
}

- (void)stopPresentation {
[self.presenter stopPresenting];
}

#pragma mark - ConfirmationAlertActionHandler

- (void)confirmationAlertPrimaryAction {
[self stopPresentation];
}

- (void)confirmationAlertSecondaryAction {
DCHECK(self.authService);
__weak __typeof(self) weakSelf = self;
[self.presenter showActivityOverlay];
self.authService->SignOut(
signin_metrics::ProfileSignout::
kUserClickedSignoutFromUserPolicyNotificationDialog,
false, ^{
[weakSelf stopPresentation];
});
}

#pragma mark - UIAdaptivePresentationControllerDelegate

- (void)presentationControllerDidDismiss:
(UIPresentationController*)presentationController {
[self stopPresentation];
}

@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef IOS_CHROME_BROWSER_UI_POLICY_USER_POLICY_USER_POLICY_PROMPT_PRESENTER_H_
#define IOS_CHROME_BROWSER_UI_POLICY_USER_POLICY_USER_POLICY_PROMPT_PRESENTER_H_

@protocol UserPolicyPromptPresenter <NSObject>

// Stop presenting the view.
- (void)stopPresenting;

// Show the actvity overlay on the view.
- (void)showActivityOverlay;

// Hide the activity overlay on the view.
- (void)hideActivityOverlay;

@end

#endif // IOS_CHROME_BROWSER_UI_POLICY_USER_POLICY_USER_POLICY_PROMPT_PRESENTER_H_
Loading

0 comments on commit 8bdb2f9

Please sign in to comment.