1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.users;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.mockito.ArgumentMatchers.any;
22 import static org.mockito.ArgumentMatchers.eq;
23 import static org.mockito.ArgumentMatchers.same;
24 import static org.mockito.Mockito.mock;
25 import static org.mockito.Mockito.spy;
26 import static org.mockito.Mockito.times;
27 import static org.mockito.Mockito.verify;
28 import static org.mockito.Mockito.when;
29 
30 import android.app.Dialog;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.content.pm.UserInfo;
34 import android.graphics.drawable.Drawable;
35 import android.widget.EditText;
36 import android.widget.ImageView;
37 
38 import androidx.appcompat.app.AlertDialog;
39 import androidx.fragment.app.Fragment;
40 import androidx.fragment.app.FragmentActivity;
41 
42 import com.android.settings.R;
43 import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
44 
45 import org.junit.Before;
46 import org.junit.Test;
47 import org.junit.runner.RunWith;
48 import org.mockito.Answers;
49 import org.mockito.Mock;
50 import org.mockito.MockitoAnnotations;
51 import org.robolectric.RobolectricTestRunner;
52 import org.robolectric.android.controller.ActivityController;
53 import org.robolectric.annotation.Config;
54 
55 import java.util.stream.Collectors;
56 import java.util.stream.Stream;
57 
58 @RunWith(RobolectricTestRunner.class)
59 public class EditUserInfoControllerTest {
60     private static final int MAX_USER_NAME_LENGTH = 100;
61 
62     @Mock
63     private Fragment mFragment;
64     @Mock
65     private Drawable mCurrentIcon;
66 
67     private boolean mCanChangePhoto;
68 
69     private FragmentActivity mActivity;
70     private TestEditUserInfoController mController;
71 
72     public class TestEditUserInfoController extends EditUserInfoController {
73         private EditUserPhotoController mPhotoController;
74 
getPhotoController()75         private EditUserPhotoController getPhotoController() {
76             return mPhotoController;
77         }
78 
79         @Override
createEditUserPhotoController(Fragment fragment, ImageView userPhotoView, Drawable drawable)80         protected EditUserPhotoController createEditUserPhotoController(Fragment fragment,
81                 ImageView userPhotoView, Drawable drawable) {
82             mPhotoController = mock(EditUserPhotoController.class, Answers.RETURNS_DEEP_STUBS);
83             return mPhotoController;
84         }
85 
86         @Override
canChangePhoto(Context context, UserInfo user)87         boolean canChangePhoto(Context context, UserInfo user) {
88             return mCanChangePhoto;
89         }
90     }
91 
92     @Before
setUp()93     public void setUp() {
94         MockitoAnnotations.initMocks(this);
95         mActivity = spy(ActivityController.of(new FragmentActivity()).get());
96         when(mFragment.getActivity()).thenReturn(mActivity);
97         mController = new TestEditUserInfoController();
98         mCanChangePhoto = true;
99     }
100 
101     @Test
photoControllerOnActivityResult_whenWaiting_isCalled()102     public void photoControllerOnActivityResult_whenWaiting_isCalled() {
103         mController.createDialog(mFragment, mCurrentIcon, "test user",
104                 "title", null,
105                 android.os.Process.myUserHandle(), null);
106         mController.startingActivityForResult();
107         Intent resultData = new Intent();
108         mController.onActivityResult(0, 0, resultData);
109         EditUserPhotoController photoController = mController.getPhotoController();
110         assertThat(photoController).isNotNull();
111         verify(photoController).onActivityResult(eq(0), eq(0), same(resultData));
112     }
113 
114     @Test
115     @Config(shadows = ShadowAlertDialogCompat.class)
userNameView_inputLongName_shouldBeConstrained()116     public void userNameView_inputLongName_shouldBeConstrained() {
117         // generate a string of 200 'A's
118         final String longName = Stream.generate(
119                 () -> String.valueOf('A')).limit(200).collect(Collectors.joining());
120         final AlertDialog dialog = (AlertDialog) mController.createDialog(mFragment, mCurrentIcon,
121                 "test user", "title", null,
122                 android.os.Process.myUserHandle(), null);
123         final EditText userName = ShadowAlertDialogCompat.shadowOf(dialog).getView()
124                 .findViewById(R.id.user_name);
125 
126         userName.setText(longName);
127 
128         assertThat(userName.getText().length()).isEqualTo(MAX_USER_NAME_LENGTH);
129     }
130 
131     @Test
onDialogCompleteCallback_isCalled_whenCancelled()132     public void onDialogCompleteCallback_isCalled_whenCancelled() {
133         EditUserInfoController.OnContentChangedCallback contentChangeCallback = mock(
134                 EditUserInfoController.OnContentChangedCallback.class);
135 
136         EditUserInfoController.OnDialogCompleteCallback dialogCompleteCallback = mock(
137                 EditUserInfoController.OnDialogCompleteCallback.class);
138 
139         AlertDialog dialog = (AlertDialog) mController.createDialog(
140                 mFragment, mCurrentIcon, "test",
141                 "title", contentChangeCallback,
142                 android.os.Process.myUserHandle(),
143                 dialogCompleteCallback);
144 
145         dialog.show();
146         dialog.cancel();
147 
148         verify(contentChangeCallback, times(0))
149                 .onLabelChanged(any(), any());
150         verify(contentChangeCallback, times(0))
151                 .onPhotoChanged(any(), any());
152         verify(dialogCompleteCallback, times(0)).onPositive();
153         verify(dialogCompleteCallback, times(1)).onNegativeOrCancel();
154     }
155 
156     @Test
onDialogCompleteCallback_isCalled_whenPositiveClicked()157     public void onDialogCompleteCallback_isCalled_whenPositiveClicked() {
158         EditUserInfoController.OnContentChangedCallback contentChangeCallback = mock(
159                 EditUserInfoController.OnContentChangedCallback.class);
160 
161         EditUserInfoController.OnDialogCompleteCallback dialogCompleteCallback = mock(
162                 EditUserInfoController.OnDialogCompleteCallback.class);
163 
164         AlertDialog dialog = (AlertDialog) mController.createDialog(
165                 mFragment, mCurrentIcon, "test",
166                 "title", contentChangeCallback,
167                 android.os.Process.myUserHandle(),
168                 dialogCompleteCallback);
169 
170         // No change to the photo.
171         when(mController.getPhotoController().getNewUserPhotoDrawable()).thenReturn(mCurrentIcon);
172 
173         dialog.show();
174         dialog.getButton(Dialog.BUTTON_POSITIVE).performClick();
175 
176         verify(contentChangeCallback, times(0))
177                 .onLabelChanged(any(), any());
178         verify(contentChangeCallback, times(0))
179                 .onPhotoChanged(any(), any());
180         verify(dialogCompleteCallback, times(1)).onPositive();
181         verify(dialogCompleteCallback, times(0)).onNegativeOrCancel();
182     }
183 
184     @Test
onDialogCompleteCallback_isCalled_whenNegativeClicked()185     public void onDialogCompleteCallback_isCalled_whenNegativeClicked() {
186         EditUserInfoController.OnContentChangedCallback contentChangeCallback = mock(
187                 EditUserInfoController.OnContentChangedCallback.class);
188 
189         EditUserInfoController.OnDialogCompleteCallback dialogCompleteCallback = mock(
190                 EditUserInfoController.OnDialogCompleteCallback.class);
191 
192         AlertDialog dialog = (AlertDialog) mController.createDialog(
193                 mFragment, mCurrentIcon, "test",
194                 "title", contentChangeCallback,
195                 android.os.Process.myUserHandle(),
196                 dialogCompleteCallback);
197 
198         dialog.show();
199         dialog.getButton(Dialog.BUTTON_NEGATIVE).performClick();
200 
201         verify(contentChangeCallback, times(0))
202                 .onLabelChanged(any(), any());
203         verify(contentChangeCallback, times(0))
204                 .onPhotoChanged(any(), any());
205         verify(dialogCompleteCallback, times(0)).onPositive();
206         verify(dialogCompleteCallback, times(1)).onNegativeOrCancel();
207     }
208 
209     @Test
onContentChangedCallback_isCalled_whenLabelChanges()210     public void onContentChangedCallback_isCalled_whenLabelChanges() {
211         EditUserInfoController.OnContentChangedCallback contentChangeCallback = mock(
212                 EditUserInfoController.OnContentChangedCallback.class);
213 
214         EditUserInfoController.OnDialogCompleteCallback dialogCompleteCallback = mock(
215                 EditUserInfoController.OnDialogCompleteCallback.class);
216 
217         AlertDialog dialog = (AlertDialog) mController.createDialog(
218                 mFragment, mCurrentIcon, "test",
219                 "title", contentChangeCallback,
220                 android.os.Process.myUserHandle(),
221                 dialogCompleteCallback);
222 
223         // No change to the photo.
224         when(mController.getPhotoController().getNewUserPhotoDrawable()).thenReturn(mCurrentIcon);
225 
226         dialog.show();
227         String expectedNewName = "new test user";
228         EditText editText = (EditText) dialog.findViewById(R.id.user_name);
229         editText.setText(expectedNewName);
230 
231         dialog.getButton(Dialog.BUTTON_POSITIVE).performClick();
232 
233         verify(contentChangeCallback, times(1))
234                 .onLabelChanged(any(), eq(expectedNewName));
235         verify(contentChangeCallback, times(0))
236                 .onPhotoChanged(any(), any());
237         verify(dialogCompleteCallback, times(1)).onPositive();
238         verify(dialogCompleteCallback, times(0)).onNegativeOrCancel();
239     }
240 
241     @Test
onContentChangedCallback_isCalled_whenPhotoChanges()242     public void onContentChangedCallback_isCalled_whenPhotoChanges() {
243         EditUserInfoController.OnContentChangedCallback contentChangeCallback = mock(
244                 EditUserInfoController.OnContentChangedCallback.class);
245 
246         EditUserInfoController.OnDialogCompleteCallback dialogCompleteCallback = mock(
247                 EditUserInfoController.OnDialogCompleteCallback.class);
248 
249         AlertDialog dialog = (AlertDialog) mController.createDialog(
250                 mFragment, mCurrentIcon, "test",
251                 "title", contentChangeCallback,
252                 android.os.Process.myUserHandle(),
253                 dialogCompleteCallback);
254 
255         // A different drawable.
256         Drawable newPhoto = mock(Drawable.class);
257         when(mController.getPhotoController().getNewUserPhotoDrawable()).thenReturn(newPhoto);
258 
259         dialog.show();
260         dialog.getButton(Dialog.BUTTON_POSITIVE).performClick();
261 
262         verify(contentChangeCallback, times(0))
263                 .onLabelChanged(any(), any());
264         verify(contentChangeCallback, times(1))
265                 .onPhotoChanged(any(), eq(newPhoto));
266         verify(dialogCompleteCallback, times(1)).onPositive();
267         verify(dialogCompleteCallback, times(0)).onNegativeOrCancel();
268     }
269 
270     @Test
createDialog_canNotChangePhoto_nullPhotoController()271     public void createDialog_canNotChangePhoto_nullPhotoController() {
272         mCanChangePhoto = false;
273 
274         mController.createDialog(
275                 mFragment, mCurrentIcon, "test",
276                 "title", null,
277                 android.os.Process.myUserHandle(),
278                 null);
279 
280         assertThat(mController.mPhotoController).isNull();
281     }
282 }
283