Merge "Fix flaky room-paging test loadEverything_inReverse" into androidx-main
diff --git a/activity/activity-compose/build.gradle b/activity/activity-compose/build.gradle
index b89c2b2..8ef2c0b 100644
--- a/activity/activity-compose/build.gradle
+++ b/activity/activity-compose/build.gradle
@@ -37,7 +37,7 @@
// Outside of androidx this is resolved via constraint added to lifecycle-common,
// but it doesn't work in androidx.
// See aosp/1804059
- implementation(project(":lifecycle:lifecycle-common-java8"))
+ implementation("androidx.lifecycle:lifecycle-common-java8:2.5.0-rc01")
androidTestImplementation projectOrArtifact(":compose:ui:ui-test-junit4")
androidTestImplementation projectOrArtifact(":compose:material:material")
diff --git a/activity/activity-ktx/build.gradle b/activity/activity-ktx/build.gradle
index 8c0d82b..21e4e1a 100644
--- a/activity/activity-ktx/build.gradle
+++ b/activity/activity-ktx/build.gradle
@@ -28,11 +28,11 @@
api("androidx.core:core-ktx:1.1.0") {
because "Mirror activity dependency graph for -ktx artifacts"
}
- api(projectOrArtifact(":lifecycle:lifecycle-runtime-ktx")) {
+ api("androidx.lifecycle:lifecycle-runtime-ktx:2.5.0-rc01") {
because 'Mirror activity dependency graph for -ktx artifacts'
}
- api(projectOrArtifact(":lifecycle:lifecycle-viewmodel-ktx"))
- api(projectOrArtifact(":savedstate:savedstate-ktx")) {
+ api("androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0-rc01")
+ api("androidx.savedstate:savedstate-ktx:1.2.0-rc01") {
because 'Mirror activity dependency graph for -ktx artifacts'
}
api(libs.kotlinStdlib)
diff --git a/activity/activity/build.gradle b/activity/activity/build.gradle
index f30a135..91a8e4d 100644
--- a/activity/activity/build.gradle
+++ b/activity/activity/build.gradle
@@ -17,11 +17,11 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
implementation("androidx.collection:collection:1.0.0")
- api(projectOrArtifact(":core:core"))
- api(projectOrArtifact(":lifecycle:lifecycle-runtime"))
- api(projectOrArtifact(":lifecycle:lifecycle-viewmodel"))
- api(projectOrArtifact(":savedstate:savedstate"))
- api(projectOrArtifact(":lifecycle:lifecycle-viewmodel-savedstate"))
+ api("androidx.core:core:1.8.0-rc01")
+ api("androidx.lifecycle:lifecycle-runtime:2.5.0-rc01")
+ api("androidx.lifecycle:lifecycle-viewmodel:2.5.0-rc01")
+ api("androidx.savedstate:savedstate:1.2.0-rc01")
+ api("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.5.0-rc01")
implementation("androidx.tracing:tracing:1.0.0")
api(libs.kotlinStdlib)
diff --git a/appcompat/appcompat/build.gradle b/appcompat/appcompat/build.gradle
index ca45019..06820cb 100644
--- a/appcompat/appcompat/build.gradle
+++ b/appcompat/appcompat/build.gradle
@@ -11,8 +11,8 @@
api("androidx.annotation:annotation:1.3.0")
api(project(":core:core"))
- implementation(project(":emoji2:emoji2"))
- implementation(project(":emoji2:emoji2-views-helper"))
+ implementation("androidx.emoji2:emoji2:1.2.0-alpha04")
+ implementation("androidx.emoji2:emoji2-views-helper:1.2.0-alpha04")
implementation("androidx.collection:collection:1.0.0")
api("androidx.cursoradapter:cursoradapter:1.0.0")
api(project(":activity:activity"))
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/playground/VerifyPlaygroundGradleConfigurationTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/playground/VerifyPlaygroundGradleConfigurationTask.kt
index 21d5323..1fff94c 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/playground/VerifyPlaygroundGradleConfigurationTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/playground/VerifyPlaygroundGradleConfigurationTask.kt
@@ -17,6 +17,8 @@
package androidx.build.playground
import com.google.common.annotations.VisibleForTesting
+import java.io.File
+import java.util.Properties
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.Project
@@ -25,8 +27,6 @@
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.TaskProvider
-import java.io.File
-import java.util.Properties
/**
* Compares the playground Gradle configuration with the main androidx Gradle configuration
@@ -71,10 +71,12 @@
playgroundGradleWrapper.get().asFile
)
if (androidxGradleVersion != playgroundGradleVersion) {
- throw GradleException("""
+ throw GradleException(
+ """
Playground gradle version ($playgroundGradleVersion) must match the AndroidX main
build gradle version ($androidxGradleVersion).
- """.trimIndent())
+ """.trimIndent()
+ )
}
}
@@ -99,15 +101,26 @@
// this includes properties that are not defined in the root androidx build as they might
// be properties which can alter the build output. We might consider allow listing certain
// properties in the future if necessary.
- playgroundProperties.forEach {
- val rootValue = rootProperties[it.key]
- if (rootValue != it.value && it.key != "org.gradle.jvmargs") {
+ val propertyKeys = rootProperties.keys + playgroundProperties.keys
+ propertyKeys.forEach { key ->
+ val rootValue = rootProperties[key]
+ val playgroundValue = playgroundProperties[key]
+
+ if (rootValue != playgroundValue &&
+ !ignoredProperties.contains(key) &&
+ exceptedProperties[key] != playgroundValue
+ ) {
throw GradleException(
"""
- ${it.key} is defined as ${it.value} in playground properties but
- it does not match the value defined in root properties file ($rootValue).
- Having inconsistent properties in playground projects might trigger wrong
- compilation output in the main AndroidX build, thus not allowed.
+ $key is defined in ${androidxProperties.get().asFile.absolutePath} as
+ $rootValue, which differs from $playgroundValue defined in
+ ${this.playgroundProperties.get().asFile.absolutePath}. If this change is
+ intentional, you can ignore it by adding it to ignoredProperties in
+ VerifyPlaygroundGradleConfigurationTask.kt
+
+ Note: Having inconsistent properties in playground projects might trigger wrong
+ compilation output in the main AndroidX build, so if a property is defined in
+ playground properties, its value **MUST** match that of regular AndroidX build.
""".trimIndent()
)
}
@@ -123,6 +136,21 @@
companion object {
private const val TASK_NAME = "verifyPlaygroundGradleConfiguration"
+ // A mapping of the expected override in playground, which should generally follow AOSP on
+ // androidx-main. Generally, should only be used for conflicting properties which have
+ // different values in different built targets on AOSP, but still should be declared in
+ // playground.
+ private val exceptedProperties = mapOf(
+ "androidx.writeVersionedApiFiles" to "true",
+ )
+
+ private val ignoredProperties = setOf(
+ "org.gradle.jvmargs",
+ "org.gradle.daemon",
+ "android.builder.sdkDownload",
+ "android.suppressUnsupportedCompileSdk",
+ )
+
/**
* Regular expression to extract the gradle version from a distributionUrl property.
* Sample input looks like: <some-path>/gradle-7.3-rc-2-all.zip
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/util/ArrayRingBuffer.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/util/ArrayRingBuffer.java
new file mode 100644
index 0000000..aca7060
--- /dev/null
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/util/ArrayRingBuffer.java
@@ -0,0 +1,66 @@
+/*
+ * 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.camera.camera2.internal.util;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayDeque;
+
+/**
+ * Implements {@link RingBuffer} with an {@link ArrayDeque}.
+ *
+ * @param <T> the type of elements stored in the RingBuffer.
+ */
+public class ArrayRingBuffer<T> implements RingBuffer<T> {
+
+ private final int mRingBufferCapacity;
+ private final ArrayDeque<T> mBuffer;
+ @Nullable final OnRemoveCallback<T> mOnRemoveCallback;
+
+ public ArrayRingBuffer(int ringBufferCapacity) {
+ this(ringBufferCapacity, null);
+ }
+
+ public ArrayRingBuffer(int ringBufferCapacity, @Nullable OnRemoveCallback<T> onRemoveCallback) {
+ mRingBufferCapacity = ringBufferCapacity;
+ mBuffer = new ArrayDeque<>(mRingBufferCapacity);
+ mOnRemoveCallback = onRemoveCallback;
+ }
+
+ @Override
+ public void enqueue(@NonNull T element) {
+ if (mBuffer.size() >= mRingBufferCapacity) {
+ T removedItem = this.dequeue();
+ if (mOnRemoveCallback != null) {
+ mOnRemoveCallback.onRemove(removedItem);
+ }
+ }
+ mBuffer.addFirst(element);
+ }
+
+ @Override
+ public @NonNull T dequeue() {
+ T removedElement = mBuffer.removeLast();
+ return removedElement;
+ }
+
+ @Override
+ public int getMaxCapacity() {
+ return mRingBufferCapacity;
+ }
+}
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/util/ArrayRingBufferTest.kt b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/util/ArrayRingBufferTest.kt
new file mode 100644
index 0000000..d147aa7
--- /dev/null
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/util/ArrayRingBufferTest.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.camera.camera2.internal.util
+
+import androidx.camera.camera2.internal.util.RingBuffer.OnRemoveCallback
+import androidx.testutils.assertThrows
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.any
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+@RunWith(JUnit4::class)
+class ArrayRingBufferTest {
+
+ @Test
+ fun testEnqueue() {
+ val testBuffer: RingBuffer<Int> =
+ androidx.camera.camera2.internal.util.ArrayRingBuffer(3)
+ testBuffer.enqueue(1)
+ testBuffer.enqueue(2)
+ testBuffer.enqueue(3)
+ testBuffer.enqueue(4)
+ assertThat(testBuffer.dequeue()).isEqualTo(2)
+ }
+
+ @Test
+ fun testDequeue_correctValueIsDequeued() {
+ @Suppress("UNCHECKED_CAST")
+ val mockCallback: OnRemoveCallback<Int> = mock(
+ OnRemoveCallback::class.java) as OnRemoveCallback<Int>
+
+ val testBuffer: RingBuffer<Int> =
+ androidx.camera.camera2.internal.util.ArrayRingBuffer(
+ 3,
+ mockCallback
+ )
+ testBuffer.enqueue(1)
+ testBuffer.enqueue(2)
+ testBuffer.enqueue(3)
+ assertThat(testBuffer.dequeue()).isEqualTo(1)
+ verify(mockCallback, times(0)).onRemove(any())
+ }
+
+ @Test
+ fun testDequeue_OnRemoveCallbackCalledOnlyWhenDiscardingItemsDueToCapacity() {
+ @Suppress("UNCHECKED_CAST")
+ val mockCallback: OnRemoveCallback<Int> = mock(
+ OnRemoveCallback::class.java) as OnRemoveCallback<Int>
+
+ val testBuffer: RingBuffer<Int> =
+ androidx.camera.camera2.internal.util.ArrayRingBuffer(
+ 3,
+ mockCallback
+ )
+ testBuffer.enqueue(1)
+ testBuffer.enqueue(2)
+ testBuffer.enqueue(3)
+ testBuffer.enqueue(4)
+ verify(mockCallback).onRemove(1)
+ assertThat(testBuffer.dequeue()).isEqualTo(2)
+ verify(mockCallback, times(1)).onRemove(any())
+ }
+
+ @Test()
+ fun testDequeue_exceptionThrownWhenBufferEmpty() {
+ val testBuffer: RingBuffer<Int> =
+ androidx.camera.camera2.internal.util.ArrayRingBuffer(5)
+ assertThrows(NoSuchElementException::class.java, testBuffer::dequeue)
+ }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/util/ZslRingBuffer.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/util/ZslRingBuffer.java
new file mode 100644
index 0000000..0b70d28
--- /dev/null
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/util/ZslRingBuffer.java
@@ -0,0 +1,76 @@
+/*
+ * 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.camera.camera2.internal.util;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.ImageInfo;
+import androidx.camera.core.ImageProxy;
+import androidx.camera.core.impl.CameraCaptureMetaData.AeState;
+import androidx.camera.core.impl.CameraCaptureMetaData.AfState;
+import androidx.camera.core.impl.CameraCaptureMetaData.AwbState;
+import androidx.camera.core.impl.CameraCaptureResult;
+import androidx.camera.core.impl.CameraCaptureResults;
+
+/**
+ * Used for storing frames for ZSL capture.
+ *
+ * <p>Enqueueing process ignores frames when the quality is inadequate for ZSL capture.</p>
+ *
+ * <p>Adequate quality is defined as:
+ * - AF Focused
+ * - AE Converged
+ * - AWB Converged
+ * </p>
+ */
+@RequiresApi(21)
+public final class ZslRingBuffer extends ArrayRingBuffer<ImageProxy> {
+
+ public ZslRingBuffer(int ringBufferCapacity,
+ @NonNull OnRemoveCallback<ImageProxy> onRemoveCallback) {
+ super(ringBufferCapacity, onRemoveCallback);
+ }
+
+ @Override
+ public void enqueue(@NonNull ImageProxy imageProxy) {
+ if (isValidZslFrame(imageProxy.getImageInfo())) {
+ super.enqueue(imageProxy);
+ } else {
+ mOnRemoveCallback.onRemove(imageProxy);
+ }
+ }
+
+ private boolean isValidZslFrame(@NonNull ImageInfo imageInfo) {
+ CameraCaptureResult cameraCaptureResult =
+ CameraCaptureResults.retrieveCameraCaptureResult(imageInfo);
+
+ if (cameraCaptureResult.getAfState() != AfState.LOCKED_FOCUSED
+ && cameraCaptureResult.getAfState() != AfState.PASSIVE_FOCUSED) {
+ return false;
+ }
+
+ if (cameraCaptureResult.getAeState() != AeState.CONVERGED) {
+ return false;
+ }
+
+ if (cameraCaptureResult.getAwbState() != AwbState.CONVERGED) {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/util/ZslRingBufferTest.kt b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/util/ZslRingBufferTest.kt
new file mode 100644
index 0000000..4fcaff0
--- /dev/null
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/util/ZslRingBufferTest.kt
@@ -0,0 +1,109 @@
+/*
+ * 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.camera.camera2.internal.util
+
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.camera.core.ImageInfo
+import androidx.camera.core.ImageProxy
+import androidx.camera.core.impl.CameraCaptureMetaData.AeState
+import androidx.camera.core.impl.CameraCaptureMetaData.AfState
+import androidx.camera.core.impl.CameraCaptureMetaData.AwbState
+import androidx.camera.core.impl.CameraCaptureResult
+import androidx.camera.core.internal.CameraCaptureResultImageInfo
+import androidx.camera.camera2.internal.util.RingBuffer.OnRemoveCallback
+import androidx.camera.testing.fakes.FakeImageProxy
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.any
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+@RunWith(JUnit4::class)
+@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+class ZslRingBufferTest {
+
+ lateinit var mMockedCameraCaptureResult: CameraCaptureResult
+
+ @Before
+ fun setup() {
+ mMockedCameraCaptureResult = mock(CameraCaptureResult::class.java)
+ `when`(mMockedCameraCaptureResult.aeState).thenReturn(AeState.CONVERGED)
+ `when`(mMockedCameraCaptureResult.afState).thenReturn(AfState.LOCKED_FOCUSED)
+ `when`(mMockedCameraCaptureResult.awbState).thenReturn(AwbState.CONVERGED)
+ }
+
+ @Test
+ fun enqueue_ensureOldFramesAreRemoved() {
+ @Suppress("UNCHECKED_CAST")
+ val onRemoveCallback = mock(OnRemoveCallback::class.java) as OnRemoveCallback<ImageProxy>
+ val ringBuffer = androidx.camera.camera2.internal.util.ZslRingBuffer(
+ 2,
+ onRemoveCallback
+ )
+
+ val imageInfo: ImageInfo = CameraCaptureResultImageInfo(mMockedCameraCaptureResult)
+
+ val imageProxy1 = FakeImageProxy(imageInfo)
+ ringBuffer.enqueue(imageProxy1)
+ ringBuffer.enqueue(FakeImageProxy(imageInfo))
+ ringBuffer.enqueue(FakeImageProxy(imageInfo))
+
+ verify(onRemoveCallback).onRemove(imageProxy1)
+ verify(onRemoveCallback, times(1)).onRemove(any())
+ }
+
+ @Test
+ fun enqueue_framesWithBad3AStatesNotQueued() {
+ @Suppress("UNCHECKED_CAST")
+ val onRemoveCallback = mock(OnRemoveCallback::class.java) as OnRemoveCallback<ImageProxy>
+ val ringBuffer = androidx.camera.camera2.internal.util.ZslRingBuffer(
+ 2,
+ onRemoveCallback
+ )
+
+ val imageInfo: ImageInfo = CameraCaptureResultImageInfo(mMockedCameraCaptureResult)
+
+ val imageProxy1 = FakeImageProxy(imageInfo)
+ ringBuffer.enqueue(imageProxy1)
+
+ `when`(mMockedCameraCaptureResult.aeState).thenReturn(AeState.SEARCHING)
+ val imageProxy2 = FakeImageProxy(imageInfo)
+ ringBuffer.enqueue(imageProxy2)
+ verify(onRemoveCallback, times(1)).onRemove(imageProxy2)
+ `when`(mMockedCameraCaptureResult.aeState).thenReturn(AeState.CONVERGED)
+
+ `when`(mMockedCameraCaptureResult.afState).thenReturn(AfState.PASSIVE_NOT_FOCUSED)
+ val imageProxy3 = FakeImageProxy(imageInfo)
+ ringBuffer.enqueue(imageProxy3)
+ verify(onRemoveCallback, times(1)).onRemove(imageProxy3)
+ `when`(mMockedCameraCaptureResult.afState).thenReturn(AfState.PASSIVE_NOT_FOCUSED)
+
+ `when`(mMockedCameraCaptureResult.awbState).thenReturn(AwbState.METERING)
+ val imageProxy4 = FakeImageProxy(imageInfo)
+ ringBuffer.enqueue(imageProxy4)
+ verify(onRemoveCallback, times(1)).onRemove(imageProxy4)
+ `when`(mMockedCameraCaptureResult.awbState).thenReturn(AwbState.CONVERGED)
+
+ verify(onRemoveCallback, times(3)).onRemove(any())
+ assertThat(ringBuffer.dequeue()).isEqualTo(imageProxy1)
+ }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java
index bbbe4d1..d0b4e82 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java
@@ -38,7 +38,7 @@
import androidx.camera.camera2.internal.compat.CameraManagerCompat;
import androidx.camera.camera2.internal.compat.workaround.ExcludedSupportedSizesContainer;
import androidx.camera.camera2.internal.compat.workaround.ExtraSupportedSurfaceCombinationsContainer;
-import androidx.camera.camera2.internal.compat.workaround.ResolutionSelector;
+import androidx.camera.camera2.internal.compat.workaround.ResolutionCorrector;
import androidx.camera.camera2.internal.compat.workaround.TargetAspectRatio;
import androidx.camera.core.AspectRatio;
import androidx.camera.core.CameraUnavailableException;
@@ -101,7 +101,7 @@
private Map<Integer, Size[]> mOutputSizesCache = new HashMap<>();
@NonNull
private final DisplayInfoManager mDisplayInfoManager;
- private final ResolutionSelector mResolutionSelector = new ResolutionSelector();
+ private final ResolutionCorrector mResolutionCorrector = new ResolutionCorrector();
SupportedSurfaceCombination(@NonNull Context context, @NonNull String cameraId,
@NonNull CameraManagerCompat cameraManagerCompat,
@@ -478,7 +478,7 @@
}
}
- supportedResolutions = mResolutionSelector.insertOrPrioritize(
+ supportedResolutions = mResolutionCorrector.insertOrPrioritize(
getConfigType(config.getInputFormat()),
supportedResolutions);
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/ResolutionSelector.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/ResolutionCorrector.java
similarity index 91%
rename from camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/ResolutionSelector.java
rename to camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/ResolutionCorrector.java
index 8812500..467ceb8 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/ResolutionSelector.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/ResolutionCorrector.java
@@ -34,23 +34,23 @@
* quirks.
*/
@RequiresApi(21)
-public class ResolutionSelector {
+public class ResolutionCorrector {
@Nullable
private final ExtraCroppingQuirk mExtraCroppingQuirk;
/**
- * Constructs new {@link ResolutionSelector}.
+ * Constructs new {@link ResolutionCorrector}.
*/
- public ResolutionSelector() {
+ public ResolutionCorrector() {
this(DeviceQuirks.get(ExtraCroppingQuirk.class));
}
/**
- * Constructs new {@link ResolutionSelector}.
+ * Constructs new {@link ResolutionCorrector}.
*/
@VisibleForTesting
- ResolutionSelector(@Nullable ExtraCroppingQuirk extraCroppingQuirk) {
+ ResolutionCorrector(@Nullable ExtraCroppingQuirk extraCroppingQuirk) {
mExtraCroppingQuirk = extraCroppingQuirk;
}
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/quirk/ResolutionSelectorQuirkTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/quirk/ResolutionCorrectorQuirkTest.java
similarity index 97%
rename from camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/quirk/ResolutionSelectorQuirkTest.java
rename to camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/quirk/ResolutionCorrectorQuirkTest.java
index e1ce2e4..6faf911 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/quirk/ResolutionSelectorQuirkTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/quirk/ResolutionCorrectorQuirkTest.java
@@ -33,7 +33,7 @@
@RunWith(RobolectricTestRunner.class)
@DoNotInstrument
@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
-public class ResolutionSelectorQuirkTest {
+public class ResolutionCorrectorQuirkTest {
@Test
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/workaround/ResolutionSelectorTest.kt b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/workaround/ResolutionCorrectorTest.kt
similarity index 89%
rename from camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/workaround/ResolutionSelectorTest.kt
rename to camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/workaround/ResolutionCorrectorTest.kt
index 6f2d99f..98426d4 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/workaround/ResolutionSelectorTest.kt
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/workaround/ResolutionCorrectorTest.kt
@@ -38,14 +38,14 @@
private val SUPPORTED_RESOLUTIONS = listOf(RESOLUTION_1, RESOLUTION_2)
/**
- * Unit test for [ResolutionSelector].
+ * Unit test for [ResolutionCorrector].
*/
@RunWith(RobolectricTestRunner::class)
@DoNotInstrument
@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
-class ResolutionSelectorTest {
+class ResolutionCorrectorTest {
- private val mResolutionSelector = ResolutionSelector(object : ExtraCroppingQuirk() {
+ private val mResolutionCorrector = ResolutionCorrector(object : ExtraCroppingQuirk() {
override fun getVerifiedResolution(configType: SurfaceConfig.ConfigType): Size? {
return when (configType) {
SurfaceConfig.ConfigType.YUV -> SELECT_RESOLUTION_YUV
@@ -77,7 +77,7 @@
) {
val resolutions: MutableList<Size> = ArrayList<Size>(SUPPORTED_RESOLUTIONS)
resolutions.add(resolution)
- Truth.assertThat(mResolutionSelector.insertOrPrioritize(configType, resolutions))
+ Truth.assertThat(mResolutionCorrector.insertOrPrioritize(configType, resolutions))
.containsExactly(resolution, RESOLUTION_1, RESOLUTION_2).inOrder()
}
@@ -100,7 +100,7 @@
configType: SurfaceConfig.ConfigType,
resolution: Size
) {
- Truth.assertThat(mResolutionSelector.insertOrPrioritize(configType, SUPPORTED_RESOLUTIONS))
+ Truth.assertThat(mResolutionCorrector.insertOrPrioritize(configType, SUPPORTED_RESOLUTIONS))
.containsExactly(resolution, RESOLUTION_1, RESOLUTION_2).inOrder()
}
@@ -117,8 +117,8 @@
private fun noQuirk_returnsOriginalSupportedResolutions(
quirk: ExtraCroppingQuirk?
) {
- val resolutionSelector = ResolutionSelector(quirk)
- val result = resolutionSelector.insertOrPrioritize(
+ val resolutionCorrector = ResolutionCorrector(quirk)
+ val result = resolutionCorrector.insertOrPrioritize(
SurfaceConfig.ConfigType.PRIV,
SUPPORTED_RESOLUTIONS
)
diff --git a/compose/animation/animation-core-lint/src/main/java/androidx/compose/animation/core/lint/UnrememberedAnimatableDetector.kt b/compose/animation/animation-core-lint/src/main/java/androidx/compose/animation/core/lint/UnrememberedAnimatableDetector.kt
index 18d29c2..adcf925 100644
--- a/compose/animation/animation-core-lint/src/main/java/androidx/compose/animation/core/lint/UnrememberedAnimatableDetector.kt
+++ b/compose/animation/animation-core-lint/src/main/java/androidx/compose/animation/core/lint/UnrememberedAnimatableDetector.kt
@@ -21,7 +21,7 @@
import androidx.compose.lint.Name
import androidx.compose.lint.Names
import androidx.compose.lint.isInPackageName
-import androidx.compose.lint.invokedInComposableBodyAndNotRemembered
+import androidx.compose.lint.isNotRemembered
import com.android.tools.lint.client.api.UElementHandler
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Detector
@@ -61,7 +61,7 @@
if (!returnType.rawType().equalsToText(Animatable.javaFqn)) return
}
- if (node.invokedInComposableBodyAndNotRemembered()) {
+ if (node.isNotRemembered()) {
context.report(
UnrememberedAnimatable,
node,
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/libraries/Libraries.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/libraries/Libraries.kt
index 89e8c65..10d886e 100644
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/libraries/Libraries.kt
+++ b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/libraries/Libraries.kt
@@ -139,7 +139,9 @@
navigation(startDestination = innerStartRoute, route = "Parent") {
// ...
composable("exampleWithRoute") { backStackEntry ->
- val parentEntry = remember { navController.getBackStackEntry("Parent") }
+ val parentEntry = remember(backStackEntry) {
+ navController.getBackStackEntry("Parent")
+ }
val parentViewModel = hiltViewModel<ParentViewModel>(parentEntry)
ExampleWithRouteScreen(parentViewModel)
}
diff --git a/compose/lint/common-test/src/main/java/androidx/compose/lint/test/Stubs.kt b/compose/lint/common-test/src/main/java/androidx/compose/lint/test/Stubs.kt
index 7429971..f601de0 100644
--- a/compose/lint/common-test/src/main/java/androidx/compose/lint/test/Stubs.kt
+++ b/compose/lint/common-test/src/main/java/androidx/compose/lint/test/Stubs.kt
@@ -348,6 +348,37 @@
"""
)
+ val PaddingValues: TestFile = compiledStub(
+ filename = "Padding.kt",
+ filepath = "androidx/compose/foundation/layout",
+ checksum = 0xeedd3f96,
+ """
+
+ package androidx.compose.foundation.layout
+
+ import androidx.compose.ui.Modifier
+
+ interface PaddingValues
+
+ """,
+ """
+ META-INF/main.kotlin_module:
+ H4sIAAAAAAAAAGNgYGBmYGBgBGI2BijgUueSTMxLKcrPTKnQS87PLcgvTtXL
+ TSxJLcpMzBHiCk5OTEvLz0nxLuHi5WJOy88XYgtJLS7xLlFi0GIAACJwI+tQ
+ AAAA
+ """,
+ """
+ androidx/compose/foundation/layout/PaddingValues.class:
+ H4sIAAAAAAAAAJVOTUvDQBB9s9Gkxq9ULdQ/YdrizZMXIVBRFHrJaZtsyzbp
+ rnQ3pd76uzxIz/4ocVL9A87Amzfz4L35+v74BHCLHmEgTbmyutykhV2+WafS
+ mW1MKb22Jq3lu218+izLUpv5RNaNchGIkCzkWrJs5unTdKEKHyEgdMeV9bU2
+ 6aPyki3kHUEs1wFnUQthCyBQxfeNbrcBs3JI6O22nVj0RSwSZrP+bjsSA2rF
+ EWE0/u+THMw58d/tpvK8vNpmVagHXSvC9UtjvF6qiXZ6Wqt7Y6zfu7mQM3GA
+ 3xK43OMFrngO2fKQO8wRZIgydDIcIWaK4wwnOM1BDmc4zyEcEofuD692uKBp
+ AQAA
+ """
+ )
+
val Remember: TestFile = compiledStub(
filename = "Remember.kt",
filepath = "androidx/compose/runtime",
diff --git a/compose/lint/common/src/main/java/androidx/compose/lint/ComposableUtils.kt b/compose/lint/common/src/main/java/androidx/compose/lint/ComposableUtils.kt
index e725baf..339c299 100644
--- a/compose/lint/common/src/main/java/androidx/compose/lint/ComposableUtils.kt
+++ b/compose/lint/common/src/main/java/androidx/compose/lint/ComposableUtils.kt
@@ -45,21 +45,44 @@
import org.jetbrains.uast.withContainingElements
/**
- * Returns whether this [UCallExpression] is invoked within the body of a Composable function or
- * lambda, and is not `remember`ed.
- *
- * This searches parent declarations until we find a lambda expression or a function, and looks
- * to see if these are Composable. If they are Composable, this function returns whether or not
- * this call expression is inside the block of a `remember` call.
+ * Returns whether this [UCallExpression] is directly invoked within the body of a Composable
+ * function or lambda without being `remember`ed.
*/
-fun UCallExpression.invokedInComposableBodyAndNotRemembered(): Boolean {
+fun UCallExpression.isNotRemembered(): Boolean = isNotRememberedWithKeys()
+
+/**
+ * Returns whether this [UCallExpression] is directly invoked within the body of a Composable
+ * function or lambda without being `remember`ed, or whether it is invoked inside a `remember call
+ * without the provided [keys][keyClassNames].
+ * - Returns true if this [UCallExpression] is directly invoked inside a Composable function or
+ * lambda without being `remember`ed
+ * - Returns true if this [UCallExpression] is invoked inside a call to `remember`, but without all
+ * of the provided [keys][keyClassNames] being used as key parameters to `remember`
+ * - Returns false if this [UCallExpression] is correctly `remember`ed with the provided
+ * [keys][keyClassNames], or is not called inside a `remember` block, and is not called inside a
+ * Composable function or lambda
+ *
+ * @param keyClassNames [Name]s representing the expected classes that should be used as a key
+ * parameter to the `remember` call
+ */
+fun UCallExpression.isNotRememberedWithKeys(vararg keyClassNames: Name): Boolean {
val visitor = ComposableBodyVisitor(this)
- if (!visitor.isComposable()) return false
- return visitor.parentUElements().none {
- (it as? UCallExpression)?.let { element ->
- element.methodName == Names.Runtime.Remember.shortName &&
- element.resolve()?.isInPackageName(Names.Runtime.PackageName) == true
- } == true
+ // The nearest method or lambda expression that contains this call expression
+ val boundaryElement = visitor.parentUElements().last()
+ // Check if the nearest lambda expression is actually a call to remember
+ val rememberCall: UCallExpression? = (boundaryElement.uastParent as? UCallExpression)?.takeIf {
+ it.methodName == Names.Runtime.Remember.shortName &&
+ it.resolve()?.isInPackageName(Names.Runtime.PackageName) == true
+ }
+ return if (rememberCall == null) {
+ visitor.isComposable()
+ } else {
+ val parameterTypes = rememberCall.valueArguments.mapNotNull {
+ it.getExpressionType()?.canonicalText
+ }
+ !keyClassNames.all {
+ parameterTypes.contains(it.javaFqn)
+ }
}
}
diff --git a/compose/lint/common/src/main/java/androidx/compose/lint/Names.kt b/compose/lint/common/src/main/java/androidx/compose/lint/Names.kt
index 7423dbe..016e112 100644
--- a/compose/lint/common/src/main/java/androidx/compose/lint/Names.kt
+++ b/compose/lint/common/src/main/java/androidx/compose/lint/Names.kt
@@ -28,9 +28,6 @@
object AnimationCore {
val PackageName = Package("androidx.compose.animation.core")
}
- object Material {
- val PackageName = Package("androidx.compose.material")
- }
object Runtime {
val PackageName = Package("androidx.compose.runtime")
diff --git a/compose/material/material-lint/src/main/java/androidx/compose/material/lint/ColorsDetector.kt b/compose/material/material-lint/src/main/java/androidx/compose/material/lint/ColorsDetector.kt
index 11cbd84..0e1a911 100644
--- a/compose/material/material-lint/src/main/java/androidx/compose/material/lint/ColorsDetector.kt
+++ b/compose/material/material-lint/src/main/java/androidx/compose/material/lint/ColorsDetector.kt
@@ -19,8 +19,8 @@
package androidx.compose.material.lint
import androidx.compose.lint.Name
-import androidx.compose.lint.Names
import androidx.compose.lint.isInPackageName
+import androidx.compose.material.lint.MaterialNames.Material
import com.android.tools.lint.client.api.UElementHandler
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Detector
@@ -55,7 +55,7 @@
override fun createUastHandler(context: JavaContext) = object : UElementHandler() {
override fun visitCallExpression(node: UCallExpression) {
val method = node.resolve() ?: return
- if (!method.isInPackageName(Names.Material.PackageName)) return
+ if (!method.isInPackageName(Material.PackageName)) return
if (node.isConstructorCall()) {
if (method.containingClass?.name != Colors.shortName) return
@@ -229,6 +229,6 @@
"error" to "onError"
)
-private val LightColors = Name(Names.Material.PackageName, "lightColors")
-private val DarkColors = Name(Names.Material.PackageName, "darkColors")
-private val Colors = Name(Names.Material.PackageName, "Colors")
+private val LightColors = Name(Material.PackageName, "lightColors")
+private val DarkColors = Name(Material.PackageName, "darkColors")
+private val Colors = Name(Material.PackageName, "Colors")
diff --git a/compose/material/material-lint/src/main/java/androidx/compose/material/lint/MaterialNames.kt b/compose/material/material-lint/src/main/java/androidx/compose/material/lint/MaterialNames.kt
new file mode 100644
index 0000000..dae87fd
--- /dev/null
+++ b/compose/material/material-lint/src/main/java/androidx/compose/material/lint/MaterialNames.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.material.lint
+
+import androidx.compose.lint.Package
+
+/**
+ * Contains common names used for Material lint checks.
+ */
+object MaterialNames {
+ object Material {
+ val PackageName = Package("androidx.compose.material")
+ }
+}
diff --git a/compose/material/material-lint/src/main/java/androidx/compose/material/lint/ScaffoldPaddingDetector.kt b/compose/material/material-lint/src/main/java/androidx/compose/material/lint/ScaffoldPaddingDetector.kt
index 252ab25..870a673 100644
--- a/compose/material/material-lint/src/main/java/androidx/compose/material/lint/ScaffoldPaddingDetector.kt
+++ b/compose/material/material-lint/src/main/java/androidx/compose/material/lint/ScaffoldPaddingDetector.kt
@@ -17,9 +17,9 @@
package androidx.compose.material.lint
import androidx.compose.lint.Name
-import androidx.compose.lint.Names
import androidx.compose.lint.findUnreferencedParameters
import androidx.compose.lint.isInPackageName
+import androidx.compose.material.lint.MaterialNames.Material
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
@@ -44,7 +44,7 @@
override fun getApplicableMethodNames(): List<String> = listOf(Scaffold.shortName)
override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
- if (method.isInPackageName(Names.Material.PackageName)) {
+ if (method.isInPackageName(Material.PackageName)) {
val contentArgument = computeKotlinArgumentMapping(node, method)
.orEmpty()
.filter { (_, parameter) ->
@@ -86,4 +86,4 @@
}
}
-private val Scaffold = Name(Names.Material.PackageName, "Scaffold")
\ No newline at end of file
+private val Scaffold = Name(Material.PackageName, "Scaffold")
\ No newline at end of file
diff --git a/compose/material/material-lint/src/test/java/androidx/compose/material/lint/ScaffoldPaddingDetectorTest.kt b/compose/material/material-lint/src/test/java/androidx/compose/material/lint/ScaffoldPaddingDetectorTest.kt
index 88b9576..7537ea8 100644
--- a/compose/material/material-lint/src/test/java/androidx/compose/material/lint/ScaffoldPaddingDetectorTest.kt
+++ b/compose/material/material-lint/src/test/java/androidx/compose/material/lint/ScaffoldPaddingDetectorTest.kt
@@ -39,38 +39,6 @@
override fun getIssues(): MutableList<Issue> =
mutableListOf(ScaffoldPaddingDetector.UnusedMaterialScaffoldPaddingParameter)
- private val PaddingValuesStub = compiledStub(
- filename = "Padding.kt",
- filepath = "androidx/compose/foundation/layout",
- checksum = 0xeedd3f96,
- """
-
- package androidx.compose.foundation.layout
-
- import androidx.compose.ui.Modifier
-
- interface PaddingValues
-
- """,
- """
- META-INF/main.kotlin_module:
- H4sIAAAAAAAAAGNgYGBmYGBgBGI2BijgUueSTMxLKcrPTKnQS87PLcgvTtXL
- TSxJLcpMzBHiCk5OTEvLz0nxLuHi5WJOy88XYgtJLS7xLlFi0GIAACJwI+tQ
- AAAA
- """,
- """
- androidx/compose/foundation/layout/PaddingValues.class:
- H4sIAAAAAAAAAJVOTUvDQBB9s9Gkxq9ULdQ/YdrizZMXIVBRFHrJaZtsyzbp
- rnQ3pd76uzxIz/4ocVL9A87Amzfz4L35+v74BHCLHmEgTbmyutykhV2+WafS
- mW1MKb22Jq3lu218+izLUpv5RNaNchGIkCzkWrJs5unTdKEKHyEgdMeV9bU2
- 6aPyki3kHUEs1wFnUQthCyBQxfeNbrcBs3JI6O22nVj0RSwSZrP+bjsSA2rF
- EWE0/u+THMw58d/tpvK8vNpmVagHXSvC9UtjvF6qiXZ6Wqt7Y6zfu7mQM3GA
- 3xK43OMFrngO2fKQO8wRZIgydDIcIWaK4wwnOM1BDmc4zyEcEofuD692uKBp
- AQAA
- """
-
- )
-
// Simplified Scaffold.kt stubs
private val ScaffoldStub = compiledStub(
filename = "Scaffold.kt",
@@ -185,9 +153,9 @@
}
"""
),
- PaddingValuesStub,
ScaffoldStub,
Stubs.Modifier,
+ Stubs.PaddingValues,
Stubs.Composable
)
.run()
@@ -252,9 +220,9 @@
}
"""
),
- PaddingValuesStub,
ScaffoldStub,
Stubs.Modifier,
+ Stubs.PaddingValues,
Stubs.Composable
)
.run()
@@ -293,9 +261,9 @@
}
"""
),
- PaddingValuesStub,
ScaffoldStub,
Stubs.Modifier,
+ Stubs.PaddingValues,
Stubs.Composable
)
.run()
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Chip.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Chip.kt
index 731e868..3de524a 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Chip.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Chip.kt
@@ -69,8 +69,7 @@
* Alternatively, use Accompanist's [Flow Layouts](https://google.github.io/accompanist/flowlayout/)
* to wrap chips to a new line.
*
- * @param onClick called when the chip is clicked. If null, then this chip will be considered
- * read-only unless something else handles its input events and updates its state.
+ * @param onClick called when the chip is clicked.
* @param modifier Modifier to be applied to the chip
* @param enabled When disabled, chip will not respond to user input. It will also appear visually
* disabled and disabled to accessibility services.
diff --git a/compose/material3/material3-lint/build.gradle b/compose/material3/material3-lint/build.gradle
new file mode 100644
index 0000000..c626c35
--- /dev/null
+++ b/compose/material3/material3-lint/build.gradle
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+
+import androidx.build.BundleInsideHelper
+import androidx.build.LibraryType
+
+plugins {
+ id("AndroidXPlugin")
+ id("kotlin")
+}
+
+BundleInsideHelper.forInsideLintJar(project)
+
+dependencies {
+ compileOnly libs.androidLintMinComposeApi
+ compileOnly libs.kotlinStdlib
+ bundleInside(project(":compose:lint:common"))
+
+ testImplementation(project(":compose:lint:common-test"))
+ testImplementation libs.kotlinStdlib
+ testImplementation libs.androidLint
+ testImplementation libs.androidLintTests
+ testImplementation libs.junit
+ testImplementation libs.truth
+}
+
+androidx {
+ name = "Compose Material3 Lint Checks"
+ type = LibraryType.LINT
+ mavenGroup = LibraryGroups.COMPOSE_MATERIAL3
+ inceptionYear = "2022"
+ description = "Compose Material3 Lint Checks"
+}
diff --git a/compose/material3/material3-lint/src/main/java/androidx/compose/material3/lint/Material3IssueRegistry.kt b/compose/material3/material3-lint/src/main/java/androidx/compose/material3/lint/Material3IssueRegistry.kt
new file mode 100644
index 0000000..8529446
--- /dev/null
+++ b/compose/material3/material3-lint/src/main/java/androidx/compose/material3/lint/Material3IssueRegistry.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.material3.lint
+
+import com.android.tools.lint.client.api.IssueRegistry
+import com.android.tools.lint.client.api.Vendor
+import com.android.tools.lint.detector.api.CURRENT_API
+
+/**
+ * [IssueRegistry] containing Material3 specific lint issues.
+ */
+class Material3IssueRegistry : IssueRegistry() {
+ // Tests are run with this version. We ensure that with ApiLintVersionsTest
+ override val api = 13
+ override val minApi = CURRENT_API
+ override val issues get() = listOf(
+ ScaffoldPaddingDetector.UnusedMaterial3ScaffoldPaddingParameter
+ )
+ override val vendor = Vendor(
+ vendorName = "Jetpack Compose",
+ identifier = "androidx.compose.material3",
+ feedbackUrl = "https://issuetracker.google.com/issues/new?component=612128"
+ )
+}
diff --git a/compose/material3/material3-lint/src/main/java/androidx/compose/material3/lint/Material3Names.kt b/compose/material3/material3-lint/src/main/java/androidx/compose/material3/lint/Material3Names.kt
new file mode 100644
index 0000000..733fd2e
--- /dev/null
+++ b/compose/material3/material3-lint/src/main/java/androidx/compose/material3/lint/Material3Names.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.material3.lint
+
+import androidx.compose.lint.Package
+
+/**
+ * Contains common names used for Material3 lint checks.
+ */
+object Material3Names {
+ object Material3 {
+ val PackageName = Package("androidx.compose.material3")
+ }
+}
diff --git a/compose/material3/material3-lint/src/main/java/androidx/compose/material3/lint/ScaffoldPaddingDetector.kt b/compose/material3/material3-lint/src/main/java/androidx/compose/material3/lint/ScaffoldPaddingDetector.kt
new file mode 100644
index 0000000..eba6ffd
--- /dev/null
+++ b/compose/material3/material3-lint/src/main/java/androidx/compose/material3/lint/ScaffoldPaddingDetector.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.material3.lint
+
+import androidx.compose.lint.Name
+import androidx.compose.lint.findUnreferencedParameters
+import androidx.compose.lint.isInPackageName
+import androidx.compose.material3.lint.Material3Names.Material3
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.android.tools.lint.detector.api.computeKotlinArgumentMapping
+import com.intellij.psi.PsiMethod
+import java.util.EnumSet
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.ULambdaExpression
+
+/**
+ * [Detector] that checks `Scaffold` usages for correctness.
+ *
+ * Scaffold provides an padding parameter to the `content` lambda. If this value is unused,
+ * then the content may be obscured by app bars defined by the scaffold.
+ */
+class ScaffoldPaddingDetector : Detector(), SourceCodeScanner {
+ override fun getApplicableMethodNames(): List<String> = listOf(Scaffold.shortName)
+
+ override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
+ if (method.isInPackageName(Material3.PackageName)) {
+ val contentArgument = computeKotlinArgumentMapping(node, method)
+ .orEmpty()
+ .filter { (_, parameter) ->
+ parameter.name == "content"
+ }
+ .keys
+ .filterIsInstance<ULambdaExpression>()
+ .firstOrNull() ?: return
+
+ contentArgument.findUnreferencedParameters().forEach { unreferencedParameter ->
+ val location = unreferencedParameter.parameter
+ ?.let { context.getLocation(it) }
+ ?: context.getLocation(contentArgument)
+ val name = unreferencedParameter.name
+ context.report(
+ UnusedMaterial3ScaffoldPaddingParameter,
+ node,
+ location,
+ "Content padding parameter $name is not used"
+ )
+ }
+ }
+ }
+
+ companion object {
+ val UnusedMaterial3ScaffoldPaddingParameter = Issue.create(
+ "UnusedMaterial3ScaffoldPaddingParameter",
+ "Scaffold content should use the padding provided as a lambda parameter",
+ "The `content` lambda in Scaffold has a padding parameter " +
+ "which will include any inner padding for the content due to app bars. If this " +
+ "parameter is ignored, then content may be obscured by the app bars resulting in " +
+ "visual issues or elements that can't be interacted with.",
+ Category.CORRECTNESS, 3, Severity.ERROR,
+ Implementation(
+ ScaffoldPaddingDetector::class.java,
+ EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
+ )
+ )
+ }
+}
+
+private val Scaffold = Name(Material3.PackageName, "Scaffold")
\ No newline at end of file
diff --git a/compose/material3/material3-lint/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry b/compose/material3/material3-lint/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry
new file mode 100644
index 0000000..ffddc1d
--- /dev/null
+++ b/compose/material3/material3-lint/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry
@@ -0,0 +1 @@
+androidx.compose.material3.lint.Material3IssueRegistry
diff --git a/compose/material3/material3-lint/src/test/java/androidx/compose/material3/lint/ApiLintVersionsTest.kt b/compose/material3/material3-lint/src/test/java/androidx/compose/material3/lint/ApiLintVersionsTest.kt
new file mode 100644
index 0000000..6bb46ba
--- /dev/null
+++ b/compose/material3/material3-lint/src/test/java/androidx/compose/material3/lint/ApiLintVersionsTest.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+@file:Suppress("UnstableApiUsage")
+
+package androidx.compose.material3.lint
+
+import com.android.tools.lint.client.api.LintClient
+import com.android.tools.lint.detector.api.CURRENT_API
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class ApiLintVersionsTest {
+
+ @Test
+ fun versionsCheck() {
+ LintClient.clientName = LintClient.CLIENT_UNIT_TESTS
+
+ val registry = Material3IssueRegistry()
+ assertThat(registry.api).isEqualTo(CURRENT_API)
+ assertThat(registry.minApi).isEqualTo(10)
+ }
+}
diff --git a/compose/material3/material3-lint/src/test/java/androidx/compose/material3/lint/ScaffoldPaddingDetectorTest.kt b/compose/material3/material3-lint/src/test/java/androidx/compose/material3/lint/ScaffoldPaddingDetectorTest.kt
new file mode 100644
index 0000000..1fb1874
--- /dev/null
+++ b/compose/material3/material3-lint/src/test/java/androidx/compose/material3/lint/ScaffoldPaddingDetectorTest.kt
@@ -0,0 +1,273 @@
+/*
+ * 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.
+ */
+
+@file:Suppress("UnstableApiUsage")
+
+package androidx.compose.material3.lint
+
+import androidx.compose.lint.test.Stubs
+import androidx.compose.lint.test.compiledStub
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+/* ktlint-disable max-line-length */
+@RunWith(JUnit4::class)
+
+/**
+ * Test for [ScaffoldPaddingDetector].
+ */
+class ScaffoldPaddingDetectorTest : LintDetectorTest() {
+ override fun getDetector(): Detector = ScaffoldPaddingDetector()
+
+ override fun getIssues(): MutableList<Issue> =
+ mutableListOf(ScaffoldPaddingDetector.UnusedMaterial3ScaffoldPaddingParameter)
+
+ // Simplified Scaffold.kt stubs
+ private val ScaffoldStub = compiledStub(
+ filename = "Scaffold.kt",
+ filepath = "androidx/compose/material3",
+ checksum = 0xfee46355,
+ """
+ package androidx.compose.material3
+
+ import androidx.compose.foundation.layout.PaddingValues
+ import androidx.compose.runtime.Composable
+ import androidx.compose.ui.Modifier
+
+ @Composable
+ fun Scaffold(
+ modifier: Modifier = Modifier,
+ topBar: @Composable () -> Unit = {},
+ bottomBar: @Composable () -> Unit = {},
+ content: @Composable (PaddingValues) -> Unit
+ ) {}
+
+ """,
+ """
+ META-INF/main.kotlin_module:
+ H4sIAAAAAAAAAGNgYGBmYGBgBGI2Bijg0uCSSsxLKcrPTKnQS87PLcgvTtXL
+ TSxJLcpMzDEW4gpOTkxLy89J8S7h4uViTsvPF2ILSS0u8S5RYtBiAACpks1u
+ UQAAAA==
+ """,
+ """
+ androidx/compose/material3/ScaffoldKt$Scaffold$1.class:
+ H4sIAAAAAAAAAKVU604TQRT+Zlt62RZbEOUi3kFbULYt3ktICIG4oWBisYnh
+ 17S7haG7s6a7bfAfD+ET+ASiiSSaGOJPH8p4ZmkNhogaN9mzJ+d835lzm/32
+ /dMXAPfwhKHApdX2hLVnNDz3lefbhssDuy24M29UG7zZ9BxrLZjqq1PFOBjD
+ WqXlBY6Qxm7XNYQkguSOUeFu3eLlk75mRzYC4UnfWO1phYW+/4UUQXmxzDDx
+ +2BxRBmunB0wjhhDbEFQuEWGSC5fY4jmzHwtjQR0HQNIkSHYET5DqfKv9VJ+
+ MSG7XstmGMnlK7u8yw2Hy23jWX3XbgTlNDJI6tAwxJA6UVoc5xkS5kZ1c2lj
+ eYVh8Je607iAi0mMYJRACw0nzF4lHIaaUO5zSdImGYb6xHU74BYPOKWkud0I
+ jZApEVMCDKyllAg594TSCqRZRYbJo/2EfrSva1mNPtmj/QmtwJ7qX9/GtISm
+ MCVKfIFLT752vY5PPaRg03/VpzjuMGR/Nsuym7zjBAxvcqf73BHGumeJprDb
+ f1qR//QXy+bpMal1mINBpfbTnWtRptFlz6LJDle8BndqnAqsO/amEgyZipD2
+ Rset2+2eJW1KabeXHe77Nm1TZkU2HM8XcptGs+NZDMmq2JY86LQJrFe9Trth
+ rwrFHH/ekYFw7ZrwBYVaktILeJg2CjTmAWo53SyMq7nT7KL00i6QpUTaFCGY
+ mvRM5BDpg3Da8yTTx1YMhpwhtYg9xmyIoVeBNbrqCqYMqRNEdkzMLhEx2yOW
+ 1CKpw2c+Yvg9xt6dwU/0Dk5Q2v2DRwmtntRnaC8PcekDLh+EhgHcJ6kT7Bgw
+ hgdhnXep/ofhIRE8Cr9FPA7/TnTxiXV1CxET10xcN3EDN01qxrSJW7i9BeYj
+ hzz5fcz4mPWR+QHL6C/y2gQAAA==
+ """,
+ """
+ androidx/compose/material3/ScaffoldKt$Scaffold$2.class:
+ H4sIAAAAAAAAAKVUbU/TUBR+bjf2xnADUV7Ed9ANlI7h+wgJIRAbBiYOlxg+
+ 3bUdXNbemrVd8Bs/wl/gLxBNNNHEED/6o4znlk0xRNTYpKcn5zzPueft9uu3
+ j58B3MEjhhKXVtsT1p5ueu4Lz7d1lwd2W3BnXq+ZvNn0HGstmOypk+UkGMNa
+ teUFjpD6bsfVhSSC5I5e5W7D4pXjvmYozUB40tdXu1ppoed/JkVQWawwjP8+
+ WBJxhkunB0wiwZBYEBRukSFWKNYZ4gWjWM8ihUwGfegnQ7AjfIZy9V/rpfwS
+ Qna8ls0wXChWd3mH6w6X2/qTxq5tBpUsckhnoGGQof9YaUmcZUgZG7XNpY3l
+ FYaBX+rO4hzOpzGMEQItmE6UvUo4CjWu3GfSpE0wDPaI63bALR5wSklzOzEa
+ IVMioQQYWEspMXLuCaWVSLPmGCYO91OZw/2Mltfokz/cH9dK7HHmy+uEltIU
+ pkyJL3DpyZeuF/rUQwo29Vd9SuIWQ/5Hsyy7yUMnYHhVONnnUOjrniWawm7/
+ aUX+0z9XMU6OSa3DLHQqtZfubIsyjS97Fk12qOqZ3KlzKrDh2JtKMOSqQtob
+ oduw211L1pDSbi873Pdt2qbcijQdzxdym0az41kM6ZrYljwI2wTO1Lywbdqr
+ QjHHnoYyEK5dF76gUEtSegGP0kaJxtxHLaebhTE1d5pdnF7aBbKUSZskBFOT
+ no59QPYgmvY8yeyRFQMRZ1AtYpcxE2HoVWCNrrqCsYjyk8iOiPklIua7xLJa
+ JHX49HsMvcXom1P4qe7BKUq7d/AIodXT/wna8w+48A4XDyJDH+6SzBDsCDCK
+ e1Gdt6n++9EhMTyIvnN4GP2d6OIT6/IWYgauGLhq4BquG9SMKQM3cHMLzEcB
+ RfL7mPYx4yP3HfQNQiHaBAAA
+ """,
+ """
+ androidx/compose/material3/ScaffoldKt.class:
+ H4sIAAAAAAAAAMVUS3PbVBT+ru1YkmOnrhKniVtCaRya5lHZbnk6FFLTtCK2
+ 6eA2m6yuZdkolq8yemTKhgnDX2DDln8Aqw4LxsOSf8EfYXok2yGNOwm0zLDQ
+ Pc97zneOzj1//PXrbwDuos6wwkXbdaz2M81w+oeOZ2p97puuxe07WtPgnY5j
+ t3d9CYwhe8CPuGZz0dW+bB2YBmnjDPLYi+G71dpEtMDS6k7b6limW6n1HN+2
+ hHZw1Nc6gTB8yxGetjPiim9oL1Vu7TH8+WYYtsb2p8LyK/f+W/fS1uYkuI4T
+ iDYPzdTab5zA1x7zdtsS3T1uB6ZXOZMhrHFlMoobCN/qm1o1knnLNisMyzXH
+ 7WoHpt9yuUU4uBCOz4eYGo7fCGybvOT+qDcyUgxLpyqwBE2C4LamC9+lAJbh
+ SUgz5IyvTaM3ivCYu7xvkiPDzdXa2RGpnNI0wyBdqiCNGVxKIYMsQ9J3Du9z
+ yq0yKC3H951+JM4xSIZDAIQvY55wnf9bGa5fND0XupTIJTue5kLb7PDA9hl+
+ +J+nWp9sajgE184DJeEtamc4DFxQEIbzayiceFbSeBvXFSzhHYbiP9oNhZOW
+ lSQs0zzpjeaT7Ub1AUN5MusFESj/Ct5VUMDNl2fxFZ2TcOvfYyxLWH8NYOUI
+ 2KaCDdxOYwrJFGIoMlwe/7266XN6xpxmKNY/itN6ZeGRDA8wsF7IxMj4zAo5
+ uhprlxi+HxzfSA2OU7FsLCILJyT65NiYzz/NDo7zsSIryzI5ExcvzxKXyGfU
+ hEr64tTvPyVjcjLSShPaK1k5PxvpUiOLMrQ8kkIoZRaiVMfVnH41E8rwnbyi
+ gRctMobpcTtv9+hdJapO22S4VLOE2Qj6LdN9Eu6tMKFjcHuP098geaRUmlZX
+ cD9wib/61XDb6eLI8iwyb/+92BgKZ60nG+olt0zT50avzg9HCdK6EKZbtbnn
+ mWRONZ3ANcwdK7QtjkLuTaRDieYgEf5joovhYJBUJYmP9Itr6vRzXF5XZ+nc
+ UHN0bqpX6Pw5uvJ5OCPU+QVaiQ+IXxteQoo0iDiVPhZxc/TFIm4eecSxE0WQ
+ 8HAUQyb6KLQnSFCisTtzZhVcxTXiQ4R9SpUkWs4lEt/+iNQvuDHA0m4uMTWU
+ VgdYq+US0lDSSKqvrW9sPkdpCF2ncwrxmUwmqmKJkICSSIR9hmgO05RKwTLS
+ VJVCeL8gu0oXC1FlC/SUhnQ3CncfNaI1AlemsHf2EddxV8d7Ot7HBzo+xEc6
+ PkZlH8zDFj7Zx7SHKQ/3PKQ8LHhQPXzqQfYw52Hew2cetl8ABZTb8egIAAA=
+ """
+ )
+
+ @Test
+ fun unreferencedParameters() {
+ lint().files(
+ kotlin(
+ """
+ package foo
+
+ import androidx.compose.material3.*
+ import androidx.compose.runtime.*
+ import androidx.compose.ui.*
+
+ @Composable
+ fun Test() {
+ Scaffold { /**/ }
+ Scaffold(Modifier) { /**/ }
+ Scaffold(Modifier, topBar = {}, bottomBar = {}) { /**/ }
+ Scaffold(Modifier, topBar = {}, bottomBar = {}, content = { /**/ })
+ Scaffold(Modifier, topBar = {}, bottomBar = {}) { _ -> /**/ }
+ Scaffold(Modifier, topBar = {}, bottomBar = {}) { innerPadding -> /**/ }
+ }
+ """
+ ),
+ ScaffoldStub,
+ Stubs.Modifier,
+ Stubs.PaddingValues,
+ Stubs.Composable
+ )
+ .run()
+ .expect(
+ """
+src/foo/test.kt:10: Error: Content padding parameter it is not used [UnusedMaterial3ScaffoldPaddingParameter]
+ Scaffold { /**/ }
+ ~~~~~~~~
+src/foo/test.kt:11: Error: Content padding parameter it is not used [UnusedMaterial3ScaffoldPaddingParameter]
+ Scaffold(Modifier) { /**/ }
+ ~~~~~~~~
+src/foo/test.kt:12: Error: Content padding parameter it is not used [UnusedMaterial3ScaffoldPaddingParameter]
+ Scaffold(Modifier, topBar = {}, bottomBar = {}) { /**/ }
+ ~~~~~~~~
+src/foo/test.kt:13: Error: Content padding parameter it is not used [UnusedMaterial3ScaffoldPaddingParameter]
+ Scaffold(Modifier, topBar = {}, bottomBar = {}, content = { /**/ })
+ ~~~~~~~~
+src/foo/test.kt:14: Error: Content padding parameter _ is not used [UnusedMaterial3ScaffoldPaddingParameter]
+ Scaffold(Modifier, topBar = {}, bottomBar = {}) { _ -> /**/ }
+ ~
+src/foo/test.kt:15: Error: Content padding parameter innerPadding is not used [UnusedMaterial3ScaffoldPaddingParameter]
+ Scaffold(Modifier, topBar = {}, bottomBar = {}) { innerPadding -> /**/ }
+ ~~~~~~~~~~~~
+6 errors, 0 warnings
+ """
+ )
+ }
+
+ @Test
+ fun unreferencedParameter_shadowedNames() {
+ lint().files(
+ kotlin(
+ """
+ package foo
+
+ import androidx.compose.material3.*
+ import androidx.compose.runtime.*
+ import androidx.compose.ui.*
+
+ val foo = false
+
+ @Composable
+ fun Test() {
+ Scaffold {
+ foo.let {
+ // These `it`s refer to the `let`, not the `Scaffold`, so we
+ // should still report an error
+ it.let {
+ if (it) { /**/ } else { /**/ }
+ }
+ }
+ }
+ Scaffold(Modifier, topBar = {}, bottomBar = {}) { innerPadding ->
+ foo.let { innerPadding ->
+ // These `innerPadding`s refer to the `let`, not the `Scaffold`, so we
+ // should still report an error
+ innerPadding.let {
+ if (innerPadding) { /**/ } else { /**/ }
+ }
+ }
+ }
+ }
+ """
+ ),
+ ScaffoldStub,
+ Stubs.Modifier,
+ Stubs.PaddingValues,
+ Stubs.Composable
+ )
+ .run()
+ .expect(
+ """
+src/foo/test.kt:12: Error: Content padding parameter it is not used [UnusedMaterial3ScaffoldPaddingParameter]
+ Scaffold {
+ ^
+src/foo/test.kt:21: Error: Content padding parameter innerPadding is not used [UnusedMaterial3ScaffoldPaddingParameter]
+ Scaffold(Modifier, topBar = {}, bottomBar = {}) { innerPadding ->
+ ~~~~~~~~~~~~
+2 errors, 0 warnings
+ """
+ )
+ }
+
+ @Test
+ fun noErrors() {
+ lint().files(
+ kotlin(
+ """
+ package foo
+
+ import androidx.compose.material3.*
+ import androidx.compose.runtime.*
+ import androidx.compose.ui.*
+
+ @Composable
+ fun Test() {
+ Scaffold {
+ it
+ }
+ Scaffold(Modifier, topBar = {}, bottomBar = {}) { innerPadding ->
+ innerPadding
+ }
+ }
+ """
+ ),
+ ScaffoldStub,
+ Stubs.Modifier,
+ Stubs.PaddingValues,
+ Stubs.Composable
+ )
+ .run()
+ .expectClean()
+ }
+}
+/* ktlint-enable max-line-length */
diff --git a/compose/material3/material3/build.gradle b/compose/material3/material3/build.gradle
index 80a15e0..1a20d20 100644
--- a/compose/material3/material3/build.gradle
+++ b/compose/material3/material3/build.gradle
@@ -68,6 +68,8 @@
androidTestImplementation(libs.mockitoCore)
androidTestImplementation(libs.mockitoKotlin)
androidTestImplementation(libs.testUiautomator)
+
+ lintPublish project(":compose:material3:material3-lint")
}
}
diff --git a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/UnrememberedMutableStateDetector.kt b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/UnrememberedMutableStateDetector.kt
index 1310a6c..0a820be 100644
--- a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/UnrememberedMutableStateDetector.kt
+++ b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/UnrememberedMutableStateDetector.kt
@@ -20,7 +20,7 @@
import androidx.compose.lint.Names
import androidx.compose.lint.isInPackageName
-import androidx.compose.lint.invokedInComposableBodyAndNotRemembered
+import androidx.compose.lint.isNotRemembered
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
@@ -47,7 +47,7 @@
override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
if (!method.isInPackageName(Names.Runtime.PackageName)) return
- if (node.invokedInComposableBodyAndNotRemembered()) {
+ if (node.isNotRemembered()) {
context.report(
UnrememberedMutableState,
node,
diff --git a/compose/runtime/runtime-saveable/src/androidAndroidTest/kotlin/androidx/compose/runtime/saveable/SaveableStateHolderTest.kt b/compose/runtime/runtime-saveable/src/androidAndroidTest/kotlin/androidx/compose/runtime/saveable/SaveableStateHolderTest.kt
index 7170fd85..29dd6fc 100644
--- a/compose/runtime/runtime-saveable/src/androidAndroidTest/kotlin/androidx/compose/runtime/saveable/SaveableStateHolderTest.kt
+++ b/compose/runtime/runtime-saveable/src/androidAndroidTest/kotlin/androidx/compose/runtime/saveable/SaveableStateHolderTest.kt
@@ -18,6 +18,7 @@
import android.os.Bundle
import androidx.activity.ComponentActivity
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -292,6 +293,29 @@
}
}
+ @Test
+ fun saveNothingWhenNoRememberSaveableIsUsedInternally() {
+ var showFirstPage by mutableStateOf(true)
+ val registry = SaveableStateRegistry(null) { true }
+
+ rule.setContent {
+ CompositionLocalProvider(LocalSaveableStateRegistry provides registry) {
+ val holder = rememberSaveableStateHolder()
+ holder.SaveableStateProvider(showFirstPage) {
+ }
+ }
+ }
+
+ rule.runOnIdle {
+ showFirstPage = false
+ }
+
+ rule.runOnIdle {
+ val savedData = registry.performSave()
+ assertThat(savedData).isEqualTo(emptyMap<String, List<Any?>>())
+ }
+ }
+
class Activity : ComponentActivity() {
fun doFakeSave() {
onSaveInstanceState(Bundle())
diff --git a/compose/runtime/runtime-saveable/src/commonMain/kotlin/androidx/compose/runtime/saveable/SaveableStateHolder.kt b/compose/runtime/runtime-saveable/src/commonMain/kotlin/androidx/compose/runtime/saveable/SaveableStateHolder.kt
index 30224a5..a34e0bf 100644
--- a/compose/runtime/runtime-saveable/src/commonMain/kotlin/androidx/compose/runtime/saveable/SaveableStateHolder.kt
+++ b/compose/runtime/runtime-saveable/src/commonMain/kotlin/androidx/compose/runtime/saveable/SaveableStateHolder.kt
@@ -97,10 +97,10 @@
}
}
- private fun saveAll(): MutableMap<Any, Map<String, List<Any?>>> {
+ private fun saveAll(): MutableMap<Any, Map<String, List<Any?>>>? {
val map = savedStates.toMutableMap()
registryHolders.values.forEach { it.saveTo(map) }
- return map
+ return map.ifEmpty { null }
}
override fun removeState(key: Any) {
@@ -122,7 +122,12 @@
fun saveTo(map: MutableMap<Any, Map<String, List<Any?>>>) {
if (shouldSave) {
- map[key] = registry.performSave()
+ val savedData = registry.performSave()
+ if (savedData.isEmpty()) {
+ map -= key
+ } else {
+ map[key] = savedData
+ }
}
}
}
diff --git a/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/ParameterFactoryTest.kt b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/ParameterFactoryTest.kt
index 7eb7645..75198d6 100644
--- a/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/ParameterFactoryTest.kt
+++ b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/ParameterFactoryTest.kt
@@ -884,22 +884,12 @@
)
validate(create("style", style)) {
parameter("style", ParameterType.String, TextStyle::class.java.simpleName) {
- parameter("background", ParameterType.String, "Unspecified")
- parameter("color", ParameterType.Color, Color.Red.toArgb(), index = 2)
- parameter("fontSize", ParameterType.String, "Unspecified", index = 5)
- parameter("letterSpacing", ParameterType.String, "Unspecified", index = 9)
- parameter("lineHeight", ParameterType.String, "Unspecified", index = 10)
- parameter("paragraphStyle", ParameterType.String, "ParagraphStyle", index = 13) {
- parameter("lineHeight", ParameterType.String, "Unspecified", index = 0)
- }
- parameter("spanStyle", ParameterType.String, "SpanStyle", index = 16) {
- parameter("background", ParameterType.String, "Unspecified")
- parameter("color", ParameterType.Color, Color.Red.toArgb(), index = 2)
- parameter("fontSize", ParameterType.String, "Unspecified", index = 5)
- parameter("letterSpacing", ParameterType.String, "Unspecified", index = 9)
- parameter("textDecoration", ParameterType.String, "Underline", index = 13)
- }
- parameter("textDecoration", ParameterType.String, "Underline", index = 18)
+ parameter("color", ParameterType.Color, Color.Red.toArgb())
+ parameter("fontSize", ParameterType.String, "Unspecified", index = 1)
+ parameter("letterSpacing", ParameterType.String, "Unspecified", index = 7)
+ parameter("background", ParameterType.String, "Unspecified", index = 11)
+ parameter("textDecoration", ParameterType.String, "Underline", index = 12)
+ parameter("lineHeight", ParameterType.String, "Unspecified", index = 15)
}
}
}
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/ParameterFactory.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/ParameterFactory.kt
index 984fa4a..752ff77 100644
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/ParameterFactory.kt
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/ParameterFactory.kt
@@ -33,6 +33,7 @@
import androidx.compose.ui.platform.InspectableModifier
import androidx.compose.ui.platform.InspectableValue
import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontListFontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
@@ -423,6 +424,14 @@
return createReferenceToExistingValue(name, value, parentValue, existing)
}
+ private fun create(
+ name: String,
+ value: Any?,
+ parentValue: Any?,
+ specifiedIndex: Int = 0
+ ): NodeParameter? =
+ create(name, value, parentValue)?.apply { index = specifiedIndex }
+
private fun createFromSimpleValue(name: String, value: Any?): NodeParameter? {
if (value == null) {
return null
@@ -474,6 +483,7 @@
value.javaClass.isArray -> createFromArray(name, value, startIndex, maxElements)
value is Offset -> createFromOffset(name, value)
value is Shadow -> createFromShadow(name, value)
+ value is TextStyle -> createFromTextStyle(name, value)
else -> createFromKotlinReflection(name, value)
}
@@ -488,6 +498,7 @@
value.javaClass.isArray -> findFromArray(value, index)
value is Offset -> findFromOffset(value, index)
value is Shadow -> findFromShadow(value, index)
+ value is TextStyle -> findFromTextStyle(value, index)
else -> findFromKotlinReflection(value, index)
}
@@ -912,6 +923,58 @@
return Pair("blurRadius", with(density) { value.blurRadius.toDp() })
}
+ // Temporary handling of TextStyle: remove when TextStyle implements InspectableValue
+ // Hide: paragraphStyle, spanStyle, platformStyle, lineHeightBehavior
+ private fun createFromTextStyle(name: String, value: TextStyle): NodeParameter? {
+ val parameter =
+ NodeParameter(name, ParameterType.String, TextStyle::class.java.simpleName)
+ val elements = parameter.elements
+ create("color", value.color, value)?.let { elements.add(it) }
+ create("fontSize", value.fontSize, value, 1)?.let { elements.add(it) }
+ create("fontWeight", value.fontWeight, value, 2)?.let { elements.add(it) }
+ create("fontStyle", value.fontStyle, value, 3)?.let { elements.add(it) }
+ create("fontSynthesis", value.fontSynthesis, value, 4)?.let { elements.add(it) }
+ create("fontFamily", value.fontFamily, value, 5)?.let { elements.add(it) }
+ create("fontFeatureSettings", value.fontFeatureSettings, value, 6)?.let {
+ elements.add(it)
+ }
+ create("letterSpacing", value.letterSpacing, value, 7)?.let { elements.add(it) }
+ create("baselineShift", value.baselineShift, value, 8)?.let { elements.add(it) }
+ create("textGeometricTransform", value.textGeometricTransform, value, 9)?.let {
+ elements.add(it)
+ }
+ create("localeList", value.localeList, value, 10)?.let { elements.add(it) }
+ create("background", value.background, value, 11)?.let { elements.add(it) }
+ create("textDecoration", value.textDecoration, value, 12)?.let { elements.add(it) }
+ create("shadow", value.shadow, value, 13)?.let { elements.add(it) }
+ create("textDirection", value.textDirection, value, 14)?.let { elements.add(it) }
+ create("lineHeight", value.lineHeight, value, 15)?.let { elements.add(it) }
+ create("textIndent", value.textIndent, value, 16)?.let { elements.add(it) }
+ return parameter
+ }
+
+ private fun findFromTextStyle(value: TextStyle, index: Int): Pair<String, Any?>? =
+ when (index) {
+ 0 -> Pair("color", value.color)
+ 1 -> Pair("fontSize", value.fontSize)
+ 2 -> Pair("fontWeight", value.fontWeight)
+ 3 -> Pair("fontStyle", value.fontStyle)
+ 4 -> Pair("fontSynthesis", value.fontSynthesis)
+ 5 -> Pair("fontFamily", value.fontFamily)
+ 6 -> Pair("fontFeatureSettings", value.fontFeatureSettings)
+ 7 -> Pair("letterSpacing", value.letterSpacing)
+ 8 -> Pair("baselineShift", value.baselineShift)
+ 9 -> Pair("textGeometricTransform", value.textGeometricTransform)
+ 10 -> Pair("localeList", value.localeList)
+ 11 -> Pair("background", value.background)
+ 12 -> Pair("textDecoration", value.textDecoration)
+ 13 -> Pair("shadow", value.shadow)
+ 14 -> Pair("textDirection", value.textDirection)
+ 15 -> Pair("lineHeight", value.lineHeight)
+ 16 -> Pair("textIndent", value.textIndent)
+ else -> null
+ }
+
@Suppress("DEPRECATION")
private fun createFromTextUnit(name: String, value: TextUnit): NodeParameter =
when (value.type) {
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index d1e7571..f203b26 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -113,6 +113,10 @@
public final class ComposedModifierKt {
method public static androidx.compose.ui.Modifier composed(androidx.compose.ui.Modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
+ method public static androidx.compose.ui.Modifier composed(androidx.compose.ui.Modifier, String fullyQualifiedName, Object? key1, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
+ method public static androidx.compose.ui.Modifier composed(androidx.compose.ui.Modifier, String fullyQualifiedName, Object? key1, Object? key2, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
+ method public static androidx.compose.ui.Modifier composed(androidx.compose.ui.Modifier, String fullyQualifiedName, Object? key1, Object? key2, Object? key3, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
+ method public static androidx.compose.ui.Modifier composed(androidx.compose.ui.Modifier, String fullyQualifiedName, Object![]? keys, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
method public static androidx.compose.ui.Modifier materialize(androidx.compose.runtime.Composer, androidx.compose.ui.Modifier modifier);
}
diff --git a/compose/ui/ui/api/public_plus_experimental_current.txt b/compose/ui/ui/api/public_plus_experimental_current.txt
index 1aebd5a..8cd62e2 100644
--- a/compose/ui/ui/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui/api/public_plus_experimental_current.txt
@@ -113,10 +113,10 @@
public final class ComposedModifierKt {
method public static androidx.compose.ui.Modifier composed(androidx.compose.ui.Modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
- method @androidx.compose.ui.ExperimentalComposeUiApi public static androidx.compose.ui.Modifier composed(androidx.compose.ui.Modifier, String fullyQualifiedName, Object? key1, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
- method @androidx.compose.ui.ExperimentalComposeUiApi public static androidx.compose.ui.Modifier composed(androidx.compose.ui.Modifier, String fullyQualifiedName, Object? key1, Object? key2, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
- method @androidx.compose.ui.ExperimentalComposeUiApi public static androidx.compose.ui.Modifier composed(androidx.compose.ui.Modifier, String fullyQualifiedName, Object? key1, Object? key2, Object? key3, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
- method @androidx.compose.ui.ExperimentalComposeUiApi public static androidx.compose.ui.Modifier composed(androidx.compose.ui.Modifier, String fullyQualifiedName, Object![]? keys, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
+ method public static androidx.compose.ui.Modifier composed(androidx.compose.ui.Modifier, String fullyQualifiedName, Object? key1, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
+ method public static androidx.compose.ui.Modifier composed(androidx.compose.ui.Modifier, String fullyQualifiedName, Object? key1, Object? key2, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
+ method public static androidx.compose.ui.Modifier composed(androidx.compose.ui.Modifier, String fullyQualifiedName, Object? key1, Object? key2, Object? key3, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
+ method public static androidx.compose.ui.Modifier composed(androidx.compose.ui.Modifier, String fullyQualifiedName, Object![]? keys, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
method public static androidx.compose.ui.Modifier materialize(androidx.compose.runtime.Composer, androidx.compose.ui.Modifier modifier);
}
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index 9b0aade..1b85c50 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -113,6 +113,10 @@
public final class ComposedModifierKt {
method public static androidx.compose.ui.Modifier composed(androidx.compose.ui.Modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
+ method public static androidx.compose.ui.Modifier composed(androidx.compose.ui.Modifier, String fullyQualifiedName, Object? key1, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
+ method public static androidx.compose.ui.Modifier composed(androidx.compose.ui.Modifier, String fullyQualifiedName, Object? key1, Object? key2, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
+ method public static androidx.compose.ui.Modifier composed(androidx.compose.ui.Modifier, String fullyQualifiedName, Object? key1, Object? key2, Object? key3, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
+ method public static androidx.compose.ui.Modifier composed(androidx.compose.ui.Modifier, String fullyQualifiedName, Object![]? keys, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.platform.InspectorInfo,kotlin.Unit> inspectorInfo, kotlin.jvm.functions.Function1<? super androidx.compose.ui.Modifier,? extends androidx.compose.ui.Modifier> factory);
method public static androidx.compose.ui.Modifier materialize(androidx.compose.runtime.Composer, androidx.compose.ui.Modifier modifier);
}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/ComposedModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/ComposedModifier.kt
index 51901fc..6bca085 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/ComposedModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/ComposedModifier.kt
@@ -71,7 +71,6 @@
* [materialize] must be called to create instance-specific modifiers if you are directly
* applying a [Modifier] to an element tree node.
*/
-@ExperimentalComposeUiApi
fun Modifier.composed(
fullyQualifiedName: String,
key1: Any?,
@@ -102,7 +101,6 @@
* [materialize] must be called to create instance-specific modifiers if you are directly
* applying a [Modifier] to an element tree node.
*/
-@ExperimentalComposeUiApi
fun Modifier.composed(
fullyQualifiedName: String,
key1: Any?,
@@ -134,7 +132,6 @@
* [materialize] must be called to create instance-specific modifiers if you are directly
* applying a [Modifier] to an element tree node.
*/
-@ExperimentalComposeUiApi
fun Modifier.composed(
fullyQualifiedName: String,
key1: Any?,
@@ -167,7 +164,6 @@
* [materialize] must be called to create instance-specific modifiers if you are directly
* applying a [Modifier] to an element tree node.
*/
-@ExperimentalComposeUiApi
fun Modifier.composed(
fullyQualifiedName: String,
vararg keys: Any?,
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/modifier/ModifierLocal.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/modifier/ModifierLocal.kt
index e82c42e..b98ef0a 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/modifier/ModifierLocal.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/modifier/ModifierLocal.kt
@@ -24,7 +24,7 @@
* current layout node. The provided [ModifierLocal]s are also available to layout nodes that are
* children of the current layout node.
*
- * One must create a [ModifierLocal] instance, which cah be referenced by consumers statically.
+ * One must create a [ModifierLocal] instance, which can be referenced by consumers statically.
* [ModifierLocal] instances themselves hold no data, and can be thought of as a type-safe
* identifier for the data being passed to other modifiers to the right of the providing
* modifier or down the tree. [ModifierLocal] factory functions take a single parameter: a
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index db109a0..910a464 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -1084,6 +1084,11 @@
<sha256 value="70cb1d3cc7f82c148e56c95a13255577bafbe343540ca74232ed8258e267d5e7" origin="Generated by Gradle because artifact wasn't signed"/>
</artifact>
</component>
+ <component group="org.jetbrains.kotlin" name="kotlin-serialization" version="1.6.21">
+ <artifact name="kotlin-serialization-1.6.21.jar">
+ <sha256 value="da25598d1a5ab1a747fbdbbb61ac2f04cea7608fa01966d5712b7a93186cc225" origin="GradlePluginPortal artifact is missing the signature. see: https://youtrack.jetbrains.com/issue/KT-52162"/>
+ </artifact>
+ </component>
<component group="org.jetbrains.kotlinx" name="atomicfu" version="0.17.0">
<artifact name="atomicfu-metadata-0.17.0-all.jar">
<sha256 value="b80d58fd737087f8a74d8ed802f5545e550209823e91d0062de0b09343f0c389" origin="Generated by Gradle because artifact wasn't signed"/>
diff --git a/health/health-connect-client/api/current.txt b/health/health-connect-client/api/current.txt
index fa18e8b..2b75ef7 100644
--- a/health/health-connect-client/api/current.txt
+++ b/health/health-connect-client/api/current.txt
@@ -333,6 +333,30 @@
public static final class ElevationGained.Companion {
}
+ public final class HeartRate {
+ ctor public HeartRate(java.time.Instant time, long beatsPerMinute);
+ method public long getBeatsPerMinute();
+ method public java.time.Instant getTime();
+ property public final long beatsPerMinute;
+ property public final java.time.Instant time;
+ }
+
+ public final class HeartRateSeries implements androidx.health.connect.client.records.Record {
+ ctor public HeartRateSeries(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, java.util.List<androidx.health.connect.client.records.HeartRate> samples, optional androidx.health.connect.client.metadata.Metadata metadata);
+ method public java.time.Instant getEndTime();
+ method public java.time.ZoneOffset? getEndZoneOffset();
+ method public androidx.health.connect.client.metadata.Metadata getMetadata();
+ method public java.util.List<androidx.health.connect.client.records.HeartRate> getSamples();
+ method public java.time.Instant getStartTime();
+ method public java.time.ZoneOffset? getStartZoneOffset();
+ property public java.time.Instant endTime;
+ property public java.time.ZoneOffset? endZoneOffset;
+ property public androidx.health.connect.client.metadata.Metadata metadata;
+ property public java.util.List<androidx.health.connect.client.records.HeartRate> samples;
+ property public java.time.Instant startTime;
+ property public java.time.ZoneOffset? startZoneOffset;
+ }
+
public final class Height implements androidx.health.connect.client.records.Record {
ctor public Height(double heightMeters, java.time.Instant time, java.time.ZoneOffset? zoneOffset, optional androidx.health.connect.client.metadata.Metadata metadata);
method public double getHeightMeters();
diff --git a/health/health-connect-client/api/public_plus_experimental_current.txt b/health/health-connect-client/api/public_plus_experimental_current.txt
index fa18e8b..2b75ef7 100644
--- a/health/health-connect-client/api/public_plus_experimental_current.txt
+++ b/health/health-connect-client/api/public_plus_experimental_current.txt
@@ -333,6 +333,30 @@
public static final class ElevationGained.Companion {
}
+ public final class HeartRate {
+ ctor public HeartRate(java.time.Instant time, long beatsPerMinute);
+ method public long getBeatsPerMinute();
+ method public java.time.Instant getTime();
+ property public final long beatsPerMinute;
+ property public final java.time.Instant time;
+ }
+
+ public final class HeartRateSeries implements androidx.health.connect.client.records.Record {
+ ctor public HeartRateSeries(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, java.util.List<androidx.health.connect.client.records.HeartRate> samples, optional androidx.health.connect.client.metadata.Metadata metadata);
+ method public java.time.Instant getEndTime();
+ method public java.time.ZoneOffset? getEndZoneOffset();
+ method public androidx.health.connect.client.metadata.Metadata getMetadata();
+ method public java.util.List<androidx.health.connect.client.records.HeartRate> getSamples();
+ method public java.time.Instant getStartTime();
+ method public java.time.ZoneOffset? getStartZoneOffset();
+ property public java.time.Instant endTime;
+ property public java.time.ZoneOffset? endZoneOffset;
+ property public androidx.health.connect.client.metadata.Metadata metadata;
+ property public java.util.List<androidx.health.connect.client.records.HeartRate> samples;
+ property public java.time.Instant startTime;
+ property public java.time.ZoneOffset? startZoneOffset;
+ }
+
public final class Height implements androidx.health.connect.client.records.Record {
ctor public Height(double heightMeters, java.time.Instant time, java.time.ZoneOffset? zoneOffset, optional androidx.health.connect.client.metadata.Metadata metadata);
method public double getHeightMeters();
diff --git a/health/health-connect-client/api/restricted_current.txt b/health/health-connect-client/api/restricted_current.txt
index 0b35453..e07f699 100644
--- a/health/health-connect-client/api/restricted_current.txt
+++ b/health/health-connect-client/api/restricted_current.txt
@@ -333,6 +333,30 @@
public static final class ElevationGained.Companion {
}
+ public final class HeartRate {
+ ctor public HeartRate(java.time.Instant time, long beatsPerMinute);
+ method public long getBeatsPerMinute();
+ method public java.time.Instant getTime();
+ property public final long beatsPerMinute;
+ property public final java.time.Instant time;
+ }
+
+ public final class HeartRateSeries implements androidx.health.connect.client.records.SeriesRecord<androidx.health.connect.client.records.HeartRate> {
+ ctor public HeartRateSeries(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, java.util.List<androidx.health.connect.client.records.HeartRate> samples, optional androidx.health.connect.client.metadata.Metadata metadata);
+ method public java.time.Instant getEndTime();
+ method public java.time.ZoneOffset? getEndZoneOffset();
+ method public androidx.health.connect.client.metadata.Metadata getMetadata();
+ method public java.util.List<androidx.health.connect.client.records.HeartRate> getSamples();
+ method public java.time.Instant getStartTime();
+ method public java.time.ZoneOffset? getStartZoneOffset();
+ property public java.time.Instant endTime;
+ property public java.time.ZoneOffset? endZoneOffset;
+ property public androidx.health.connect.client.metadata.Metadata metadata;
+ property public java.util.List<androidx.health.connect.client.records.HeartRate> samples;
+ property public java.time.Instant startTime;
+ property public java.time.ZoneOffset? startZoneOffset;
+ }
+
public final class Height implements androidx.health.connect.client.records.InstantaneousRecord {
ctor public Height(double heightMeters, java.time.Instant time, java.time.ZoneOffset? zoneOffset, optional androidx.health.connect.client.metadata.Metadata metadata);
method public double getHeightMeters();
@@ -393,6 +417,11 @@
property public abstract androidx.health.connect.client.metadata.Metadata metadata;
}
+ @kotlin.PublishedApi internal interface SeriesRecord<T> extends androidx.health.connect.client.records.IntervalRecord {
+ method public java.util.List<T> getSamples();
+ property public abstract java.util.List<T> samples;
+ }
+
public final class Steps implements androidx.health.connect.client.records.IntervalRecord {
ctor public Steps(long count, java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, optional androidx.health.connect.client.metadata.Metadata metadata);
method public long getCount();
diff --git a/health/health-connect-client/src/main/java/androidx/health/connect/client/records/HeartRateSeries.kt b/health/health-connect-client/src/main/java/androidx/health/connect/client/records/HeartRateSeries.kt
index 981f549..95201d1 100644
--- a/health/health-connect-client/src/main/java/androidx/health/connect/client/records/HeartRateSeries.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/connect/client/records/HeartRateSeries.kt
@@ -15,14 +15,12 @@
*/
package androidx.health.connect.client.records
-import androidx.annotation.RestrictTo
import androidx.health.connect.client.aggregate.AggregateMetric
import androidx.health.connect.client.metadata.Metadata
import java.time.Instant
import java.time.ZoneOffset
/** Captures the user's heart rate. Each record represents a series of measurements. */
-@RestrictTo(RestrictTo.Scope.LIBRARY) // Will be made public after API reviews
public class HeartRateSeries(
override val startTime: Instant,
override val startZoneOffset: ZoneOffset?,
@@ -100,7 +98,6 @@
*
* @param beatsPerMinute Heart beats per minute. Validation range: 1-300.
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY) // Will be made public after API reviews
public class HeartRate(
val time: Instant,
val beatsPerMinute: Long,
diff --git a/health/health-connect-client/src/main/java/androidx/health/connect/client/records/SeriesRecord.kt b/health/health-connect-client/src/main/java/androidx/health/connect/client/records/SeriesRecord.kt
index 9fb8ee6..b98d9e5 100644
--- a/health/health-connect-client/src/main/java/androidx/health/connect/client/records/SeriesRecord.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/connect/client/records/SeriesRecord.kt
@@ -17,6 +17,7 @@
/** A record that contains a series of measurements. */
// Consider exposing this one and other similar interfaces!
+@PublishedApi
internal interface SeriesRecord<out T : Any> : IntervalRecord {
val samples: List<T>
diff --git a/navigation/navigation-compose-lint/src/main/java/androidx/navigation/compose/lint/UnrememberedGetBackStackEntryDetector.kt b/navigation/navigation-compose-lint/src/main/java/androidx/navigation/compose/lint/UnrememberedGetBackStackEntryDetector.kt
index 00a0b44..4313039 100644
--- a/navigation/navigation-compose-lint/src/main/java/androidx/navigation/compose/lint/UnrememberedGetBackStackEntryDetector.kt
+++ b/navigation/navigation-compose-lint/src/main/java/androidx/navigation/compose/lint/UnrememberedGetBackStackEntryDetector.kt
@@ -20,8 +20,8 @@
import androidx.compose.lint.Name
import androidx.compose.lint.Package
-import androidx.compose.lint.invokedInComposableBodyAndNotRemembered
import androidx.compose.lint.isInPackageName
+import androidx.compose.lint.isNotRememberedWithKeys
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
@@ -31,12 +31,12 @@
import com.android.tools.lint.detector.api.Severity
import com.android.tools.lint.detector.api.SourceCodeScanner
import com.intellij.psi.PsiMethod
-import org.jetbrains.uast.UCallExpression
import java.util.EnumSet
+import org.jetbrains.uast.UCallExpression
/**
* [Detector] that checks `getBackStackEntry` calls to make sure that if they are called inside a
- * Composable body, they are `remember`ed.
+ * Composable body, they are `remember`ed with a `NavBackStackEntry` as a key.
*/
class UnrememberedGetBackStackEntryDetector : Detector(), SourceCodeScanner {
override fun getApplicableMethodNames(): List<String> = listOf(
@@ -46,12 +46,13 @@
override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
if (!method.isInPackageName(PackageName)) return
- if (node.invokedInComposableBodyAndNotRemembered()) {
+ if (node.isNotRememberedWithKeys(NavBackStackEntry)) {
context.report(
UnrememberedGetBackStackEntry,
node,
context.getNameLocation(node),
- "Calling getBackStackEntry during composition without using `remember`"
+ "Calling getBackStackEntry during composition without using `remember` " +
+ "with a NavBackStackEntry key"
)
}
}
@@ -59,11 +60,15 @@
companion object {
val UnrememberedGetBackStackEntry = Issue.create(
"UnrememberedGetBackStackEntry",
- "Calling getBackStackEntry during composition with using `remember`",
+ "Calling getBackStackEntry during composition without using `remember`" +
+ "with a NavBackStackEntry key",
"Backstack entries retrieved during composition need to be `remember`ed, otherwise " +
- "they will be retrieved from the navController again, and be changed. Either " +
- "hoist the state to an object that is not created during composition, or wrap " +
- "the state in a call to `remember`.",
+ "they will be retrieved from the navController again, and be changed. You also " +
+ "need to pass in a key of a NavBackStackEntry to the remember call or they will " +
+ "not be updated properly. If this is in a `NavGraphBuilder.composable` scope, " +
+ "you should pass in the lambda's given entry as the key. Either hoist the state " +
+ "to an object that is not created during composition, or wrap the state in a " +
+ "call to `remember` with a `NavBackStackEntry` as a key.",
Category.CORRECTNESS, 3, Severity.ERROR,
Implementation(
UnrememberedGetBackStackEntryDetector::class.java,
@@ -75,3 +80,4 @@
private val PackageName = Package("androidx.navigation")
private val GetBackStackEntry = Name(PackageName, "getBackStackEntry")
+private val NavBackStackEntry = Name(PackageName, "NavBackStackEntry")
diff --git a/navigation/navigation-compose-lint/src/test/java/androidx/navigation/compose/lint/UnrememberedGetBackStackEntryDetectorTest.kt b/navigation/navigation-compose-lint/src/test/java/androidx/navigation/compose/lint/UnrememberedGetBackStackEntryDetectorTest.kt
index defabf5..b33210d 100644
--- a/navigation/navigation-compose-lint/src/test/java/androidx/navigation/compose/lint/UnrememberedGetBackStackEntryDetectorTest.kt
+++ b/navigation/navigation-compose-lint/src/test/java/androidx/navigation/compose/lint/UnrememberedGetBackStackEntryDetectorTest.kt
@@ -108,28 +108,28 @@
.run()
.expect(
"""
-src/com/example/{.kt:10: Error: Calling getBackStackEntry during composition without using remember [UnrememberedGetBackStackEntry]
+src/com/example/{.kt:10: Error: Calling getBackStackEntry during composition without using remember with a NavBackStackEntry key [UnrememberedGetBackStackEntry]
navController.getBackStackEntry("test")
~~~~~~~~~~~~~~~~~
-src/com/example/{.kt:15: Error: Calling getBackStackEntry during composition without using remember [UnrememberedGetBackStackEntry]
+src/com/example/{.kt:15: Error: Calling getBackStackEntry during composition without using remember with a NavBackStackEntry key [UnrememberedGetBackStackEntry]
navController.getBackStackEntry("test")
~~~~~~~~~~~~~~~~~
-src/com/example/{.kt:20: Error: Calling getBackStackEntry during composition without using remember [UnrememberedGetBackStackEntry]
+src/com/example/{.kt:20: Error: Calling getBackStackEntry during composition without using remember with a NavBackStackEntry key [UnrememberedGetBackStackEntry]
navController.getBackStackEntry("test")
~~~~~~~~~~~~~~~~~
-src/com/example/{.kt:30: Error: Calling getBackStackEntry during composition without using remember [UnrememberedGetBackStackEntry]
+src/com/example/{.kt:30: Error: Calling getBackStackEntry during composition without using remember with a NavBackStackEntry key [UnrememberedGetBackStackEntry]
navController.getBackStackEntry("test")
~~~~~~~~~~~~~~~~~
-src/com/example/{.kt:34: Error: Calling getBackStackEntry during composition without using remember [UnrememberedGetBackStackEntry]
+src/com/example/{.kt:34: Error: Calling getBackStackEntry during composition without using remember with a NavBackStackEntry key [UnrememberedGetBackStackEntry]
navController.getBackStackEntry("test")
~~~~~~~~~~~~~~~~~
-src/com/example/{.kt:41: Error: Calling getBackStackEntry during composition without using remember [UnrememberedGetBackStackEntry]
+src/com/example/{.kt:41: Error: Calling getBackStackEntry during composition without using remember with a NavBackStackEntry key [UnrememberedGetBackStackEntry]
navController.getBackStackEntry("test")
~~~~~~~~~~~~~~~~~
-src/com/example/{.kt:46: Error: Calling getBackStackEntry during composition without using remember [UnrememberedGetBackStackEntry]
+src/com/example/{.kt:46: Error: Calling getBackStackEntry during composition without using remember with a NavBackStackEntry key [UnrememberedGetBackStackEntry]
navController.getBackStackEntry("test")
~~~~~~~~~~~~~~~~~
-src/com/example/{.kt:54: Error: Calling getBackStackEntry during composition without using remember [UnrememberedGetBackStackEntry]
+src/com/example/{.kt:54: Error: Calling getBackStackEntry during composition without using remember with a NavBackStackEntry key [UnrememberedGetBackStackEntry]
val entry = navController.getBackStackEntry("test")
~~~~~~~~~~~~~~~~~
8 errors, 0 warnings
@@ -138,7 +138,7 @@
}
@Test
- fun rememberedInsideComposableBody() {
+ fun rememberedInsideComposableBodyWithoutEntryKey() {
lint().files(
kotlin(
"""
@@ -196,6 +196,102 @@
NAV_CONTROLLER
)
.run()
+ .expect(
+ """
+src/com/example/test.kt:10: Error: Calling getBackStackEntry during composition without using remember with a NavBackStackEntry key [UnrememberedGetBackStackEntry]
+ val entry = remember { navController.getBackStackEntry("test") }
+ ~~~~~~~~~~~~~~~~~
+src/com/example/test.kt:15: Error: Calling getBackStackEntry during composition without using remember with a NavBackStackEntry key [UnrememberedGetBackStackEntry]
+ val entry = remember { navController.getBackStackEntry("test") }
+ ~~~~~~~~~~~~~~~~~
+src/com/example/test.kt:20: Error: Calling getBackStackEntry during composition without using remember with a NavBackStackEntry key [UnrememberedGetBackStackEntry]
+ val entry = remember { navController.getBackStackEntry("test") }
+ ~~~~~~~~~~~~~~~~~
+src/com/example/test.kt:30: Error: Calling getBackStackEntry during composition without using remember with a NavBackStackEntry key [UnrememberedGetBackStackEntry]
+ val entry = remember { navController.getBackStackEntry("test") }
+ ~~~~~~~~~~~~~~~~~
+src/com/example/test.kt:34: Error: Calling getBackStackEntry during composition without using remember with a NavBackStackEntry key [UnrememberedGetBackStackEntry]
+ val entry = remember { navController.getBackStackEntry("test") }
+ ~~~~~~~~~~~~~~~~~
+src/com/example/test.kt:41: Error: Calling getBackStackEntry during composition without using remember with a NavBackStackEntry key [UnrememberedGetBackStackEntry]
+ val entry = remember { navController.getBackStackEntry("test") }
+ ~~~~~~~~~~~~~~~~~
+src/com/example/test.kt:46: Error: Calling getBackStackEntry during composition without using remember with a NavBackStackEntry key [UnrememberedGetBackStackEntry]
+ val entry = remember { navController.getBackStackEntry("test") }
+ ~~~~~~~~~~~~~~~~~
+7 errors, 0 warnings
+ """
+ )
+ }
+
+ @Test
+ fun rememberedInsideComposableBodyWithEntryKey() {
+ lint().files(
+ kotlin(
+ """
+ package com.example
+
+ import androidx.compose.runtime.*
+ import androidx.navigation.NavController
+ import androidx.navigation.NavBackStackEntry
+
+ @Composable
+ fun Test() {
+ val navController = NavController()
+ val rememberedEntry = NavBackStackEntry()
+ val entry = remember(rememberedEntry) { navController.getBackStackEntry("test") }
+ }
+
+ val lambda = @Composable {
+ val navController = NavController()
+ val rememberedEntry = NavBackStackEntry()
+ val entry = remember(rememberedEntry) { navController.getBackStackEntry("test") }
+ }
+
+ val lambda2: @Composable () -> Unit = {
+ val navController = NavController()
+ val rememberedEntry = NavBackStackEntry()
+ val entry = remember(rememberedEntry) { navController.getBackStackEntry("test") }
+ }
+
+ @Composable
+ fun LambdaParameter(content: @Composable () -> Unit) {}
+
+ @Composable
+ fun Test2() {
+ LambdaParameter(content = {
+ val navController = NavController()
+ val rememberedEntry = NavBackStackEntry()
+ val entry = remember(rememberedEntry) { navController.getBackStackEntry("test") }
+ })
+ LambdaParameter {
+ val navController = NavController()
+ val rememberedEntry = NavBackStackEntry()
+ val entry = remember(rememberedEntry) { navController.getBackStackEntry("test") }
+ }
+ }
+
+ fun test3() {
+ val localLambda1 = @Composable {
+ val navController = NavController()
+ val rememberedEntry = NavBackStackEntry()
+ val entry = remember(rememberedEntry) { navController.getBackStackEntry("test") }
+ }
+
+ val localLambda2: @Composable () -> Unit = {
+ val navController = NavController()
+ val rememberedEntry = NavBackStackEntry()
+ val entry = remember(rememberedEntry) { navController.getBackStackEntry("test") }
+ }
+ }
+ """
+ ),
+ Stubs.Composable,
+ Stubs.Remember,
+ NAV_BACK_STACK_ENTRY,
+ NAV_CONTROLLER
+ )
+ .run()
.expectClean()
}
diff --git a/navigation/navigation-compose/samples/build.gradle b/navigation/navigation-compose/samples/build.gradle
index 5f4d6a8..981abe6 100644
--- a/navigation/navigation-compose/samples/build.gradle
+++ b/navigation/navigation-compose/samples/build.gradle
@@ -47,3 +47,15 @@
android {
namespace "androidx.navigation.compose.samples"
}
+
+// Workaround for https://github.com/gradle/gradle/issues/19882
+configurations.all {
+ resolutionStrategy.dependencySubstitution {
+ substitute(module("androidx.lifecycle:lifecycle-runtime:")).
+ using project(":lifecycle:lifecycle-runtime")
+ substitute(module("androidx.lifecycle:lifecycle-runtime-ktx:")).
+ using project(":lifecycle:lifecycle-runtime-ktx")
+ substitute(module("androidx.savedstate:savedstate-ktx:")).
+ using project(":savedstate:savedstate-ktx")
+ }
+}
diff --git a/navigation/navigation-runtime-lint/build.gradle b/navigation/navigation-runtime-lint/build.gradle
new file mode 100644
index 0000000..c586a3d
--- /dev/null
+++ b/navigation/navigation-runtime-lint/build.gradle
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+import androidx.build.LibraryType
+
+plugins {
+ id("AndroidXPlugin")
+ id("kotlin")
+}
+
+dependencies {
+ compileOnly(libs.androidLintMinApi)
+ compileOnly(libs.kotlinStdlib)
+
+ testImplementation(libs.kotlinStdlib)
+ testImplementation(libs.androidLint)
+ testImplementation(libs.androidLintTests)
+ testImplementation(libs.junit)
+ testImplementation(libs.truth)
+}
+
+androidx {
+ name = "Navigation Runtime Lint"
+ type = LibraryType.LINT
+ mavenGroup = LibraryGroups.NAVIGATION
+ inceptionYear = "2022"
+ description = "Lint checks for Navigation Runtime"
+}
diff --git a/navigation/navigation-runtime-lint/src/main/java/androidx/navigation/runtime/lint/DeepLinkInActivityDestinationDetector.kt b/navigation/navigation-runtime-lint/src/main/java/androidx/navigation/runtime/lint/DeepLinkInActivityDestinationDetector.kt
new file mode 100644
index 0000000..89d6730
--- /dev/null
+++ b/navigation/navigation-runtime-lint/src/main/java/androidx/navigation/runtime/lint/DeepLinkInActivityDestinationDetector.kt
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+
+@file:Suppress("UnstableApiUsage")
+
+package androidx.navigation.runtime.lint
+
+import com.android.SdkConstants.TAG_ACTIVITY
+import com.android.SdkConstants.TAG_DEEP_LINK
+import com.android.resources.ResourceFolderType
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Incident
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.ResourceXmlDetector
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.XmlContext
+import java.util.Collections
+import org.w3c.dom.Element
+
+/**
+ * Lint check for detecting use of <deeplink> inside of <activity>.
+ */
+class DeepLinkInActivityDestinationDetector : ResourceXmlDetector() {
+
+ override fun appliesTo(folderType: ResourceFolderType): Boolean {
+ return folderType == ResourceFolderType.NAVIGATION
+ }
+
+ override fun getApplicableElements(): Collection<String>? = Collections.singleton(TAG_DEEP_LINK)
+
+ override fun visitElement(context: XmlContext, element: Element) {
+ if (element.parentNode?.nodeName == TAG_ACTIVITY) {
+ val incident = Incident(context)
+ .issue(DeepLinkInActivityDestination)
+ .location(context.getLocation(element))
+ .message("Do not attach a <deeplink> to an <activity> destination. " +
+ "Attach the deeplink directly to the second activity or the start " +
+ "destination of a nav host in the second activity instead.")
+ .scope(context.getNameLocation(element))
+ context.report(incident)
+ }
+ }
+
+ companion object {
+ val DeepLinkInActivityDestination = Issue.create(
+ id = "DeepLinkInActivityDestination",
+ briefDescription = "A <deeplink> should not be attached to an <activity> destination",
+ explanation = """Attaching a <deeplink> to an <activity> destination will never give \
+ the right behavior when using an implicit deep link on another app's task \
+ (where the system back should immediately take the user back to the app that \
+ triggered the deep link). Instead, attach the deep link directly to \
+ the second activity (either by manually writing the appropriate <intent-filter> \
+ or by adding the <deeplink> to the start destination of a nav host in that second \
+ activity).""",
+ category = Category.CORRECTNESS,
+ severity = Severity.WARNING,
+ implementation = Implementation(
+ DeepLinkInActivityDestinationDetector::class.java, Scope.RESOURCE_FILE_SCOPE
+ ),
+ androidSpecific = true
+ )
+ }
+}
diff --git a/navigation/navigation-runtime-lint/src/main/java/androidx/navigation/runtime/lint/NavigationRuntimeIssueRegistry.kt b/navigation/navigation-runtime-lint/src/main/java/androidx/navigation/runtime/lint/NavigationRuntimeIssueRegistry.kt
new file mode 100644
index 0000000..b4d90ba
--- /dev/null
+++ b/navigation/navigation-runtime-lint/src/main/java/androidx/navigation/runtime/lint/NavigationRuntimeIssueRegistry.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+@file:Suppress("UnstableApiUsage")
+
+package androidx.navigation.runtime.lint
+
+import com.android.tools.lint.client.api.IssueRegistry
+import com.android.tools.lint.client.api.Vendor
+import com.android.tools.lint.detector.api.CURRENT_API
+
+/**
+ * [IssueRegistry] containing runtime specific lint issues.
+ */
+class NavigationRuntimeIssueRegistry : IssueRegistry() {
+ // Tests are run with this version. We ensure that with ApiLintVersionsTest
+ override val api = 13
+ override val minApi = CURRENT_API
+ override val issues get() = listOf(
+ DeepLinkInActivityDestinationDetector.DeepLinkInActivityDestination
+ )
+ override val vendor = Vendor(
+ feedbackUrl = "https://issuetracker.google.com/issues/new?component=409828",
+ vendorName = "Android Open Source Project",
+ identifier = "androidx.navigation.runtime"
+ )
+}
diff --git a/navigation/navigation-runtime-lint/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry b/navigation/navigation-runtime-lint/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry
new file mode 100644
index 0000000..89f8da4
--- /dev/null
+++ b/navigation/navigation-runtime-lint/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry
@@ -0,0 +1 @@
+androidx.navigation.runtime.lint.NavigationRuntimeIssueRegistry
diff --git a/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/ApiLintVersionsTest.kt b/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/ApiLintVersionsTest.kt
new file mode 100644
index 0000000..64f4740
--- /dev/null
+++ b/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/ApiLintVersionsTest.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.navigation.runtime.lint
+
+import com.android.tools.lint.client.api.LintClient
+import com.android.tools.lint.detector.api.CURRENT_API
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class ApiLintVersionsTest {
+ @Test
+ fun versionsCheck() {
+ LintClient.clientName = LintClient.CLIENT_UNIT_TESTS
+
+ val registry = NavigationRuntimeIssueRegistry()
+ assertThat(registry.api).isEqualTo(CURRENT_API)
+ assertThat(registry.minApi).isEqualTo(10)
+ }
+}
diff --git a/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/DeepLinkInActivityDestinationDetectorTest.kt b/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/DeepLinkInActivityDestinationDetectorTest.kt
new file mode 100644
index 0000000..a9e4177
--- /dev/null
+++ b/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/DeepLinkInActivityDestinationDetectorTest.kt
@@ -0,0 +1,101 @@
+/*
+ * 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.navigation.runtime.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class DeepLinkInActivityDestinationDetectorTest : LintDetectorTest() {
+ override fun getDetector(): Detector = DeepLinkInActivityDestinationDetector()
+
+ override fun getIssues(): MutableList<Issue> {
+ return mutableListOf(DeepLinkInActivityDestinationDetector.DeepLinkInActivityDestination)
+ }
+
+ @Test
+ fun expectPass() {
+ lint().files(
+ xml("res/navigation/nav_main.xml",
+ """
+<navigation xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/nav_main"
+ app:startDestination="@id/fragment_main"
+ >
+
+ <fragment
+ android:id="@+id/fragment_main"
+ android:name="com.example.deeplink.MainFragment"
+ >
+ <deepLink app:uri="www.example.com" />
+ </fragment>
+
+ <activity
+ android:id="@+id/activity_deep_link"
+ android:name="com.example.deeplink.Activity"
+ />
+
+</navigation>
+ """
+ )
+ )
+ .run()
+ .expectClean()
+ }
+
+ @Test
+ fun expectFail() {
+ lint().files(
+ xml("res/navigation/nav_main.xml",
+ """
+<navigation xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/nav_main"
+ app:startDestination="@id/fragment_main"
+ >
+
+ <fragment
+ android:id="@+id/fragment_main"
+ android:name="com.example.deeplink.MainFragment"
+ />
+
+ <activity
+ android:id="@+id/activity_deep_link"
+ android:name="com.example.deeplink.DeepLinkActivity"
+ >
+ <deepLink app:uri="www.example.com" />
+ </activity>
+
+</navigation>
+ """
+ )
+ )
+ .run()
+ .expect("""
+res/navigation/nav_main.xml:17: Warning: Do not attach a <deeplink> to an <activity> destination. Attach the deeplink directly to the second activity or the start destination of a nav host in the second activity instead. [DeepLinkInActivityDestination]
+ <deepLink app:uri="www.example.com" />
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+0 errors, 1 warnings
+ """
+ )
+ }
+}
diff --git a/navigation/navigation-runtime/build.gradle b/navigation/navigation-runtime/build.gradle
index 34ea7a4..151198c6 100644
--- a/navigation/navigation-runtime/build.gradle
+++ b/navigation/navigation-runtime/build.gradle
@@ -47,6 +47,8 @@
androidTestImplementation(libs.dexmakerMockito, excludes.bytebuddy)
androidTestImplementation(libs.kotlinStdlib)
androidTestImplementation(libs.multidex)
+
+ lintPublish(project(':navigation:navigation-runtime-lint'))
}
android {
@@ -69,4 +71,4 @@
// Allow usage of Kotlin's @OptIn.
freeCompilerArgs += ["-opt-in=kotlin.RequiresOptIn"]
}
-}
\ No newline at end of file
+}
diff --git a/playground-common/androidx-shared.properties b/playground-common/androidx-shared.properties
index c31e4d0..d678753 100644
--- a/playground-common/androidx-shared.properties
+++ b/playground-common/androidx-shared.properties
@@ -25,21 +25,56 @@
# at configuration time.
org.gradle.jvmargs=-Xmx4g -XX:+HeapDumpOnOutOfMemoryError -XX:+UseParallelGC -XX:MaxMetaspaceSize=512m -Dkotlin.daemon.jvm.options=-XX:MaxMetaspaceSize=1g -Dlint.nullness.ignore-deprecated=true
+org.gradle.configureondemand=true
+org.gradle.parallel=true
+org.gradle.caching=true
+# Disabled due to https://github.com/gradle/gradle/issues/18626
+# org.gradle.vfs.watch=true
+org.gradle.dependency.verification.console=verbose
+org.gradle.unsafe.configuration-cache=true
+org.gradle.unsafe.configuration-cache-problems=warn
+org.gradle.unsafe.configuration-cache.max-problems=4000
+
+android.uniquePackageNames=false
+android.enableAdditionalTestOutput=true
android.useAndroidX=true
+android.nonTransitiveRClass=true
+android.disableAutomaticComponentCreation=true
+# Suppress pointless warning about mpp being experimental
+kotlin.mpp.stability.nowarn=true
+# Workaround for b/141364941
+android.forceJacocoOutOfProcess=true
+android.experimental.lint.missingBaselineIsEmptyBaseline=true
+
+# Generate versioned API files
+androidx.writeVersionedApiFiles=true
+
# Disable features we do not use
android.defaults.buildfeatures.aidl=false
android.defaults.buildfeatures.buildconfig=false
android.defaults.buildfeatures.renderscript=false
android.defaults.buildfeatures.resvalues=false
android.defaults.buildfeatures.shaders=false
-android.disableAutomaticComponentCreation=true
-android.experimental.lint.missingBaselineIsEmptyBaseline=true
-org.gradle.configureondemand=true
-org.gradle.parallel=true
-org.gradle.caching=true
-# Disabled due to https://github.com/gradle/gradle/issues/18626
-# org.gradle.vfs.watch=true
-org.gradle.unsafe.configuration-cache=true
-org.gradle.unsafe.configuration-cache-problems=warn
-org.gradle.unsafe.configuration-cache.max-problems=4000
+# do not automatically include stdlib
+kotlin.stdlib.default.dependency=false
+
+# https://b.corp.google.com/issues/227307216
+kotlin.mpp.absentAndroidTarget.nowarn=true
+
+# Enable adding baseline-prof.txt files to AAR artifacts
+android.experimental.enableArtProfiles=true
+
+# Disallow resolving dependencies at configuration time, which is a slight performance problem
+android.dependencyResolutionAtConfigurationTime.disallow=true
+android.suppressUnsupportedOptionWarnings=android.suppressUnsupportedOptionWarnings,android.dependencyResolutionAtConfigurationTime.disallow,android.experimental.lint.missingBaselineIsEmptyBaseline
+# Workaround for b/162074215
+android.includeDependencyInfoInApks=false
+
+# Properties we often want to toggle
+# ksp.version.check=false
+# androidx.compose.multiplatformEnabled=true
+
+# Do _not_ toggle or override unless you have read and understand this:
+# https://blog.jetbrains.com/kotlin/2021/10/important-ua-parser-js-exploit-and-kotlin-js/
+androidx.kmp.js.enabled=false
diff --git a/room/integration-tests/kotlintestapp/build.gradle b/room/integration-tests/kotlintestapp/build.gradle
index 128ef22..00e68bc 100644
--- a/room/integration-tests/kotlintestapp/build.gradle
+++ b/room/integration-tests/kotlintestapp/build.gradle
@@ -86,7 +86,6 @@
project(path: ":room:room-compiler", configuration: "shadowAndImplementation")
)
androidTestImplementation(projectOrArtifact(":lifecycle:lifecycle-livedata-ktx"))
- androidTestImplementation(projectOrArtifact(":arch:core:core-runtime")) // Added for b/155802460
androidTestImplementation(libs.testExtJunit)
androidTestImplementation(libs.testCore)
androidTestImplementation(libs.testRunner) {
@@ -100,14 +99,13 @@
androidTestImplementation(libs.truth)
androidTestImplementation(libs.kotlinTest)
androidTestImplementation(project(":room:room-guava"))
- androidTestImplementation(project(":room:room-paging")) // Added for b/155802460
androidTestImplementation(project(":room:room-testing"))
androidTestImplementation(project(":room:room-rxjava2"))
androidTestImplementation(project(":room:room-rxjava3"))
androidTestImplementation(project(":room:room-ktx"))
androidTestImplementation(project(":internal-testutils-common"))
androidTestImplementation("androidx.arch.core:core-testing:2.0.1")
- androidTestImplementation(projectOrArtifact(":paging:paging-runtime"))
+ androidTestImplementation("androidx.paging:paging-runtime:3.1.1")
androidTestImplementation(libs.guavaAndroid)
androidTestImplementation(libs.rxjava2)
testImplementation(libs.mockitoCore)
diff --git a/room/room-paging/build.gradle b/room/room-paging/build.gradle
index 84367bf..e1a2e69 100644
--- a/room/room-paging/build.gradle
+++ b/room/room-paging/build.gradle
@@ -47,7 +47,7 @@
api(project(":room:room-runtime"))
implementation(project(":room:room-ktx"))
- api("androidx.paging:paging-common:3.1.0")
+ api("androidx.paging:paging-common:3.1.1")
androidTestImplementation(libs.kotlinCoroutinesTest)
androidTestImplementation(libs.multidex)
diff --git a/settings.gradle b/settings.gradle
index 8bd3b85..afc7025 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -431,6 +431,7 @@
includeProject(":compose:lint:common-test", [BuildType.COMPOSE, BuildType.MAIN])
includeProject(":compose:material", [BuildType.COMPOSE])
includeProject(":compose:material3:material3", [BuildType.COMPOSE])
+includeProject(":compose:material3:material3-lint", [BuildType.COMPOSE])
includeProject(":compose:material3:material3-window-size-class", [BuildType.COMPOSE])
includeProject(":compose:material3:material3-window-size-class:material3-window-size-class-samples", "compose/material3/material3-window-size-class/samples", [BuildType.COMPOSE])
includeProject(":compose:material:material", [BuildType.COMPOSE])
@@ -659,6 +660,7 @@
includeProject(":navigation:navigation-integration-tests", "navigation/integration-tests", [BuildType.MAIN, BuildType.FLAN])
includeProject(":navigation:navigation-integration-tests:testapp", "navigation/integration-tests/testapp", [BuildType.MAIN, BuildType.FLAN])
includeProject(":navigation:navigation-runtime", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE])
+includeProject(":navigation:navigation-runtime-lint", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE])
includeProject(":navigation:navigation-runtime-ktx", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE])
includeProject(":navigation:navigation-runtime-truth", [BuildType.MAIN, BuildType.FLAN])
includeProject(":navigation:navigation-safe-args-generator", [BuildType.MAIN, BuildType.FLAN])
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/PickerDemo.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/PickerDemo.kt
index 8a0ad6b..be31ccb 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/PickerDemo.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/PickerDemo.kt
@@ -221,7 +221,7 @@
initiallySelectedOption = 6
),
focusRequester = focusRequester1,
- modifier = Modifier.size(40.dp, 100.dp),
+ modifier = Modifier.size(64.dp, 100.dp),
readOnlyLabel = { LabelText("Hour") }
) { hour: Int ->
TimePiece(
diff --git a/wear/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml b/wear/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
index dac72dd..e7975fc 100644
--- a/wear/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
+++ b/wear/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
@@ -50,10 +50,6 @@
android:theme="@style/AppTheme"
android:exported="true">
<intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- <intent-filter>
<action android:name=
"androidx.wear.compose.integration.macrobenchmark.target.SWIPE_ACTIVITY" />
<category android:name="android.intent.category.DEFAULT" />
@@ -76,6 +72,10 @@
android:theme="@style/AppTheme"
android:exported="true">
<intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <intent-filter>
<action android:name=
"androidx.wear.compose.integration.macrobenchmark.target.BASELINE_ACTIVITY" />
<category android:name="android.intent.category.DEFAULT" />
diff --git a/wear/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/wear/compose/integration/macrobenchmark/target/BaselineActivity.kt b/wear/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/wear/compose/integration/macrobenchmark/target/BaselineActivity.kt
index 756bf60..84a58aa 100644
--- a/wear/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/wear/compose/integration/macrobenchmark/target/BaselineActivity.kt
+++ b/wear/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/wear/compose/integration/macrobenchmark/target/BaselineActivity.kt
@@ -19,11 +19,15 @@
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
+import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
@@ -83,6 +87,14 @@
private val ALERT_DIALOG = "alert-dialog"
private val CONFIRMATION_DIALOG = "confirmation-dialog"
+private val BUTTONS = "buttons"
+private val CARDS = "cards"
+private val CHIPS = "chips"
+private val DIALOGS = "dialogs"
+private val PICKER = "picker"
+private val PROGRESSINDICATORS = "progressindicators"
+private val SLIDER = "slider"
+private val START_INDEX = "start-index"
private val STEPPER = "stepper"
private val SWIPE_DISMISS = "swipe-dismiss"
private val PROGRESS_INDICATOR = "progress-indicator"
@@ -101,39 +113,18 @@
Scaffold(
timeText = { TimeText() },
positionIndicator = { PositionIndicator(scrollState = scrollState) },
- vignette = {
- Vignette(vignettePosition = VignettePosition.TopAndBottom)
- },
+ vignette = { Vignette(vignettePosition = VignettePosition.TopAndBottom) },
) {
SwipeDismissableNavHost(
navController = navController,
- startDestination = "start",
+ startDestination = START_INDEX,
modifier = Modifier
.background(MaterialTheme.colors.background)
.semantics { contentDescription = SWIPE_DISMISS }
) {
- composable("start") {
- Box {
- CurvedTexts()
- Column(
- modifier = Modifier
- .fillMaxSize()
- .verticalScroll(state = scrollState)
- .padding(vertical = 16.dp)
- .semantics { contentDescription = CONTENT_DESCRIPTION },
- verticalArrangement = Arrangement.Center,
- horizontalAlignment = Alignment.CenterHorizontally
- ) {
- Dialogs(navController)
- Steppers(navController)
- ProgressIndicators(navController)
- Buttons()
- Cards()
- Chips()
- Sliders()
- Pickers()
- }
- }
+ composable(START_INDEX) { StartIndex(navController, scrollState) }
+ composable(DIALOGS) {
+ Dialogs(navController)
}
composable(ALERT_DIALOG) {
Alert(
@@ -148,17 +139,11 @@
content = { Text("Confirmation") },
)
}
- composable(STEPPER) {
- var value by remember { mutableStateOf(2f) }
- Stepper(
- value = value,
- onValueChange = { value = it },
- increaseIcon = { Icon(StepperDefaults.Increase, "Increase") },
- decreaseIcon = { Icon(StepperDefaults.Decrease, "Decrease") },
- valueRange = 1f..4f,
- steps = 7
- ) { Text("Value: $value") }
- }
+ composable(BUTTONS) { Buttons() }
+ composable(CARDS) { Cards() }
+ composable(CHIPS) { Chips() }
+ composable(PICKER) { Picker(scrollState) }
+ composable(PROGRESSINDICATORS) { ProgressIndicators(navController) }
composable(PROGRESS_INDICATOR) {
CircularProgressIndicator(
modifier = Modifier.fillMaxSize(),
@@ -170,11 +155,14 @@
composable(PROGRESS_INDICATOR_INDETERMINATE) {
Column(
verticalArrangement = Arrangement.Center,
- horizontalAlignment = Alignment.CenterHorizontally
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier.fillMaxSize(),
) {
CircularProgressIndicator()
}
}
+ composable(SLIDER) { Slider() }
+ composable(STEPPER) { Stepper() }
}
}
}
@@ -183,49 +171,234 @@
}
@Composable
+fun StartIndex(navController: NavHostController, scrollState: ScrollState) {
+ Box {
+ CurvedTexts()
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .verticalScroll(state = scrollState)
+ .padding(vertical = 32.dp)
+ .semantics { contentDescription = CONTENT_DESCRIPTION },
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
+ Widget(navController, BUTTONS, "Btn", BUTTONS)
+ Widget(navController, CARDS, "Card", CARDS)
+ }
+ Spacer(modifier = Modifier.height(4.dp))
+ Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
+ Widget(navController, CHIPS, "Chip", CHIPS)
+ Widget(navController, DIALOGS, "Dlg", DIALOGS)
+ Widget(navController, PICKER, "Pick", PICKER)
+ }
+ Spacer(modifier = Modifier.height(4.dp))
+ Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
+ Widget(navController, PROGRESSINDICATORS,
+ "Prog", PROGRESSINDICATORS)
+ Widget(navController, SLIDER, "Sldr", SLIDER)
+ Widget(navController, STEPPER, "Stpr", STEPPER)
+ }
+ }
+ }
+}
+
+@Composable
+fun Dialogs(navController: NavHostController) {
+ Column(
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ ListHeader { Text("Dialogs") }
+ CompactChip(
+ onClick = { navController.navigate(ALERT_DIALOG) },
+ colors = ChipDefaults.primaryChipColors(),
+ label = { Text(ALERT_DIALOG) },
+ modifier = Modifier.semantics {
+ contentDescription = ALERT_DIALOG
+ },
+ )
+ Spacer(Modifier.height(4.dp))
+ CompactChip(
+ onClick = { navController.navigate(CONFIRMATION_DIALOG) },
+ colors = ChipDefaults.primaryChipColors(),
+ label = { Text(CONFIRMATION_DIALOG) },
+ modifier = Modifier.semantics {
+ contentDescription = CONFIRMATION_DIALOG
+ },
+ )
+ }
+}
+
+@Composable
fun Buttons() {
- ListHeader { Text("Buttons") }
- Button(onClick = {}) { Text("Button") }
- CompactButton(onClick = {}) { Text("CompactButton") }
- ToggleButton(checked = true, onCheckedChange = {}) { Text("ToggleButton") }
+ Column(
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ ListHeader { Text("Buttons") }
+ Button(onClick = {}) { Text("Button") }
+ CompactButton(onClick = {}) { Text("CompactButton") }
+ ToggleButton(
+ checked = true,
+ onCheckedChange = {}) { Text("ToggleButton") }
+ }
}
@Composable
fun Cards() {
- ListHeader { Text("Cards") }
- Card(onClick = {}) { Text("Card") }
- AppCard(onClick = {}, appName = {}, time = {}, title = {}) { Text("AppCard") }
- TitleCard(onClick = {}, title = {}) { Text("TitleCard") }
+ Column(
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ ListHeader { Text("Cards") }
+ Card(onClick = {}) { Text("Card") }
+ AppCard(onClick = {},
+ appName = { Text("AppName") }, title = {},
+ time = { Text("02:34") }) {
+ Text("AppCard")
+ }
+ TitleCard(onClick = {}, title = { Text("Title") }) {
+ Text("TitleCard")
+ }
+ }
}
@Composable
fun Chips() {
- ListHeader { Text("Chips") }
- Chip(onClick = {}, colors = ChipDefaults.primaryChipColors()) { Text("Chip") }
- CompactChip(onClick = {}, label = { Text("CompactChip") })
- ToggleChip(
- checked = true,
- onCheckedChange = {},
- label = { Text("ToggleChip") },
- toggleControl = {
- Icon(
- imageVector = ToggleChipDefaults.radioIcon(checked = false),
- contentDescription = null
- )
- }
- )
- SplitToggleChip(
- checked = true,
- onCheckedChange = {},
- label = { Text("SplitToggleChip") },
- onClick = {},
- toggleControl = {
- Icon(
- imageVector = ToggleChipDefaults.radioIcon(checked = true),
- contentDescription = null
- )
- }
- )
+ Column(
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ ListHeader { Text("Chips") }
+ Chip(
+ onClick = {},
+ colors = ChipDefaults.primaryChipColors()
+ ) { Text("Chip") }
+ CompactChip(onClick = {}, label = { Text("CompactChip") })
+ ToggleChip(
+ checked = true,
+ onCheckedChange = {},
+ label = { Text("ToggleChip") },
+ toggleControl = {
+ Icon(
+ imageVector =
+ ToggleChipDefaults.radioIcon(checked = false),
+ contentDescription = null
+ )
+ }
+ )
+ SplitToggleChip(
+ checked = true,
+ onCheckedChange = {},
+ label = { Text("SplitToggleChip") },
+ onClick = {},
+ toggleControl = {
+ Icon(
+ imageVector =
+ ToggleChipDefaults.radioIcon(checked = true),
+ contentDescription = null
+ )
+ }
+ )
+ }
+}
+
+@Composable
+fun Picker(scrollState: ScrollState) {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .verticalScroll(state = scrollState)
+ .padding(vertical = 16.dp)
+ .semantics { contentDescription = CONTENT_DESCRIPTION },
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ ListHeader { Text("Pickers") }
+ val items = listOf("One", "Two", "Three", "Four", "Five")
+ Picker(
+ state = rememberPickerState(items.size),
+ option = { Text(items[it]) },
+ modifier = Modifier.size(100.dp, 100.dp),
+ )
+ }
+}
+
+@Composable
+fun ProgressIndicators(navController: NavHostController) {
+ Column(
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ ListHeader { Text("Progress Indicators") }
+ // Test both circular progress indicator with gap and spinning indicator.
+ CompactChip(
+ onClick = { navController.navigate(PROGRESS_INDICATOR) },
+ colors = ChipDefaults.primaryChipColors(),
+ label = { Text(PROGRESS_INDICATOR) },
+ modifier = Modifier.semantics {
+ contentDescription = PROGRESS_INDICATOR
+ },
+ )
+ Spacer(Modifier.height(4.dp))
+ CompactChip(
+ onClick = {
+ navController.navigate(
+ PROGRESS_INDICATOR_INDETERMINATE
+ )
+ },
+ colors = ChipDefaults.primaryChipColors(),
+ label = { Text(PROGRESS_INDICATOR_INDETERMINATE) },
+ modifier = Modifier.semantics {
+ contentDescription = PROGRESS_INDICATOR_INDETERMINATE
+ },
+ )
+ }
+}
+
+@Composable
+fun Slider() {
+ Column(
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ ListHeader { Text("Sliders") }
+ var value by remember { mutableStateOf(4.5f) }
+ InlineSlider(
+ value = value,
+ onValueChange = { value = it },
+ increaseIcon = {
+ Icon(
+ InlineSliderDefaults.Increase,
+ "Increase"
+ )
+ },
+ decreaseIcon = {
+ Icon(
+ InlineSliderDefaults.Decrease,
+ "Decrease"
+ )
+ },
+ valueRange = 3f..6f,
+ steps = 5,
+ segmented = false
+ )
+ }
+}
+
+@Composable
+fun Stepper() {
+ var value by remember { mutableStateOf(2f) }
+ Stepper(
+ value = value,
+ onValueChange = { value = it },
+ increaseIcon = { Icon(StepperDefaults.Increase, "Increase") },
+ decreaseIcon = { Icon(StepperDefaults.Decrease, "Decrease") },
+ valueRange = 1f..4f,
+ steps = 7
+ ) { Text("Value: $value") }
}
@Composable
@@ -248,73 +421,11 @@
}
@Composable
-fun Dialogs(navController: NavHostController) {
- ListHeader { Text("Dialogs") }
- CompactChip(
- onClick = { navController.navigate(ALERT_DIALOG) },
- colors = ChipDefaults.primaryChipColors(),
- label = { Text(ALERT_DIALOG) },
- modifier = Modifier.semantics { contentDescription = ALERT_DIALOG },
- )
- CompactChip(
- onClick = { navController.navigate(CONFIRMATION_DIALOG) },
- colors = ChipDefaults.primaryChipColors(),
- label = { Text(CONFIRMATION_DIALOG) },
- modifier = Modifier.semantics { contentDescription = CONFIRMATION_DIALOG },
- )
-}
-
-@Composable
-fun Pickers() {
- ListHeader { Text("Pickers") }
- val items = listOf("One", "Two", "Three", "Four", "Five")
- Picker(
- state = rememberPickerState(items.size),
- option = { Text(items[it]) },
- modifier = Modifier.size(100.dp, 100.dp),
- )
-}
-
-@Composable
-fun ProgressIndicators(navController: NavHostController) {
- ListHeader { Text("Progress Indicators") }
- // Test both circular progress indicator with gap and spinning indicator.
- CompactChip(
- onClick = { navController.navigate(PROGRESS_INDICATOR) },
- colors = ChipDefaults.primaryChipColors(),
- label = { Text(PROGRESS_INDICATOR) },
- modifier = Modifier.semantics { contentDescription = PROGRESS_INDICATOR },
- )
- CompactChip(
- onClick = { navController.navigate(PROGRESS_INDICATOR_INDETERMINATE) },
- colors = ChipDefaults.primaryChipColors(),
- label = { Text(PROGRESS_INDICATOR_INDETERMINATE) },
- modifier = Modifier.semantics { contentDescription = PROGRESS_INDICATOR_INDETERMINATE },
- )
-}
-
-@Composable
-fun Sliders() {
- ListHeader { Text("Sliders") }
- var value by remember { mutableStateOf(4.5f) }
- InlineSlider(
- value = value,
- onValueChange = { value = it },
- increaseIcon = { Icon(InlineSliderDefaults.Increase, "Increase") },
- decreaseIcon = { Icon(InlineSliderDefaults.Decrease, "Decrease") },
- valueRange = 3f..6f,
- steps = 5,
- segmented = false
- )
-}
-
-@Composable
-fun Steppers(navController: NavHostController) {
- ListHeader { Text("Steppers") }
- CompactChip(
- onClick = { navController.navigate(STEPPER) },
- colors = ChipDefaults.primaryChipColors(),
- label = { Text(STEPPER) },
- modifier = Modifier.semantics { contentDescription = STEPPER },
- )
+fun Widget(navController: NavHostController, destination: String, text: String, desc: String) {
+ Button(
+ onClick = { navController.navigate(destination) },
+ modifier = Modifier.semantics { contentDescription = desc }
+ ) {
+ Text(text)
+ }
}
diff --git a/wear/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/wear/compose/integration/macrobenchmark/BaselineProfile.kt b/wear/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/wear/compose/integration/macrobenchmark/BaselineProfile.kt
index 74d435c..9114d23 100644
--- a/wear/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/wear/compose/integration/macrobenchmark/BaselineProfile.kt
+++ b/wear/compose/integration-tests/macrobenchmark/src/androidTest/java/androidx/wear/compose/integration/macrobenchmark/BaselineProfile.kt
@@ -17,14 +17,15 @@
package androidx.wear.compose.integration.macrobenchmark.test
import android.content.Intent
-import android.graphics.Point
import androidx.benchmark.macro.ExperimentalBaselineProfilesApi
import androidx.benchmark.macro.junit4.BaselineProfileRule
import androidx.test.filters.LargeTest
import androidx.test.filters.SdkSuppress
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
+import androidx.test.uiautomator.BySelector
import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
import androidx.testutils.createCompilationParams
import org.junit.Before
import org.junit.Rule
@@ -66,6 +67,13 @@
private lateinit var device: UiDevice
private val ALERT_DIALOG = "alert-dialog"
private val CONFIRMATION_DIALOG = "confirmation-dialog"
+ private val BUTTONS = "buttons"
+ private val CARDS = "cards"
+ private val CHIPS = "chips"
+ private val DIALOGS = "dialogs"
+ private val PICKER = "picker"
+ private val PROGRESSINDICATORS = "progressindicators"
+ private val SLIDER = "slider"
private val STEPPER = "stepper"
private val PROGRESS_INDICATOR = "progress-indicator"
private val PROGRESS_INDICATOR_INDETERMINATE = "progress-indicator-indeterminate"
@@ -84,37 +92,46 @@
val intent = Intent()
intent.action = ACTION
startActivityAndWait(intent)
- testDestination(ALERT_DIALOG)
- scrollDown()
- testDestination(CONFIRMATION_DIALOG)
- scrollDown()
- testDestination(STEPPER)
- scrollDown()
- testDestination(PROGRESS_INDICATOR)
- scrollDown()
- testDestination(PROGRESS_INDICATOR_INDETERMINATE)
-
- repeat(30) {
- scrollDown()
- }
+ testDestination(description = BUTTONS)
+ testDestination(description = CARDS)
+ testDestination(description = CHIPS)
+ testDialogs()
+ testDestination(description = PICKER)
+ testProgressIndicators()
+ testDestination(description = SLIDER)
+ testDestination(description = STEPPER)
}
)
}
- private fun testDestination(name: String) {
- device.findObject(By.desc(name)).click()
+ private fun testDialogs() {
+ findAndClick(By.desc(DIALOGS))
+ device.waitForIdle()
+ testDestination(description = ALERT_DIALOG)
+ testDestination(description = CONFIRMATION_DIALOG)
+ device.pressBack()
+ device.waitForIdle()
+ }
+
+ private fun testProgressIndicators() {
+ findAndClick(By.desc(PROGRESSINDICATORS))
+ device.waitForIdle()
+ testDestination(description = PROGRESS_INDICATOR)
+ testDestination(description = PROGRESS_INDICATOR_INDETERMINATE)
+ device.pressBack()
+ device.waitForIdle()
+ }
+
+ private fun testDestination(description: String) {
+ findAndClick(By.desc(description))
device.waitForIdle()
device.pressBack()
device.waitForIdle()
}
- private fun scrollDown() {
- // Scroll down to view remaining UI elements
- // Setting a gesture margin is important otherwise gesture nav is triggered.
- val list = device.findObject(By.desc(CONTENT_DESCRIPTION))
- list.setGestureMargin(device.displayWidth / 5)
- list.drag(Point(list.visibleCenter.x, list.visibleCenter.y / 3))
- device.waitForIdle()
+ private fun findAndClick(selector: BySelector) {
+ device.wait(Until.findObject(selector), 3000)
+ device.findObject(selector).click()
}
companion object {
diff --git a/wear/tiles/OWNERS b/wear/tiles/OWNERS
index 7c0968e..b831820 100644
--- a/wear/tiles/OWNERS
+++ b/wear/tiles/OWNERS
@@ -1,3 +1,4 @@
# Bug component: 1112272
jgarside@google.com
-jnichol@google.com
\ No newline at end of file
+jnichol@google.com
+msab@google.com
diff --git a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/DocumentStartJavaScriptActivity.java b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/DocumentStartJavaScriptActivity.java
index 37fe4c6..dc199bd 100644
--- a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/DocumentStartJavaScriptActivity.java
+++ b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/DocumentStartJavaScriptActivity.java
@@ -44,6 +44,8 @@
* An {@link Activity} to exercise {@link WebViewCompat#addDocumentStartJavaScript(WebView, String,
* Set)} related functionality.
*/
+// TODO(swestphal): Remove the @SuppressLint after addDocumentStartJavaScript is unhidden.
+@SuppressLint("RestrictedApi")
public class DocumentStartJavaScriptActivity extends AppCompatActivity {
private final Uri mExampleUri = new Uri.Builder()
.scheme("https")
diff --git a/webkit/webkit/api/current.txt b/webkit/webkit/api/current.txt
index fa38e7f..c0537d9 100644
--- a/webkit/webkit/api/current.txt
+++ b/webkit/webkit/api/current.txt
@@ -45,10 +45,6 @@
method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void showInterstitial(boolean);
}
- public abstract class ScriptHandler {
- method @RequiresFeature(name=androidx.webkit.WebViewFeature.DOCUMENT_START_SCRIPT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void remove();
- }
-
public abstract class ServiceWorkerClientCompat {
ctor public ServiceWorkerClientCompat();
method @WorkerThread public abstract android.webkit.WebResourceResponse? shouldInterceptRequest(android.webkit.WebResourceRequest);
@@ -193,7 +189,6 @@
}
public class WebViewCompat {
- method @RequiresFeature(name=androidx.webkit.WebViewFeature.DOCUMENT_START_SCRIPT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.ScriptHandler addDocumentStartJavaScript(android.webkit.WebView, String, java.util.Set<java.lang.String!>);
method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_LISTENER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void addWebMessageListener(android.webkit.WebView, String, java.util.Set<java.lang.String!>, androidx.webkit.WebViewCompat.WebMessageListener);
method @RequiresFeature(name=androidx.webkit.WebViewFeature.CREATE_WEB_MESSAGE_CHANNEL, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.WebMessagePortCompat![] createWebMessageChannel(android.webkit.WebView);
method public static android.content.pm.PackageInfo? getCurrentWebViewPackage(android.content.Context);
@@ -227,7 +222,6 @@
field public static final String ALGORITHMIC_DARKENING = "ALGORITHMIC_DARKENING";
field public static final String CREATE_WEB_MESSAGE_CHANNEL = "CREATE_WEB_MESSAGE_CHANNEL";
field public static final String DISABLED_ACTION_MODE_MENU_ITEMS = "DISABLED_ACTION_MODE_MENU_ITEMS";
- field public static final String DOCUMENT_START_SCRIPT = "DOCUMENT_START_SCRIPT";
field public static final String FORCE_DARK = "FORCE_DARK";
field public static final String FORCE_DARK_STRATEGY = "FORCE_DARK_STRATEGY";
field public static final String GET_VARIATIONS_HEADER = "GET_VARIATIONS_HEADER";
diff --git a/webkit/webkit/api/public_plus_experimental_current.txt b/webkit/webkit/api/public_plus_experimental_current.txt
index fa38e7f..c0537d9 100644
--- a/webkit/webkit/api/public_plus_experimental_current.txt
+++ b/webkit/webkit/api/public_plus_experimental_current.txt
@@ -45,10 +45,6 @@
method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void showInterstitial(boolean);
}
- public abstract class ScriptHandler {
- method @RequiresFeature(name=androidx.webkit.WebViewFeature.DOCUMENT_START_SCRIPT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void remove();
- }
-
public abstract class ServiceWorkerClientCompat {
ctor public ServiceWorkerClientCompat();
method @WorkerThread public abstract android.webkit.WebResourceResponse? shouldInterceptRequest(android.webkit.WebResourceRequest);
@@ -193,7 +189,6 @@
}
public class WebViewCompat {
- method @RequiresFeature(name=androidx.webkit.WebViewFeature.DOCUMENT_START_SCRIPT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.ScriptHandler addDocumentStartJavaScript(android.webkit.WebView, String, java.util.Set<java.lang.String!>);
method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_LISTENER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void addWebMessageListener(android.webkit.WebView, String, java.util.Set<java.lang.String!>, androidx.webkit.WebViewCompat.WebMessageListener);
method @RequiresFeature(name=androidx.webkit.WebViewFeature.CREATE_WEB_MESSAGE_CHANNEL, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.WebMessagePortCompat![] createWebMessageChannel(android.webkit.WebView);
method public static android.content.pm.PackageInfo? getCurrentWebViewPackage(android.content.Context);
@@ -227,7 +222,6 @@
field public static final String ALGORITHMIC_DARKENING = "ALGORITHMIC_DARKENING";
field public static final String CREATE_WEB_MESSAGE_CHANNEL = "CREATE_WEB_MESSAGE_CHANNEL";
field public static final String DISABLED_ACTION_MODE_MENU_ITEMS = "DISABLED_ACTION_MODE_MENU_ITEMS";
- field public static final String DOCUMENT_START_SCRIPT = "DOCUMENT_START_SCRIPT";
field public static final String FORCE_DARK = "FORCE_DARK";
field public static final String FORCE_DARK_STRATEGY = "FORCE_DARK_STRATEGY";
field public static final String GET_VARIATIONS_HEADER = "GET_VARIATIONS_HEADER";
diff --git a/webkit/webkit/api/restricted_current.txt b/webkit/webkit/api/restricted_current.txt
index fa38e7f..c0537d9 100644
--- a/webkit/webkit/api/restricted_current.txt
+++ b/webkit/webkit/api/restricted_current.txt
@@ -45,10 +45,6 @@
method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void showInterstitial(boolean);
}
- public abstract class ScriptHandler {
- method @RequiresFeature(name=androidx.webkit.WebViewFeature.DOCUMENT_START_SCRIPT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void remove();
- }
-
public abstract class ServiceWorkerClientCompat {
ctor public ServiceWorkerClientCompat();
method @WorkerThread public abstract android.webkit.WebResourceResponse? shouldInterceptRequest(android.webkit.WebResourceRequest);
@@ -193,7 +189,6 @@
}
public class WebViewCompat {
- method @RequiresFeature(name=androidx.webkit.WebViewFeature.DOCUMENT_START_SCRIPT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.ScriptHandler addDocumentStartJavaScript(android.webkit.WebView, String, java.util.Set<java.lang.String!>);
method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_LISTENER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void addWebMessageListener(android.webkit.WebView, String, java.util.Set<java.lang.String!>, androidx.webkit.WebViewCompat.WebMessageListener);
method @RequiresFeature(name=androidx.webkit.WebViewFeature.CREATE_WEB_MESSAGE_CHANNEL, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.WebMessagePortCompat![] createWebMessageChannel(android.webkit.WebView);
method public static android.content.pm.PackageInfo? getCurrentWebViewPackage(android.content.Context);
@@ -227,7 +222,6 @@
field public static final String ALGORITHMIC_DARKENING = "ALGORITHMIC_DARKENING";
field public static final String CREATE_WEB_MESSAGE_CHANNEL = "CREATE_WEB_MESSAGE_CHANNEL";
field public static final String DISABLED_ACTION_MODE_MENU_ITEMS = "DISABLED_ACTION_MODE_MENU_ITEMS";
- field public static final String DOCUMENT_START_SCRIPT = "DOCUMENT_START_SCRIPT";
field public static final String FORCE_DARK = "FORCE_DARK";
field public static final String FORCE_DARK_STRATEGY = "FORCE_DARK_STRATEGY";
field public static final String GET_VARIATIONS_HEADER = "GET_VARIATIONS_HEADER";
diff --git a/webkit/webkit/src/main/java/androidx/webkit/ScriptHandler.java b/webkit/webkit/src/main/java/androidx/webkit/ScriptHandler.java
index db7447e..2aac172 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/ScriptHandler.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/ScriptHandler.java
@@ -25,7 +25,11 @@
* corresponding JavaScript script should be removed.
*
* @see WebViewCompat#addDocumentStartJavaScript(android.webkit.WebView, String, Set)
+ *
+ * TODO(swestphal): unhide when ready.
+ * @hide
*/
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public abstract class ScriptHandler {
/**
* Removes the corresponding script, it will take effect from next page load.
diff --git a/webkit/webkit/src/main/java/androidx/webkit/WebViewCompat.java b/webkit/webkit/src/main/java/androidx/webkit/WebViewCompat.java
index 814b9d7..8073dfa 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/WebViewCompat.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/WebViewCompat.java
@@ -31,6 +31,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresFeature;
+import androidx.annotation.RestrictTo;
import androidx.annotation.UiThread;
import androidx.webkit.internal.WebMessagePortImpl;
import androidx.webkit.internal.WebViewFeatureInternal;
@@ -724,7 +725,11 @@
* @throws IllegalArgumentException If one of the {@code allowedOriginRules} is invalid.
* @see #addWebMessageListener(WebView, String, Set, WebMessageListener)
* @see ScriptHandler
+ *
+ * TODO(swestphal): unhide when ready.
+ * @hide
*/
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@RequiresFeature(
name = WebViewFeature.DOCUMENT_START_SCRIPT,
enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
diff --git a/webkit/webkit/src/main/java/androidx/webkit/WebViewFeature.java b/webkit/webkit/src/main/java/androidx/webkit/WebViewFeature.java
index 1eef16b..5de5148 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/WebViewFeature.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/WebViewFeature.java
@@ -460,7 +460,11 @@
* Feature for {@link #isFeatureSupported(String)}.
* This feature covers {@link WebViewCompat#addDocumentStartJavaScript(android.webkit.WebView,
* String, Set)}.
+ *
+ * TODO(swestphal): unhide when ready.
+ * @hide
*/
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static final String DOCUMENT_START_SCRIPT = "DOCUMENT_START_SCRIPT";
/**