diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index 1469961f8..2009308b4 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -15,10 +15,10 @@ **API Changes** * Added ApplicationInfoBuilder.setFlags(int) -* Make suspend function versions of ViewCapture/WindowCapture/DeviceCapture APIs +* Make suspend function versions of ViewCapture/WindowCapture/DeviceCapture APIs, + and rename existing methods as *Async variants that return ListenableFutures * Make Bitmap.writeToTestStorage use the registered PlatformTestStorage instead of hardcoding TestStorage -* Add *Async variants of capture*ToBitmap methods - +* Remove ExperimentalTestApi/RequiresOptIn restrictions from captureToBitmap and takeScreenshot APIs **Breaking API Changes** diff --git a/core/java/androidx/test/core/api/current_internal.txt b/core/java/androidx/test/core/api/current_internal.txt index 2cf56459d..c6d1ac143 100644 --- a/core/java/androidx/test/core/api/current_internal.txt +++ b/core/java/androidx/test/core/api/current_internal.txt @@ -1,4 +1,13 @@ // Signature format: 3.0 +package androidx.test.core.app { + + public final class DeviceCapture { + method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static boolean canTakeScreenshot(); + method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static android.graphics.Bitmap takeScreenshotNoSync() throws java.lang.RuntimeException; + } + +} + package androidx.test.core.graphics { public final class BitmapStorage { @@ -7,3 +16,11 @@ package androidx.test.core.graphics { } +package androidx.test.core.view { + + public final class ViewCapture { + method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static suspend Object? forceRedraw(android.view.View, kotlin.coroutines.Continuation); + } + +} + diff --git a/core/java/androidx/test/core/api/current_public.txt b/core/java/androidx/test/core/api/current_public.txt index 78920710f..62dfd9a6e 100644 --- a/core/java/androidx/test/core/api/current_public.txt +++ b/core/java/androidx/test/core/api/current_public.txt @@ -26,6 +26,10 @@ package androidx.test.core.app { method public static T! getApplicationContext(); } + public final class DeviceCapture { + method @kotlin.jvm.Throws(exceptionClasses=RuntimeException::class) public static android.graphics.Bitmap takeScreenshot() throws java.lang.RuntimeException; + } + } package androidx.test.core.content.pm { @@ -53,6 +57,7 @@ package androidx.test.core.content.pm { package androidx.test.core.graphics { public final class BitmapStorage { + method @kotlin.jvm.Throws(exceptionClasses=IOException::class) public static void writeToTestStorage(android.graphics.Bitmap, String name) throws java.io.IOException; } } @@ -104,5 +109,15 @@ package androidx.test.core.view { method public androidx.test.core.view.PointerPropertiesBuilder! setToolType(int); } + public final class ViewCapture { + method public static suspend Object? captureToBitmap(android.view.View, android.graphics.Rect? rect = null, kotlin.coroutines.Continuation); + method public static com.google.common.util.concurrent.ListenableFuture captureToBitmapAsync(android.view.View, android.graphics.Rect? rect = null); + } + + public final class WindowCapture { + method public static suspend Object? captureRegionToBitmap(android.view.Window, android.graphics.Rect? boundsInWindow = null, kotlin.coroutines.Continuation); + method public static com.google.common.util.concurrent.ListenableFuture captureRegionToBitmapAsync(android.view.Window, android.graphics.Rect? boundsInWindow = null); + } + } diff --git a/core/java/androidx/test/core/app/DeviceCapture.kt b/core/java/androidx/test/core/app/DeviceCapture.kt index 844030eee..033efcdad 100644 --- a/core/java/androidx/test/core/app/DeviceCapture.kt +++ b/core/java/androidx/test/core/app/DeviceCapture.kt @@ -24,7 +24,6 @@ import android.os.Looper import android.util.Log import android.view.Choreographer import androidx.annotation.RestrictTo -import androidx.test.annotation.ExperimentalTestApi import androidx.test.core.internal.os.HandlerExecutor import androidx.test.core.view.forceRedraw import androidx.test.internal.util.Checks @@ -46,8 +45,10 @@ import kotlinx.coroutines.withTimeout * this method returns false then attempting to take a screenshot will fail. Note that taking a * screenshot may still fail if this method returns true, for example if the call to [UiAutomation] * fails. + * + * @hide */ -@ExperimentalTestApi +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) fun canTakeScreenshot(): Boolean = getInstrumentation().uiAutomation != null && Looper.myLooper() != Looper.getMainLooper() @@ -72,7 +73,6 @@ fun canTakeScreenshot(): Boolean = * @throws [IllegalStateException] if called on the main thread. This is a limitation of connecting * to UiAutomation, [RuntimeException] if UiAutomation fails to take the screenshot */ -@ExperimentalTestApi @Suppress("FutureReturnValueIgnored") @Throws(RuntimeException::class) fun takeScreenshot(): Bitmap { @@ -91,7 +91,6 @@ fun takeScreenshot(): Bitmap { * to UiAutomation, [RuntimeException] if UiAutomation fails to take the screenshot * @hide */ -@ExperimentalTestApi @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @Suppress("FutureReturnValueIgnored") @Throws(RuntimeException::class) diff --git a/core/java/androidx/test/core/graphics/BitmapStorageExt.kt b/core/java/androidx/test/core/graphics/BitmapStorageExt.kt index 7da16488b..683c1af87 100644 --- a/core/java/androidx/test/core/graphics/BitmapStorageExt.kt +++ b/core/java/androidx/test/core/graphics/BitmapStorageExt.kt @@ -20,7 +20,6 @@ package androidx.test.core.graphics import android.graphics.Bitmap import androidx.annotation.RestrictTo -import androidx.test.annotation.ExperimentalTestApi import androidx.test.platform.io.PlatformTestStorage import androidx.test.platform.io.PlatformTestStorageRegistry import java.io.IOException @@ -31,7 +30,6 @@ import java.io.IOException * @param name a descriptive base name for the resulting file. '.png' will be appended to this name. * @throws IOException if bitmap could not be compressed or written to ds */ -@ExperimentalTestApi @Throws(IOException::class) fun Bitmap.writeToTestStorage(name: String) { writeToTestStorage(PlatformTestStorageRegistry.getInstance(), name) diff --git a/core/java/androidx/test/core/view/ViewCapture.kt b/core/java/androidx/test/core/view/ViewCapture.kt index bdab03d1e..0e0dda56a 100644 --- a/core/java/androidx/test/core/view/ViewCapture.kt +++ b/core/java/androidx/test/core/view/ViewCapture.kt @@ -32,8 +32,8 @@ import android.view.View import android.view.ViewTreeObserver.OnDrawListener import android.view.WindowManager import androidx.annotation.RequiresApi +import androidx.annotation.RestrictTo import androidx.concurrent.futures.SuspendToFutureAdapter -import androidx.test.annotation.ExperimentalTestApi import androidx.test.core.internal.os.HandlerExecutor import androidx.test.internal.platform.ServiceLoaderWrapper import androidx.test.internal.platform.os.ControlledLooper @@ -79,10 +79,7 @@ import kotlinx.coroutines.suspendCancellableCoroutine * The resulting image is captured after forcing the View to redraw, and waiting for the draw to * operation complete. This is done as a means to improve the stability of the resulting image - * especially in cases where hardware rendering drawing is off initially. - * - * This API is currently experimental and subject to change or removal. */ -@ExperimentalTestApi suspend fun View.captureToBitmap(rect: Rect? = null): Bitmap { checkState(isAttachedToWindow, "View must be attached to a window") checkState( @@ -110,7 +107,6 @@ private fun getControlledLooper(): ControlledLooper { } /** A ListenableFuture variant of captureToBitmap intended for use from Java. */ -@ExperimentalTestApi fun View.captureToBitmapAsync(rect: Rect? = null): ListenableFuture { return SuspendToFutureAdapter.launchFuture(Dispatchers.Main) { captureToBitmap(rect) } } @@ -119,10 +115,10 @@ fun View.captureToBitmapAsync(rect: Rect? = null): ListenableFuture { * Trigger a redraw of the given view. * * Should only be called on UI thread. + * + * @hide */ -// TODO(b/316921934): uncomment once @ExperimentalTestApi is removed -// @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -@ExperimentalTestApi +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) suspend fun View.forceRedraw() { checkState(handler.looper.isCurrentThread, "Must be called from view's handler thread") if (!getControlledLooper().areDrawCallbacksSupported()) { diff --git a/core/java/androidx/test/core/view/WindowCapture.kt b/core/java/androidx/test/core/view/WindowCapture.kt index 46d81e3e0..8bbc9206b 100644 --- a/core/java/androidx/test/core/view/WindowCapture.kt +++ b/core/java/androidx/test/core/view/WindowCapture.kt @@ -26,7 +26,6 @@ import android.view.PixelCopy import android.view.Window import androidx.annotation.RequiresApi import androidx.concurrent.futures.SuspendToFutureAdapter -import androidx.test.annotation.ExperimentalTestApi import androidx.test.platform.graphics.HardwareRendererCompat import com.google.common.util.concurrent.ListenableFuture import kotlin.coroutines.resumeWithException @@ -49,10 +48,7 @@ import kotlinx.coroutines.suspendCancellableCoroutine * The resulting image is captured after forcing the View to redraw, and waiting for the draw to * operation complete. This is done as a means to improve the stability of the resulting image - * especially in cases where hardware rendering drawing is off initially. - * - * This API is currently experimental and subject to change or removal. */ -@ExperimentalTestApi suspend fun Window.captureRegionToBitmap(boundsInWindow: Rect? = null): Bitmap { var bitmap: Bitmap? = null @@ -69,7 +65,6 @@ suspend fun Window.captureRegionToBitmap(boundsInWindow: Rect? = null): Bitmap { } /** A ListenableFuture variant of captureRegionToBitmap intended for use from Java. */ -@ExperimentalTestApi fun Window.captureRegionToBitmapAsync(boundsInWindow: Rect? = null): ListenableFuture { return SuspendToFutureAdapter.launchFuture(Dispatchers.Main) { captureRegionToBitmap(boundsInWindow) diff --git a/espresso/CHANGELOG.md b/espresso/CHANGELOG.md index c742503bb..44ff3780d 100644 --- a/espresso/CHANGELOG.md +++ b/espresso/CHANGELOG.md @@ -29,7 +29,8 @@ The following artifacts were released: **API Changes** * Adapt to ViewCapture API changes -* Delete ViewInteraction.captureToBitmap in favor of ViewActions.captureToBitmap +* Delete ViewInteraction.captureToBitmap in favor of ViewActions.captureToBitmap, + and promote to a stable API from ExperimentalTestApi **Breaking API Changes** diff --git a/espresso/core/java/androidx/test/espresso/action/CaptureToBitmapAction.kt b/espresso/core/java/androidx/test/espresso/action/CaptureToBitmapAction.kt index e42d6dfcb..0f22d94c7 100644 --- a/espresso/core/java/androidx/test/espresso/action/CaptureToBitmapAction.kt +++ b/espresso/core/java/androidx/test/espresso/action/CaptureToBitmapAction.kt @@ -3,7 +3,6 @@ package androidx.test.espresso.action import android.os.Handler import android.os.Looper import android.view.View -import androidx.test.annotation.ExperimentalTestApi import androidx.test.core.internal.os.HandlerExecutor import androidx.test.core.view.captureToBitmapAsync import androidx.test.espresso.IdlingRegistry @@ -14,7 +13,6 @@ import java.util.concurrent.TimeUnit import org.hamcrest.Matcher import org.hamcrest.Matchers.any -@ExperimentalTestApi class CaptureToBitmapAction(val bitmapReceiver: ViewActions.BitmapReceiver) : ViewAction { override fun getConstraints(): Matcher { return any(View::class.java) diff --git a/espresso/core/java/androidx/test/espresso/action/ViewActions.java b/espresso/core/java/androidx/test/espresso/action/ViewActions.java index ea8bca0a0..352dc29c5 100644 --- a/espresso/core/java/androidx/test/espresso/action/ViewActions.java +++ b/espresso/core/java/androidx/test/espresso/action/ViewActions.java @@ -30,7 +30,6 @@ import android.view.MotionEvent; import android.view.View; import androidx.annotation.NonNull; -import androidx.test.annotation.ExperimentalTestApi; import androidx.test.espresso.PerformException; import androidx.test.espresso.UiController; import androidx.test.espresso.ViewAction; @@ -571,7 +570,6 @@ public static ViewAction repeatedlyUntil( new RepeatActionUntilViewState(action, desiredStateMatcher, maxAttempts)); } - @ExperimentalTestApi public interface BitmapReceiver { void onBitmapCaptured(Bitmap bitmap); } @@ -585,7 +583,6 @@ public interface BitmapReceiver { * thread. * @return the ViewAction */ - @ExperimentalTestApi public static ViewAction captureToBitmap(BitmapReceiver bitmapReceiver) { return new CaptureToBitmapAction(bitmapReceiver); } diff --git a/espresso/core/java/androidx/test/espresso/api/current_public.txt b/espresso/core/java/androidx/test/espresso/api/current_public.txt index c665e5090..32b535f6e 100644 --- a/espresso/core/java/androidx/test/espresso/api/current_public.txt +++ b/espresso/core/java/androidx/test/espresso/api/current_public.txt @@ -76,6 +76,8 @@ package androidx.test.espresso { method public static void setIdlingResourceTimeout(long, java.util.concurrent.TimeUnit!); method public static void setMasterPolicyTimeout(long, java.util.concurrent.TimeUnit!); method public static void setMasterPolicyTimeoutWhenDebuggerAttached(boolean); + method @androidx.test.annotation.ExperimentalTestApi public static void unsafeMakeIdlingResourceErrorPolicyWarning(); + method @androidx.test.annotation.ExperimentalTestApi public static void unsafeMakeMasterPolicyWarning(); } public final class IdlingPolicy { @@ -231,6 +233,15 @@ package androidx.test.espresso.action { method public static androidx.test.espresso.action.AdapterViewProtocol! standardProtocol(); } + public final class CaptureToBitmapAction implements androidx.test.espresso.ViewAction { + ctor public CaptureToBitmapAction(androidx.test.espresso.action.ViewActions.BitmapReceiver bitmapReceiver); + method public androidx.test.espresso.action.ViewActions.BitmapReceiver getBitmapReceiver(); + method public org.hamcrest.Matcher getConstraints(); + method public String getDescription(); + method public void perform(androidx.test.espresso.UiController uiController, android.view.View view); + property public final androidx.test.espresso.action.ViewActions.BitmapReceiver bitmapReceiver; + } + public final class CloseKeyboardAction implements androidx.test.espresso.ViewAction { ctor @androidx.test.espresso.remote.annotation.RemoteMsgConstructor public CloseKeyboardAction(); method public org.hamcrest.Matcher! getConstraints(); @@ -419,6 +430,7 @@ package androidx.test.espresso.action { @com.google.errorprone.annotations.CheckReturnValue public final class ViewActions { method public static androidx.test.espresso.ViewAction! actionWithAssertions(androidx.test.espresso.ViewAction!); method public static void addGlobalAssertion(String!, androidx.test.espresso.ViewAssertion!); + method public static androidx.test.espresso.ViewAction! captureToBitmap(androidx.test.espresso.action.ViewActions.BitmapReceiver!); method public static void clearGlobalAssertions(); method public static androidx.test.espresso.ViewAction! clearText(); method public static androidx.test.espresso.ViewAction! click(); @@ -453,6 +465,10 @@ package androidx.test.espresso.action { method public static androidx.test.espresso.ViewAction! typeTextIntoFocusedView(String!); } + public static interface ViewActions.BitmapReceiver { + method public void onBitmapCaptured(android.graphics.Bitmap!); + } + } package androidx.test.espresso.assertion { @@ -705,6 +721,7 @@ package androidx.test.espresso.matcher { method public static org.hamcrest.Matcher! isRoot(); method public static org.hamcrest.Matcher! isSelected(); method public static org.hamcrest.Matcher! supportsInputMethods(); + method @androidx.test.annotation.ExperimentalTestApi public static org.hamcrest.Matcher! thatMatchesFirst(org.hamcrest.Matcher!); method public static org.hamcrest.Matcher! withAlpha(float); method public static org.hamcrest.Matcher! withChild(org.hamcrest.Matcher!); method public static org.hamcrest.Matcher! withClassName(org.hamcrest.Matcher!); diff --git a/services/storage/java/androidx/test/services/storage/api/current_public.txt b/services/storage/java/androidx/test/services/storage/api/current_public.txt index a1242aafc..3b0cd3ec7 100644 --- a/services/storage/java/androidx/test/services/storage/api/current_public.txt +++ b/services/storage/java/androidx/test/services/storage/api/current_public.txt @@ -16,5 +16,83 @@ package androidx.test.services.storage { method public java.io.OutputStream! openOutputFile(String, boolean) throws java.io.FileNotFoundException; } + @androidx.test.annotation.ExperimentalTestApi public final class TestStorageConstants { + field public static final String INTERNAL_USE_PROVIDER_AUTHORITY = "androidx.test.services.storage._internal_use_files"; + field public static final String ON_DEVICE_FIXTURE_SCRIPTS = "googletest/fixture_scripts/"; + field public static final String ON_DEVICE_PATH_INTERNAL_USE = "googletest/internal_use/"; + field public static final String ON_DEVICE_PATH_ROOT = "googletest/"; + field public static final String ON_DEVICE_PATH_TEST_OUTPUT = "googletest/test_outputfiles/"; + field public static final String ON_DEVICE_PATH_TEST_PROPERTIES = "googletest/test_exportproperties/"; + field public static final String ON_DEVICE_TEST_RUNFILES = "googletest/test_runfiles/"; + field public static final String OUTPUT_PROPERTIES_PROVIDER_AUTHORITY = "androidx.test.services.storage.properties"; + field public static final String TEST_ARGS_FILE_NAME = "test_args.dat"; + field public static final String TEST_ARGS_PROVIDER_AUTHORITY = "androidx.test.services.storage.testargs"; + field public static final String TEST_OUTPUT_PROVIDER_AUTHORITY = "androidx.test.services.storage.outputfiles"; + field public static final String TEST_RUNFILES_PROVIDER_AUTHORITY = "androidx.test.services.storage.runfiles"; + } + + @androidx.test.annotation.ExperimentalTestApi public class TestStorageException extends java.lang.RuntimeException { + ctor public TestStorageException(String!); + ctor public TestStorageException(String!, Throwable!); + } + +} + +package androidx.test.services.storage.file { + + @androidx.test.annotation.ExperimentalTestApi public final class HostedFile { + method public static android.net.Uri! buildUri(androidx.test.services.storage.file.HostedFile.FileHost!, String!); + method public static java.io.File! getInputRootDirectory(android.content.Context!); + method public static java.io.File! getOutputRootDirectory(android.content.Context!); + } + + public enum HostedFile.FileHost { + method public String! getAuthority(); + method public boolean isWritable(); + enum_constant public static final androidx.test.services.storage.file.HostedFile.FileHost EXPORT_PROPERTIES; + enum_constant public static final androidx.test.services.storage.file.HostedFile.FileHost INTERNAL_USE_ONLY; + enum_constant public static final androidx.test.services.storage.file.HostedFile.FileHost OUTPUT; + enum_constant public static final androidx.test.services.storage.file.HostedFile.FileHost TEST_FILE; + } + + public enum HostedFile.FileType { + method public static androidx.test.services.storage.file.HostedFile.FileType! fromTypeCode(String!); + method public String! getTypeCode(); + enum_constant public static final androidx.test.services.storage.file.HostedFile.FileType DIRECTORY; + enum_constant public static final androidx.test.services.storage.file.HostedFile.FileType FILE; + } + + public enum HostedFile.HostedFileColumn { + method public int getAndroidType(); + method public String! getColumnName(); + method public static String![]! getColumnNames(); + method public Class! getColumnType(); + method public int getPosition(); + enum_constant public static final androidx.test.services.storage.file.HostedFile.HostedFileColumn DATA; + enum_constant public static final androidx.test.services.storage.file.HostedFile.HostedFileColumn DISPLAY_NAME; + enum_constant public static final androidx.test.services.storage.file.HostedFile.HostedFileColumn NAME; + enum_constant public static final androidx.test.services.storage.file.HostedFile.HostedFileColumn SIZE; + enum_constant public static final androidx.test.services.storage.file.HostedFile.HostedFileColumn SIZE_2; + enum_constant public static final androidx.test.services.storage.file.HostedFile.HostedFileColumn TYPE; + } + + @androidx.test.annotation.ExperimentalTestApi public final class PropertyFile { + method public static android.net.Uri! buildUri(androidx.test.services.storage.file.PropertyFile.Authority!); + method public static android.net.Uri! buildUri(androidx.test.services.storage.file.PropertyFile.Authority!, String!); + } + + public enum PropertyFile.Authority { + method public String! getAuthority(); + enum_constant public static final androidx.test.services.storage.file.PropertyFile.Authority TEST_ARGS; + } + + public enum PropertyFile.Column { + method public String! getName(); + method public static String![]! getNames(); + method public int getPosition(); + enum_constant public static final androidx.test.services.storage.file.PropertyFile.Column NAME; + enum_constant public static final androidx.test.services.storage.file.PropertyFile.Column VALUE; + } + }