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