Merge "Reset pointer IDs after not accepting an input stream" into androidx-main
diff --git a/appsearch/appsearch-builtin-types/OWNERS b/appsearch/appsearch-builtin-types/OWNERS
new file mode 100644
index 0000000..e863f7d
--- /dev/null
+++ b/appsearch/appsearch-builtin-types/OWNERS
@@ -0,0 +1,4 @@
[email protected]
[email protected]
[email protected]
[email protected]
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
index e377245..1e1b44d 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
@@ -113,6 +113,7 @@
 
         project.configureTaskTimeouts()
         project.configureMavenArtifactUpload(extension)
+        project.configureExportLibraryGroupsToXml()
         project.configureExternalDependencyLicenseCheck()
         project.configureProjectStructureValidation(extension)
         project.configureProjectVersionValidation(extension)
@@ -394,6 +395,17 @@
         project.addToProjectMap(extension)
     }
 
+    private fun Project.configureExportLibraryGroupsToXml() {
+        project.tasks.register(
+            "exportLibraryGroupsToXml",
+            ExportLibraryGroupsToXmlTask::class.java
+        ) { task ->
+            task.libraryGroupFile = project.file("${project.getSupportRootFolder()}" +
+                "/buildSrc/public/src/main/kotlin/androidx/build/LibraryGroups.kt")
+            task.xmlOutputFile = project.file("${project.buildDir}/lint/library-groups.xml")
+        }
+    }
+
     private fun Project.configureProjectStructureValidation(
         extension: AndroidXExtension
     ) {
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/ExportLibraryGroupsToXmlTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/ExportLibraryGroupsToXmlTask.kt
new file mode 100644
index 0000000..bb8df4a
--- /dev/null
+++ b/buildSrc/private/src/main/kotlin/androidx/build/ExportLibraryGroupsToXmlTask.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2021 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.build
+
+import com.google.common.io.Files
+import org.gradle.api.DefaultTask
+import org.gradle.api.tasks.CacheableTask
+import org.gradle.api.tasks.InputFile
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.PathSensitive
+import org.gradle.api.tasks.PathSensitivity
+import org.gradle.api.tasks.TaskAction
+import java.io.BufferedWriter
+import java.io.File
+import java.io.Writer
+import kotlin.reflect.full.memberProperties
+import kotlin.text.Charsets.UTF_8
+
+/**
+ * Task that parses the contents of a given library group file (usually [LibraryGroups]) and writes
+ * them to an XML file. The XML file is then used by Lint.
+ */
+@CacheableTask
+abstract class ExportLibraryGroupsToXmlTask : DefaultTask() {
+
+    @get:[InputFile PathSensitive(PathSensitivity.NONE)]
+    lateinit var libraryGroupFile: File
+
+    @get:OutputFile
+    lateinit var xmlOutputFile: File
+
+    @TaskAction
+    fun exec() {
+        val writer: Writer = BufferedWriter(Files.newWriter(xmlOutputFile, UTF_8))
+
+        // Write XML header and outermost opening tag
+        writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
+        writer.write("<libraryGroups>\n")
+
+        LibraryGroups::class.memberProperties.forEach { member ->
+            try {
+                val libraryGroup = member.get(LibraryGroups) as LibraryGroup
+                val groupName = libraryGroup.group
+                val isAtomic = (libraryGroup.forcedVersion != null)
+
+                // Write data for this LibraryGroup
+                writer.run {
+                    write("    <libraryGroup>\n")
+                    write("        <group>$groupName</group>\n")
+                    write("        <isAtomic>$isAtomic</isAtomic>\n")
+                    write("    </libraryGroup>\n")
+                }
+            } catch (ignore: ClassCastException) {
+                // Object isn't a LibraryGroup, skip it
+            }
+        }
+
+        // Write outermost closing tag and close writer
+        writer.write("<libraryGroups>\n")
+        writer.close()
+    }
+}
diff --git a/camera/camera-camera2-pipe/build.gradle b/camera/camera-camera2-pipe/build.gradle
index af4e291..3cdad88 100644
--- a/camera/camera-camera2-pipe/build.gradle
+++ b/camera/camera-camera2-pipe/build.gradle
@@ -43,7 +43,7 @@
     testImplementation(libs.testRunner)
     testImplementation(libs.junit)
     testImplementation(libs.truth)
-    testImplementation(libs.robolectric)
+    testImplementation("org.robolectric:robolectric:4.6.1") // TODO(b/205731854): fix tests to work with SDK 31 and robolectric 4.7
     testImplementation(libs.kotlinCoroutinesTest)
     testImplementation(project(":camera:camera-camera2-pipe-testing"))
     testImplementation(project(":internal-testutils-truth"))
diff --git a/camera/camera-camera2/build.gradle b/camera/camera-camera2/build.gradle
index 11c1c32..386a9c0 100644
--- a/camera/camera-camera2/build.gradle
+++ b/camera/camera-camera2/build.gradle
@@ -38,7 +38,7 @@
     testImplementation(libs.testRunner)
     testImplementation(libs.junit)
     testImplementation(libs.truth)
-    testImplementation(libs.robolectric)
+    testImplementation("org.robolectric:robolectric:4.6.1") // TODO(b/205731854): fix tests to work with SDK 31 and robolectric 4.7
     testImplementation(libs.mockitoCore)
     testImplementation(libs.kotlinCoroutinesTest)
     testImplementation("androidx.annotation:annotation-experimental:1.1.0")
diff --git a/car/app/app-automotive/src/test/java/androidx/car/app/hardware/info/AutomotiveCarInfoTest.java b/car/app/app-automotive/src/test/java/androidx/car/app/hardware/info/AutomotiveCarInfoTest.java
index 55deed1..8bb3a03 100644
--- a/car/app/app-automotive/src/test/java/androidx/car/app/hardware/info/AutomotiveCarInfoTest.java
+++ b/car/app/app-automotive/src/test/java/androidx/car/app/hardware/info/AutomotiveCarInfoTest.java
@@ -224,9 +224,9 @@
         assertThat(tollCard.getCardState().getValue()).isEqualTo(TollCard.TOLLCARD_STATE_VALID);
     }
 
-    @Config(minSdk = 30)
+    @Config(maxSdk = 30)
     @Test
-    public void getTollCard_verifyResponseApi30() {
+    public void getTollCard_verifyResponseApi30() throws InterruptedException {
         AtomicReference<TollCard> loadedResult = new AtomicReference<>();
         OnCarDataAvailableListener<TollCard> listener = (data) -> {
             loadedResult.set(data);
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Checkbox.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Checkbox.kt
index 49995ca..49e75b3 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Checkbox.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Checkbox.kt
@@ -54,6 +54,7 @@
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.util.lerp
 import kotlin.math.floor
+import kotlin.math.max
 
 /**
  * <a href="https://material.io/components/checkboxes" class="external" target="_blank">Material Design checkbox</a>.
@@ -322,7 +323,8 @@
         boxColor,
         topLeft = Offset(strokeWidth, strokeWidth),
         size = Size(checkboxSize - strokeWidth * 2, checkboxSize - strokeWidth * 2),
-        cornerRadius = CornerRadius(radius / 2),
+        // Set the inner radius to be equal to the outer radius - border's stroke width.
+        cornerRadius = CornerRadius(max(0f, radius - strokeWidth)),
         style = Fill
     )
     drawRoundRect(
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/IconButtonScreenshotTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/IconButtonScreenshotTest.kt
new file mode 100644
index 0000000..67f6d1e
--- /dev/null
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/IconButtonScreenshotTest.kt
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3
+
+import android.os.Build
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.testutils.assertAgainstGolden
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusProperties
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.hasClickAction
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performMouseInput
+import androidx.compose.ui.test.performTouchInput
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.screenshot.AndroidXScreenshotTestRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+@OptIn(ExperimentalTestApi::class)
+class IconButtonScreenshotTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    @get:Rule
+    val screenshotRule = AndroidXScreenshotTestRule(GOLDEN_MATERIAL3)
+
+    private val wrap = Modifier.wrapContentSize(Alignment.TopStart)
+    private val wrapperTestTag = "iconButtonWrapper"
+
+    @Test
+    fun iconButton_lightTheme() {
+        rule.setMaterialContent {
+            Box(wrap.testTag(wrapperTestTag)) {
+                IconButton(onClick = { /* doSomething() */ }) {
+                    Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+                }
+            }
+        }
+        assertAgainstGolden("iconButton_lightTheme")
+    }
+
+    @Test
+    fun iconButton_darkTheme() {
+        rule.setContent {
+            MaterialTheme(darkColorScheme()) {
+                Surface(modifier = Modifier.fillMaxSize()) {
+                    Box(wrap.testTag(wrapperTestTag)) {
+                        IconButton(onClick = { /* doSomething() */ }) {
+                            Icon(
+                                Icons.Filled.Favorite,
+                                contentDescription = "Localized description"
+                            )
+                        }
+                    }
+                }
+            }
+        }
+        assertAgainstGolden("iconButton_darkTheme")
+    }
+
+    @Test
+    fun iconButton_lightTheme_disabled() {
+
+        rule.setMaterialContent {
+            Box(wrap.testTag(wrapperTestTag)) {
+                IconButton(onClick = { /* doSomething() */ }, enabled = false) {
+                    Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+                }
+            }
+        }
+        assertAgainstGolden("iconButton_lightTheme_disabled")
+    }
+
+    @Test
+    fun iconButton_lightTheme_pressed() {
+        rule.setMaterialContent {
+            Box(wrap.testTag(wrapperTestTag)) {
+                IconButton(onClick = { /* doSomething() */ }) {
+                    Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+                }
+            }
+        }
+
+        rule.mainClock.autoAdvance = false
+        rule.onNode(hasClickAction())
+            .performTouchInput { down(center) }
+
+        rule.mainClock.advanceTimeByFrame()
+        rule.waitForIdle() // Wait for measure
+        rule.mainClock.advanceTimeBy(milliseconds = 200)
+
+        // Ripples are drawn on the RenderThread, not the main (UI) thread, so we can't wait for
+        // synchronization. Instead just wait until after the ripples are finished animating.
+        Thread.sleep(300)
+
+        assertAgainstGolden("iconButton_lightTheme_pressed")
+    }
+
+    @Test
+    fun iconButton_lightTheme_hovered() {
+        rule.setMaterialContent {
+            Box(wrap.testTag(wrapperTestTag)) {
+                IconButton(onClick = { /* doSomething() */ }) {
+                    Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+                }
+            }
+        }
+        rule.onNodeWithTag(wrapperTestTag).performMouseInput {
+            enter(center)
+        }
+
+        assertAgainstGolden("iconButton_lightTheme_hovered")
+    }
+
+    @Test
+    fun iconButton_lightTheme_focused() {
+        val focusRequester = FocusRequester()
+
+        rule.setMaterialContent {
+            Box(wrap.testTag(wrapperTestTag)) {
+                IconButton(onClick = { /* doSomething() */ },
+                    modifier = Modifier
+                        // Normally this is only focusable in non-touch mode, so let's force it to
+                        // always be focusable so we can test how it appears
+                        .focusProperties { canFocus = true }
+                        .focusRequester(focusRequester)
+                ) {
+                    Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            focusRequester.requestFocus()
+        }
+
+        assertAgainstGolden("iconButton_lightTheme_focused")
+    }
+
+    private fun assertAgainstGolden(goldenName: String) {
+        rule.onNodeWithTag(wrapperTestTag)
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, goldenName)
+    }
+}
\ No newline at end of file
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Checkbox.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Checkbox.kt
index ed666cb..13e1bf2 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Checkbox.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Checkbox.kt
@@ -55,6 +55,7 @@
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.util.lerp
 import kotlin.math.floor
+import kotlin.math.max
 
 /**
  * Material Design checkbox.
@@ -341,7 +342,8 @@
         boxColor,
         topLeft = Offset(strokeWidth, strokeWidth),
         size = Size(checkboxSize - strokeWidth * 2, checkboxSize - strokeWidth * 2),
-        cornerRadius = CornerRadius(radius / 2),
+        // Set the inner radius to be equal to the outer radius - border's stroke width.
+        cornerRadius = CornerRadius(max(0f, radius - strokeWidth)),
         style = Fill
     )
     drawRoundRect(
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/IconButton.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/IconButton.kt
index ba9daf7..cafed40 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/IconButton.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/IconButton.kt
@@ -70,7 +70,10 @@
                     enabled = enabled,
                     role = Role.Button,
                     interactionSource = interactionSource,
-                    indication = rememberRipple(bounded = false, radius = RippleRadius)
+                    indication = rememberRipple(
+                        bounded = false,
+                        radius = IconButton.StateLayerSize / 2
+                    )
                 ),
         contentAlignment = Alignment.Center
     ) {
@@ -121,7 +124,10 @@
                     enabled = enabled,
                     role = Role.Checkbox,
                     interactionSource = interactionSource,
-                    indication = rememberRipple(bounded = false, radius = RippleRadius)
+                    indication = rememberRipple(
+                        bounded = false,
+                        radius = IconButton.StateLayerSize / 2
+                    )
                 ),
         contentAlignment = Alignment.Center
     ) {
@@ -134,6 +140,3 @@
         CompositionLocalProvider(LocalContentColor provides contentColor, content = content)
     }
 }
-
-// Default radius of an unbounded ripple in an IconButton
-private val RippleRadius = IconButton.StateLayerSize
diff --git a/compose/ui/ui-tooling/src/androidAndroidTest/kotlin/androidx/compose/ui/tooling/ComposeViewAdapterTest.kt b/compose/ui/ui-tooling/src/androidAndroidTest/kotlin/androidx/compose/ui/tooling/ComposeViewAdapterTest.kt
index f52e504..863a04a 100644
--- a/compose/ui/ui-tooling/src/androidAndroidTest/kotlin/androidx/compose/ui/tooling/ComposeViewAdapterTest.kt
+++ b/compose/ui/ui-tooling/src/androidAndroidTest/kotlin/androidx/compose/ui/tooling/ComposeViewAdapterTest.kt
@@ -17,6 +17,7 @@
 package androidx.compose.ui.tooling
 
 import android.app.Activity
+import android.os.Build
 import android.os.Bundle
 import androidx.compose.animation.core.InternalAnimationApi
 import androidx.compose.ui.tooling.animation.PreviewAnimationClock
@@ -336,10 +337,14 @@
                 onDraw = { onDrawCounter++ }
             )
         }
+
+        // API before 22, might issue an additional draw under testing.
+        val expectedDrawCount = if (Build.VERSION.SDK_INT < 22) 2 else 1
         repeat(5) {
             activityTestRule.runOnUiThread {
                 assertEquals(1, compositionCount.get())
-                assertTrue("At most, 1 draw is expected", onDrawCounter < 2)
+                assertTrue("At most, $expectedDrawCount draw is expected ($onDrawCounter happened)",
+                    onDrawCounter <= expectedDrawCount)
             }
             Thread.sleep(250)
         }
diff --git a/compose/ui/ui/build.gradle b/compose/ui/ui/build.gradle
index e1712fe..1e133f1 100644
--- a/compose/ui/ui/build.gradle
+++ b/compose/ui/ui/build.gradle
@@ -70,7 +70,7 @@
         testImplementation(libs.truth)
         testImplementation(libs.mockitoCore)
         testImplementation(libs.mockitoKotlin)
-        testImplementation(libs.robolectric)
+        testImplementation("org.robolectric:robolectric:4.6.1") // TODO(b/205731854): fix tests to work with SDK 31 and robolectric 4.7
         testImplementation(project(":compose:ui:ui-test-junit4"))
         testImplementation(project(":compose:test-utils"))
 
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/Modifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/Modifier.kt
index 9e39112..086de49 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/Modifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/Modifier.kt
@@ -125,7 +125,7 @@
      *
      * @sample androidx.compose.ui.samples.ModifierParameterSample
      */
-    // The companion object implements `Modifier` so that it may be used  as the start of a
+    // The companion object implements `Modifier` so that it may be used as the start of a
     // modifier extension factory expression.
     companion object : Modifier {
         override fun <R> foldIn(initial: R, operation: (R, Element) -> R): R = initial
diff --git a/core/OWNERS b/core/OWNERS
index cb02012..3a3ca6d 100644
--- a/core/OWNERS
+++ b/core/OWNERS
@@ -16,5 +16,5 @@
 [email protected]
 
 # For shortcut related files
[email protected]
[email protected]
 [email protected]
diff --git a/core/core-performance/build.gradle b/core/core-performance/build.gradle
index add1f93..f64c5a7 100644
--- a/core/core-performance/build.gradle
+++ b/core/core-performance/build.gradle
@@ -26,6 +26,8 @@
 
 dependencies {
     api(libs.kotlinStdlib)
+
+    testImplementation(libs.testCore)
     testImplementation(libs.kotlinStdlib)
     testImplementation(libs.junit)
     testImplementation(libs.truth)
diff --git a/core/core-performance/src/main/kotlin/androidx/core/performance/PerformanceClass.kt b/core/core-performance/src/main/kotlin/androidx/core/performance/PerformanceClass.kt
index 246f23a..518ab09 100644
--- a/core/core-performance/src/main/kotlin/androidx/core/performance/PerformanceClass.kt
+++ b/core/core-performance/src/main/kotlin/androidx/core/performance/PerformanceClass.kt
@@ -16,12 +16,14 @@
 
 package androidx.core.performance
 
+import android.content.Context
 import android.os.Build
 
 /**
  * Reports the media performance class of the device.
+ * @param context ApplicationContext
  */
-class PerformanceClass {
+class PerformanceClass(private val context: Context) {
 
     /**
      * The media performance class of the device or 0 if none.
diff --git a/core/core-performance/src/test/kotlin/androidx/core/performance/PerformanceClassTest.kt b/core/core-performance/src/test/kotlin/androidx/core/performance/PerformanceClassTest.kt
index 895b376..ab2b47b 100644
--- a/core/core-performance/src/test/kotlin/androidx/core/performance/PerformanceClassTest.kt
+++ b/core/core-performance/src/test/kotlin/androidx/core/performance/PerformanceClassTest.kt
@@ -16,8 +16,10 @@
 
 package androidx.core.performance
 
+import android.app.Application
 import android.os.Build.VERSION_CODES.R
 import android.os.Build.VERSION_CODES.S
+import androidx.test.core.app.ApplicationProvider
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -30,17 +32,17 @@
 @RunWith(RobolectricTestRunner::class)
 class PerformanceClassTest {
 
-    private val pc = PerformanceClass()
-
     @Test
     @Config(maxSdk = R)
     fun getMediaPerformanceClass_sdk30() {
+        val pc = createPerformanceClass()
         assertThat(pc.getMediaPerformanceClass()).isEqualTo(0)
     }
 
     @Test
     @Config(minSdk = S)
     fun getMediaPerformanceClass_sdk31_declared30() {
+        val pc = createPerformanceClass()
         // TODO(b/205732671): Use ShadowBuild.setMediaPerformanceClass when available
         ShadowSystemProperties.override("ro.odm.build.media_performance_class", "30")
         ShadowBuild.reset()
@@ -50,8 +52,13 @@
     @Test
     @Config(minSdk = S)
     fun getMediaPerformanceClass_sdk31_notDeclared() {
+        val pc = createPerformanceClass()
         // TODO(b/205732671): Use ShadowBuild.setMediaPerformanceClass when available
         ShadowBuild.reset()
         assertThat(pc.getMediaPerformanceClass()).isEqualTo(0)
     }
+
+    private fun createPerformanceClass(): PerformanceClass {
+        return PerformanceClass(ApplicationProvider.getApplicationContext<Application>())
+    }
 }
\ No newline at end of file
diff --git a/development/build_log_simplifier/messages.ignore b/development/build_log_simplifier/messages.ignore
index 36287014..635400e3 100644
--- a/development/build_log_simplifier/messages.ignore
+++ b/development/build_log_simplifier/messages.ignore
@@ -224,6 +224,7 @@
 location\: class PendingIntent
 \$OUT_DIR\/androidx\/docs\-public\/build\/unzippedDocsSources\/androidx\/work\/impl\/utils\/ForceStopRunnable\.java\:[0-9]+\: error\: cannot find symbol
 # > Task :buildOnServer
+[0-9]+ problems were found reusing the configuration cache\.
 [0-9]+ problems were found reusing the configuration cache, [0-9]+ of which seem unique\.
 [0-9]+ actionable tasks: [0-9]+ executed, [0-9]+ up\-to\-date
 Configuration cache entry reused with [0-9]+ problems\.
@@ -610,4 +611,6 @@
 Info: Methods with invalid locals information:
 java\.lang\.Object androidx\.glance\.state\.GlanceState\.getDataStore\(android\.content\.Context, androidx\.glance\.state\.GlanceStateDefinition, java\.lang\.String, kotlin\.coroutines\.Continuation\)
 Information in locals\-table is invalid with respect to the stack map table\. Local refers to non\-present stack map type for register: [0-9]+ with constraint OBJECT\.
-Info: Some warnings are typically a sign of using an outdated Java toolchain\. To fix, recompile the source with an updated toolchain\.
\ No newline at end of file
+Info: Some warnings are typically a sign of using an outdated Java toolchain\. To fix, recompile the source with an updated toolchain\.
+# > Task :compose:ui:ui-inspection:buildCMakeRelWithDebInfo[arm64-v8a]
+C/C\+\+: ninja: warning: bad deps log signature or version; starting over
\ No newline at end of file
diff --git a/development/build_log_simplifier/update.sh b/development/build_log_simplifier/update.sh
index ef8ba07..e5b3a20 100755
--- a/development/build_log_simplifier/update.sh
+++ b/development/build_log_simplifier/update.sh
@@ -54,7 +54,12 @@
     else
       logName="gradle.${i}.log"
     fi
-    if fetch_artifact --bid "$buildId" --target "$target" "logs/$logName"; then
+    filepath="logs/$logName"
+    # incremental build uses a subdirectory
+    if [ "$target" == "androidx_incremental" ]; then
+      filepath="incremental/$filepath"
+    fi
+    if fetch_artifact --bid "$buildId" --target "$target" "$filepath"; then
       echo "downloaded log ${i} in build $buildId target $target"
     else
       echo
diff --git a/development/project-creator/create_project.py b/development/project-creator/create_project.py
index 7501453..ae4b191 100755
--- a/development/project-creator/create_project.py
+++ b/development/project-creator/create_project.py
@@ -745,6 +745,10 @@
         project_type = ProjectType.KOTLIN
     else:
         project_type = ask_project_type()
+    insert_new_group_id_into_library_versions_kt(
+        args.group_id,
+        args.artifact_id
+    )
     create_directories(
         args.group_id,
         args.artifact_id,
@@ -752,8 +756,6 @@
         is_compose_project(args.group_id, args.artifact_id)
     )
     update_settings_gradle(args.group_id, args.artifact_id)
-    insert_new_group_id_into_library_versions_kt(args.group_id,
-                                                 args.artifact_id)
     update_docs_tip_of_tree_build_grade(args.group_id, args.artifact_id)
     print("Created directories. \nRunning updateApi for the new "
           "library, this may take a minute...", end='')
diff --git a/glance/glance-appwidget/api/current.txt b/glance/glance-appwidget/api/current.txt
index 2efcafe..302578a 100644
--- a/glance/glance-appwidget/api/current.txt
+++ b/glance/glance-appwidget/api/current.txt
@@ -54,12 +54,15 @@
     method @androidx.compose.runtime.Composable public abstract void Content();
     method public androidx.glance.appwidget.SizeMode getSizeMode();
     method public androidx.glance.state.GlanceStateDefinition<?>? getStateDefinition();
+    method public suspend Object? onDelete(androidx.glance.GlanceId glanceId, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
     method public final suspend Object? update(android.content.Context context, androidx.glance.GlanceId glanceId, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
     property public androidx.glance.appwidget.SizeMode sizeMode;
     property public androidx.glance.state.GlanceStateDefinition<?>? stateDefinition;
   }
 
   public final class GlanceAppWidgetKt {
+    method public static suspend Object? updateAll(androidx.glance.appwidget.GlanceAppWidget, android.content.Context context, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method public static suspend inline <reified State> void updateIf(androidx.glance.appwidget.GlanceAppWidget, android.content.Context context, kotlin.jvm.functions.Function1<? super State,? extends java.lang.Boolean> predicate);
   }
 
   public final class GlanceAppWidgetManager {
@@ -183,7 +186,9 @@
 
   public final class GlanceAppWidgetStateKt {
     method public static suspend <T> Object? getAppWidgetState(android.content.Context context, androidx.glance.state.GlanceStateDefinition<T> definition, androidx.glance.GlanceId glanceId, kotlin.coroutines.Continuation<? super T> p);
+    method public static suspend <T> Object? getAppWidgetState(androidx.glance.appwidget.GlanceAppWidget, android.content.Context context, androidx.glance.GlanceId glanceId, kotlin.coroutines.Continuation<? super T> p);
     method public static suspend <T> Object? updateAppWidgetState(android.content.Context context, androidx.glance.state.GlanceStateDefinition<T> definition, androidx.glance.GlanceId glanceId, kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super T>,?> updateState, kotlin.coroutines.Continuation<? super T> p);
+    method public static suspend <T> Object? updateAppWidgetState(androidx.glance.appwidget.GlanceAppWidget, android.content.Context context, androidx.glance.GlanceId glanceId, kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super T>,?> updateState, kotlin.coroutines.Continuation<? super T> p);
   }
 
 }
diff --git a/glance/glance-appwidget/api/public_plus_experimental_current.txt b/glance/glance-appwidget/api/public_plus_experimental_current.txt
index 2efcafe..302578a 100644
--- a/glance/glance-appwidget/api/public_plus_experimental_current.txt
+++ b/glance/glance-appwidget/api/public_plus_experimental_current.txt
@@ -54,12 +54,15 @@
     method @androidx.compose.runtime.Composable public abstract void Content();
     method public androidx.glance.appwidget.SizeMode getSizeMode();
     method public androidx.glance.state.GlanceStateDefinition<?>? getStateDefinition();
+    method public suspend Object? onDelete(androidx.glance.GlanceId glanceId, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
     method public final suspend Object? update(android.content.Context context, androidx.glance.GlanceId glanceId, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
     property public androidx.glance.appwidget.SizeMode sizeMode;
     property public androidx.glance.state.GlanceStateDefinition<?>? stateDefinition;
   }
 
   public final class GlanceAppWidgetKt {
+    method public static suspend Object? updateAll(androidx.glance.appwidget.GlanceAppWidget, android.content.Context context, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method public static suspend inline <reified State> void updateIf(androidx.glance.appwidget.GlanceAppWidget, android.content.Context context, kotlin.jvm.functions.Function1<? super State,? extends java.lang.Boolean> predicate);
   }
 
   public final class GlanceAppWidgetManager {
@@ -183,7 +186,9 @@
 
   public final class GlanceAppWidgetStateKt {
     method public static suspend <T> Object? getAppWidgetState(android.content.Context context, androidx.glance.state.GlanceStateDefinition<T> definition, androidx.glance.GlanceId glanceId, kotlin.coroutines.Continuation<? super T> p);
+    method public static suspend <T> Object? getAppWidgetState(androidx.glance.appwidget.GlanceAppWidget, android.content.Context context, androidx.glance.GlanceId glanceId, kotlin.coroutines.Continuation<? super T> p);
     method public static suspend <T> Object? updateAppWidgetState(android.content.Context context, androidx.glance.state.GlanceStateDefinition<T> definition, androidx.glance.GlanceId glanceId, kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super T>,?> updateState, kotlin.coroutines.Continuation<? super T> p);
+    method public static suspend <T> Object? updateAppWidgetState(androidx.glance.appwidget.GlanceAppWidget, android.content.Context context, androidx.glance.GlanceId glanceId, kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super T>,?> updateState, kotlin.coroutines.Continuation<? super T> p);
   }
 
 }
diff --git a/glance/glance-appwidget/api/restricted_current.txt b/glance/glance-appwidget/api/restricted_current.txt
index 2efcafe..302578a 100644
--- a/glance/glance-appwidget/api/restricted_current.txt
+++ b/glance/glance-appwidget/api/restricted_current.txt
@@ -54,12 +54,15 @@
     method @androidx.compose.runtime.Composable public abstract void Content();
     method public androidx.glance.appwidget.SizeMode getSizeMode();
     method public androidx.glance.state.GlanceStateDefinition<?>? getStateDefinition();
+    method public suspend Object? onDelete(androidx.glance.GlanceId glanceId, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
     method public final suspend Object? update(android.content.Context context, androidx.glance.GlanceId glanceId, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
     property public androidx.glance.appwidget.SizeMode sizeMode;
     property public androidx.glance.state.GlanceStateDefinition<?>? stateDefinition;
   }
 
   public final class GlanceAppWidgetKt {
+    method public static suspend Object? updateAll(androidx.glance.appwidget.GlanceAppWidget, android.content.Context context, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method public static suspend inline <reified State> void updateIf(androidx.glance.appwidget.GlanceAppWidget, android.content.Context context, kotlin.jvm.functions.Function1<? super State,? extends java.lang.Boolean> predicate);
   }
 
   public final class GlanceAppWidgetManager {
@@ -183,7 +186,9 @@
 
   public final class GlanceAppWidgetStateKt {
     method public static suspend <T> Object? getAppWidgetState(android.content.Context context, androidx.glance.state.GlanceStateDefinition<T> definition, androidx.glance.GlanceId glanceId, kotlin.coroutines.Continuation<? super T> p);
+    method public static suspend <T> Object? getAppWidgetState(androidx.glance.appwidget.GlanceAppWidget, android.content.Context context, androidx.glance.GlanceId glanceId, kotlin.coroutines.Continuation<? super T> p);
     method public static suspend <T> Object? updateAppWidgetState(android.content.Context context, androidx.glance.state.GlanceStateDefinition<T> definition, androidx.glance.GlanceId glanceId, kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super T>,?> updateState, kotlin.coroutines.Continuation<? super T> p);
+    method public static suspend <T> Object? updateAppWidgetState(androidx.glance.appwidget.GlanceAppWidget, android.content.Context context, androidx.glance.GlanceId glanceId, kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super T>,?> updateState, kotlin.coroutines.Continuation<? super T> p);
   }
 
 }
diff --git a/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/AppWidgetHostRule.kt b/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/AppWidgetHostRule.kt
index 0a187b8..981a4cb 100644
--- a/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/AppWidgetHostRule.kt
+++ b/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/AppWidgetHostRule.kt
@@ -85,12 +85,15 @@
     private val mInnerRules = RuleChain.outerRule(mActivityRule).around(mOrientationRule)
 
     private var mHostStarted = false
-    lateinit var mHostView: TestAppWidgetHostView
+    private var mMaybeHostView: TestAppWidgetHostView? = null
     private var mAppWidgetId = 0
     private val mScenario: ActivityScenario<AppWidgetHostTestActivity>
         get() = mActivityRule.scenario
     private val mContext = ApplicationProvider.getApplicationContext<Context>()
 
+    val mHostView: TestAppWidgetHostView
+        get() = checkNotNull(mMaybeHostView) { "No app widget installed on the host" }
+
     override fun apply(base: Statement, description: Description) = object : Statement() {
 
         override fun evaluate() {
@@ -111,12 +114,21 @@
         mHostStarted = true
 
         mActivityRule.scenario.onActivity { activity ->
-            mHostView = activity.bindAppWidget(mPortraitSize, mLandscapeSize)
+            mMaybeHostView = activity.bindAppWidget(mPortraitSize, mLandscapeSize)
         }
 
+        val hostView = checkNotNull(mMaybeHostView) { "Host view wasn't successfully started" }
+
         runAndWaitForChildren {
-            mAppWidgetId = mHostView.appWidgetId
-            mHostView.waitForRemoteViews()
+            mAppWidgetId = hostView.appWidgetId
+            hostView.waitForRemoteViews()
+        }
+    }
+
+    fun removeAppWidget() {
+        mActivityRule.scenario.onActivity { activity ->
+            val hostView = checkNotNull(mMaybeHostView) { "No app widget to remove" }
+            activity.deleteAppWidget(hostView)
         }
     }
 
@@ -182,18 +194,21 @@
         mPortraitSize = portrait
         if (!mHostStarted) return
 
-        mScenario.onActivity {
-            mHostView.setSizes(portrait, landscape)
-        }
+        val hostView = mMaybeHostView
+        if (hostView != null) {
+            mScenario.onActivity {
+                hostView.setSizes(portrait, landscape)
+            }
 
-        if (updateRemoteViews) {
-            runAndWaitForChildren {
-                mHostView.resetRemoteViewsLatch()
-                AppWidgetManager.getInstance(mContext).updateAppWidgetOptions(
-                    mAppWidgetId,
-                    optionsBundleOf(listOf(portrait, landscape))
-                )
-                mHostView.waitForRemoteViews()
+            if (updateRemoteViews) {
+                runAndWaitForChildren {
+                    hostView.resetRemoteViewsLatch()
+                    AppWidgetManager.getInstance(mContext).updateAppWidgetOptions(
+                        mAppWidgetId,
+                        optionsBundleOf(listOf(portrait, landscape))
+                    )
+                    hostView.waitForRemoteViews()
+                }
             }
         }
     }
@@ -203,12 +218,13 @@
         run: () -> Unit = {},
         test: () -> Boolean
     ) {
+        val hostView = mHostView
         val latch = CountDownLatch(1)
         val onDrawListener = ViewTreeObserver.OnDrawListener {
-            if (mHostView.childCount > 0 && test()) latch.countDown()
+            if (hostView.childCount > 0 && test()) latch.countDown()
         }
         mActivityRule.scenario.onActivity {
-            mHostView.viewTreeObserver.addOnDrawListener(onDrawListener)
+            hostView.viewTreeObserver.addOnDrawListener(onDrawListener)
         }
 
         run()
@@ -224,7 +240,7 @@
         } finally {
             latch.countDown() // make sure it's released in all conditions
             mActivityRule.scenario.onActivity {
-                mHostView.viewTreeObserver.removeOnDrawListener(onDrawListener)
+                hostView.viewTreeObserver.removeOnDrawListener(onDrawListener)
             }
         }
     }
diff --git a/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/AppWidgetHostTestActivity.kt b/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/AppWidgetHostTestActivity.kt
index a5b1fab..97f6c7d 100644
--- a/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/AppWidgetHostTestActivity.kt
+++ b/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/AppWidgetHostTestActivity.kt
@@ -101,6 +101,14 @@
         return hostView
     }
 
+    fun deleteAppWidget(hostView: TestAppWidgetHostView) {
+        val appWidgetId = hostView.appWidgetId
+        mHost?.deleteAppWidgetId(appWidgetId)
+        mHostViews.remove(hostView)
+        val contentFrame = findViewById<FrameLayout>(R.id.content)
+        contentFrame.removeView(hostView)
+    }
+
     override fun onConfigurationChanged(newConfig: Configuration) {
         super.onConfigurationChanged(newConfig)
         updateAllSizes(newConfig.orientation)
diff --git a/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiverTest.kt b/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiverTest.kt
index bbf99f3..65e7965 100644
--- a/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiverTest.kt
+++ b/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiverTest.kt
@@ -34,15 +34,21 @@
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.dp
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.core.intPreferencesKey
 import androidx.glance.Button
+import androidx.glance.GlanceId
 import androidx.glance.GlanceModifier
 import androidx.glance.Image
 import androidx.glance.ImageProvider
 import androidx.glance.LocalContext
 import androidx.glance.LocalSize
 import androidx.glance.action.actionLaunchActivity
+import androidx.glance.appwidget.state.getAppWidgetState
+import androidx.glance.appwidget.state.updateAppWidgetState
 import androidx.glance.appwidget.test.R
 import androidx.glance.background
+import androidx.glance.currentState
 import androidx.glance.layout.Box
 import androidx.glance.layout.Column
 import androidx.glance.layout.ContentScale
@@ -53,6 +59,7 @@
 import androidx.glance.layout.height
 import androidx.glance.layout.width
 import androidx.glance.layout.wrapContentHeight
+import androidx.glance.state.PreferencesGlanceStateDefinition
 import androidx.glance.text.FontStyle
 import androidx.glance.text.FontWeight
 import androidx.glance.text.Text
@@ -60,10 +67,17 @@
 import androidx.glance.text.TextStyle
 import androidx.test.filters.MediumTest
 import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
 import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.runBlocking
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.atomic.AtomicBoolean
+import java.util.concurrent.atomic.AtomicReference
 import kotlin.test.assertIs
 import kotlin.test.assertNotNull
 
@@ -73,6 +87,8 @@
     @get:Rule
     val mHostRule = AppWidgetHostRule()
 
+    val context = InstrumentationRegistry.getInstrumentation().targetContext!!
+
     @Before
     fun setUp() {
         // Reset the size mode to the default
@@ -432,7 +448,7 @@
 
     @Test
     fun bitmapBackground() {
-        TestGlanceAppWidget.uiDefinition = compose@{
+        TestGlanceAppWidget.uiDefinition = {
             val context = LocalContext.current
             val bitmap =
                 (context.resources.getDrawable(R.drawable.compose, null) as BitmapDrawable).bitmap
@@ -455,8 +471,156 @@
         }
     }
 
+    @Test
+    fun removeAppWidget() {
+        TestGlanceAppWidget.stateDefinition = PreferencesGlanceStateDefinition
+        TestGlanceAppWidget.uiDefinition = {
+            Text("something")
+        }
+
+        mHostRule.startHost()
+
+        val appWidgetManager = GlanceAppWidgetManager(context)
+        val glanceId = runBlocking {
+            appWidgetManager.getGlanceIds(TestGlanceAppWidget::class.java).single()
+        }
+
+        runBlocking {
+            TestGlanceAppWidget.updateAppWidgetState<Preferences>(context, glanceId) { prefs ->
+                prefs.toMutablePreferences().apply {
+                    this[testKey] = 3
+                }
+            }
+        }
+
+        val fileKey = createUniqueRemoteUiName((glanceId as AppWidgetId).appWidgetId)
+        val preferencesFile = PreferencesGlanceStateDefinition.getLocation(context, fileKey)
+
+        assertThat(preferencesFile.exists())
+
+        val deleteLatch = CountDownLatch(1)
+        TestGlanceAppWidget.setOnDeleteBlock {
+            deleteLatch.countDown()
+        }
+
+        mHostRule.removeAppWidget()
+
+        deleteLatch.await(5, TimeUnit.SECONDS)
+        val interval = 200L
+        for (timeout in 0..2000L step interval) {
+            if (!preferencesFile.exists()) return
+            Thread.sleep(interval)
+        }
+        assertWithMessage("View state file exists").that(preferencesFile.exists())
+            .isFalse()
+    }
+
+    @Test
+    fun updateAll() {
+        TestGlanceAppWidget.uiDefinition = {
+            Text("before")
+        }
+
+        mHostRule.startHost()
+
+        val didRun = AtomicBoolean(false)
+        TestGlanceAppWidget.uiDefinition = {
+            didRun.set(true)
+            Text("after")
+        }
+
+        runBlocking {
+            TestGlanceAppWidget.updateAll(context)
+        }
+        assertThat(didRun.get()).isTrue()
+    }
+
+    @Test
+    fun updateIf() {
+        TestGlanceAppWidget.stateDefinition = PreferencesGlanceStateDefinition
+
+        TestGlanceAppWidget.uiDefinition = {
+            Text("before")
+        }
+
+        mHostRule.startHost()
+
+        val appWidgetManager = GlanceAppWidgetManager(context)
+        runBlocking {
+            appWidgetManager.getGlanceIds(TestGlanceAppWidget::class.java)
+                .forEach { glanceId ->
+                    updateAppWidgetState(
+                        context,
+                        PreferencesGlanceStateDefinition,
+                        glanceId
+                    ) { prefs ->
+                        prefs.toMutablePreferences().apply {
+                            this[testKey] = 2
+                        }
+                    }
+                }
+        }
+
+        // Make sure the app widget is updated if the test is true
+        val didRun = AtomicBoolean(false)
+        TestGlanceAppWidget.uiDefinition = {
+            didRun.set(true)
+            Text("after")
+        }
+        runBlocking {
+            TestGlanceAppWidget.updateIf<Preferences>(context) { prefs ->
+                prefs[testKey] == 2
+            }
+        }
+
+        assertThat(didRun.get()).isTrue()
+
+        // Make sure it is not if the test is false
+        didRun.set(false)
+        runBlocking {
+            TestGlanceAppWidget.updateIf<Preferences>(context) { prefs ->
+                prefs[testKey] == 3
+            }
+        }
+
+        assertThat(didRun.get()).isFalse()
+    }
+
+    @Test
+    fun viewState() {
+        TestGlanceAppWidget.stateDefinition = PreferencesGlanceStateDefinition
+
+        TestGlanceAppWidget.uiDefinition = {
+            val value = currentState<Preferences>()[testKey] ?: -1
+            Text("Value = $value")
+        }
+
+        mHostRule.startHost()
+
+        val appWidgetId = AtomicReference<GlanceId>()
+        mHostRule.onHostView { view ->
+            appWidgetId.set(AppWidgetId(view.appWidgetId))
+        }
+
+        runBlocking {
+            TestGlanceAppWidget.updateAppWidgetState<Preferences>(
+                context,
+                appWidgetId.get()
+            ) { prefs ->
+                prefs.toMutablePreferences().apply {
+                    this[testKey] = 2
+                }
+            }
+
+            val prefs =
+                TestGlanceAppWidget.getAppWidgetState<Preferences>(context, appWidgetId.get())
+            assertThat(prefs[testKey]).isEqualTo(2)
+        }
+    }
+
     // Check there is a single span of the given type and that it passes the [check].
-    private inline fun <reified T> SpannedString.checkHasSingleTypedSpan(check: (T) -> Unit) {
+    private inline
+    fun <reified T> SpannedString.checkHasSingleTypedSpan(check: (T) -> Unit) {
         val spans = getSpans(0, length, T::class.java)
         assertThat(spans).hasLength(1)
         check(spans[0])
@@ -464,8 +628,10 @@
 
     private fun assertViewSize(view: View, expectedSize: DpSize) {
         val density = view.context.resources.displayMetrics.density
-        assertThat(view.width / density).isWithin(1.1f / density).of(expectedSize.width.value)
-        assertThat(view.height / density).isWithin(1.1f / density).of(expectedSize.height.value)
+        assertThat(view.width / density).isWithin(1.1f / density)
+            .of(expectedSize.width.value)
+        assertThat(view.height / density).isWithin(1.1f / density)
+            .of(expectedSize.height.value)
     }
 
     private fun assertViewDimension(view: View, sizePx: Int, expectedSize: Dp) {
@@ -473,3 +639,5 @@
         assertThat(sizePx / density).isWithin(1.1f / density).of(expectedSize.value)
     }
 }
+
+private val testKey = intPreferencesKey("testKey")
\ No newline at end of file
diff --git a/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/TestGlanceAppWidgetReceiver.kt b/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/TestGlanceAppWidgetReceiver.kt
index 5f17899..12ac3f0 100644
--- a/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/TestGlanceAppWidgetReceiver.kt
+++ b/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/TestGlanceAppWidgetReceiver.kt
@@ -17,6 +17,8 @@
 package androidx.glance.appwidget
 
 import androidx.compose.runtime.Composable
+import androidx.glance.GlanceId
+import androidx.glance.state.GlanceStateDefinition
 
 class TestGlanceAppWidgetReceiver : GlanceAppWidgetReceiver() {
     override val glanceAppWidget: GlanceAppWidget = TestGlanceAppWidget
@@ -24,6 +26,8 @@
 
 object TestGlanceAppWidget : GlanceAppWidget(errorUiLayout = 0) {
 
+    override var stateDefinition: GlanceStateDefinition<*>? = null
+
     override var sizeMode: SizeMode = SizeMode.Single
 
     @Composable
@@ -31,5 +35,19 @@
         uiDefinition()
     }
 
+    private var onDeleteBlock: ((GlanceId) -> Unit)? = null
+
+    fun setOnDeleteBlock(block: (GlanceId) -> Unit) {
+        onDeleteBlock = block
+    }
+
+    fun resetOnDeleteBlock() {
+        onDeleteBlock = null
+    }
+
+    override suspend fun onDelete(glanceId: GlanceId) {
+        onDeleteBlock?.apply { this(glanceId) }
+    }
+
     var uiDefinition: @Composable () -> Unit = { }
 }
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/GlanceAppWidget.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/GlanceAppWidget.kt
index 56e7a11..05336fd 100644
--- a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/GlanceAppWidget.kt
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/GlanceAppWidget.kt
@@ -19,7 +19,6 @@
 import android.appwidget.AppWidgetManager
 import android.appwidget.AppWidgetProviderInfo
 import android.content.Context
-import android.content.Intent
 import android.os.Build
 import android.os.Bundle
 import android.util.DisplayMetrics
@@ -43,6 +42,7 @@
 import androidx.glance.LocalGlanceId
 import androidx.glance.LocalSize
 import androidx.glance.LocalState
+import androidx.glance.appwidget.state.getAppWidgetState
 import kotlinx.coroutines.CancellationException
 import androidx.glance.state.GlanceState
 import androidx.glance.state.GlanceStateDefinition
@@ -85,6 +85,13 @@
     public open val stateDefinition: GlanceStateDefinition<*>? = null
 
     /**
+     * Method called by the framework when an App Widget has been removed from its host.
+     *
+     * When the method returns, the state associated with the [glanceId] will be deleted.
+     */
+    public open suspend fun onDelete(glanceId: GlanceId) { }
+
+    /**
      * Triggers the composition of [Content] and sends the result to the [AppWidgetManager].
      */
     public suspend fun update(context: Context, glanceId: GlanceId) {
@@ -95,6 +102,26 @@
     }
 
     /**
+     * Calls [onDelete], then clear local data associated with the [appWidgetId].
+     *
+     * This is meant to be called when the App Widget instance has been deleted from the host.
+     */
+    internal suspend fun deleted(context: Context, appWidgetId: Int) {
+        val glanceId = AppWidgetId(appWidgetId)
+        try {
+            onDelete(glanceId)
+        } catch (cancelled: CancellationException) {
+            // Nothing to do here
+        } catch (t: Throwable) {
+            Log.e(GlanceAppWidgetTag, "Error in user-provided deletion callback", t)
+        } finally {
+            stateDefinition?.let {
+                GlanceState.deleteStore(context, it, createUniqueRemoteUiName(appWidgetId))
+            }
+        }
+    }
+
+    /**
      * Internal version of [update], to be used by the broadcast receiver directly.
      */
     internal suspend fun update(
@@ -505,14 +532,24 @@
     Log.e(GlanceAppWidgetTag, "Error in Glance App Widget", throwable)
 }
 
-private fun Intent.extractAppWidgetIds() =
-    getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS)
-        ?: intArrayOf(
-            getIntExtra(
-                AppWidgetManager.EXTRA_APPWIDGET_ID,
-                AppWidgetManager.INVALID_APPWIDGET_ID
-            ).also {
-                check(it != AppWidgetManager.INVALID_APPWIDGET_ID) {
-                    "Cannot determine the app widget id"
-                }
-            })
+/** Update all App Widgets managed by the [GlanceAppWidget] class. */
+public suspend fun GlanceAppWidget.updateAll(@Suppress("ContextFirst") context: Context) {
+    val manager = GlanceAppWidgetManager(context)
+    manager.getGlanceIds(javaClass).forEach { update(context, it) }
+}
+
+/**
+ * Update all App Widgets managed by the [GlanceAppWidget] class, if they fulfill some condition.
+ */
+public suspend inline fun <reified State> GlanceAppWidget.updateIf(
+    @Suppress("ContextFirst") context: Context,
+    predicate: (State) -> Boolean
+) {
+    val stateDef = stateDefinition
+    requireNotNull(stateDef) { "GlanceAppWidget.updateIf cannot be used if no state is defined." }
+    val manager = GlanceAppWidgetManager(context)
+    manager.getGlanceIds(javaClass).forEach { glanceId ->
+        val state = getAppWidgetState(context, stateDef, glanceId) as State
+        if (predicate(state)) update(context, glanceId)
+    }
+}
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiver.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiver.kt
index 6545359..a276ec9 100644
--- a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiver.kt
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiver.kt
@@ -43,6 +43,10 @@
  *
  * Note: If you override any of the [AppWidgetProvider] methods, ensure you call their super-class
  * implementation.
+ *
+ * Important: if you override any of the methods of this class, you must call the super
+ * implementation, and you must not call [AppWidgetProvider.goAsync], as it will be called by the
+ * super implementation. This means your processing time must be short.
  */
 abstract class GlanceAppWidgetReceiver : AppWidgetProvider() {
 
@@ -88,10 +92,11 @@
         }
     }
 
-    override fun onDeleted(context: Context?, appWidgetIds: IntArray?) {
-        // TODO: When a widget is deleted, delete the datastore
-        appWidgetIds?.forEach {
-            createUniqueRemoteUiName(it)
+    @CallSuper
+    override fun onDeleted(context: Context, appWidgetIds: IntArray) {
+        goAsync {
+            updateManager(context)
+            appWidgetIds.forEach { glanceAppWidget.deleted(context, it) }
         }
     }
 
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/state/GlanceAppWidgetState.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/state/GlanceAppWidgetState.kt
index fc8ed71..3c4455d 100644
--- a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/state/GlanceAppWidgetState.kt
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/state/GlanceAppWidgetState.kt
@@ -19,6 +19,7 @@
 import android.content.Context
 import androidx.glance.GlanceId
 import androidx.glance.appwidget.AppWidgetId
+import androidx.glance.appwidget.GlanceAppWidget
 import androidx.glance.appwidget.createUniqueRemoteUiName
 import androidx.glance.state.GlanceState
 import androidx.glance.state.GlanceStateDefinition
@@ -56,3 +57,31 @@
         updateState,
     )
 }
+
+/** Get the state of an App Widget. */
+@Suppress("UNCHECKED_CAST")
+public suspend fun <T> GlanceAppWidget.getAppWidgetState(
+    @Suppress("ContextFirst") context: Context,
+    glanceId: GlanceId
+): T =
+    getAppWidgetState(
+        context,
+        checkNotNull(stateDefinition) { "No state defined in this provider" },
+        glanceId
+    ) as T
+
+/** Update the state of an app widget. */
+@Suppress("UNCHECKED_CAST")
+public suspend fun <T> GlanceAppWidget.updateAppWidgetState(
+    @Suppress("ContextFirst") context: Context,
+    glanceId: GlanceId,
+    updateState: suspend (T) -> T,
+): T =
+    updateAppWidgetState(
+        context,
+        checkNotNull(stateDefinition as GlanceStateDefinition<T>) {
+            "No state defined in this provider"
+        },
+        glanceId,
+        updateState,
+    )
diff --git a/glance/glance/src/androidMain/kotlin/androidx/glance/state/GlanceStateDefinition.kt b/glance/glance/src/androidMain/kotlin/androidx/glance/state/GlanceStateDefinition.kt
index 3be6ce2..51b9999 100644
--- a/glance/glance/src/androidMain/kotlin/androidx/glance/state/GlanceStateDefinition.kt
+++ b/glance/glance/src/androidMain/kotlin/androidx/glance/state/GlanceStateDefinition.kt
@@ -62,43 +62,57 @@
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 public object GlanceState {
-    // TODO(b/205496180): Make methods internal
     /**
      * Returns the stored data associated with the given UI key string.
      *
      * @param definition the configuration that defines this state.
-     * @param fileName identifies the data file associated with the store, must be unique for any
+     * @param fileKey identifies the data file associated with the store, must be unique for any
      * remote UI in the app.
      */
     public suspend fun <T> getValue(
         context: Context,
         definition: GlanceStateDefinition<T>,
-        fileName: String
-    ): T = getDataStore(context, definition, fileName).data.first()
+        fileKey: String
+    ): T = getDataStore(context, definition, fileKey).data.first()
 
     /**
      * Updates the underlying data by applying the provided update block.
      *
      * @param definition the configuration that defines this state.
-     * @param fileName identifies the date file associated with the store, must be unique for any
+     * @param fileKey identifies the date file associated with the store, must be unique for any
      * remote UI in the app.
      */
     public suspend fun <T> updateValue(
         context: Context,
         definition: GlanceStateDefinition<T>,
-        fileName: String,
+        fileKey: String,
         updateBlock: suspend (T) -> T
-    ): T = getDataStore(context, definition, fileName).updateData(updateBlock)
+    ): T = getDataStore(context, definition, fileKey).updateData(updateBlock)
+
+    /**
+     * Delete the file underlying the [DataStore] and remove local references to the [DataStore].
+     */
+    public suspend fun deleteStore(
+        context: Context,
+        definition: GlanceStateDefinition<*>,
+        fileKey: String
+    ) {
+        mutex.withLock {
+            dataStores.remove(fileKey)
+            val location = definition.getLocation(context, fileKey)
+            location.delete()
+        }
+    }
 
     @Suppress("UNCHECKED_CAST")
     private suspend fun <T> getDataStore(
         context: Context,
         definition: GlanceStateDefinition<T>,
-        fileName: String
+        fileKey: String
     ): DataStore<T> =
         mutex.withLock {
-            dataStores.getOrPut(fileName) {
-                definition.getDataStore(context, fileName)
+            dataStores.getOrPut(fileKey) {
+                definition.getDataStore(context, fileKey)
             } as DataStore<T>
         }
 
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index eb02229..ac87caf 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -15,7 +15,7 @@
 androidLintMinCompose = "30.0.0"
 androidxTestRunner = "1.4.0"
 androidxTestRules = "1.4.0"
-androidxTestMonitor = "1.5.0-beta01"
+androidxTestMonitor = "1.5.0-rc01"
 androidxTestCore = "1.4.0"
 androidxTestExtJunit = "1.1.3"
 androidxTestExtTruth = "1.4.0"
@@ -145,7 +145,7 @@
 protobufLite = { module = "com.google.protobuf:protobuf-javalite", version = "3.10.0" }
 reactiveStreams = { module = "org.reactivestreams:reactive-streams", version = "1.0.0" }
 retrofit = { module = "com.squareup.retrofit2:retrofit", version = "2.7.2" }
-robolectric = { module = "org.robolectric:robolectric", version = "4.6.1" }
+robolectric = { module = "org.robolectric:robolectric", version = "4.7" }
 rxjava2 = { module = "io.reactivex.rxjava2:rxjava", version = "2.2.9" }
 rxjava3 = { module = "io.reactivex.rxjava3:rxjava", version = "3.0.0" }
 shadow = { module = "gradle.plugin.com.github.johnrengelman:shadow", version = "7.1.0" }
diff --git a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/ActivityNavigatorTest.kt b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/ActivityNavigatorTest.kt
index c1e144d..fb24f725 100644
--- a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/ActivityNavigatorTest.kt
+++ b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/ActivityNavigatorTest.kt
@@ -302,6 +302,82 @@
     }
 
     @Test
+    fun testEquals() {
+        val firstDestination = activityNavigator.createDestination().apply {
+            id = TARGET_ID
+            setComponentName(ComponentName(activityRule.activity, TargetActivity::class.java))
+        }
+        val secondDestination = activityNavigator.createDestination().apply {
+            id = TARGET_ID
+            setComponentName(ComponentName(activityRule.activity, TargetActivity::class.java))
+        }
+        assertThat(firstDestination).isEqualTo(secondDestination)
+    }
+
+    @Test
+    fun testFilterEquals() {
+        val firstDestination = activityNavigator.createDestination().apply {
+            id = TARGET_ID
+            setComponentName(ComponentName(activityRule.activity, TargetActivity::class.java))
+        }
+        val secondDestination = activityNavigator.createDestination().apply {
+            id = TARGET_ID
+            setComponentName(ComponentName(activityRule.activity, TargetActivity::class.java))
+            intent!!.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+        }
+        assertThat(firstDestination).isEqualTo(secondDestination)
+    }
+
+    @Test
+    fun testEqualsBothIntentNull() {
+        val firstDestination = activityNavigator.createDestination().apply {
+            id = TARGET_ID
+        }
+        val secondDestination = activityNavigator.createDestination().apply {
+            id = TARGET_ID
+        }
+        assertThat(firstDestination).isEqualTo(secondDestination)
+    }
+
+    @Test
+    fun testNotEquals() {
+        val firstDestination = activityNavigator.createDestination().apply {
+            id = TARGET_ID
+            setComponentName(ComponentName(activityRule.activity, TargetActivity::class.java))
+        }
+        val secondDestination = activityNavigator.createDestination().apply {
+            id = TARGET_ID
+            setComponentName(ComponentName(activityRule.activity, TargetActivity::class.java))
+            setAction(TARGET_ACTION)
+        }
+        assertThat(firstDestination).isNotEqualTo(secondDestination)
+    }
+
+    @Test
+    fun testNotEqualsFirstIntentNull() {
+        val firstDestination = activityNavigator.createDestination().apply {
+            id = TARGET_ID
+        }
+        val secondDestination = activityNavigator.createDestination().apply {
+            id = TARGET_ID
+            setComponentName(ComponentName(activityRule.activity, TargetActivity::class.java))
+        }
+        assertThat(firstDestination).isNotEqualTo(secondDestination)
+    }
+
+    @Test
+    fun testNotEqualsSecondIntentNull() {
+        val firstDestination = activityNavigator.createDestination().apply {
+            id = TARGET_ID
+            setComponentName(ComponentName(activityRule.activity, TargetActivity::class.java))
+        }
+        val secondDestination = activityNavigator.createDestination().apply {
+            id = TARGET_ID
+        }
+        assertThat(firstDestination).isNotEqualTo(secondDestination)
+    }
+
+    @Test
     fun testToString() {
         val targetDestination = activityNavigator.createDestination().apply {
             id = TARGET_ID
diff --git a/navigation/navigation-runtime/src/main/java/androidx/navigation/ActivityNavigator.kt b/navigation/navigation-runtime/src/main/java/androidx/navigation/ActivityNavigator.kt
index f52061b..8d880eac 100644
--- a/navigation/navigation-runtime/src/main/java/androidx/navigation/ActivityNavigator.kt
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/ActivityNavigator.kt
@@ -407,22 +407,14 @@
         override fun equals(other: Any?): Boolean {
             if (other == null || other !is Destination) return false
             return super.equals(other) &&
-                intent == other.intent &&
-                dataPattern == other.dataPattern &&
-                targetPackage == other.targetPackage &&
-                component == other.component &&
-                action == other.action &&
-                data == other.data
+                intent?.filterEquals(other.intent) ?: (other.intent == null) &&
+                dataPattern == other.dataPattern
         }
 
         override fun hashCode(): Int {
             var result = super.hashCode()
-            result = 31 * result + intent.hashCode()
+            result = 31 * result + (intent?.filterHashCode() ?: 0)
             result = 31 * result + dataPattern.hashCode()
-            result = 31 * result + targetPackage.hashCode()
-            result = 31 * result + component.hashCode()
-            result = 31 * result + action.hashCode()
-            result = 31 * result + data.hashCode()
             return result
         }
     }
diff --git a/wear/watchface/watchface-editor/src/androidTest/java/androidx/wear/watchface/editor/EditorSessionTest.kt b/wear/watchface/watchface-editor/src/androidTest/java/androidx/wear/watchface/editor/EditorSessionTest.kt
index 47863ba..9a8b93d 100644
--- a/wear/watchface/watchface-editor/src/androidTest/java/androidx/wear/watchface/editor/EditorSessionTest.kt
+++ b/wear/watchface/watchface-editor/src/androidTest/java/androidx/wear/watchface/editor/EditorSessionTest.kt
@@ -34,6 +34,7 @@
 import android.os.Handler
 import android.os.HandlerThread
 import android.os.IBinder
+import android.os.Looper
 import android.support.wearable.complications.IPreviewComplicationDataCallback
 import android.support.wearable.complications.IProviderInfoService
 import android.support.wearable.watchface.Constants
@@ -2175,8 +2176,8 @@
 
         scenario.onActivity { activity ->
             val mockWatchFaceHostApi = mock(WatchFaceHostApi::class.java)
-            val mockHandler = mock(Handler::class.java)
-            `when`(mockWatchFaceHostApi.getUiThreadHandler()).thenReturn(mockHandler)
+            val handler = Handler(Looper.myLooper()!!)
+            `when`(mockWatchFaceHostApi.getUiThreadHandler()).thenReturn(handler)
             `when`(mockWatchFaceHostApi.getContext()).thenReturn(
                 ApplicationProvider.getApplicationContext<Context>()
             )
@@ -2222,7 +2223,7 @@
                     watchState,
                     mockWatchFaceHostApi,
                     CompletableDeferred(),
-                    CoroutineScope(mockHandler.asCoroutineDispatcher())
+                    CoroutineScope(handler.asCoroutineDispatcher())
                 ),
                 null
             )
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/BroadcastsReceiver.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/BroadcastsReceiver.kt
index 6411050..5e60d2d 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/BroadcastsReceiver.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/BroadcastsReceiver.kt
@@ -184,6 +184,7 @@
         context.unregisterReceiver(actionBatteryLowReceiver)
         context.unregisterReceiver(actionBatteryOkayReceiver)
         context.unregisterReceiver(actionPowerConnectedReceiver)
+        context.unregisterReceiver(actionPowerDisconnectedReceiver)
         context.unregisterReceiver(mockTimeReceiver)
     }
 }
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
index 91b47c0..cd03aef 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
@@ -671,6 +671,7 @@
 
         if (!watchState.isHeadless) {
             WatchFace.registerEditorDelegate(componentName, WFEditorDelegate())
+            registerReceivers()
         }
 
         val mainScope = CoroutineScope(Dispatchers.Main.immediate)
@@ -806,10 +807,10 @@
 
     @UiThread
     private fun registerReceivers() {
+        // Looper can be null in some tests.
         require(watchFaceHostApi.getUiThreadHandler().looper.isCurrentThread) {
             "registerReceivers must be called the UiThread"
         }
-
         // There's no point registering BroadcastsReceiver for headless instances.
         if (broadcastsReceiver == null && !watchState.isHeadless) {
             broadcastsReceiver =
@@ -819,6 +820,7 @@
 
     @UiThread
     private fun unregisterReceivers() {
+        // Looper can be null in some tests.
         require(watchFaceHostApi.getUiThreadHandler().looper.isCurrentThread) {
             "unregisterReceivers must be called the UiThread"
         }