summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--common/device-side/util-axt/Android.bp1
-rw-r--r--common/device-side/util-axt/src/com/android/compatibility/common/util/UiAutomatorUtils.java3
-rw-r--r--common/device-side/util-axt/src/com/android/compatibility/common/util/UiAutomatorUtils2.java167
-rw-r--r--hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java14
-rw-r--r--hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java66
-rw-r--r--tests/app/Android.bp5
6 files changed, 224 insertions, 32 deletions
diff --git a/common/device-side/util-axt/Android.bp b/common/device-side/util-axt/Android.bp
index 11f851b78dd..720a74055b0 100644
--- a/common/device-side/util-axt/Android.bp
+++ b/common/device-side/util-axt/Android.bp
@@ -33,6 +33,7 @@ java_library_static {
"ub-uiautomator",
"mockito-target-minus-junit4",
"androidx.annotation_annotation",
+ "androidx.test.uiautomator_uiautomator",
"truth-prebuilt",
"modules-utils-build_system",
],
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/UiAutomatorUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/UiAutomatorUtils.java
index a9543c246ab..a162b9c9626 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/UiAutomatorUtils.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/UiAutomatorUtils.java
@@ -36,6 +36,9 @@ import androidx.test.core.app.ApplicationProvider;
import java.util.regex.Pattern;
+/**
+ * @deprecated , Use {@link UiAutomatorUtils2}, which uses latest androidx automator classes.
+ */
public class UiAutomatorUtils {
private UiAutomatorUtils() {}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/UiAutomatorUtils2.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/UiAutomatorUtils2.java
new file mode 100644
index 00000000000..b677dbf3449
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/UiAutomatorUtils2.java
@@ -0,0 +1,167 @@
+package com.android.compatibility.common.util;
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+import static org.junit.Assert.assertNotNull;
+
+import android.graphics.Rect;
+import android.util.Log;
+import android.util.TypedValue;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.StaleObjectException;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.UiObjectNotFoundException;
+import androidx.test.uiautomator.UiScrollable;
+import androidx.test.uiautomator.UiSelector;
+import androidx.test.uiautomator.Until;
+
+import java.util.regex.Pattern;
+
+public class UiAutomatorUtils2 {
+ private UiAutomatorUtils2() {}
+
+ private static final String LOG_TAG = "UiAutomatorUtils";
+
+ /** Default swipe deadzone percentage. See {@link UiScrollable}. */
+ private static final double DEFAULT_SWIPE_DEADZONE_PCT = 0.1;
+
+ /** Minimum view height accepted (before needing to scroll more). */
+ private static final float MIN_VIEW_HEIGHT_DP = 8;
+
+ private static Pattern sCollapsingToolbarResPattern =
+ Pattern.compile(".*:id/collapsing_toolbar");
+
+ public static UiDevice getUiDevice() {
+ return UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ }
+
+ public static UiObject2 waitFindObject(BySelector selector) throws UiObjectNotFoundException {
+ return waitFindObject(selector, 20_000);
+ }
+
+ public static UiObject2 waitFindObject(BySelector selector, long timeoutMs)
+ throws UiObjectNotFoundException {
+ final UiObject2 view = waitFindObjectOrNull(selector, timeoutMs);
+ ExceptionUtils.wrappingExceptions(UiDumpUtils::wrapWithUiDump, () -> {
+ assertNotNull("View not found after waiting for " + timeoutMs + "ms: " + selector,
+ view);
+ });
+ return view;
+ }
+
+ public static UiObject2 waitFindObjectOrNull(BySelector selector)
+ throws UiObjectNotFoundException {
+ return waitFindObjectOrNull(selector, 20_000);
+ }
+
+ private static int convertDpToPx(float dp) {
+ return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
+ ApplicationProvider.getApplicationContext().getResources().getDisplayMetrics()));
+ }
+
+ public static UiObject2 waitFindObjectOrNull(BySelector selector, long timeoutMs)
+ throws UiObjectNotFoundException {
+ UiObject2 view = null;
+ long start = System.currentTimeMillis();
+
+ boolean isAtEnd = false;
+ boolean wasScrolledUpAlready = false;
+ boolean scrolledPastCollapsibleToolbar = false;
+
+ final int minViewHeightPx = convertDpToPx(MIN_VIEW_HEIGHT_DP);
+
+ while (view == null && start + timeoutMs > System.currentTimeMillis()) {
+ try {
+ view = getUiDevice().wait(Until.findObject(selector), 1000);
+ } catch (StaleObjectException exception) {
+ // UiDevice.wait() may cause StaleObjectException if the {@link View} attached to
+ // UiObject2 is no longer in the view tree.
+ Log.v(LOG_TAG, "UiObject2 view is no longer in the view tree.", exception);
+ getUiDevice().waitForIdle();
+ continue;
+ }
+
+ if (view == null || view.getVisibleBounds().height() < minViewHeightPx) {
+ final double deadZone = !(FeatureUtil.isWatch() || FeatureUtil.isTV())
+ ? 0.25 : DEFAULT_SWIPE_DEADZONE_PCT;
+ UiScrollable scrollable = new UiScrollable(new UiSelector().scrollable(true));
+ scrollable.setSwipeDeadZonePercentage(deadZone);
+ if (scrollable.exists()) {
+ if (!scrolledPastCollapsibleToolbar) {
+ scrollPastCollapsibleToolbar(scrollable, deadZone);
+ scrolledPastCollapsibleToolbar = true;
+ continue;
+ }
+ if (isAtEnd) {
+ if (wasScrolledUpAlready) {
+ return null;
+ }
+ scrollable.scrollToBeginning(Integer.MAX_VALUE);
+ isAtEnd = false;
+ wasScrolledUpAlready = true;
+ scrolledPastCollapsibleToolbar = false;
+ } else {
+ Rect boundsBeforeScroll = scrollable.getBounds();
+ boolean scrollAtStartOrEnd = !scrollable.scrollForward();
+ // The scrollable view may no longer be scrollable after the toolbar is
+ // collapsed.
+ if (scrollable.exists()) {
+ Rect boundsAfterScroll = scrollable.getBounds();
+ isAtEnd = scrollAtStartOrEnd && boundsBeforeScroll.equals(
+ boundsAfterScroll);
+ } else {
+ isAtEnd = scrollAtStartOrEnd;
+ }
+ }
+ } else {
+ // There might be a collapsing toolbar, but no scrollable view. Try to collapse
+ scrollPastCollapsibleToolbar(null, deadZone);
+ }
+ }
+ }
+ return view;
+ }
+
+ private static void scrollPastCollapsibleToolbar(UiScrollable scrollable, double deadZone)
+ throws UiObjectNotFoundException {
+ final UiObject2 collapsingToolbar = getUiDevice().findObject(
+ By.res(sCollapsingToolbarResPattern));
+ if (collapsingToolbar == null) {
+ return;
+ }
+
+ final int steps = 55; // == UiScrollable.SCROLL_STEPS
+ if (scrollable != null && scrollable.exists()) {
+ final Rect scrollableBounds = scrollable.getVisibleBounds();
+ final int distanceToSwipe = collapsingToolbar.getVisibleBounds().height() / 2;
+ getUiDevice().drag(scrollableBounds.centerX(), scrollableBounds.centerY(),
+ scrollableBounds.centerX(), scrollableBounds.centerY() - distanceToSwipe,
+ steps);
+ } else {
+ // There might be a collapsing toolbar, but no scrollable view. Try to collapse
+ int maxY = getUiDevice().getDisplayHeight();
+ int minY = (int) (deadZone * maxY);
+ maxY -= minY;
+ getUiDevice().drag(0, maxY, 0, minY, steps);
+ }
+ }
+}
diff --git a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
index f5c6af5b4a0..36e889d30b8 100644
--- a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
+++ b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
@@ -1245,7 +1245,7 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest {
assertThat(fileInPodcastsDirLowerCase.createNewFile()).isTrue();
} finally {
fileInPodcastsDirLowerCase.delete();
- deleteAsLegacyApp(podcastsDirLowerCase);
+ deleteRecursivelyAsLegacyApp(podcastsDirLowerCase);
podcastsDir.mkdirs();
}
}
@@ -2660,8 +2660,8 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest {
// We can't rename a non-top level directory to a top level directory.
assertCantRenameDirectory(nonTopLevelDir, topLevelDir2, null);
} finally {
- deleteAsLegacyApp(topLevelDir1);
- deleteAsLegacyApp(topLevelDir2);
+ deleteRecursivelyAsLegacyApp(topLevelDir1);
+ deleteRecursivelyAsLegacyApp(topLevelDir2);
deleteRecursively(nonTopLevelDir);
}
}
@@ -2671,7 +2671,7 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest {
final File podcastsDir = getPodcastsDir();
try {
if (podcastsDir.exists()) {
- deleteAsLegacyApp(podcastsDir);
+ deleteRecursivelyAsLegacyApp(podcastsDir);
}
assertThat(podcastsDir.mkdir()).isTrue();
} finally {
@@ -2693,7 +2693,7 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest {
if (cameraDir.exists()) {
// This is a work around to address a known inode cache inconsistency issue
// that occurs when test runs for the second time.
- deleteAsLegacyApp(cameraDir);
+ deleteRecursivelyAsLegacyApp(cameraDir);
}
createDirectoryAsLegacyApp(cameraDir);
@@ -2717,7 +2717,7 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest {
} finally {
deleteWithMediaProviderNoThrow(targetUri);
deleteAsLegacyApp(nomediaFile);
- deleteAsLegacyApp(cameraDir);
+ deleteRecursivelyAsLegacyApp(cameraDir);
}
}
@@ -2770,7 +2770,7 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest {
} finally {
deleteAsLegacyApp(videoFile);
deleteAsLegacyApp(nomediaFile);
- deleteAsLegacyApp(directory);
+ deleteRecursivelyAsLegacyApp(directory);
}
}
diff --git a/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java b/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java
index 2e694ed65a7..79735361339 100644
--- a/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java
+++ b/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java
@@ -995,33 +995,23 @@ public class TestUtils {
final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace(
callingPackageName, otherApp.getPackageName()));
final File file = new File(otherAppExternalDataDir, fileName);
+ String absolutePath = file.getAbsolutePath();
+
+ final ContentValues valuesWithRelativePath = new ContentValues();
+ final String absoluteDirectoryPath = otherAppExternalDataDir.getAbsolutePath();
+ valuesWithRelativePath.put(MediaStore.MediaColumns.RELATIVE_PATH,
+ absoluteDirectoryPath.substring(absoluteDirectoryPath.indexOf("Android")));
+ valuesWithRelativePath.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
+
try {
assertThat(createFileAs(otherApp, file.getPath())).isTrue();
+ assertCantInsertDataValue(throwsExceptionForDataValue, absolutePath);
+ assertCantInsertDataValue(throwsExceptionForDataValue,
+ "/sdcard/" + absolutePath.substring(absolutePath.indexOf("Android")));
+ assertCantInsertDataValue(throwsExceptionForDataValue,
+ "/storage/emulated/0/Pictures/../"
+ + absolutePath.substring(absolutePath.indexOf("Android")));
- final ContentValues valuesWithData = new ContentValues();
- valuesWithData.put(MediaStore.MediaColumns.DATA, file.getAbsolutePath());
- try {
- Uri uri = getContentResolver().insert(
- MediaStore.Files.getContentUri(VOLUME_EXTERNAL),
- valuesWithData);
-
- if (throwsExceptionForDataValue) {
- fail("File insert expected to fail: " + file);
- } else {
- try (Cursor c = getContentResolver().query(uri, new String[]{
- MediaStore.MediaColumns.DATA}, null, null)) {
- assertThat(c.moveToFirst()).isTrue();
- assertThat(c.getString(0)).isNotEqualTo(file.getAbsolutePath());
- }
- }
- } catch (IllegalArgumentException expected) {
- }
-
- final ContentValues valuesWithRelativePath = new ContentValues();
- final String path = file.getAbsolutePath();
- valuesWithRelativePath.put(MediaStore.MediaColumns.RELATIVE_PATH,
- path.substring(path.indexOf("Android")));
- valuesWithRelativePath.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
try {
getContentResolver().insert(MediaStore.Files.getContentUri(VOLUME_EXTERNAL),
valuesWithRelativePath);
@@ -1033,6 +1023,34 @@ public class TestUtils {
}
}
+ private static void assertCantInsertDataValue(boolean throwsExceptionForDataValue,
+ String path) throws Exception {
+ if (throwsExceptionForDataValue) {
+ assertThrowsErrorOnInsertToOtherAppPrivateDirectories(path);
+ } else {
+ insertDataWithValue(path);
+ try (Cursor c = getContentResolver().query(
+ MediaStore.Files.getContentUri(VOLUME_EXTERNAL),
+ new String[] {MediaStore.MediaColumns.DATA},
+ MediaStore.MediaColumns.DATA + "=?", new String[] {path}, null)) {
+ assertThat(c.getCount()).isEqualTo(0);
+ }
+ }
+ }
+
+ private static void assertThrowsErrorOnInsertToOtherAppPrivateDirectories(String path)
+ throws Exception {
+ assertThrows(IllegalArgumentException.class, () -> insertDataWithValue(path));
+ }
+
+ private static void insertDataWithValue(String path) {
+ final ContentValues valuesWithData = new ContentValues();
+ valuesWithData.put(MediaStore.MediaColumns.DATA, path);
+
+ getContentResolver().insert(MediaStore.Files.getContentUri(VOLUME_EXTERNAL),
+ valuesWithData);
+ }
+
/**
* Assert that app cannot update files in other app's private directories
*
diff --git a/tests/app/Android.bp b/tests/app/Android.bp
index 2a9a925276c..11bc2047c3f 100644
--- a/tests/app/Android.bp
+++ b/tests/app/Android.bp
@@ -51,12 +51,15 @@ android_test {
],
instrumentation_for: "CtsAppTestStubs",
sdk_version: "test_current",
- min_sdk_version: "14",
+ // 21 required for multi-dex.
+ min_sdk_version: "21",
// Disable coverage since it pushes us over the dex limit and we don't
// actually need to measure the tests themselves.
jacoco: {
exclude_filter: ["**"],
},
+ // Even with coverage disabled, we're close to the single dex limit, so allow use of multi-dex.
+ dxflags: ["--multi-dex"],
data: [
":CtsSimpleApp",
":CtsAppTestStubs",