Refactor Add new user / restricted profile UX flow.

Incorporate the choose user name / avatar into the
user creation flow so that we don't end up with many
"New User"s.

Bug: 147653252
Test: make -j64 RunSettingsRoboTests ROBOTEST_FILTER="com.android.settings.users.EditUserInfoControllerTest"
Change-Id: Ie19230791d8b50c8ab04df89909606179364ebab
diff --git a/src/com/android/settings/users/EditUserInfoController.java b/src/com/android/settings/users/EditUserInfoController.java
index 4d9244a..373d6a9 100644
--- a/src/com/android/settings/users/EditUserInfoController.java
+++ b/src/com/android/settings/users/EditUserInfoController.java
@@ -20,10 +20,8 @@
 import android.app.Dialog;
 import android.content.DialogInterface;
 import android.content.Intent;
-import android.content.pm.UserInfo;
 import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
-import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -39,7 +37,6 @@
 import androidx.fragment.app.Fragment;
 
 import com.android.settings.R;
-import com.android.settingslib.Utils;
 import com.android.settingslib.drawable.CircleFramedDrawable;
 
 import java.io.File;
@@ -59,9 +56,24 @@
     private UserManager mUserManager;
     private boolean mWaitingForActivityResult = false;
 
+    /**
+     * Callback made when either the username text or photo choice changes.
+     */
     public interface OnContentChangedCallback {
-        public void onPhotoChanged(Drawable photo);
-        public void onLabelChanged(CharSequence label);
+        /** Photo updated. */
+        void onPhotoChanged(UserHandle user, Drawable photo);
+        /** Username updated. */
+        void onLabelChanged(UserHandle user, CharSequence label);
+    }
+
+    /**
+     * Callback made when the dialog finishes.
+     */
+    public interface OnDialogCompleteCallback {
+        /** Dialog closed with positive button. */
+        void onPositive();
+        /** Dialog closed with negative button or cancelled. */
+        void onNegativeOrCancel();
     }
 
     public void clear() {
@@ -111,7 +123,8 @@
 
     public Dialog createDialog(final Fragment fragment, final Drawable currentUserIcon,
             final CharSequence currentUserName,
-            int titleResId, final OnContentChangedCallback callback, UserHandle user) {
+            String title, final OnContentChangedCallback callback, UserHandle user,
+            OnDialogCompleteCallback completeCallback) {
         Activity activity = fragment.getActivity();
         mUser = user;
         if (mUserManager == null) {
@@ -120,10 +133,8 @@
         LayoutInflater inflater = activity.getLayoutInflater();
         View content = inflater.inflate(R.layout.edit_user_info_dialog_content, null);
 
-        UserInfo info = mUserManager.getUserInfo(mUser.getIdentifier());
-
         final EditText userNameView = (EditText) content.findViewById(R.id.user_name);
-        userNameView.setText(info.name);
+        userNameView.setText(currentUserName);
 
         final ImageView userPhotoView = (ImageView) content.findViewById(R.id.user_photo);
         Drawable drawable = null;
@@ -131,14 +142,11 @@
             drawable = CircleFramedDrawable.getInstance(activity, mSavedPhoto);
         } else {
             drawable = currentUserIcon;
-            if (drawable == null) {
-                drawable = Utils.getUserIcon(activity, mUserManager, info);
-            }
         }
         userPhotoView.setImageDrawable(drawable);
         mEditUserPhotoController = createEditUserPhotoController(fragment, userPhotoView, drawable);
         mEditUserInfoDialog = new AlertDialog.Builder(activity)
-                .setTitle(R.string.profile_info_settings_title)
+                .setTitle(title)
                 .setView(content)
                 .setCancelable(true)
                 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@@ -149,41 +157,45 @@
                             CharSequence userName = userNameView.getText();
                             if (!TextUtils.isEmpty(userName)) {
                                 if (currentUserName == null
-                                        || !userName.toString().equals(currentUserName.toString())) {
+                                        || !userName.toString().equals(
+                                                currentUserName.toString())) {
                                     if (callback != null) {
-                                        callback.onLabelChanged(userName.toString());
+                                        callback.onLabelChanged(mUser, userName.toString());
                                     }
-                                    mUserManager.setUserName(mUser.getIdentifier(),
-                                            userName.toString());
                                 }
                             }
                             // Update the photo if changed.
                             Drawable drawable = mEditUserPhotoController.getNewUserPhotoDrawable();
-                            Bitmap bitmap = mEditUserPhotoController.getNewUserPhotoBitmap();
-                            if (drawable != null && bitmap != null
-                                    && !drawable.equals(currentUserIcon)) {
+                            if (drawable != null && !drawable.equals(currentUserIcon)) {
                                 if (callback != null) {
-                                    callback.onPhotoChanged(drawable);
+                                        callback.onPhotoChanged(mUser, drawable);
                                 }
-                                new AsyncTask<Void, Void, Void>() {
-                                    @Override
-                                    protected Void doInBackground(Void... params) {
-                                        mUserManager.setUserIcon(mUser.getIdentifier(),
-                                                mEditUserPhotoController.getNewUserPhotoBitmap());
-                                        return null;
-                                    }
-                                }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
                             }
                             fragment.getActivity().removeDialog(
                                     RestrictedProfileSettings.DIALOG_ID_EDIT_USER_INFO);
                         }
                         clear();
+                        if (completeCallback != null) {
+                            completeCallback.onPositive();
+                        }
                     }
                 })
                 .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
                     @Override
                     public void onClick(DialogInterface dialog, int which) {
                         clear();
+                        if (completeCallback != null) {
+                            completeCallback.onNegativeOrCancel();
+                        }
+                    }
+                })
+                .setOnCancelListener(new DialogInterface.OnCancelListener() {
+                    @Override
+                    public void onCancel(DialogInterface dialog) {
+                        clear();
+                        if (completeCallback != null) {
+                            completeCallback.onNegativeOrCancel();
+                        }
                     }
                 })
                 .create();
diff --git a/src/com/android/settings/users/RestrictedProfileSettings.java b/src/com/android/settings/users/RestrictedProfileSettings.java
index b00ee83..57c0d02 100644
--- a/src/com/android/settings/users/RestrictedProfileSettings.java
+++ b/src/com/android/settings/users/RestrictedProfileSettings.java
@@ -23,12 +23,15 @@
 import android.content.pm.UserInfo;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
+import android.os.UserHandle;
 import android.view.View;
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import com.android.internal.util.UserIcons;
 import com.android.settings.R;
 import com.android.settings.Utils;
+import com.android.settingslib.utils.ThreadUtils;
 
 public class RestrictedProfileSettings extends AppRestrictionsFragment
         implements EditUserInfoController.OnContentChangedCallback {
@@ -117,8 +120,8 @@
     public Dialog onCreateDialog(int dialogId) {
         if (dialogId == DIALOG_ID_EDIT_USER_INFO) {
             return mEditUserInfoController.createDialog(this, mUserIconView.getDrawable(),
-                    mUserNameView.getText(), R.string.profile_info_settings_title,
-                    this, mUser);
+                    mUserNameView.getText(), getString(R.string.profile_info_settings_title),
+                    this, mUser, null);
         } else if (dialogId == DIALOG_CONFIRM_REMOVE) {
             Dialog dlg =
                     UserDialogs.createRemoveDialog(getActivity(), mUser.getIdentifier(),
@@ -156,12 +159,19 @@
     }
 
     @Override
-    public void onPhotoChanged(Drawable photo) {
+    public void onPhotoChanged(UserHandle user, Drawable photo) {
         mUserIconView.setImageDrawable(photo);
+        ThreadUtils.postOnBackgroundThread(new Runnable() {
+            @Override
+            public void run() {
+                mUserManager.setUserIcon(user.getIdentifier(), UserIcons.convertToBitmap(photo));
+            }
+        });
     }
 
     @Override
-    public void onLabelChanged(CharSequence label) {
+    public void onLabelChanged(UserHandle user, CharSequence label) {
         mUserNameView.setText(label);
+        mUserManager.setUserName(user.getIdentifier(), label.toString());
     }
 }
diff --git a/src/com/android/settings/users/UserSettings.java b/src/com/android/settings/users/UserSettings.java
index 7a9f026..cd305e9 100644
--- a/src/com/android/settings/users/UserSettings.java
+++ b/src/com/android/settings/users/UserSettings.java
@@ -16,6 +16,8 @@
 
 package com.android.settings.users;
 
+import static android.os.Process.myUserHandle;
+
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.Dialog;
@@ -73,6 +75,7 @@
 import com.android.settingslib.RestrictedPreference;
 import com.android.settingslib.drawable.CircleFramedDrawable;
 import com.android.settingslib.search.SearchIndexable;
+import com.android.settingslib.utils.ThreadUtils;
 
 import com.google.android.setupcompat.util.WizardManagerHelper;
 
@@ -82,6 +85,7 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Random;
 
 /**
  * Screen that manages the list of users on the device.
@@ -95,8 +99,7 @@
 public class UserSettings extends SettingsPreferenceFragment
         implements Preference.OnPreferenceClickListener, View.OnClickListener,
         MultiUserSwitchBarController.OnMultiUserSwitchChangedListener,
-        DialogInterface.OnDismissListener,
-        EditUserInfoController.OnContentChangedCallback {
+        DialogInterface.OnDismissListener {
 
     private static final String TAG = "UserSettings";
 
@@ -125,6 +128,8 @@
     private static final int DIALOG_NEED_LOCKSCREEN = 7;
     private static final int DIALOG_CONFIRM_EXIT_GUEST = 8;
     private static final int DIALOG_USER_PROFILE_EDITOR = 9;
+    private static final int DIALOG_USER_PROFILE_EDITOR_ADD_USER = 10;
+    private static final int DIALOG_USER_PROFILE_EDITOR_ADD_RESTRICTED_PROFILE = 11;
 
     private static final int MESSAGE_UPDATE_LIST = 1;
     private static final int MESSAGE_SETUP_USER = 2;
@@ -168,6 +173,9 @@
     private AddUserWhenLockedPreferenceController mAddUserWhenLockedPreferenceController;
     private MultiUserFooterPreferenceController mMultiUserFooterPreferenceController;
 
+    private CharSequence mPendingUserName;
+    private Drawable mPendingUserIcon;
+
     // A place to cache the generated default avatar
     private Drawable mDefaultIconDrawable;
 
@@ -447,7 +455,7 @@
                         break;
                     case USER_TYPE_RESTRICTED_PROFILE:
                         if (hasLockscreenSecurity()) {
-                            addUserNow(USER_TYPE_RESTRICTED_PROFILE);
+                            showDialog(DIALOG_USER_PROFILE_EDITOR_ADD_RESTRICTED_PROFILE);
                         } else {
                             showDialog(DIALOG_NEED_LOCKSCREEN);
                         }
@@ -466,22 +474,6 @@
         }
     }
 
-    private UserInfo createRestrictedProfile() {
-        UserInfo newUserInfo = mUserManager.createRestrictedProfile(mAddingUserName);
-        if (newUserInfo != null && !assignDefaultPhoto(getActivity(), newUserInfo.id)) {
-            return null;
-        }
-        return newUserInfo;
-    }
-
-    private UserInfo createTrustedUser() {
-        UserInfo newUserInfo = mUserManager.createUser(mAddingUserName, 0);
-        if (newUserInfo != null && !assignDefaultPhoto(getActivity(), newUserInfo.id)) {
-            return null;
-        }
-        return newUserInfo;
-    }
-
     private void onManageUserClicked(int userId, boolean newUser) {
         mAddingUser = false;
         if (userId == UserPreference.USERID_GUEST_DEFAULTS) {
@@ -571,15 +563,13 @@
                 final int messageResId = longMessageDisplayed
                         ? R.string.user_add_user_message_short
                         : R.string.user_add_user_message_long;
-                final int userType = dialogId == DIALOG_ADD_USER
-                        ? USER_TYPE_USER : USER_TYPE_RESTRICTED_PROFILE;
                 Dialog dlg = new AlertDialog.Builder(context)
                         .setTitle(R.string.user_add_user_title)
                         .setMessage(messageResId)
                         .setPositiveButton(android.R.string.ok,
                                 new DialogInterface.OnClickListener() {
                                     public void onClick(DialogInterface dialog, int which) {
-                                        addUserNow(userType);
+                                        showDialog(DIALOG_USER_PROFILE_EDITOR_ADD_USER);
                                         if (!longMessageDisplayed) {
                                             preferences.edit().putBoolean(
                                                     KEY_ADD_USER_LONG_MESSAGE_DISPLAYED,
@@ -675,20 +665,96 @@
                 return dlg;
             }
             case DIALOG_USER_PROFILE_EDITOR: {
-                Dialog dlg = mEditUserInfoController.createDialog(
+                UserHandle user = myUserHandle();
+                UserInfo info = mUserManager.getUserInfo(user.getIdentifier());
+                return mEditUserInfoController.createDialog(
                         this,
-                        null,
-                        mMePreference.getTitle(),
-                        R.string.profile_info_settings_title,
-                        this /* callback */,
-                        android.os.Process.myUserHandle());
-                return dlg;
+                        Utils.getUserIcon(getPrefContext(), mUserManager, info),
+                        info.name,
+                        getString(R.string.profile_info_settings_title),
+                        new EditUserInfoController.OnContentChangedCallback() {
+                            @Override
+                            public void onPhotoChanged(UserHandle user, Drawable photo) {
+                                ThreadUtils.postOnBackgroundThread(new Runnable() {
+                                    @Override
+                                    public void run() {
+                                        mUserManager.setUserIcon(user.getIdentifier(),
+                                                UserIcons.convertToBitmap(photo));
+                                    }
+                                });
+                                mMePreference.setIcon(photo);
+                            }
+
+                            @Override
+                            public void onLabelChanged(UserHandle user, CharSequence label) {
+                                mMePreference.setTitle(label.toString());
+                                mUserManager.setUserName(user.getIdentifier(), label.toString());
+                            }
+                        },
+                        user,
+                        null);
+            }
+            case DIALOG_USER_PROFILE_EDITOR_ADD_USER: {
+                synchronized (mUserLock) {
+                    mPendingUserIcon = UserIcons.getDefaultUserIcon(getPrefContext().getResources(),
+                            new Random(System.currentTimeMillis()).nextInt(8), false);
+                    mPendingUserName = getString(R.string.user_new_user_name);
+                }
+                return buildAddUserProfileEditorDialog(USER_TYPE_USER);
+            }
+            case DIALOG_USER_PROFILE_EDITOR_ADD_RESTRICTED_PROFILE: {
+                synchronized (mUserLock) {
+                    mPendingUserIcon = UserIcons.getDefaultUserIcon(getPrefContext().getResources(),
+                            new Random(System.currentTimeMillis()).nextInt(8), false);
+                    mPendingUserName = getString(R.string.user_new_profile_name);
+                }
+                return buildAddUserProfileEditorDialog(USER_TYPE_RESTRICTED_PROFILE);
             }
             default:
                 return null;
         }
     }
 
+    private Dialog buildAddUserProfileEditorDialog(int userType) {
+        Dialog d;
+        synchronized (mUserLock) {
+            d = mEditUserInfoController.createDialog(
+                    this,
+                    mPendingUserIcon,
+                    mPendingUserName,
+                    getString(userType == USER_TYPE_USER
+                            ? R.string.user_info_settings_title
+                            : R.string.profile_info_settings_title),
+                    new EditUserInfoController.OnContentChangedCallback() {
+                        @Override
+                        public void onPhotoChanged(UserHandle user, Drawable photo) {
+                            mPendingUserIcon = photo;
+                        }
+
+                        @Override
+                        public void onLabelChanged(UserHandle user, CharSequence label) {
+                            mPendingUserName = label;
+                        }
+                    },
+                    myUserHandle(),
+                    new EditUserInfoController.OnDialogCompleteCallback() {
+                        @Override
+                        public void onPositive() {
+                            addUserNow(userType);
+                        }
+
+                        @Override
+                        public void onNegativeOrCancel() {
+                            synchronized (mUserLock) {
+                                mPendingUserIcon = null;
+                                mPendingUserName = null;
+                            }
+                        }
+                    });
+        }
+        return d;
+    }
+
     @Override
     public int getDialogMetricsCategory(int dialogId) {
         switch (dialogId) {
@@ -709,6 +775,8 @@
             case DIALOG_CONFIRM_EXIT_GUEST:
                 return SettingsEnums.DIALOG_USER_CONFIRM_EXIT_GUEST;
             case DIALOG_USER_PROFILE_EDITOR:
+            case DIALOG_USER_PROFILE_EDITOR_ADD_USER:
+            case DIALOG_USER_PROFILE_EDITOR_ADD_RESTRICTED_PROFILE:
                 return SettingsEnums.DIALOG_USER_EDIT_PROFILE;
             default:
                 return 0;
@@ -719,14 +787,15 @@
         if (mRemovingUserId == UserHandle.myUserId()) {
             removeThisUser();
         } else {
-            new Thread() {
+            ThreadUtils.postOnBackgroundThread(new Runnable() {
+                @Override
                 public void run() {
                     synchronized (mUserLock) {
                         mUserManager.removeUser(mRemovingUserId);
                         mHandler.sendEmptyMessage(MESSAGE_UPDATE_LIST);
                     }
                 }
-            }.start();
+            });
         }
     }
 
@@ -746,39 +815,59 @@
     private void addUserNow(final int userType) {
         synchronized (mUserLock) {
             mAddingUser = true;
-            mAddingUserName = userType == USER_TYPE_USER ? getString(R.string.user_new_user_name)
-                    : getString(R.string.user_new_profile_name);
-            //updateUserList();
-            new Thread() {
-                public void run() {
-                    UserInfo user;
-                    // Could take a few seconds
-                    if (userType == USER_TYPE_USER) {
-                        user = createTrustedUser();
-                    } else {
-                        user = createRestrictedProfile();
-                    }
+            mAddingUserName = userType == USER_TYPE_USER
+                    ? (mPendingUserName != null ? mPendingUserName.toString()
+                    : getString(R.string.user_new_user_name))
+                    : (mPendingUserName != null ? mPendingUserName.toString()
+                            : getString(R.string.user_new_profile_name));
+        }
+        ThreadUtils.postOnBackgroundThread(new Runnable() {
+            @Override
+            public void run() {
+                UserInfo user;
+                String username;
+
+                synchronized (mUserLock) {
+                    username = mAddingUserName;
+                }
+
+                // Could take a few seconds
+                if (userType == USER_TYPE_USER) {
+                    user = mUserManager.createUser(username, 0);
+                } else {
+                    user = mUserManager.createRestrictedProfile(username);
+                }
+
+                synchronized (mUserLock) {
                     if (user == null) {
                         mAddingUser = false;
+                        mPendingUserIcon = null;
+                        mPendingUserName = null;
                         return;
                     }
-                    synchronized (mUserLock) {
-                        if (userType == USER_TYPE_USER) {
-                            mHandler.sendEmptyMessage(MESSAGE_UPDATE_LIST);
-                            // Skip setting up user which results in user switching when the
-                            // restriction is set.
-                            if (!mUserCaps.mDisallowSwitchUser) {
-                                mHandler.sendMessage(mHandler.obtainMessage(
-                                        MESSAGE_SETUP_USER, user.id, user.serialNumber));
-                            }
-                        } else {
-                            mHandler.sendMessage(mHandler.obtainMessage(
-                                    MESSAGE_CONFIG_USER, user.id, user.serialNumber));
-                        }
+
+                    if (mPendingUserIcon != null) {
+                        mUserManager.setUserIcon(user.id,
+                                UserIcons.convertToBitmap(mPendingUserIcon));
                     }
+
+                    if (userType == USER_TYPE_USER) {
+                        mHandler.sendEmptyMessage(MESSAGE_UPDATE_LIST);
+                        // Skip setting up user which results in user switching when the
+                        // restriction is set.
+                        if (!mUserCaps.mDisallowSwitchUser) {
+                            mHandler.sendMessage(mHandler.obtainMessage(
+                                    MESSAGE_SETUP_USER, user.id, user.serialNumber));
+                        }
+                    } else {
+                        mHandler.sendMessage(mHandler.obtainMessage(
+                                MESSAGE_CONFIG_USER, user.id, user.serialNumber));
+                    }
+                    mPendingUserIcon = null;
+                    mPendingUserName = null;
                 }
-            }.start();
-        }
+            }
+        });
     }
 
     private void switchUserNow(int userId) {
@@ -1123,16 +1212,6 @@
         return R.string.help_url_users;
     }
 
-    @Override
-    public void onPhotoChanged(Drawable photo) {
-        mMePreference.setIcon(photo);
-    }
-
-    @Override
-    public void onLabelChanged(CharSequence label) {
-        mMePreference.setTitle(label);
-    }
-
     /**
      * Returns a default user icon (as a {@link Bitmap}) for the given user.
      *
diff --git a/tests/robotests/src/com/android/settings/users/EditUserInfoControllerTest.java b/tests/robotests/src/com/android/settings/users/EditUserInfoControllerTest.java
index a6a76f8..1c191fa 100644
--- a/tests/robotests/src/com/android/settings/users/EditUserInfoControllerTest.java
+++ b/tests/robotests/src/com/android/settings/users/EditUserInfoControllerTest.java
@@ -18,13 +18,16 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.same;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.Dialog;
 import android.content.Intent;
 import android.graphics.drawable.Drawable;
 import android.widget.EditText;
@@ -37,9 +40,6 @@
 import com.android.settings.R;
 import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
 
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -50,6 +50,9 @@
 import org.robolectric.android.controller.ActivityController;
 import org.robolectric.annotation.Config;
 
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
 @RunWith(RobolectricTestRunner.class)
 public class EditUserInfoControllerTest {
     private static final int MAX_USER_NAME_LENGTH = 100;
@@ -88,7 +91,8 @@
     @Test
     public void photoControllerOnActivityResult_whenWaiting_isCalled() {
         mController.createDialog(mFragment, mCurrentIcon, "test user",
-                R.string.profile_info_settings_title, null, android.os.Process.myUserHandle());
+                "title", null,
+                android.os.Process.myUserHandle(), null);
         mController.startingActivityForResult();
         Intent resultData = new Intent();
         mController.onActivityResult(0, 0, resultData);
@@ -104,8 +108,8 @@
         final String longName = Stream.generate(
                 () -> String.valueOf('A')).limit(200).collect(Collectors.joining());
         final AlertDialog dialog = (AlertDialog) mController.createDialog(mFragment, mCurrentIcon,
-                "test user", R.string.profile_info_settings_title, null,
-                android.os.Process.myUserHandle());
+                "test user", "title", null,
+                android.os.Process.myUserHandle(), null);
         final EditText userName = ShadowAlertDialogCompat.shadowOf(dialog).getView()
                 .findViewById(R.id.user_name);
 
@@ -113,4 +117,143 @@
 
         assertThat(userName.getText().length()).isEqualTo(MAX_USER_NAME_LENGTH);
     }
+
+    @Test
+    public void onDialogCompleteCallback_isCalled_whenCancelled() {
+        EditUserInfoController.OnContentChangedCallback contentChangeCallback = mock(
+                EditUserInfoController.OnContentChangedCallback.class);
+
+        EditUserInfoController.OnDialogCompleteCallback dialogCompleteCallback = mock(
+                EditUserInfoController.OnDialogCompleteCallback.class);
+
+        AlertDialog dialog = (AlertDialog) mController.createDialog(
+                mFragment, mCurrentIcon, "test",
+                "title", contentChangeCallback,
+                android.os.Process.myUserHandle(),
+                dialogCompleteCallback);
+
+        dialog.show();
+        dialog.cancel();
+
+        verify(contentChangeCallback, times(0))
+                .onLabelChanged(any(), any());
+        verify(contentChangeCallback, times(0))
+                .onPhotoChanged(any(), any());
+        verify(dialogCompleteCallback, times(0)).onPositive();
+        verify(dialogCompleteCallback, times(1)).onNegativeOrCancel();
+    }
+
+    @Test
+    public void onDialogCompleteCallback_isCalled_whenPositiveClicked() {
+        EditUserInfoController.OnContentChangedCallback contentChangeCallback = mock(
+                EditUserInfoController.OnContentChangedCallback.class);
+
+        EditUserInfoController.OnDialogCompleteCallback dialogCompleteCallback = mock(
+                EditUserInfoController.OnDialogCompleteCallback.class);
+
+        AlertDialog dialog = (AlertDialog) mController.createDialog(
+                mFragment, mCurrentIcon, "test",
+                "title", contentChangeCallback,
+                android.os.Process.myUserHandle(),
+                dialogCompleteCallback);
+
+        // No change to the photo.
+        when(mController.getPhotoController().getNewUserPhotoDrawable()).thenReturn(mCurrentIcon);
+
+        dialog.show();
+        dialog.getButton(Dialog.BUTTON_POSITIVE).performClick();
+
+        verify(contentChangeCallback, times(0))
+                .onLabelChanged(any(), any());
+        verify(contentChangeCallback, times(0))
+                .onPhotoChanged(any(), any());
+        verify(dialogCompleteCallback, times(1)).onPositive();
+        verify(dialogCompleteCallback, times(0)).onNegativeOrCancel();
+    }
+
+    @Test
+    public void onDialogCompleteCallback_isCalled_whenNegativeClicked() {
+        EditUserInfoController.OnContentChangedCallback contentChangeCallback = mock(
+                EditUserInfoController.OnContentChangedCallback.class);
+
+        EditUserInfoController.OnDialogCompleteCallback dialogCompleteCallback = mock(
+                EditUserInfoController.OnDialogCompleteCallback.class);
+
+        AlertDialog dialog = (AlertDialog) mController.createDialog(
+                mFragment, mCurrentIcon, "test",
+                "title", contentChangeCallback,
+                android.os.Process.myUserHandle(),
+                dialogCompleteCallback);
+
+        dialog.show();
+        dialog.getButton(Dialog.BUTTON_NEGATIVE).performClick();
+
+        verify(contentChangeCallback, times(0))
+                .onLabelChanged(any(), any());
+        verify(contentChangeCallback, times(0))
+                .onPhotoChanged(any(), any());
+        verify(dialogCompleteCallback, times(0)).onPositive();
+        verify(dialogCompleteCallback, times(1)).onNegativeOrCancel();
+    }
+
+    @Test
+    public void onContentChangedCallback_isCalled_whenLabelChanges() {
+        EditUserInfoController.OnContentChangedCallback contentChangeCallback = mock(
+                EditUserInfoController.OnContentChangedCallback.class);
+
+        EditUserInfoController.OnDialogCompleteCallback dialogCompleteCallback = mock(
+                EditUserInfoController.OnDialogCompleteCallback.class);
+
+        AlertDialog dialog = (AlertDialog) mController.createDialog(
+                mFragment, mCurrentIcon, "test",
+                "title", contentChangeCallback,
+                android.os.Process.myUserHandle(),
+                dialogCompleteCallback);
+
+        // No change to the photo.
+        when(mController.getPhotoController().getNewUserPhotoDrawable()).thenReturn(mCurrentIcon);
+
+        dialog.show();
+        String expectedNewName = "new test user";
+        EditText editText = (EditText) dialog.findViewById(R.id.user_name);
+        editText.setText(expectedNewName);
+
+        dialog.getButton(Dialog.BUTTON_POSITIVE).performClick();
+
+        verify(contentChangeCallback, times(1))
+                .onLabelChanged(any(), eq(expectedNewName));
+        verify(contentChangeCallback, times(0))
+                .onPhotoChanged(any(), any());
+        verify(dialogCompleteCallback, times(1)).onPositive();
+        verify(dialogCompleteCallback, times(0)).onNegativeOrCancel();
+    }
+
+    @Test
+    public void onContentChangedCallback_isCalled_whenPhotoChanges() {
+        EditUserInfoController.OnContentChangedCallback contentChangeCallback = mock(
+                EditUserInfoController.OnContentChangedCallback.class);
+
+        EditUserInfoController.OnDialogCompleteCallback dialogCompleteCallback = mock(
+                EditUserInfoController.OnDialogCompleteCallback.class);
+
+        AlertDialog dialog = (AlertDialog) mController.createDialog(
+                mFragment, mCurrentIcon, "test",
+                "title", contentChangeCallback,
+                android.os.Process.myUserHandle(),
+                dialogCompleteCallback);
+
+        // A different drawable.
+        Drawable newPhoto = mock(Drawable.class);
+        when(mController.getPhotoController().getNewUserPhotoDrawable()).thenReturn(newPhoto);
+
+        dialog.show();
+        dialog.getButton(Dialog.BUTTON_POSITIVE).performClick();
+
+        verify(contentChangeCallback, times(0))
+                .onLabelChanged(any(), any());
+        verify(contentChangeCallback, times(1))
+                .onPhotoChanged(any(), eq(newPhoto));
+        verify(dialogCompleteCallback, times(1)).onPositive();
+        verify(dialogCompleteCallback, times(0)).onNegativeOrCancel();
+    }
 }