[go: nahoru, domu]

Reland cros: Views login backend for removing users.

Relands https://chromium-review.googlesource.com/c/chromium/src/+/935675. It was
reverted in https://chromium-review.googlesource.com/c/chromium/src/+/969228

TBR: xiyuan@chromium.org
TBR: dcheng@chromium.org
Bug: 809637
Change-Id: I9e44d96cbe26bc090e177460e18542b8b4d387b2
Reviewed-on: https://chromium-review.googlesource.com/971161
Reviewed-by: Jacob Dufault <jdufault@chromium.org>
Reviewed-by: Xiyuan Xia <xiyuan@chromium.org>
Commit-Queue: Jacob Dufault <jdufault@chromium.org>
Cr-Commit-Position: refs/heads/master@{#544423}
diff --git a/ash/login/login_screen_controller.cc b/ash/login/login_screen_controller.cc
index e024c81..1f4f88c 100644
--- a/ash/login/login_screen_controller.cc
+++ b/ash/login/login_screen_controller.cc
@@ -312,6 +312,18 @@
   login_screen_client_->ShowGaiaSignin();
 }
 
+void LoginScreenController::OnRemoveUserWarningShown() {
+  if (!login_screen_client_)
+    return;
+  login_screen_client_->OnRemoveUserWarningShown();
+}
+
+void LoginScreenController::RemoveUser(const AccountId& account_id) {
+  if (!login_screen_client_)
+    return;
+  login_screen_client_->RemoveUser(account_id);
+}
+
 void LoginScreenController::AddLockScreenAppsFocusObserver(
     LockScreenAppsFocusObserver* observer) {
   lock_screen_apps_focus_observers_.AddObserver(observer);
diff --git a/ash/login/login_screen_controller.h b/ash/login/login_screen_controller.h
index f9460a75..c1a24b2 100644
--- a/ash/login/login_screen_controller.h
+++ b/ash/login/login_screen_controller.h
@@ -92,6 +92,8 @@
   void OnMaxIncorrectPasswordAttempted(const AccountId& account_id);
   void FocusLockScreenApps(bool reverse);
   void ShowGaiaSignin();
+  void OnRemoveUserWarningShown();
+  void RemoveUser(const AccountId& account_id);
 
   // Methods to manage lock screen apps focus observers.
   // The observers will be notified when lock screen apps focus changes are
diff --git a/ash/login/mock_login_screen_client.cc b/ash/login/mock_login_screen_client.cc
index d70995c8..2ad25b4 100644
--- a/ash/login/mock_login_screen_client.cc
+++ b/ash/login/mock_login_screen_client.cc
@@ -36,9 +36,9 @@
 }
 
 std::unique_ptr<MockLoginScreenClient> BindMockLoginScreenClient() {
-  LoginScreenController* controller = Shell::Get()->login_screen_controller();
   auto client = std::make_unique<MockLoginScreenClient>();
-  controller->SetClient(client->CreateInterfacePtrAndBind());
+  Shell::Get()->login_screen_controller()->SetClient(
+      client->CreateInterfacePtrAndBind());
   return client;
 }
 
diff --git a/ash/login/mock_login_screen_client.h b/ash/login/mock_login_screen_client.h
index c5026b16..5a9ce6d 100644
--- a/ash/login/mock_login_screen_client.h
+++ b/ash/login/mock_login_screen_client.h
@@ -59,6 +59,8 @@
                void(const AccountId& account_id));
   MOCK_METHOD1(FocusLockScreenApps, void(bool reverse));
   MOCK_METHOD0(ShowGaiaSignin, void());
+  MOCK_METHOD0(OnRemoveUserWarningShown, void());
+  MOCK_METHOD1(RemoveUser, void(const AccountId& account_id));
 
  private:
   bool authenticate_user_callback_result_ = true;
diff --git a/ash/login/ui/lock_contents_view.cc b/ash/login/ui/lock_contents_view.cc
index 90b346d..b3a9ad4 100644
--- a/ash/login/ui/lock_contents_view.cc
+++ b/ash/login/ui/lock_contents_view.cc
@@ -814,7 +814,7 @@
 
 void LockContentsView::SwapToAuthUser(int user_index) {
   DCHECK(users_list_);
-  auto* view = users_list_->GetUserViewAtIndex(user_index);
+  LoginUserView* view = users_list_->user_view_at(user_index);
   DCHECK(view);
   mojom::LoginUserInfoPtr previous_auth_user =
       primary_auth_->current_user()->Clone();
@@ -826,6 +826,34 @@
   OnAuthUserChanged();
 }
 
+void LockContentsView::OnRemoveUserWarningShown(bool is_primary) {
+  Shell::Get()->login_screen_controller()->OnRemoveUserWarningShown();
+}
+
+void LockContentsView::RemoveUser(bool is_primary) {
+  LoginAuthUserView* to_remove =
+      is_primary ? primary_auth_ : opt_secondary_auth_;
+  DCHECK(to_remove->current_user()->can_remove);
+  AccountId user = to_remove->current_user()->basic_user_info->account_id;
+
+  // Ask chrome to remove the user.
+  Shell::Get()->login_screen_controller()->RemoveUser(user);
+
+  // Display the new user list less |user|.
+  std::vector<mojom::LoginUserInfoPtr> new_users;
+  if (!is_primary)
+    new_users.push_back(primary_auth_->current_user()->Clone());
+  if (is_primary && opt_secondary_auth_)
+    new_users.push_back(opt_secondary_auth_->current_user()->Clone());
+  if (users_list_) {
+    for (int i = 0; i < users_list_->user_count(); ++i) {
+      new_users.push_back(
+          users_list_->user_view_at(i)->current_user()->Clone());
+    }
+  }
+  data_dispatcher_->NotifyUsers(new_users);
+}
+
 void LockContentsView::OnAuthUserChanged() {
   const AccountId new_auth_user =
       CurrentAuthUserView()->current_user()->basic_user_info->account_id;
@@ -961,15 +989,22 @@
 LoginAuthUserView* LockContentsView::AllocateLoginAuthUserView(
     const mojom::LoginUserInfoPtr& user,
     bool is_primary) {
-  return new LoginAuthUserView(
-      user,
-      base::Bind(&LockContentsView::OnAuthenticate, base::Unretained(this)),
-      base::Bind(&LockContentsView::SwapActiveAuthBetweenPrimaryAndSecondary,
-                 base::Unretained(this), is_primary),
-      base::Bind(&LockContentsView::OnEasyUnlockIconHovered,
-                 base::Unretained(this)),
-      base::Bind(&LockContentsView::OnEasyUnlockIconTapped,
-                 base::Unretained(this)));
+  LoginAuthUserView::Callbacks callbacks;
+  callbacks.on_auth = base::BindRepeating(&LockContentsView::OnAuthenticate,
+                                          base::Unretained(this)),
+  callbacks.on_tap = base::BindRepeating(
+      &LockContentsView::SwapActiveAuthBetweenPrimaryAndSecondary,
+      base::Unretained(this), is_primary),
+  callbacks.on_remove_warning_shown =
+      base::BindRepeating(&LockContentsView::OnRemoveUserWarningShown,
+                          base::Unretained(this), is_primary);
+  callbacks.on_remove = base::BindRepeating(&LockContentsView::RemoveUser,
+                                            base::Unretained(this), is_primary);
+  callbacks.on_easy_unlock_icon_hovered = base::BindRepeating(
+      &LockContentsView::OnEasyUnlockIconHovered, base::Unretained(this));
+  callbacks.on_easy_unlock_icon_tapped = base::BindRepeating(
+      &LockContentsView::OnEasyUnlockIconTapped, base::Unretained(this));
+  return new LoginAuthUserView(user, callbacks);
 }
 
 LoginAuthUserView* LockContentsView::TryToFindAuthUser(
diff --git a/ash/login/ui/lock_contents_view.h b/ash/login/ui/lock_contents_view.h
index 4c0ee10..a0d2eee 100644
--- a/ash/login/ui/lock_contents_view.h
+++ b/ash/login/ui/lock_contents_view.h
@@ -213,6 +213,11 @@
   // the actual user may change.
   void SwapToAuthUser(int user_index);
 
+  // Warning to remove a user is shown.
+  void OnRemoveUserWarningShown(bool is_primary);
+  // Remove one of the auth users.
+  void RemoveUser(bool is_primary);
+
   // Called after the auth user change has taken place.
   void OnAuthUserChanged();
 
diff --git a/ash/login/ui/lock_screen_sanity_unittest.cc b/ash/login/ui/lock_screen_sanity_unittest.cc
index bcb5723f..2ecbecb 100644
--- a/ash/login/ui/lock_screen_sanity_unittest.cc
+++ b/ash/login/ui/lock_screen_sanity_unittest.cc
@@ -8,6 +8,8 @@
 #include "ash/login/mock_login_screen_client.h"
 #include "ash/login/ui/fake_login_detachable_base_model.h"
 #include "ash/login/ui/lock_contents_view.h"
+#include "ash/login/ui/login_auth_user_view.h"
+#include "ash/login/ui/login_bubble.h"
 #include "ash/login/ui/login_test_base.h"
 #include "ash/login/ui/login_test_utils.h"
 #include "ash/public/cpp/config.h"
@@ -113,8 +115,9 @@
   std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents);
 
   // Textfield should have focus.
-  EXPECT_EQ(MakeLoginPasswordTestApi(contents).textfield(),
-            contents->GetFocusManager()->GetFocusedView());
+  EXPECT_EQ(
+      MakeLoginPasswordTestApi(contents, AuthTarget::kPrimary).textfield(),
+      contents->GetFocusManager()->GetFocusedView());
 }
 
 // Verifies submitting the password invokes mojo lock screen client.
@@ -153,7 +156,7 @@
   SetUserCount(1);
   std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents);
   LoginPasswordView::TestApi password_test_api =
-      MakeLoginPasswordTestApi(contents);
+      MakeLoginPasswordTestApi(contents, AuthTarget::kPrimary);
 
   MockLoginScreenClient::AuthenticateUserCallback callback;
   auto submit_password = [&]() {
@@ -370,4 +373,73 @@
   EXPECT_TRUE(VerifyFocused(shelf));
 }
 
+TEST_F(LockScreenSanityTest, RemoveUser) {
+  std::unique_ptr<MockLoginScreenClient> client = BindMockLoginScreenClient();
+  LoginScreenController* controller =
+      ash::Shell::Get()->login_screen_controller();
+
+  auto* contents = new LockContentsView(
+      mojom::TrayActionState::kAvailable, data_dispatcher(),
+      std::make_unique<FakeLoginDetachableBaseModel>(data_dispatcher()));
+
+  // Add two users, the first of which can be removed.
+  users().push_back(CreateUser("test1@test"));
+  users()[0]->can_remove = true;
+  users().push_back(CreateUser("test2@test"));
+  data_dispatcher()->NotifyUsers(users());
+
+  std::unique_ptr<views::Widget> widget = CreateWidgetWithContent(contents);
+
+  auto primary = [&]() {
+    return LoginUserView::TestApi(
+        MakeLoginAuthTestApi(contents, AuthTarget::kPrimary).user_view());
+  };
+  auto secondary = [&]() {
+    return LoginUserView::TestApi(
+        MakeLoginAuthTestApi(contents, AuthTarget::kSecondary).user_view());
+  };
+
+  // Fires a return and validates that mock expectations have been satisfied.
+  auto submit = [&]() {
+    GetEventGenerator().PressKey(ui::VKEY_RETURN, 0);
+    controller->FlushForTesting();
+    testing::Mock::VerifyAndClearExpectations(client.get());
+  };
+  auto focus_and_submit = [&](views::View* view) {
+    view->RequestFocus();
+    DCHECK(view->HasFocus());
+    submit();
+  };
+
+  // The secondary user is not removable (as configured above) so showing the
+  // dropdown does not result in an interactive/focusable view.
+  focus_and_submit(secondary().dropdown());
+  EXPECT_TRUE(secondary().menu());
+  EXPECT_FALSE(
+      HasFocusInAnyChildView(secondary().menu()->bubble_view_for_test()));
+  // TODO(jdufault): Run submit() and then EXPECT_FALSE(secondary().menu()); to
+  // verify that double-enter closes the bubble.
+
+  // The primary user is removable, so the menu is interactive. Submitting the
+  // first time shows the remove user warning, submitting the second time
+  // actually removes the user. Removing the user triggers a mojo API call as
+  // well as removes the user from the UI.
+  focus_and_submit(primary().dropdown());
+  EXPECT_TRUE(primary().menu());
+  EXPECT_TRUE(HasFocusInAnyChildView(primary().menu()->bubble_view_for_test()));
+  EXPECT_CALL(*client, OnRemoveUserWarningShown()).Times(1);
+  submit();
+  EXPECT_CALL(*client, RemoveUser(users()[0]->basic_user_info->account_id))
+      .Times(1);
+  submit();
+
+  // Secondary auth should be gone because it is now the primary auth.
+  EXPECT_FALSE(MakeLockContentsViewTestApi(contents).opt_secondary_auth());
+  EXPECT_TRUE(MakeLockContentsViewTestApi(contents)
+                  .primary_auth()
+                  ->current_user()
+                  ->basic_user_info->account_id ==
+              users()[1]->basic_user_info->account_id);
+}
+
 }  // namespace ash
diff --git a/ash/login/ui/login_auth_user_view.cc b/ash/login/ui/login_auth_user_view.cc
index 9e7dca8..ddf306b 100644
--- a/ash/login/ui/login_auth_user_view.cc
+++ b/ash/login/ui/login_auth_user_view.cc
@@ -101,23 +101,31 @@
   return view_->pin_view_;
 }
 
-LoginAuthUserView::LoginAuthUserView(
-    const mojom::LoginUserInfoPtr& user,
-    const OnAuthCallback& on_auth,
-    const LoginUserView::OnTap& on_tap,
-    const OnEasyUnlockIconHovered& on_easy_unlock_icon_hovered,
-    const OnEasyUnlockIconTapped& on_easy_unlock_icon_tapped)
+LoginAuthUserView::Callbacks::Callbacks() {}
+
+LoginAuthUserView::Callbacks::~Callbacks() {}
+
+LoginAuthUserView::LoginAuthUserView(const mojom::LoginUserInfoPtr& user,
+                                     const Callbacks& callbacks)
     : NonAccessibleView(kLoginAuthUserViewClassName),
-      on_auth_(on_auth),
-      on_tap_(on_tap),
+      on_auth_(callbacks.on_auth),
+      on_tap_(callbacks.on_tap),
       weak_factory_(this) {
+  DCHECK(callbacks.on_auth);
+  DCHECK(callbacks.on_tap);
+  DCHECK(callbacks.on_remove_warning_shown);
+  DCHECK(callbacks.on_remove);
+  DCHECK(callbacks.on_easy_unlock_icon_hovered);
+  DCHECK(callbacks.on_easy_unlock_icon_tapped);
   DCHECK_NE(user->basic_user_info->type,
             user_manager::USER_TYPE_PUBLIC_ACCOUNT);
 
   // Build child views.
   user_view_ = new LoginUserView(
       LoginDisplayStyle::kLarge, true /*show_dropdown*/, false /*show_domain*/,
-      base::Bind(&LoginAuthUserView::OnUserViewTap, base::Unretained(this)));
+      base::BindRepeating(&LoginAuthUserView::OnUserViewTap,
+                          base::Unretained(this)),
+      callbacks.on_remove_warning_shown, callbacks.on_remove);
 
   password_view_ = new LoginPasswordView();
   password_view_->SetPaintToLayer();  // Needed for opacity animation.
@@ -137,7 +145,8 @@
       base::Bind(&LoginAuthUserView::OnAuthSubmit, base::Unretained(this)),
       base::Bind(&LoginPinView::OnPasswordTextChanged,
                  base::Unretained(pin_view_)),
-      on_easy_unlock_icon_hovered, on_easy_unlock_icon_tapped);
+      callbacks.on_easy_unlock_icon_hovered,
+      callbacks.on_easy_unlock_icon_tapped);
 
   // Child views animate outside view bounds.
   SetPaintToLayer(ui::LayerType::LAYER_NOT_DRAWN);
diff --git a/ash/login/ui/login_auth_user_view.h b/ash/login/ui/login_auth_user_view.h
index 6441f86e0..806aba53 100644
--- a/ash/login/ui/login_auth_user_view.h
+++ b/ash/login/ui/login_auth_user_view.h
@@ -44,6 +44,30 @@
     LoginAuthUserView* const view_;
   };
 
+  using  auth_success)>;
+  using >
+  using >
+
+  struct Callbacks {
+    Callbacks();
+    ~Callbacks();
+
+    // Executed whenever an authentication result is available, such as when the
+    // user submits a password or taps the user icon when AUTH_TAP is enabled.
+    OnAuthCallback on_auth;
+    // Called when the user taps the user view and AUTH_TAP is not enabled.
+    LoginUserView::OnTap on_tap;
+    // Called when the remove user warning message has been shown.
+    LoginUserView::OnRemoveWarningShown on_remove_warning_shown;
+    // Called when the user should be removed. The callback should do the actual
+    // removal.
+    LoginUserView::OnRemove on_remove;
+    // Called when the easy unlock icon is hovered.
+    OnEasyUnlockIconHovered on_easy_unlock_icon_hovered;
+    // Called when the easy unlock icon is tapped.
+    OnEasyUnlockIconTapped on_easy_unlock_icon_tapped;
+  };
+
   // Flags which describe the set of currently visible auth methods.
   enum AuthMethods {
     AUTH_NONE = 0,           // No extra auth methods.
@@ -52,23 +76,8 @@
     AUTH_TAP = 1 << 2,       // Tap to unlock.
   };
 
-  using  auth_success)>;
-  using >
-  using >
-
-  // |on_auth| is executed whenever an authentication result is available, such
-  // as when the user submits a password or taps the user icon when AUTH_TAP is
-  // enabled.
-  //
-  // |on_tap| is called when the user taps the user view and AUTH_TAP is not
-  // enabled.
-  //
-  // None of the callbacks can be null.
   LoginAuthUserView(const mojom::LoginUserInfoPtr& user,
-                    const OnAuthCallback& on_auth,
-                    const LoginUserView::OnTap& on_tap,
-                    const OnEasyUnlockIconHovered& on_easy_unlock_icon_hovered,
-                    const OnEasyUnlockIconTapped& on_easy_unlock_icon_tapped);
+                    const Callbacks& callbacks);
   ~LoginAuthUserView() override;
 
   // Set the displayed set of auth methods. |auth_methods| contains or-ed
diff --git a/ash/login/ui/login_auth_user_view_unittest.cc b/ash/login/ui/login_auth_user_view_unittest.cc
index e85c624..fcfe0f4 100644
--- a/ash/login/ui/login_auth_user_view_unittest.cc
+++ b/ash/login/ui/login_auth_user_view_unittest.cc
@@ -24,8 +24,15 @@
     LoginTestBase::SetUp();
 
     user_ = CreateUser("user@domain.com");
-    view_ = new LoginAuthUserView(user_, base::DoNothing(), base::DoNothing(),
-                                  base::DoNothing(), base::DoNothing());
+
+    LoginAuthUserView::Callbacks auth_callbacks;
+    auth_callbacks.on_auth = base::DoNothing();
+    auth_callbacks.on_easy_unlock_icon_hovered = base::DoNothing();
+    auth_callbacks.on_easy_unlock_icon_tapped = base::DoNothing();
+    auth_callbacks.on_tap = base::DoNothing();
+    auth_callbacks.on_remove_warning_shown = base::DoNothing();
+    auth_callbacks.on_remove = base::DoNothing();
+    view_ = new LoginAuthUserView(user_, auth_callbacks);
 
     // We proxy |view_| inside of |container_| so we can control layout.
     container_ = new views::View();
diff --git a/ash/login/ui/login_bubble.cc b/ash/login/ui/login_bubble.cc
index 50867ed..8e1d5ce 100644
--- a/ash/login/ui/login_bubble.cc
+++ b/ash/login/ui/login_bubble.cc
@@ -155,10 +155,12 @@
                     bool is_owner,
                     views::View* anchor_view,
                     bool show_remove_user,
-                    base::OnceClosure do_remove_user)
+                    base::OnceClosure on_remove_user_warning_shown,
+                    base::OnceClosure on_remove_user_requested)
       : LoginBaseBubbleView(anchor_view),
         bubble_(bubble),
-        do_remove_user_(std::move(do_remove_user)) {
+        on_remove_user_warning_shown_(std::move(on_remove_user_warning_shown)),
+        on_remove_user_requested_(std::move(on_remove_user_requested)) {
     // This view has content the user can interact with if the remove user
     // button is displayed.
     set_can_activate(show_remove_user);
@@ -308,17 +310,23 @@
       SizeToContents();
       GetWidget()->SetSize(size());
       Layout();
+      if (on_remove_user_warning_shown_)
+        std::move(on_remove_user_warning_shown_).Run();
       return;
     }
 
-    if (do_remove_user_)
-      std::move(do_remove_user_).Run();
+    // Close the bubble before calling |on_remove_user_requested_|. |bubble_| is
+    // an unowned reference; |on_remove_user_requested_| may delete it.
     bubble_->Close();
+
+    if (on_remove_user_requested_)
+      std::move(on_remove_user_requested_).Run();
   }
 
  private:
   LoginBubble* bubble_ = nullptr;
-  base::OnceClosure do_remove_user_;
+  base::OnceClosure on_remove_user_warning_shown_;
+  base::OnceClosure on_remove_user_requested_;
   views::View* remove_user_confirm_data_ = nullptr;
   views::Label* remove_user_label_ = nullptr;
   ButtonWithContent* remove_user_button_ = nullptr;
@@ -382,19 +390,19 @@
                                views::View* anchor_view,
                                LoginButton* bubble_opener,
                                bool show_remove_user,
-                               base::OnceClosure do_remove_user) {
+                               base::OnceClosure on_remove_user_warning_shown,
+                               base::OnceClosure on_remove_user_requested) {
   if (bubble_view_)
     CloseImmediately();
 
   flags_ = kFlagsNone;
   bubble_opener_ = bubble_opener;
-  bubble_view_ =
-      new LoginUserMenuView(this, username, email, type, is_owner, anchor_view,
-                            show_remove_user, std::move(do_remove_user));
+  bubble_view_ = new LoginUserMenuView(this, username, email, type, is_owner,
+                                       anchor_view, show_remove_user,
+                                       std::move(on_remove_user_warning_shown),
+                                       std::move(on_remove_user_requested));
   bool had_focus = bubble_opener_->HasFocus();
-
   Show();
-
   if (had_focus) {
     // Try to focus the bubble view only if the tooltip was focused.
     bubble_view_->RequestFocus();
diff --git a/ash/login/ui/login_bubble.h b/ash/login/ui/login_bubble.h
index 52be89c..ff1903a 100644
--- a/ash/login/ui/login_bubble.h
+++ b/ash/login/ui/login_bubble.h
@@ -52,7 +52,8 @@
                     views::View* anchor_view,
                     LoginButton* bubble_opener,
                     bool show_remove_user,
-                    base::OnceClosure do_remove_user);
+                    base::OnceClosure on_remove_user_warning_shown,
+                    base::OnceClosure on_remove_user_requested);
 
   // Shows a tooltip.
   void ShowTooltip(const base::string16& message, views::View* anchor_view);
diff --git a/ash/login/ui/login_bubble_unittest.cc b/ash/login/ui/login_bubble_unittest.cc
index cbf07da7..1f4b83c3 100644
--- a/ash/login/ui/login_bubble_unittest.cc
+++ b/ash/login/ui/login_bubble_unittest.cc
@@ -69,12 +69,14 @@
     LoginTestBase::TearDown();
   }
 
-  void ShowUserMenu(base::OnceClosure on_remove) {
+  void ShowUserMenu(base::OnceClosure on_remove_show_warning,
+                    base::OnceClosure on_remove) {
     bool show_remove_user = !on_remove.is_null();
     bubble_->ShowUserMenu(
         base::string16() /*username*/, base::string16() /*email*/,
         user_manager::UserType::USER_TYPE_REGULAR, false /*is_owner*/,
-        container_, bubble_opener_, show_remove_user, std::move(on_remove));
+        container_, bubble_opener_, show_remove_user,
+        std::move(on_remove_show_warning), std::move(on_remove));
   }
 
   // Owned by test widget view hierarchy.
@@ -118,7 +120,7 @@
   EXPECT_FALSE(bubble_->IsVisible());
 
   // Verifies that key event on the bubble opener view won't close the bubble.
-  ShowUserMenu(base::OnceClosure());
+  ShowUserMenu(base::OnceClosure(), base::OnceClosure());
   EXPECT_TRUE(bubble_->IsVisible());
   bubble_opener_->RequestFocus();
   generator.PressKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
@@ -141,7 +143,7 @@
   EXPECT_FALSE(bubble_->IsVisible());
 
   // Verifies that mouse event on the bubble opener view won't close the bubble.
-  ShowUserMenu(base::OnceClosure());
+  ShowUserMenu(base::OnceClosure(), base::OnceClosure());
   EXPECT_TRUE(bubble_->IsVisible());
   generator.MoveMouseTo(bubble_opener_->GetBoundsInScreen().CenterPoint());
   generator.ClickLeftButton();
@@ -170,7 +172,7 @@
 
   // Verifies that gesture event on the bubble opener view won't close the
   // bubble.
-  ShowUserMenu(base::OnceClosure());
+  ShowUserMenu(base::OnceClosure(), base::OnceClosure());
   EXPECT_TRUE(bubble_->IsVisible());
   generator.GestureTapAt(bubble_opener_->GetBoundsInScreen().CenterPoint());
   EXPECT_TRUE(bubble_->IsVisible());
@@ -192,7 +194,7 @@
             views::InkDropHostView::InkDropMode::ON);
 
   // Show the bubble to activate the ripple effect.
-  ShowUserMenu(base::OnceClosure());
+  ShowUserMenu(base::OnceClosure(), base::OnceClosure());
   EXPECT_TRUE(bubble_->IsVisible());
   EXPECT_TRUE(ink_drop_api.HasInkDrop());
   EXPECT_EQ(ink_drop_api.GetInkDrop()->GetTargetInkDropState(),
@@ -216,9 +218,14 @@
 // callback.
 TEST_F(LoginBubbleTest, RemoveUserRequiresTwoActivations) {
   // Show the user menu.
+  bool remove_warning_called = false;
   bool remove_called = false;
-  ShowUserMenu(base::BindOnce(
-      [](bool* remove_called) { *remove_called = true; }, &remove_called));
+  ShowUserMenu(
+      base::BindOnce(
+          [](bool* remove_warning_called) { *remove_warning_called = true; },
+          &remove_warning_called),
+      base::BindOnce([](bool* remove_called) { *remove_called = true; },
+                     &remove_called));
   EXPECT_TRUE(bubble_->IsVisible());
 
   // Focus the remove user button.
@@ -228,14 +235,20 @@
   remove_user_button->RequestFocus();
   EXPECT_TRUE(remove_user_button->HasFocus());
 
-  // Click it twice. Verify only the second click fires the callback.
   auto click = [&]() {
     EXPECT_TRUE(remove_user_button->HasFocus());
     GetEventGenerator().PressKey(ui::KeyboardCode::VKEY_RETURN, 0);
   };
+
+  // First click calls remove warning.
   EXPECT_NO_FATAL_FAILURE(click());
+  EXPECT_TRUE(remove_warning_called);
   EXPECT_FALSE(remove_called);
+  remove_warning_called = false;
+
+  // Second click calls remove.
   EXPECT_NO_FATAL_FAILURE(click());
+  EXPECT_FALSE(remove_warning_called);
   EXPECT_TRUE(remove_called);
 }
 
diff --git a/ash/login/ui/login_test_base.h b/ash/login/ui/login_test_base.h
index 205acfc6..7605055 100644
--- a/ash/login/ui/login_test_base.h
+++ b/ash/login/ui/login_test_base.h
@@ -40,6 +40,7 @@
   // Changes the active number of users. Fires an event on |data_dispatcher()|.
   void SetUserCount(size_t count);
 
+  std::vector<mojom::LoginUserInfoPtr>& users() { return users_; }
   const std::vector<mojom::LoginUserInfoPtr>& users() const { return users_; }
 
   LoginDataDispatcher* data_dispatcher() { return &data_dispatcher_; }
diff --git a/ash/login/ui/login_test_utils.cc b/ash/login/ui/login_test_utils.cc
index 8e8c23c9..46a5522 100644
--- a/ash/login/ui/login_test_utils.cc
+++ b/ash/login/ui/login_test_utils.cc
@@ -11,14 +11,24 @@
   return LockContentsView::TestApi(view);
 }
 
-LoginAuthUserView::TestApi MakeLoginPrimaryAuthTestApi(LockContentsView* view) {
-  return LoginAuthUserView::TestApi(
-      MakeLockContentsViewTestApi(view).primary_auth());
+LoginAuthUserView::TestApi MakeLoginAuthTestApi(LockContentsView* view,
+                                                AuthTarget target) {
+  switch (target) {
+    case AuthTarget::kPrimary:
+      return LoginAuthUserView::TestApi(
+          MakeLockContentsViewTestApi(view).primary_auth());
+    case AuthTarget::kSecondary:
+      return LoginAuthUserView::TestApi(
+          MakeLockContentsViewTestApi(view).opt_secondary_auth());
+  }
+
+  NOTREACHED();
 }
 
-LoginPasswordView::TestApi MakeLoginPasswordTestApi(LockContentsView* view) {
+LoginPasswordView::TestApi MakeLoginPasswordTestApi(LockContentsView* view,
+                                                    AuthTarget target) {
   return LoginPasswordView::TestApi(
-      MakeLoginPrimaryAuthTestApi(view).password_view());
+      MakeLoginAuthTestApi(view, target).password_view());
 }
 
 mojom::LoginUserInfoPtr CreateUser(const std::string& email) {
diff --git a/ash/login/ui/login_test_utils.h b/ash/login/ui/login_test_utils.h
index ad02dbf0..91fc4da 100644
--- a/ash/login/ui/login_test_utils.h
+++ b/ash/login/ui/login_test_utils.h
@@ -12,10 +12,14 @@
 
 namespace ash {
 
+enum class AuthTarget { kPrimary, kSecondary };
+
 // Helpers for constructing TestApi instances.
 LockContentsView::TestApi MakeLockContentsViewTestApi(LockContentsView* view);
-LoginAuthUserView::TestApi MakeLoginPrimaryAuthTestApi(LockContentsView* view);
-LoginPasswordView::TestApi MakeLoginPasswordTestApi(LockContentsView* view);
+LoginAuthUserView::TestApi MakeLoginAuthTestApi(LockContentsView* view,
+                                                AuthTarget auth);
+LoginPasswordView::TestApi MakeLoginPasswordTestApi(LockContentsView* view,
+                                                    AuthTarget auth);
 
 // Utility method to create a new |mojom::UserInfoPtr| instance.
 mojom::LoginUserInfoPtr CreateUser(const std::string& email);
diff --git a/ash/login/ui/login_user_view.cc b/ash/login/ui/login_user_view.cc
index 7210fad..400dc67 100644
--- a/ash/login/ui/login_user_view.cc
+++ b/ash/login/ui/login_user_view.cc
@@ -294,6 +294,14 @@
   return view_->tap_button_;
 }
 
+views::View* LoginUserView::TestApi::dropdown() const {
+  return view_->user_dropdown_;
+}
+
+LoginBubble* LoginUserView::TestApi::menu() const {
+  return view_->user_menu_.get();
+}
+
 bool LoginUserView::TestApi::is_opaque() const {
   return view_->is_opaque_;
 }
@@ -313,15 +321,25 @@
   return 0;
 }
 
-LoginUserView::LoginUserView(LoginDisplayStyle style,
-                             bool show_dropdown,
-                             bool show_domain,
-                             const OnTap& on_tap)
-    : on_tap_(on_tap), display_style_(style) {
-  // show_dropdown and show_domain can only be true when the user view is
-  // rendering in large mode.
+LoginUserView::LoginUserView(
+    LoginDisplayStyle style,
+    bool show_dropdown,
+    bool show_domain,
+    const OnTap& on_tap,
+    const OnRemoveWarningShown& on_remove_warning_shown,
+    const OnRemove& on_remove)
+    : on_tap_(on_tap),
+      on_remove_warning_shown_(on_remove_warning_shown),
+      on_remove_(on_remove),
+      display_style_(style) {
+  // show_dropdown can only be true when the user view is rendering in large
+  // mode.
   DCHECK(!show_dropdown || style == LoginDisplayStyle::kLarge);
   DCHECK(!show_domain || style == LoginDisplayStyle::kLarge);
+  // |on_remove_warning_shown| and |on_remove| is only available iff
+  // |show_dropdown| is true.
+  DCHECK(show_dropdown == !!on_remove_warning_shown);
+  DCHECK(show_dropdown == !!on_remove);
 
   user_image_ = new UserImage(GetImageSize(style));
   user_label_ = new UserLabel(style);
@@ -478,7 +496,7 @@
           current_user_->basic_user_info->type, current_user_->is_device_owner,
           user_dropdown_ /*anchor_view*/, user_dropdown_ /*bubble_opener*/,
           current_user_->can_remove /*show_remove_user*/,
-          base::OnceClosure() /*do_remove_user*/);
+          on_remove_warning_shown_, on_remove_);
     } else {
       user_menu_->Close();
     }
diff --git a/ash/login/ui/login_user_view.h b/ash/login/ui/login_user_view.h
index 4d27549c..45241b62 100644
--- a/ash/login/ui/login_user_view.h
+++ b/ash/login/ui/login_user_view.h
@@ -35,6 +35,8 @@
 
     views::View* user_label() const;
     views::View* tap_button() const;
+    views::View* dropdown() const;
+    LoginBubble* menu() const;
 
     bool is_opaque() const;
 
@@ -43,6 +45,8 @@
   };
 
   using >
+  using >
+  using >
 
   // Returns the width of this view for the given display style.
   static int WidthForLayoutStyle(LoginDisplayStyle style);
@@ -50,7 +54,9 @@
   LoginUserView(LoginDisplayStyle style,
                 bool show_dropdown,
                 bool show_domain,
-                const OnTap& on_tap);
+                const OnTap& on_tap,
+                const OnRemoveWarningShown& on_remove_warning_shown,
+                const OnRemove& on_remove);
   ~LoginUserView() override;
 
   // Update the user view to display the given user information.
@@ -91,6 +97,10 @@
 
   // Executed when the user view is pressed.
   OnTap on_tap_;
+  // Executed when the user has seen the remove user warning.
+  OnRemoveWarningShown on_remove_warning_shown_;
+  // Executed when a user-remove has been requested.
+  OnRemove on_remove_;
 
   // The user that is currently being displayed (or will be displayed when an
   // animation completes).
@@ -102,8 +112,10 @@
   LoginDisplayStyle display_style_;
   UserImage* user_image_ = nullptr;
   UserLabel* user_label_ = nullptr;
+  // TODO(jdufault): Rename user_dropdown_ to dropdown_.
   LoginButton* user_dropdown_ = nullptr;
   TapButton* tap_button_ = nullptr;
+  // TODO(jdufault): Rename user_menu_ to menu_ or popup_menu_.
   std::unique_ptr<LoginBubble> user_menu_;
 
   // Show the domain information for public account user.
diff --git a/ash/login/ui/login_user_view_unittest.cc b/ash/login/ui/login_user_view_unittest.cc
index 65406597..65faed8 100644
--- a/ash/login/ui/login_user_view_unittest.cc
+++ b/ash/login/ui/login_user_view_unittest.cc
@@ -25,10 +25,20 @@
   LoginUserView* AddUserView(LoginDisplayStyle display_style,
                              bool show_dropdown) {
     // TODO(crbug.com/809635): Add test case for show_domain.
+    LoginUserView::OnRemoveWarningShown on_remove_warning_shown;
+    LoginUserView::OnRemove on_remove;
+    if (show_dropdown) {
+      on_remove_warning_shown = base::BindRepeating(
+          &LoginUserViewUnittest::OnRemoveWarningShown, base::Unretained(this));
+      on_remove = base::BindRepeating(&LoginUserViewUnittest::OnRemove,
+                                      base::Unretained(this));
+    }
+
     auto* view =
         new LoginUserView(display_style, show_dropdown, false /*show_domain*/,
                           base::BindRepeating(&LoginUserViewUnittest::OnTapped,
-                                              base::Unretained(this)));
+                                              base::Unretained(this)),
+                          on_remove_warning_shown, on_remove);
     mojom::LoginUserInfoPtr user = CreateUser("foo@foo.com");
     view->UpdateForUser(user, false /*animate*/);
     container_->AddChildView(view);
@@ -52,11 +62,15 @@
   }
 
   int tap_count_ = 0;
+  int remove_show_warning_count_ = 0;
+  int remove_count_ = 0;
 
   views::View* container_ = nullptr;  // Owned by test widget view hierarchy.
 
  private:
   void OnTapped() { ++tap_count_; }
+  void OnRemoveWarningShown() { ++remove_show_warning_count_; }
+  void OnRemove() { ++remove_count_; }
 
   DISALLOW_COPY_AND_ASSIGN(LoginUserViewUnittest);
 };
diff --git a/ash/login/ui/scrollable_users_list_view.cc b/ash/login/ui/scrollable_users_list_view.cc
index 4ad0497d..08e87b8b 100644
--- a/ash/login/ui/scrollable_users_list_view.cc
+++ b/ash/login/ui/scrollable_users_list_view.cc
@@ -117,7 +117,7 @@
 
 ScrollableUsersListView::ScrollableUsersListView(
     const std::vector<mojom::LoginUserInfoPtr>& users,
-    const OnUserViewTap& on_user_view_tap,
+    const ActionWithUser& on_tap_user,
     LoginDisplayStyle display_style)
     : views::ScrollView() {
   layout_params_ = GetLayoutParams(display_style);
@@ -136,8 +136,8 @@
   for (std::size_t i = 1u; i < users.size(); ++i) {
     auto* view = new LoginUserView(
         layout_params_.display_style, false /*show_dropdown*/,
-        false /*show_domain*/,
-        base::BindRepeating(on_user_view_tap, i - 1) /*on_tap*/);
+        false /*show_domain*/, base::BindRepeating(on_tap_user, i - 1),
+        base::RepeatingClosure(), base::RepeatingClosure());
     user_views_.push_back(view);
     view->UpdateForUser(users[i], false /*animate*/);
     contents->AddChildView(view);
@@ -158,11 +158,6 @@
 
 ScrollableUsersListView::~ScrollableUsersListView() = default;
 
-LoginUserView* ScrollableUsersListView::GetUserViewAtIndex(int index) {
-  return static_cast<size_t>(index) < user_views_.size() ? user_views_[index]
-                                                         : nullptr;
-}
-
 void ScrollableUsersListView::Layout() {
   DCHECK(layout_);
   bool should_show_landscape =
diff --git a/ash/login/ui/scrollable_users_list_view.h b/ash/login/ui/scrollable_users_list_view.h
index 6a4e91a..99e433e 100644
--- a/ash/login/ui/scrollable_users_list_view.h
+++ b/ash/login/ui/scrollable_users_list_view.h
@@ -9,6 +9,7 @@
 
 #include "ash/ash_export.h"
 #include "ash/login/ui/login_display_style.h"
+#include "ash/login/ui/login_user_view.h"
 #include "ash/public/interfaces/login_user_info.mojom.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/views/controls/scroll_view.h"
@@ -21,7 +22,6 @@
 namespace ash {
 
 class HoverNotifier;
-class LoginUserView;
 class ScrollBar;
 
 // Scrollable list of the users. Stores the list of login user views. Can be
@@ -41,18 +41,24 @@
     ScrollableUsersListView* const view_;
   };
 
-  using >
+  // TODO(jdufault): Pass AccountId or LoginUserView* instead of index.
+  using ActionWithUser = base::RepeatingCallback<void(int)>;
 
   // Initializes users list with rows for all |users|. The |display_style| is
   // used to determine layout and sizings. |on_user_view_tap| callback is
   // invoked whenever user row is tapped.
   ScrollableUsersListView(const std::vector<mojom::LoginUserInfoPtr>& users,
-                          const OnUserViewTap& on_user_view_tap,
+                          const ActionWithUser& on_tap_user,
                           LoginDisplayStyle display_style);
   ~ScrollableUsersListView() override;
 
   // Returns user view at |index| if it exists or nullptr otherwise.
-  LoginUserView* GetUserViewAtIndex(int index);
+  int user_count() const { return static_cast<int>(user_views_.size()); }
+  LoginUserView* user_view_at(int index) {
+    DCHECK_GE(index, 0);
+    DCHECK_LT(index, user_count());
+    return user_views_[index];
+  }
 
   // views::View:
   void Layout() override;
@@ -104,4 +110,4 @@
 
 }  // namespace ash
 
-#endif  // ASH_LOGIN_UI_SCROLLABLE_USERS_LIST_VIEW_H_
\ No newline at end of file
+#endif  // ASH_LOGIN_UI_SCROLLABLE_USERS_LIST_VIEW_H_
diff --git a/ash/metrics/login_metrics_recorder_unittest.cc b/ash/metrics/login_metrics_recorder_unittest.cc
index ff437773..5a8224b 100644
--- a/ash/metrics/login_metrics_recorder_unittest.cc
+++ b/ash/metrics/login_metrics_recorder_unittest.cc
@@ -169,7 +169,7 @@
   EXPECT_EQ(test_api.primary_auth()->auth_methods(),
             (auth_method | LoginAuthUserView::AUTH_TAP));
   EXPECT_CALL(*client, AttemptUnlock(primary_user));
-  generator.MoveMouseTo(MakeLoginPrimaryAuthTestApi(contents)
+  generator.MoveMouseTo(MakeLoginAuthTestApi(contents, AuthTarget::kPrimary)
                             .user_view()
                             ->GetBoundsInScreen()
                             .CenterPoint());
diff --git a/ash/public/interfaces/login_screen.mojom b/ash/public/interfaces/login_screen.mojom
index 5b50918..c614cc58 100644
--- a/ash/public/interfaces/login_screen.mojom
+++ b/ash/public/interfaces/login_screen.mojom
@@ -166,4 +166,10 @@
 
   // Show Gaia signin dialog in chrome.
   ShowGaiaSignin();
+
+  // Notification that the remove user warning was shown.
+  OnRemoveUserWarningShown();
+
+  // Try to remove |account_id|.
+  RemoveUser(signin.mojom.AccountId account_id);
 };
diff --git a/chrome/browser/ui/ash/login_screen_client.cc b/chrome/browser/ui/ash/login_screen_client.cc
index 66332a88..7142062 100644
--- a/chrome/browser/ui/ash/login_screen_client.cc
+++ b/chrome/browser/ui/ash/login_screen_client.cc
@@ -11,7 +11,9 @@
 #include "chrome/browser/chromeos/login/reauth_stats.h"
 #include "chrome/browser/chromeos/login/ui/login_display_host.h"
 #include "chrome/browser/chromeos/login/ui/user_adding_screen.h"
+#include "chrome/browser/profiles/profile_metrics.h"
 #include "chrome/browser/ui/ash/wallpaper_controller_client.h"
+#include "components/user_manager/remove_user_delegate.h"
 #include "content/public/common/service_manager_connection.h"
 #include "services/service_manager/public/cpp/connector.h"
 
@@ -115,6 +117,18 @@
   }
 }
 
+void LoginScreenClient::OnRemoveUserWarningShown() {
+  ProfileMetrics::LogProfileDeleteUser(
+      ProfileMetrics::DELETE_PROFILE_USER_MANAGER_SHOW_WARNING);
+}
+
+void LoginScreenClient::RemoveUser(const AccountId& account_id) {
+  ProfileMetrics::LogProfileDeleteUser(
+      ProfileMetrics::DELETE_PROFILE_USER_MANAGER);
+  user_manager::UserManager::Get()->RemoveUser(account_id,
+                                               nullptr /*delegate*/);
+}
+
 void LoginScreenClient::LoadWallpaper(const AccountId& account_id) {
   WallpaperControllerClient::Get()->ShowUserWallpaper(account_id);
 }
diff --git a/chrome/browser/ui/ash/login_screen_client.h b/chrome/browser/ui/ash/login_screen_client.h
index 2fedbd4..2a186f60 100644
--- a/chrome/browser/ui/ash/login_screen_client.h
+++ b/chrome/browser/ui/ash/login_screen_client.h
@@ -74,6 +74,8 @@
   void OnMaxIncorrectPasswordAttempted(const AccountId& account_id) override;
   void FocusLockScreenApps(bool reverse) override;
   void ShowGaiaSignin() override;
+  void OnRemoveUserWarningShown() override;
+  void RemoveUser(const AccountId& account_id) override;
 
  private:
   // Lock screen mojo service in ash.