1 /*
2  * Copyright (C) 2020 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.documentsui;
18 
19 import static org.junit.Assert.assertNotNull;
20 import static org.junit.Assert.assertNull;
21 import static org.junit.Assert.assertTrue;
22 import static org.junit.Assert.fail;
23 
24 import android.app.Activity;
25 import android.app.Instrumentation;
26 import android.app.UiAutomation;
27 import android.content.Intent;
28 import android.content.pm.ActivityInfo;
29 import android.graphics.Paint;
30 import android.os.Build;
31 import android.os.ParcelFileDescriptor;
32 
33 import androidx.fragment.app.FragmentManager;
34 import androidx.test.filters.LargeTest;
35 import androidx.test.platform.app.InstrumentationRegistry;
36 import androidx.test.rule.ActivityTestRule;
37 import androidx.test.runner.AndroidJUnit4;
38 
39 import com.android.documentsui.base.DocumentInfo;
40 import com.android.documentsui.dirlist.RenameDocumentFragment;
41 import com.android.documentsui.files.DeleteDocumentFragment;
42 import com.android.documentsui.files.FilesActivity;
43 import com.android.documentsui.queries.SearchFragment;
44 import com.android.documentsui.sorting.SortListFragment;
45 import com.android.documentsui.sorting.SortModel;
46 
47 import com.google.android.material.textfield.TextInputEditText;
48 
49 import org.junit.After;
50 import org.junit.Before;
51 import org.junit.Rule;
52 import org.junit.Test;
53 import org.junit.runner.RunWith;
54 import org.mockito.Mockito;
55 
56 import java.io.FileInputStream;
57 import java.io.IOException;
58 
59 @LargeTest
60 @RunWith(AndroidJUnit4.class)
61 public class DialogUiTest {
62 
63     private static final String CREATE_FRAGEMENT_TAG = "create_directory";
64     CreateDirectoryFragment mCreateDirectoryFragment;
65     FragmentManager mFragmentManager;
66     ScreenDensitySession mScreenDensitySession;
67     Intent mFileActivityIntent;
68 
69     @Rule
70     public ActivityTestRule<FilesActivity> mActivityTestRule = new ActivityTestRule<>(
71             FilesActivity.class);
72 
73     @Before
setup()74     public void setup() {
75         mFileActivityIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
76         mFragmentManager = mActivityTestRule.getActivity().getSupportFragmentManager();
77         mScreenDensitySession = new ScreenDensitySession();
78     }
79 
80     @After
tearDown()81     public void tearDown() {
82         mScreenDensitySession.close();
83         mCreateDirectoryFragment = null;
84     }
85 
86     @Test
testCreateDialogShows()87     public void testCreateDialogShows() throws Throwable {
88         mActivityTestRule.runOnUiThread(() -> CreateDirectoryFragment.show(mFragmentManager));
89         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
90         mCreateDirectoryFragment =
91                 (CreateDirectoryFragment) mFragmentManager.findFragmentByTag(CREATE_FRAGEMENT_TAG);
92 
93         assertNotNull("Dialog was null", mCreateDirectoryFragment.getDialog());
94         assertTrue("Dialog was not being shown", mCreateDirectoryFragment.getDialog().isShowing());
95     }
96 
97     @Test
testCreateDialogShowsDismiss()98     public void testCreateDialogShowsDismiss() throws Throwable {
99         mActivityTestRule.runOnUiThread(() -> CreateDirectoryFragment.show(mFragmentManager));
100         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
101         mCreateDirectoryFragment =
102                 (CreateDirectoryFragment) mFragmentManager.findFragmentByTag(CREATE_FRAGEMENT_TAG);
103 
104         assertNotNull("Dialog was null", mCreateDirectoryFragment.getDialog());
105         assertTrue("Dialog was not being shown", mCreateDirectoryFragment.getDialog().isShowing());
106 
107         mActivityTestRule.runOnUiThread(() -> mCreateDirectoryFragment.getDialog().dismiss());
108         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
109 
110         assertNull("Dialog should be null after dismiss()", mCreateDirectoryFragment.getDialog());
111     }
112 
113     @Test
testCreateDialogShows_textInputEditText_shouldNotTruncateOnPortrait()114     public void testCreateDialogShows_textInputEditText_shouldNotTruncateOnPortrait()
115             throws Throwable {
116         mActivityTestRule.runOnUiThread(() -> CreateDirectoryFragment.show(mFragmentManager));
117         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
118         mCreateDirectoryFragment =
119                 (CreateDirectoryFragment) mFragmentManager.findFragmentByTag(CREATE_FRAGEMENT_TAG);
120 
121         final TextInputEditText inputView =
122                 mCreateDirectoryFragment.getDialog().findViewById(android.R.id.text1);
123 
124         assertTrue(inputView.getHeight() > getInputTextHeight(inputView));
125     }
126 
127     @Test
testCreateDialog_textInputEditText_shouldNotTruncateOnLargeDensity()128     public void testCreateDialog_textInputEditText_shouldNotTruncateOnLargeDensity()
129             throws Throwable {
130 
131         mScreenDensitySession.setLargeDensity();
132         mActivityTestRule.finishActivity();
133         mActivityTestRule.launchActivity(mFileActivityIntent);
134         mFragmentManager = mActivityTestRule.getActivity().getSupportFragmentManager();
135 
136         mActivityTestRule.runOnUiThread(() -> CreateDirectoryFragment.show(mFragmentManager));
137         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
138         mCreateDirectoryFragment =
139                 (CreateDirectoryFragment) mFragmentManager.findFragmentByTag(CREATE_FRAGEMENT_TAG);
140 
141         final TextInputEditText inputView =
142                 mCreateDirectoryFragment.getDialog().getWindow().findViewById(android.R.id.text1);
143 
144         assertTrue(inputView.getHeight() > getInputTextHeight(inputView));
145 
146     }
147 
148     @Test
testCreateDialog_textInputEditText_shouldNotTruncateOnLargerDensity()149     public void testCreateDialog_textInputEditText_shouldNotTruncateOnLargerDensity()
150             throws Throwable {
151 
152         mScreenDensitySession.setLargerDensity();
153         mActivityTestRule.finishActivity();
154         mActivityTestRule.launchActivity(mFileActivityIntent);
155         mFragmentManager = mActivityTestRule.getActivity().getSupportFragmentManager();
156 
157         mActivityTestRule.runOnUiThread(() -> CreateDirectoryFragment.show(mFragmentManager));
158         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
159         mCreateDirectoryFragment =
160                 (CreateDirectoryFragment) mFragmentManager.findFragmentByTag(CREATE_FRAGEMENT_TAG);
161 
162         final TextInputEditText inputView =
163                 mCreateDirectoryFragment.getDialog().getWindow().findViewById(android.R.id.text1);
164 
165         assertTrue(inputView.getHeight() > getInputTextHeight(inputView));
166     }
167 
168     @Test
testCreateDialog_textInputEditText_shouldNotTruncateOnLargestDensity()169     public void testCreateDialog_textInputEditText_shouldNotTruncateOnLargestDensity()
170             throws Throwable {
171 
172         mScreenDensitySession.setLargestDensity();
173         mActivityTestRule.finishActivity();
174         mActivityTestRule.launchActivity(mFileActivityIntent);
175         mFragmentManager = mActivityTestRule.getActivity().getSupportFragmentManager();
176 
177         mActivityTestRule.runOnUiThread(() -> CreateDirectoryFragment.show(mFragmentManager));
178         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
179         mCreateDirectoryFragment =
180                 (CreateDirectoryFragment) mFragmentManager.findFragmentByTag(CREATE_FRAGEMENT_TAG);
181 
182         final TextInputEditText inputView =
183                 mCreateDirectoryFragment.getDialog().getWindow().findViewById(android.R.id.text1);
184 
185         assertTrue(inputView.getHeight() > getInputTextHeight(inputView));
186     }
187 
188     @Test
testCreateDirectoryFragmentShows_textInputEditText_shouldNotTruncateOnLandscape()189     public void testCreateDirectoryFragmentShows_textInputEditText_shouldNotTruncateOnLandscape()
190             throws Throwable {
191         switchOrientation(mActivityTestRule.getActivity());
192         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
193         mScreenDensitySession.setLargestDensity();
194         mActivityTestRule.finishActivity();
195         mActivityTestRule.launchActivity(mFileActivityIntent);
196         mFragmentManager = mActivityTestRule.getActivity().getSupportFragmentManager();
197 
198         mActivityTestRule.runOnUiThread(() -> CreateDirectoryFragment.show(mFragmentManager));
199         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
200         mCreateDirectoryFragment =
201                 (CreateDirectoryFragment) mFragmentManager.findFragmentByTag(CREATE_FRAGEMENT_TAG);
202 
203         final TextInputEditText inputView =
204                 mCreateDirectoryFragment.getDialog().getWindow().findViewById(android.R.id.text1);
205 
206         assertTrue(
207                 "Failed with inputView height " + inputView.getHeight() + " and input text height "
208                         + getInputTextHeight(inputView),
209                 inputView.getHeight() > getInputTextHeight(inputView));
210 
211         switchOrientation(mActivityTestRule.getActivity());
212     }
213 
214     @Test
testCreateDirectoryFragmentShows_skipWhenStateSaved()215     public void testCreateDirectoryFragmentShows_skipWhenStateSaved() {
216         mFragmentManager = Mockito.mock(FragmentManager.class);
217         Mockito.when(mFragmentManager.isStateSaved()).thenReturn(true);
218 
219         // Use mock FragmentManager will cause NPE then test fail when DialogFragment.show is
220         // called, so test pass means it skip.
221         CreateDirectoryFragment.show(mFragmentManager);
222     }
223 
224     @Test
testDeleteDocumentFragmentShows_skipWhenStateSaved()225     public void testDeleteDocumentFragmentShows_skipWhenStateSaved() {
226         mFragmentManager = Mockito.mock(FragmentManager.class);
227         Mockito.when(mFragmentManager.isStateSaved()).thenReturn(true);
228 
229         DeleteDocumentFragment.show(mFragmentManager, null, null);
230     }
231 
232     @Test
testRenameDocumentFragmentShows_skipWhenStateSaved()233     public void testRenameDocumentFragmentShows_skipWhenStateSaved() {
234         mFragmentManager = Mockito.mock(FragmentManager.class);
235         Mockito.when(mFragmentManager.isStateSaved()).thenReturn(true);
236 
237         RenameDocumentFragment.show(mFragmentManager, new DocumentInfo());
238     }
239 
240     @Test
testSearchFragmentShows_skipWhenStateSaved()241     public void testSearchFragmentShows_skipWhenStateSaved() {
242         mFragmentManager = Mockito.mock(FragmentManager.class);
243         Mockito.when(mFragmentManager.isStateSaved()).thenReturn(true);
244 
245         SearchFragment.showFragment(mFragmentManager, "");
246     }
247 
248     @Test
testSortListFragmentShows_skipWhenStateSaved()249     public void testSortListFragmentShows_skipWhenStateSaved() {
250         mFragmentManager = Mockito.mock(FragmentManager.class);
251         Mockito.when(mFragmentManager.isStateSaved()).thenReturn(true);
252         SortModel sortModel = Mockito.mock(SortModel.class);
253 
254         SortListFragment.show(mFragmentManager, sortModel);
255     }
256 
getInputTextHeight(TextInputEditText v)257     private static int getInputTextHeight(TextInputEditText v) {
258         Paint paint = v.getPaint();
259         final float textSize = paint.getTextSize();
260         final float textSpace = paint.getFontSpacing();
261         return Math.round(textSize + textSpace);
262     }
263 
switchOrientation(final Activity activity)264     private static void switchOrientation(final Activity activity) {
265         final int[] orientations = getOrientations(activity);
266         activity.setRequestedOrientation(orientations[1]);
267     }
268 
getOrientations(final Activity activity)269     private static int[] getOrientations(final Activity activity) {
270         final int originalOrientation = activity.getResources().getConfiguration().orientation;
271         final int newOrientation = originalOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
272                 ? ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
273                 : ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
274         return new int[]{originalOrientation, newOrientation};
275     }
276 
277     private static class ScreenDensitySession implements AutoCloseable {
278         private static final String DENSITY_PROP_DEVICE = "ro.sf.lcd_density";
279         private static final String DENSITY_PROP_EMULATOR = "qemu.sf.lcd_density";
280 
281         private static final float DENSITY_DEFAULT = 1f;
282         private static final float DENSITY_LARGE = 1.09f;
283         private static final float DENSITY_LARGER = 1.19f;
284         private static final float DENSITY_LARGEST = 1.29f;
285 
setDefaultDensity()286         void setDefaultDensity() {
287             final int stableDensity = getStableDensity();
288             final int targetDensity = (int) (stableDensity * DENSITY_DEFAULT);
289             setDensity(targetDensity);
290         }
291 
setLargeDensity()292         void setLargeDensity() {
293             final int stableDensity = getStableDensity();
294             final int targetDensity = (int) (stableDensity * DENSITY_LARGE);
295             setDensity(targetDensity);
296         }
297 
setLargerDensity()298         void setLargerDensity() {
299             final int stableDensity = getStableDensity();
300             final int targetDensity = (int) (stableDensity * DENSITY_LARGER);
301             setDensity(targetDensity);
302         }
303 
setLargestDensity()304         void setLargestDensity() {
305             final int stableDensity = getStableDensity();
306             final int targetDensity = (int) (stableDensity * DENSITY_LARGEST);
307             setDensity(targetDensity);
308         }
309 
310         @Override
close()311         public void close() {
312             resetDensity();
313         }
314 
getStableDensity()315         private int getStableDensity() {
316             final String densityProp;
317             if (Build.IS_EMULATOR) {
318                 densityProp = DENSITY_PROP_EMULATOR;
319             } else {
320                 densityProp = DENSITY_PROP_DEVICE;
321             }
322 
323             return Integer.parseInt(executeShellCommand("getprop " + densityProp).trim());
324         }
325 
setDensity(int targetDensity)326         private void setDensity(int targetDensity) {
327             executeShellCommand("wm density " + targetDensity);
328 
329             // Verify that the density is changed.
330             final String output = executeShellCommand("wm density");
331             final boolean success = output.contains("Override density: " + targetDensity);
332 
333             assertTrue("Failed to set density to " + targetDensity, success);
334         }
335 
resetDensity()336         private void resetDensity() {
337             executeShellCommand("wm density reset");
338         }
339     }
340 
executeShellCommand(String cmd)341     public static String executeShellCommand(String cmd) {
342         try {
343             return runShellCommand(InstrumentationRegistry.getInstrumentation(), cmd);
344         } catch (IOException e) {
345             fail("Failed reading command output: " + e);
346             return "";
347         }
348     }
349 
runShellCommand(Instrumentation instrumentation, String cmd)350     public static String runShellCommand(Instrumentation instrumentation, String cmd)
351             throws IOException {
352         return runShellCommand(instrumentation.getUiAutomation(), cmd);
353     }
354 
runShellCommand(UiAutomation automation, String cmd)355     public static String runShellCommand(UiAutomation automation, String cmd)
356             throws IOException {
357         if (cmd.startsWith("pm grant ") || cmd.startsWith("pm revoke ")) {
358             throw new UnsupportedOperationException("Use UiAutomation.grantRuntimePermission() "
359                     + "or revokeRuntimePermission() directly, which are more robust.");
360         }
361         ParcelFileDescriptor pfd = automation.executeShellCommand(cmd);
362         byte[] buf = new byte[512];
363         int bytesRead;
364         FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
365         StringBuffer stdout = new StringBuffer();
366         while ((bytesRead = fis.read(buf)) != -1) {
367             stdout.append(new String(buf, 0, bytesRead));
368         }
369         fis.close();
370         return stdout.toString();
371     }
372 }
373