Merge "Text doesn't crash when text is very very tall" into androidx-main
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/AnimRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/AnimRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/AnimRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/AnimRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/AnimatorRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/AnimatorRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/AnimatorRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/AnimatorRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/AnyRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/AnyRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/AnyRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/AnyRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/ArrayRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/ArrayRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/ArrayRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/ArrayRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/AttrRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/AttrRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/AttrRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/AttrRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/BinderThread.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/BinderThread.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/BinderThread.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/BinderThread.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/BoolRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/BoolRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/BoolRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/BoolRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/ChecksSdkIntAtLeast.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/ChecksSdkIntAtLeast.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/ChecksSdkIntAtLeast.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/ChecksSdkIntAtLeast.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/ColorRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/ColorRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/ColorRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/ColorRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/ContentView.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/ContentView.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/ContentView.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/ContentView.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DeprecatedSinceApi.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DeprecatedSinceApi.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DeprecatedSinceApi.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DeprecatedSinceApi.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DimenRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DimenRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DimenRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DimenRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/Dimension.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/Dimension.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/Dimension.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/Dimension.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DimensionUnit.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DimensionUnit.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DimensionUnit.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DimensionUnit.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DisplayContext.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DisplayContext.jvm.kt
old mode 100755
new mode 100644
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DisplayContext.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DisplayContext.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DoNotInline.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DoNotInline.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DoNotInline.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DoNotInline.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DrawableRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DrawableRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DrawableRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/DrawableRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/FontRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/FontRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/FontRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/FontRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/FractionRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/FractionRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/FractionRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/FractionRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/GravityInt.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/GravityInt.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/GravityInt.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/GravityInt.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/HalfFloat.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/HalfFloat.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/HalfFloat.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/HalfFloat.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/IdRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/IdRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/IdRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/IdRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/InspectableProperty.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/InspectableProperty.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/InspectableProperty.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/InspectableProperty.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/IntegerRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/IntegerRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/IntegerRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/IntegerRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/InterpolatorRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/InterpolatorRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/InterpolatorRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/InterpolatorRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/Keep.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/Keep.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/Keep.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/Keep.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/LayoutRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/LayoutRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/LayoutRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/LayoutRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/MainThread.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/MainThread.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/MainThread.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/MainThread.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/MenuRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/MenuRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/MenuRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/MenuRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/NavigationRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/NavigationRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/NavigationRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/NavigationRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/NonNull.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/NonNull.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/NonNull.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/NonNull.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/NonUiContext.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/NonUiContext.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/NonUiContext.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/NonUiContext.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/Nullable.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/Nullable.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/Nullable.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/Nullable.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/PluralsRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/PluralsRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/PluralsRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/PluralsRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/Px.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/Px.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/Px.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/Px.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/RawRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/RawRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/RawRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/RawRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/RequiresExtension.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/RequiresExtension.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/RequiresExtension.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/RequiresExtension.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/RequiresPermission.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/RequiresPermission.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/RequiresPermission.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/RequiresPermission.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/StringRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/StringRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/StringRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/StringRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/StyleRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/StyleRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/StyleRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/StyleRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/StyleableRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/StyleableRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/StyleableRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/StyleableRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/TransitionRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/TransitionRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/TransitionRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/TransitionRes.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/UiContext.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/UiContext.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/UiContext.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/UiContext.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/WorkerThread.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/WorkerThread.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/WorkerThread.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/WorkerThread.jvm.kt
diff --git a/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/XmlRes.kt b/annotation/annotation/src/jvmMain/kotlin/androidx/annotation/XmlRes.jvm.kt
similarity index 100%
rename from annotation/annotation/src/jvmMain/kotlin/androidx/annotation/XmlRes.kt
rename to annotation/annotation/src/jvmMain/kotlin/androidx/annotation/XmlRes.jvm.kt
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/VirtualCameraInfo.java b/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/VirtualCameraInfo.java
index 88d96ad..635428a 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/VirtualCameraInfo.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/VirtualCameraInfo.java
@@ -19,6 +19,7 @@
import static androidx.camera.core.impl.utils.TransformUtils.within360;
import android.os.Build;
+import android.view.Surface;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
@@ -61,6 +62,11 @@
}
@Override
+ public int getSensorRotationDegrees() {
+ return getSensorRotationDegrees(Surface.ROTATION_0);
+ }
+
+ @Override
public int getSensorRotationDegrees(@ImageOutputConfig.RotationValue int relativeRotation) {
// The child UseCase calls this method to get the remaining rotation degrees, which is the
// original rotation minus the rotation applied by the virtual camera.
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/streamsharing/VirtualCameraTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/streamsharing/VirtualCameraTest.kt
index 65446d4..1b2a88e 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/streamsharing/VirtualCameraTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/streamsharing/VirtualCameraTest.kt
@@ -80,6 +80,8 @@
assertThat(parentCamera.cameraInfoInternal.getSensorRotationDegrees(Surface.ROTATION_0))
.isEqualTo(90)
virtualCamera.setRotationDegrees(180)
+ assertThat(virtualCamera.cameraInfoInternal.getSensorRotationDegrees())
+ .isEqualTo(270)
assertThat(virtualCamera.cameraInfoInternal.getSensorRotationDegrees(Surface.ROTATION_0))
.isEqualTo(270)
}
diff --git a/camera/camera-viewfinder-core/src/main/java/androidx/viewfinder/core/ZoomGestureDetector.java b/camera/camera-viewfinder-core/src/main/java/androidx/viewfinder/core/ZoomGestureDetector.java
new file mode 100644
index 0000000..1888109
--- /dev/null
+++ b/camera/camera-viewfinder-core/src/main/java/androidx/viewfinder/core/ZoomGestureDetector.java
@@ -0,0 +1,591 @@
+/*
+ * Copyright 2023 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.
+ */
+
+package androidx.viewfinder.core;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.os.Build;
+import android.os.Handler;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
+
+/**
+ * Detects scaling transformation gestures that interprets zooming events using the supplied
+ * {@link MotionEvent}s.
+ *
+ * <p>The {@link OnZoomGestureListener} callback will notify users when a particular
+ * gesture event has occurred.
+ *
+ * <p>This class should only be used with {@link MotionEvent}s reported via touch.
+ *
+ * <p>To use this class:
+ * <ul>
+ * <li>Create an instance of the {@code ZoomGestureDetector} for your
+ * {@link View}
+ * <li>In the {@link View#onTouchEvent(MotionEvent)} method ensure you call
+ * {@link #onTouchEvent(MotionEvent)}. The methods defined in your
+ * callback will be executed when the events occur.
+ * </ul>
+ */
+// TODO(b/314701735): update the documentation with examples using camera classes.
+// TODO(b/314701401): convert to kotlin implementation.
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public class ZoomGestureDetector {
+ private static final String TAG = "ZoomGestureDetector";
+ // The default minimum span that the detector interprets a zooming event with. It's set to 0
+ // to give the most responsiveness.
+ // TODO(b/314702145): define a different span if appropriate.
+ private static final int DEFAULT_MIN_SPAN = 0;
+
+ /**
+ * The listener for receiving notifications when gestures occur.
+ * If you want to listen for all the different gestures then implement
+ * this interface.
+ *
+ * <p>An application will receive events in the following order:
+ * <ul>
+ * <li>One {@link OnZoomGestureListener#onZoomBegin(ZoomGestureDetector)}
+ * <li>Zero or more {@link OnZoomGestureListener#onZoom(ZoomGestureDetector)}
+ * <li>One {@link OnZoomGestureListener#onZoomEnd(ZoomGestureDetector)}
+ * </ul>
+ */
+ public interface OnZoomGestureListener {
+ /**
+ * Responds to zooming events for a gesture in progress.
+ * Reported by pointer motion.
+ *
+ * @param detector The detector reporting the event - use this to
+ * retrieve extended info about event state.
+ * @return Whether or not the detector should consider this event
+ * as handled. If an event was not handled, the detector
+ * will continue to accumulate movement until an event is
+ * handled. This can be useful if an application, for example,
+ * only wants to update scaling factors if the change is
+ * greater than 0.01.
+ */
+ default boolean onZoom(@NonNull ZoomGestureDetector detector) {
+ return false;
+ }
+
+ /**
+ * Responds to the beginning of a zooming gesture. Reported by
+ * new pointers going down.
+ *
+ * @param detector The detector reporting the event - use this to
+ * retrieve extended info about event state.
+ * @return Whether or not the detector should continue recognizing
+ * this gesture. For example, if a gesture is beginning
+ * with a focal point outside of a region where it makes
+ * sense, onZoomBegin() may return false to ignore the
+ * rest of the gesture.
+ */
+ default boolean onZoomBegin(@NonNull ZoomGestureDetector detector) {
+ return true;
+ }
+
+ /**
+ * Responds to the end of a zoom gesture. Reported by existing
+ * pointers going up.
+ *
+ * <p>Once a zoom has ended, {@link ZoomGestureDetector#getFocusX()}
+ * and {@link ZoomGestureDetector#getFocusY()} will return focal point
+ * of the pointers remaining on the screen.
+ *
+ * @param detector The detector reporting the event - use this to
+ * retrieve extended info about event state.
+ */
+ default void onZoomEnd(@NonNull ZoomGestureDetector detector) {
+ // Intentionally empty
+ }
+ }
+
+ private final Context mContext;
+ private final OnZoomGestureListener mListener;
+
+ private float mFocusX;
+ private float mFocusY;
+
+ private boolean mQuickZoomEnabled;
+ private boolean mStylusZoomEnabled;
+
+ private float mCurrSpan;
+ private float mPrevSpan;
+ private float mInitialSpan;
+ private float mCurrSpanX;
+ private float mCurrSpanY;
+ private float mPrevSpanX;
+ private float mPrevSpanY;
+ private long mCurrTime;
+ private long mPrevTime;
+ private boolean mInProgress;
+ private int mSpanSlop;
+
+ private int mMinSpan;
+
+ private final Handler mHandler;
+
+ private float mAnchoredZoomStartX;
+ private float mAnchoredZoomStartY;
+ private int mAnchoredZoomMode = ANCHORED_ZOOM_MODE_NONE;
+
+ private static final float SCALE_FACTOR = .5f;
+ private static final int ANCHORED_ZOOM_MODE_NONE = 0;
+ private static final int ANCHORED_ZOOM_MODE_DOUBLE_TAP = 1;
+ private static final int ANCHORED_ZOOM_MODE_STYLUS = 2;
+ private GestureDetector mGestureDetector;
+
+ private boolean mEventBeforeOrAboveStartingGestureEvent;
+
+ /**
+ * Creates a ZoomGestureDetector with the supplied listener.
+ * You may only use this constructor from a {@link android.os.Looper Looper} thread.
+ *
+ * @param context the application's context
+ * @param listener the listener invoked for all the callbacks, this must
+ * not be null.
+ *
+ * @throws NullPointerException if {@code listener} is null.
+ */
+ public ZoomGestureDetector(@NonNull Context context,
+ @NonNull OnZoomGestureListener listener) {
+ this(context, null, listener);
+ }
+
+ /**
+ * Creates a ZoomGestureDetector with the supplied listener.
+ * @see android.os.Handler#Handler()
+ *
+ * @param context the application's context
+ * @param listener the listener invoked for all the callbacks, this must
+ * not be null.
+ * @param handler the handler to use for running deferred listener events.
+ *
+ * @throws NullPointerException if {@code listener} is null.
+ */
+ public ZoomGestureDetector(@NonNull Context context, @Nullable Handler handler,
+ @NonNull OnZoomGestureListener listener) {
+ this(context, ViewConfiguration.get(context).getScaledTouchSlop() * 2,
+ DEFAULT_MIN_SPAN, handler, listener);
+ }
+
+ /**
+ * Creates a ZoomGestureDetector with span slop and min span.
+ *
+ * @param context the application's context.
+ * @param spanSlop the threshold for interpreting a touch movement as zooming.
+ * @param minSpan the minimum threshold of zooming span. The span could be
+ * overridden by other usages to specify a different zooming span, for instance,
+ * if you need pinch gestures to continue closer together than the default.
+ * @param listener the listener invoked for all the callbacks, this must not be null.
+ * @param handler the handler to use for running deferred listener events.
+ *
+ * @throws NullPointerException if {@code listener} is null.
+ */
+ @SuppressLint("ExecutorRegistration")
+ public ZoomGestureDetector(@NonNull Context context, int spanSlop,
+ int minSpan, @Nullable Handler handler,
+ @NonNull OnZoomGestureListener listener) {
+ mContext = context;
+ mListener = listener;
+ mSpanSlop = spanSlop;
+ mMinSpan = minSpan;
+ mHandler = handler;
+ // Quick zoom is enabled by default after JB_MR2
+ final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
+ if (targetSdkVersion > Build.VERSION_CODES.JELLY_BEAN_MR2) {
+ setQuickZoomEnabled(true);
+ }
+ // Stylus zoom is enabled by default after LOLLIPOP_MR1
+ if (targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
+ setStylusZoomEnabled(true);
+ }
+ }
+
+ /**
+ * Accepts MotionEvents and dispatches events to a {@link OnZoomGestureListener}
+ * when appropriate.
+ *
+ * <p>Applications should pass a complete and consistent event stream to this method.
+ * A complete and consistent event stream involves all MotionEvents from the initial
+ * ACTION_DOWN to the final ACTION_UP or ACTION_CANCEL.</p>
+ *
+ * @param event The event to process
+ * @return true if the event was processed and the detector wants to receive the
+ * rest of the MotionEvents in this event stream.
+ */
+ public boolean onTouchEvent(@NonNull MotionEvent event) {
+ mCurrTime = event.getEventTime();
+
+ final int action = event.getActionMasked();
+
+ // Forward the event to check for double tap gesture
+ if (mQuickZoomEnabled) {
+ mGestureDetector.onTouchEvent(event);
+ }
+
+ final int count = event.getPointerCount();
+ final boolean isStylusButtonDown =
+ (event.getButtonState() & MotionEvent.BUTTON_STYLUS_PRIMARY) != 0;
+
+ final boolean anchoredZoomCancelled =
+ mAnchoredZoomMode == ANCHORED_ZOOM_MODE_STYLUS && !isStylusButtonDown;
+ final boolean streamComplete = action == MotionEvent.ACTION_UP
+ || action == MotionEvent.ACTION_CANCEL
+ || anchoredZoomCancelled;
+
+ if (action == MotionEvent.ACTION_DOWN || streamComplete) {
+ // Reset any scale in progress with the listener.
+ // If it's an ACTION_DOWN we're beginning a new event stream.
+ // This means the app probably didn't give us all the events. Shame on it.
+ if (mInProgress) {
+ mListener.onZoomEnd(this);
+ mInProgress = false;
+ mInitialSpan = 0;
+ mAnchoredZoomMode = ANCHORED_ZOOM_MODE_NONE;
+ } else if (inAnchoredZoomMode() && streamComplete) {
+ mInProgress = false;
+ mInitialSpan = 0;
+ mAnchoredZoomMode = ANCHORED_ZOOM_MODE_NONE;
+ }
+
+ if (streamComplete) {
+ return true;
+ }
+ }
+
+ if (!mInProgress && mStylusZoomEnabled && !inAnchoredZoomMode()
+ && !streamComplete && isStylusButtonDown) {
+ // Start of a button zoom gesture
+ mAnchoredZoomStartX = event.getX();
+ mAnchoredZoomStartY = event.getY();
+ mAnchoredZoomMode = ANCHORED_ZOOM_MODE_STYLUS;
+ mInitialSpan = 0;
+ }
+
+ final boolean configChanged = action == MotionEvent.ACTION_DOWN
+ || action == MotionEvent.ACTION_POINTER_UP
+ || action == MotionEvent.ACTION_POINTER_DOWN
+ || anchoredZoomCancelled;
+
+ final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;
+ final int skipIndex = pointerUp ? event.getActionIndex() : -1;
+
+ // Determine focal point
+ float sumX = 0, sumY = 0;
+ final int div = pointerUp ? count - 1 : count;
+ final float focusX;
+ final float focusY;
+ if (inAnchoredZoomMode()) {
+ // In anchored scale mode, the focal pt is always where the double tap
+ // or button down gesture started
+ focusX = mAnchoredZoomStartX;
+ focusY = mAnchoredZoomStartY;
+ if (event.getY() < focusY) {
+ mEventBeforeOrAboveStartingGestureEvent = true;
+ } else {
+ mEventBeforeOrAboveStartingGestureEvent = false;
+ }
+ } else {
+ for (int i = 0; i < count; i++) {
+ if (skipIndex == i) continue;
+ sumX += event.getX(i);
+ sumY += event.getY(i);
+ }
+
+ focusX = sumX / div;
+ focusY = sumY / div;
+ }
+
+ // Determine average deviation from focal point
+ float devSumX = 0, devSumY = 0;
+ for (int i = 0; i < count; i++) {
+ if (skipIndex == i) continue;
+
+ // Convert the resulting diameter into a radius.
+ devSumX += Math.abs(event.getX(i) - focusX);
+ devSumY += Math.abs(event.getY(i) - focusY);
+ }
+ final float devX = devSumX / div;
+ final float devY = devSumY / div;
+
+ // Span is the average distance between touch points through the focal point;
+ // i.e. the diameter of the circle with a radius of the average deviation from
+ // the focal point.
+ final float spanX = devX * 2;
+ final float spanY = devY * 2;
+ final float span;
+ if (inAnchoredZoomMode()) {
+ span = spanY;
+ } else {
+ span = (float) Math.hypot(spanX, spanY);
+ }
+
+ // Dispatch begin/end events as needed.
+ // If the configuration changes, notify the app to reset its current state by beginning
+ // a fresh zoom event stream.
+ final boolean wasInProgress = mInProgress;
+ mFocusX = focusX;
+ mFocusY = focusY;
+ if (!inAnchoredZoomMode() && mInProgress && (span < mMinSpan || configChanged)) {
+ mListener.onZoomEnd(this);
+ mInProgress = false;
+ mInitialSpan = span;
+ }
+ if (configChanged) {
+ mPrevSpanX = mCurrSpanX = spanX;
+ mPrevSpanY = mCurrSpanY = spanY;
+ mInitialSpan = mPrevSpan = mCurrSpan = span;
+ }
+
+ final int minSpan = inAnchoredZoomMode() ? mSpanSlop : mMinSpan;
+ if (!mInProgress && span >= minSpan
+ && (wasInProgress || Math.abs(span - mInitialSpan) > mSpanSlop)) {
+ mPrevSpanX = mCurrSpanX = spanX;
+ mPrevSpanY = mCurrSpanY = spanY;
+ mPrevSpan = mCurrSpan = span;
+ mPrevTime = mCurrTime;
+ mInProgress = mListener.onZoomBegin(this);
+ }
+
+ // Handle motion; focal point and span/scale factor are changing.
+ if (action == MotionEvent.ACTION_MOVE) {
+ mCurrSpanX = spanX;
+ mCurrSpanY = spanY;
+ mCurrSpan = span;
+
+ boolean updatePrev = true;
+
+ if (mInProgress) {
+ updatePrev = mListener.onZoom(this);
+ }
+
+ if (updatePrev) {
+ mPrevSpanX = mCurrSpanX;
+ mPrevSpanY = mCurrSpanY;
+ mPrevSpan = mCurrSpan;
+ mPrevTime = mCurrTime;
+ }
+ }
+
+ return true;
+ }
+
+ private boolean inAnchoredZoomMode() {
+ return mAnchoredZoomMode != ANCHORED_ZOOM_MODE_NONE;
+ }
+
+ /**
+ * Set whether the associated {@link OnZoomGestureListener} should receive onZoom callbacks
+ * when the user performs a doubleTap followed by a swipe.
+ *
+ * <p>If not set, this is enabled by default.
+ *
+ * @param enabled {@code true} to enable quick zooming, {@code false} to disable.
+ */
+ public void setQuickZoomEnabled(boolean enabled) {
+ mQuickZoomEnabled = enabled;
+ if (mQuickZoomEnabled && mGestureDetector == null) {
+ GestureDetector.SimpleOnGestureListener gestureListener =
+ new GestureDetector.SimpleOnGestureListener() {
+ @Override
+ public boolean onDoubleTap(MotionEvent e) {
+ // Double tap: start watching for a swipe
+ mAnchoredZoomStartX = e.getX();
+ mAnchoredZoomStartY = e.getY();
+ mAnchoredZoomMode = ANCHORED_ZOOM_MODE_DOUBLE_TAP;
+ return true;
+ }
+ };
+ mGestureDetector = new GestureDetector(mContext, gestureListener, mHandler);
+ }
+ }
+
+ /**
+ * Return whether the quick zoom gesture, in which the user performs a double tap followed by a
+ * swipe, should perform zooming.
+ *
+ * @see #setQuickZoomEnabled(boolean)
+ */
+ public boolean isQuickZoomEnabled() {
+ return mQuickZoomEnabled;
+ }
+
+ /**
+ * Sets whether the associates {@link OnZoomGestureListener} should receive
+ * onZoom callbacks when the user uses a stylus and presses the button.
+ *
+ * <p>If not set, this is enabled by default.
+ *
+ * @param enabled {@code true} to enable stylus zooming, {@code false} to disable.
+ */
+ public void setStylusZoomEnabled(boolean enabled) {
+ mStylusZoomEnabled = enabled;
+ }
+
+ /**
+ * Return whether the stylus zoom gesture, in which the user uses a stylus and presses the
+ * button, should perform zooming. {@see #setStylusScaleEnabled(boolean)}
+ */
+ public boolean isStylusZoomEnabled() {
+ return mStylusZoomEnabled;
+ }
+
+ /**
+ * Returns {@code true} if a zoom gesture is in progress.
+ */
+ public boolean isInProgress() {
+ return mInProgress;
+ }
+
+ /**
+ * Get the X coordinate of the current gesture's focal point.
+ * If a gesture is in progress, the focal point is between
+ * each of the pointers forming the gesture.
+ *
+ * <p>If {@link #isInProgress()} would return false, the result of this
+ * function is undefined.
+ *
+ * @return X coordinate of the focal point in pixels.
+ */
+ public float getFocusX() {
+ return mFocusX;
+ }
+
+ /**
+ * Get the Y coordinate of the current gesture's focal point.
+ * If a gesture is in progress, the focal point is between
+ * each of the pointers forming the gesture.
+ *
+ * <p>If {@link #isInProgress()} would return false, the result of this
+ * function is undefined.
+ *
+ * @return Y coordinate of the focal point in pixels.
+ */
+ public float getFocusY() {
+ return mFocusY;
+ }
+
+ /**
+ * Return the average distance between each of the pointers forming the
+ * gesture in progress through the focal point.
+ *
+ * @return Distance between pointers in pixels.
+ */
+ public float getCurrentSpan() {
+ return mCurrSpan;
+ }
+
+ /**
+ * Return the average X distance between each of the pointers forming the
+ * gesture in progress through the focal point.
+ *
+ * @return Distance between pointers in pixels.
+ */
+ public float getCurrentSpanX() {
+ return mCurrSpanX;
+ }
+
+ /**
+ * Return the average Y distance between each of the pointers forming the
+ * gesture in progress through the focal point.
+ *
+ * @return Distance between pointers in pixels.
+ */
+ public float getCurrentSpanY() {
+ return mCurrSpanY;
+ }
+
+ /**
+ * Return the previous average distance between each of the pointers forming the
+ * gesture in progress through the focal point.
+ *
+ * @return Previous distance between pointers in pixels.
+ */
+ public float getPreviousSpan() {
+ return mPrevSpan;
+ }
+
+ /**
+ * Return the previous average X distance between each of the pointers forming the
+ * gesture in progress through the focal point.
+ *
+ * @return Previous distance between pointers in pixels.
+ */
+ public float getPreviousSpanX() {
+ return mPrevSpanX;
+ }
+
+ /**
+ * Return the previous average Y distance between each of the pointers forming the
+ * gesture in progress through the focal point.
+ *
+ * @return Previous distance between pointers in pixels.
+ */
+ public float getPreviousSpanY() {
+ return mPrevSpanY;
+ }
+
+ /**
+ * Return the scaling factor from the previous zoom event to the current
+ * event. This value is defined as
+ * ({@link #getCurrentSpan()} / {@link #getPreviousSpan()}).
+ *
+ * @return The current scaling factor.
+ */
+ public float getScaleFactor() {
+ if (inAnchoredZoomMode()) {
+ // Drag is moving up; the further away from the gesture
+ // start, the smaller the span should be, the closer,
+ // the larger the span, and therefore the larger the scale
+ final boolean scaleUp =
+ (mEventBeforeOrAboveStartingGestureEvent
+ && (mCurrSpan < mPrevSpan))
+ || (!mEventBeforeOrAboveStartingGestureEvent
+ && (mCurrSpan > mPrevSpan));
+ final float spanDiff = (Math.abs(1 - (mCurrSpan / mPrevSpan)) * SCALE_FACTOR);
+ return mPrevSpan <= mSpanSlop ? 1 : scaleUp ? (1 + spanDiff) : (1 - spanDiff);
+ }
+ return mPrevSpan > 0 ? mCurrSpan / mPrevSpan : 1;
+ }
+
+ /**
+ * Return the time difference in milliseconds between the previous
+ * accepted zooming event and the current zooming event.
+ *
+ * @return Time difference since the last zooming event in milliseconds.
+ */
+ public long getTimeDelta() {
+ return mCurrTime - mPrevTime;
+ }
+
+ /**
+ * Return the event time of the current event being processed.
+ *
+ * @return Current event time in milliseconds.
+ */
+ public long getEventTime() {
+ return mCurrTime;
+ }
+}
diff --git a/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/WindowInsetsAnimationTest.kt b/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/WindowInsetsAnimationTest.kt
deleted file mode 100644
index 9cabe4e..0000000
--- a/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/WindowInsetsAnimationTest.kt
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright 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.
- */
-package androidx.compose.foundation.layout
-
-import android.view.View
-import androidx.compose.foundation.text.BasicTextField
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.FocusRequester
-import androidx.compose.ui.focus.focusRequester
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.LocalView
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
-import androidx.core.view.WindowInsetsCompat
-import androidx.core.view.WindowInsetsControllerCompat
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.MediumTest
-import androidx.test.filters.SdkSuppress
-import java.util.concurrent.TimeUnit
-import org.junit.After
-import org.junit.Before
-import org.junit.Ignore
-import org.junit.Rule
-import org.junit.Test
-
-@MediumTest
-class WindowInsetsAnimationTest {
- @get:Rule
- val rule = createAndroidComposeRule<WindowInsetsActionBarActivity>()
-
- @Before
- fun setup() {
- rule.activity.createdLatch.await(1, TimeUnit.SECONDS)
- rule.activity.attachedToWindowLatch.await(1, TimeUnit.SECONDS)
- }
-
- @After
- fun teardown() {
- rule.runOnUiThread {
- val window = rule.activity.window
- val view = window.decorView
- WindowInsetsControllerCompat(window, view).hide(WindowInsetsCompat.Type.ime())
- }
- }
-
- @SdkSuppress(minSdkVersion = 22) // b/266742122
- @OptIn(ExperimentalLayoutApi::class)
- @Test
- @Ignore("b/266742122")
- fun imeAnimationWhenShowingIme() {
- val imeAnimationSourceValues = mutableListOf<Int>()
- val imeAnimationTargetValues = mutableListOf<Int>()
- val focusRequester = FocusRequester()
- rule.setContent {
- val density = LocalDensity.current
- val source = WindowInsets.imeAnimationSource
- val target = WindowInsets.imeAnimationTarget
- val sourceBottom = source.getBottom(density)
- imeAnimationSourceValues += sourceBottom
- val targetBottom = target.getBottom(density)
- imeAnimationTargetValues += targetBottom
- BasicTextField(
- value = "Hello World",
- onValueChange = {},
- Modifier.focusRequester(focusRequester)
- )
- }
-
- rule.waitForIdle()
- rule.runOnUiThread {
- focusRequester.requestFocus()
- }
-
- rule.waitForIdle()
- rule.waitUntil(timeoutMillis = 3000) {
- imeAnimationSourceValues.last() > imeAnimationTargetValues.first()
- }
-
- rule.waitUntil(timeoutMillis = 3000) {
- imeAnimationTargetValues.last() == imeAnimationSourceValues.last()
- }
- }
-
- @OptIn(ExperimentalLayoutApi::class)
- @FlakyTest(bugId = 256020254)
- @Test
- fun imeAnimationWhenHidingIme() {
- val imeAnimationSourceValues = mutableListOf<Int>()
- val imeAnimationTargetValues = mutableListOf<Int>()
- val focusRequester = FocusRequester()
- lateinit var view: View
- rule.setContent {
- view = LocalView.current
- val density = LocalDensity.current
- val source = WindowInsets.imeAnimationSource
- val target = WindowInsets.imeAnimationTarget
- val sourceBottom = source.getBottom(density)
- imeAnimationSourceValues += sourceBottom
- val targetBottom = target.getBottom(density)
- imeAnimationTargetValues += targetBottom
- BasicTextField(
- value = "Hello World",
- onValueChange = {},
- Modifier.focusRequester(focusRequester)
- )
- }
-
- rule.waitForIdle()
- rule.runOnUiThread {
- focusRequester.requestFocus()
- }
-
- rule.waitUntil(timeoutMillis = 3000) {
- val target = imeAnimationTargetValues.last()
- val source = imeAnimationSourceValues.last()
- target > imeAnimationSourceValues.first() && target == source
- }
-
- rule.runOnUiThread {
- val window = rule.activity.window
- WindowInsetsControllerCompat(window, view).hide(WindowInsetsCompat.Type.ime())
- }
-
- rule.waitForIdle()
-
- rule.waitUntil(timeoutMillis = 3000) {
- imeAnimationTargetValues.last() == imeAnimationSourceValues.first()
- }
- }
-}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/BasePagerTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/BasePagerTest.kt
index 614d0d4..1350683 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/BasePagerTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/BasePagerTest.kt
@@ -58,6 +58,7 @@
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
+import kotlin.math.absoluteValue
import kotlin.test.assertTrue
import kotlinx.coroutines.CoroutineScope
@@ -357,12 +358,12 @@
pagerState.currentPageOffsetFraction != 0.0f
} // wait for first move from drag
rule.mainClock.advanceTimeUntil {
- pagerState.currentPageOffsetFraction == 0.0f
+ pagerState.currentPageOffsetFraction.absoluteValue < 0.00001
} // wait for fling settling
// pump the clock twice and check we're still settled.
rule.mainClock.advanceTimeByFrame()
rule.mainClock.advanceTimeByFrame()
- assertTrue { pagerState.currentPageOffsetFraction == 0.0f }
+ assertTrue { pagerState.currentPageOffsetFraction.absoluteValue < 0.00001 }
}
}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PageLayoutPositionOnScrollingTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PageLayoutPositionOnScrollingTest.kt
index 69ef744..71635fb 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PageLayoutPositionOnScrollingTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PageLayoutPositionOnScrollingTest.kt
@@ -89,10 +89,10 @@
add(
ParamConfig(
orientation = orientation,
- pageSpacing = pageSpacing,
mainAxisContentPadding = contentPadding,
reverseLayout = reverseLayout,
- layoutDirection = layoutDirection
+ layoutDirection = layoutDirection,
+ pageSpacing = pageSpacing
)
)
}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerStateNonGestureScrollingTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerStateNonGestureScrollingTest.kt
index 219d247..37a5df7 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerStateNonGestureScrollingTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerStateNonGestureScrollingTest.kt
@@ -18,6 +18,7 @@
import androidx.compose.foundation.AutoTestFrameClock
import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.gestures.scrollBy
import androidx.compose.foundation.gestures.snapping.SnapPosition
import androidx.compose.foundation.gestures.snapping.calculateDistanceToDesiredSnapPosition
import androidx.compose.foundation.layout.Box
@@ -36,11 +37,14 @@
import androidx.compose.ui.test.performTouchInput
import androidx.compose.ui.test.swipeLeft
import androidx.compose.ui.test.swipeUp
+import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastMaxBy
import androidx.test.filters.LargeTest
import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertThat
import kotlin.math.abs
+import kotlin.math.roundToInt
import kotlin.test.assertFalse
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
@@ -93,6 +97,42 @@
}
@Test
+ fun pageSizeIsZero_offsetFractionShouldNotBeNan() {
+ // Arrange
+ val zeroPageSize = object : PageSize {
+ override fun Density.calculateMainAxisPageSize(
+ availableSpace: Int,
+ pageSpacing: Int
+ ): Int {
+ return 0
+ }
+ }
+
+ rule.setContent {
+ pagerState = rememberPagerState {
+ DefaultPageCount
+ }
+ HorizontalOrVerticalPager(
+ state = pagerState,
+ modifier = Modifier
+ .size(0.dp)
+ .testTag(PagerTestTag)
+ .onSizeChanged { pagerSize = if (vertical) it.height else it.width },
+ pageSize = zeroPageSize,
+ reverseLayout = config.reverseLayout,
+ pageSpacing = config.pageSpacing,
+ contentPadding = config.mainAxisContentPadding,
+ ) {
+ Page(index = it)
+ }
+ }
+
+ rule.runOnIdle {
+ Truth.assertThat(pagerState.currentPageOffsetFraction).isNotNaN()
+ }
+ }
+
+ @Test
fun initialPageOnPagerState_shouldDisplayThatPageFirst() {
// Arrange
@@ -500,6 +540,105 @@
}
}
+ @Test
+ fun canScrollForwardAndBackward_afterSmallScrollFromStart() {
+ val pageSizePx = 100
+ val pageSizeDp = with(rule.density) { pageSizePx.toDp() }
+ createPager(
+ modifier = Modifier.size(pageSizeDp * 1.5f),
+ pageSize = { PageSize.Fixed(pageSizeDp) })
+
+ val delta = (pageSizePx / 3f).roundToInt()
+
+ runBlocking {
+ withContext(Dispatchers.Main + AutoTestFrameClock()) {
+ // small enough scroll to not cause any new items to be composed or old ones disposed.
+ pagerState.scrollBy(delta.toFloat())
+ }
+ rule.runOnIdle {
+ assertThat(pagerState.firstVisiblePageOffset).isEqualTo(delta)
+ assertThat(pagerState.canScrollForward).isTrue()
+ assertThat(pagerState.canScrollBackward).isTrue()
+ }
+ // and scroll back to start
+ withContext(Dispatchers.Main + AutoTestFrameClock()) {
+ pagerState.scrollBy(-delta.toFloat())
+ }
+ rule.runOnIdle {
+ assertThat(pagerState.canScrollForward).isTrue()
+ assertThat(pagerState.canScrollBackward).isFalse()
+ }
+ }
+ }
+
+ @Test
+ fun canScrollForwardAndBackward_afterSmallScrollFromEnd() {
+ val pageSizePx = 100
+ val pageSizeDp = with(rule.density) { pageSizePx.toDp() }
+ createPager(
+ modifier = Modifier.size(pageSizeDp * 1.5f),
+ pageSize = { PageSize.Fixed(pageSizeDp) })
+ val delta = -(pageSizePx / 3f).roundToInt()
+ runBlocking {
+ withContext(Dispatchers.Main + AutoTestFrameClock()) {
+ // scroll to the end of the list.
+ pagerState.scrollToPage(DefaultPageCount)
+ // small enough scroll to not cause any new items to be composed or old ones disposed.
+ pagerState.scrollBy(delta.toFloat())
+ }
+ rule.runOnIdle {
+ assertThat(pagerState.canScrollForward).isTrue()
+ assertThat(pagerState.canScrollBackward).isTrue()
+ }
+ // and scroll back to the end
+ withContext(Dispatchers.Main + AutoTestFrameClock()) {
+ pagerState.scrollBy(-delta.toFloat())
+ }
+ rule.runOnIdle {
+ assertThat(pagerState.canScrollForward).isFalse()
+ assertThat(pagerState.canScrollBackward).isTrue()
+ }
+ }
+ }
+
+ @Test
+ fun canScrollForwardAndBackward_afterSmallScrollFromEnd_withContentPadding() {
+ val pageSizePx = 100
+ val pageSizeDp = with(rule.density) { pageSizePx.toDp() }
+ val afterContentPaddingDp = with(rule.density) { 2.toDp() }
+ createPager(
+ modifier = Modifier.size(pageSizeDp * 1.5f),
+ pageSize = { PageSize.Fixed(pageSizeDp) },
+ contentPadding = PaddingValues(afterContent = afterContentPaddingDp)
+ )
+
+ val delta = -(pageSizePx / 3f).roundToInt()
+ runBlocking {
+ withContext(Dispatchers.Main + AutoTestFrameClock()) {
+ // scroll to the end of the list.
+ pagerState.scrollToPage(DefaultPageCount)
+
+ assertThat(pagerState.canScrollForward).isFalse()
+ assertThat(pagerState.canScrollBackward).isTrue()
+
+ // small enough scroll to not cause any new pages to be composed or old ones disposed.
+ pagerState.scrollBy(delta.toFloat())
+ }
+ rule.runOnIdle {
+ assertThat(pagerState.canScrollForward).isTrue()
+ assertThat(pagerState.canScrollBackward).isTrue()
+ }
+ // and scroll back to the end
+ withContext(Dispatchers.Main + AutoTestFrameClock()) {
+ pagerState.scrollBy(-delta.toFloat())
+ }
+ rule.runOnIdle {
+ assertThat(pagerState.canScrollForward).isFalse()
+ assertThat(pagerState.canScrollBackward).isTrue()
+ }
+ }
+ }
+
companion object {
@JvmStatic
@Parameterized.Parameters(name = "{0}")
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/MeasuredPage.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/MeasuredPage.kt
index 45748d4..6586433 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/MeasuredPage.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/MeasuredPage.kt
@@ -78,14 +78,14 @@
if (isVertical) {
placeableOffsets[indexInArray] =
requireNotNull(horizontalAlignment) { "null horizontalAlignment" }
- .align(placeable.width, layoutWidth, layoutDirection)
+ .align(placeable.width, layoutWidth, layoutDirection)
placeableOffsets[indexInArray + 1] = mainAxisOffset
mainAxisOffset += placeable.height
} else {
placeableOffsets[indexInArray] = mainAxisOffset
placeableOffsets[indexInArray + 1] =
requireNotNull(verticalAlignment) { "null verticalAlignment" }
- .align(placeable.height, layoutHeight)
+ .align(placeable.height, layoutHeight)
mainAxisOffset += placeable.width
}
}
@@ -110,8 +110,20 @@
}
}
+ fun applyScrollDelta(delta: Int) {
+ offset += delta
+ repeat(placeableOffsets.size) { index ->
+ // placeableOffsets consist of x and y pairs for each placeable.
+ // if isVertical is true then the main axis offsets are located at indexes 1, 3, 5 etc.
+ if ((isVertical && index % 2 == 1) || (!isVertical && index % 2 == 0)) {
+ placeableOffsets[index] += delta
+ }
+ }
+ }
+
private fun getOffset(index: Int) =
IntOffset(placeableOffsets[index * 2], placeableOffsets[index * 2 + 1])
+
private val Placeable.mainAxisSize get() = if (isVertical) height else width
private inline fun IntOffset.copy(mainAxisMap: (Int) -> Int): IntOffset =
IntOffset(if (isVertical) x else mainAxisMap(x), if (isVertical) mainAxisMap(y) else y)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasure.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasure.kt
index 70b4f7a..bef2c4f 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasure.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasure.kt
@@ -22,6 +22,7 @@
import androidx.compose.foundation.gestures.snapping.calculateDistanceToDesiredSnapPosition
import androidx.compose.foundation.layout.Arrangement.Absolute.spacedBy
import androidx.compose.foundation.lazy.layout.LazyLayoutMeasureScope
+import androidx.compose.foundation.lazy.layout.ObservableScopeInvalidator
import androidx.compose.ui.Alignment
import androidx.compose.ui.layout.MeasureResult
import androidx.compose.ui.layout.Placeable
@@ -56,6 +57,7 @@
beyondBoundsPageCount: Int,
pinnedPages: List<Int>,
snapPosition: SnapPosition,
+ placementScopeInvalidator: ObservableScopeInvalidator,
layout: (Int, Int, Placeable.PlacementScope.() -> Unit) -> MeasureResult
): PagerMeasureResult {
require(beforeContentPadding >= 0) { "negative beforeContentPadding" }
@@ -86,7 +88,8 @@
canScrollForward = false,
currentPage = null,
currentPageOffsetFraction = 0.0f,
- snapPosition = snapPosition
+ snapPosition = snapPosition,
+ remeasureNeeded = false
)
} else {
@@ -185,10 +188,24 @@
val maxMainAxis = (maxOffset + afterContentPadding).coerceAtLeast(0)
var currentMainAxisOffset = -currentFirstPageScrollOffset
+ // will be set to true if we composed some items only to know their size and apply scroll,
+ // while in the end this item will not end up in the visible viewport. we will need an
+ // extra remeasure in order to dispose such items.
+ var remeasureNeeded = false
+
// first we need to skip pages we already composed while composing backward
- visiblePages.fastForEach {
- index++
- currentMainAxisOffset += pageSizeWithSpacing
+ var indexInVisibleItems = 0
+
+ while (indexInVisibleItems < visiblePages.size) {
+ if (currentMainAxisOffset >= maxMainAxis) {
+ // this item is out of the bounds and will not be visible.
+ visiblePages.removeAt(indexInVisibleItems)
+ remeasureNeeded = true
+ } else {
+ index++
+ currentMainAxisOffset += pageSizeWithSpacing
+ indexInVisibleItems++
+ }
}
debugLog { "Composing Forward Starting at Index=$index" }
@@ -223,9 +240,10 @@
}
if (currentMainAxisOffset <= minOffset && index != pageCount - 1) {
- // this page is offscreen and will not be placed. advance firstVisiblePage
+ // this page is offscreen and will not be visible. advance currentFirstPage
currentFirstPage = index + 1
currentFirstPageScrollOffset -= pageSizeWithSpacing
+ remeasureNeeded = true
} else {
maxCrossAxis = maxOf(maxCrossAxis, measuredPage.crossAxisSize)
visiblePages.add(measuredPage)
@@ -401,11 +419,14 @@
val currentPagePositionOffset = (newCurrentPage?.offset ?: 0)
- val currentPageOffsetFraction =
+ val currentPageOffsetFraction = if (pageSizeWithSpacing == 0) {
+ 0.0f
+ } else {
((snapOffset - currentPagePositionOffset) / (pageSizeWithSpacing.toFloat())).coerceIn(
MinPageOffset,
MaxPageOffset
)
+ }
debugLog {
"Finished Measure Pass" +
@@ -421,6 +442,8 @@
positionedPages.fastForEach {
it.place(this)
}
+ // we attach it during the placement so PagerState can trigger re-placement
+ placementScopeInvalidator.attachToScope()
},
viewportStartOffset = -beforeContentPadding,
viewportEndOffset = maxOffset + afterContentPadding,
@@ -434,7 +457,8 @@
canScrollForward = index < pageCount || currentMainAxisOffset > maxOffset,
currentPage = newCurrentPage,
currentPageOffsetFraction = currentPageOffsetFraction,
- snapPosition = snapPosition
+ snapPosition = snapPosition,
+ remeasureNeeded = remeasureNeeded
)
}
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasurePolicy.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasurePolicy.kt
index f255c9e..f5e80fa 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasurePolicy.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasurePolicy.kt
@@ -181,8 +181,8 @@
reverseLayout = reverseLayout,
pinnedPages = pinnedPages,
snapPosition = snapPosition,
+ placementScopeInvalidator = state.placementScopeInvalidator,
layout = { width, height, placement ->
- state.remeasureTrigger // read state to trigger remeasures on state write
layout(
containerConstraints.constrainWidth(width + totalHorizontalPadding),
containerConstraints.constrainHeight(height + totalVerticalPadding),
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasureResult.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasureResult.kt
index c236604..4d193eb 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasureResult.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasureResult.kt
@@ -21,10 +21,11 @@
import androidx.compose.foundation.gestures.snapping.SnapPosition
import androidx.compose.ui.layout.MeasureResult
import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.util.fastForEach
@OptIn(ExperimentalFoundationApi::class)
internal class PagerMeasureResult(
- override val visiblePagesInfo: List<PageInfo>,
+ override val visiblePagesInfo: List<MeasuredPage>,
override val pageSize: Int,
override val pageSpacing: Int,
override val afterContentPadding: Int,
@@ -35,13 +36,86 @@
override val beyondBoundsPageCount: Int,
val firstVisiblePage: MeasuredPage?,
val currentPage: MeasuredPage?,
- val currentPageOffsetFraction: Float,
- val firstVisiblePageScrollOffset: Int,
- val canScrollForward: Boolean,
+ var currentPageOffsetFraction: Float,
+ var firstVisiblePageScrollOffset: Int,
+ var canScrollForward: Boolean,
override val snapPosition: SnapPosition,
measureResult: MeasureResult,
+ /** True when extra remeasure is required. */
+ val remeasureNeeded: Boolean,
) : PagerLayoutInfo, MeasureResult by measureResult {
override val viewportSize: IntSize
get() = IntSize(width, height)
override val beforeContentPadding: Int get() = -viewportStartOffset
+
+ val canScrollBackward
+ get() = (firstVisiblePage?.index ?: 0) != 0 || firstVisiblePageScrollOffset != 0
+
+ /**
+ * Tries to apply a scroll [delta] for this layout info. In some cases we can apply small
+ * scroll deltas by just changing the offsets for each [visiblePagesInfo].
+ * But we can only do so if after applying the delta we would not need to compose a new item
+ * or dispose an item which is currently visible. In this case this function will not apply
+ * the [delta] and return false.
+ *
+ * @return true if we can safely apply a passed scroll [delta] to this layout info.
+ * If true is returned, only the placement phase is needed to apply new offsets.
+ * If false is returned, it means we have to rerun the full measure phase to apply the [delta].
+ */
+ fun tryToApplyScrollWithoutRemeasure(delta: Int): Boolean {
+ val pageSizeWithSpacing = pageSize + pageSpacing
+ if (remeasureNeeded || visiblePagesInfo.isEmpty() || firstVisiblePage == null ||
+ // applying this delta will change firstVisibleItem
+ (firstVisiblePageScrollOffset - delta) !in 0 until pageSizeWithSpacing
+ ) {
+ return false
+ }
+
+ val deltaFraction = if (pageSizeWithSpacing != 0) {
+ (delta / pageSizeWithSpacing.toFloat())
+ } else {
+ 0.0f
+ }
+
+ val newCurrentPageOffsetFraction = currentPageOffsetFraction - deltaFraction
+ if (currentPage == null ||
+ // applying this delta will change current page
+ newCurrentPageOffsetFraction >= MaxPageOffset ||
+ newCurrentPageOffsetFraction <= MinPageOffset
+ ) {
+ return false
+ }
+
+ val first = visiblePagesInfo.first()
+ val last = visiblePagesInfo.last()
+ val canApply = if (delta < 0) {
+ // scrolling forward
+ val deltaToFirstItemChange =
+ first.offset + pageSizeWithSpacing - viewportStartOffset
+ val deltaToLastItemChange =
+ last.offset + pageSizeWithSpacing - viewportEndOffset
+ minOf(deltaToFirstItemChange, deltaToLastItemChange) > -delta
+ } else {
+ // scrolling backward
+ val deltaToFirstItemChange =
+ viewportStartOffset - first.offset
+ val deltaToLastItemChange =
+ viewportEndOffset - last.offset
+ minOf(deltaToFirstItemChange, deltaToLastItemChange) > delta
+ }
+ return if (canApply) {
+ currentPageOffsetFraction -= deltaFraction
+ firstVisiblePageScrollOffset -= delta
+ visiblePagesInfo.fastForEach {
+ it.applyScrollDelta(delta)
+ }
+ if (!canScrollForward && delta > 0) {
+ // we scrolled backward, so now we can scroll forward
+ canScrollForward = true
+ }
+ true
+ } else {
+ false
+ }
+ }
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerScrollPosition.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerScrollPosition.kt
index 425b1c4..c7a96e6 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerScrollPosition.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerScrollPosition.kt
@@ -106,6 +106,10 @@
currentPageOffsetFraction = offsetFraction
}
+ fun updateCurrentPageOffsetFraction(offsetFraction: Float) {
+ currentPageOffsetFraction = offsetFraction
+ }
+
fun currentAbsoluteScrollOffset(): Int {
return ((currentPage +
currentPageOffsetFraction) * state.pageSizeWithSpacing).roundToInt()
@@ -113,9 +117,12 @@
fun applyScrollDelta(delta: Int) {
debugLog { "Applying Delta=$delta" }
- val fractionUpdate = delta / state.pageSizeWithSpacing.toFloat()
+ val fractionUpdate = if (state.pageSizeWithSpacing == 0) {
+ 0.0f
+ } else {
+ delta / state.pageSizeWithSpacing.toFloat()
+ }
currentPageOffsetFraction += fractionUpdate
- state.remeasureTrigger = Unit // trigger remeasure
}
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
index 5781bf4..0459e93 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
@@ -31,6 +31,7 @@
import androidx.compose.foundation.lazy.layout.LazyLayoutBeyondBoundsInfo
import androidx.compose.foundation.lazy.layout.LazyLayoutPinnedItemList
import androidx.compose.foundation.lazy.layout.LazyLayoutPrefetchState
+import androidx.compose.foundation.lazy.layout.ObservableScopeInvalidator
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.derivedStateOf
@@ -46,11 +47,12 @@
import androidx.compose.runtime.snapshots.Snapshot
import androidx.compose.runtime.structuralEqualityPolicy
import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.layout.AlignmentLine
+import androidx.compose.ui.layout.MeasureResult
import androidx.compose.ui.layout.Remeasurement
import androidx.compose.ui.layout.RemeasurementModifier
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import kotlin.math.abs
import kotlin.math.absoluteValue
@@ -175,8 +177,6 @@
private var isScrollingForward: Boolean by mutableStateOf(false)
- internal var remeasureTrigger by mutableStateOf(Unit, neverEqualPolicy())
-
internal val scrollPosition = PagerScrollPosition(currentPage, currentPageOffsetFraction, this)
internal var firstVisiblePage = currentPage
@@ -218,14 +218,28 @@
val newValue = absolute.coerceIn(0.0f, maxScrollOffset)
val changed = absolute != newValue
val consumed = newValue - currentScrollPosition
-
+ previousPassDelta = consumed
if (consumed.absoluteValue != 0.0f) {
isScrollingForward = consumed > 0.0f
}
val consumedInt = consumed.roundToInt()
- scrollPosition.applyScrollDelta(consumedInt)
- previousPassDelta = consumed
+
+ val layoutInfo = pagerLayoutInfoState.value
+
+ if (layoutInfo.tryToApplyScrollWithoutRemeasure(-consumedInt)) {
+ debugLog { "Will Apply Without Remeasure" }
+ applyMeasureResult(
+ result = layoutInfo,
+ visibleItemsStayedTheSame = true
+ )
+ // we don't need to remeasure, so we only trigger re-placement:
+ placementScopeInvalidator.invalidateScope()
+ } else {
+ debugLog { "Will Apply With Remeasure" }
+ scrollPosition.applyScrollDelta(consumedInt)
+ remeasurement?.forceRemeasure()
+ }
accumulator = consumed - consumedInt
// Avoid floating-point rounding error
@@ -260,7 +274,8 @@
private var wasPrefetchingForward = false
/** Backing state for PagerLayoutInfo */
- private var pagerLayoutInfoState = mutableStateOf<PagerLayoutInfo>(EmptyLayoutInfo)
+ private var pagerLayoutInfoState =
+ mutableStateOf(EmptyLayoutInfo, neverEqualPolicy())
/**
* A [PagerLayoutInfo] that contains useful information about the Pager's last layout pass.
@@ -425,6 +440,8 @@
internal val nearestRange: IntRange by scrollPosition.nearestRangeState
+ internal val placementScopeInvalidator = ObservableScopeInvalidator()
+
/**
* Scroll (jump immediately) to a given [page].
*
@@ -592,20 +609,27 @@
/**
* Updates the state with the new calculated scroll position and consumed scroll.
*/
- internal fun applyMeasureResult(result: PagerMeasureResult) {
+ internal fun applyMeasureResult(
+ result: PagerMeasureResult,
+ visibleItemsStayedTheSame: Boolean = false
+ ) {
debugLog { "Applying Measure Result" }
- scrollPosition.updateFromMeasureResult(result)
+ if (visibleItemsStayedTheSame) {
+ scrollPosition.updateCurrentPageOffsetFraction(result.currentPageOffsetFraction)
+ } else {
+ scrollPosition.updateFromMeasureResult(result)
+ cancelPrefetchIfVisibleItemsChanged(result)
+ }
pagerLayoutInfoState.value = result
canScrollForward = result.canScrollForward
- canScrollBackward = (result.firstVisiblePage?.index ?: 0) != 0 ||
- result.firstVisiblePageScrollOffset != 0
+ canScrollBackward = result.canScrollBackward
numMeasurePasses++
result.firstVisiblePage?.let { firstVisiblePage = it.index }
firstVisiblePageOffset = result.firstVisiblePageScrollOffset
- cancelPrefetchIfVisibleItemsChanged(result)
tryRunPrefetch(result)
maxScrollOffset = result.calculateNewMaxScrollOffset(pageCount)
- debugLog { "Finished Applying Measure Result" }
+ debugLog { "Finished Applying Measure Result" +
+ "\nNew maxScrollOffset=$maxScrollOffset" }
}
private fun tryRunPrefetch(result: PagerMeasureResult) = Snapshot.withoutReadObservation {
@@ -725,20 +749,33 @@
internal const val PagesToPrefetch = 1
@OptIn(ExperimentalFoundationApi::class)
-internal object EmptyLayoutInfo : PagerLayoutInfo {
- override val visiblePagesInfo: List<PageInfo> = emptyList()
- override val pageSize: Int = 0
- override val pageSpacing: Int = 0
- override val beforeContentPadding: Int = 0
- override val afterContentPadding: Int = 0
- override val viewportSize: IntSize = IntSize.Zero
- override val orientation: Orientation = Orientation.Horizontal
- override val viewportStartOffset: Int = 0
- override val viewportEndOffset: Int = 0
- override val reverseLayout: Boolean = false
- override val beyondBoundsPageCount: Int = 0
- override val snapPosition: SnapPosition = SnapPosition.Start
-}
+internal val EmptyLayoutInfo = PagerMeasureResult(
+ visiblePagesInfo = emptyList(),
+ pageSize = 0,
+ pageSpacing = 0,
+ afterContentPadding = 0,
+ orientation = Orientation.Horizontal,
+ viewportStartOffset = 0,
+ viewportEndOffset = 0,
+ reverseLayout = false,
+ beyondBoundsPageCount = 0,
+ firstVisiblePage = null,
+ firstVisiblePageScrollOffset = 0,
+ currentPage = null,
+ currentPageOffsetFraction = 0.0f,
+ canScrollForward = false,
+ snapPosition = SnapPosition.Start,
+ measureResult = object : MeasureResult {
+ override val width: Int = 0
+
+ override val height: Int = 0
+ @Suppress("PrimitiveInCollection")
+ override val alignmentLines: Map<AlignmentLine, Int> = mapOf()
+
+ override fun placeChildren() {}
+ },
+ remeasureNeeded = false
+)
private val UnitDensity = object : Density {
override val density: Float = 1f
@@ -757,7 +794,7 @@
@OptIn(ExperimentalFoundationApi::class)
private fun PagerMeasureResult.calculateNewMaxScrollOffset(pageCount: Int): Float {
- return beforeContentPadding +
+ return (beforeContentPadding +
pageCount * (pageSpacing + pageSize).toFloat() +
- afterContentPadding - pageSpacing - singleAxisViewPort
+ afterContentPadding - pageSpacing - singleAxisViewPort).coerceAtLeast(0.0f)
}
diff --git a/compose/integration-tests/material-catalog/build.gradle b/compose/integration-tests/material-catalog/build.gradle
index 80f662f..c7bf51b 100644
--- a/compose/integration-tests/material-catalog/build.gradle
+++ b/compose/integration-tests/material-catalog/build.gradle
@@ -45,7 +45,7 @@
implementation project(":compose:runtime:runtime")
implementation project(":compose:foundation:foundation-layout")
implementation project(":compose:ui:ui")
- implementation project(":compose:material:material")
+ implementation("androidx.compose.material:material:1.6.0-beta01")
implementation project(":compose:material3:material3")
implementation project(":compose:material:material:integration-tests:material-catalog")
implementation project(":compose:material3:material3:integration-tests:material3-catalog")
diff --git a/compose/material/material/integration-tests/material-catalog/build.gradle b/compose/material/material/integration-tests/material-catalog/build.gradle
index 15bc9f9..49c18e2 100644
--- a/compose/material/material/integration-tests/material-catalog/build.gradle
+++ b/compose/material/material/integration-tests/material-catalog/build.gradle
@@ -30,8 +30,8 @@
implementation project(":compose:runtime:runtime")
implementation project(":compose:foundation:foundation-layout")
implementation project(":compose:ui:ui")
- implementation project(":compose:material:material")
- implementation project(":compose:material:material:material-samples")
+ implementation("androidx.compose.material:material:1.6.0-beta01")
+ implementation("androidx.compose.material:material-samples:1.6.0-beta01")
implementation project(":navigation:navigation-compose")
}
diff --git a/compose/material3/material3-adaptive/api/current.txt b/compose/material3/material3-adaptive/api/current.txt
index e1ffa3b..6ed20b5 100644
--- a/compose/material3/material3-adaptive/api/current.txt
+++ b/compose/material3/material3-adaptive/api/current.txt
@@ -17,8 +17,8 @@
public final class AndroidWindowInfo_androidKt {
method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.util.List<androidx.window.layout.FoldingFeature>> collectFoldingFeaturesAsState();
- method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.IntSize> collectWindowSizeAsState();
method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.WindowAdaptiveInfo currentWindowAdaptiveInfo();
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static long currentWindowSize();
}
@SuppressCompatibility @kotlin.RequiresOptIn(message="This material3-adaptive API is experimental and is likely to change or to be" + "removed in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalMaterial3AdaptiveApi {
diff --git a/compose/material3/material3-adaptive/api/restricted_current.txt b/compose/material3/material3-adaptive/api/restricted_current.txt
index e1ffa3b..6ed20b5 100644
--- a/compose/material3/material3-adaptive/api/restricted_current.txt
+++ b/compose/material3/material3-adaptive/api/restricted_current.txt
@@ -17,8 +17,8 @@
public final class AndroidWindowInfo_androidKt {
method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<java.util.List<androidx.window.layout.FoldingFeature>> collectFoldingFeaturesAsState();
- method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.compose.ui.unit.IntSize> collectWindowSizeAsState();
method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.WindowAdaptiveInfo currentWindowAdaptiveInfo();
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static long currentWindowSize();
}
@SuppressCompatibility @kotlin.RequiresOptIn(message="This material3-adaptive API is experimental and is likely to change or to be" + "removed in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalMaterial3AdaptiveApi {
diff --git a/compose/material3/material3-adaptive/benchmark/build.gradle b/compose/material3/material3-adaptive/benchmark/build.gradle
new file mode 100644
index 0000000..48f6444
--- /dev/null
+++ b/compose/material3/material3-adaptive/benchmark/build.gradle
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2023 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.
+ */
+
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.library")
+ id("AndroidXComposePlugin")
+ id("org.jetbrains.kotlin.android")
+ id("androidx.benchmark")
+}
+
+dependencies {
+ androidTestImplementation(project(":compose:material3:material3-adaptive"))
+ androidTestImplementation(project(":benchmark:benchmark-junit4"))
+ androidTestImplementation(project(":compose:runtime:runtime"))
+ androidTestImplementation(project(":compose:benchmark-utils"))
+ androidTestImplementation(libs.testRules)
+ androidTestImplementation(libs.kotlinStdlib)
+ androidTestImplementation(libs.kotlinTestCommon)
+ androidTestImplementation(libs.junit)
+}
+
+android {
+ namespace "androidx.compose.material3.adaptive.benchmark"
+}
diff --git a/compose/material3/material3-adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/ListDetailPaneScaffoldBenchmark.kt b/compose/material3/material3-adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/ListDetailPaneScaffoldBenchmark.kt
new file mode 100644
index 0000000..b2e7be5
--- /dev/null
+++ b/compose/material3/material3-adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/ListDetailPaneScaffoldBenchmark.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2023 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.
+ */
+
+package androidx.compose.material3.adaptive.benchmark
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.adaptive.AnimatedPane
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.material3.adaptive.ListDetailPaneScaffold
+import androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole
+import androidx.compose.material3.adaptive.calculateListDetailPaneScaffoldState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.testutils.LayeredComposeTestCase
+import androidx.compose.testutils.ToggleableTestCase
+import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
+import androidx.compose.testutils.benchmark.benchmarkFirstCompose
+import androidx.compose.testutils.benchmark.benchmarkToFirstPixel
+import androidx.compose.testutils.benchmark.toggleStateBenchmarkComposeMeasureLayout
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.testTag
+import org.junit.Rule
+import org.junit.Test
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+class ListDetailPaneScaffoldBenchmark {
+ @get:Rule
+ val benchmarkRule = ComposeBenchmarkRule()
+
+ @Test
+ fun singlePane_firstPixel() {
+ benchmarkRule.benchmarkToFirstPixel {
+ ListDetailPaneScaffoldTestCase().apply {
+ currentScaffoldDirective = singlePaneDirective
+ }
+ }
+ }
+
+ @Test
+ fun dualPane_firstPixel() {
+ benchmarkRule.benchmarkToFirstPixel {
+ ListDetailPaneScaffoldTestCase().apply {
+ currentScaffoldDirective = dualPaneDirective
+ }
+ }
+ }
+
+ @Test
+ fun singlePane_firstCompose() {
+ benchmarkRule.benchmarkFirstCompose {
+ ListDetailPaneScaffoldTestCase().apply {
+ currentScaffoldDirective = singlePaneDirective
+ }
+ }
+ }
+
+ @Test
+ fun dualPane_firstCompose() {
+ benchmarkRule.benchmarkFirstCompose {
+ ListDetailPaneScaffoldTestCase().apply {
+ currentScaffoldDirective = dualPaneDirective
+ }
+ }
+ }
+
+ @Test
+ fun singlePane_navigateToDetail() {
+ benchmarkRule.toggleStateBenchmarkComposeMeasureLayout(
+ {
+ object : ListDetailPaneScaffoldTestCase() {
+ override fun toggleState() {
+ currentDestination = ListDetailPaneScaffoldRole.Detail
+ }
+ }.apply {
+ currentScaffoldDirective = singlePaneDirective
+ }
+ },
+ assertOneRecomposition = false,
+ )
+ }
+
+ @Test
+ fun dualPane_navigateToExtra() {
+ benchmarkRule.toggleStateBenchmarkComposeMeasureLayout(
+ {
+ object : ListDetailPaneScaffoldTestCase() {
+ override fun toggleState() {
+ currentDestination = ListDetailPaneScaffoldRole.Extra
+ }
+ }.apply {
+ currentScaffoldDirective = dualPaneDirective
+ }
+ },
+ assertOneRecomposition = false,
+ )
+ }
+}
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+internal open class ListDetailPaneScaffoldTestCase : LayeredComposeTestCase(), ToggleableTestCase {
+ var currentScaffoldDirective by mutableStateOf(singlePaneDirective)
+ var currentDestination by mutableStateOf(ListDetailPaneScaffoldRole.List)
+
+ @Composable
+ override fun MeasuredContent() {
+ ListDetailPaneScaffold(
+ scaffoldState = calculateListDetailPaneScaffoldState(
+ scaffoldDirective = currentScaffoldDirective,
+ currentPaneDestination = currentDestination
+ ),
+ listPane = {
+ AnimatedPane(
+ modifier = Modifier.testTag(tag = "ListPane")
+ ) {
+ Box(modifier = Modifier.fillMaxSize().background(Color.Red))
+ }
+ },
+ extraPane = {
+ AnimatedPane(
+ modifier = Modifier.testTag(tag = "ExtraPane")
+ ) {
+ Box(modifier = Modifier.fillMaxSize().background(Color.Blue))
+ }
+ }
+ ) {
+ AnimatedPane(
+ modifier = Modifier.testTag(tag = "DetailPane")
+ ) {
+ Box(modifier = Modifier.fillMaxSize().background(Color.Yellow))
+ }
+ }
+ }
+
+ override fun toggleState() {}
+}
diff --git a/compose/material3/material3-adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/SupportingPaneScaffoldBenchmark.kt b/compose/material3/material3-adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/SupportingPaneScaffoldBenchmark.kt
new file mode 100644
index 0000000..932422a
--- /dev/null
+++ b/compose/material3/material3-adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/SupportingPaneScaffoldBenchmark.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2023 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.
+ */
+
+package androidx.compose.material3.adaptive.benchmark
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.adaptive.AnimatedPane
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.material3.adaptive.SupportingPaneScaffold
+import androidx.compose.material3.adaptive.SupportingPaneScaffoldRole
+import androidx.compose.material3.adaptive.calculateSupportingPaneScaffoldState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.testutils.LayeredComposeTestCase
+import androidx.compose.testutils.ToggleableTestCase
+import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
+import androidx.compose.testutils.benchmark.benchmarkFirstCompose
+import androidx.compose.testutils.benchmark.benchmarkToFirstPixel
+import androidx.compose.testutils.benchmark.toggleStateBenchmarkComposeMeasureLayout
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.testTag
+import org.junit.Rule
+import org.junit.Test
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+class SupportingPaneScaffoldBenchmark {
+ @get:Rule
+ val benchmarkRule = ComposeBenchmarkRule()
+
+ @Test
+ fun singlePane_firstPixel() {
+ benchmarkRule.benchmarkToFirstPixel {
+ SupportingPaneScaffoldTestCase().apply {
+ currentScaffoldDirective = singlePaneDirective
+ }
+ }
+ }
+
+ @Test
+ fun dualPane_firstPixel() {
+ benchmarkRule.benchmarkToFirstPixel {
+ SupportingPaneScaffoldTestCase().apply {
+ currentScaffoldDirective = dualPaneDirective
+ }
+ }
+ }
+
+ @Test
+ fun singlePane_firstCompose() {
+ benchmarkRule.benchmarkFirstCompose {
+ SupportingPaneScaffoldTestCase().apply {
+ currentScaffoldDirective = singlePaneDirective
+ }
+ }
+ }
+
+ @Test
+ fun dualPane_firstCompose() {
+ benchmarkRule.benchmarkFirstCompose {
+ SupportingPaneScaffoldTestCase().apply {
+ currentScaffoldDirective = dualPaneDirective
+ }
+ }
+ }
+
+ @Test
+ fun singlePane_navigateToSupporting() {
+ benchmarkRule.toggleStateBenchmarkComposeMeasureLayout(
+ {
+ object : SupportingPaneScaffoldTestCase() {
+ override fun toggleState() {
+ currentDestination = SupportingPaneScaffoldRole.Supporting
+ }
+ }.apply {
+ currentScaffoldDirective = singlePaneDirective
+ }
+ },
+ assertOneRecomposition = false,
+ )
+ }
+
+ @Test
+ fun dualPane_navigateToExtra() {
+ benchmarkRule.toggleStateBenchmarkComposeMeasureLayout(
+ {
+ object : SupportingPaneScaffoldTestCase() {
+ override fun toggleState() {
+ currentDestination = SupportingPaneScaffoldRole.Extra
+ }
+ }.apply {
+ currentScaffoldDirective = dualPaneDirective
+ }
+ },
+ assertOneRecomposition = false,
+ )
+ }
+}
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+internal open class SupportingPaneScaffoldTestCase : LayeredComposeTestCase(), ToggleableTestCase {
+ var currentScaffoldDirective by mutableStateOf(singlePaneDirective)
+ var currentDestination by mutableStateOf(SupportingPaneScaffoldRole.Main)
+
+ @Composable
+ override fun MeasuredContent() {
+ SupportingPaneScaffold(
+ scaffoldState = calculateSupportingPaneScaffoldState(
+ scaffoldDirective = currentScaffoldDirective,
+ currentPaneDestination = currentDestination
+ ),
+ supportingPane = {
+ AnimatedPane(
+ modifier = Modifier.testTag(tag = "SupportingPane")
+ ) {
+ Box(modifier = Modifier.fillMaxSize().background(Color.Red))
+ }
+ },
+ extraPane = {
+ AnimatedPane(
+ modifier = Modifier.testTag(tag = "ExtraPane")
+ ) {
+ Box(modifier = Modifier.fillMaxSize().background(Color.Blue))
+ }
+ }
+ ) {
+ AnimatedPane(
+ modifier = Modifier.testTag(tag = "MainPane")
+ ) {
+ Box(modifier = Modifier.fillMaxSize().background(Color.Yellow))
+ }
+ }
+ }
+
+ override fun toggleState() {}
+}
diff --git a/compose/material3/material3-adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/TestUtils.kt b/compose/material3/material3-adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/TestUtils.kt
new file mode 100644
index 0000000..4b2e283d
--- /dev/null
+++ b/compose/material3/material3-adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/TestUtils.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2023 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.
+ */
+
+package androidx.compose.material3.adaptive.benchmark
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.material3.adaptive.PaneScaffoldDirective
+import androidx.compose.ui.unit.dp
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+val singlePaneDirective = PaneScaffoldDirective(
+ contentPadding = PaddingValues(16.dp),
+ maxHorizontalPartitions = 1,
+ horizontalPartitionSpacerSize = 0.dp,
+ maxVerticalPartitions = 1,
+ verticalPartitionSpacerSize = 0.dp,
+ excludedBounds = emptyList()
+)
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+val dualPaneDirective = PaneScaffoldDirective(
+ contentPadding = PaddingValues(24.dp),
+ maxHorizontalPartitions = 2,
+ horizontalPartitionSpacerSize = 24.dp,
+ maxVerticalPartitions = 1,
+ verticalPartitionSpacerSize = 0.dp,
+ excludedBounds = emptyList()
+)
diff --git a/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/CollectWindowSizeAsStateTest.kt b/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/CollectWindowSizeAsStateTest.kt
index ec1328d..8ff84ab 100644
--- a/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/CollectWindowSizeAsStateTest.kt
+++ b/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/CollectWindowSizeAsStateTest.kt
@@ -58,7 +58,7 @@
testConfiguration.screenWidthDp = mockWindowSize.value.width
testConfiguration.screenHeightDp = mockWindowSize.value.height
CompositionLocalProvider(LocalConfiguration provides testConfiguration) {
- actualWindowSize = collectWindowSizeAsState().value
+ actualWindowSize = currentWindowSize()
}
}
diff --git a/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/LargeScreenTestUtils.kt b/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/LargeScreenTestUtils.kt
index 6aa8ade..8ed7c70 100644
--- a/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/LargeScreenTestUtils.kt
+++ b/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/LargeScreenTestUtils.kt
@@ -37,7 +37,7 @@
setContent {
val currentDensity = LocalDensity.current
val windowSize = with(currentDensity) {
- collectWindowSizeAsState().value.toSize().toDpSize();
+ currentWindowSize().toSize().toDpSize();
}
val simulatedDensity = Density(
currentDensity.density * (windowSize.width / simulatedWidth)
diff --git a/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/AndroidWindowInfo.android.kt b/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/AndroidWindowInfo.android.kt
index 5b7b34c..37ff942 100644
--- a/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/AndroidWindowInfo.android.kt
+++ b/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/AndroidWindowInfo.android.kt
@@ -22,7 +22,6 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
@@ -48,39 +47,31 @@
WindowAdaptiveInfo(
WindowSizeClass.calculateFromSize(
with(LocalDensity.current) {
- collectWindowSizeAsState().value.toSize().toDpSize()
+ currentWindowSize().toSize().toDpSize()
}
),
calculatePosture(collectFoldingFeaturesAsState().value)
)
/**
- * Collects the current window size from [WindowMetricsCalculator] in to a [State].
+ * Returns and automatically update the current window size from [WindowMetricsCalculator].
*
- * @return a [State] of [IntSize] that represents the current window size.
+ * @return an [IntSize] that represents the current window size.
*/
@ExperimentalMaterial3AdaptiveApi
@Composable
-fun collectWindowSizeAsState(): State<IntSize> {
- val size = remember {
- mutableStateOf(IntSize(0, 0))
- }
-
+fun currentWindowSize(): IntSize {
// Observe view configuration changes and recalculate the size class on each change. We can't
// use Activity#onConfigurationChanged as this will sometimes fail to be called on different
// API levels, hence why this function needs to be @Composable so we can observe the
// ComposeView's configuration changes.
- val context = LocalContext.current
- size.value = remember(context, LocalConfiguration.current) {
- val windowBounds =
- WindowMetricsCalculator
- .getOrCreate()
- .computeCurrentWindowMetrics(context)
- .bounds
- IntSize(windowBounds.width(), windowBounds.height())
- }
-
- return size
+ LocalConfiguration.current
+ val windowBounds =
+ WindowMetricsCalculator
+ .getOrCreate()
+ .computeCurrentWindowMetrics(LocalContext.current)
+ .bounds
+ return IntSize(windowBounds.width(), windowBounds.height())
}
/**
diff --git a/compose/material3/material3/api/1.2.0-beta01.txt b/compose/material3/material3/api/1.2.0-beta01.txt
index 7ca2ade..7138834 100644
--- a/compose/material3/material3/api/1.2.0-beta01.txt
+++ b/compose/material3/material3/api/1.2.0-beta01.txt
@@ -1097,9 +1097,6 @@
method @androidx.compose.runtime.Composable public static void OutlinedTextField(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional int minLines, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
}
- @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public interface PlainTooltipState extends androidx.compose.material3.BasicTooltipState {
- }
-
public final class ProgressIndicatorDefaults {
method @androidx.compose.runtime.Composable public long getCircularColor();
method public int getCircularDeterminateStrokeCap();
@@ -1184,9 +1181,6 @@
property public final long titleContentColor;
}
- @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public interface RichTooltipState extends androidx.compose.material3.BasicTooltipState {
- }
-
public final class ScaffoldDefaults {
method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.WindowInsets getContentWindowInsets();
property @androidx.compose.runtime.Composable public final androidx.compose.foundation.layout.WindowInsets contentWindowInsets;
@@ -1895,10 +1889,6 @@
method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.TimePickerState,?> Saver();
}
- @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public interface TooltipBoxScope {
- method @Deprecated public androidx.compose.ui.Modifier tooltipTrigger(androidx.compose.ui.Modifier);
- }
-
@SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class TooltipDefaults {
method @androidx.compose.runtime.Composable public long getPlainTooltipContainerColor();
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getPlainTooltipContainerShape();
@@ -1916,13 +1906,9 @@
public final class TooltipKt {
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltip(optional androidx.compose.ui.Modifier modifier, optional long contentColor, optional long containerColor, optional float tonalElevation, optional float shadowElevation, optional androidx.compose.ui.graphics.Shape shape, kotlin.jvm.functions.Function0<kotlin.Unit> content);
- method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltipBox(kotlin.jvm.functions.Function0<kotlin.Unit> tooltip, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional androidx.compose.material3.PlainTooltipState tooltipState, optional androidx.compose.ui.graphics.Shape shape, optional long containerColor, optional float tonalElevation, optional float shadowElevation, optional long contentColor, kotlin.jvm.functions.Function1<? super androidx.compose.material3.TooltipBoxScope,kotlin.Unit> content);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltip(optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, optional androidx.compose.ui.graphics.Shape shape, kotlin.jvm.functions.Function0<kotlin.Unit> text);
- method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltipBox(kotlin.jvm.functions.Function0<kotlin.Unit> text, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional androidx.compose.material3.RichTooltipState tooltipState, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function1<? super androidx.compose.material3.TooltipBoxScope,kotlin.Unit> content);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TooltipBox(androidx.compose.ui.window.PopupPositionProvider positionProvider, kotlin.jvm.functions.Function0<kotlin.Unit> tooltip, androidx.compose.material3.TooltipState state, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional boolean enableUserInput, kotlin.jvm.functions.Function0<kotlin.Unit> content);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.material3.TooltipState TooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
- method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.PlainTooltipState rememberPlainTooltipState(optional androidx.compose.foundation.MutatorMutex mutatorMutex);
- method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.RichTooltipState rememberRichTooltipState(boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.TooltipState rememberTooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
}
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index 7ca2ade..7138834 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -1097,9 +1097,6 @@
method @androidx.compose.runtime.Composable public static void OutlinedTextField(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional int minLines, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
}
- @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public interface PlainTooltipState extends androidx.compose.material3.BasicTooltipState {
- }
-
public final class ProgressIndicatorDefaults {
method @androidx.compose.runtime.Composable public long getCircularColor();
method public int getCircularDeterminateStrokeCap();
@@ -1184,9 +1181,6 @@
property public final long titleContentColor;
}
- @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public interface RichTooltipState extends androidx.compose.material3.BasicTooltipState {
- }
-
public final class ScaffoldDefaults {
method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.WindowInsets getContentWindowInsets();
property @androidx.compose.runtime.Composable public final androidx.compose.foundation.layout.WindowInsets contentWindowInsets;
@@ -1895,10 +1889,6 @@
method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.TimePickerState,?> Saver();
}
- @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public interface TooltipBoxScope {
- method @Deprecated public androidx.compose.ui.Modifier tooltipTrigger(androidx.compose.ui.Modifier);
- }
-
@SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class TooltipDefaults {
method @androidx.compose.runtime.Composable public long getPlainTooltipContainerColor();
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getPlainTooltipContainerShape();
@@ -1916,13 +1906,9 @@
public final class TooltipKt {
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltip(optional androidx.compose.ui.Modifier modifier, optional long contentColor, optional long containerColor, optional float tonalElevation, optional float shadowElevation, optional androidx.compose.ui.graphics.Shape shape, kotlin.jvm.functions.Function0<kotlin.Unit> content);
- method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltipBox(kotlin.jvm.functions.Function0<kotlin.Unit> tooltip, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional androidx.compose.material3.PlainTooltipState tooltipState, optional androidx.compose.ui.graphics.Shape shape, optional long containerColor, optional float tonalElevation, optional float shadowElevation, optional long contentColor, kotlin.jvm.functions.Function1<? super androidx.compose.material3.TooltipBoxScope,kotlin.Unit> content);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltip(optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, optional androidx.compose.ui.graphics.Shape shape, kotlin.jvm.functions.Function0<kotlin.Unit> text);
- method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltipBox(kotlin.jvm.functions.Function0<kotlin.Unit> text, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional androidx.compose.material3.RichTooltipState tooltipState, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function1<? super androidx.compose.material3.TooltipBoxScope,kotlin.Unit> content);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TooltipBox(androidx.compose.ui.window.PopupPositionProvider positionProvider, kotlin.jvm.functions.Function0<kotlin.Unit> tooltip, androidx.compose.material3.TooltipState state, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional boolean enableUserInput, kotlin.jvm.functions.Function0<kotlin.Unit> content);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.material3.TooltipState TooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
- method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.PlainTooltipState rememberPlainTooltipState(optional androidx.compose.foundation.MutatorMutex mutatorMutex);
- method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.RichTooltipState rememberRichTooltipState(boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.TooltipState rememberTooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
}
diff --git a/compose/material3/material3/api/restricted_1.2.0-beta01.txt b/compose/material3/material3/api/restricted_1.2.0-beta01.txt
index 7ca2ade..7138834 100644
--- a/compose/material3/material3/api/restricted_1.2.0-beta01.txt
+++ b/compose/material3/material3/api/restricted_1.2.0-beta01.txt
@@ -1097,9 +1097,6 @@
method @androidx.compose.runtime.Composable public static void OutlinedTextField(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional int minLines, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
}
- @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public interface PlainTooltipState extends androidx.compose.material3.BasicTooltipState {
- }
-
public final class ProgressIndicatorDefaults {
method @androidx.compose.runtime.Composable public long getCircularColor();
method public int getCircularDeterminateStrokeCap();
@@ -1184,9 +1181,6 @@
property public final long titleContentColor;
}
- @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public interface RichTooltipState extends androidx.compose.material3.BasicTooltipState {
- }
-
public final class ScaffoldDefaults {
method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.WindowInsets getContentWindowInsets();
property @androidx.compose.runtime.Composable public final androidx.compose.foundation.layout.WindowInsets contentWindowInsets;
@@ -1895,10 +1889,6 @@
method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.TimePickerState,?> Saver();
}
- @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public interface TooltipBoxScope {
- method @Deprecated public androidx.compose.ui.Modifier tooltipTrigger(androidx.compose.ui.Modifier);
- }
-
@SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class TooltipDefaults {
method @androidx.compose.runtime.Composable public long getPlainTooltipContainerColor();
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getPlainTooltipContainerShape();
@@ -1916,13 +1906,9 @@
public final class TooltipKt {
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltip(optional androidx.compose.ui.Modifier modifier, optional long contentColor, optional long containerColor, optional float tonalElevation, optional float shadowElevation, optional androidx.compose.ui.graphics.Shape shape, kotlin.jvm.functions.Function0<kotlin.Unit> content);
- method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltipBox(kotlin.jvm.functions.Function0<kotlin.Unit> tooltip, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional androidx.compose.material3.PlainTooltipState tooltipState, optional androidx.compose.ui.graphics.Shape shape, optional long containerColor, optional float tonalElevation, optional float shadowElevation, optional long contentColor, kotlin.jvm.functions.Function1<? super androidx.compose.material3.TooltipBoxScope,kotlin.Unit> content);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltip(optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, optional androidx.compose.ui.graphics.Shape shape, kotlin.jvm.functions.Function0<kotlin.Unit> text);
- method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltipBox(kotlin.jvm.functions.Function0<kotlin.Unit> text, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional androidx.compose.material3.RichTooltipState tooltipState, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function1<? super androidx.compose.material3.TooltipBoxScope,kotlin.Unit> content);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TooltipBox(androidx.compose.ui.window.PopupPositionProvider positionProvider, kotlin.jvm.functions.Function0<kotlin.Unit> tooltip, androidx.compose.material3.TooltipState state, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional boolean enableUserInput, kotlin.jvm.functions.Function0<kotlin.Unit> content);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.material3.TooltipState TooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
- method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.PlainTooltipState rememberPlainTooltipState(optional androidx.compose.foundation.MutatorMutex mutatorMutex);
- method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.RichTooltipState rememberRichTooltipState(boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.TooltipState rememberTooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
}
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index 7ca2ade..7138834 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -1097,9 +1097,6 @@
method @androidx.compose.runtime.Composable public static void OutlinedTextField(String value, kotlin.jvm.functions.Function1<? super java.lang.String,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? prefix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? suffix, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional int minLines, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
}
- @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public interface PlainTooltipState extends androidx.compose.material3.BasicTooltipState {
- }
-
public final class ProgressIndicatorDefaults {
method @androidx.compose.runtime.Composable public long getCircularColor();
method public int getCircularDeterminateStrokeCap();
@@ -1184,9 +1181,6 @@
property public final long titleContentColor;
}
- @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public interface RichTooltipState extends androidx.compose.material3.BasicTooltipState {
- }
-
public final class ScaffoldDefaults {
method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.WindowInsets getContentWindowInsets();
property @androidx.compose.runtime.Composable public final androidx.compose.foundation.layout.WindowInsets contentWindowInsets;
@@ -1895,10 +1889,6 @@
method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.TimePickerState,?> Saver();
}
- @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public interface TooltipBoxScope {
- method @Deprecated public androidx.compose.ui.Modifier tooltipTrigger(androidx.compose.ui.Modifier);
- }
-
@SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class TooltipDefaults {
method @androidx.compose.runtime.Composable public long getPlainTooltipContainerColor();
method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getPlainTooltipContainerShape();
@@ -1916,13 +1906,9 @@
public final class TooltipKt {
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltip(optional androidx.compose.ui.Modifier modifier, optional long contentColor, optional long containerColor, optional float tonalElevation, optional float shadowElevation, optional androidx.compose.ui.graphics.Shape shape, kotlin.jvm.functions.Function0<kotlin.Unit> content);
- method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltipBox(kotlin.jvm.functions.Function0<kotlin.Unit> tooltip, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional androidx.compose.material3.PlainTooltipState tooltipState, optional androidx.compose.ui.graphics.Shape shape, optional long containerColor, optional float tonalElevation, optional float shadowElevation, optional long contentColor, kotlin.jvm.functions.Function1<? super androidx.compose.material3.TooltipBoxScope,kotlin.Unit> content);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltip(optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, optional androidx.compose.ui.graphics.Shape shape, kotlin.jvm.functions.Function0<kotlin.Unit> text);
- method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltipBox(kotlin.jvm.functions.Function0<kotlin.Unit> text, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional androidx.compose.material3.RichTooltipState tooltipState, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, optional float tonalElevation, optional float shadowElevation, kotlin.jvm.functions.Function1<? super androidx.compose.material3.TooltipBoxScope,kotlin.Unit> content);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TooltipBox(androidx.compose.ui.window.PopupPositionProvider positionProvider, kotlin.jvm.functions.Function0<kotlin.Unit> tooltip, androidx.compose.material3.TooltipState state, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional boolean enableUserInput, kotlin.jvm.functions.Function0<kotlin.Unit> content);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.material3.TooltipState TooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
- method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.PlainTooltipState rememberPlainTooltipState(optional androidx.compose.foundation.MutatorMutex mutatorMutex);
- method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.RichTooltipState rememberRichTooltipState(boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.TooltipState rememberTooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
}
diff --git a/compose/material3/material3/integration-tests/material3-catalog/build.gradle b/compose/material3/material3/integration-tests/material3-catalog/build.gradle
index 1313927..4d02722 100644
--- a/compose/material3/material3/integration-tests/material3-catalog/build.gradle
+++ b/compose/material3/material3/integration-tests/material3-catalog/build.gradle
@@ -30,7 +30,7 @@
implementation project(":compose:runtime:runtime")
implementation project(":compose:foundation:foundation-layout")
implementation project(":compose:ui:ui")
- implementation project(":compose:material:material")
+ implementation("androidx.compose.material:material:1.6.0-beta01")
implementation project(":compose:material:material-icons-extended")
implementation project(":compose:material3:material3")
implementation project(":compose:material3:material3:material3-samples")
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/TooltipPopup.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/TooltipPopup.android.kt
deleted file mode 100644
index 1074b7e..0000000
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/TooltipPopup.android.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2023 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.
- */
-
-package androidx.compose.material3
-
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.window.Popup
-import androidx.compose.ui.window.PopupPositionProvider
-import androidx.compose.ui.window.PopupProperties
-
-@Composable
-@ExperimentalMaterial3Api
-internal actual fun TooltipPopup(
- popupPositionProvider: PopupPositionProvider,
- onDismissRequest: () -> Unit,
- focusable: Boolean,
- content: @Composable () -> Unit
-) = Popup(
- popupPositionProvider = popupPositionProvider,
- onDismissRequest = onDismissRequest,
- content = content,
- properties = PopupProperties(focusable = focusable)
-)
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt
index 9d4e08a..f0a309e 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt
@@ -25,9 +25,6 @@
import androidx.compose.animation.core.updateTransition
import androidx.compose.foundation.MutatePriority
import androidx.compose.foundation.MutatorMutex
-import androidx.compose.foundation.gestures.awaitEachGesture
-import androidx.compose.foundation.gestures.awaitFirstDown
-import androidx.compose.foundation.gestures.waitForUpOrCancellation
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
@@ -39,29 +36,17 @@
import androidx.compose.material3.tokens.RichTooltipTokens
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.input.pointer.PointerEventPass
-import androidx.compose.ui.input.pointer.PointerEventTimeoutCancellationException
-import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.debugInspectorInfo
-import androidx.compose.ui.semantics.LiveRegionMode
-import androidx.compose.ui.semantics.liveRegion
-import androidx.compose.ui.semantics.onLongClick
-import androidx.compose.ui.semantics.paneTitle
-import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntRect
@@ -70,511 +55,10 @@
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.PopupPositionProvider
import kotlinx.coroutines.CancellableContinuation
-import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeout
/**
- * Plain tooltip that provides a descriptive message for an anchor.
- *
- * Tooltip that is invoked when the anchor is long pressed:
- *
- * @sample androidx.compose.material3.samples.PlainTooltipSample
- *
- * If control of when the tooltip is shown is desired please see
- *
- * @sample androidx.compose.material3.samples.PlainTooltipWithManualInvocationSample
- *
- * @param tooltip the composable that will be used to populate the tooltip's content.
- * @param modifier the [Modifier] to be applied to the tooltip.
- * @param focusable [Boolean] that determines if the tooltip is focusable. When true,
- * the tooltip will consume touch events while it's shown and will have accessibility
- * focus move to the first element of the component. When false, the tooltip
- * won't consume touch events while it's shown but assistive-tech users will need
- * to swipe or drag to get to the first element of the component.
- * @param tooltipState handles the state of the tooltip's visibility.
- * @param shape the [Shape] that should be applied to the tooltip container.
- * @param containerColor [Color] that will be applied to the tooltip's container.
- * @param tonalElevation the tonal elevation of the tooltip.
- * @param shadowElevation the shadow elevation of the tooltip.
- * @param contentColor [Color] that will be applied to the tooltip's content.
- * @param content the composable that the tooltip will anchor to.
- */
-@Suppress("DEPRECATION")
-@Composable
-@ExperimentalMaterial3Api
-@Deprecated("Use TooltipBox with PlainTooltip")
-fun PlainTooltipBox(
- tooltip: @Composable () -> Unit,
- modifier: Modifier = Modifier,
- focusable: Boolean = true,
- tooltipState: PlainTooltipState = rememberPlainTooltipState(),
- shape: Shape = TooltipDefaults.plainTooltipContainerShape,
- containerColor: Color = TooltipDefaults.plainTooltipContainerColor,
- tonalElevation: Dp = 0.dp,
- shadowElevation: Dp = 0.dp,
- contentColor: Color = TooltipDefaults.plainTooltipContentColor,
- content: @Composable TooltipBoxScope.() -> Unit
-) {
- Material3TooltipBox(
- tooltipContent = {
- Box(modifier = Modifier.padding(PlainTooltipContentPadding)) {
- val textStyle =
- MaterialTheme.typography.fromToken(PlainTooltipTokens.SupportingTextFont)
- CompositionLocalProvider(
- LocalContentColor provides contentColor,
- LocalTextStyle provides textStyle,
- content = tooltip
- )
- }
- },
- modifier = modifier,
- focusable = focusable,
- tooltipState = tooltipState,
- shape = shape,
- containerColor = containerColor,
- tooltipPositionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
- tonalElevation = tonalElevation,
- shadowElevation = shadowElevation,
- maxWidth = PlainTooltipMaxWidth,
- content = content
- )
-}
-
-/**
- * Rich text tooltip that allows the user to pass in a title, text, and action.
- * Tooltips are used to provide a descriptive message for an anchor.
- *
- * Tooltip that is invoked when the anchor is long pressed:
- *
- * @sample androidx.compose.material3.samples.RichTooltipSample
- *
- * If control of when the tooltip is shown is desired please see
- *
- * @sample androidx.compose.material3.samples.RichTooltipWithManualInvocationSample
- *
- * @param text the message to be displayed in the center of the tooltip.
- * @param modifier the [Modifier] to be applied to the tooltip.
- * @param focusable [Boolean] that determines if the tooltip is focusable. When true,
- * the tooltip will consume touch events while it's shown and will have accessibility
- * focus move to the first element of the component. When false, the tooltip
- * won't consume touch events while it's shown but assistive-tech users will need
- * to swipe or drag to get to the first element of the component.
- * @param tooltipState handles the state of the tooltip's visibility.
- * @param title An optional title for the tooltip.
- * @param action An optional action for the tooltip.
- * @param shape the [Shape] that should be applied to the tooltip container.
- * @param colors [RichTooltipColors] that will be applied to the tooltip's container and content.
- * @param tonalElevation the tonal elevation of the tooltip.
- * @param shadowElevation the shadow elevation of the tooltip.
- * @param content the composable that the tooltip will anchor to.
- */
-@Suppress("DEPRECATION")
-@Composable
-@ExperimentalMaterial3Api
-@Deprecated("Use TooltipBox with RichTooltip")
-fun RichTooltipBox(
- text: @Composable () -> Unit,
- modifier: Modifier = Modifier,
- focusable: Boolean = true,
- title: (@Composable () -> Unit)? = null,
- action: (@Composable () -> Unit)? = null,
- tooltipState: RichTooltipState = rememberRichTooltipState(action != null),
- shape: Shape = TooltipDefaults.richTooltipContainerShape,
- colors: RichTooltipColors = TooltipDefaults.richTooltipColors(),
- tonalElevation: Dp = RichTooltipTokens.ContainerElevation,
- shadowElevation: Dp = RichTooltipTokens.ContainerElevation,
- content: @Composable TooltipBoxScope.() -> Unit
-) {
- Material3TooltipBox(
- tooltipContent = {
- val actionLabelTextStyle =
- MaterialTheme.typography.fromToken(RichTooltipTokens.ActionLabelTextFont)
- val subheadTextStyle =
- MaterialTheme.typography.fromToken(RichTooltipTokens.SubheadFont)
- val supportingTextStyle =
- MaterialTheme.typography.fromToken(RichTooltipTokens.SupportingTextFont)
- Column(
- modifier = Modifier.padding(horizontal = RichTooltipHorizontalPadding)
- ) {
- title?.let {
- Box(
- modifier = Modifier.paddingFromBaseline(top = HeightToSubheadFirstLine)
- ) {
- CompositionLocalProvider(
- LocalContentColor provides colors.titleContentColor,
- LocalTextStyle provides subheadTextStyle,
- content = it
- )
- }
- }
- Box(
- modifier = Modifier.textVerticalPadding(title != null, action != null)
- ) {
- CompositionLocalProvider(
- LocalContentColor provides colors.contentColor,
- LocalTextStyle provides supportingTextStyle,
- content = text
- )
- }
- action?.let {
- Box(
- modifier = Modifier
- .requiredHeightIn(min = ActionLabelMinHeight)
- .padding(bottom = ActionLabelBottomPadding)
- ) {
- CompositionLocalProvider(
- LocalContentColor provides colors.actionContentColor,
- LocalTextStyle provides actionLabelTextStyle,
- content = it
- )
- }
- }
- }
- },
- shape = shape,
- containerColor = colors.containerColor,
- tooltipPositionProvider = TooltipDefaults.rememberRichTooltipPositionProvider(),
- tooltipState = tooltipState,
- tonalElevation = tonalElevation,
- shadowElevation = shadowElevation,
- maxWidth = RichTooltipMaxWidth,
- modifier = modifier,
- focusable = focusable,
- content = content
- )
-}
-
-@Suppress("DEPRECATION")
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-private fun Material3TooltipBox(
- tooltipContent: @Composable () -> Unit,
- tooltipPositionProvider: PopupPositionProvider,
- modifier: Modifier,
- focusable: Boolean,
- shape: Shape,
- tooltipState: BasicTooltipState,
- containerColor: Color,
- tonalElevation: Dp,
- shadowElevation: Dp,
- maxWidth: Dp,
- content: @Composable TooltipBoxScope.() -> Unit,
-) {
- val coroutineScope = rememberCoroutineScope()
- val longPressLabel = getString(string = Strings.TooltipLongPressLabel)
-
- val scope = remember(tooltipState) {
- object : TooltipBoxScope {
- override fun Modifier.tooltipTrigger(): Modifier {
- val onLongPress = {
- coroutineScope.launch {
- tooltipState.show()
- }
- }
- return pointerInput(tooltipState) {
- awaitEachGesture {
- val longPressTimeout = viewConfiguration.longPressTimeoutMillis
- val pass = PointerEventPass.Initial
-
- // wait for the first down press
- awaitFirstDown(pass = pass)
-
- try {
- // listen to if there is up gesture within the longPressTimeout limit
- withTimeout(longPressTimeout) {
- waitForUpOrCancellation(pass = pass)
- }
- } catch (_: PointerEventTimeoutCancellationException) {
- // handle long press - Show the tooltip
- onLongPress()
-
- // consume the children's click handling
- val changes = awaitPointerEvent(pass = pass).changes
- for (i in 0 until changes.size) { changes[i].consume() }
- }
- }
- }.semantics(mergeDescendants = true) {
- onLongClick(
- label = longPressLabel,
- action = {
- onLongPress()
- true
- }
- )
- }
- }
- }
- }
-
- Box {
- val transition = updateTransition(tooltipState.isVisible, label = "Tooltip transition")
- if (transition.currentState || transition.targetState) {
- val tooltipPaneDescription = getString(Strings.TooltipPaneDescription)
- TooltipPopup(
- popupPositionProvider = tooltipPositionProvider,
- onDismissRequest = {
- if (tooltipState.isVisible) {
- coroutineScope.launch { tooltipState.dismiss() }
- }
- },
- focusable = focusable
- ) {
- Surface(
- modifier = modifier
- .sizeIn(
- minWidth = TooltipMinWidth,
- maxWidth = maxWidth,
- minHeight = TooltipMinHeight
- )
- .animateTooltip(transition)
- .semantics {
- liveRegion = LiveRegionMode.Assertive
- paneTitle = tooltipPaneDescription
- },
- shape = shape,
- color = containerColor,
- tonalElevation = tonalElevation,
- shadowElevation = shadowElevation,
- content = tooltipContent
- )
- }
- }
-
- scope.content()
- }
-
- DisposableEffect(tooltipState) {
- onDispose { tooltipState.onDispose() }
- }
-}
-
-/**
- * Scope of [PlainTooltipBox] and RichTooltipBox
- */
-@Suppress("DEPRECATION")
-@ExperimentalMaterial3Api
-@Deprecated("Use TooltipBox with enableUserInput to handle default gestures")
-interface TooltipBoxScope {
- /**
- * [Modifier] that should be applied to the anchor composable when showing the tooltip
- * after long pressing the anchor composable is desired. It appends a long click to
- * the composable that this modifier is chained with.
- */
- fun Modifier.tooltipTrigger(): Modifier
-}
-
-@Composable
-@ExperimentalMaterial3Api
-internal expect fun TooltipPopup(
- popupPositionProvider: PopupPositionProvider,
- onDismissRequest: () -> Unit,
- focusable: Boolean,
- content: @Composable () -> Unit
-)
-
-/**
- * Create and remember the default [PlainTooltipState].
- *
- * @param mutatorMutex [MutatorMutex] used to ensure that for all of the tooltips associated
- * with the mutator mutex, only one will be shown on the screen at any time.
- */
-@Suppress("DEPRECATION")
-@Composable
-@ExperimentalMaterial3Api
-@Deprecated("Use rememberTooltipState with TooltipBox.")
-fun rememberPlainTooltipState(
- mutatorMutex: MutatorMutex = BasicTooltipDefaults.GlobalMutatorMutex
-): PlainTooltipState =
- remember(mutatorMutex) { PlainTooltipStateImpl(mutatorMutex) }
-
-/**
- * Create and remember the default [RichTooltipState].
- *
- * @param isPersistent [Boolean] that determines if the tooltip associated with this
- * [RichTooltipState] will be persistent or not. If isPersistent is true, then the tooltip will
- * only be dismissed when the user clicks outside the bounds of the tooltip or if
- * [TooltipState.dismiss] is called. When isPersistent is false, the tooltip will dismiss after
- * a short duration. Ideally, this should be set to true when an action is provided to the
- * [RichTooltipBox] that this [RichTooltipState] is associated with.
- * @param mutatorMutex [MutatorMutex] used to ensure that for all of the tooltips associated
- * with the mutator mutex, only one will be shown on the screen at any time.
- */
-@Suppress("DEPRECATION")
-@Composable
-@ExperimentalMaterial3Api
-@Deprecated("Use rememberTooltipState with TooltipBox")
-fun rememberRichTooltipState(
- isPersistent: Boolean,
- mutatorMutex: MutatorMutex = BasicTooltipDefaults.GlobalMutatorMutex
-): RichTooltipState =
- remember(
- isPersistent,
- mutatorMutex
- ) { RichTooltipStateImpl(isPersistent = isPersistent, mutatorMutex = mutatorMutex) }
-
-/**
- * The default implementation for [RichTooltipState]
- *
- * @param isPersistent [Boolean] that determines if the tooltip associated with this
- * [RichTooltipState] will be persistent or not. If isPersistent is true, then the tooltip will
- * only be dismissed when the user clicks outside the bounds of the tooltip or if
- * [TooltipState.dismiss] is called. When isPersistent is false, the tooltip will dismiss after
- * a short duration. Ideally, this should be set to true when an action is provided to the
- * [RichTooltipBox] that this [RichTooltipState] is associated with.
- * @param mutatorMutex [MutatorMutex] used to ensure that for all of the tooltips associated
- * with the mutator mutex, only one will be shown on the screen at any time.
- */
-@Suppress("DEPRECATION")
-@OptIn(ExperimentalMaterial3Api::class)
-@Stable
-internal class RichTooltipStateImpl(
- override val isPersistent: Boolean,
- private val mutatorMutex: MutatorMutex
-) : RichTooltipState {
-
- /**
- * [Boolean] that will be used to update the visibility
- * state of the associated tooltip.
- */
- override var isVisible: Boolean by mutableStateOf(false)
- private set
-
- /**
- * Show the tooltip associated with the current [RichTooltipState].
- * It will persist or dismiss after a short duration depending on [isPersistent].
- * When this method is called, all of the other tooltips currently
- * being shown will dismiss.
- */
- override suspend fun show(mutatePriority: MutatePriority) {
- val cancellableShow: suspend () -> Unit = {
- suspendCancellableCoroutine { continuation ->
- isVisible = true
- job = continuation
- }
- }
-
- // Show associated tooltip for [TooltipDuration] amount of time
- // or until tooltip is explicitly dismissed depending on [isPersistent].
- mutatorMutex.mutate(mutatePriority) {
- try {
- if (isPersistent) {
- cancellableShow()
- } else {
- withTimeout(BasicTooltipDefaults.TooltipDuration) {
- cancellableShow()
- }
- }
- } finally {
- // timeout or cancellation has occurred
- // and we close out the current tooltip.
- isVisible = false
- }
- }
- }
-
- /**
- * continuation used to clean up
- */
- private var job: (CancellableContinuation<Unit>)? = null
-
- /**
- * Dismiss the tooltip associated with
- * this [RichTooltipState] if it's currently being shown.
- */
- override fun dismiss() {
- isVisible = false
- }
-
- /**
- * Cleans up [MutatorMutex] when the tooltip associated
- * with this state leaves Composition.
- */
- override fun onDispose() {
- job?.cancel()
- }
-}
-
-/**
- * The default implementation for [PlainTooltipState]
- */
-@Suppress("DEPRECATION")
-@OptIn(ExperimentalMaterial3Api::class)
-@Stable
-internal class PlainTooltipStateImpl(private val mutatorMutex: MutatorMutex) : PlainTooltipState {
-
- /**
- * [Boolean] that will be used to update the visibility
- * state of the associated tooltip.
- */
- override var isVisible by mutableStateOf(false)
- private set
-
- override val isPersistent: Boolean = false
-
- /**
- * Show the tooltip associated with the current [PlainTooltipState].
- * It will dismiss after a short duration. When this method is called,
- * all of the other tooltips currently being shown will dismiss.
- */
- override suspend fun show(mutatePriority: MutatePriority) {
- mutatorMutex.mutate(mutatePriority) {
- try {
- withTimeout(BasicTooltipDefaults.TooltipDuration) {
- suspendCancellableCoroutine { continuation ->
- isVisible = true
- job = continuation
- }
- }
- } finally {
- // timeout or cancellation has occurred
- // and we close out the current tooltip.
- isVisible = false
- }
- }
- }
-
- /**
- * continuation used to clean up
- */
- private var job: (CancellableContinuation<Unit>)? = null
-
- /**
- * Dismiss the tooltip associated with
- * this [PlainTooltipState] if it's currently being shown.
- */
- override fun dismiss() {
- isVisible = false
- }
-
- /**
- * Cleans up [MutatorMutex] when the tooltip associated
- * with this state leaves Composition.
- */
- override fun onDispose() {
- job?.cancel()
- }
-}
-
-/**
- * The [TooltipState] that should be used with [RichTooltipBox]
- */
-@Suppress("DEPRECATION")
-@Stable
-@ExperimentalMaterial3Api
-@Deprecated("Use TooltipState with TooltipBox")
-interface PlainTooltipState : BasicTooltipState
-
-/**
- * The [TooltipState] that should be used with [RichTooltipBox]
- */
-@Suppress("DEPRECATION")
-@Stable
-@ExperimentalMaterial3Api
-@Deprecated("Use TooltipState with TooltipBox")
-interface RichTooltipState : BasicTooltipState
-
-/**
* Material TooltipBox that wraps a composable with a tooltip.
*
* tooltips provide a descriptive message for an anchor.
diff --git a/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material3/TooltipPopup.desktop.kt b/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material3/TooltipPopup.desktop.kt
deleted file mode 100644
index cd83c7c..0000000
--- a/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material3/TooltipPopup.desktop.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2023 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.
- */
-
-package androidx.compose.material3
-
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.window.Popup
-import androidx.compose.ui.window.PopupPositionProvider
-
-@Composable
-@ExperimentalMaterial3Api
-internal actual fun TooltipPopup(
- popupPositionProvider: PopupPositionProvider,
- onDismissRequest: () -> Unit,
- focusable: Boolean,
- content: @Composable () -> Unit
-) = Popup(
- popupPositionProvider = popupPositionProvider,
- onDismissRequest = onDismissRequest,
- content = content
-)
diff --git a/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTreeTest.kt b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTreeTest.kt
index 635450c..2da7184 100644
--- a/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTreeTest.kt
+++ b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTreeTest.kt
@@ -31,6 +31,7 @@
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.text.BasicText
import androidx.compose.material.AlertDialog
import androidx.compose.material.Button
@@ -60,17 +61,21 @@
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.inspection.compose.flatten
import androidx.compose.ui.inspection.testdata.TestActivity
+import androidx.compose.ui.inspection.util.ThreadUtils
import androidx.compose.ui.layout.GraphicLayerInfo
import androidx.compose.ui.node.Ref
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
+import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.text
import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performScrollToIndex
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
@@ -974,7 +979,36 @@
val builder = LayoutInspectorTree()
builder.hideSystemNodes = false
builder.includeAllParameters = false
- builder.convert(androidComposeView)
+ }
+
+ @Test // regression test for b/311436726
+ fun testLazyColumn() {
+ val slotTableRecord = CompositionDataRecord.create()
+
+ show {
+ Inspectable(slotTableRecord) {
+ LazyColumn(modifier = Modifier.testTag("LazyColumn")) {
+ items(100) { index ->
+ Text(text = "Item: $index")
+ }
+ }
+ }
+ }
+
+ val androidComposeView = findAndroidComposeView()
+ androidComposeView.setTag(R.id.inspection_slot_table_set, slotTableRecord.store)
+ val builder = LayoutInspectorTree()
+ builder.hideSystemNodes = false
+ builder.includeAllParameters = true
+ ThreadUtils.runOnMainThread {
+ builder.convert(androidComposeView)
+ }
+ for (index in 20..40) {
+ composeTestRule.onNodeWithTag("LazyColumn").performScrollToIndex(index)
+ }
+ ThreadUtils.runOnMainThread {
+ builder.convert(androidComposeView)
+ }
}
@Suppress("SameParameterValue")
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTree.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTree.kt
index a36e3d6..8a72392 100644
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTree.kt
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTree.kt
@@ -503,7 +503,7 @@
val size = box.size.toSize()
val coordinates = layoutInfo.coordinates
var bounds: QuadBounds? = null
- if (layoutInfo.isAttached) {
+ if (layoutInfo.isAttached && coordinates.isAttached) {
val topLeft = toIntOffset(coordinates.localToWindow(Offset.Zero))
val topRight = toIntOffset(coordinates.localToWindow(Offset(size.width, 0f)))
val bottomRight =
diff --git a/compose/ui/ui-tooling-data/src/jvmMain/kotlin/androidx/compose/ui/tooling/data/SlotTree.jvm.kt b/compose/ui/ui-tooling-data/src/jvmMain/kotlin/androidx/compose/ui/tooling/data/SlotTree.jvm.kt
index d2505db..c439766 100644
--- a/compose/ui/ui-tooling-data/src/jvmMain/kotlin/androidx/compose/ui/tooling/data/SlotTree.jvm.kt
+++ b/compose/ui/ui-tooling-data/src/jvmMain/kotlin/androidx/compose/ui/tooling/data/SlotTree.jvm.kt
@@ -557,7 +557,8 @@
}
private fun boundsOfLayoutNode(node: LayoutInfo): IntRect {
- if (!node.isAttached) {
+ val coordinates = node.coordinates
+ if (!node.isAttached || !coordinates.isAttached) {
return IntRect(
left = 0,
top = 0,
@@ -565,8 +566,8 @@
bottom = node.height
)
}
- val position = node.coordinates.positionInWindow()
- val size = node.coordinates.size
+ val position = coordinates.positionInWindow()
+ val size = coordinates.size
val left = position.x.roundToInt()
val top = position.y.roundToInt()
val right = left + size.width
diff --git a/development/build_log_simplifier/message-flakes.ignore b/development/build_log_simplifier/message-flakes.ignore
index ec9a2fc..21a9218 100644
--- a/development/build_log_simplifier/message-flakes.ignore
+++ b/development/build_log_simplifier/message-flakes.ignore
@@ -42,11 +42,6 @@
[0-9]+ problems were found reusing the configuration cache\.
[0-9]+ problem was found reusing the configuration cache\.
Configuration cache entry reused with [0-9]+ problem\.
-# > Task :webkit:integration-tests:testapp:compileReleaseJavaWithJavac
-\[ant\:jacocoReport\] Note\: Some input files use or override a deprecated API\.
-\[ant\:jacocoReport\] Note\: Recompile with \-Xlint\:deprecation for details\.
-\[ant\:jacocoReport\] Note\: Some input files use unchecked or unsafe operations\.
-\[ant\:jacocoReport\] Note\: Recompile with \-Xlint\:unchecked for details\.
# b/179833331 , https://youtrack.jetbrains.com/issue/KT-35156
# b/181258249 , https://youtrack.jetbrains.com/issue/KT-43881
\.\.\. [0-9]+ more
diff --git a/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetManagerTest.kt b/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetManagerTest.kt
index 1181f98..190983d 100644
--- a/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetManagerTest.kt
+++ b/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetManagerTest.kt
@@ -20,10 +20,8 @@
import androidx.glance.text.Text
import androidx.test.filters.MediumTest
import androidx.test.filters.SdkSuppress
-import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import androidx.test.uiautomator.UiSelector
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.junit.After
@@ -32,7 +30,6 @@
import org.junit.Rule
import org.junit.Test
-@OptIn(ExperimentalCoroutinesApi::class)
@SdkSuppress(minSdkVersion = 29)
@MediumTest
class GlanceAppWidgetManagerTest {
@@ -47,45 +44,53 @@
@After
fun tearDown() {
- getInstrumentation().context.startActivity(Intent(Intent.ACTION_MAIN).apply {
+ context.startActivity(Intent(Intent.ACTION_MAIN).apply {
addCategory(Intent.CATEGORY_HOME)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
})
}
@Test
- fun noAppWidget() {
- mHostRule.onHostActivity { activity ->
- val manager = GlanceAppWidgetManager(activity)
- runBlocking {
- assertThat(manager.getGlanceIds(TestGlanceAppWidget::class.java)).isEmpty()
- }
- }
+ fun noAppWidget() = runBlocking {
+ val manager = GlanceAppWidgetManager(context)
+
+ assertThat(manager.getGlanceIds(TestGlanceAppWidget::class.java)).isEmpty()
}
@Test
- fun withAppWidget() {
+ fun withAppWidget() = runBlocking {
TestGlanceAppWidget.uiDefinition = {
Text("Something")
}
+ val manager = GlanceAppWidgetManager(context)
+
+ suspend fun verifyGlanceIdsAndSizes() {
+ val glanceIds = manager.getGlanceIds(TestGlanceAppWidget::class.java)
+ assertThat(glanceIds).hasSize(1)
+ assertThat(manager.listKnownReceivers()).containsExactly(
+ TestGlanceAppWidgetReceiver::class.java.canonicalName
+ )
+
+ val glanceId = manager.getGlanceIdBy((glanceIds[0] as AppWidgetId).appWidgetId)
+ assertThat(glanceId).isEqualTo(glanceIds[0])
+
+ val sizes = manager.getAppWidgetSizes(glanceIds[0])
+ assertThat(sizes).containsExactly(
+ mHostRule.portraitSize,
+ mHostRule.landscapeSize
+ )
+ }
mHostRule.startHost()
- mHostRule.onHostActivity { activity ->
- val manager = GlanceAppWidgetManager(activity)
+ verifyGlanceIdsAndSizes()
- runBlocking {
- val glanceIds = manager.getGlanceIds(TestGlanceAppWidget::class.java)
- assertThat(glanceIds).hasSize(1)
- val glanceId = manager.getGlanceIdBy((glanceIds[0] as AppWidgetId).appWidgetId)
- assertThat(glanceId).isEqualTo(glanceIds[0])
- val sizes = manager.getAppWidgetSizes(glanceIds[0])
- assertThat(sizes).containsExactly(
- mHostRule.portraitSize,
- mHostRule.landscapeSize
- )
- }
- }
+ // using "pm clear <package>" is not suitable here - it will crash process.
+ // See https://github.com/android/testing-samples/issues/98
+ // So, we clear datastore to mimic clearing appData.
+ manager.clearDataStore()
+
+ verifyGlanceIdsAndSizes()
}
@Test
@@ -99,6 +104,7 @@
TestGlanceAppWidgetReceiver::class.java,
preview = TestGlanceAppWidget
)
+
assertThat(result).isTrue()
mHostRule.onHostActivity {
assertThat(mHostRule.device.findObject(UiSelector().text(text)).exists())
@@ -110,28 +116,27 @@
val result = GlanceAppWidgetManager(context).requestPinGlanceAppWidget(
DummyGlanceAppWidgetReceiver::class.java,
)
+
assertThat(result).isFalse()
}
@Ignore("b/285198114")
@Test
- fun cleanReceivers() {
- mHostRule.onHostActivity { activity ->
- val manager = GlanceAppWidgetManager(activity)
+ fun cleanReceivers() = runBlocking<Unit> {
+ val manager = GlanceAppWidgetManager(context)
- runBlocking {
- manager.updateReceiver(DummyGlanceAppWidgetReceiver(), TestGlanceAppWidget)
- assertThat(manager.listKnownReceivers()).containsExactly(
- DummyGlanceAppWidgetReceiver::class.java.canonicalName,
- TestGlanceAppWidgetReceiver::class.java.canonicalName
- )
+ manager.updateReceiver(DummyGlanceAppWidgetReceiver(), TestGlanceAppWidget)
- manager.cleanReceivers()
- assertThat(manager.listKnownReceivers()).containsExactly(
- TestGlanceAppWidgetReceiver::class.java.canonicalName
- )
- }
- }
+ assertThat(manager.listKnownReceivers()).containsExactly(
+ DummyGlanceAppWidgetReceiver::class.java.canonicalName,
+ TestGlanceAppWidgetReceiver::class.java.canonicalName
+ )
+
+ manager.cleanReceivers()
+
+ assertThat(manager.listKnownReceivers()).containsExactly(
+ TestGlanceAppWidgetReceiver::class.java.canonicalName
+ )
}
}
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/CheckBox.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/CheckBox.kt
index d344989..934465e 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/CheckBox.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/CheckBox.kt
@@ -54,6 +54,9 @@
* checked is provided to this action in its ActionParameters, and can be retrieved using the
* [ToggleableStateKey]. If this action launches an activity, the current value of checked will be
* passed as an intent extra with the name [RemoteViews.EXTRA_CHECKED].
+ * In order to allow the Launcher to provide this extra on Android version S and later, we use a
+ * mutable PendingIntent ([android.app.PendingIntent.FLAG_MUTABLE]) when this action is not a
+ * lambda. Before S, and for lambda actions, this will be an immutable PendingIntent.
* @param modifier the modifier to apply to the check box
* @param text the text to display to the end of the check box
* @param style the style to apply to [text]
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceAppWidgetManager.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceAppWidgetManager.kt
index 41c7633..9f86183 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceAppWidgetManager.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceAppWidgetManager.kt
@@ -18,6 +18,7 @@
import android.app.PendingIntent
import android.appwidget.AppWidgetManager
+import android.appwidget.AppWidgetProviderInfo
import android.content.ComponentName
import android.content.Context
import android.content.Intent
@@ -29,10 +30,12 @@
import androidx.compose.ui.unit.DpSize
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.core.stringSetPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import androidx.glance.GlanceId
+import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.firstOrNull
/**
@@ -70,8 +73,8 @@
receiver: R,
provider: P,
) {
- val receiverName = requireNotNull(receiver.javaClass.canonicalName) { "no receiver name" }
- val providerName = requireNotNull(provider.javaClass.canonicalName) { "no provider name" }
+ val receiverName = receiver.canonicalName()
+ val providerName = provider.canonicalName()
dataStore.updateData { pref ->
pref.toMutablePreferences().also { builder ->
builder[providersKey] = (pref[providersKey] ?: emptySet()) + receiverName
@@ -92,8 +95,20 @@
)
}
- private suspend fun getState() =
- dataStore.data.firstOrNull()?.let { createState(it) } ?: State()
+ private suspend fun getState(): State {
+ // Preferences won't contain value for providersKey when either -
+ // 1. App doesn't have any widget placed, but app requested for glanceIds for a widget class
+ // 2. User cleared app data, so, the provider to receivers mapping is lost (even if widgets
+ // are still pinned).
+ // In case of #2, we want to return an appropriate list of glance ids, so, we back-fill the
+ // datastore with all known glance receivers and providers.
+ // #1 isn't something that an app would commonly do, and even if it does, it would get empty
+ // IDs as expected.
+ return createState(
+ prefs = dataStore.data.first().takeIf { it[providersKey] != null }
+ ?: addAllReceiversAndProvidersToPreferences()
+ )
+ }
/**
* Returns the [GlanceId] of the App Widgets installed for a particular provider.
@@ -237,10 +252,53 @@
}
}
+ /**
+ * Identifies [GlanceAppWidget] (provider) for each [GlanceAppWidgetReceiver] in the app and
+ * saves the mapping in the preferences datastore. Also stores the set of glance-based
+ * receiver class names.
+ *
+ * [getGlanceIds] looks up the set of associated receivers for the given [GlanceAppWidget]
+ * (provider) from the datastore to be able to get the appwidget ids from [AppWidgetManager].
+ *
+ * Typically, the information is stored / overwritten by [updateReceiver] during widget
+ * lifecycle, however, when app data is cleared by the user, it is lost. So, we reconstruct it
+ * (for all known glance-based receivers).
+ *
+ * Follow b/305232907 to know the recommendation from appWidgets on handling cleared app data
+ * scenarios for widgets.
+ */
+ @Suppress("ListIterator")
+ private suspend fun addAllReceiversAndProvidersToPreferences(): Preferences {
+ val installedGlanceAppWidgetReceivers = appWidgetManager.installedProviders
+ .filter { it.provider.packageName == context.packageName }
+ .mapNotNull { it.maybeGlanceAppWidgetReceiver() }
+
+ return dataStore.updateData { prefs ->
+ prefs.toMutablePreferences().apply {
+ this[providersKey] =
+ installedGlanceAppWidgetReceivers.map { it.javaClass.name }.toSet()
+ installedGlanceAppWidgetReceivers.forEach {
+ this[providerKey(it.canonicalName())] = it.glanceAppWidget.canonicalName()
+ }
+ }.toPreferences()
+ }
+ }
+
@VisibleForTesting
internal suspend fun listKnownReceivers(): Collection<String>? =
dataStore.data.firstOrNull()?.let { it[providersKey] }
+ /**
+ * Clears the datastore that holds information about glance app widgets (providers) and
+ * receivers.
+ *
+ * Useful for tests that wish to mimic clearing app data.
+ */
+ @VisibleForTesting
+ internal suspend fun clearDataStore() {
+ dataStore.edit { it.clear() }
+ }
+
private companion object {
private val Context.appManagerDataStore
by preferencesDataStore(name = "GlanceAppWidgetManager")
@@ -248,6 +306,20 @@
private val providersKey = stringSetPreferencesKey("list::Providers")
private fun providerKey(provider: String) = stringPreferencesKey("provider:$provider")
+
+ private fun GlanceAppWidgetReceiver.canonicalName() =
+ requireNotNull(this.javaClass.canonicalName) { "no receiver name" }
+
+ private fun GlanceAppWidget.canonicalName() =
+ requireNotNull(this.javaClass.canonicalName) { "no provider name" }
+
+ private fun AppWidgetProviderInfo.maybeGlanceAppWidgetReceiver(): GlanceAppWidgetReceiver? {
+ val receiver = Class.forName(provider.className).getDeclaredConstructor().newInstance()
+ if (receiver is GlanceAppWidgetReceiver) {
+ return receiver
+ }
+ return null
+ }
}
@RequiresApi(Build.VERSION_CODES.O)
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/Switch.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/Switch.kt
index 44f7f99..2e43830 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/Switch.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/Switch.kt
@@ -54,6 +54,9 @@
* checked is provided to this action in its ActionParameters, and can be retrieved using the
* [ToggleableStateKey]. If this action launches an activity, the current value of checked will be
* passed as an intent extra with the name [RemoteViews.EXTRA_CHECKED].
+ * In order to allow the Launcher to provide this extra on Android version S and later, we use a
+ * mutable PendingIntent ([android.app.PendingIntent.FLAG_MUTABLE]) when this action is not a
+ * lambda. Before S, and for lambda actions, this will be an immutable PendingIntent.
* @param modifier the modifier to apply to the switch
* @param text the text to display to the end of the switch
* @param style the style to apply to [text]
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/action/ApplyAction.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/action/ApplyAction.kt
index 6e2a201..7a94b75 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/action/ApplyAction.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/action/ApplyAction.kt
@@ -75,6 +75,7 @@
translationContext: TranslationContext,
@IdRes viewId: Int,
editParams: (ActionParameters) -> ActionParameters = { it },
+ mutability: Int = PendingIntent.FLAG_IMMUTABLE,
): PendingIntent {
when (action) {
is StartActivityAction -> {
@@ -93,7 +94,7 @@
)
}
},
- PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
+ mutability or PendingIntent.FLAG_UPDATE_CURRENT,
action.activityOptions,
)
}
@@ -119,7 +120,7 @@
translationContext.context,
0,
intent,
- PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
+ mutability or PendingIntent.FLAG_UPDATE_CURRENT,
)
}
}
@@ -136,7 +137,7 @@
)
}
},
- PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
+ mutability or PendingIntent.FLAG_UPDATE_CURRENT,
)
}
is RunCallbackAction -> {
@@ -156,7 +157,7 @@
ActionTrampolineType.CALLBACK,
)
},
- PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
+ mutability or PendingIntent.FLAG_UPDATE_CURRENT,
)
}
is LambdaAction -> {
@@ -179,7 +180,7 @@
action.key,
)
},
- PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
+ mutability or PendingIntent.FLAG_UPDATE_CURRENT,
)
}
is CompoundButtonAction -> {
@@ -188,6 +189,15 @@
translationContext,
viewId,
action.getActionParameters(),
+ mutability = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
+ action.innerAction !is LambdaAction) {
+ // RemoteViews.setOnCheckedChangedResponse (API 31+) requires a mutable
+ // PendingIntent in order to set the EXTRA_CHECKED extra with the current state
+ // of the button. Lambda actions do not use this extra so they can be immutable.
+ PendingIntent.FLAG_MUTABLE
+ } else {
+ mutability
+ }
)
}
else -> error("Cannot create PendingIntent for action type: $action")
@@ -370,7 +380,7 @@
context,
0,
intent,
- PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
+ PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
)
}
}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index ef9344a..073053b 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -27,14 +27,14 @@
byteBuddy = "1.12.10"
asm = "9.3"
cmake = "3.22.1"
-dagger = "2.48"
+dagger = "2.49"
dexmaker = "2.28.3"
dokka = "1.8.20-dev-214"
espresso = "3.6.0-alpha01"
espressoDevice = "1.0.0-alpha05"
grpc = "1.52.0"
guavaJre = "31.1-jre"
-hilt = "2.48"
+hilt = "2.49"
incap = "0.2"
jcodec = "0.2.5"
kotlin17 = "1.7.10"
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/GLRendererTest.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/GLRendererTest.kt
index 3637e56..d4d4753 100644
--- a/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/GLRendererTest.kt
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/GLRendererTest.kt
@@ -650,9 +650,34 @@
assertNotNull(textureView)
glRenderer = GLRenderer().apply { start() }
- target = glRenderer!!.attach(it.textureView, ColorRenderCallback(Color.BLUE) {
- renderLatch.get().countDown()
- })
+ target = glRenderer!!.attach(it.textureView, ColorRenderCallback(Color.BLUE))
+ val listener = textureView!!.surfaceTextureListener
+ textureView!!.surfaceTextureListener = object : SurfaceTextureListener {
+ override fun onSurfaceTextureAvailable(
+ surface: SurfaceTexture,
+ width: Int,
+ height: Int
+ ) {
+ listener?.onSurfaceTextureAvailable(surface, width, height)
+ }
+
+ override fun onSurfaceTextureSizeChanged(
+ surface: SurfaceTexture,
+ width: Int,
+ height: Int
+ ) {
+ listener?.onSurfaceTextureSizeChanged(surface, width, height)
+ }
+
+ override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
+ return listener?.onSurfaceTextureDestroyed(surface) ?: true
+ }
+
+ override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {
+ listener?.onSurfaceTextureUpdated(surface)
+ renderLatch.get().countDown()
+ }
+ }
}
scenario.moveToState(State.RESUMED)
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlCompatTest.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlCompatTest.kt
index a63e68b..81c25e2 100644
--- a/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlCompatTest.kt
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlCompatTest.kt
@@ -1725,59 +1725,25 @@
createTransaction: (SurfaceView) -> SurfaceControlCompat.Transaction,
verifyOutput: (Bitmap, Rect) -> Boolean
) {
- val transactionLatch = CountDownLatch(1)
- var surfaceView: SurfaceView? = null
- val destroyLatch = CountDownLatch(1)
- val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
- .moveToState(
- Lifecycle.State.CREATED
- ).onActivity {
- it.setDestroyCallback { destroyLatch.countDown() }
- val callback = object : SurfaceHolderCallback() {
- override fun surfaceCreated(sh: SurfaceHolder) {
- surfaceView = it.mSurfaceView
- val transaction = createTransaction(surfaceView!!)
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- transaction.addTransactionCommittedListener(
- executor!!,
- object : SurfaceControlCompat.TransactionCommittedListener {
- override fun onTransactionCommitted() {
- transactionLatch.countDown()
- }
- }
- )
- } else {
- transactionLatch.countDown()
+ SurfaceControlUtils.surfaceControlTestHelper(
+ { surfaceView, latch ->
+ val transaction = createTransaction(surfaceView)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ transaction.addTransactionCommittedListener(
+ executor!!,
+ object : SurfaceControlCompat.TransactionCommittedListener {
+ override fun onTransactionCommitted() {
+ latch.countDown()
+ }
}
- transaction.commit()
- }
- }
-
- it.addSurface(it.mSurfaceView, callback)
- surfaceView = it.mSurfaceView
- }
-
- scenario.moveToState(Lifecycle.State.RESUMED)
-
- assertTrue(transactionLatch.await(3000, TimeUnit.MILLISECONDS))
- val coords = intArrayOf(0, 0)
- surfaceView!!.getLocationOnScreen(coords)
- try {
- SurfaceControlUtils.validateOutput { bitmap ->
- verifyOutput(
- bitmap,
- Rect(
- coords[0],
- coords[1],
- coords[0] + SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
- coords[1] + SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT
)
- )
- }
- } finally {
- scenario.moveToState(Lifecycle.State.DESTROYED)
- assertTrue(destroyLatch.await(3000, TimeUnit.MILLISECONDS))
- }
+ } else {
+ latch.countDown()
+ }
+ transaction.commit()
+ },
+ verifyOutput
+ )
}
fun Color.compositeOver(background: Color): Color {
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlUtils.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlUtils.kt
index 4e89941..74031ed 100644
--- a/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlUtils.kt
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlUtils.kt
@@ -19,19 +19,86 @@
import android.app.Instrumentation
import android.graphics.Bitmap
import android.graphics.Color
+import android.graphics.Rect
import android.hardware.HardwareBuffer
import android.os.Build
import android.os.SystemClock
+import android.view.SurfaceHolder
+import android.view.SurfaceView
import android.view.Window
import androidx.annotation.RequiresApi
+import androidx.lifecycle.Lifecycle
+import androidx.test.core.app.ActivityScenario
import androidx.test.filters.SdkSuppress
import androidx.test.platform.app.InstrumentationRegistry
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
import org.junit.Assert
@SdkSuppress(minSdkVersion = 29)
internal class SurfaceControlUtils {
companion object {
+ @RequiresApi(Build.VERSION_CODES.Q)
+ fun surfaceControlTestHelper(
+ onSurfaceCreated: (SurfaceView, CountDownLatch) -> Unit,
+ verifyOutput: (Bitmap, Rect) -> Boolean
+ ) {
+ val setupLatch = CountDownLatch(1)
+ var surfaceView: SurfaceView? = null
+ val destroyLatch = CountDownLatch(1)
+ val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
+ .moveToState(
+ Lifecycle.State.CREATED
+ ).onActivity {
+ it.setDestroyCallback { destroyLatch.countDown() }
+ val callback = object : SurfaceHolder.Callback {
+ override fun surfaceCreated(sh: SurfaceHolder) {
+ surfaceView = it.mSurfaceView
+ onSurfaceCreated(surfaceView!!, setupLatch)
+ }
+
+ override fun surfaceChanged(
+ holder: SurfaceHolder,
+ format: Int,
+ width: Int,
+ height: Int
+ ) {
+ // NO-OP
+ }
+
+ override fun surfaceDestroyed(holder: SurfaceHolder) {
+ // NO-OP
+ }
+ }
+
+ it.addSurface(it.mSurfaceView, callback)
+ surfaceView = it.mSurfaceView
+ }
+
+ scenario.moveToState(Lifecycle.State.RESUMED)
+
+ Assert.assertTrue(setupLatch.await(3000, TimeUnit.MILLISECONDS))
+ val coords = intArrayOf(0, 0)
+ surfaceView!!.getLocationOnScreen(coords)
+ try {
+ validateOutput { bitmap ->
+ verifyOutput(
+ bitmap,
+ Rect(
+ coords[0],
+ coords[1],
+ coords[0] + SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+ coords[1] + SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT
+ )
+ )
+ }
+ } finally {
+ scenario.moveToState(Lifecycle.State.DESTROYED)
+ Assert.assertTrue(destroyLatch.await(3000, TimeUnit.MILLISECONDS))
+ }
+ }
+
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
fun validateOutput(window: Window, block: (bitmap: Bitmap) -> Boolean) {
val uiAutomation = InstrumentationRegistry.getInstrumentation().uiAutomation
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlWrapperTest.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlWrapperTest.kt
index 89891d4..0902082 100644
--- a/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlWrapperTest.kt
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlWrapperTest.kt
@@ -16,6 +16,7 @@
package androidx.graphics.surface
+import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.ColorSpace
import android.graphics.Rect
@@ -25,6 +26,8 @@
import android.view.Surface
import android.view.SurfaceControl
import android.view.SurfaceHolder
+import android.view.SurfaceView
+import androidx.annotation.RequiresApi
import androidx.lifecycle.Lifecycle
import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -47,7 +50,7 @@
@SmallTest
@SdkSuppress(minSdkVersion = 29)
class SurfaceControlWrapperTest {
- var executor: Executor? = null
+ private var executor: Executor? = null
@Before
fun setup() {
@@ -64,16 +67,20 @@
@Test
fun testCreateFromWindow() {
- var surfaceControl = SurfaceControl.Builder()
+ val surfaceControl = SurfaceControl.Builder()
.setName("SurfaceControlCompact_createFromWindow")
.build()
+ var scWrapper: SurfaceControlWrapper? = null
try {
- SurfaceControlWrapper.Builder()
+ scWrapper = SurfaceControlWrapper.Builder()
.setParent(Surface(surfaceControl))
.setDebugName("SurfaceControlWrapperTest")
.build()
} catch (e: IllegalArgumentException) {
fail()
+ } finally {
+ scWrapper?.release()
+ surfaceControl.release()
}
}
@@ -82,22 +89,29 @@
val surfaceControl = SurfaceControl.Builder()
.setName("SurfaceControlCompact_createFromWindow")
.build()
+ var scWrapper: SurfaceControlWrapper? = null
try {
- SurfaceControlWrapper.Builder()
+ scWrapper = SurfaceControlWrapper.Builder()
.setParent(Surface(surfaceControl))
.setDebugName("SurfaceControlWrapperTest")
.build()
} catch (e: IllegalArgumentException) {
fail()
+ } finally {
+ scWrapper?.release()
+ surfaceControl.release()
}
}
@Test
fun testSurfaceTransactionCreate() {
+ var scWrapperTransaction: SurfaceControlWrapper.Transaction? = null
try {
- SurfaceControlWrapper.Transaction()
+ scWrapperTransaction = SurfaceControlWrapper.Transaction()
} catch (e: java.lang.IllegalArgumentException) {
fail()
+ } finally {
+ scWrapperTransaction?.close()
}
}
@@ -128,8 +142,10 @@
val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
.moveToState(Lifecycle.State.CREATED)
+ val destroyLatch = CountDownLatch(1)
try {
scenario.onActivity {
+ it.setDestroyCallback { destroyLatch.countDown() }
SurfaceControlWrapper.Transaction()
.addTransactionCompletedListener(listener)
.commit()
@@ -143,6 +159,7 @@
} finally {
// ensure activity is destroyed after any failures
scenario.moveToState(Lifecycle.State.DESTROYED)
+ assertTrue(destroyLatch.await(3000, TimeUnit.MILLISECONDS))
}
}
@@ -154,8 +171,10 @@
val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
.moveToState(Lifecycle.State.CREATED)
+ val destroyLatch = CountDownLatch(1)
try {
scenario.onActivity {
+ it.setDestroyCallback { destroyLatch.countDown() }
SurfaceControlWrapper.Transaction()
.addTransactionCommittedListener(executor!!, listener)
.commit()
@@ -168,6 +187,7 @@
} finally {
// ensure activity is destroyed after any failures
scenario.moveToState(Lifecycle.State.DESTROYED)
+ assertTrue(destroyLatch.await(3000, TimeUnit.MILLISECONDS))
}
}
@@ -180,8 +200,10 @@
val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
.moveToState(Lifecycle.State.CREATED)
+ val destroyLatch = CountDownLatch(1)
try {
scenario.onActivity {
+ it.setDestroyCallback { destroyLatch.countDown() }
SurfaceControlWrapper.Transaction()
.addTransactionCommittedListener(executor!!, listener)
.addTransactionCommittedListener(executor!!, listener2)
@@ -201,6 +223,7 @@
} finally {
// ensure activity is destroyed after any failures
scenario.moveToState(Lifecycle.State.DESTROYED)
+ assertTrue(destroyLatch.await(3000, TimeUnit.MILLISECONDS))
}
}
@@ -213,8 +236,10 @@
val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
.moveToState(Lifecycle.State.CREATED)
+ val destroyLatch = CountDownLatch(1)
try {
scenario.onActivity {
+ it.setDestroyCallback { destroyLatch.countDown() }
SurfaceControlWrapper.Transaction()
.addTransactionCommittedListener(executor!!, listener1)
.addTransactionCompletedListener(listener2)
@@ -233,12 +258,13 @@
} finally {
// ensure activity is destroyed after any failures
scenario.moveToState(Lifecycle.State.DESTROYED)
+ assertTrue(destroyLatch.await(3000, TimeUnit.MILLISECONDS))
}
}
@Test
fun testSurfaceControlIsValid_valid() {
- var surfaceControl = SurfaceControl.Builder()
+ val surfaceControl = SurfaceControl.Builder()
.setName("SurfaceControlCompact_createFromWindow")
.build()
var scCompat: SurfaceControlWrapper? = null
@@ -247,16 +273,19 @@
.setParent(Surface(surfaceControl))
.setDebugName("SurfaceControlWrapperTest")
.build()
+
+ assertTrue(scCompat.isValid())
} catch (e: IllegalArgumentException) {
fail()
+ } finally {
+ scCompat?.release()
+ surfaceControl.release()
}
-
- assertTrue(scCompat!!.isValid())
}
@Test
fun testSurfaceControlIsValid_validNotValid() {
- var surfaceControl = SurfaceControl.Builder()
+ val surfaceControl = SurfaceControl.Builder()
.setName("SurfaceControlCompact_createFromWindow")
.build()
var scCompat: SurfaceControlWrapper? = null
@@ -266,18 +295,23 @@
.setParent(Surface(surfaceControl))
.setDebugName("SurfaceControlWrapperTest")
.build()
+ assertTrue(scCompat.isValid())
+
+ scCompat.release()
+ assertFalse(scCompat.isValid())
} catch (e: IllegalArgumentException) {
fail()
+ } finally {
+ if (scCompat != null && scCompat.isValid()) {
+ scCompat.release()
+ }
+ surfaceControl.release()
}
-
- assertTrue(scCompat!!.isValid())
- scCompat.release()
- assertFalse(scCompat.isValid())
}
@Test
fun testSurfaceControlIsValid_multipleRelease() {
- var surfaceControl = SurfaceControl.Builder()
+ val surfaceControl = SurfaceControl.Builder()
.setName("SurfaceControlCompact_createFromWindow")
.build()
var scCompat: SurfaceControlWrapper? = null
@@ -287,668 +321,458 @@
.setParent(Surface(surfaceControl))
.setDebugName("SurfaceControlWrapperTest")
.build()
+ assertTrue(scCompat.isValid())
+ scCompat.release()
+ scCompat.release()
+ assertFalse(scCompat.isValid())
} catch (e: IllegalArgumentException) {
fail()
+ } finally {
+ if (scCompat != null && scCompat.isValid()) {
+ scCompat.release()
+ }
+ surfaceControl.release()
}
-
- assertTrue(scCompat!!.isValid())
- scCompat.release()
- scCompat.release()
- assertFalse(scCompat.isValid())
}
@Test
fun testTransactionReparent_null() {
- val listener = TransactionOnCompleteListener()
- val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
- .moveToState(
- Lifecycle.State.CREATED
- ).onActivity {
- val callback = object : SurfaceHolderCallback() {
- override fun surfaceCreated(sh: SurfaceHolder) {
- val scCompat = SurfaceControlWrapper
- .Builder()
- .setParent(it.getSurfaceView().holder.surface)
- .setDebugName("SurfaceControlWrapperTest")
- .build()
+ verifySurfaceControlWrapperTest(
+ { surfaceView ->
+ val scCompat = SurfaceControlWrapper
+ .Builder()
+ .setParent(surfaceView.holder.surface)
+ .setDebugName("SurfaceControlWrapperTest")
+ .build()
- // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
- val buffer =
- SurfaceControlUtils.getSolidBuffer(
- SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
- SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
- Color.BLUE
- )
- assertNotNull(buffer)
+ // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+ val buffer =
+ SurfaceControlUtils.getSolidBuffer(
+ SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+ SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+ Color.BLUE
+ )
+ assertNotNull(buffer)
- SurfaceControlWrapper.Transaction()
- .setBuffer(scCompat, buffer)
- .reparent(scCompat, null)
- .commit()
-
- // Trying to set a callback with a transaction of a null reparent doesn't
- // get called, so lets set a listener for a 2nd transaction instead. This
- // should be placed in the queue where this will be executed after the
- // reparent transaction
- SurfaceControlWrapper.Transaction()
- .addTransactionCompletedListener(listener)
- .commit()
- }
- }
-
- it.addSurface(it.mSurfaceView, callback)
+ SurfaceControlWrapper.Transaction()
+ .setBuffer(scCompat, buffer)
+ .reparent(scCompat, null)
+ },
+ { bitmap, rect ->
+ Color.BLACK == bitmap.getPixel(rect.left, rect.top)
}
-
- scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
- assertTrue(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
- SurfaceControlUtils.validateOutput { bitmap ->
- val coord = intArrayOf(0, 0)
- it.mSurfaceView.getLocationOnScreen(coord)
- Color.BLACK == bitmap.getPixel(coord[0], coord[1])
- }
- }
+ )
}
@Test
fun testTransactionReparent_childOfSibling() {
- val listener = TransactionOnCompleteListener()
- val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
- .moveToState(
- Lifecycle.State.CREATED
- ).onActivity {
- val callback = object : SurfaceHolderCallback() {
- override fun surfaceCreated(sh: SurfaceHolder) {
- val scCompat = SurfaceControlWrapper
- .Builder()
- .setParent(it.getSurfaceView().holder.surface)
- .setDebugName("SurfaceControlWrapperTest")
- .build()
- val scCompat2 = SurfaceControlWrapper
- .Builder()
- .setParent(it.getSurfaceView().holder.surface)
- .setDebugName("SurfaceControlWrapper")
- .build()
+ verifySurfaceControlWrapperTest(
+ { surfaceView ->
+ val scCompat = SurfaceControlWrapper
+ .Builder()
+ .setParent(surfaceView.holder.surface)
+ .setDebugName("SurfaceControlWrapperTest")
+ .build()
+ val scCompat2 = SurfaceControlWrapper
+ .Builder()
+ .setParent(surfaceView.holder.surface)
+ .setDebugName("SurfaceControlWrapper")
+ .build()
- // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
- val buffer =
- SurfaceControlUtils.getSolidBuffer(
- SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
- SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
- Color.BLUE
- )
- assertNotNull(buffer)
+ // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+ val buffer =
+ SurfaceControlUtils.getSolidBuffer(
+ SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+ SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+ Color.BLUE
+ )
+ assertNotNull(buffer)
- val buffer2 =
- SurfaceControlUtils.getSolidBuffer(
- SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
- SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
- Color.GREEN
- )
- assertNotNull(buffer2)
+ val buffer2 =
+ SurfaceControlUtils.getSolidBuffer(
+ SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+ SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+ Color.GREEN
+ )
+ assertNotNull(buffer2)
- SurfaceControlWrapper.Transaction()
- .addTransactionCompletedListener(listener)
- .setBuffer(scCompat, buffer)
- .setBuffer(scCompat2, buffer2)
- .reparent(scCompat, scCompat2)
- .commit()
- }
- }
-
- it.addSurface(it.mSurfaceView, callback)
+ SurfaceControlWrapper.Transaction()
+ .setBuffer(scCompat, buffer)
+ .setBuffer(scCompat2, buffer2)
+ .reparent(scCompat, scCompat2)
+ },
+ { bitmap, rect ->
+ Color.RED == bitmap.getPixel(rect.left, rect.top)
}
-
- scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
- assertTrue(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
- SurfaceControlUtils.validateOutput { bitmap ->
- val coord = intArrayOf(0, 0)
- it.mSurfaceView.getLocationOnScreen(coord)
- Color.RED == bitmap.getPixel(coord[0], coord[1])
- }
- }
+ )
}
@Test
fun testTransactionSetVisibility_show() {
- val listener = TransactionOnCompleteListener()
- val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
- .moveToState(
- Lifecycle.State.CREATED
- ).onActivity {
- val callback = object : SurfaceHolderCallback() {
- override fun surfaceCreated(sh: SurfaceHolder) {
- val scCompat = SurfaceControlWrapper
- .Builder()
- .setParent(it.getSurfaceView().holder.surface)
- .setDebugName("SurfaceControlWrapperTest")
- .build()
+ verifySurfaceControlWrapperTest(
+ { surfaceView ->
+ val scCompat = SurfaceControlWrapper
+ .Builder()
+ .setParent(surfaceView.holder.surface)
+ .setDebugName("SurfaceControlWrapperTest")
+ .build()
- // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
- val buffer =
- SurfaceControlUtils.getSolidBuffer(
- SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
- SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
- Color.BLUE
- )
- assertNotNull(buffer)
+ // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+ val buffer =
+ SurfaceControlUtils.getSolidBuffer(
+ SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+ SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+ Color.BLUE
+ )
+ assertNotNull(buffer)
- SurfaceControlWrapper.Transaction()
- .addTransactionCompletedListener(listener)
- .setBuffer(scCompat, buffer)
- .setVisibility(
- scCompat,
- true
- ).commit()
- }
- }
-
- it.addSurface(it.mSurfaceView, callback)
+ SurfaceControlWrapper.Transaction()
+ .setBuffer(scCompat, buffer)
+ .setVisibility(
+ scCompat,
+ true
+ )
+ },
+ { bitmap, rect ->
+ Color.RED == bitmap.getPixel(rect.left, rect.top)
}
-
- scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
- assertTrue(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
- SurfaceControlUtils.validateOutput { bitmap ->
- val coord = intArrayOf(0, 0)
- it.mSurfaceView.getLocationOnScreen(coord)
- Color.RED == bitmap.getPixel(coord[0], coord[1])
- }
- }
+ )
}
@Test
fun testTransactionSetVisibility_hide() {
- val listener = TransactionOnCompleteListener()
- val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
- .moveToState(
- Lifecycle.State.CREATED
- ).onActivity {
- val callback = object : SurfaceHolderCallback() {
- override fun surfaceCreated(sh: SurfaceHolder) {
- val scCompat = SurfaceControlWrapper
- .Builder()
- .setParent(it.getSurfaceView().holder.surface)
- .setDebugName("SurfaceControlWrapperTest")
- .build()
+ verifySurfaceControlWrapperTest(
+ { surfaceView ->
+ val scCompat = SurfaceControlWrapper
+ .Builder()
+ .setParent(surfaceView.holder.surface)
+ .setDebugName("SurfaceControlWrapperTest")
+ .build()
- // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
- val buffer =
- SurfaceControlUtils.getSolidBuffer(
- SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
- SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
- Color.BLUE
- )
- assertNotNull(buffer)
+ // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+ val buffer =
+ SurfaceControlUtils.getSolidBuffer(
+ SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+ SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+ Color.BLUE
+ )
+ assertNotNull(buffer)
- SurfaceControlWrapper.Transaction()
- .addTransactionCompletedListener(listener)
- .setBuffer(scCompat, buffer)
- .setVisibility(
- scCompat,
- false
- ).commit()
- }
- }
-
- it.addSurface(it.mSurfaceView, callback)
+ SurfaceControlWrapper.Transaction()
+ .setBuffer(scCompat, buffer)
+ .setVisibility(
+ scCompat,
+ false
+ )
+ },
+ { bitmap, rect ->
+ Color.BLACK == bitmap.getPixel(rect.left, rect.top)
}
-
- scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
- assertTrue(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
- SurfaceControlUtils.validateOutput { bitmap ->
- val coord = intArrayOf(0, 0)
- it.mSurfaceView.getLocationOnScreen(coord)
- Color.BLACK == bitmap.getPixel(coord[0], coord[1])
- }
- }
+ )
}
@Test
fun testTransactionSetLayer_zero() {
- val listener = TransactionOnCompleteListener()
- val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
- .moveToState(
- Lifecycle.State.CREATED
- ).onActivity {
- val callback = object : SurfaceHolderCallback() {
- override fun surfaceCreated(sh: SurfaceHolder) {
- val scCompat1 = SurfaceControlWrapper
- .Builder()
- .setParent(it.getSurfaceView().holder.surface)
- .setDebugName("SurfaceControlWrapperTest")
- .build()
- val scCompat2 = SurfaceControlWrapper
- .Builder()
- .setParent(it.getSurfaceView().holder.surface)
- .setDebugName("SurfaceControlWrapperTest")
- .build()
+ verifySurfaceControlWrapperTest(
+ { surfaceView ->
+ val scCompat1 = SurfaceControlWrapper
+ .Builder()
+ .setParent(surfaceView.holder.surface)
+ .setDebugName("SurfaceControlWrapperTest")
+ .build()
+ val scCompat2 = SurfaceControlWrapper
+ .Builder()
+ .setParent(surfaceView.holder.surface)
+ .setDebugName("SurfaceControlWrapperTest")
+ .build()
- // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
- SurfaceControlWrapper.Transaction()
- .addTransactionCompletedListener(listener)
- .setLayer(scCompat1, 1)
- .setBuffer(
- scCompat1,
- SurfaceControlUtils.getSolidBuffer(
- SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
- SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
- Color.BLUE
- )
- )
- .setLayer(scCompat2, 0)
- .setBuffer(
- scCompat2,
- SurfaceControlUtils.getSolidBuffer(
- SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
- SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
- Color.GREEN
- )
- )
- .commit()
- }
- }
-
- it.addSurface(it.mSurfaceView, callback)
+ // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+ SurfaceControlWrapper.Transaction()
+ .setLayer(scCompat1, 1)
+ .setBuffer(
+ scCompat1,
+ SurfaceControlUtils.getSolidBuffer(
+ SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+ SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+ Color.BLUE
+ )
+ )
+ .setLayer(scCompat2, 0)
+ .setBuffer(
+ scCompat2,
+ SurfaceControlUtils.getSolidBuffer(
+ SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+ SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+ Color.GREEN
+ )
+ )
+ },
+ { bitmap, rect ->
+ Color.RED == bitmap.getPixel(rect.left, rect.top)
}
-
- scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
- assert(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
- SurfaceControlUtils.validateOutput { bitmap ->
- val coord = intArrayOf(0, 0)
- it.mSurfaceView.getLocationOnScreen(coord)
- Color.RED == bitmap.getPixel(coord[0], coord[1])
- }
- }
+ )
}
@Test
fun testTransactionSetLayer_positive() {
- val listener = TransactionOnCompleteListener()
- val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
- .moveToState(
- Lifecycle.State.CREATED
- ).onActivity {
- val callback = object : SurfaceHolderCallback() {
- override fun surfaceCreated(sh: SurfaceHolder) {
- val scCompat1 = SurfaceControlWrapper
- .Builder()
- .setParent(it.getSurfaceView().holder.surface)
- .setDebugName("SurfaceControlWrapperTest")
- .build()
- val scCompat2 = SurfaceControlWrapper
- .Builder()
- .setParent(it.getSurfaceView().holder.surface)
- .setDebugName("SurfaceControlWrapperTest")
- .build()
+ verifySurfaceControlWrapperTest(
+ { surfaceView ->
+ val scCompat1 = SurfaceControlWrapper
+ .Builder()
+ .setParent(surfaceView.holder.surface)
+ .setDebugName("SurfaceControlWrapperTest")
+ .build()
+ val scCompat2 = SurfaceControlWrapper
+ .Builder()
+ .setParent(surfaceView.holder.surface)
+ .setDebugName("SurfaceControlWrapperTest")
+ .build()
- // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
- SurfaceControlWrapper.Transaction()
- .addTransactionCompletedListener(listener)
- .setLayer(scCompat1, 1)
- .setBuffer(
- scCompat1,
- SurfaceControlUtils.getSolidBuffer(
- SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
- SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
- Color.GREEN
- )
- )
- .setLayer(scCompat2, 24)
- .setBuffer(
- scCompat2,
- SurfaceControlUtils.getSolidBuffer(
- SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
- SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
- Color.BLUE
- )
- )
- .commit()
- }
- }
-
- it.addSurface(it.mSurfaceView, callback)
+ // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+ SurfaceControlWrapper.Transaction()
+ .setLayer(scCompat1, 1)
+ .setBuffer(
+ scCompat1,
+ SurfaceControlUtils.getSolidBuffer(
+ SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+ SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+ Color.GREEN
+ )
+ )
+ .setLayer(scCompat2, 24)
+ .setBuffer(
+ scCompat2,
+ SurfaceControlUtils.getSolidBuffer(
+ SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+ SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+ Color.BLUE
+ )
+ )
+ },
+ { bitmap, rect ->
+ Color.RED == bitmap.getPixel(rect.left, rect.top)
}
-
- scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
- assert(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
- SurfaceControlUtils.validateOutput { bitmap ->
- val coord = intArrayOf(0, 0)
- it.mSurfaceView.getLocationOnScreen(coord)
- Color.RED == bitmap.getPixel(coord[0], coord[1])
- }
- }
+ )
}
@Test
fun testTransactionSetLayer_negative() {
- val listener = TransactionOnCompleteListener()
- val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
- .moveToState(
- Lifecycle.State.CREATED
- ).onActivity {
- val callback = object : SurfaceHolderCallback() {
- override fun surfaceCreated(sh: SurfaceHolder) {
- val scCompat1 = SurfaceControlWrapper
- .Builder()
- .setParent(it.getSurfaceView().holder.surface)
- .setDebugName("SurfaceControlWrapperTest")
- .build()
- val scCompat2 = SurfaceControlWrapper
- .Builder()
- .setParent(it.getSurfaceView().holder.surface)
- .setDebugName("SurfaceControlWrapperTest")
- .build()
+ verifySurfaceControlWrapperTest(
+ { surfaceView ->
+ val scCompat1 = SurfaceControlWrapper
+ .Builder()
+ .setParent(surfaceView.holder.surface)
+ .setDebugName("SurfaceControlWrapperTest")
+ .build()
+ val scCompat2 = SurfaceControlWrapper
+ .Builder()
+ .setParent(surfaceView.holder.surface)
+ .setDebugName("SurfaceControlWrapperTest")
+ .build()
- // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
- SurfaceControlWrapper.Transaction()
- .addTransactionCompletedListener(listener)
- .setLayer(scCompat1, 1)
- .setBuffer(
- scCompat1,
- SurfaceControlUtils.getSolidBuffer(
- SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
- SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
- Color.BLUE
- )
- )
- .setLayer(scCompat2, -7)
- .setBuffer(
- scCompat2,
- SurfaceControlUtils.getSolidBuffer(
- SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
- SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
- Color.GREEN
- )
- )
- .commit()
- }
- }
-
- it.addSurface(it.mSurfaceView, callback)
+ // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+ SurfaceControlWrapper.Transaction()
+ .setLayer(scCompat1, 1)
+ .setBuffer(
+ scCompat1,
+ SurfaceControlUtils.getSolidBuffer(
+ SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+ SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+ Color.BLUE
+ )
+ )
+ .setLayer(scCompat2, -7)
+ .setBuffer(
+ scCompat2,
+ SurfaceControlUtils.getSolidBuffer(
+ SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+ SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+ Color.GREEN
+ )
+ )
+ },
+ { bitmap, rect ->
+ Color.RED == bitmap.getPixel(rect.left, rect.top)
}
-
- scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
- assert(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
- SurfaceControlUtils.validateOutput { bitmap ->
- val coord = intArrayOf(0, 0)
- it.mSurfaceView.getLocationOnScreen(coord)
- Color.RED == bitmap.getPixel(coord[0], coord[1])
- }
- }
+ )
}
@Test
fun testTransactionSetDamageRegion_all() {
- val listener = TransactionOnCompleteListener()
- val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
- .moveToState(
- Lifecycle.State.CREATED
- ).onActivity {
- val callback = object : SurfaceHolderCallback() {
- override fun surfaceCreated(sh: SurfaceHolder) {
- val scCompat = SurfaceControlWrapper
- .Builder()
- .setParent(it.getSurfaceView().holder.surface)
- .setDebugName("SurfaceControlWrapperTest")
- .build()
+ verifySurfaceControlWrapperTest(
+ { surfaceView ->
+ val scCompat = SurfaceControlWrapper
+ .Builder()
+ .setParent(surfaceView.holder.surface)
+ .setDebugName("SurfaceControlWrapperTest")
+ .build()
- // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
- SurfaceControlWrapper.Transaction()
- .addTransactionCompletedListener(listener)
- .setDamageRegion(
- scCompat,
- Region(
- 0,
- 0,
- SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
- SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT
- )
- )
- .setBuffer(
- scCompat,
- SurfaceControlUtils.getSolidBuffer(
- SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
- SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
- Color.BLUE
- )
- )
- .commit()
- }
- }
-
- it.addSurface(it.mSurfaceView, callback)
+ // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+ SurfaceControlWrapper.Transaction()
+ .setDamageRegion(
+ scCompat,
+ Region(
+ 0,
+ 0,
+ SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+ SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT
+ )
+ )
+ .setBuffer(
+ scCompat,
+ SurfaceControlUtils.getSolidBuffer(
+ SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+ SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+ Color.BLUE
+ )
+ )
+ },
+ { bitmap, rect ->
+ Color.RED == bitmap.getPixel(rect.left, rect.top)
}
-
- scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
- assert(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
- SurfaceControlUtils.validateOutput { bitmap ->
- val coord = intArrayOf(0, 0)
- it.mSurfaceView.getLocationOnScreen(coord)
- Color.RED == bitmap.getPixel(coord[0], coord[1])
- }
- }
+ )
}
@Test
fun testTransactionSetDamageRegion_null() {
- val listener = TransactionOnCompleteListener()
- val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
- .moveToState(
- Lifecycle.State.CREATED
- ).onActivity {
- val callback = object : SurfaceHolderCallback() {
- override fun surfaceCreated(sh: SurfaceHolder) {
- val scCompat = SurfaceControlWrapper
- .Builder()
- .setParent(it.getSurfaceView().holder.surface)
- .setDebugName("SurfaceControlWrapperTest")
- .build()
+ verifySurfaceControlWrapperTest(
+ { surfaceView ->
+ val scCompat = SurfaceControlWrapper
+ .Builder()
+ .setParent(surfaceView.holder.surface)
+ .setDebugName("SurfaceControlWrapperTest")
+ .build()
- // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
- SurfaceControlWrapper.Transaction()
- .addTransactionCompletedListener(listener)
- .setDamageRegion(
- scCompat,
- null
- )
- .setBuffer(
- scCompat,
- SurfaceControlUtils.getSolidBuffer(
- SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
- SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
- Color.BLUE
- )
- )
- .commit()
- }
- }
-
- it.addSurface(it.mSurfaceView, callback)
+ // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+ SurfaceControlWrapper.Transaction()
+ .setDamageRegion(
+ scCompat,
+ null
+ )
+ .setBuffer(
+ scCompat,
+ SurfaceControlUtils.getSolidBuffer(
+ SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+ SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+ Color.BLUE
+ )
+ )
+ },
+ { bitmap, rect ->
+ Color.RED == bitmap.getPixel(rect.left, rect.top)
}
-
- scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
- assert(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
- SurfaceControlUtils.validateOutput { bitmap ->
- val coord = intArrayOf(0, 0)
- it.mSurfaceView.getLocationOnScreen(coord)
- Color.RED == bitmap.getPixel(coord[0], coord[1])
- }
- }
+ )
}
@Test
fun testTransactionSetDesiredPresentTime_now() {
- val listener = TransactionOnCompleteListener()
- val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
- .moveToState(
- Lifecycle.State.CREATED
- ).onActivity {
- val callback = object : SurfaceHolderCallback() {
- override fun surfaceCreated(sh: SurfaceHolder) {
- val scCompat = SurfaceControlWrapper
- .Builder()
- .setParent(it.getSurfaceView().holder.surface)
- .setDebugName("SurfaceControlWrapperTest")
- .build()
+ verifySurfaceControlWrapperTest(
+ { surfaceView ->
+ val scCompat = SurfaceControlWrapper
+ .Builder()
+ .setParent(surfaceView.holder.surface)
+ .setDebugName("SurfaceControlWrapperTest")
+ .build()
- // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
- SurfaceControlWrapper.Transaction()
- .addTransactionCompletedListener(listener)
- .setBuffer(
- scCompat,
- SurfaceControlUtils.getSolidBuffer(
- SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
- SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
- Color.BLUE
- )
- )
- .setDesiredPresentTime(0)
- .commit()
- }
- }
-
- it.addSurface(it.mSurfaceView, callback)
+ // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+ SurfaceControlWrapper.Transaction()
+ .setBuffer(
+ scCompat,
+ SurfaceControlUtils.getSolidBuffer(
+ SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+ SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+ Color.BLUE
+ )
+ )
+ .setDesiredPresentTime(0)
+ },
+ { bitmap, rect ->
+ Color.RED == bitmap.getPixel(rect.left, rect.top)
}
-
- scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
- assert(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
- SurfaceControlUtils.validateOutput { bitmap ->
- val coord = intArrayOf(0, 0)
- it.mSurfaceView.getLocationOnScreen(coord)
- Color.RED == bitmap.getPixel(coord[0], coord[1])
- }
- }
+ )
}
@Test
fun testTransactionSetBufferTransparency_opaque() {
- val listener = TransactionOnCompleteListener()
- val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
- .moveToState(
- Lifecycle.State.CREATED
- ).onActivity {
- val callback = object : SurfaceHolderCallback() {
- override fun surfaceCreated(sh: SurfaceHolder) {
- val scCompat = SurfaceControlWrapper
- .Builder()
- .setParent(it.getSurfaceView().holder.surface)
- .setDebugName("SurfaceControlWrapperTest")
- .build()
+ verifySurfaceControlWrapperTest(
+ { surfaceView ->
+ val scCompat = SurfaceControlWrapper
+ .Builder()
+ .setParent(surfaceView.holder.surface)
+ .setDebugName("SurfaceControlWrapperTest")
+ .build()
- // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
- val buffer = SurfaceControlUtils.getSolidBuffer(
- SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
- SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
- Color.BLUE
- )
- SurfaceControlWrapper.Transaction()
- .addTransactionCompletedListener(listener)
- .setBuffer(scCompat, buffer)
- .setOpaque(
- scCompat,
- true
- )
- .commit()
- }
- }
-
- it.addSurface(it.mSurfaceView, callback)
+ // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+ val buffer = SurfaceControlUtils.getSolidBuffer(
+ SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+ SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+ Color.BLUE
+ )
+ SurfaceControlWrapper.Transaction()
+ .setBuffer(scCompat, buffer)
+ .setOpaque(
+ scCompat,
+ true
+ )
+ },
+ { bitmap, rect ->
+ Color.RED == bitmap.getPixel(rect.left, rect.top)
}
-
- scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
- assert(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
- SurfaceControlUtils.validateOutput { bitmap ->
- val coord = intArrayOf(0, 0)
- it.mSurfaceView.getLocationOnScreen(coord)
- Color.RED == bitmap.getPixel(coord[0], coord[1])
- }
- }
+ )
}
@Test
fun testTransactionSetAlpha_0_0() {
- val listener = TransactionOnCompleteListener()
- val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
- .moveToState(
- Lifecycle.State.CREATED
- ).onActivity {
- val callback = object : SurfaceHolderCallback() {
- override fun surfaceCreated(sh: SurfaceHolder) {
- val scCompat = SurfaceControlWrapper
- .Builder()
- .setParent(it.getSurfaceView().holder.surface)
- .setDebugName("SurfaceControlWrapperTest")
- .build()
+ verifySurfaceControlWrapperTest(
+ { surfaceView ->
+ val scCompat = SurfaceControlWrapper
+ .Builder()
+ .setParent(surfaceView.holder.surface)
+ .setDebugName("SurfaceControlWrapperTest")
+ .build()
- // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
- val buffer = SurfaceControlUtils.getSolidBuffer(
- SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
- SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
- Color.BLUE
- )
- SurfaceControlWrapper.Transaction()
- .addTransactionCompletedListener(listener)
- .setBuffer(scCompat, buffer)
- .setOpaque(
- scCompat,
- false
- )
- .setAlpha(scCompat, 0.0f)
- .commit()
- }
- }
-
- it.addSurface(it.mSurfaceView, callback)
+ // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+ val buffer = SurfaceControlUtils.getSolidBuffer(
+ SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+ SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+ Color.BLUE
+ )
+ SurfaceControlWrapper.Transaction()
+ .setBuffer(scCompat, buffer)
+ .setOpaque(
+ scCompat,
+ false
+ )
+ .setAlpha(scCompat, 0.0f)
+ },
+ { bitmap, rect ->
+ Color.BLACK == bitmap.getPixel(rect.left, rect.top)
}
-
- scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
- assert(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
- SurfaceControlUtils.validateOutput { bitmap ->
- val coord = intArrayOf(0, 0)
- it.mSurfaceView.getLocationOnScreen(coord)
- Color.BLACK == bitmap.getPixel(coord[0], coord[1])
- }
- }
+ )
}
@Test
fun testTransactionSetAlpha_0_5() {
- val listener = TransactionOnCompleteListener()
- val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
- .moveToState(
- Lifecycle.State.CREATED
- ).onActivity {
- val callback = object : SurfaceHolderCallback() {
- override fun surfaceCreated(sh: SurfaceHolder) {
- val scCompat = SurfaceControlWrapper
- .Builder()
- .setParent(it.getSurfaceView().holder.surface)
- .setDebugName("SurfaceControlWrapperTest")
- .build()
+ verifySurfaceControlWrapperTest(
+ { surfaceView ->
+ val scCompat = SurfaceControlWrapper
+ .Builder()
+ .setParent(surfaceView.holder.surface)
+ .setDebugName("SurfaceControlWrapperTest")
+ .build()
- // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
- val buffer = SurfaceControlUtils.getSolidBuffer(
- SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
- SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
- Color.BLUE
- )
- SurfaceControlWrapper.Transaction()
- .addTransactionCompletedListener(listener)
- .setBuffer(scCompat, buffer)
- .setAlpha(scCompat, 0.5f)
- .commit()
- }
- }
-
- it.addSurface(it.mSurfaceView, callback)
- }
-
- scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
- assert(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
-
- SurfaceControlUtils.validateOutput { bitmap ->
- val coord = intArrayOf(0, 0)
- it.mSurfaceView.getLocationOnScreen(coord)
-
+ // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+ val buffer = SurfaceControlUtils.getSolidBuffer(
+ SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+ SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+ Color.BLUE
+ )
+ SurfaceControlWrapper.Transaction()
+ .setBuffer(scCompat, buffer)
+ .setAlpha(scCompat, 0.5f)
+ },
+ { bitmap, rect ->
+ val coord = intArrayOf(rect.left, rect.top)
val fConnector: ColorSpace.Connector = ColorSpace.connect(
ColorSpace.get(ColorSpace.Named.SRGB),
bitmap.colorSpace!!
@@ -969,139 +793,93 @@
expectedResult.blue() - bitmap.getColor(coord[0], coord[1]).blue()
) < 2.5e-3f)
}
- }
+ )
}
@Test
fun testTransactionSetAlpha_1_0() {
- val listener = TransactionOnCompleteListener()
- val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
- .moveToState(
- Lifecycle.State.CREATED
- ).onActivity {
- val callback = object : SurfaceHolderCallback() {
- override fun surfaceCreated(sh: SurfaceHolder) {
- val scCompat = SurfaceControlWrapper
- .Builder()
- .setParent(it.getSurfaceView().holder.surface)
- .setDebugName("SurfaceControlWrapperTest")
- .build()
+ verifySurfaceControlWrapperTest(
+ { surfaceView ->
+ val scCompat = SurfaceControlWrapper
+ .Builder()
+ .setParent(surfaceView.holder.surface)
+ .setDebugName("SurfaceControlWrapperTest")
+ .build()
- // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
- val buffer = SurfaceControlUtils.getSolidBuffer(
- SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
- SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
- Color.BLUE
- )
- SurfaceControlWrapper.Transaction()
- .addTransactionCompletedListener(listener)
- .setBuffer(scCompat, buffer)
- .setAlpha(scCompat, 1.0f)
- .commit()
- }
- }
-
- it.addSurface(it.mSurfaceView, callback)
+ // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+ val buffer = SurfaceControlUtils.getSolidBuffer(
+ SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+ SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+ Color.BLUE
+ )
+ SurfaceControlWrapper.Transaction()
+ .setBuffer(scCompat, buffer)
+ .setAlpha(scCompat, 1.0f)
+ },
+ { bitmap, rect ->
+ Color.RED == bitmap.getPixel(rect.left, rect.top)
}
-
- scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
- assert(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
- SurfaceControlUtils.validateOutput { bitmap ->
- val coord = intArrayOf(0, 0)
- it.mSurfaceView.getLocationOnScreen(coord)
- Color.RED == bitmap.getPixel(coord[0], coord[1])
- }
- }
+ )
}
@Test
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
fun testTransactionSetCrop_null() {
- val listener = TransactionOnCompleteListener()
- val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
- .moveToState(
- Lifecycle.State.CREATED
- ).onActivity {
- val callback = object : SurfaceHolderCallback() {
- override fun surfaceCreated(sh: SurfaceHolder) {
- val scCompat = SurfaceControlWrapper
- .Builder()
- .setParent(it.getSurfaceView().holder.surface)
- .setDebugName("SurfaceControlWrapperTest")
- .build()
+ verifySurfaceControlWrapperTest(
+ { surfaceView ->
+ val scCompat = SurfaceControlWrapper
+ .Builder()
+ .setParent(surfaceView.holder.surface)
+ .setDebugName("SurfaceControlWrapperTest")
+ .build()
- // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
- val buffer = SurfaceControlUtils.getSolidBuffer(
- SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
- SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
- Color.BLUE
- )
+ // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+ val buffer = SurfaceControlUtils.getSolidBuffer(
+ SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+ SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+ Color.BLUE
+ )
- SurfaceControlWrapper.Transaction()
- .addTransactionCompletedListener(listener)
- .setBuffer(scCompat, buffer)
- .setVisibility(scCompat, true)
- .setCrop(scCompat, null)
- .commit()
- }
- }
-
- it.addSurface(it.mSurfaceView, callback)
- }
-
- scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
- assert(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
- SurfaceControlUtils.validateOutput { bitmap ->
- val coord = intArrayOf(0, 0)
- it.mSurfaceView.getLocationOnScreen(coord)
+ SurfaceControlWrapper.Transaction()
+ .setBuffer(scCompat, buffer)
+ .setVisibility(scCompat, true)
+ .setCrop(scCompat, null)
+ },
+ { bitmap, rect ->
+ val coord = intArrayOf(rect.left, rect.top)
SurfaceControlUtils.checkNullCrop(bitmap, coord)
}
- }
+ )
}
@Test
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
fun testTransactionSetCrop_standardCrop() {
- val listener = TransactionOnCompleteListener()
- val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
- .moveToState(
- Lifecycle.State.CREATED
- ).onActivity {
- val callback = object : SurfaceHolderCallback() {
- override fun surfaceCreated(sh: SurfaceHolder) {
- val scCompat = SurfaceControlWrapper
- .Builder()
- .setParent(it.getSurfaceView().holder.surface)
- .setDebugName("SurfaceControlCompatTest")
- .build()
+ verifySurfaceControlWrapperTest(
+ { surfaceView ->
+ val scCompat = SurfaceControlWrapper
+ .Builder()
+ .setParent(surfaceView.holder.surface)
+ .setDebugName("SurfaceControlCompatTest")
+ .build()
- // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
- val buffer = SurfaceControlUtils.getSolidBuffer(
- SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
- SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
- Color.BLUE
- )
+ // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+ val buffer = SurfaceControlUtils.getSolidBuffer(
+ SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+ SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+ Color.BLUE
+ )
- SurfaceControlWrapper.Transaction()
- .addTransactionCompletedListener(listener)
- .setBuffer(scCompat, buffer)
- .setVisibility(scCompat, true)
- .setCrop(scCompat, Rect(20, 30, 90, 60))
- .commit()
- }
- }
-
- it.addSurface(it.mSurfaceView, callback)
- }
-
- scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
- assert(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
- SurfaceControlUtils.validateOutput { bitmap ->
- val coord = intArrayOf(0, 0)
- it.mSurfaceView.getLocationOnScreen(coord)
+ SurfaceControlWrapper.Transaction()
+ .setBuffer(scCompat, buffer)
+ .setVisibility(scCompat, true)
+ .setCrop(scCompat, Rect(20, 30, 90, 60))
+ },
+ { bitmap, rect ->
+ val coord = intArrayOf(rect.left, rect.top)
SurfaceControlUtils.checkStandardCrop(bitmap, coord)
}
- }
+ )
}
@Test
@@ -1172,44 +950,28 @@
@Test
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
fun testTransactionSetPosition() {
- val listener = TransactionOnCompleteListener()
- val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
- .moveToState(
- Lifecycle.State.CREATED
- ).onActivity {
- val callback = object : SurfaceHolderCallback() {
- override fun surfaceCreated(sh: SurfaceHolder) {
- val scCompat = SurfaceControlWrapper
- .Builder()
- .setParent(it.getSurfaceView().holder.surface)
- .setDebugName("SurfaceControlCompatTest")
- .build()
+ verifySurfaceControlWrapperTest(
+ { surfaceView ->
+ val scCompat = SurfaceControlWrapper
+ .Builder()
+ .setParent(surfaceView.holder.surface)
+ .setDebugName("SurfaceControlCompatTest")
+ .build()
- // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
- val buffer = SurfaceControlUtils.getSolidBuffer(
- SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
- SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
- Color.BLUE
- )
+ // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+ val buffer = SurfaceControlUtils.getSolidBuffer(
+ SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+ SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+ Color.BLUE
+ )
- SurfaceControlWrapper.Transaction()
- .addTransactionCompletedListener(listener)
- .setBuffer(scCompat, buffer)
- .setVisibility(scCompat, true)
- .setPosition(scCompat, 30f, 30f)
- .commit()
- }
- }
-
- it.addSurface(it.mSurfaceView, callback)
- }
-
- scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
- assert(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
- SurfaceControlUtils.validateOutput { bitmap ->
- val coord = intArrayOf(0, 0)
- it.mSurfaceView.getLocationOnScreen(coord)
- // Ensure it actually shifted by checking its outer bounds are black
+ SurfaceControlWrapper.Transaction()
+ .setBuffer(scCompat, buffer)
+ .setVisibility(scCompat, true)
+ .setPosition(scCompat, 30f, 30f)
+ },
+ { bitmap, rect ->
+ val coord = intArrayOf(rect.left, rect.top)
Color.BLACK ==
bitmap.getPixel(
coord[0] + SurfaceControlWrapperTestActivity.DEFAULT_WIDTH / 2,
@@ -1222,49 +984,34 @@
) &&
Color.RED == bitmap.getPixel(coord[0] + 30, coord[1] + 30)
}
- }
+ )
}
@Test
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
fun testTransactionSetScale() {
- val listener = TransactionOnCompleteListener()
- val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
- .moveToState(
- Lifecycle.State.CREATED
- ).onActivity {
- val callback = object : SurfaceHolderCallback() {
- override fun surfaceCreated(sh: SurfaceHolder) {
- val scCompat = SurfaceControlWrapper
- .Builder()
- .setParent(it.getSurfaceView().holder.surface)
- .setDebugName("SurfaceControlCompatTest")
- .build()
+ verifySurfaceControlWrapperTest(
+ { surfaceView ->
+ val scCompat = SurfaceControlWrapper
+ .Builder()
+ .setParent(surfaceView.holder.surface)
+ .setDebugName("SurfaceControlCompatTest")
+ .build()
- // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
- val buffer = SurfaceControlUtils.getSolidBuffer(
- SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
- SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
- Color.BLUE
- )
+ // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+ val buffer = SurfaceControlUtils.getSolidBuffer(
+ SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+ SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+ Color.BLUE
+ )
- SurfaceControlWrapper.Transaction()
- .addTransactionCompletedListener(listener)
- .setBuffer(scCompat, buffer)
- .setVisibility(scCompat, true)
- .setScale(scCompat, 0.5f, 0.5f)
- .commit()
- }
- }
-
- it.addSurface(it.mSurfaceView, callback)
- }
-
- scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
- assert(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
- SurfaceControlUtils.validateOutput { bitmap ->
- val coord = intArrayOf(0, 0)
- it.mSurfaceView.getLocationOnScreen(coord)
+ SurfaceControlWrapper.Transaction()
+ .setBuffer(scCompat, buffer)
+ .setVisibility(scCompat, true)
+ .setScale(scCompat, 0.5f, 0.5f)
+ },
+ { bitmap, rect ->
+ val coord = intArrayOf(rect.left, rect.top)
// Check outer bounds of square to ensure its scaled correctly
Color.RED == bitmap.getPixel(coord[0], coord[1]) &&
Color.RED ==
@@ -1279,55 +1026,40 @@
coord[1] + SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT / 2
)
}
- }
+ )
}
@Test
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
fun testTransactionSetBufferTransform_identity() {
- val listener = TransactionOnCompleteListener()
- val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
- .moveToState(
- Lifecycle.State.CREATED
- ).onActivity {
- val callback = object : SurfaceHolderCallback() {
- override fun surfaceCreated(sh: SurfaceHolder) {
- val scCompat = SurfaceControlWrapper
- .Builder()
- .setParent(it.getSurfaceView().holder.surface)
- .setDebugName("SurfaceControlCompatTest")
- .build()
+ verifySurfaceControlWrapperTest(
+ { surfaceView ->
+ val scCompat = SurfaceControlWrapper
+ .Builder()
+ .setParent(surfaceView.holder.surface)
+ .setDebugName("SurfaceControlCompatTest")
+ .build()
- // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
- val buffer = SurfaceControlUtils.getQuadrantBuffer(
- SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
- SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
- Color.BLUE,
- Color.BLACK,
- Color.BLACK,
- Color.BLACK
- )
+ // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+ val buffer = SurfaceControlUtils.getQuadrantBuffer(
+ SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+ SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+ Color.BLUE,
+ Color.BLACK,
+ Color.BLACK,
+ Color.BLACK
+ )
- SurfaceControlWrapper.Transaction()
- .addTransactionCompletedListener(listener)
- .setBuffer(scCompat, buffer)
- .setVisibility(scCompat, true)
- .setBufferTransform(
- scCompat,
- SurfaceControlCompat.BUFFER_TRANSFORM_IDENTITY
- )
- .commit()
- }
- }
-
- it.addSurface(it.mSurfaceView, callback)
- }
-
- scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
- assert(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
- SurfaceControlUtils.validateOutput { bitmap ->
- val coord = intArrayOf(0, 0)
- it.mSurfaceView.getLocationOnScreen(coord)
+ SurfaceControlWrapper.Transaction()
+ .setBuffer(scCompat, buffer)
+ .setVisibility(scCompat, true)
+ .setBufferTransform(
+ scCompat,
+ SurfaceControlCompat.BUFFER_TRANSFORM_IDENTITY
+ )
+ },
+ { bitmap, rect ->
+ val coord = intArrayOf(rect.left, rect.top)
// Check outer bounds of square to ensure its scaled correctly
Color.RED == bitmap.getPixel(coord[0], coord[1]) &&
@@ -1342,59 +1074,44 @@
coord[1] + SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT / 2
)
}
- }
+ )
}
@Test
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
fun testTransactionSetGeometry_identity() {
- val listener = TransactionOnCompleteListener()
- val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
- .moveToState(
- Lifecycle.State.CREATED
- ).onActivity {
- val callback = object : SurfaceHolderCallback() {
- override fun surfaceCreated(sh: SurfaceHolder) {
- val scCompat = SurfaceControlWrapper
- .Builder()
- .setParent(it.getSurfaceView().holder.surface)
- .setDebugName("SurfaceControlCompatTest")
- .build()
+ verifySurfaceControlWrapperTest(
+ { surfaceView ->
+ val scCompat = SurfaceControlWrapper
+ .Builder()
+ .setParent(surfaceView.holder.surface)
+ .setDebugName("SurfaceControlCompatTest")
+ .build()
- // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
- val buffer = SurfaceControlUtils.getQuadrantBuffer(
- SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
- SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
- Color.BLUE,
- Color.BLACK,
- Color.BLACK,
- Color.BLACK
- )
+ // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+ val buffer = SurfaceControlUtils.getQuadrantBuffer(
+ SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+ SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+ Color.BLUE,
+ Color.BLACK,
+ Color.BLACK,
+ Color.BLACK
+ )
- SurfaceControlWrapper.Transaction()
- .addTransactionCompletedListener(listener)
- .setBuffer(scCompat, buffer)
- .setVisibility(scCompat, true)
- .setGeometry(
- scCompat,
- SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
- SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
- SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
- SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
- SurfaceControlCompat.BUFFER_TRANSFORM_IDENTITY
- )
- .commit()
- }
- }
-
- it.addSurface(it.mSurfaceView, callback)
- }
-
- scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
- assert(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
- SurfaceControlUtils.validateOutput { bitmap ->
- val coord = intArrayOf(0, 0)
- it.mSurfaceView.getLocationOnScreen(coord)
+ SurfaceControlWrapper.Transaction()
+ .setBuffer(scCompat, buffer)
+ .setVisibility(scCompat, true)
+ .setGeometry(
+ scCompat,
+ SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+ SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+ SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+ SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+ SurfaceControlCompat.BUFFER_TRANSFORM_IDENTITY
+ )
+ },
+ { bitmap, rect ->
+ val coord = intArrayOf(rect.left, rect.top)
// Check outer bounds of square to ensure its scaled correctly
Color.RED == bitmap.getPixel(coord[0], coord[1]) &&
@@ -1409,55 +1126,40 @@
coord[1] + SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT / 2
)
}
- }
+ )
}
@Test
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
fun testTransactionSetBufferTransform_singleTransform() {
- val listener = TransactionOnCompleteListener()
- val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
- .moveToState(
- Lifecycle.State.CREATED
- ).onActivity {
- val callback = object : SurfaceHolderCallback() {
- override fun surfaceCreated(sh: SurfaceHolder) {
- val scCompat = SurfaceControlWrapper
- .Builder()
- .setParent(it.getSurfaceView().holder.surface)
- .setDebugName("SurfaceControlCompatTest")
- .build()
+ verifySurfaceControlWrapperTest(
+ { surfaceView ->
+ val scCompat = SurfaceControlWrapper
+ .Builder()
+ .setParent(surfaceView.holder.surface)
+ .setDebugName("SurfaceControlCompatTest")
+ .build()
- // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
- val buffer = SurfaceControlUtils.getQuadrantBuffer(
- SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
- SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
- Color.BLUE,
- Color.BLACK,
- Color.BLACK,
- Color.BLACK
- )
+ // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+ val buffer = SurfaceControlUtils.getQuadrantBuffer(
+ SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+ SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+ Color.BLUE,
+ Color.BLACK,
+ Color.BLACK,
+ Color.BLACK
+ )
- SurfaceControlWrapper.Transaction()
- .addTransactionCompletedListener(listener)
- .setBuffer(scCompat, buffer)
- .setVisibility(scCompat, true)
- .setBufferTransform(
- scCompat,
- SurfaceControlCompat.BUFFER_TRANSFORM_MIRROR_HORIZONTAL
- )
- .commit()
- }
- }
-
- it.addSurface(it.mSurfaceView, callback)
- }
-
- scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
- assert(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
- SurfaceControlUtils.validateOutput { bitmap ->
- val coord = intArrayOf(0, 0)
- it.mSurfaceView.getLocationOnScreen(coord)
+ SurfaceControlWrapper.Transaction()
+ .setBuffer(scCompat, buffer)
+ .setVisibility(scCompat, true)
+ .setBufferTransform(
+ scCompat,
+ SurfaceControlCompat.BUFFER_TRANSFORM_MIRROR_HORIZONTAL
+ )
+ },
+ { bitmap, rect ->
+ val coord = intArrayOf(rect.left, rect.top)
// Ensure it actually rotated by checking its outer bounds are black
Color.BLACK ==
bitmap.getPixel(
@@ -1475,59 +1177,44 @@
coord[1] + SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT / 2 - 1
)
}
- }
+ )
}
@Test
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
fun testTransactionSetGeometry_singleTransform() {
- val listener = TransactionOnCompleteListener()
- val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
- .moveToState(
- Lifecycle.State.CREATED
- ).onActivity {
- val callback = object : SurfaceHolderCallback() {
- override fun surfaceCreated(sh: SurfaceHolder) {
- val scCompat = SurfaceControlWrapper
- .Builder()
- .setParent(it.getSurfaceView().holder.surface)
- .setDebugName("SurfaceControlCompatTest")
- .build()
+ verifySurfaceControlWrapperTest(
+ { surfaceView ->
+ val scCompat = SurfaceControlWrapper
+ .Builder()
+ .setParent(surfaceView.holder.surface)
+ .setDebugName("SurfaceControlCompatTest")
+ .build()
- // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
- val buffer = SurfaceControlUtils.getQuadrantBuffer(
- SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
- SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
- Color.BLUE,
- Color.BLACK,
- Color.BLACK,
- Color.BLACK
- )
+ // Buffer colorspace is RGBA, so Color.BLUE will be visually Red
+ val buffer = SurfaceControlUtils.getQuadrantBuffer(
+ SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+ SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+ Color.BLUE,
+ Color.BLACK,
+ Color.BLACK,
+ Color.BLACK
+ )
- SurfaceControlWrapper.Transaction()
- .addTransactionCompletedListener(listener)
- .setBuffer(scCompat, buffer)
- .setVisibility(scCompat, true)
- .setGeometry(
- scCompat,
- SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
- SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
- SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
- SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
- SurfaceControlCompat.BUFFER_TRANSFORM_MIRROR_HORIZONTAL
- )
- .commit()
- }
- }
-
- it.addSurface(it.mSurfaceView, callback)
- }
-
- scenario.moveToState(Lifecycle.State.RESUMED).onActivity {
- assert(listener.mLatch.await(3000, TimeUnit.MILLISECONDS))
- SurfaceControlUtils.validateOutput { bitmap ->
- val coord = intArrayOf(0, 0)
- it.mSurfaceView.getLocationOnScreen(coord)
+ SurfaceControlWrapper.Transaction()
+ .setBuffer(scCompat, buffer)
+ .setVisibility(scCompat, true)
+ .setGeometry(
+ scCompat,
+ SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+ SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+ SurfaceControlWrapperTestActivity.DEFAULT_WIDTH,
+ SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT,
+ SurfaceControlCompat.BUFFER_TRANSFORM_MIRROR_HORIZONTAL
+ )
+ },
+ { bitmap, rect ->
+ val coord = intArrayOf(rect.left, rect.top)
// Ensure it actually rotated by checking its outer bounds are black
Color.BLACK ==
bitmap.getPixel(
@@ -1545,7 +1232,33 @@
coord[1] + SurfaceControlWrapperTestActivity.DEFAULT_HEIGHT / 2 - 1
)
}
- }
+ )
+ }
+
+ @RequiresApi(Build.VERSION_CODES.Q)
+ private fun verifySurfaceControlWrapperTest(
+ createTransaction: (SurfaceView) -> SurfaceControlWrapper.Transaction,
+ verifyOutput: (Bitmap, Rect) -> Boolean
+ ) {
+ SurfaceControlUtils.surfaceControlTestHelper(
+ { surfaceView, latch ->
+ val transaction = createTransaction(surfaceView)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ transaction.addTransactionCommittedListener(
+ executor!!,
+ object : SurfaceControlCompat.TransactionCommittedListener {
+ override fun onTransactionCommitted() {
+ latch.countDown()
+ }
+ }
+ )
+ } else {
+ latch.countDown()
+ }
+ transaction.commit()
+ },
+ verifyOutput
+ )
}
fun Color.compositeOver(background: Color): Color {
diff --git a/graphics/graphics-shapes/src/androidInstrumentedTest/kotlin/androidx/graphics/shapes/RoundedPolygonTest.kt b/graphics/graphics-shapes/src/androidInstrumentedTest/kotlin/androidx/graphics/shapes/RoundedPolygonTest.kt
index 2ac9a18..79201f0 100644
--- a/graphics/graphics-shapes/src/androidInstrumentedTest/kotlin/androidx/graphics/shapes/RoundedPolygonTest.kt
+++ b/graphics/graphics-shapes/src/androidInstrumentedTest/kotlin/androidx/graphics/shapes/RoundedPolygonTest.kt
@@ -200,6 +200,46 @@
}
}
+ @Test
+ fun creatingFullSizeTest() {
+ val radius = 400f
+ val innerRadiusFactor = 0.35f
+ val innerRadius = radius * innerRadiusFactor
+ val roundingFactor = 0.32f
+
+ val fullSizeShape = RoundedPolygon.star(
+ numVerticesPerRadius = 4,
+ radius,
+ innerRadius,
+ rounding = CornerRounding(radius * roundingFactor),
+ innerRounding = CornerRounding(radius * roundingFactor),
+ centerX = radius,
+ centerY = radius
+ ).transformed { x, y -> TransformResult((x - radius) / radius, (y - radius) / radius) }
+
+ val canonicalShape = RoundedPolygon.star(
+ numVerticesPerRadius = 4,
+ 1f,
+ innerRadiusFactor,
+ rounding = CornerRounding(roundingFactor),
+ innerRounding = CornerRounding(roundingFactor)
+ )
+
+ val cubics = canonicalShape.cubics
+ val cubics1 = fullSizeShape.cubics
+ assertEquals(cubics.size, cubics1.size)
+ cubics.zip(cubics1).forEach { (cubic, cubic1) ->
+ assertEqualish(cubic.anchor0X, cubic1.anchor0X)
+ assertEqualish(cubic.anchor0Y, cubic1.anchor0Y)
+ assertEqualish(cubic.anchor1X, cubic1.anchor1X)
+ assertEqualish(cubic.anchor1Y, cubic1.anchor1Y)
+ assertEqualish(cubic.control0X, cubic1.control0X)
+ assertEqualish(cubic.control0Y, cubic1.control0Y)
+ assertEqualish(cubic.control1X, cubic1.control1X)
+ assertEqualish(cubic.control1Y, cubic1.control1Y)
+ }
+ }
+
private fun doUnevenSmoothTest(
// Corner rounding parameter for vertex 0 (top left)
rounding0: CornerRounding,
diff --git a/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/RoundedPolygon.kt b/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/RoundedPolygon.kt
index 375ab64..99b577e 100644
--- a/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/RoundedPolygon.kt
+++ b/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/RoundedPolygon.kt
@@ -572,8 +572,12 @@
private fun lineIntersection(p0: Point, d0: Point, p1: Point, d1: Point): Point? {
val rotatedD1 = d1.rotate90()
val den = d0.dotProduct(rotatedD1)
- if (abs(den) < AngleEpsilon) return null
- val k = (p1 - p0).dotProduct(rotatedD1) / den
+ if (abs(den) < DistanceEpsilon) return null
+ val num = (p1 - p0).dotProduct(rotatedD1)
+ // Also check the relative value. This is equivalent to abs(den/num) < DistanceEpsilon,
+ // but avoid doing a division
+ if (abs(den) < DistanceEpsilon * abs(num)) return null
+ val k = num / den
return p0 + d0 * k
}
}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/HealthEvent.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/HealthEvent.kt
index 5c2b868..7964629 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/data/HealthEvent.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/HealthEvent.kt
@@ -55,11 +55,10 @@
override fun toString(): String = name
- internal fun toProto(): DataProto.HealthEvent.HealthEventType =
- DataProto.HealthEvent.HealthEventType.forNumber(id)
- ?: DataProto.HealthEvent.HealthEventType.HEALTH_EVENT_TYPE_UNKNOWN
+ internal fun toProto(): Int = id
public companion object {
+ private const val CUSTOM_TYPE_NAME_PREFIX = "health_services.device_private."
/**
* The Health Event is unknown, or is represented by a value too new for this library
* version to parse.
@@ -76,20 +75,30 @@
internal fun fromProto(proto: DataProto.HealthEvent.HealthEventType): Type =
VALUES.firstOrNull { it.id == proto.number } ?: UNKNOWN
+
+ internal fun fromProto(typeId: Int): Type {
+ if (isInCustomHealthEventRange(typeId)) {
+ return Type(typeId, CUSTOM_TYPE_NAME_PREFIX + typeId)
+ }
+
+ return VALUES.firstOrNull { it.id == typeId } ?: UNKNOWN
+ }
+
+ private fun isInCustomHealthEventRange(id: Int) = id in 0x40000..0x4ffff
}
}
internal constructor(
proto: DataProto.HealthEvent
) : this(
- Type.fromProto(proto.type),
+ Type.fromProto(proto.healthEventTypeId),
Instant.ofEpochMilli(proto.eventTimeEpochMs),
fromHealthEventProto(proto)
)
internal val proto: DataProto.HealthEvent =
DataProto.HealthEvent.newBuilder()
- .setType(type.toProto())
+ .setHealthEventTypeId(type.toProto())
.setEventTimeEpochMs(eventTime.toEpochMilli())
.addAllMetrics(toEventProtoList(metrics))
.build()
diff --git a/health/health-services-client/src/main/proto/data.proto b/health/health-services-client/src/main/proto/data.proto
index 2a2e5ee..a25ee7c5 100644
--- a/health/health-services-client/src/main/proto/data.proto
+++ b/health/health-services-client/src/main/proto/data.proto
@@ -499,7 +499,7 @@
reserved 3 to max; // Next ID
}
- optional HealthEventType type = 1;
+ optional int32 health_event_type_id = 1;
optional int64 event_time_epoch_ms = 2;
repeated MetricsEntry metrics = 3;
reserved 4 to max; // Next ID
@@ -561,7 +561,7 @@
repeated DataType supported_data_types_passive_monitoring = 1;
repeated DataType supported_data_types_passive_goals = 2;
repeated int32 supported_hr_sampling_intervals_seconds = 3 [packed = true];
- repeated HealthEvent.HealthEventType supported_health_event_types = 4
+ repeated int32 supported_health_event_types = 4
[packed = true];
repeated UserActivityState supported_user_activity_states = 5 [packed = true];
reserved 6 to max; // Next ID
@@ -571,7 +571,7 @@
repeated DataType data_types = 1;
optional bool include_user_activity_state = 2;
repeated PassiveGoal passive_goals = 3;
- repeated HealthEvent.HealthEventType health_event_types = 4;
+ repeated int32 health_event_types = 4;
reserved 5 to max; // Next ID
}
diff --git a/health/health-services-client/src/main/proto/requests.proto b/health/health-services-client/src/main/proto/requests.proto
index 0a6d865..b548312 100644
--- a/health/health-services-client/src/main/proto/requests.proto
+++ b/health/health-services-client/src/main/proto/requests.proto
@@ -63,7 +63,7 @@
message HealthEventsRegistrationRequest {
optional string package_name = 1;
optional string receiver_class_name = 2;
- repeated HealthEvent.HealthEventType event_types = 3;
+ repeated int32 event_types = 3;
reserved 4 to max; // Next ID
}
diff --git a/health/health-services-client/src/test/java/androidx/health/services/client/impl/ServiceBackedPassiveMonitoringClientTest.kt b/health/health-services-client/src/test/java/androidx/health/services/client/impl/ServiceBackedPassiveMonitoringClientTest.kt
index cd408a3..c56d440e 100644
--- a/health/health-services-client/src/test/java/androidx/health/services/client/impl/ServiceBackedPassiveMonitoringClientTest.kt
+++ b/health/health-services-client/src/test/java/androidx/health/services/client/impl/ServiceBackedPassiveMonitoringClientTest.kt
@@ -48,7 +48,6 @@
import androidx.health.services.client.impl.response.PassiveMonitoringUpdateResponse
import androidx.health.services.client.proto.DataProto
import androidx.health.services.client.proto.DataProto.ComparisonType.COMPARISON_TYPE_GREATER_THAN
-import androidx.health.services.client.proto.DataProto.HealthEvent.HealthEventType.HEALTH_EVENT_TYPE_FALL_DETECTED
import androidx.health.services.client.proto.DataProto.PassiveGoal.TriggerFrequency.TRIGGER_FREQUENCY_ONCE
import androidx.health.services.client.proto.DataProto.UserActivityState.USER_ACTIVITY_STATE_PASSIVE
import androidx.health.services.client.proto.ResponsesProto
@@ -290,7 +289,7 @@
HealthEventResponse(
ResponsesProto.HealthEventResponse.newBuilder().setHealthEvent(
DataProto.HealthEvent.newBuilder()
- .setType(HEALTH_EVENT_TYPE_FALL_DETECTED)
+ .setHealthEventTypeId(FALL_DETECTED.id)
.build()
).build()
)
diff --git a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/MotionEventPredictor.java b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/MotionEventPredictor.java
index 3403d9bf..36ca19e 100644
--- a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/MotionEventPredictor.java
+++ b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/MotionEventPredictor.java
@@ -72,11 +72,14 @@
@NonNull
static MotionEventPredictor newInstance(@NonNull View view) {
Context context = view.getContext();
+ Configuration configuration = Configuration.getInstance();
if (Build.VERSION.SDK_INT >= 34
- && Configuration.getInstance().preferSystemPrediction()) {
- return SystemMotionEventPredictor.newInstance(context);
+ && configuration.preferSystemPrediction()) {
+ return SystemMotionEventPredictor.newInstance(
+ context,
+ configuration.predictionStrategy());
} else {
- return new KalmanMotionEventPredictor(context);
+ return new KalmanMotionEventPredictor(context, configuration.predictionStrategy());
}
}
}
diff --git a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/common/Configuration.java b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/common/Configuration.java
index c67cfcf..46a1247 100644
--- a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/common/Configuration.java
+++ b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/common/Configuration.java
@@ -25,11 +25,17 @@
*/
@RestrictTo(LIBRARY)
public class Configuration {
+ public static final int STRATEGY_BALANCED = 0;
+ public static final int STRATEGY_SAFE = 1;
+ public static final int STRATEGY_AGGRESSIVE = 2;
+
private static volatile Configuration sInstance = null;
private static final Object sLock = new Object();
- private boolean mPreferSystemPrediction;
- private int mPredictionOffset;
+ private final boolean mPredictLift;
+ private final boolean mPreferSystemPrediction;
+ private final int mPredictionOffset;
+ private final int mPredictionStrategy;
/**
* Returns the configuration for prediction in this system.
@@ -52,6 +58,8 @@
mPreferSystemPrediction = SystemProperty
.getBoolean("debug.input.androidx_prefer_system_prediction");
mPredictionOffset = SystemProperty.getInt("debug.input.androidx_prediction_offset");
+ mPredictLift = SystemProperty.getBoolean("debug.input.androidx_predict_lift");
+ mPredictionStrategy = SystemProperty.getInt("debug.input.androidx_prediction_strategy");
}
/**
@@ -71,4 +79,22 @@
public int predictionOffset() {
return mPredictionOffset;
}
+
+ /**
+ * Returns whether or not the pressure should be used to adjust the distance of the prediction
+ *
+ * @return true if the pressure should be used to determine the prediction length
+ */
+ public boolean predictLift() {
+ return mPredictLift;
+ }
+
+ /**
+ * Returns the default prediction strategy
+ *
+ * @return the strategy to use as default; 0 is balanced
+ */
+ public int predictionStrategy() {
+ return mPredictionStrategy;
+ }
}
diff --git a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/KalmanMotionEventPredictor.java b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/KalmanMotionEventPredictor.java
index 64adc26..12eb416 100644
--- a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/KalmanMotionEventPredictor.java
+++ b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/KalmanMotionEventPredictor.java
@@ -31,11 +31,12 @@
*/
@RestrictTo(LIBRARY)
public class KalmanMotionEventPredictor implements MotionEventPredictor {
- private final MultiPointerPredictor mMultiPointerPredictor = new MultiPointerPredictor();
+ private final MultiPointerPredictor mMultiPointerPredictor;
private final PredictionEstimator mPredictionEstimator;
- public KalmanMotionEventPredictor(@NonNull Context context) {
+ public KalmanMotionEventPredictor(@NonNull Context context, int strategy) {
mPredictionEstimator = new PredictionEstimator(context);
+ mMultiPointerPredictor = new MultiPointerPredictor(strategy);
}
@Override
diff --git a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/MultiPointerPredictor.java b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/MultiPointerPredictor.java
index f9fdcfb..ae35971 100644
--- a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/MultiPointerPredictor.java
+++ b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/MultiPointerPredictor.java
@@ -37,8 +37,11 @@
private final SparseArray<SinglePointerPredictor> mPredictorMap = new SparseArray<>();
private int mReportRateMs = 0;
+ private final int mStrategy;
- public MultiPointerPredictor() {}
+ public MultiPointerPredictor(int strategy) {
+ mStrategy = strategy;
+ }
@Override
public void setReportRate(int reportRateMs) {
@@ -60,6 +63,7 @@
int pointerId = event.getPointerId(actionIndex);
if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_DOWN) {
SinglePointerPredictor predictor = new SinglePointerPredictor(
+ mStrategy,
pointerId,
event.getToolType(actionIndex)
);
diff --git a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/SinglePointerPredictor.java b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/SinglePointerPredictor.java
index fd64a94..5ad7111 100644
--- a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/SinglePointerPredictor.java
+++ b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/SinglePointerPredictor.java
@@ -24,6 +24,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
+import androidx.input.motionprediction.common.Configuration;
import androidx.input.motionprediction.kalman.matrix.DVector2;
import java.util.LinkedList;
@@ -92,6 +93,9 @@
private double mLastOrientation = 0;
private double mLastTilt = 0;
+ private final boolean mPredictLift;
+ private final int mStrategy;
+
/**
* Kalman based predictor, predicting the location of the pen `predictionTarget`
* milliseconds into the future.
@@ -100,13 +104,15 @@
* achieving close-to-zero latency, prediction errors can be more visible and the target should
* be reduced to 20ms.
*/
- public SinglePointerPredictor(int pointerId, int toolType) {
+ public SinglePointerPredictor(int strategy, int pointerId, int toolType) {
+ mStrategy = strategy;
mKalman.reset();
mLastSeenEventTime = 0;
mLastPredictEventTime = 0;
mDownEventTime = 0;
mPointerId = pointerId;
mToolType = toolType;
+ mPredictLift = Configuration.getInstance().predictLift();
}
private void update(float x, float y, float pressure, float orientation,
@@ -228,6 +234,11 @@
double jankFactor = 1.0 - normalizeRange(jankAbs, lowJank, highJank);
double confidenceFactor = speedFactor * jankFactor;
+ if (mStrategy == Configuration.STRATEGY_AGGRESSIVE) {
+ // We are very confident
+ confidenceFactor = 1;
+ }
+
MotionEvent predictedEvent = null;
final MotionEvent.PointerProperties[] pointerProperties =
new MotionEvent.PointerProperties[1];
@@ -256,6 +267,11 @@
}
}
+ if (mStrategy == Configuration.STRATEGY_SAFE) {
+ // Just a single prediction step is very accurate
+ predictionTargetInSamples = Math.max(predictionTargetInSamples, 1);
+ }
+
long predictedEventTime = mLastSeenEventTime;
int i = 0;
for (; i < predictionTargetInSamples; i++) {
@@ -277,7 +293,8 @@
long nextPredictedEventTime = predictedEventTime + Math.round(mReportRateMs);
// Abort prediction if the pen is to be lifted.
- if (mPressure < 0.1
+ if (mPredictLift
+ && mPressure < 0.1
&& nextPredictedEventTime > mLastPredictEventTime) {
//TODO: Should we generate ACTION_UP MotionEvent instead of ACTION_MOVE?
break;
diff --git a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/system/SystemMotionEventPredictor.java b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/system/SystemMotionEventPredictor.java
index a1a6b20..91ceb59 100644
--- a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/system/SystemMotionEventPredictor.java
+++ b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/system/SystemMotionEventPredictor.java
@@ -48,10 +48,12 @@
// at some point, the source is checked first, which means that it will never be read before
// it has been written with a valid id.
private int mLastRecordedDeviceId = -2;
+ private final int mStrategy;
- public SystemMotionEventPredictor(@NonNull Context context) {
+ public SystemMotionEventPredictor(@NonNull Context context, int strategy) {
mPredictionEstimator = new PredictionEstimator(context);
mSystemPredictor = new MotionPredictor(context);
+ mStrategy = strategy;
}
@Override
@@ -86,7 +88,7 @@
private MultiPointerPredictor getKalmanPredictor() {
if (mKalmanPredictor == null) {
- mKalmanPredictor = new MultiPointerPredictor();
+ mKalmanPredictor = new MultiPointerPredictor(mStrategy);
}
return mKalmanPredictor;
}
@@ -95,11 +97,12 @@
* Builds a new instance of the system motion event prediction
*
* @param context the application context
+ * @param strategy the strategy to use
* @return the new instance
*/
@NonNull
- public static SystemMotionEventPredictor newInstance(@NonNull Context context) {
- return new SystemMotionEventPredictor(context);
+ public static SystemMotionEventPredictor newInstance(@NonNull Context context, int strategy) {
+ return new SystemMotionEventPredictor(context, strategy);
}
}
diff --git a/input/input-motionprediction/src/test/kotlin/androidx/input/motionprediction/kalman/MultiPointerPredictorTest.kt b/input/input-motionprediction/src/test/kotlin/androidx/input/motionprediction/kalman/MultiPointerPredictorTest.kt
index 9987dc8..cec9bab 100644
--- a/input/input-motionprediction/src/test/kotlin/androidx/input/motionprediction/kalman/MultiPointerPredictorTest.kt
+++ b/input/input-motionprediction/src/test/kotlin/androidx/input/motionprediction/kalman/MultiPointerPredictorTest.kt
@@ -18,6 +18,7 @@
import android.view.MotionEvent
import androidx.input.motionprediction.MotionEventGenerator
+import androidx.input.motionprediction.common.Configuration
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import com.google.common.truth.Truth.assertThat
@@ -31,7 +32,7 @@
// Ensures that the historical time is properly populated (b/302300930)
@Test
fun historicalTime() {
- val predictor = MultiPointerPredictor()
+ val predictor = MultiPointerPredictor(Configuration.STRATEGY_BALANCED)
val generator = MotionEventGenerator(
{ delta: Long -> delta.toFloat() },
{ delta: Long -> delta.toFloat() },
@@ -56,7 +57,7 @@
// Ensures that the down time is properly populated
@Test
fun downTime() {
- val predictor = MultiPointerPredictor()
+ val predictor = MultiPointerPredictor(Configuration.STRATEGY_BALANCED)
val generator = MotionEventGenerator(
{ delta: Long -> delta.toFloat() },
{ delta: Long -> delta.toFloat() },
diff --git a/input/input-motionprediction/src/test/kotlin/androidx/input/motionprediction/kalman/SinglePointerPredictorTest.kt b/input/input-motionprediction/src/test/kotlin/androidx/input/motionprediction/kalman/SinglePointerPredictorTest.kt
index 2f66349..4200608 100644
--- a/input/input-motionprediction/src/test/kotlin/androidx/input/motionprediction/kalman/SinglePointerPredictorTest.kt
+++ b/input/input-motionprediction/src/test/kotlin/androidx/input/motionprediction/kalman/SinglePointerPredictorTest.kt
@@ -18,6 +18,7 @@
import android.view.MotionEvent
import androidx.input.motionprediction.MotionEventGenerator
+import androidx.input.motionprediction.common.Configuration
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import com.google.common.truth.Truth.assertThat
@@ -92,23 +93,22 @@
}
@Test
- fun predictionNeverGoesBackwardsEvenWhenLifting() {
+ fun liftingDoesNotAffectPredictionDistance() {
val predictor = constructPredictor()
val coordGenerator = { delta: Long -> delta.toFloat() }
// Pressure will be 1 at the beginning and trend to zero while never getting there
val pressureGenerator = fun(delta: Long): Float {
- if (delta > 500) {
- return ((700 - delta) / 500).toFloat()
- }
- return 1f
+ if (delta > 500) {
+ return ((700 - delta) / 500).toFloat()
}
+ return 1f
+ }
val motionGenerator =
MotionEventGenerator(coordGenerator, coordGenerator, pressureGenerator)
var lastPredictedTime = 0L
var lastPredictedEvent: MotionEvent? = null
var predicted: MotionEvent?
- var iteration = 0
- do {
+ for (i in 1..MAX_ITERATIONS) {
predictor.onTouchEvent(motionGenerator.next())
predicted = predictor.predict(motionGenerator.getRateMs().toInt() * 10)
if (predicted != null) {
@@ -118,15 +118,19 @@
assertThat(lastPredictedEvent.getHistorySize()).isEqualTo(0);
}
lastPredictedEvent = predicted
- iteration++
- } while (predicted != null || iteration < INITIAL_FEED)
+ if (i > INITIAL_FEED) {
+ assertThat(predicted).isNotNull()
+ }
+ }
}
}
private fun constructPredictor(): SinglePointerPredictor = SinglePointerPredictor(
+ Configuration.STRATEGY_BALANCED,
0,
MotionEvent.TOOL_TYPE_STYLUS
)
private const val INITIAL_FEED = 20
+private const val MAX_ITERATIONS = 10000
private const val PREDICT_LENGTH = 10
diff --git a/libraryversions.toml b/libraryversions.toml
index 872c259..8acc8f4 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -70,7 +70,7 @@
GRAPHICS_CORE = "1.0.0-beta01"
GRAPHICS_FILTERS = "1.0.0-alpha01"
GRAPHICS_PATH = "1.0.0-beta01"
-GRAPHICS_SHAPES = "1.0.0-alpha03"
+GRAPHICS_SHAPES = "1.0.0-alpha04"
GRIDLAYOUT = "1.1.0-beta02"
HEALTH_CONNECT = "1.1.0-alpha07"
HEALTH_SERVICES_CLIENT = "1.1.0-alpha02"
@@ -156,9 +156,9 @@
WEAR_INPUT_TESTING = "1.2.0-alpha03"
WEAR_ONGOING = "1.1.0-alpha02"
WEAR_PHONE_INTERACTIONS = "1.1.0-alpha04"
-WEAR_PROTOLAYOUT = "1.1.0-alpha03"
+WEAR_PROTOLAYOUT = "1.1.0-alpha04"
WEAR_REMOTE_INTERACTIONS = "1.1.0-alpha01"
-WEAR_TILES = "1.3.0-alpha03"
+WEAR_TILES = "1.3.0-alpha04"
WEAR_TOOLING_PREVIEW = "1.0.0-rc01"
WEAR_WATCHFACE = "1.3.0-alpha01"
WEBKIT = "1.10.0-beta01"
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/lint-baseline.xml b/privacysandbox/sdkruntime/sdkruntime-client/lint-baseline.xml
index 24c78da..1d7dbea 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/lint-baseline.xml
+++ b/privacysandbox/sdkruntime/sdkruntime-client/lint-baseline.xml
@@ -19,49 +19,4 @@
file="src/androidTest/java/androidx/privacysandbox/sdkruntime/client/loader/storage/CachedLocalSdkStorageTest.kt"/>
</issue>
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 34; however, the containing class androidx.privacysandbox.sdkruntime.client.SdkSandboxManagerCompat.ApiAdServicesV4Impl is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" sdkSandboxManager.unloadSdk(sdkName)"
- errorLine2=" ~~~~~~~~~">
- <location
- file="src/main/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompat.kt"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 34; however, the containing class androidx.privacysandbox.sdkruntime.client.SdkSandboxManagerCompat.ApiAdServicesV4Impl is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" sdkSandboxManager.addSdkSandboxProcessDeathCallback(callbackExecutor, delegate)"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompat.kt"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 34; however, the containing class androidx.privacysandbox.sdkruntime.client.SdkSandboxManagerCompat.ApiAdServicesV4Impl is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" sdkSandboxManager.removeSdkSandboxProcessDeathCallback(delegate)"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompat.kt"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 34; however, the containing class androidx.privacysandbox.sdkruntime.client.SdkSandboxManagerCompat.ApiAdServicesV4Impl is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" sdkSandboxManager.loadSdk("
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompat.kt"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 34; however, the containing class androidx.privacysandbox.sdkruntime.client.SdkSandboxManagerCompat.ApiAdServicesV5Impl is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" .sandboxedSdks"
- errorLine2=" ~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompat.kt"/>
- </issue>
-
</issues>
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompat.kt b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompat.kt
index 59ee471..e04bd1ff 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompat.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-client/src/main/java/androidx/privacysandbox/sdkruntime/client/SdkSandboxManagerCompat.kt
@@ -294,7 +294,6 @@
}
@RequiresApi(34)
- @SuppressLint("NewApi", "ClassVerificationFailure") // until updating checks to requires api 34
private open class Api34Impl(context: Context) : PlatformApi {
protected val sdkSandboxManager = context.getSystemService(
SdkSandboxManager::class.java
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/lint-baseline.xml b/privacysandbox/sdkruntime/sdkruntime-core/lint-baseline.xml
deleted file mode 100644
index 3f6432a..0000000
--- a/privacysandbox/sdkruntime/sdkruntime-core/lint-baseline.xml
+++ /dev/null
@@ -1,85 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.2.0-alpha14" type="baseline" client="gradle" dependencies="false" name="AGP (8.2.0-alpha14)" variant="all" version="8.2.0-alpha14">
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 34; however, the containing class androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException.ApiAdServicesV4Impl is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" return LoadSdkException("
- errorLine2=" ^">
- <location
- file="src/main/java/androidx/privacysandbox/sdkruntime/core/LoadSdkCompatException.kt"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 34; however, the containing class androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException.ApiAdServicesV4Impl is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" toLoadSdkErrorCodeCompat(ex.loadSdkErrorCode),"
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/privacysandbox/sdkruntime/core/LoadSdkCompatException.kt"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 34; however, the containing class androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException.ApiAdServicesV4Impl is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" ex.extraInformation"
- errorLine2=" ~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/privacysandbox/sdkruntime/core/LoadSdkCompatException.kt"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 34; however, the containing class androidx.privacysandbox.sdkruntime.core.controller.impl.PlatformImpl is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" .sandboxedSdks"
- errorLine2=" ~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/PlatformImpl.kt"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 34; however, the containing class androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat.ApiAdServicesV4Impl is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" return sandboxedSdk.getInterface()"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkCompat.kt"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 34; however, the containing class androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat.ApiAdServicesV4Impl.Companion is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" return SandboxedSdk(sdkInterface)"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkCompat.kt"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 34; however, the containing class androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat.ApiAdServicesV5Impl is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" val sharedLibraryInfo = sandboxedSdk.sharedLibraryInfo"
- errorLine2=" ~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkCompat.kt"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 34; however, the containing class androidx.privacysandbox.sdkruntime.core.SandboxedSdkProviderAdapter is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=") : SandboxedSdkProvider() {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkProviderAdapter.kt"/>
- </issue>
-
- <issue
- id="ClassVerificationFailure"
- message="This call references a method added in API level 34; however, the containing class androidx.privacysandbox.sdkruntime.core.SandboxedSdkProviderAdapter is reachable from earlier API levels and will fail run-time class verification."
- errorLine1=" val currentContext = context!!"
- errorLine2=" ~~~~~~~">
- <location
- file="src/main/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkProviderAdapter.kt"/>
- </issue>
-
-</issues>
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/LoadSdkCompatExceptionTest.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/LoadSdkCompatExceptionTest.kt
index ca2302d..ea78cd7 100644
--- a/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/LoadSdkCompatExceptionTest.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/LoadSdkCompatExceptionTest.kt
@@ -16,32 +16,19 @@
package androidx.privacysandbox.sdkruntime.core
import android.app.sdksandbox.LoadSdkException
-import android.os.Build.VERSION_CODES.TIRAMISU
import android.os.Bundle
-import android.os.ext.SdkExtensions.AD_SERVICES
-import androidx.annotation.RequiresExtension
import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException.Companion.toLoadCompatSdkException
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SdkSuppress
import androidx.test.filters.SmallTest
import com.google.common.truth.Truth.assertThat
-import org.junit.Assume.assumeTrue
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
-// TODO(b/262577044) Remove RequiresExtension after extensions support in @SdkSuppress
-@RequiresExtension(extension = AD_SERVICES, version = 4)
-@SdkSuppress(minSdkVersion = TIRAMISU)
+@SdkSuppress(minSdkVersion = 34)
class LoadSdkCompatExceptionTest {
-
- @Before
- fun setUp() {
- assumeTrue("Requires Sandbox API available", isSandboxApiAvailable())
- }
-
@Test
fun toLoadSdkException_returnLoadSdkException() {
val loadSdkCompatException = LoadSdkCompatException(RuntimeException(), Bundle())
@@ -72,7 +59,4 @@
assertThat(loadCompatSdkException.loadSdkErrorCode)
.isEqualTo(loadSdkException.loadSdkErrorCode)
}
-
- private fun isSandboxApiAvailable() =
- AdServicesInfo.isAtLeastV4()
}
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkCompatTest.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkCompatTest.kt
index 7ff1ce7..f862dec 100644
--- a/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkCompatTest.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/androidTest/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkCompatTest.kt
@@ -18,16 +18,11 @@
import android.app.sdksandbox.SandboxedSdk
import android.content.pm.SharedLibraryInfo
import android.os.Binder
-import android.os.Build.VERSION_CODES.TIRAMISU
-import android.os.ext.SdkExtensions
import androidx.annotation.RequiresApi
-import androidx.annotation.RequiresExtension
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SdkSuppress
import androidx.test.filters.SmallTest
import com.google.common.truth.Truth.assertThat
-import org.junit.Assume.assumeFalse
-import org.junit.Assume.assumeTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito
@@ -50,12 +45,8 @@
}
@Test
- // TODO(b/262577044) Remove RequiresExtension after extensions support in @SdkSuppress
- @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
- @SdkSuppress(minSdkVersion = TIRAMISU)
+ @SdkSuppress(minSdkVersion = 34)
fun toSandboxedSdk_whenCreatedFromBinder_returnsSandboxedSdkWithSameBinder() {
- assumeTrue("Requires Sandbox API available", isSandboxApiAvailable())
-
val binder = Binder()
val toSandboxedSdkResult = SandboxedSdkCompat(binder).toSandboxedSdk()
@@ -65,12 +56,8 @@
}
@Test
- // TODO(b/262577044) Remove RequiresExtension after extensions support in @SdkSuppress
- @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
- @SdkSuppress(minSdkVersion = TIRAMISU)
+ @SdkSuppress(minSdkVersion = 34)
fun toSandboxedSdk_whenCreatedFromSandboxedSdk_returnsSameSandboxedSdk() {
- assumeTrue("Requires Sandbox API available", isSandboxApiAvailable())
-
val binder = Binder()
val sandboxedSdk = SandboxedSdk(binder)
@@ -98,38 +85,12 @@
}
@Test
- // TODO(b/262577044) Remove RequiresExtension after extensions support in @SdkSuppress
- @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
- @SdkSuppress(minSdkVersion = TIRAMISU)
- fun getSdkInfo_whenCreatedFromSandboxedSdkAndNoSharedLibraryInfo_returnsNull() {
- assumeTrue("Requires Sandbox API available", isSandboxApiAvailable())
- assumeFalse(
- "Requires SharedLibraryInfo not available",
- isSharedLibraryInfoAvailable()
- )
-
- val binder = Binder()
- val sandboxedSdk = SandboxedSdk(binder)
- val sandboxedSdkCompat = SandboxedSdkCompat(sandboxedSdk)
-
- assertThat(sandboxedSdkCompat.getSdkInfo()).isNull()
- }
-
- @Test
- // TODO(b/262577044) Remove RequiresExtension after extensions support in @SdkSuppress
- @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
- @SdkSuppress(minSdkVersion = TIRAMISU)
- fun getSdkInfo_whenCreatedFromSandboxedSdkAndSharedLibraryInfoAvailable_returnsSdkInfo() {
- assumeTrue("Requires Sandbox API available", isSandboxApiAvailable())
- assumeTrue(
- "Requires SharedLibraryInfo available",
- isSharedLibraryInfoAvailable()
- )
-
+ @SdkSuppress(minSdkVersion = 34)
+ fun getSdkInfo_whenCreatedFromSandboxedSdk_returnsSdkInfo() {
val sdkName = "sdkName"
val sdkVersion = 1L
- val sharedLibraryInfo = ApiAdServicesV5.mockSharedLibraryInfo(sdkName, sdkVersion)
- val sandboxedSdk = ApiAdServicesV5.mockSandboxedSdkWithSharedLibraryInfo(sharedLibraryInfo)
+ val sharedLibraryInfo = Api34Impl.mockSharedLibraryInfo(sdkName, sdkVersion)
+ val sandboxedSdk = Api34Impl.mockSandboxedSdkWithSharedLibraryInfo(sharedLibraryInfo)
val sandboxedSdkCompat = SandboxedSdkCompat(sandboxedSdk)
val sdkInfo = sandboxedSdkCompat.getSdkInfo()
@@ -142,7 +103,7 @@
)
}
- private object ApiAdServicesV5 {
+ private object Api34Impl {
@RequiresApi(28)
fun mockSharedLibraryInfo(
sdkName: String,
@@ -155,7 +116,7 @@
return sharedLibraryInfo
}
- @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
+ @RequiresApi(34)
fun mockSandboxedSdkWithSharedLibraryInfo(
sharedLibraryInfo: SharedLibraryInfo
): SandboxedSdk {
@@ -167,10 +128,4 @@
return sandboxedSdkSpy
}
}
-
- private fun isSandboxApiAvailable() =
- AdServicesInfo.isAtLeastV4()
-
- private fun isSharedLibraryInfoAvailable() =
- AdServicesInfo.isAtLeastV5()
}
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/AdServicesInfo.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/AdServicesInfo.kt
index f10c07b..b8104bd 100644
--- a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/AdServicesInfo.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/AdServicesInfo.kt
@@ -17,10 +17,7 @@
package androidx.privacysandbox.sdkruntime.core
import android.os.Build
-import android.os.ext.SdkExtensions
import androidx.annotation.ChecksSdkIntAtLeast
-import androidx.annotation.DoNotInline
-import androidx.annotation.RequiresApi
import androidx.annotation.RestrictTo
/**
@@ -30,29 +27,7 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
object AdServicesInfo {
-
- fun isAtLeastV4(): Boolean {
- return Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
- Extensions30Impl.isAtLeastV4()
- }
-
- fun isAtLeastV5(): Boolean {
- return Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
- Extensions30Impl.isAtLeastV5()
- }
-
@ChecksSdkIntAtLeast(codename = "UpsideDownCake")
fun isDeveloperPreview(): Boolean =
Build.VERSION.CODENAME.equals("UpsideDownCakePrivacySandbox")
-
- @RequiresApi(30)
- private object Extensions30Impl {
- @DoNotInline
- fun isAtLeastV4() =
- SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES) >= 4
-
- @DoNotInline
- fun isAtLeastV5() =
- SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES) >= 5
- }
}
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/LoadSdkCompatException.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/LoadSdkCompatException.kt
index 53f7424..05e901d 100644
--- a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/LoadSdkCompatException.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/LoadSdkCompatException.kt
@@ -17,10 +17,9 @@
import android.app.sdksandbox.LoadSdkException
import android.os.Bundle
-import android.os.ext.SdkExtensions.AD_SERVICES
import androidx.annotation.DoNotInline
import androidx.annotation.IntDef
-import androidx.annotation.RequiresExtension
+import androidx.annotation.RequiresApi
import androidx.annotation.RestrictTo
import androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP
@@ -110,14 +109,14 @@
*
* @return Platform exception.
*/
- @RequiresExtension(extension = AD_SERVICES, version = 4)
+ @RequiresApi(34)
@RestrictTo(LIBRARY_GROUP)
fun toLoadSdkException(): LoadSdkException {
- return ApiAdServicesV4Impl.toLoadSdkException(this)
+ return Api34Impl.toLoadSdkException(this)
}
- @RequiresExtension(extension = AD_SERVICES, version = 4)
- private object ApiAdServicesV4Impl {
+ @RequiresApi(34)
+ private object Api34Impl {
@DoNotInline
fun toLoadSdkException(ex: LoadSdkCompatException): LoadSdkException {
@@ -212,10 +211,10 @@
* @param ex Platform exception
* @return Compat exception.
*/
- @RequiresExtension(extension = AD_SERVICES, version = 4)
+ @RequiresApi(34)
@RestrictTo(LIBRARY_GROUP)
fun toLoadCompatSdkException(ex: LoadSdkException): LoadSdkCompatException {
- return ApiAdServicesV4Impl.toLoadCompatSdkException(ex)
+ return Api34Impl.toLoadCompatSdkException(ex)
}
}
}
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkCompat.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkCompat.kt
index 6a0d8015..fa98a22 100644
--- a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkCompat.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkCompat.kt
@@ -17,11 +17,9 @@
import android.app.sdksandbox.SandboxedSdk
import android.os.IBinder
-import android.os.ext.SdkExtensions.AD_SERVICES
import androidx.annotation.DoNotInline
import androidx.annotation.Keep
import androidx.annotation.RequiresApi
-import androidx.annotation.RequiresExtension
import androidx.annotation.RestrictTo
import androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP
@@ -73,10 +71,10 @@
*
* @param sandboxedSdk SandboxedSdk object. All calls will be delegated to that object.
*/
- @RequiresExtension(extension = AD_SERVICES, version = 4)
+ @RequiresApi(34)
@RestrictTo(LIBRARY_GROUP)
constructor(sandboxedSdk: SandboxedSdk) : this(
- SdkImplFactory.createSdkImpl(sandboxedSdk)
+ Api34Impl(sandboxedSdk)
)
/**
@@ -104,7 +102,7 @@
*
* @return Platform SandboxedSdk
*/
- @RequiresExtension(extension = AD_SERVICES, version = 4)
+ @RequiresApi(34)
@RestrictTo(LIBRARY_GROUP)
fun toSandboxedSdk() = sdkImpl.toSandboxedSdk()
@@ -113,13 +111,13 @@
fun getSdkInfo(): SandboxedSdkInfo?
- @RequiresExtension(extension = AD_SERVICES, version = 4)
+ @RequiresApi(34)
@DoNotInline
fun toSandboxedSdk(): SandboxedSdk
}
- @RequiresExtension(extension = AD_SERVICES, version = 4)
- private open class ApiAdServicesV4Impl(
+ @RequiresApi(34)
+ private open class Api34Impl(
protected val sandboxedSdk: SandboxedSdk
) : SandboxedSdkImpl {
@@ -128,7 +126,14 @@
return sandboxedSdk.getInterface()
}
- override fun getSdkInfo(): SandboxedSdkInfo? = null
+ @DoNotInline
+ override fun getSdkInfo(): SandboxedSdkInfo {
+ val sharedLibraryInfo = sandboxedSdk.sharedLibraryInfo
+ return SandboxedSdkInfo(
+ name = sharedLibraryInfo.name,
+ version = sharedLibraryInfo.longVersion,
+ )
+ }
@DoNotInline
override fun toSandboxedSdk(): SandboxedSdk {
@@ -143,32 +148,6 @@
}
}
- @RequiresApi(33)
- @RequiresExtension(extension = AD_SERVICES, version = 5)
- private class ApiAdServicesV5Impl(
- sandboxedSdk: SandboxedSdk
- ) : ApiAdServicesV4Impl(sandboxedSdk) {
-
- override fun getSdkInfo(): SandboxedSdkInfo {
- val sharedLibraryInfo = sandboxedSdk.sharedLibraryInfo
- return SandboxedSdkInfo(
- name = sharedLibraryInfo.name,
- version = sharedLibraryInfo.longVersion,
- )
- }
- }
-
- @RequiresExtension(extension = AD_SERVICES, version = 4)
- private object SdkImplFactory {
- fun createSdkImpl(sandboxedSdk: SandboxedSdk): SandboxedSdkImpl {
- return if (AdServicesInfo.isAtLeastV5()) {
- ApiAdServicesV5Impl(sandboxedSdk)
- } else {
- ApiAdServicesV4Impl(sandboxedSdk)
- }
- }
- }
-
private class CompatImpl(
private val sdkInterface: IBinder,
private val sdkInfo: SandboxedSdkInfo?
@@ -182,10 +161,10 @@
override fun getSdkInfo(): SandboxedSdkInfo? = sdkInfo
- @RequiresExtension(extension = AD_SERVICES, version = 4)
+ @RequiresApi(34)
override fun toSandboxedSdk(): SandboxedSdk {
// avoid class verifications errors
- return ApiAdServicesV4Impl.createSandboxedSdk(sdkInterface)
+ return Api34Impl.createSandboxedSdk(sdkInterface)
}
}
}
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkProviderAdapter.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkProviderAdapter.kt
index 24771d7..acfb2a5 100644
--- a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkProviderAdapter.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/SandboxedSdkProviderAdapter.kt
@@ -15,16 +15,13 @@
*/
package androidx.privacysandbox.sdkruntime.core
-import android.annotation.SuppressLint
import android.app.sdksandbox.LoadSdkException
import android.app.sdksandbox.SandboxedSdk
import android.app.sdksandbox.SandboxedSdkProvider
import android.content.Context
import android.os.Bundle
-import android.os.ext.SdkExtensions.AD_SERVICES
import android.view.View
import androidx.annotation.RequiresApi
-import androidx.annotation.RequiresExtension
import androidx.annotation.RestrictTo
/**
@@ -32,9 +29,7 @@
* Gets compat class name from asset "SandboxedSdkProviderCompatClassName.txt"
*
*/
-@SuppressLint("Override") // b/273473397
@RequiresApi(34)
-@RequiresExtension(extension = AD_SERVICES, version = 4)
// TODO(b/301437557) Remove after documentation migration to sdkruntime-provider
@Deprecated(
message = "Use SandboxedSdkProviderAdapter from sdkruntime-provider library",
diff --git a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/PlatformUDCImpl.kt b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/PlatformUDCImpl.kt
index 601d474..4e212c0 100644
--- a/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/PlatformUDCImpl.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-core/src/main/java/androidx/privacysandbox/sdkruntime/core/controller/impl/PlatformUDCImpl.kt
@@ -16,7 +16,6 @@
package androidx.privacysandbox.sdkruntime.core.controller.impl
-import android.annotation.SuppressLint
import android.app.Activity
import android.app.Application
import android.app.sdksandbox.sdkprovider.SdkSandboxActivityHandler
@@ -39,7 +38,6 @@
* Implementation that delegates to platform [SdkSandboxController] for Android U.
*/
@RequiresApi(34)
-@SuppressLint("NewApi", "ClassVerificationFailure") // until updating checks to requires api 34
internal class PlatformUDCImpl(
private val controller: SdkSandboxController
) : SdkSandboxControllerCompat.SandboxControllerImpl {
diff --git a/privacysandbox/sdkruntime/sdkruntime-provider/src/main/java/androidx/privacysandbox/sdkruntime/provider/SandboxedSdkProviderAdapter.kt b/privacysandbox/sdkruntime/sdkruntime-provider/src/main/java/androidx/privacysandbox/sdkruntime/provider/SandboxedSdkProviderAdapter.kt
index 5c70191..0ec0f13 100644
--- a/privacysandbox/sdkruntime/sdkruntime-provider/src/main/java/androidx/privacysandbox/sdkruntime/provider/SandboxedSdkProviderAdapter.kt
+++ b/privacysandbox/sdkruntime/sdkruntime-provider/src/main/java/androidx/privacysandbox/sdkruntime/provider/SandboxedSdkProviderAdapter.kt
@@ -16,7 +16,6 @@
package androidx.privacysandbox.sdkruntime.provider
-import android.annotation.SuppressLint
import android.app.sdksandbox.LoadSdkException
import android.app.sdksandbox.SandboxedSdk
import android.app.sdksandbox.SandboxedSdkProvider
@@ -33,7 +32,6 @@
*
*/
@RequiresApi(34)
-@SuppressLint("NewApi", "ClassVerificationFailure") // until updating checks to requires api 34
class SandboxedSdkProviderAdapter internal constructor(
private val classNameProvider: CompatClassNameProvider
) : SandboxedSdkProvider() {
diff --git a/room/benchmark/src/androidTest/java/androidx/room/benchmark/InvalidationTrackerBenchmark.kt b/room/benchmark/src/androidTest/java/androidx/room/benchmark/InvalidationTrackerBenchmark.kt
index 62de81d..c0001de 100644
--- a/room/benchmark/src/androidTest/java/androidx/room/benchmark/InvalidationTrackerBenchmark.kt
+++ b/room/benchmark/src/androidTest/java/androidx/room/benchmark/InvalidationTrackerBenchmark.kt
@@ -103,7 +103,13 @@
@Parameterized.Parameters(name = "sampleSize={0}, mode={1}")
fun data(): List<Array<Any>> =
generateAllEnumerations(
- listOf(100, 1000, 5000, 10000),
+ listOf(
+ 100,
+ 1000,
+ 5000,
+ // Removed due to due to slow run times, see b/267544445 for details.
+ // 10000
+ ),
listOf(
Mode.MEASURE_INSERT,
Mode.MEASURE_DELETE,
diff --git a/room/integration-tests/testapp/build.gradle b/room/integration-tests/testapp/build.gradle
index 55d6608..d2f0759 100644
--- a/room/integration-tests/testapp/build.gradle
+++ b/room/integration-tests/testapp/build.gradle
@@ -75,6 +75,9 @@
}
}
compileOptions {
+ // For testing Java records
+ sourceCompatibility JavaVersion.VERSION_17
+ targetCompatibility JavaVersion.VERSION_17
coreLibraryDesugaringEnabled true
}
namespace "androidx.room.integration.testapp"
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/TestDatabase.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/TestDatabase.java
index 4d4e446..79f7b5c 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/TestDatabase.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/TestDatabase.java
@@ -28,6 +28,7 @@
import androidx.room.integration.testapp.dao.PetDao;
import androidx.room.integration.testapp.dao.ProductDao;
import androidx.room.integration.testapp.dao.RawDao;
+import androidx.room.integration.testapp.dao.RecordEntityDao;
import androidx.room.integration.testapp.dao.RobotsDao;
import androidx.room.integration.testapp.dao.SchoolDao;
import androidx.room.integration.testapp.dao.SpecificDogDao;
@@ -46,6 +47,7 @@
import androidx.room.integration.testapp.vo.PetCouple;
import androidx.room.integration.testapp.vo.PetWithUser;
import androidx.room.integration.testapp.vo.Product;
+import androidx.room.integration.testapp.vo.RecordEntity;
import androidx.room.integration.testapp.vo.Robot;
import androidx.room.integration.testapp.vo.RoomLibraryPojo;
import androidx.room.integration.testapp.vo.School;
@@ -60,7 +62,8 @@
@Database(entities = {User.class, Pet.class, School.class, PetCouple.class, Toy.class,
BlobEntity.class, Product.class, FunnyNamedEntity.class, House.class,
- FriendsJunction.class, Hivemind.class, Robot.class, RoomLibraryPojo.class},
+ FriendsJunction.class, Hivemind.class, Robot.class, RoomLibraryPojo.class,
+ RecordEntity.class},
views = {PetWithUser.class},
version = 1, exportSchema = false)
@TypeConverters(TestDatabase.Converters.class)
@@ -81,6 +84,7 @@
public abstract UserHouseDao getUserHouseDao();
public abstract RobotsDao getRobotsDao();
public abstract LibraryItemDao getLibraryItemDao();
+ public abstract RecordEntityDao getRecordEntityDao();
@SuppressWarnings("unused")
public static class Converters {
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/RecordEntityDao.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/RecordEntityDao.java
new file mode 100644
index 0000000..6a6cc70
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/RecordEntityDao.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2023 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.
+ */
+
+package androidx.room.integration.testapp.dao;
+
+import androidx.room.Dao;
+import androidx.room.Insert;
+import androidx.room.Query;
+import androidx.room.integration.testapp.vo.RecordEntity;
+
+import java.util.List;
+
+@Dao
+public interface RecordEntityDao {
+
+ @Query("SELECT * FROM RecordEntity")
+ List<RecordEntity> getAll();
+
+ @Insert
+ void insert(RecordEntity item);
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/PojoTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/PojoTest.java
index 873556a..2af75eb 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/PojoTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/PojoTest.java
@@ -23,17 +23,18 @@
import androidx.room.Room;
import androidx.room.integration.testapp.TestDatabase;
+import androidx.room.integration.testapp.dao.RecordEntityDao;
import androidx.room.integration.testapp.dao.UserDao;
import androidx.room.integration.testapp.database.ProductDao;
import androidx.room.integration.testapp.database.Review;
import androidx.room.integration.testapp.database.SampleDatabase;
import androidx.room.integration.testapp.vo.AvgWeightByAge;
+import androidx.room.integration.testapp.vo.RecordEntity;
import androidx.room.integration.testapp.vo.User;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -43,17 +44,13 @@
@LargeTest
@RunWith(AndroidJUnit4.class)
public class PojoTest {
- private UserDao mUserDao;
-
- @Before
- public void createDb() {
- Context context = ApplicationProvider.getApplicationContext();
- TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class).build();
- mUserDao = db.getUserDao();
- }
@Test
public void weightsByAge() {
+ Context context = ApplicationProvider.getApplicationContext();
+ TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class).build();
+ UserDao userDao = db.getUserDao();
+
User[] users = TestUtil.createUsersArray(3, 5, 7, 10);
users[0].setAge(10);
users[0].setWeight(20);
@@ -67,8 +64,8 @@
users[3].setAge(35);
users[3].setWeight(55);
- mUserDao.insertAll(users);
- assertThat(mUserDao.weightByAge(), is(
+ userDao.insertAll(users);
+ assertThat(userDao.weightByAge(), is(
Arrays.asList(
new AvgWeightByAge(35, 55),
new AvgWeightByAge(10, 25),
@@ -87,4 +84,15 @@
assertThat(result.size(), is(1));
db.close();
}
+
+ @Test
+ public void recordEntity() {
+ Context context = ApplicationProvider.getApplicationContext();
+ TestDatabase db = Room.inMemoryDatabaseBuilder(context, TestDatabase.class).build();
+ RecordEntityDao recordDao = db.getRecordEntityDao();
+ recordDao.insert(new RecordEntity(1, "I am a RECORD"));
+ List<RecordEntity> result = recordDao.getAll();
+ assertThat(result.size(), is(1));
+ assertThat(result.get(0).data(), is("I am a RECORD"));
+ }
}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/RecordEntity.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/RecordEntity.java
new file mode 100644
index 0000000..3a1f149
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/vo/RecordEntity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2023 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.
+ */
+
+package androidx.room.integration.testapp.vo;
+
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+
+@Entity
+public record RecordEntity(
+ @PrimaryKey long id,
+ String data
+) {}
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XTypeElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XTypeElement.kt
index 98883ca..307b910 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XTypeElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XTypeElement.kt
@@ -144,6 +144,11 @@
fun isCompanionObject(): Boolean
/**
+ * Returns `true` if this [XTypeElement] is a Java record class (i.e. [java.lang.Record]).
+ */
+ fun isRecordClass(): Boolean
+
+ /**
* Fields declared in this type
* includes all instance/static fields in this
*/
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt
index f404202..33e3c00 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt
@@ -158,6 +158,10 @@
return kotlinMetadata?.isInterface() ?: (element.kind == ElementKind.INTERFACE)
}
+ override fun isRecordClass(): Boolean {
+ return element.kind == ElementKind.RECORD
+ }
+
override fun findPrimaryConstructor(): JavacConstructorElement? {
val primarySignature = kotlinMetadata?.primaryConstructorSignature ?: return null
return getConstructors().firstOrNull {
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt
index 9d4aba5..b2b8de4 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt
@@ -251,6 +251,12 @@
return !isInterface() && !declaration.isOpen()
}
+ override fun isRecordClass(): Boolean {
+ // Need to also check super type since @JvmRecord is @Retention(SOURCE)
+ return hasAnnotation(JvmRecord::class) ||
+ superClass?.isTypeOf(java.lang.Record::class) == true
+ }
+
override fun getDeclaredFields(): List<XFieldElement> {
return _declaredFields
}
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
index 5633347..d4d0197 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
@@ -32,6 +32,8 @@
import androidx.room.compiler.processing.util.getDeclaredMethodByJvmName
import androidx.room.compiler.processing.util.getField
import androidx.room.compiler.processing.util.getMethodByJvmName
+import androidx.room.compiler.processing.util.runJavaProcessorTest
+import androidx.room.compiler.processing.util.runKspTest
import androidx.room.compiler.processing.util.runProcessorTest
import com.squareup.kotlinpoet.INT
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
@@ -2415,6 +2417,61 @@
}
}
+ @Test
+ fun isRecordClass() {
+ val javaSrc = Source.java(
+ "JavaRecord",
+ """
+ public record JavaRecord(String name) { }
+ """.trimIndent()
+ )
+ val kotlinSrc = Source.kotlin(
+ "KotlinRecord.kt",
+ """
+ @JvmRecord
+ data class KotlinRecord(val name: String)
+ """.trimIndent()
+ )
+ val handler: (XTestInvocation) -> Unit = {
+ val javaElement = it.processingEnv.requireTypeElement("JavaRecord")
+ assertThat(javaElement.isRecordClass())
+ if (it.isKsp) {
+ val kotlinElement = it.processingEnv.requireTypeElement("KotlinRecord")
+ assertThat(kotlinElement.isRecordClass())
+ }
+ }
+ // Run only javac and KSP tests as @JvmRecord is broken with KAPT (KT-44706)
+ if (isPreCompiled) {
+ val compiled = compileFiles(
+ sources = listOf(javaSrc, kotlinSrc),
+ kotlincArguments = listOf("-jvm-target=16")
+ )
+ runJavaProcessorTest(
+ sources = listOf(
+ Source.java("PlaceholderJava", "public class PlaceholderJava {}")
+ ),
+ classpath = compiled,
+ handler = handler
+ )
+ runKspTest(
+ sources = listOf(Source.kotlin("placeholder.kt", "class PlaceholderKotlin")),
+ kotlincArguments = listOf("-jvm-target=16"),
+ classpath = compiled,
+ handler = handler
+ )
+ } else {
+ runJavaProcessorTest(
+ sources = listOf(javaSrc),
+ handler = handler
+ )
+ runKspTest(
+ sources = listOf(javaSrc, kotlinSrc),
+ kotlincArguments = listOf("-jvm-target=16"),
+ handler = handler
+ )
+ }
+ }
+
/**
* it is good to exclude methods coming from Object when testing as they differ between KSP
* and KAPT but irrelevant for Room.
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/PojoProcessor.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/PojoProcessor.kt
index 05c36d0..824889d 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/PojoProcessor.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/PojoProcessor.kt
@@ -961,19 +961,25 @@
private class DefaultDelegate(private val context: Context) : Delegate {
override fun onPreProcess(element: XTypeElement) {
- // Check that certain Room annotations with @Target(METHOD) are not used in the POJO
- // since it is not annotated with AutoValue.
- element.getAllMethods()
- .filter { it.hasAnyAnnotation(*TARGET_METHOD_ANNOTATIONS) }
- .forEach { method ->
- val annotationName = TARGET_METHOD_ANNOTATIONS
- .first { method.hasAnnotation(it) }
- .java.simpleName
- context.logger.e(
- method,
- ProcessorErrors.invalidAnnotationTarget(annotationName, method.kindName())
- )
- }
+ // If POJO is not a record then check that certain Room annotations with @Target(METHOD)
+ // are not used since the POJO is not annotated with AutoValue where Room column
+ // annotations are allowed in methods.
+ if (!element.isRecordClass()) {
+ element.getAllMethods()
+ .filter { it.hasAnyAnnotation(*TARGET_METHOD_ANNOTATIONS) }
+ .forEach { method ->
+ val annotationName = TARGET_METHOD_ANNOTATIONS
+ .first { columnAnnotation -> method.hasAnnotation(columnAnnotation) }
+ .java.simpleName
+ context.logger.e(
+ method,
+ ProcessorErrors.invalidAnnotationTarget(
+ annotationName,
+ method.kindName()
+ )
+ )
+ }
+ }
}
override fun findConstructors(element: XTypeElement) = element.getConstructors().filterNot {
diff --git a/settings.gradle b/settings.gradle
index 3ba7212..b60f210 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -505,6 +505,7 @@
includeProject(":compose:material3:benchmark", [BuildType.COMPOSE])
includeProject(":compose:material3:material3-adaptive", [BuildType.COMPOSE])
includeProject(":compose:material3:material3-adaptive:material3-adaptive-samples", "compose/material3/material3-adaptive/samples", [BuildType.COMPOSE])
+includeProject(":compose:material3:material3-adaptive:material3-adaptive-benchmark", "compose/material3/material3-adaptive/benchmark", [BuildType.COMPOSE])
includeProject(":compose:material3:material3-adaptive-navigation-suite", [BuildType.COMPOSE])
includeProject(":compose:material3:material3-adaptive-navigation-suite:material3-adaptive-navigation-suite-samples", "compose/material3/material3-adaptive-navigation-suite/samples", [BuildType.COMPOSE])
includeProject(":compose:material3:material3-common", [BuildType.COMPOSE])
diff --git a/slidingpanelayout/slidingpanelayout/src/androidTest/java/androidx/slidingpanelayout/widget/UserResizeModeTest.kt b/slidingpanelayout/slidingpanelayout/src/androidTest/java/androidx/slidingpanelayout/widget/UserResizeModeTest.kt
index daf0bef..6108114 100644
--- a/slidingpanelayout/slidingpanelayout/src/androidTest/java/androidx/slidingpanelayout/widget/UserResizeModeTest.kt
+++ b/slidingpanelayout/slidingpanelayout/src/androidTest/java/androidx/slidingpanelayout/widget/UserResizeModeTest.kt
@@ -160,6 +160,42 @@
.that(spl[1].width)
.isEqualTo(30)
}
+
+ @Test
+ fun splitDividerPositionDoesNotChangePadding() {
+ val context = InstrumentationRegistry.getInstrumentation().context
+ val spl = createTestSpl(context)
+ spl.setPadding(4, 0, 10, 0)
+ spl.measureAndLayoutForTest()
+ assertWithMessage("left padding")
+ .that(spl.paddingLeft)
+ .isEqualTo(4)
+ assertWithMessage("right padding")
+ .that(spl.paddingRight)
+ .isEqualTo(10)
+
+ fun testPadding(description: String) {
+ assertWithMessage("left edge of first child $description")
+ .that(spl[0].left)
+ .isEqualTo(4)
+ assertWithMessage("right edge of second child $description")
+ .that(spl[1].right)
+ .isEqualTo(spl.width - 10)
+ }
+
+ testPadding("before divider position change")
+ val firstChildExpectedWidth = spl[0].width
+ val secondChildExpectedWidth = spl[1].width
+ spl.splitDividerPosition = spl.visualDividerPosition
+ spl.measureAndLayoutForTest()
+ assertWithMessage("first child width")
+ .that(spl[0].width)
+ .isEqualTo(firstChildExpectedWidth)
+ assertWithMessage("second child width")
+ .that(spl[1].width)
+ .isEqualTo(secondChildExpectedWidth)
+ testPadding("after divider position change")
+ }
}
private fun View.drawToBitmap(): Bitmap {
diff --git a/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.kt b/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.kt
index c81253c..2082a20 100644
--- a/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.kt
+++ b/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.kt
@@ -920,8 +920,9 @@
if ((index == 0) xor isLayoutRtl) {
dividerPos - lp.horizontalMargin - paddingLeft
} else {
- // padding accounted for in widthAvailable
- widthAvailable - lp.horizontalMargin - dividerPos
+ // padding accounted for in widthAvailable;
+ // dividerPos includes left padding
+ widthAvailable - lp.horizontalMargin - (dividerPos - paddingLeft)
}
}
}
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicDataBiTransformNode.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicDataBiTransformNode.java
index 2b3ecda..5bcb602 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicDataBiTransformNode.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicDataBiTransformNode.java
@@ -33,6 +33,27 @@
* descendants of this class will implement operations of the form "O = LHS [op] RHS", or "O =
* op(LHS, RHS)".
*
+ * <p>This node will wait until there's no pending data from either side:
+ *
+ * <ul>
+ * <li>This node will wait until both sides invoked either {@code onData} or {@code onInvalidated}
+ * at least once.
+ * <li>If one side invoked {@code onPreUpdate}, this node will wait until it invoked either {@code
+ * onData} or {@code onInvalidated}.
+ * <li>This node expects each side to invoke exactly on {@code onData} or {@code onInvalidated}
+ * after each {@code onPreUpdate}:
+ * <ul>
+ * <li>If {@code onData} or {@code onInvalidated} are invoked without {@code onPreUpdate},
+ * this node will log a warning and downstream the callback immediately (unless waiting
+ * for the other side).
+ * <li>If {@code onPreUpdate} is invoked more than once without {@code onData} or {@code
+ * onInvalidated}, this node will log a warning and ignore the followup {@code
+ * onPreUpdate}.
+ * </ul>
+ * Both of these scenarios can mean {@link DynamicTypeEvaluator} might publish a partially
+ * evaluated update.
+ * </ul>
+ *
* @param <LhsT> The source data type for the left-hand side of the operation.
* @param <RhsT> The source data type for the right-hand side of the operation.
* @param <O> The data type that this node emits.
@@ -40,133 +61,95 @@
class DynamicDataBiTransformNode<LhsT, RhsT, O> implements DynamicDataNode<O> {
private static final String TAG = "DynamicDataBiTransform";
- private final DynamicTypeValueReceiverWithPreUpdate<LhsT> mLhsIncomingCallback;
- private final DynamicTypeValueReceiverWithPreUpdate<RhsT> mRhsIncomingCallback;
+ private final UpstreamCallback<LhsT> mLhsUpstreamCallback = new UpstreamCallback<>();
+ private final UpstreamCallback<RhsT> mRhsUpstreamCallback = new UpstreamCallback<>();
final DynamicTypeValueReceiverWithPreUpdate<O> mDownstream;
private final BiFunction<LhsT, RhsT, O> mTransformer;
- @Nullable LhsT mCachedLhsData;
- @Nullable RhsT mCachedRhsData;
-
- int mPendingLhsStateUpdates = 0;
- int mPendingRhsStateUpdates = 0;
+ boolean mDownstreamPreUpdated = false;
DynamicDataBiTransformNode(
DynamicTypeValueReceiverWithPreUpdate<O> downstream,
BiFunction<LhsT, RhsT, O> transformer) {
this.mDownstream = downstream;
this.mTransformer = transformer;
+ }
- // These classes refer to handlePreStateUpdate, which is @UnderInitialization when these
- // initializers run, and hence raise an error. It's invalid to annotate
- // handle{Pre}StateUpdate as @UnderInitialization (since it refers to initialized fields),
- // and moving this assignment into the constructor yields the same error (since one of the
- // fields has to be assigned first, when the class is still under initialization).
- //
- // The only path to get these is via get{Lhs,Rhs}IncomingCallback, which can only be called
- // when the class is initialized (and which also cannot be called from a sub-constructor, as
- // that will again complain that it's calling something which is @UnderInitialization).
- // Given that, suppressing the warning in onStateUpdate should be safe.
- this.mLhsIncomingCallback =
- new DynamicTypeValueReceiverWithPreUpdate<LhsT>() {
- @Override
- public void onPreUpdate() {
- mPendingLhsStateUpdates++;
+ private class UpstreamCallback<T> implements DynamicTypeValueReceiverWithPreUpdate<T> {
+ private boolean mUpstreamPreUpdated = false;
- if (mPendingLhsStateUpdates == 1 && mPendingRhsStateUpdates == 0) {
- mDownstream.onPreUpdate();
- }
- }
+ /**
+ * Whether {@link #cache} should be used, i.e. set to true when either {@link #onData} or
+ * {@link #onInvalidated} were invoked at least once, and there's no pending {@link
+ * #onPreUpdate} call.
+ */
+ boolean isCacheReady = false;
- @SuppressWarnings("method.invocation")
- @Override
- public void onData(@NonNull LhsT newData) {
- onUpdatedImpl(newData);
- }
+ /**
+ * Latest value arrived in {@link #onData}, or {@code null} if the last callback was {@link
+ * #onInvalidated}. Should not be used if {@link #isCacheReady} is {@code false}.
+ */
+ @Nullable T cache;
- private void onUpdatedImpl(@Nullable LhsT newData) {
- if (mPendingLhsStateUpdates == 0) {
- Log.w(
- TAG,
- "Received a state update, but one or more suppliers did not"
- + " call onPreStateUpdate");
- } else {
- mPendingLhsStateUpdates--;
- }
+ @Override
+ public void onPreUpdate() {
+ if (mUpstreamPreUpdated) {
+ Log.w(TAG, "Received onPreUpdate twice without onData/onInvalidated.");
+ }
+ isCacheReady = false;
+ mUpstreamPreUpdated = true;
+ if (!mDownstreamPreUpdated) {
+ mDownstreamPreUpdated = true;
+ mDownstream.onPreUpdate();
+ }
+ }
- mCachedLhsData = newData;
- handleStateUpdate();
- }
+ @Override
+ public void onData(@NonNull T newData) {
+ onUpdate(newData);
+ }
- @Override
- public void onInvalidated() {
- // Note: Casts are required here to help out the null checker.
- onUpdatedImpl((LhsT) null);
- }
- };
+ @Override
+ public void onInvalidated() {
+ onUpdate(null);
+ }
- this.mRhsIncomingCallback =
- new DynamicTypeValueReceiverWithPreUpdate<RhsT>() {
- @Override
- public void onPreUpdate() {
- mPendingRhsStateUpdates++;
-
- if (mPendingLhsStateUpdates == 0 && mPendingRhsStateUpdates == 1) {
- mDownstream.onPreUpdate();
- }
- }
-
- @SuppressWarnings("method.invocation")
- @Override
- public void onData(@NonNull RhsT newData) {
- onUpdatedImpl(newData);
- }
-
- private void onUpdatedImpl(@Nullable RhsT newData) {
- if (mPendingRhsStateUpdates == 0) {
- Log.w(
- TAG,
- "Received a state update, but one or more suppliers did not"
- + " call onPreStateUpdate");
- } else {
- mPendingRhsStateUpdates--;
- }
-
- mCachedRhsData = newData;
- handleStateUpdate();
- }
-
- @Override
- public void onInvalidated() {
- onUpdatedImpl((RhsT) null);
- }
- };
+ private void onUpdate(@Nullable T newData) {
+ if (!mUpstreamPreUpdated) {
+ Log.w(TAG, "Received onData/onInvalidated without onPreUpdate.");
+ }
+ mUpstreamPreUpdated = false;
+ isCacheReady = true;
+ cache = newData;
+ handleStateUpdate();
+ }
}
void handleStateUpdate() {
- if (mPendingLhsStateUpdates == 0 && mPendingRhsStateUpdates == 0) {
- LhsT lhs = mCachedLhsData;
- RhsT rhs = mCachedRhsData;
+ if (!mLhsUpstreamCallback.isCacheReady || !mRhsUpstreamCallback.isCacheReady) {
+ return;
+ }
+ LhsT lhs = mLhsUpstreamCallback.cache;
+ RhsT rhs = mRhsUpstreamCallback.cache;
- if (lhs == null || rhs == null) {
+ if (lhs == null || rhs == null) {
+ mDownstream.onInvalidated();
+ } else {
+ O result = mTransformer.apply(lhs, rhs);
+ if (result == null) {
mDownstream.onInvalidated();
} else {
- O result = mTransformer.apply(lhs, rhs);
- if (result == null) {
- mDownstream.onInvalidated();
- } else {
- mDownstream.onData(result);
- }
+ mDownstream.onData(result);
}
}
}
- public DynamicTypeValueReceiverWithPreUpdate<LhsT> getLhsIncomingCallback() {
- return mLhsIncomingCallback;
+ public DynamicTypeValueReceiverWithPreUpdate<LhsT> getLhsUpstreamCallback() {
+ return mLhsUpstreamCallback;
}
- public DynamicTypeValueReceiverWithPreUpdate<RhsT> getRhsIncomingCallback() {
- return mRhsIncomingCallback;
+ public DynamicTypeValueReceiverWithPreUpdate<RhsT> getRhsUpstreamCallback() {
+ return mRhsUpstreamCallback;
}
}
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicTypeEvaluator.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicTypeEvaluator.java
index a3c1cd2..5dc4a67 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicTypeEvaluator.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicTypeEvaluator.java
@@ -633,12 +633,12 @@
node = concatNode;
bindRecursively(
stringSource.getConcatOp().getInputLhs(),
- concatNode.getLhsIncomingCallback(),
+ concatNode.getLhsUpstreamCallback(),
locale,
resultBuilder);
bindRecursively(
stringSource.getConcatOp().getInputRhs(),
- concatNode.getRhsIncomingCallback(),
+ concatNode.getRhsUpstreamCallback(),
locale,
resultBuilder);
break;
@@ -681,11 +681,11 @@
bindRecursively(
int32Source.getArithmeticOperation().getInputLhs(),
- arithmeticNode.getLhsIncomingCallback(),
+ arithmeticNode.getLhsUpstreamCallback(),
resultBuilder);
bindRecursively(
int32Source.getArithmeticOperation().getInputRhs(),
- arithmeticNode.getRhsIncomingCallback(),
+ arithmeticNode.getRhsUpstreamCallback(),
resultBuilder);
break;
@@ -809,11 +809,11 @@
node = betweenInstancesNode;
bindRecursively(
durationSource.getBetween().getStartInclusive(),
- betweenInstancesNode.getLhsIncomingCallback(),
+ betweenInstancesNode.getLhsUpstreamCallback(),
resultBuilder);
bindRecursively(
durationSource.getBetween().getEndExclusive(),
- betweenInstancesNode.getRhsIncomingCallback(),
+ betweenInstancesNode.getRhsUpstreamCallback(),
resultBuilder);
break;
case FIXED:
@@ -984,11 +984,11 @@
bindRecursively(
floatSource.getArithmeticOperation().getInputLhs(),
- arithmeticNode.getLhsIncomingCallback(),
+ arithmeticNode.getLhsUpstreamCallback(),
resultBuilder);
bindRecursively(
floatSource.getArithmeticOperation().getInputRhs(),
- arithmeticNode.getRhsIncomingCallback(),
+ arithmeticNode.getRhsUpstreamCallback(),
resultBuilder);
break;
@@ -1167,11 +1167,11 @@
bindRecursively(
boolSource.getInt32Comparison().getInputLhs(),
- compNode.getLhsIncomingCallback(),
+ compNode.getLhsUpstreamCallback(),
resultBuilder);
bindRecursively(
boolSource.getInt32Comparison().getInputRhs(),
- compNode.getRhsIncomingCallback(),
+ compNode.getRhsUpstreamCallback(),
resultBuilder);
break;
@@ -1184,11 +1184,11 @@
bindRecursively(
boolSource.getLogicalOp().getInputLhs(),
- logicalNode.getLhsIncomingCallback(),
+ logicalNode.getLhsUpstreamCallback(),
resultBuilder);
bindRecursively(
boolSource.getLogicalOp().getInputRhs(),
- logicalNode.getRhsIncomingCallback(),
+ logicalNode.getRhsUpstreamCallback(),
resultBuilder);
break;
@@ -1211,11 +1211,11 @@
bindRecursively(
boolSource.getFloatComparison().getInputLhs(),
- compNode.getLhsIncomingCallback(),
+ compNode.getLhsUpstreamCallback(),
resultBuilder);
bindRecursively(
boolSource.getFloatComparison().getInputRhs(),
- compNode.getRhsIncomingCallback(),
+ compNode.getRhsUpstreamCallback(),
resultBuilder);
break;
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/BoolNodesTest.java b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/BoolNodesTest.java
index 00591df..176d5bc 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/BoolNodesTest.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/BoolNodesTest.java
@@ -160,11 +160,11 @@
new BoolNodes.LogicalBoolOp(protoNode, new AddToListCallback<>(results));
FixedBool lhsProtoNode = FixedBool.newBuilder().setValue(lhs).build();
- FixedBoolNode lhsNode = new FixedBoolNode(lhsProtoNode, node.getLhsIncomingCallback());
+ FixedBoolNode lhsNode = new FixedBoolNode(lhsProtoNode, node.getLhsUpstreamCallback());
lhsNode.init();
FixedBool rhsProtoNode = FixedBool.newBuilder().setValue(rhs).build();
- FixedBoolNode rhsNode = new FixedBoolNode(rhsProtoNode, node.getRhsIncomingCallback());
+ FixedBoolNode rhsNode = new FixedBoolNode(rhsProtoNode, node.getRhsUpstreamCallback());
rhsNode.init();
return results.get(0);
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/DurationNodesTest.java b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/DurationNodesTest.java
index 22ef7ad..c5607d0 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/DurationNodesTest.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/DurationNodesTest.java
@@ -62,8 +62,8 @@
Instant secondInstant = Instant.ofEpochSecond(12345L);
BetweenInstancesNode node = new BetweenInstancesNode(new AddToListCallback<>(results));
- node.getLhsIncomingCallback().onData(firstInstant);
- node.getRhsIncomingCallback().onData(secondInstant);
+ node.getLhsUpstreamCallback().onData(firstInstant);
+ node.getRhsUpstreamCallback().onData(secondInstant);
assertThat(results).containsExactly(Duration.between(firstInstant, secondInstant));
}
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/DynamicDataBiTransformNodeTest.java b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/DynamicDataBiTransformNodeTest.java
index 4bd9905..5501a16 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/DynamicDataBiTransformNodeTest.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/DynamicDataBiTransformNodeTest.java
@@ -18,11 +18,12 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -36,68 +37,124 @@
@Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
@Mock private DynamicTypeValueReceiverWithPreUpdate<Integer> mMockCallback;
- private DynamicDataBiTransformNode<Integer, Integer, Integer> mNodeUnderTest;
+ private DynamicDataBiTransformNode<Integer, Integer, Integer> mSumNodeUnderTest;
@Before
public void setUp() {
- mNodeUnderTest = new DynamicDataBiTransformNode<>(mMockCallback, Integer::sum);
+ mSumNodeUnderTest = new DynamicDataBiTransformNode<>(mMockCallback, Integer::sum);
+ }
+
+ @After
+ public void assertNoOtherCallbacks() {
+ // DynamicDataBiTransformNode is fragile, we want to make sure an unexpected callback wasn't
+ // used on one of the behaviors tested here.
+ verifyNoMoreInteractions(mMockCallback);
}
@Test
public void onPreStateUpdate_propagatesOnce() {
- mNodeUnderTest.getLhsIncomingCallback().onPreUpdate();
+ mSumNodeUnderTest.getLhsUpstreamCallback().onPreUpdate();
verify(mMockCallback).onPreUpdate();
+ reset(mMockCallback);
// Next call should not cause another call to be passed through.
- mNodeUnderTest.getRhsIncomingCallback().onPreUpdate();
- verify(mMockCallback).onPreUpdate();
+ mSumNodeUnderTest.getRhsUpstreamCallback().onPreUpdate();
+ verify(mMockCallback, never()).onPreUpdate();
}
@Test
- public void onStateUpdate_invokesCallback() {
- mNodeUnderTest.getLhsIncomingCallback().onPreUpdate();
- mNodeUnderTest.getRhsIncomingCallback().onPreUpdate();
+ public void onPreStateUpdate_twice_ignoresSecondOne() {
+ mSumNodeUnderTest.getLhsUpstreamCallback().onPreUpdate();
+ verify(mMockCallback).onPreUpdate();
+ reset(mMockCallback);
- mNodeUnderTest.getLhsIncomingCallback().onData(5);
- mNodeUnderTest.getRhsIncomingCallback().onData(6);
+ mSumNodeUnderTest.getLhsUpstreamCallback().onPreUpdate();
+ mSumNodeUnderTest.getRhsUpstreamCallback().onPreUpdate();
+ // Doesn't pre-update again.
+ verify(mMockCallback, never()).onPreUpdate();
+ mSumNodeUnderTest.getLhsUpstreamCallback().onData(5);
+ mSumNodeUnderTest.getRhsUpstreamCallback().onData(6);
+ // Still propagates data.
verify(mMockCallback).onData(11);
}
@Test
- public void onStateUpdate_onlyOneSide_doesntInvokeCallback() {
- // Note: RHS has *not* been seeded with any data here...
- mNodeUnderTest.getLhsIncomingCallback().onPreUpdate();
- mNodeUnderTest.getLhsIncomingCallback().onData(5);
+ public void onStateUpdate_invokesCallback() {
+ mSumNodeUnderTest.getLhsUpstreamCallback().onPreUpdate();
+ mSumNodeUnderTest.getRhsUpstreamCallback().onPreUpdate();
+ mSumNodeUnderTest.getLhsUpstreamCallback().onData(5);
+ mSumNodeUnderTest.getRhsUpstreamCallback().onData(6);
+
+ verify(mMockCallback).onPreUpdate();
+ verify(mMockCallback).onData(11);
+ }
+
+ @Test
+ public void onStateUpdate_onlyOneSide_doesNotInvokeCallback() {
+ // Note: RHS has *not* been seeded with any data here...
+ mSumNodeUnderTest.getLhsUpstreamCallback().onPreUpdate();
+ mSumNodeUnderTest.getLhsUpstreamCallback().onData(5);
+
+ verify(mMockCallback).onPreUpdate();
verify(mMockCallback, never()).onData(any());
}
@Test
public void onStateUpdate_onPreStateUpdateNotCalled_stillPropagatesData() {
- mNodeUnderTest.getLhsIncomingCallback().onData(5);
- mNodeUnderTest.getRhsIncomingCallback().onData(6);
+ mSumNodeUnderTest.getLhsUpstreamCallback().onData(5);
+ mSumNodeUnderTest.getRhsUpstreamCallback().onData(6);
verify(mMockCallback, never()).onPreUpdate();
verify(mMockCallback).onData(11);
}
- @SuppressWarnings("unchecked")
@Test
public void onStateUpdate_onStateUpdate_eachSidePendingUpdateHandledSeparately() {
// Seed with some real data
- mNodeUnderTest.getLhsIncomingCallback().onData(5);
- mNodeUnderTest.getRhsIncomingCallback().onData(6);
+ mSumNodeUnderTest.getLhsUpstreamCallback().onData(5);
+ mSumNodeUnderTest.getRhsUpstreamCallback().onData(6);
reset(mMockCallback);
- mNodeUnderTest.getLhsIncomingCallback().onPreUpdate();
- mNodeUnderTest.getRhsIncomingCallback().onData(10);
+ mSumNodeUnderTest.getLhsUpstreamCallback().onPreUpdate();
+ verify(mMockCallback).onPreUpdate();
+ mSumNodeUnderTest.getRhsUpstreamCallback().onData(10);
// The "is update pending" counters should be evaluated separately here, so the incoming
// update on the RHS should not count towards the state update from the LHS
verify(mMockCallback, never()).onData(any());
// And now, fulfilling the LHS should still cause an update to be fired.
- mNodeUnderTest.getLhsIncomingCallback().onData(20);
+ mSumNodeUnderTest.getLhsUpstreamCallback().onData(20);
verify(mMockCallback).onData(30);
}
+
+ @Test
+ public void onStateUpdate_secondTimeOnlyOneSide_doesNotWaitForOtherSide() {
+ // Seed with some real data
+ mSumNodeUnderTest.getLhsUpstreamCallback().onData(5);
+ mSumNodeUnderTest.getRhsUpstreamCallback().onData(6);
+ reset(mMockCallback);
+
+ // Only on the left.
+ mSumNodeUnderTest.getLhsUpstreamCallback().onData(10);
+
+ // Not waiting for the right.
+ verify(mMockCallback).onData(16);
+ }
+
+ @Test
+ public void onInvalidated_propagatesAfterBothSides() {
+ mSumNodeUnderTest.getLhsUpstreamCallback().onInvalidated();
+ verifyNoMoreInteractions(mMockCallback);
+
+ mSumNodeUnderTest.getRhsUpstreamCallback().onData(5);
+ verify(mMockCallback).onInvalidated();
+ }
+
+ /** Same as {@link org.mockito.Mockito#reset} but suppresses the unchecked warning. */
+ @SuppressWarnings("unchecked")
+ private <T> void reset(T mock) {
+ org.mockito.Mockito.reset(mock);
+ }
}
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/FloatNodeTest.java b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/FloatNodeTest.java
index 27ea07b..c8aea06 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/FloatNodeTest.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/FloatNodeTest.java
@@ -312,7 +312,7 @@
float lhsValue = 6.6f;
FixedFloat lhsProtoNode = FixedFloat.newBuilder().setValue(lhsValue).build();
- FixedFloatNode lhsNode = new FixedFloatNode(lhsProtoNode, node.getLhsIncomingCallback());
+ FixedFloatNode lhsNode = new FixedFloatNode(lhsProtoNode, node.getLhsUpstreamCallback());
lhsNode.init();
float oldRhsValue = 6.5f;
@@ -325,7 +325,7 @@
.build()));
StateFloatSource rhsProtoNode = StateFloatSource.newBuilder().setSourceKey("foo").build();
StateFloatSourceNode rhsNode =
- new StateFloatSourceNode(oss, rhsProtoNode, node.getRhsIncomingCallback());
+ new StateFloatSourceNode(oss, rhsProtoNode, node.getRhsUpstreamCallback());
rhsNode.preInit();
rhsNode.init();
@@ -359,11 +359,11 @@
float numerator = 0f;
FixedFloat lhsProtoNode = FixedFloat.newBuilder().setValue(numerator).build();
- FixedFloatNode lhsNode = new FixedFloatNode(lhsProtoNode, node.getLhsIncomingCallback());
+ FixedFloatNode lhsNode = new FixedFloatNode(lhsProtoNode, node.getLhsUpstreamCallback());
float denominator = 0f;
FixedFloat rhsProtoNode = FixedFloat.newBuilder().setValue(denominator).build();
- FixedFloatNode rhsNode = new FixedFloatNode(rhsProtoNode, node.getRhsIncomingCallback());
+ FixedFloatNode rhsNode = new FixedFloatNode(rhsProtoNode, node.getRhsUpstreamCallback());
lhsNode.preInit();
rhsNode.preInit();
@@ -389,11 +389,11 @@
float numerator = 1f;
FixedFloat lhsProtoNode = FixedFloat.newBuilder().setValue(numerator).build();
- FixedFloatNode lhsNode = new FixedFloatNode(lhsProtoNode, node.getLhsIncomingCallback());
+ FixedFloatNode lhsNode = new FixedFloatNode(lhsProtoNode, node.getLhsUpstreamCallback());
float denominator = 0;
FixedFloat rhsProtoNode = FixedFloat.newBuilder().setValue(denominator).build();
- FixedFloatNode rhsNode = new FixedFloatNode(rhsProtoNode, node.getRhsIncomingCallback());
+ FixedFloatNode rhsNode = new FixedFloatNode(rhsProtoNode, node.getRhsUpstreamCallback());
lhsNode.preInit();
rhsNode.preInit();
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/Int32NodesTest.java b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/Int32NodesTest.java
index 0adc166..1771223 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/Int32NodesTest.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/Int32NodesTest.java
@@ -113,11 +113,11 @@
int numerator = 4;
FixedInt32 lhsProtoNode = FixedInt32.newBuilder().setValue(numerator).build();
- FixedInt32Node lhsNode = new FixedInt32Node(lhsProtoNode, node.getLhsIncomingCallback());
+ FixedInt32Node lhsNode = new FixedInt32Node(lhsProtoNode, node.getLhsUpstreamCallback());
int denominator = 2;
FixedInt32 rhsProtoNode = FixedInt32.newBuilder().setValue(denominator).build();
- FixedInt32Node rhsNode = new FixedInt32Node(rhsProtoNode, node.getRhsIncomingCallback());
+ FixedInt32Node rhsNode = new FixedInt32Node(rhsProtoNode, node.getRhsUpstreamCallback());
lhsNode.preInit();
rhsNode.preInit();
@@ -143,11 +143,11 @@
int numerator = 0;
FixedInt32 lhsProtoNode = FixedInt32.newBuilder().setValue(numerator).build();
- FixedInt32Node lhsNode = new FixedInt32Node(lhsProtoNode, node.getLhsIncomingCallback());
+ FixedInt32Node lhsNode = new FixedInt32Node(lhsProtoNode, node.getLhsUpstreamCallback());
int denominator = 0;
FixedInt32 rhsProtoNode = FixedInt32.newBuilder().setValue(denominator).build();
- FixedInt32Node rhsNode = new FixedInt32Node(rhsProtoNode, node.getRhsIncomingCallback());
+ FixedInt32Node rhsNode = new FixedInt32Node(rhsProtoNode, node.getRhsUpstreamCallback());
lhsNode.preInit();
rhsNode.preInit();
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/ParametrizedDynamicTypeEvaluatorTest.java b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/ParametrizedDynamicTypeEvaluatorTest.java
index fea4c97..f68ef36 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/ParametrizedDynamicTypeEvaluatorTest.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/ParametrizedDynamicTypeEvaluatorTest.java
@@ -523,8 +523,8 @@
if (mExpectedValue != null) {
// Test expects an actual value.
- assertThat(results).hasSize(1);
assertThat(results).containsExactly(mExpectedValue);
+ assertThat(invalidatedCalls.get()).isEqualTo(0);
} else {
// Test expects an invalid value.
assertThat(results).isEmpty();
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/StringNodesTest.java b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/StringNodesTest.java
index b3d45da..1e74a05 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/StringNodesTest.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/StringNodesTest.java
@@ -188,11 +188,11 @@
FixedStringNode lhsNode =
new FixedStringNode(
FixedString.newBuilder().setValue(string300chars).build(),
- node.getLhsIncomingCallback());
+ node.getLhsUpstreamCallback());
FixedStringNode rhsNode =
new FixedStringNode(
FixedString.newBuilder().setValue(string300chars).build(),
- node.getRhsIncomingCallback());
+ node.getRhsUpstreamCallback());
lhsNode.preInit();
lhsNode.init();
rhsNode.preInit();
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java
index 9df108f..993dd38 100644
--- a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java
@@ -2294,6 +2294,16 @@
// Modifiers cannot be applied to android's Space, so use a plain View if this Spacer has
// modifiers.
View view;
+
+ // Init the layout params to 0 in case of linear dimension (so we don't get strange
+ // behaviour before the first data pipeline update).
+ if (spacer.getWidth().hasLinearDimension()) {
+ layoutParams.width = 0;
+ }
+ if (spacer.getHeight().hasLinearDimension()) {
+ layoutParams.height = 0;
+ }
+
if (spacer.hasModifiers()) {
view =
applyModifiers(
@@ -2311,9 +2321,6 @@
parentViewWrapper.maybeAddView(view, layoutParams);
if (spacer.getWidth().hasLinearDimension()) {
- // Init the layout params' width to 0 (so we don't get strange behaviour before the
- // first data pipeline update).
- layoutParams.width = 0;
handleProp(
spacer.getWidth().getLinearDimension(),
width -> {
@@ -2331,9 +2338,6 @@
}
if (spacer.getHeight().hasLinearDimension()) {
- // Init the layout params' width to 0 (so we don't get strange behaviour before the
- // first data pipeline update).
- layoutParams.height = 0;
handleProp(
spacer.getHeight().getLinearDimension(),
height -> {
@@ -2356,6 +2360,11 @@
handleProp(
spacer.getWidth().getLinearDimension(),
width -> {
+ // Update minimum width first, because LayoutParams could be null.
+ // This calls requestLayout.
+ int widthPx = safeDpToPx(width);
+ view.setMinimumWidth(widthPx);
+
// We still need to update layout params in case other dimension is
// expand, so 0 could
// be miss interpreted.
@@ -2365,9 +2374,7 @@
return;
}
- lp.width = safeDpToPx(width);
- // This calls requestLayout.
- view.setMinimumWidth(safeDpToPx(width));
+ lp.width = widthPx;
},
posId,
pipelineMaker);
@@ -2376,6 +2383,11 @@
handleProp(
spacer.getHeight().getLinearDimension(),
height -> {
+ // Update minimum height first, because LayoutParams could be null.
+ // This calls requestLayout.
+ int heightPx = safeDpToPx(height);
+ view.setMinimumHeight(heightPx);
+
// We still need to update layout params in case other dimension is
// expand, so 0 could be miss interpreted.
LayoutParams lp = view.getLayoutParams();
@@ -2384,9 +2396,7 @@
return;
}
- lp.height = safeDpToPx(height);
- // This calls requestLayout.
- view.setMinimumHeight(safeDpToPx(height));
+ lp.height = heightPx;
},
posId,
pipelineMaker);
diff --git a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflaterTest.java b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflaterTest.java
index 0a66ff6..4fa4807 100644
--- a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflaterTest.java
+++ b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflaterTest.java
@@ -78,6 +78,7 @@
import android.widget.TextView;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -630,14 +631,75 @@
// Dimensions are in DP, but the density is currently 1 in the tests, so this is fine.
expect.that(tv.getMeasuredWidth()).isEqualTo(width);
expect.that(tv.getMeasuredHeight()).isEqualTo(height);
+ }
- // This tests that layoutParams weren't null in the case when there's no modifiers so that
- // minimum dimension is correctly set.
+ @Test
+ public void inflate_spacer_noModifiers_hasMinDim() {
+ int width = 10;
+ int height = 20;
+ LayoutElement root =
+ LayoutElement.newBuilder()
+ .setSpacer(
+ Spacer.newBuilder()
+ .setHeight(
+ SpacerDimension.newBuilder()
+ .setLinearDimension(dp(height)))
+ .setWidth(
+ SpacerDimension.newBuilder()
+ .setLinearDimension(dp(width))))
+ .build();
+
+ FrameLayout rootLayout = renderer(fingerprintedLayout(root)).inflate();
+
+ // Check that there's a single element in the layout...
+ assertThat(rootLayout.getChildCount()).isEqualTo(1);
+ View tv = rootLayout.getChildAt(0);
+
+ // This tests that minimum dimension is correctly set.
+ // Dimensions are in DP, but the density is currently 1 in the tests, so this is fine.
expect.that(tv.getMinimumWidth()).isEqualTo(width);
expect.that(tv.getMeasuredHeight()).isEqualTo(height);
}
@Test
+ public void inflate_spacer_afterMutation_hasMinDim() {
+ // Confirms that all dimensions are correctly set after mutation.
+ int width = 10;
+ int newWidth = width - 5;
+ int height = 20;
+ int newHeight = height - 6;
+
+ Layout layout1 = layoutBoxWithSpacer(width, height);
+ Layout layout2 = layoutBoxWithSpacer(newWidth, newHeight);
+
+ // Add initial layout.
+ Renderer renderer = renderer(layout1);
+ ViewGroup inflatedViewParent = renderer.inflate();
+
+ // Compute the mutation.
+ ViewGroupMutation mutation =
+ renderer.computeMutation(getRenderedMetadata(inflatedViewParent), layout2);
+ assertThat(mutation).isNotNull();
+ assertThat(mutation.isNoOp()).isFalse();
+
+ // Apply the mutation.
+ boolean mutationResult = renderer.applyMutation(inflatedViewParent, mutation);
+ assertThat(mutationResult).isTrue();
+
+ // This contains layout after the mutation.
+ ViewGroup boxAfterMutation = (ViewGroup) inflatedViewParent.getChildAt(0);
+ View spacerAfterMutation = boxAfterMutation.getChildAt(0);
+
+ // Dimensions are in DP, but the density is currently 1 in the tests, so this is fine.
+ expect.that(spacerAfterMutation.getMeasuredWidth()).isEqualTo(0);
+ expect.that(spacerAfterMutation.getMeasuredHeight()).isEqualTo(0);
+
+ // This tests that minimum dimension is correctly set.
+ expect.that(spacerAfterMutation.getMinimumWidth()).isEqualTo(newWidth);
+ expect.that(spacerAfterMutation.getMinimumHeight()).isEqualTo(newHeight);
+ }
+
+ @Test
public void inflate_spacerWithModifiers() {
int width = 10;
int height = 20;
@@ -671,6 +733,42 @@
}
@Test
+ public void inflate_spacer_withModifiers_afterMutation_hasMinDim() {
+ // Confirms that all dimensions are correctly set after mutation.
+ int width = 10;
+ int newWidth = width - 5;
+ int height = 20;
+ int newHeight = height - 6;
+ Modifiers.Builder modifiers =
+ Modifiers.newBuilder().setBorder(Border.newBuilder().setWidth(dp(2)).build());
+
+ Layout layout1 = layoutBoxWithSpacer(width, height, modifiers);
+
+ // Add initial layout.
+ Renderer renderer = renderer(layout1);
+ ViewGroup inflatedViewParent = renderer.inflate();
+
+ Layout layout2 = layoutBoxWithSpacer(newHeight, newWidth, modifiers);
+
+ // Compute the mutation.
+ ViewGroupMutation mutation =
+ renderer.computeMutation(getRenderedMetadata(inflatedViewParent), layout2);
+ assertThat(mutation).isNotNull();
+ assertThat(mutation.isNoOp()).isFalse();
+
+ // Apply the mutation.
+ boolean mutationResult = renderer.applyMutation(inflatedViewParent, mutation);
+ assertThat(mutationResult).isTrue();
+
+ ViewGroup boxAfterMutation = (ViewGroup) inflatedViewParent.getChildAt(0);
+ View spacerAfterMutation = boxAfterMutation.getChildAt(0);
+
+ // Dimensions are in DP, but the density is currently 1 in the tests, so this is fine.
+ expect.that(spacerAfterMutation.getMeasuredWidth()).isEqualTo(0);
+ expect.that(spacerAfterMutation.getMeasuredHeight()).isEqualTo(0);
+ }
+
+ @Test
public void inflate_spacerWithDynamicDimension_andModifiers() {
int width = 100;
int widthForLayout = 112;
@@ -5749,4 +5847,27 @@
.setLayoutWeight(FloatProp.newBuilder().setValue(weight).build())
.build();
}
+
+ /** Builds a wrapper Box that contains Spacer with the given parameters. */
+ private static Layout layoutBoxWithSpacer(int width, int height) {
+ return layoutBoxWithSpacer(width, height, /* modifiers= */ null);
+ }
+
+ /** Builds a wrapper Box that contains Spacer with the given parameters. */
+ private static Layout layoutBoxWithSpacer(
+ int width, int height, @Nullable Modifiers.Builder modifiers) {
+ Spacer.Builder spacer =
+ Spacer.newBuilder()
+ .setWidth(SpacerDimension.newBuilder().setLinearDimension(dp(width)))
+ .setHeight(SpacerDimension.newBuilder().setLinearDimension(dp(height)));
+ if (modifiers != null) {
+ spacer.setModifiers(modifiers);
+ }
+ return fingerprintedLayout(
+ LayoutElement.newBuilder()
+ .setBox(
+ Box.newBuilder()
+ .addContents(LayoutElement.newBuilder().setSpacer(spacer)))
+ .build());
+ }
}
diff --git a/wear/protolayout/protolayout/api/current.txt b/wear/protolayout/protolayout/api/current.txt
index a4ba9ae..e3f0976 100644
--- a/wear/protolayout/protolayout/api/current.txt
+++ b/wear/protolayout/protolayout/api/current.txt
@@ -122,12 +122,16 @@
}
@androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public static final class ColorBuilders.ColorStop {
- ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public ColorBuilders.ColorStop(androidx.wear.protolayout.ColorBuilders.ColorProp);
- ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public ColorBuilders.ColorStop(androidx.wear.protolayout.ColorBuilders.ColorProp, androidx.wear.protolayout.TypeBuilders.FloatProp);
method public androidx.wear.protolayout.ColorBuilders.ColorProp getColor();
method public androidx.wear.protolayout.TypeBuilders.FloatProp? getOffset();
}
+ public static final class ColorBuilders.ColorStop.Builder {
+ ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public ColorBuilders.ColorStop.Builder(androidx.wear.protolayout.ColorBuilders.ColorProp);
+ method public androidx.wear.protolayout.ColorBuilders.ColorStop build();
+ method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public androidx.wear.protolayout.ColorBuilders.ColorStop.Builder setOffset(androidx.wear.protolayout.TypeBuilders.FloatProp);
+ }
+
@androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public static final class ColorBuilders.SweepGradient implements androidx.wear.protolayout.ColorBuilders.Brush {
method public java.util.List<androidx.wear.protolayout.ColorBuilders.ColorStop!> getColorStops();
method public androidx.wear.protolayout.DimensionBuilders.DegreesProp getEndAngle();
diff --git a/wear/protolayout/protolayout/api/restricted_current.txt b/wear/protolayout/protolayout/api/restricted_current.txt
index a4ba9ae..e3f0976 100644
--- a/wear/protolayout/protolayout/api/restricted_current.txt
+++ b/wear/protolayout/protolayout/api/restricted_current.txt
@@ -122,12 +122,16 @@
}
@androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public static final class ColorBuilders.ColorStop {
- ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public ColorBuilders.ColorStop(androidx.wear.protolayout.ColorBuilders.ColorProp);
- ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public ColorBuilders.ColorStop(androidx.wear.protolayout.ColorBuilders.ColorProp, androidx.wear.protolayout.TypeBuilders.FloatProp);
method public androidx.wear.protolayout.ColorBuilders.ColorProp getColor();
method public androidx.wear.protolayout.TypeBuilders.FloatProp? getOffset();
}
+ public static final class ColorBuilders.ColorStop.Builder {
+ ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public ColorBuilders.ColorStop.Builder(androidx.wear.protolayout.ColorBuilders.ColorProp);
+ method public androidx.wear.protolayout.ColorBuilders.ColorStop build();
+ method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public androidx.wear.protolayout.ColorBuilders.ColorStop.Builder setOffset(androidx.wear.protolayout.TypeBuilders.FloatProp);
+ }
+
@androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public static final class ColorBuilders.SweepGradient implements androidx.wear.protolayout.ColorBuilders.Brush {
method public java.util.List<androidx.wear.protolayout.ColorBuilders.ColorStop!> getColorStops();
method public androidx.wear.protolayout.DimensionBuilders.DegreesProp getEndAngle();
diff --git a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ColorBuilders.java b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ColorBuilders.java
index c8c3b1c..f14f152 100644
--- a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ColorBuilders.java
+++ b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ColorBuilders.java
@@ -217,38 +217,6 @@
}
}
- /**
- * Constructor for {@link ColorStop}.
- *
- * <p>When all {@link ColorStop} in a Gradient have no offset, the colors are evenly
- * distributed in the gradient.
- *
- * @param color the color for this stop.
- * <p>Note that this parameter only supports static values.
- */
- @RequiresSchemaVersion(major = 1, minor = 300)
- public ColorStop(@NonNull ColorProp color) {
- ColorStop inst = new Builder().setColor(color).build();
- this.mImpl = inst.mImpl;
- this.mFingerprint = inst.mFingerprint;
- }
-
- /**
- * Constructor for {@link ColorStop}.
- *
- * <p>Note that all parameters only support static values.
- *
- * @param color the color for this stop.
- * @param offset the relative offset for this color, between 0 and 1. This determines where
- * the color is positioned relative to a gradient space.
- */
- @RequiresSchemaVersion(major = 1, minor = 300)
- public ColorStop(@NonNull ColorProp color, @NonNull FloatProp offset) {
- ColorStop inst = new Builder().setColor(color).setOffset(offset).build();
- this.mImpl = inst.mImpl;
- this.mFingerprint = inst.mFingerprint;
- }
-
/** Get the fingerprint for this object, or null if unknown. */
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
@@ -283,13 +251,10 @@
}
/** Builder for {@link ColorStop} */
- static final class Builder {
+ public static final class Builder {
private final ColorProto.ColorStop.Builder mImpl = ColorProto.ColorStop.newBuilder();
private final Fingerprint mFingerprint = new Fingerprint(-468737254);
- /** Creates an instance of {@link Builder}. */
- public Builder() {}
-
/**
* Sets the color for this stop. Only opaque colors are supported. Any transparent
* colors will have their alpha component set to 0xFF (opaque).
@@ -298,7 +263,7 @@
*/
@RequiresSchemaVersion(major = 1, minor = 300)
@NonNull
- Builder setColor(@NonNull ColorProp color) {
+ private Builder setColor(@NonNull ColorProp color) {
if (color.getDynamicValue() != null) {
throw new IllegalArgumentException(
"ColorStop.Builder.setColor doesn't support dynamic values.");
@@ -319,7 +284,7 @@
*/
@RequiresSchemaVersion(major = 1, minor = 300)
@NonNull
- Builder setOffset(@NonNull FloatProp offset) {
+ public Builder setOffset(@NonNull FloatProp offset) {
if (offset.getDynamicValue() != null) {
throw new IllegalArgumentException(
"ColorStop.Builder.setOffset doesn't support dynamic values.");
@@ -335,6 +300,21 @@
return this;
}
+ /**
+ * Creates an instance of {@link Builder}.
+ *
+ * <p>If all {@link ColorStop} in a Gradient have no offset, the colors are evenly
+ * distributed in the gradient.
+ *
+ * @param color the color for this stop. Only opaque colors are supported. Any
+ * transparent colors will have their alpha component set to 0xFF (opaque). Note
+ * that this parameter only supports static values.
+ */
+ @RequiresSchemaVersion(major = 1, minor = 300)
+ public Builder(@NonNull ColorProp color) {
+ this.setColor(color);
+ }
+
/** Builds an instance from accumulated values. */
@NonNull
public ColorStop build() {
diff --git a/wear/watchface/watchface-complications-data/api/current.txt b/wear/watchface/watchface-complications-data/api/current.txt
index 74a2166..8f499f5 100644
--- a/wear/watchface/watchface-complications-data/api/current.txt
+++ b/wear/watchface/watchface-complications-data/api/current.txt
@@ -93,6 +93,7 @@
}
@RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final class DynamicComplicationText implements androidx.wear.watchface.complications.data.ComplicationText {
+ ctor @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public DynamicComplicationText(androidx.wear.protolayout.expression.DynamicBuilders.DynamicString dynamicValue);
ctor public DynamicComplicationText(androidx.wear.protolayout.expression.DynamicBuilders.DynamicString dynamicValue, CharSequence fallbackValue);
method public androidx.wear.protolayout.expression.DynamicBuilders.DynamicString getDynamicValue();
method public CharSequence getFallbackValue();
@@ -352,6 +353,7 @@
}
public static final class RangedValueComplicationData.Builder {
+ ctor @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public RangedValueComplicationData.Builder(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat dynamicValue, float min, float max, androidx.wear.watchface.complications.data.ComplicationText contentDescription);
ctor @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public RangedValueComplicationData.Builder(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat dynamicValue, float fallbackValue, float min, float max, androidx.wear.watchface.complications.data.ComplicationText contentDescription);
ctor public RangedValueComplicationData.Builder(float value, float min, float max, androidx.wear.watchface.complications.data.ComplicationText contentDescription);
method public androidx.wear.watchface.complications.data.RangedValueComplicationData build();
diff --git a/wear/watchface/watchface-complications-data/api/restricted_current.txt b/wear/watchface/watchface-complications-data/api/restricted_current.txt
index 74a2166..8f499f5 100644
--- a/wear/watchface/watchface-complications-data/api/restricted_current.txt
+++ b/wear/watchface/watchface-complications-data/api/restricted_current.txt
@@ -93,6 +93,7 @@
}
@RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final class DynamicComplicationText implements androidx.wear.watchface.complications.data.ComplicationText {
+ ctor @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public DynamicComplicationText(androidx.wear.protolayout.expression.DynamicBuilders.DynamicString dynamicValue);
ctor public DynamicComplicationText(androidx.wear.protolayout.expression.DynamicBuilders.DynamicString dynamicValue, CharSequence fallbackValue);
method public androidx.wear.protolayout.expression.DynamicBuilders.DynamicString getDynamicValue();
method public CharSequence getFallbackValue();
@@ -352,6 +353,7 @@
}
public static final class RangedValueComplicationData.Builder {
+ ctor @RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public RangedValueComplicationData.Builder(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat dynamicValue, float min, float max, androidx.wear.watchface.complications.data.ComplicationText contentDescription);
ctor @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public RangedValueComplicationData.Builder(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat dynamicValue, float fallbackValue, float min, float max, androidx.wear.watchface.complications.data.ComplicationText contentDescription);
ctor public RangedValueComplicationData.Builder(float value, float min, float max, androidx.wear.watchface.complications.data.ComplicationText contentDescription);
method public androidx.wear.watchface.complications.data.RangedValueComplicationData build();
diff --git a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Data.kt b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Data.kt
index ea4cc25..29cf91f 100644
--- a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Data.kt
+++ b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Data.kt
@@ -325,8 +325,8 @@
placeholder?.tapAction,
cachedWireComplicationData,
dataSource = null,
- persistencePolicy =
- placeholder?.persistencePolicy ?: ComplicationPersistencePolicies.CACHING_ALLOWED,
+ persistencePolicy = placeholder?.persistencePolicy
+ ?: ComplicationPersistencePolicies.CACHING_ALLOWED,
displayPolicy = placeholder?.displayPolicy ?: ComplicationDisplayPolicies.ALWAYS_DISPLAY,
dynamicValueInvalidationFallback = placeholder,
) {
@@ -1041,6 +1041,9 @@
* don't support [dynamicValue], which should be in the range [[min]] .. [[max]]. The
* semantic meaning of value can be specified via [setValueType].
*
+ * This is only relevant before [Build.VERSION_CODES.UPSIDE_DOWN_CAKE], use the
+ * no-fallback constructor if you target an equal or higher API level.
+ *
* IMPORTANT: This is only used when the system does not support [dynamicValue] _at all_.
* See [setDynamicValueInvalidationFallback] for the situation where [dynamicValue] cannot
* be evaluated, e.g. when a data source is not available.
@@ -1060,7 +1063,30 @@
min: Float,
max: Float,
contentDescription: ComplicationText
- ) : this(fallbackValue, dynamicValue, min, max, contentDescription)
+ ) : this(value = fallbackValue, dynamicValue, min = min, max = max, contentDescription)
+
+ /**
+ * Creates a [Builder] for a [RangedValueComplicationData] with a [DynamicFloat] value, and
+ * no `fallbackValue` for API levels known to support dynamic values.
+ *
+ * @param dynamicValue The [DynamicFloat] of the ranged complication which will be evaluated
+ * into a value dynamically, and should be in the range [[min]] .. [[max]]. The semantic
+ * meaning of value can be specified via [setValueType].
+ * @param min The minimum value. For [TYPE_PERCENTAGE] this must be 0f.
+ * @param max The maximum value. This must be less than [Float.MAX_VALUE]. For
+ * [TYPE_PERCENTAGE] this must be 0f.
+ * @param contentDescription Defines localized text that briefly describes content of the
+ * complication. This property is used primarily for accessibility. Since some
+ * complications do not have textual representation this attribute can be used for
+ * providing such. Please do not include the word 'complication' in the description.
+ */
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public constructor(
+ dynamicValue: DynamicFloat,
+ min: Float,
+ max: Float,
+ contentDescription: ComplicationText
+ ) : this(value = min, dynamicValue, min = min, max = max, contentDescription)
private var tapAction: PendingIntent? = null
private var validTimeRange: TimeRange? = null
@@ -1165,7 +1191,7 @@
override fun getContentDescription(context: Context): TimeDependentText? =
_contentDescription?.emptyToNull()?.toWireComplicationText()
?: ComplicationTextTemplate.Builder().addTextAndTitle(text, title).buildOrNull()
- ?: WireComplicationText(context.getString(R.string.a11y_template_range, value, max))
+ ?: WireComplicationText(context.getString(R.string.a11y_template_range, value, max))
/** The content description field for accessibility. */
val contentDescription: ComplicationText? = _contentDescription ?: ComplicationText.EMPTY
@@ -1391,6 +1417,9 @@
* @param fallbackValue The fallback value of the goal complication which will be used on
* systems that don't support [dynamicValue], and should be >= 0.
*
+ * This is only relevant before [Build.VERSION_CODES.UPSIDE_DOWN_CAKE], use the
+ * no-fallback constructor if you target an equal or higher API level.
+ *
* IMPORTANT: This is only used when the system does not support [dynamicValue] _at all_.
* See [setDynamicValueInvalidationFallback] for the situation where the [dynamicValue]
* cannot be evaluated, e.g. when a data source is not available.
@@ -1497,7 +1526,7 @@
override fun getContentDescription(context: Context): TimeDependentText? =
_contentDescription?.emptyToNull()?.toWireComplicationText()
?: ComplicationTextTemplate.Builder().addTextAndTitle(text, title).buildOrNull()
- ?: WireComplicationText(
+ ?: WireComplicationText(
context.getString(R.string.a11y_template_range, value, targetValue)
)
diff --git a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Text.kt b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Text.kt
index 3dff985..47f00bf 100644
--- a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Text.kt
+++ b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Text.kt
@@ -614,6 +614,9 @@
* @param dynamicValue The [DynamicString] which will be evaluated into a value dynamically.
* @param fallbackValue Used when the system does not support [dynamicValue].
*
+ * This is only relevant before [Build.VERSION_CODES.UPSIDE_DOWN_CAKE], use the no-fallback
+ * constructor if you target an equal or higher API level.
+ *
* IMPORTANT: This is only used when the system does not support [dynamicValue] _at all_. See
* [ComplicationData.dynamicValueInvalidationFallback] for the situation where the [dynamicValue]
* cannot be evaluated, e.g. when a data source is not available.
@@ -623,6 +626,13 @@
public val dynamicValue: DynamicString,
public val fallbackValue: CharSequence,
) : ComplicationText {
+ /**
+ * Creates a [DynamicComplicationText] with no [fallbackValue] for API levels that are known to
+ * support dynamic values.
+ */
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public constructor(dynamicValue: DynamicString) : this(dynamicValue, "")
+
private val delegate =
DelegatingComplicationText(WireComplicationText(fallbackValue, dynamicValue))
diff --git a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/AssetLoaderAjaxActivity.java b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/AssetLoaderAjaxActivity.java
index 13eb584..56e3f0b 100644
--- a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/AssetLoaderAjaxActivity.java
+++ b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/AssetLoaderAjaxActivity.java
@@ -53,6 +53,7 @@
mUriIdlingResource = uriIdlingResource;
}
+ /** @noinspection RedundantSuppression*/
@Override
@SuppressWarnings("deprecation") // use the old one for compatibility with all API levels.
public boolean shouldOverrideUrlLoading(WebView view, String url) {
@@ -70,6 +71,7 @@
return response;
}
+ /** @noinspection RedundantSuppression*/
@Override
@SuppressWarnings("deprecation") // use the old one for compatibility with all API levels.
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
@@ -117,9 +119,7 @@
WebSettings webViewSettings = mWebView.getSettings();
webViewSettings.setJavaScriptEnabled(true);
- // Setting this off for security. Off by default for SDK versions >= 16.
- webViewSettings.setAllowFileAccessFromFileURLs(false);
- webViewSettings.setAllowUniversalAccessFromFileURLs(false);
+ setDeprecatedAllowFileAccess(webViewSettings);
// Keeping these off is less critical but still a good idea, especially
// if your app is not using file:// or content:// URLs.
webViewSettings.setAllowFileAccess(false);
@@ -129,6 +129,14 @@
loadButton.setOnClickListener(v -> loadUrl());
}
+ /** @noinspection RedundantSuppression*/
+ @SuppressWarnings("deprecation") /* b/180503860 */
+ private static void setDeprecatedAllowFileAccess(WebSettings webViewSettings) {
+ // Setting this off for security. Off by default for SDK versions >= 16.
+ webViewSettings.setAllowFileAccessFromFileURLs(false);
+ webViewSettings.setAllowUniversalAccessFromFileURLs(false);
+ }
+
/**
* Load the url https://example.com/androidx_webkit/example/assets/www/ajax_requests.html.
*/
diff --git a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/AssetLoaderInternalStorageActivity.java b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/AssetLoaderInternalStorageActivity.java
index 5f1cef3..6ff87a2 100644
--- a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/AssetLoaderInternalStorageActivity.java
+++ b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/AssetLoaderInternalStorageActivity.java
@@ -54,6 +54,7 @@
mAssetLoader = assetLoader;
}
+ /** @noinspection RedundantSuppression*/
@Override
@SuppressWarnings("deprecation") // use the old one for compatibility with all API levels.
public boolean shouldOverrideUrlLoading(WebView view, String url) {
@@ -67,6 +68,7 @@
return mAssetLoader.shouldInterceptRequest(Api21Impl.getUrl(request));
}
+ /** @noinspection RedundantSuppression*/
@Override
@SuppressWarnings("deprecation") // use the old one for compatibility with all API levels.
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
@@ -75,7 +77,6 @@
}
@Override
- @SuppressWarnings("deprecation") /* AsyncTask */
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -96,6 +97,11 @@
webView.setWebViewClient(new MyWebViewClient(assetLoader));
// Write the demo file asynchronously and then load the file after it's written.
+ writeFileInBackgroundAndShowInWebView(webView);
+ }
+
+ @SuppressWarnings("deprecation") /* b/180503860 */
+ private void writeFileInBackgroundAndShowInWebView(WebView webView) {
new WriteFileTask(webView, mDemoFile, DEMO_HTML_CONTENT).execute();
}
@@ -108,6 +114,7 @@
}
// Writes to file asynchronously in the background thread.
+ @SuppressWarnings("deprecation") /* b/180503860 */
private static class WriteFileTask extends android.os.AsyncTask<Void, Void, Void> {
@NonNull private final WebView mWebView;
@NonNull private final File mFile;
diff --git a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ForceDarkActivity.java b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ForceDarkActivity.java
index afa726a..5331d1f 100644
--- a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ForceDarkActivity.java
+++ b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ForceDarkActivity.java
@@ -37,6 +37,7 @@
private static final String DESCRIPTION =
"<html><body><h1>Force Dark Mode is %s </h1></body></html>";
+ @SuppressWarnings("deprecation") /* b/180503860 */
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -57,6 +58,7 @@
}
}
+ @SuppressWarnings("deprecation") /* b/180503860 */
private void setupWebView(WebView webView, int forceDarkMode) {
webView.setWebViewClient(new WebViewClient());
String formattedDescription;
diff --git a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ForceDarkStrategyActivity.java b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ForceDarkStrategyActivity.java
index f251d13..e6d53d4 100644
--- a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ForceDarkStrategyActivity.java
+++ b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ForceDarkStrategyActivity.java
@@ -101,6 +101,7 @@
+ "</html>"
).getBytes(), Base64.NO_PADDING);
+ @SuppressWarnings("deprecation") /* b/180503860 */
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
diff --git a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/FullscreenActivity.java b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/FullscreenActivity.java
index fce79c1..837ab2f 100644
--- a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/FullscreenActivity.java
+++ b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/FullscreenActivity.java
@@ -66,24 +66,36 @@
// At this point, the WebView is no longer drawing the content. We should cover it up
// with the new View.
mFullScreenView = view;
- mWindow.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ addDeprecatedFullScreenFlag();
mWindow.addContentView(mFullScreenView,
new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
mCustomViewCallback = callback;
}
+ /** @noinspection RedundantSuppression*/
+ @SuppressWarnings("deprecation") /* b/180503860 */
+ private void addDeprecatedFullScreenFlag() {
+ mWindow.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ }
+
@Override
public void onHideCustomView() {
// At this point, mFullScreenView is no longer drawing content. Remove this from the
// layout to show the underlying WebView, and remove the reference to the View so it can
// be GC'ed.
- mWindow.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ clearDeprecatedFullScreenFlag();
((ViewGroup) mFullScreenView.getParent()).removeView(mFullScreenView);
mFullScreenView = null;
mCustomViewCallback = null;
}
+ /** @noinspection RedundantSuppression*/
+ @SuppressWarnings("deprecation") /* b/180503860 */
+ private void clearDeprecatedFullScreenFlag() {
+ mWindow.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ }
+
/* package */ void exitFullScreen() {
if (mCustomViewCallback == null) return;
mCustomViewCallback.onCustomViewHidden();