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