1 /*
2  * Copyright (C) 2019 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.MediumTest;
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.files.FilesActivity;
40 
41 import com.google.android.material.textfield.TextInputEditText;
42 
43 import org.junit.After;
44 import org.junit.Before;
45 import org.junit.Rule;
46 import org.junit.Test;
47 import org.junit.runner.RunWith;
48 
49 import java.io.FileInputStream;
50 import java.io.IOException;
51 
52 @MediumTest
53 @RunWith(AndroidJUnit4.class)
54 public class DialogFragmentTest {
55 
56     private static final String CREATE_FRAGEMENT_TAG = "create_directory";
57     CreateDirectoryFragment mCreateDirectoryFragment;
58     FragmentManager mFragmentManager;
59     ScreenDensitySession mScreenDensitySession;
60     Intent mFileActivityIntent;
61 
62     @Rule
63     public ActivityTestRule<FilesActivity> mActivityTestRule = new ActivityTestRule<>(
64             FilesActivity.class);
65 
66     @Before
setup()67     public void setup() {
68         mFileActivityIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
69         mFragmentManager = mActivityTestRule.getActivity().getSupportFragmentManager();
70         mScreenDensitySession = new ScreenDensitySession();
71     }
72 
73     @After
tearDown()74     public void tearDown() {
75         mScreenDensitySession.close();
76         mCreateDirectoryFragment = null;
77     }
78 
79     @Test
testCreateDialogShows()80     public void testCreateDialogShows() throws Throwable {
81         mActivityTestRule.runOnUiThread(() -> CreateDirectoryFragment.show(mFragmentManager));
82         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
83         mCreateDirectoryFragment =
84                 (CreateDirectoryFragment) mFragmentManager.findFragmentByTag(CREATE_FRAGEMENT_TAG);
85 
86         assertNotNull("Dialog was null", mCreateDirectoryFragment.getDialog());
87         assertTrue("Dialog was not being shown", mCreateDirectoryFragment.getDialog().isShowing());
88     }
89 
90     @Test
testCreateDialogShowsDismiss()91     public void testCreateDialogShowsDismiss() throws Throwable {
92         mActivityTestRule.runOnUiThread(() -> CreateDirectoryFragment.show(mFragmentManager));
93         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
94         mCreateDirectoryFragment =
95                 (CreateDirectoryFragment) mFragmentManager.findFragmentByTag(CREATE_FRAGEMENT_TAG);
96 
97         assertNotNull("Dialog was null", mCreateDirectoryFragment.getDialog());
98         assertTrue("Dialog was not being shown", mCreateDirectoryFragment.getDialog().isShowing());
99 
100         mActivityTestRule.runOnUiThread(() -> mCreateDirectoryFragment.dismiss());
101         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
102 
103         assertNull("Dialog should be null after dismiss()", mCreateDirectoryFragment.getDialog());
104     }
105 
106     @Test
testCreateDialogShows_textInputEditText_shouldNotTruncateOnPortrait()107     public void testCreateDialogShows_textInputEditText_shouldNotTruncateOnPortrait()
108             throws Throwable {
109         mActivityTestRule.runOnUiThread(() -> CreateDirectoryFragment.show(mFragmentManager));
110         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
111         mCreateDirectoryFragment =
112                 (CreateDirectoryFragment) mFragmentManager.findFragmentByTag(CREATE_FRAGEMENT_TAG);
113 
114         final TextInputEditText inputView =
115                 mCreateDirectoryFragment.getDialog().findViewById(android.R.id.text1);
116 
117         assertTrue(inputView.getHeight() > getInputTextHeight(inputView));
118     }
119 
120     @Test
testCreateDialog_textInputEditText_shouldNotTruncateOnLargeDensity()121     public void testCreateDialog_textInputEditText_shouldNotTruncateOnLargeDensity()
122             throws Throwable {
123 
124         mScreenDensitySession.setLargeDensity();
125         mActivityTestRule.finishActivity();
126         mActivityTestRule.launchActivity(mFileActivityIntent);
127         mFragmentManager = mActivityTestRule.getActivity().getSupportFragmentManager();
128 
129         mActivityTestRule.runOnUiThread(() -> CreateDirectoryFragment.show(mFragmentManager));
130         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
131         mCreateDirectoryFragment =
132                 (CreateDirectoryFragment) mFragmentManager.findFragmentByTag(CREATE_FRAGEMENT_TAG);
133 
134         final TextInputEditText inputView =
135                 mCreateDirectoryFragment.getDialog().getWindow().findViewById(android.R.id.text1);
136 
137         assertTrue(inputView.getHeight() > getInputTextHeight(inputView));
138 
139     }
140 
141     @Test
testCreateDialog_textInputEditText_shouldNotTruncateOnLargerDensity()142     public void testCreateDialog_textInputEditText_shouldNotTruncateOnLargerDensity()
143             throws Throwable {
144 
145         mScreenDensitySession.setLargerDensity();
146         mActivityTestRule.finishActivity();
147         mActivityTestRule.launchActivity(mFileActivityIntent);
148         mFragmentManager = mActivityTestRule.getActivity().getSupportFragmentManager();
149 
150         mActivityTestRule.runOnUiThread(() -> CreateDirectoryFragment.show(mFragmentManager));
151         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
152         mCreateDirectoryFragment =
153                 (CreateDirectoryFragment) mFragmentManager.findFragmentByTag(CREATE_FRAGEMENT_TAG);
154 
155         final TextInputEditText inputView =
156                 mCreateDirectoryFragment.getDialog().getWindow().findViewById(android.R.id.text1);
157 
158         assertTrue(inputView.getHeight() > getInputTextHeight(inputView));
159     }
160 
161     @Test
testCreateDialog_textInputEditText_shouldNotTruncateOnLargestDensity()162     public void testCreateDialog_textInputEditText_shouldNotTruncateOnLargestDensity()
163             throws Throwable {
164 
165         mScreenDensitySession.setLargestDensity();
166         mActivityTestRule.finishActivity();
167         mActivityTestRule.launchActivity(mFileActivityIntent);
168         mFragmentManager = mActivityTestRule.getActivity().getSupportFragmentManager();
169 
170         mActivityTestRule.runOnUiThread(() -> CreateDirectoryFragment.show(mFragmentManager));
171         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
172         mCreateDirectoryFragment =
173                 (CreateDirectoryFragment) mFragmentManager.findFragmentByTag(CREATE_FRAGEMENT_TAG);
174 
175         final TextInputEditText inputView =
176                 mCreateDirectoryFragment.getDialog().getWindow().findViewById(android.R.id.text1);
177 
178         assertTrue(inputView.getHeight() > getInputTextHeight(inputView));
179     }
180 
181     @Test
testCreateDirectoryFragmentShows_textInputEditText_shouldNotTruncateOnLandscape()182     public void testCreateDirectoryFragmentShows_textInputEditText_shouldNotTruncateOnLandscape()
183             throws Throwable {
184         switchOrientation(mActivityTestRule.getActivity());
185         mScreenDensitySession.setLargestDensity();
186         mActivityTestRule.finishActivity();
187         mActivityTestRule.launchActivity(mFileActivityIntent);
188         mFragmentManager = mActivityTestRule.getActivity().getSupportFragmentManager();
189 
190         mActivityTestRule.runOnUiThread(() -> CreateDirectoryFragment.show(mFragmentManager));
191         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
192         mCreateDirectoryFragment =
193                 (CreateDirectoryFragment) mFragmentManager.findFragmentByTag(CREATE_FRAGEMENT_TAG);
194 
195         final TextInputEditText inputView =
196                 mCreateDirectoryFragment.getDialog().getWindow().findViewById(android.R.id.text1);
197         Paint paint = inputView.getPaint();
198         final float textSize = paint.getTextSize();
199 
200         assertTrue(inputView.getHeight() > Math.round(textSize));
201 
202         switchOrientation(mActivityTestRule.getActivity());
203     }
204 
getInputTextHeight(TextInputEditText v)205     private static int getInputTextHeight(TextInputEditText v) {
206         Paint paint = v.getPaint();
207         final float textSize = paint.getTextSize();
208         final float textSpace = paint.getFontSpacing();
209         return Math.round(textSize + textSpace);
210     }
211 
switchOrientation(final Activity activity)212     private static void switchOrientation(final Activity activity) {
213         final int[] orientations = getOrientations(activity);
214         activity.setRequestedOrientation(orientations[1]);
215     }
216 
getOrientations(final Activity activity)217     private static int[] getOrientations(final Activity activity) {
218         final int originalOrientation = activity.getResources().getConfiguration().orientation;
219         final int newOrientation = originalOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
220                 ? ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
221                 : ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
222         return new int[]{originalOrientation, newOrientation};
223     }
224 
225     private static class ScreenDensitySession implements AutoCloseable {
226         private static final String DENSITY_PROP_DEVICE = "ro.sf.lcd_density";
227         private static final String DENSITY_PROP_EMULATOR = "qemu.sf.lcd_density";
228 
229         private static final float DENSITY_DEFAULT = 1f;
230         private static final float DENSITY_LARGE = 1.09f;
231         private static final float DENSITY_LARGER = 1.19f;
232         private static final float DENSITY_LARGEST = 1.29f;
233 
setDefaultDensity()234         void setDefaultDensity() {
235             final int stableDensity = getStableDensity();
236             final int targetDensity = (int) (stableDensity * DENSITY_DEFAULT);
237             setDensity(targetDensity);
238         }
239 
setLargeDensity()240         void setLargeDensity() {
241             final int stableDensity = getStableDensity();
242             final int targetDensity = (int) (stableDensity * DENSITY_LARGE);
243             setDensity(targetDensity);
244         }
245 
setLargerDensity()246         void setLargerDensity() {
247             final int stableDensity = getStableDensity();
248             final int targetDensity = (int) (stableDensity * DENSITY_LARGER);
249             setDensity(targetDensity);
250         }
251 
setLargestDensity()252         void setLargestDensity() {
253             final int stableDensity = getStableDensity();
254             final int targetDensity = (int) (stableDensity * DENSITY_LARGEST);
255             setDensity(targetDensity);
256         }
257 
258         @Override
close()259         public void close() {
260             resetDensity();
261         }
262 
getStableDensity()263         private int getStableDensity() {
264             final String densityProp;
265             if (Build.IS_EMULATOR) {
266                 densityProp = DENSITY_PROP_EMULATOR;
267             } else {
268                 densityProp = DENSITY_PROP_DEVICE;
269             }
270 
271             return Integer.parseInt(executeShellCommand("getprop " + densityProp).trim());
272         }
273 
setDensity(int targetDensity)274         private void setDensity(int targetDensity) {
275             executeShellCommand("wm density " + targetDensity);
276 
277             // Verify that the density is changed.
278             final String output = executeShellCommand("wm density");
279             final boolean success = output.contains("Override density: " + targetDensity);
280 
281             assertTrue("Failed to set density to " + targetDensity, success);
282         }
283 
resetDensity()284         private void resetDensity() {
285             executeShellCommand("wm density reset");
286         }
287     }
288 
executeShellCommand(String cmd)289     public static String executeShellCommand(String cmd) {
290         try {
291             return runShellCommand(InstrumentationRegistry.getInstrumentation(), cmd);
292         } catch (IOException e) {
293             fail("Failed reading command output: " + e);
294             return "";
295         }
296     }
297 
runShellCommand(Instrumentation instrumentation, String cmd)298     public static String runShellCommand(Instrumentation instrumentation, String cmd)
299             throws IOException {
300         return runShellCommand(instrumentation.getUiAutomation(), cmd);
301     }
302 
runShellCommand(UiAutomation automation, String cmd)303     public static String runShellCommand(UiAutomation automation, String cmd)
304             throws IOException {
305         if (cmd.startsWith("pm grant ") || cmd.startsWith("pm revoke ")) {
306             throw new UnsupportedOperationException("Use UiAutomation.grantRuntimePermission() "
307                     + "or revokeRuntimePermission() directly, which are more robust.");
308         }
309         ParcelFileDescriptor pfd = automation.executeShellCommand(cmd);
310         byte[] buf = new byte[512];
311         int bytesRead;
312         FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
313         StringBuffer stdout = new StringBuffer();
314         while ((bytesRead = fis.read(buf)) != -1) {
315             stdout.append(new String(buf, 0, bytesRead));
316         }
317         fis.close();
318         return stdout.toString();
319     }
320 }
321