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