Merge "AGP 8.3.0-alpha10 and corresponding Studio" into androidx-main
diff --git a/activity/activity/api/api_lint.ignore b/activity/activity/api/api_lint.ignore
index cc91968..c271630 100644
--- a/activity/activity/api/api_lint.ignore
+++ b/activity/activity/api/api_lint.ignore
@@ -55,11 +55,6 @@
     androidx.activity.result.IntentSenderRequest does not declare a `getFlags()` method matching method androidx.activity.result.IntentSenderRequest.Builder.setFlags(int,int)
 
 
-RegistrationName: androidx.activity.OnBackPressedDispatcher#addCallback(androidx.activity.OnBackPressedCallback):
-    Callback methods should be named register/unregister; was addCallback
-RegistrationName: androidx.activity.OnBackPressedDispatcher#addCallback(androidx.lifecycle.LifecycleOwner, androidx.activity.OnBackPressedCallback):
-    Callback methods should be named register/unregister; was addCallback
-
 PairedRegistration: androidx.activity.OnBackPressedDispatcher#addCallback(androidx.activity.OnBackPressedCallback):
     Found addCallback but not removeCallback in androidx.activity.OnBackPressedDispatcher
 PairedRegistration: androidx.activity.OnBackPressedDispatcher#addCallback(androidx.lifecycle.LifecycleOwner, androidx.activity.OnBackPressedCallback):
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/ObserverManager.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/ObserverManager.java
index bf6306b..d7c46ef 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/ObserverManager.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/ObserverManager.java
@@ -390,14 +390,13 @@
         Map<String, Set<String>> schemaChanges = observerInfo.mSchemaChanges;
         Map<DocumentChangeGroupKey, Set<String>> documentChanges = observerInfo.mDocumentChanges;
         if (schemaChanges.isEmpty() && documentChanges.isEmpty()) {
+            // There is nothing to send, return early.
             return;
         }
-        if (!schemaChanges.isEmpty()) {
-            observerInfo.mSchemaChanges = new ArrayMap<>();
-        }
-        if (!documentChanges.isEmpty()) {
-            observerInfo.mDocumentChanges = new ArrayMap<>();
-        }
+        // Clean the pending changes in the observer. We already copy pending changes to local
+        // variables.
+        observerInfo.mSchemaChanges = new ArrayMap<>();
+        observerInfo.mDocumentChanges = new ArrayMap<>();
 
         // Dispatch the pending changes
         observerInfo.mExecutor.execute(() -> {
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/GlobalSearchSessionCtsTestBase.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/GlobalSearchSessionCtsTestBase.java
index 2054bee..a6374da 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/GlobalSearchSessionCtsTestBase.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/GlobalSearchSessionCtsTestBase.java
@@ -850,32 +850,36 @@
                 new ObserverSpec.Builder().addFilterSchemas("TestAddObserver-Type").build(),
                 EXECUTOR,
                 observer);
+        try {
+            // Index a document
+            mDb1.setSchemaAsync(new SetSchemaRequest.Builder().addSchemas(
+                            new AppSearchSchema.Builder("TestAddObserver-Type").build())
+                    .build()).get();
+            GenericDocument document = new GenericDocument.Builder<GenericDocument.Builder<?>>(
+                    "namespace", "testAddObserver-id1", "TestAddObserver-Type").build();
+            checkIsBatchResultSuccess(
+                    mDb1.putAsync(new PutDocumentsRequest.Builder()
+                            .addGenericDocuments(document).build()));
 
-        // Index a document
-        mDb1.setSchemaAsync(new SetSchemaRequest.Builder().addSchemas(
-                new AppSearchSchema.Builder("TestAddObserver-Type").build())
-                        .build()).get();
-        GenericDocument document = new GenericDocument.Builder<GenericDocument.Builder<?>>(
-                "namespace", "testAddObserver-id1", "TestAddObserver-Type").build();
-        checkIsBatchResultSuccess(
-                mDb1.putAsync(new PutDocumentsRequest.Builder()
-                        .addGenericDocuments(document).build()));
-
-        // Make sure the notification was received.
-        observer.waitForNotificationCount(2);
-        assertThat(observer.getSchemaChanges()).containsExactly(
-                new SchemaChangeInfo(
-                        mContext.getPackageName(),
-                        DB_NAME_1,
-                        /*changedSchemaNames=*/ImmutableSet.of("TestAddObserver-Type")));
-        assertThat(observer.getDocumentChanges()).containsExactly(
-                new DocumentChangeInfo(
-                        mContext.getPackageName(),
-                        DB_NAME_1,
-                        "namespace",
-                        "TestAddObserver-Type",
-                        /*changedDocumentIds=*/ImmutableSet.of("testAddObserver-id1"))
-        );
+            // Make sure the notification was received.
+            observer.waitForNotificationCount(2);
+            assertThat(observer.getSchemaChanges()).containsExactly(
+                    new SchemaChangeInfo(
+                            mContext.getPackageName(),
+                            DB_NAME_1,
+                            /*changedSchemaNames=*/ImmutableSet.of("TestAddObserver-Type")));
+            assertThat(observer.getDocumentChanges()).containsExactly(
+                    new DocumentChangeInfo(
+                            mContext.getPackageName(),
+                            DB_NAME_1,
+                            "namespace",
+                            "TestAddObserver-Type",
+                            /*changedDocumentIds=*/ImmutableSet.of("testAddObserver-id1"))
+            );
+        } finally {
+            // Clean the observer
+            mGlobalSearchSession.unregisterObserverCallback(mContext.getPackageName(), observer);
+        }
     }
 
     @Test
@@ -906,91 +910,98 @@
                 new ObserverSpec.Builder().addFilterSchemas(AppSearchEmail.SCHEMA_TYPE).build(),
                 EXECUTOR,
                 emailObserver);
+        try {
+            // Make sure everything is empty
+            assertThat(unfilteredObserver.getSchemaChanges()).isEmpty();
+            assertThat(unfilteredObserver.getDocumentChanges()).isEmpty();
+            assertThat(emailObserver.getSchemaChanges()).isEmpty();
+            assertThat(emailObserver.getDocumentChanges()).isEmpty();
 
-        // Make sure everything is empty
-        assertThat(unfilteredObserver.getSchemaChanges()).isEmpty();
-        assertThat(unfilteredObserver.getDocumentChanges()).isEmpty();
-        assertThat(emailObserver.getSchemaChanges()).isEmpty();
-        assertThat(emailObserver.getDocumentChanges()).isEmpty();
+            // Index some documents
+            AppSearchEmail email1 = new AppSearchEmail.Builder("namespace", "id1").build();
+            GenericDocument gift1 = new GenericDocument.Builder<GenericDocument.Builder<?>>(
+                    "namespace2", "id2", "Gift").build();
 
-        // Index some documents
-        AppSearchEmail email1 = new AppSearchEmail.Builder("namespace", "id1").build();
-        GenericDocument gift1 = new GenericDocument.Builder<GenericDocument.Builder<?>>(
-                "namespace2", "id2", "Gift").build();
+            checkIsBatchResultSuccess(
+                    mDb1.putAsync(new PutDocumentsRequest.Builder()
+                            .addGenericDocuments(email1).build()));
+            checkIsBatchResultSuccess(
+                    mDb1.putAsync(new PutDocumentsRequest.Builder()
+                            .addGenericDocuments(email1, gift1).build()));
+            checkIsBatchResultSuccess(
+                    mDb2.putAsync(new PutDocumentsRequest.Builder()
+                            .addGenericDocuments(email1).build()));
+            checkIsBatchResultSuccess(
+                    mDb1.putAsync(new PutDocumentsRequest.Builder()
+                            .addGenericDocuments(gift1).build()));
 
-        checkIsBatchResultSuccess(
-                mDb1.putAsync(new PutDocumentsRequest.Builder()
-                        .addGenericDocuments(email1).build()));
-        checkIsBatchResultSuccess(
-                mDb1.putAsync(new PutDocumentsRequest.Builder()
-                        .addGenericDocuments(email1, gift1).build()));
-        checkIsBatchResultSuccess(
-                mDb2.putAsync(new PutDocumentsRequest.Builder()
-                        .addGenericDocuments(email1).build()));
-        checkIsBatchResultSuccess(
-                mDb1.putAsync(new PutDocumentsRequest.Builder()
-                        .addGenericDocuments(gift1).build()));
+            // Make sure the notification was received.
+            unfilteredObserver.waitForNotificationCount(5);
+            emailObserver.waitForNotificationCount(3);
 
-        // Make sure the notification was received.
-        unfilteredObserver.waitForNotificationCount(5);
-        emailObserver.waitForNotificationCount(3);
+            assertThat(unfilteredObserver.getSchemaChanges()).isEmpty();
+            assertThat(unfilteredObserver.getDocumentChanges()).containsExactly(
+                    new DocumentChangeInfo(
+                            mContext.getPackageName(),
+                            DB_NAME_1,
+                            "namespace",
+                            AppSearchEmail.SCHEMA_TYPE,
+                            /*changedDocumentIds=*/ImmutableSet.of("id1")),
+                    new DocumentChangeInfo(
+                            mContext.getPackageName(),
+                            DB_NAME_1,
+                            "namespace",
+                            AppSearchEmail.SCHEMA_TYPE,
+                            /*changedDocumentIds=*/ImmutableSet.of("id1")),
+                    new DocumentChangeInfo(
+                            mContext.getPackageName(),
+                            DB_NAME_1,
+                            "namespace2",
+                            "Gift",
+                            /*changedDocumentIds=*/ImmutableSet.of("id2")),
+                    new DocumentChangeInfo(
+                            mContext.getPackageName(),
+                            DB_NAME_2,
+                            "namespace",
+                            AppSearchEmail.SCHEMA_TYPE,
+                            /*changedDocumentIds=*/ImmutableSet.of("id1")),
+                    new DocumentChangeInfo(
+                            mContext.getPackageName(),
+                            DB_NAME_1,
+                            "namespace2",
+                            "Gift",
+                            /*changedDocumentIds=*/ImmutableSet.of("id2"))
+            );
 
-        assertThat(unfilteredObserver.getSchemaChanges()).isEmpty();
-        assertThat(unfilteredObserver.getDocumentChanges()).containsExactly(
-                new DocumentChangeInfo(
-                        mContext.getPackageName(),
-                        DB_NAME_1,
-                        "namespace",
-                        AppSearchEmail.SCHEMA_TYPE,
-                        /*changedDocumentIds=*/ImmutableSet.of("id1")),
-                new DocumentChangeInfo(
-                        mContext.getPackageName(),
-                        DB_NAME_1,
-                        "namespace",
-                        AppSearchEmail.SCHEMA_TYPE,
-                        /*changedDocumentIds=*/ImmutableSet.of("id1")),
-                new DocumentChangeInfo(
-                        mContext.getPackageName(),
-                        DB_NAME_1,
-                        "namespace2",
-                        "Gift",
-                        /*changedDocumentIds=*/ImmutableSet.of("id2")),
-                new DocumentChangeInfo(
-                        mContext.getPackageName(),
-                        DB_NAME_2,
-                        "namespace",
-                        AppSearchEmail.SCHEMA_TYPE,
-                        /*changedDocumentIds=*/ImmutableSet.of("id1")),
-                new DocumentChangeInfo(
-                        mContext.getPackageName(),
-                        DB_NAME_1,
-                        "namespace2",
-                        "Gift",
-                        /*changedDocumentIds=*/ImmutableSet.of("id2"))
-        );
-
-        // Check the filtered observer
-        assertThat(emailObserver.getSchemaChanges()).isEmpty();
-        assertThat(emailObserver.getDocumentChanges()).containsExactly(
-                new DocumentChangeInfo(
-                        mContext.getPackageName(),
-                        DB_NAME_1,
-                        "namespace",
-                        AppSearchEmail.SCHEMA_TYPE,
-                        /*changedDocumentIds=*/ImmutableSet.of("id1")),
-                new DocumentChangeInfo(
-                        mContext.getPackageName(),
-                        DB_NAME_1,
-                        "namespace",
-                        AppSearchEmail.SCHEMA_TYPE,
-                        /*changedDocumentIds=*/ImmutableSet.of("id1")),
-                new DocumentChangeInfo(
-                        mContext.getPackageName(),
-                        DB_NAME_2,
-                        "namespace",
-                        AppSearchEmail.SCHEMA_TYPE,
-                        /*changedDocumentIds=*/ImmutableSet.of("id1"))
-        );
+            // Check the filtered observer
+            assertThat(emailObserver.getSchemaChanges()).isEmpty();
+            assertThat(emailObserver.getDocumentChanges()).containsExactly(
+                    new DocumentChangeInfo(
+                            mContext.getPackageName(),
+                            DB_NAME_1,
+                            "namespace",
+                            AppSearchEmail.SCHEMA_TYPE,
+                            /*changedDocumentIds=*/ImmutableSet.of("id1")),
+                    new DocumentChangeInfo(
+                            mContext.getPackageName(),
+                            DB_NAME_1,
+                            "namespace",
+                            AppSearchEmail.SCHEMA_TYPE,
+                            /*changedDocumentIds=*/ImmutableSet.of("id1")),
+                    new DocumentChangeInfo(
+                            mContext.getPackageName(),
+                            DB_NAME_2,
+                            "namespace",
+                            AppSearchEmail.SCHEMA_TYPE,
+                            /*changedDocumentIds=*/ImmutableSet.of("id1"))
+            );
+        } finally {
+            // Clean the observer
+            mGlobalSearchSession.unregisterObserverCallback(
+                    mContext.getPackageName(), emailObserver);
+            mGlobalSearchSession.unregisterObserverCallback(mContext.getPackageName(),
+                    unfilteredObserver);
+        }
     }
 
     @Test
@@ -1017,95 +1028,102 @@
                 new ObserverSpec.Builder().addFilterSchemas(AppSearchEmail.SCHEMA_TYPE).build(),
                 EXECUTOR,
                 emailObserver);
+        try {
+            // Make sure everything is empty
+            assertThat(unfilteredObserver.getSchemaChanges()).isEmpty();
+            assertThat(unfilteredObserver.getDocumentChanges()).isEmpty();
+            assertThat(emailObserver.getSchemaChanges()).isEmpty();
+            assertThat(emailObserver.getDocumentChanges()).isEmpty();
 
-        // Make sure everything is empty
-        assertThat(unfilteredObserver.getSchemaChanges()).isEmpty();
-        assertThat(unfilteredObserver.getDocumentChanges()).isEmpty();
-        assertThat(emailObserver.getSchemaChanges()).isEmpty();
-        assertThat(emailObserver.getDocumentChanges()).isEmpty();
+            // Index some documents
+            AppSearchEmail email1 = new AppSearchEmail.Builder("namespace", "id1").build();
+            GenericDocument gift1 = new GenericDocument.Builder<GenericDocument.Builder<?>>(
+                    "namespace2", "id2", "Gift").build();
 
-        // Index some documents
-        AppSearchEmail email1 = new AppSearchEmail.Builder("namespace", "id1").build();
-        GenericDocument gift1 = new GenericDocument.Builder<GenericDocument.Builder<?>>(
-                "namespace2", "id2", "Gift").build();
+            checkIsBatchResultSuccess(
+                    mDb1.putAsync(new PutDocumentsRequest.Builder()
+                            .addGenericDocuments(email1).build()));
+            checkIsBatchResultSuccess(
+                    mDb1.putAsync(new PutDocumentsRequest.Builder()
+                            .addGenericDocuments(email1, gift1).build()));
+            checkIsBatchResultSuccess(
+                    mDb2.putAsync(new PutDocumentsRequest.Builder()
+                            .addGenericDocuments(email1, gift1).build()));
+            checkIsBatchResultSuccess(
+                    mDb1.putAsync(new PutDocumentsRequest.Builder()
+                            .addGenericDocuments(gift1).build()));
 
-        checkIsBatchResultSuccess(
-                mDb1.putAsync(new PutDocumentsRequest.Builder()
-                        .addGenericDocuments(email1).build()));
-        checkIsBatchResultSuccess(
-                mDb1.putAsync(new PutDocumentsRequest.Builder()
-                        .addGenericDocuments(email1, gift1).build()));
-        checkIsBatchResultSuccess(
-                mDb2.putAsync(new PutDocumentsRequest.Builder()
-                        .addGenericDocuments(email1, gift1).build()));
-        checkIsBatchResultSuccess(
-                mDb1.putAsync(new PutDocumentsRequest.Builder()
-                        .addGenericDocuments(gift1).build()));
+            // Register the second observer
+            mGlobalSearchSession.registerObserverCallback(
+                    mContext.getPackageName(),
+                    new ObserverSpec.Builder().build(),
+                    EXECUTOR,
+                    unfilteredObserver);
 
-        // Register the second observer
-        mGlobalSearchSession.registerObserverCallback(
-                mContext.getPackageName(),
-                new ObserverSpec.Builder().build(),
-                EXECUTOR,
-                unfilteredObserver);
+            // Remove some of the documents.
+            checkIsBatchResultSuccess(mDb1.removeAsync(
+                    new RemoveByDocumentIdRequest.Builder("namespace").addIds("id1").build()));
+            checkIsBatchResultSuccess(mDb2.removeAsync(
+                    new RemoveByDocumentIdRequest.Builder("namespace2").addIds("id2").build()));
 
-        // Remove some of the documents.
-        checkIsBatchResultSuccess(mDb1.removeAsync(
-                new RemoveByDocumentIdRequest.Builder("namespace").addIds("id1").build()));
-        checkIsBatchResultSuccess(mDb2.removeAsync(
-                new RemoveByDocumentIdRequest.Builder("namespace2").addIds("id2").build()));
+            // Make sure the notification was received. emailObserver should have seen:
+            //   +db1:email, +db1:email, +db2:email, -db1:email.
+            // unfilteredObserver (registered later) should have seen:
+            //   -db1:email, -db2:gift
+            emailObserver.waitForNotificationCount(4);
+            unfilteredObserver.waitForNotificationCount(2);
 
-        // Make sure the notification was received. emailObserver should have seen:
-        //   +db1:email, +db1:email, +db2:email, -db1:email.
-        // unfilteredObserver (registered later) should have seen:
-        //   -db1:email, -db2:gift
-        emailObserver.waitForNotificationCount(4);
-        unfilteredObserver.waitForNotificationCount(2);
+            assertThat(emailObserver.getSchemaChanges()).isEmpty();
+            assertThat(emailObserver.getDocumentChanges()).containsExactly(
+                    new DocumentChangeInfo(
+                            mContext.getPackageName(),
+                            DB_NAME_1,
+                            "namespace",
+                            AppSearchEmail.SCHEMA_TYPE,
+                            /*changedDocumentIds=*/ImmutableSet.of("id1")),
+                    new DocumentChangeInfo(
+                            mContext.getPackageName(),
+                            DB_NAME_1,
+                            "namespace",
+                            AppSearchEmail.SCHEMA_TYPE,
+                            /*changedDocumentIds=*/ImmutableSet.of("id1")),
+                    new DocumentChangeInfo(
+                            mContext.getPackageName(),
+                            DB_NAME_2,
+                            "namespace",
+                            AppSearchEmail.SCHEMA_TYPE,
+                            /*changedDocumentIds=*/ImmutableSet.of("id1")),
+                    new DocumentChangeInfo(
+                            mContext.getPackageName(),
+                            DB_NAME_1,
+                            "namespace",
+                            AppSearchEmail.SCHEMA_TYPE,
+                            /*changedDocumentIds=*/ImmutableSet.of("id1"))
+            );
 
-        assertThat(emailObserver.getSchemaChanges()).isEmpty();
-        assertThat(emailObserver.getDocumentChanges()).containsExactly(
-                new DocumentChangeInfo(
-                        mContext.getPackageName(),
-                        DB_NAME_1,
-                        "namespace",
-                        AppSearchEmail.SCHEMA_TYPE,
-                        /*changedDocumentIds=*/ImmutableSet.of("id1")),
-                new DocumentChangeInfo(
-                        mContext.getPackageName(),
-                        DB_NAME_1,
-                        "namespace",
-                        AppSearchEmail.SCHEMA_TYPE,
-                        /*changedDocumentIds=*/ImmutableSet.of("id1")),
-                new DocumentChangeInfo(
-                        mContext.getPackageName(),
-                        DB_NAME_2,
-                        "namespace",
-                        AppSearchEmail.SCHEMA_TYPE,
-                        /*changedDocumentIds=*/ImmutableSet.of("id1")),
-                new DocumentChangeInfo(
-                        mContext.getPackageName(),
-                        DB_NAME_1,
-                        "namespace",
-                        AppSearchEmail.SCHEMA_TYPE,
-                        /*changedDocumentIds=*/ImmutableSet.of("id1"))
-        );
-
-        // Check unfilteredObserver
-        assertThat(unfilteredObserver.getSchemaChanges()).isEmpty();
-        assertThat(unfilteredObserver.getDocumentChanges()).containsExactly(
-                new DocumentChangeInfo(
-                        mContext.getPackageName(),
-                        DB_NAME_1,
-                        "namespace",
-                        AppSearchEmail.SCHEMA_TYPE,
-                        /*changedDocumentIds=*/ImmutableSet.of("id1")),
-                new DocumentChangeInfo(
-                        mContext.getPackageName(),
-                        DB_NAME_2,
-                        "namespace2",
-                        "Gift",
-                        /*changedDocumentIds=*/ImmutableSet.of("id2"))
-        );
+            // Check unfilteredObserver
+            assertThat(unfilteredObserver.getSchemaChanges()).isEmpty();
+            assertThat(unfilteredObserver.getDocumentChanges()).containsExactly(
+                    new DocumentChangeInfo(
+                            mContext.getPackageName(),
+                            DB_NAME_1,
+                            "namespace",
+                            AppSearchEmail.SCHEMA_TYPE,
+                            /*changedDocumentIds=*/ImmutableSet.of("id1")),
+                    new DocumentChangeInfo(
+                            mContext.getPackageName(),
+                            DB_NAME_2,
+                            "namespace2",
+                            "Gift",
+                            /*changedDocumentIds=*/ImmutableSet.of("id2"))
+            );
+        } finally {
+            // Clean the observer
+            mGlobalSearchSession.unregisterObserverCallback(mContext.getPackageName(),
+                    emailObserver);
+            mGlobalSearchSession.unregisterObserverCallback(mContext.getPackageName(),
+                    unfilteredObserver);
+        }
     }
 
     @Test
@@ -1150,65 +1168,72 @@
                 new ObserverSpec.Builder().addFilterSchemas(AppSearchEmail.SCHEMA_TYPE).build(),
                 EXECUTOR,
                 emailObserver);
+        try {
+            // Make sure everything is empty
+            assertThat(unfilteredObserver.getSchemaChanges()).isEmpty();
+            assertThat(unfilteredObserver.getDocumentChanges()).isEmpty();
+            assertThat(emailObserver.getSchemaChanges()).isEmpty();
+            assertThat(emailObserver.getDocumentChanges()).isEmpty();
 
-        // Make sure everything is empty
-        assertThat(unfilteredObserver.getSchemaChanges()).isEmpty();
-        assertThat(unfilteredObserver.getDocumentChanges()).isEmpty();
-        assertThat(emailObserver.getSchemaChanges()).isEmpty();
-        assertThat(emailObserver.getDocumentChanges()).isEmpty();
+            // Remove "cat" emails in db1 and all types in db2
+            mDb1.removeAsync("cat",
+                            new SearchSpec.Builder()
+                                    .addFilterSchemas(AppSearchEmail.SCHEMA_TYPE).build())
+                    .get();
+            mDb2.removeAsync("", new SearchSpec.Builder().build()).get();
 
-        // Remove "cat" emails in db1 and all types in db2
-        mDb1.removeAsync("cat",
-                        new SearchSpec.Builder()
-                                .addFilterSchemas(AppSearchEmail.SCHEMA_TYPE).build())
-                .get();
-        mDb2.removeAsync("", new SearchSpec.Builder().build()).get();
+            // Make sure the notification was received. UnfilteredObserver should have seen:
+            //   -db1:id2, -db2:id1, -db2:id2, -db2:id3
+            // emailObserver should have seen:
+            //   -db1:id2, -db2:id1, -db2:id2
+            unfilteredObserver.waitForNotificationCount(3);
+            emailObserver.waitForNotificationCount(2);
 
-        // Make sure the notification was received. UnfilteredObserver should have seen:
-        //   -db1:id2, -db2:id1, -db2:id2, -db2:id3
-        // emailObserver should have seen:
-        //   -db1:id2, -db2:id1, -db2:id2
-        unfilteredObserver.waitForNotificationCount(3);
-        emailObserver.waitForNotificationCount(2);
+            assertThat(unfilteredObserver.getSchemaChanges()).isEmpty();
+            assertThat(unfilteredObserver.getDocumentChanges()).containsExactly(
+                    new DocumentChangeInfo(
+                            mContext.getPackageName(),
+                            DB_NAME_1,
+                            "namespace",
+                            AppSearchEmail.SCHEMA_TYPE,
+                            /*changedDocumentIds=*/ImmutableSet.of("id2")),
+                    new DocumentChangeInfo(
+                            mContext.getPackageName(),
+                            DB_NAME_2,
+                            "namespace",
+                            AppSearchEmail.SCHEMA_TYPE,
+                            /*changedDocumentIds=*/ImmutableSet.of("id1", "id2")),
+                    new DocumentChangeInfo(
+                            mContext.getPackageName(),
+                            DB_NAME_2,
+                            "namespace2",
+                            "Gift",
+                            /*changedDocumentIds=*/ImmutableSet.of("id3"))
+            );
 
-        assertThat(unfilteredObserver.getSchemaChanges()).isEmpty();
-        assertThat(unfilteredObserver.getDocumentChanges()).containsExactly(
-                new DocumentChangeInfo(
-                        mContext.getPackageName(),
-                        DB_NAME_1,
-                        "namespace",
-                        AppSearchEmail.SCHEMA_TYPE,
-                        /*changedDocumentIds=*/ImmutableSet.of("id2")),
-                new DocumentChangeInfo(
-                        mContext.getPackageName(),
-                        DB_NAME_2,
-                        "namespace",
-                        AppSearchEmail.SCHEMA_TYPE,
-                        /*changedDocumentIds=*/ImmutableSet.of("id1", "id2")),
-                new DocumentChangeInfo(
-                        mContext.getPackageName(),
-                        DB_NAME_2,
-                        "namespace2",
-                        "Gift",
-                        /*changedDocumentIds=*/ImmutableSet.of("id3"))
-        );
-
-        // Check emailObserver
-        assertThat(emailObserver.getSchemaChanges()).isEmpty();
-        assertThat(emailObserver.getDocumentChanges()).containsExactly(
-                new DocumentChangeInfo(
-                        mContext.getPackageName(),
-                        DB_NAME_1,
-                        "namespace",
-                        AppSearchEmail.SCHEMA_TYPE,
-                        /*changedDocumentIds=*/ImmutableSet.of("id2")),
-                new DocumentChangeInfo(
-                        mContext.getPackageName(),
-                        DB_NAME_2,
-                        "namespace",
-                        AppSearchEmail.SCHEMA_TYPE,
-                        /*changedDocumentIds=*/ImmutableSet.of("id1", "id2"))
-        );
+            // Check emailObserver
+            assertThat(emailObserver.getSchemaChanges()).isEmpty();
+            assertThat(emailObserver.getDocumentChanges()).containsExactly(
+                    new DocumentChangeInfo(
+                            mContext.getPackageName(),
+                            DB_NAME_1,
+                            "namespace",
+                            AppSearchEmail.SCHEMA_TYPE,
+                            /*changedDocumentIds=*/ImmutableSet.of("id2")),
+                    new DocumentChangeInfo(
+                            mContext.getPackageName(),
+                            DB_NAME_2,
+                            "namespace",
+                            AppSearchEmail.SCHEMA_TYPE,
+                            /*changedDocumentIds=*/ImmutableSet.of("id1", "id2"))
+            );
+        } finally {
+            // Clean the observer
+            mGlobalSearchSession.unregisterObserverCallback(mContext.getPackageName(),
+                    emailObserver);
+            mGlobalSearchSession.unregisterObserverCallback(mContext.getPackageName(),
+                    unfilteredObserver);
+        }
     }
 
     @Test
@@ -1236,33 +1261,37 @@
                 new ObserverSpec.Builder().addFilterSchemas(AppSearchEmail.SCHEMA_TYPE).build(),
                 EXECUTOR,
                 observer);
+        try {
+            // Index one email and one gift
+            AppSearchEmail email1 = new AppSearchEmail.Builder("namespace", "id1").build();
+            GenericDocument gift1 = new GenericDocument.Builder<GenericDocument.Builder<?>>(
+                    "namespace2", "id3", "Gift").build();
 
-        // Index one email and one gift
-        AppSearchEmail email1 = new AppSearchEmail.Builder("namespace", "id1").build();
-        GenericDocument gift1 = new GenericDocument.Builder<GenericDocument.Builder<?>>(
-                "namespace2", "id3", "Gift").build();
+            checkIsBatchResultSuccess(
+                    mDb1.putAsync(new PutDocumentsRequest.Builder()
+                            .addGenericDocuments(email1, gift1).build()));
 
-        checkIsBatchResultSuccess(
-                mDb1.putAsync(new PutDocumentsRequest.Builder()
-                        .addGenericDocuments(email1, gift1).build()));
-
-        // Make sure the same observer received both values
-        observer.waitForNotificationCount(2);
-        assertThat(observer.getSchemaChanges()).isEmpty();
-        assertThat(observer.getDocumentChanges()).containsExactly(
-                new DocumentChangeInfo(
-                        mContext.getPackageName(),
-                        DB_NAME_1,
-                        "namespace",
-                        AppSearchEmail.SCHEMA_TYPE,
-                        /*changedDocumentIds=*/ImmutableSet.of("id1")),
-                new DocumentChangeInfo(
-                        mContext.getPackageName(),
-                        DB_NAME_1,
-                        "namespace2",
-                        "Gift",
-                        /*changedDocumentIds=*/ImmutableSet.of("id3"))
-        );
+            // Make sure the same observer received both values
+            observer.waitForNotificationCount(2);
+            assertThat(observer.getSchemaChanges()).isEmpty();
+            assertThat(observer.getDocumentChanges()).containsExactly(
+                    new DocumentChangeInfo(
+                            mContext.getPackageName(),
+                            DB_NAME_1,
+                            "namespace",
+                            AppSearchEmail.SCHEMA_TYPE,
+                            /*changedDocumentIds=*/ImmutableSet.of("id1")),
+                    new DocumentChangeInfo(
+                            mContext.getPackageName(),
+                            DB_NAME_1,
+                            "namespace2",
+                            "Gift",
+                            /*changedDocumentIds=*/ImmutableSet.of("id3"))
+            );
+        } finally {
+            // Clean the observer
+            mGlobalSearchSession.unregisterObserverCallback(mContext.getPackageName(), observer);
+        }
     }
 
     @Test
@@ -1299,93 +1328,100 @@
                 new ObserverSpec.Builder().build(),
                 EXECUTOR,
                 permanentObserver);
+        try {
+            // Make sure everything is empty
+            assertThat(temporaryObserver.getSchemaChanges()).isEmpty();
+            assertThat(temporaryObserver.getDocumentChanges()).isEmpty();
+            assertThat(permanentObserver.getSchemaChanges()).isEmpty();
+            assertThat(permanentObserver.getDocumentChanges()).isEmpty();
 
-        // Make sure everything is empty
-        assertThat(temporaryObserver.getSchemaChanges()).isEmpty();
-        assertThat(temporaryObserver.getDocumentChanges()).isEmpty();
-        assertThat(permanentObserver.getSchemaChanges()).isEmpty();
-        assertThat(permanentObserver.getDocumentChanges()).isEmpty();
+            // Index some documents
+            AppSearchEmail email1 = new AppSearchEmail.Builder("namespace", "id1").build();
+            AppSearchEmail email2 =
+                    new AppSearchEmail.Builder("namespace", "id2").setBody("caterpillar").build();
+            GenericDocument gift1 = new GenericDocument.Builder<GenericDocument.Builder<?>>(
+                    "namespace2", "id3", "Gift").build();
+            GenericDocument gift2 = new GenericDocument.Builder<GenericDocument.Builder<?>>(
+                    "namespace3", "id4", "Gift").build();
 
-        // Index some documents
-        AppSearchEmail email1 = new AppSearchEmail.Builder("namespace", "id1").build();
-        AppSearchEmail email2 =
-                new AppSearchEmail.Builder("namespace", "id2").setBody("caterpillar").build();
-        GenericDocument gift1 = new GenericDocument.Builder<GenericDocument.Builder<?>>(
-                "namespace2", "id3", "Gift").build();
-        GenericDocument gift2 = new GenericDocument.Builder<GenericDocument.Builder<?>>(
-                "namespace3", "id4", "Gift").build();
+            checkIsBatchResultSuccess(
+                    mDb1.putAsync(new PutDocumentsRequest.Builder()
+                            .addGenericDocuments(email1, gift1).build()));
 
-        checkIsBatchResultSuccess(
-                mDb1.putAsync(new PutDocumentsRequest.Builder()
-                        .addGenericDocuments(email1, gift1).build()));
+            // Make sure the notifications were received.
+            temporaryObserver.waitForNotificationCount(2);
+            permanentObserver.waitForNotificationCount(2);
 
-        // Make sure the notifications were received.
-        temporaryObserver.waitForNotificationCount(2);
-        permanentObserver.waitForNotificationCount(2);
+            List<DocumentChangeInfo> expectedChangesOrig = ImmutableList.of(
+                    new DocumentChangeInfo(
+                            mContext.getPackageName(),
+                            DB_NAME_1,
+                            "namespace",
+                            AppSearchEmail.SCHEMA_TYPE,
+                            /*changedDocumentIds=*/ImmutableSet.of("id1")),
+                    new DocumentChangeInfo(
+                            mContext.getPackageName(),
+                            DB_NAME_1,
+                            "namespace2",
+                            "Gift",
+                            /*changedDocumentIds=*/ImmutableSet.of("id3")));
+            assertThat(temporaryObserver.getSchemaChanges()).isEmpty();
+            assertThat(temporaryObserver.getDocumentChanges())
+                    .containsExactlyElementsIn(expectedChangesOrig);
+            assertThat(permanentObserver.getSchemaChanges()).isEmpty();
+            assertThat(permanentObserver.getDocumentChanges())
+                    .containsExactlyElementsIn(expectedChangesOrig);
 
-        List<DocumentChangeInfo> expectedChangesOrig = ImmutableList.of(
-                new DocumentChangeInfo(
-                        mContext.getPackageName(),
-                        DB_NAME_1,
-                        "namespace",
-                        AppSearchEmail.SCHEMA_TYPE,
-                        /*changedDocumentIds=*/ImmutableSet.of("id1")),
-                new DocumentChangeInfo(
-                        mContext.getPackageName(),
-                        DB_NAME_1,
-                        "namespace2",
-                        "Gift",
-                        /*changedDocumentIds=*/ImmutableSet.of("id3")));
-        assertThat(temporaryObserver.getSchemaChanges()).isEmpty();
-        assertThat(temporaryObserver.getDocumentChanges())
-                .containsExactlyElementsIn(expectedChangesOrig);
-        assertThat(permanentObserver.getSchemaChanges()).isEmpty();
-        assertThat(permanentObserver.getDocumentChanges())
-                .containsExactlyElementsIn(expectedChangesOrig);
+            // Unregister temporaryObserver
+            mGlobalSearchSession.unregisterObserverCallback(
+                    mContext.getPackageName(), temporaryObserver);
 
-        // Unregister temporaryObserver
-        mGlobalSearchSession.unregisterObserverCallback(
-                mContext.getPackageName(), temporaryObserver);
+            // Index some more documents
+            checkIsBatchResultSuccess(
+                    mDb1.putAsync(new PutDocumentsRequest.Builder()
+                            .addGenericDocuments(email2, gift2).build()));
 
-        // Index some more documents
-        checkIsBatchResultSuccess(
-                mDb1.putAsync(new PutDocumentsRequest.Builder()
-                        .addGenericDocuments(email2, gift2).build()));
+            // Only the permanent observer should have received this
+            permanentObserver.waitForNotificationCount(4);
+            temporaryObserver.waitForNotificationCount(2);
 
-        // Only the permanent observer should have received this
-        permanentObserver.waitForNotificationCount(4);
-        temporaryObserver.waitForNotificationCount(2);
-
-        assertThat(permanentObserver.getSchemaChanges()).isEmpty();
-        assertThat(permanentObserver.getDocumentChanges()).containsExactly(
-                new DocumentChangeInfo(
-                        mContext.getPackageName(),
-                        DB_NAME_1,
-                        "namespace",
-                        AppSearchEmail.SCHEMA_TYPE,
-                        /*changedDocumentIds=*/ImmutableSet.of("id1")),
-                new DocumentChangeInfo(
-                        mContext.getPackageName(),
-                        DB_NAME_1,
-                        "namespace2",
-                        "Gift",
-                        /*changedDocumentIds=*/ImmutableSet.of("id3")),
-                new DocumentChangeInfo(
-                        mContext.getPackageName(),
-                        DB_NAME_1,
-                        "namespace",
-                        AppSearchEmail.SCHEMA_TYPE,
-                        /*changedDocumentIds=*/ImmutableSet.of("id2")),
-                new DocumentChangeInfo(
-                        mContext.getPackageName(),
-                        DB_NAME_1,
-                        "namespace3",
-                        "Gift",
-                        /*changedDocumentIds=*/ImmutableSet.of("id4"))
-        );
-        assertThat(temporaryObserver.getSchemaChanges()).isEmpty();
-        assertThat(temporaryObserver.getDocumentChanges())
-                .containsExactlyElementsIn(expectedChangesOrig);
+            assertThat(permanentObserver.getSchemaChanges()).isEmpty();
+            assertThat(permanentObserver.getDocumentChanges()).containsExactly(
+                    new DocumentChangeInfo(
+                            mContext.getPackageName(),
+                            DB_NAME_1,
+                            "namespace",
+                            AppSearchEmail.SCHEMA_TYPE,
+                            /*changedDocumentIds=*/ImmutableSet.of("id1")),
+                    new DocumentChangeInfo(
+                            mContext.getPackageName(),
+                            DB_NAME_1,
+                            "namespace2",
+                            "Gift",
+                            /*changedDocumentIds=*/ImmutableSet.of("id3")),
+                    new DocumentChangeInfo(
+                            mContext.getPackageName(),
+                            DB_NAME_1,
+                            "namespace",
+                            AppSearchEmail.SCHEMA_TYPE,
+                            /*changedDocumentIds=*/ImmutableSet.of("id2")),
+                    new DocumentChangeInfo(
+                            mContext.getPackageName(),
+                            DB_NAME_1,
+                            "namespace3",
+                            "Gift",
+                            /*changedDocumentIds=*/ImmutableSet.of("id4"))
+            );
+            assertThat(temporaryObserver.getSchemaChanges()).isEmpty();
+            assertThat(temporaryObserver.getDocumentChanges())
+                    .containsExactlyElementsIn(expectedChangesOrig);
+        } finally {
+            // Clean the observer
+            mGlobalSearchSession.unregisterObserverCallback(mContext.getPackageName(),
+                    temporaryObserver);
+            mGlobalSearchSession.unregisterObserverCallback(mContext.getPackageName(),
+                    permanentObserver);
+        }
     }
 
     @Test
@@ -1459,40 +1495,45 @@
                 new ObserverSpec.Builder().build(),
                 EXECUTOR,
                 observer);
+        try {
+            // Add a schema type
+            assertThat(observer.getSchemaChanges()).isEmpty();
+            assertThat(observer.getDocumentChanges()).isEmpty();
+            mDb1.setSchemaAsync(
+                            new SetSchemaRequest.Builder()
+                                    .addSchemas(new AppSearchSchema.Builder("Type1").build())
+                                    .build())
+                    .get();
 
-        // Add a schema type
-        assertThat(observer.getSchemaChanges()).isEmpty();
-        assertThat(observer.getDocumentChanges()).isEmpty();
-        mDb1.setSchemaAsync(
-                        new SetSchemaRequest.Builder()
-                                .addSchemas(new AppSearchSchema.Builder("Type1").build())
-                                .build())
-                .get();
+            observer.waitForNotificationCount(1);
+            assertThat(observer.getSchemaChanges()).containsExactly(
+                    new SchemaChangeInfo(
+                            mContext.getPackageName(),
+                            DB_NAME_1,
+                            ImmutableSet.of("Type1")));
+            assertThat(observer.getDocumentChanges()).isEmpty();
 
-        observer.waitForNotificationCount(1);
-        assertThat(observer.getSchemaChanges()).containsExactly(
-                new SchemaChangeInfo(
-                        mContext.getPackageName(),
-                        DB_NAME_1,
-                        ImmutableSet.of("Type1")));
-        assertThat(observer.getDocumentChanges()).isEmpty();
+            // Add two more schema types without touching the existing one
+            observer.clear();
+            mDb1.setSchemaAsync(
+                            new SetSchemaRequest.Builder()
+                                    .addSchemas(
+                                            new AppSearchSchema.Builder("Type1").build(),
+                                            new AppSearchSchema.Builder("Type2").build(),
+                                            new AppSearchSchema.Builder("Type3").build())
+                                    .build())
+                    .get();
 
-        // Add two more schema types without touching the existing one
-        observer.clear();
-        mDb1.setSchemaAsync(
-                        new SetSchemaRequest.Builder()
-                                .addSchemas(
-                                        new AppSearchSchema.Builder("Type1").build(),
-                                        new AppSearchSchema.Builder("Type2").build(),
-                                        new AppSearchSchema.Builder("Type3").build())
-                                .build())
-                .get();
-
-        observer.waitForNotificationCount(1);
-        assertThat(observer.getSchemaChanges()).containsExactly(
-                new SchemaChangeInfo(
-                        mContext.getPackageName(), DB_NAME_1, ImmutableSet.of("Type2", "Type3")));
-        assertThat(observer.getDocumentChanges()).isEmpty();
+            observer.waitForNotificationCount(1);
+            assertThat(observer.getSchemaChanges()).containsExactly(
+                    new SchemaChangeInfo(
+                            mContext.getPackageName(), DB_NAME_1,
+                            ImmutableSet.of("Type2", "Type3")));
+            assertThat(observer.getDocumentChanges()).isEmpty();
+        } finally {
+            // Clean the observer
+            mGlobalSearchSession.unregisterObserverCallback(mContext.getPackageName(), observer);
+        }
     }
 
     @Test
@@ -1517,19 +1558,24 @@
                 EXECUTOR,
                 observer);
 
-        // Remove Type2
-        mDb1.setSchemaAsync(
-                        new SetSchemaRequest.Builder()
-                                .addSchemas(new AppSearchSchema.Builder("Type1").build())
-                                .setForceOverride(true)
-                                .build())
-                .get();
+        try {
+            // Remove Type2
+            mDb1.setSchemaAsync(
+                            new SetSchemaRequest.Builder()
+                                    .addSchemas(new AppSearchSchema.Builder("Type1").build())
+                                    .setForceOverride(true)
+                                    .build())
+                    .get();
 
-        observer.waitForNotificationCount(1);
-        assertThat(observer.getSchemaChanges()).containsExactly(
-                new SchemaChangeInfo(
-                        mContext.getPackageName(), DB_NAME_1, ImmutableSet.of("Type2")));
-        assertThat(observer.getDocumentChanges()).isEmpty();
+            observer.waitForNotificationCount(1);
+            assertThat(observer.getSchemaChanges()).containsExactly(
+                    new SchemaChangeInfo(
+                            mContext.getPackageName(), DB_NAME_1, ImmutableSet.of("Type2")));
+            assertThat(observer.getDocumentChanges()).isEmpty();
+        } finally {
+            // Clean the observer
+            mGlobalSearchSession.unregisterObserverCallback(mContext.getPackageName(), observer);
+        }
     }
 
     @Test
@@ -1561,45 +1607,48 @@
                 EXECUTOR,
                 observer);
 
-        // Update the schema, but don't make any actual changes
-        mDb1.setSchemaAsync(
-            new SetSchemaRequest.Builder()
-                    .addSchemas(
-                            new AppSearchSchema.Builder("Type1").build(),
-                            new AppSearchSchema.Builder("Type2")
-                                    .addProperty(
-                                            new AppSearchSchema.BooleanPropertyConfig.Builder(
-                                                    "booleanProp")
+        try {
+            // Update the schema, but don't make any actual changes
+            mDb1.setSchemaAsync(
+                    new SetSchemaRequest.Builder()
+                            .addSchemas(
+                                    new AppSearchSchema.Builder("Type1").build(),
+                                    new AppSearchSchema.Builder("Type2")
+                                            .addProperty(new AppSearchSchema.BooleanPropertyConfig
+                                                    .Builder("booleanProp")
                                                     .setCardinality(
                                                             PropertyConfig.CARDINALITY_REQUIRED)
                                                     .build())
-                                    .build())
-                    .build())
-            .get();
+                                            .build())
+                            .build())
+                    .get();
 
-        // Now update the schema again, but this time actually make a change (cardinality of the
-        // property)
-        mDb1.setSchemaAsync(
-            new SetSchemaRequest.Builder()
-                    .addSchemas(
-                            new AppSearchSchema.Builder("Type1").build(),
-                            new AppSearchSchema.Builder("Type2")
-                                    .addProperty(
-                                            new AppSearchSchema.BooleanPropertyConfig.Builder(
-                                                    "booleanProp")
+            // Now update the schema again, but this time actually make a change (cardinality of the
+            // property)
+            mDb1.setSchemaAsync(
+                    new SetSchemaRequest.Builder()
+                            .addSchemas(
+                                    new AppSearchSchema.Builder("Type1").build(),
+                                    new AppSearchSchema.Builder("Type2")
+                                            .addProperty(new AppSearchSchema.BooleanPropertyConfig
+                                                    .Builder("booleanProp")
                                                     .setCardinality(
                                                             PropertyConfig.CARDINALITY_OPTIONAL)
                                                     .build())
-                                    .build())
-                    .build())
-            .get();
+                                            .build())
+                            .build())
+                    .get();
 
-        // Dispatch notifications
-        observer.waitForNotificationCount(1);
-        assertThat(observer.getSchemaChanges()).containsExactly(
-                new SchemaChangeInfo(
-                        mContext.getPackageName(), DB_NAME_1, ImmutableSet.of("Type2")));
-        assertThat(observer.getDocumentChanges()).isEmpty();
+            // Dispatch notifications
+            observer.waitForNotificationCount(1);
+            assertThat(observer.getSchemaChanges()).containsExactly(
+                    new SchemaChangeInfo(
+                            mContext.getPackageName(), DB_NAME_1, ImmutableSet.of("Type2")));
+            assertThat(observer.getDocumentChanges()).isEmpty();
+        } finally {
+            // Clean the observer
+            mGlobalSearchSession.unregisterObserverCallback(mContext.getPackageName(), observer);
+        }
     }
 
     @Test
@@ -1637,42 +1686,44 @@
                 new ObserverSpec.Builder().addFilterSchemas("Type2").build(),
                 EXECUTOR,
                 observer);
+        try {
+            // Update both types of the schema (changed cardinalities)
+            mDb1.setSchemaAsync(
+                    new SetSchemaRequest.Builder()
+                           .addSchemas(
+                                   new AppSearchSchema.Builder("Type1")
+                                           .addProperty(new AppSearchSchema.BooleanPropertyConfig
+                                                   .Builder("booleanProp")
+                                                   .setCardinality(
+                                                           PropertyConfig.CARDINALITY_OPTIONAL)
+                                                   .build())
+                                           .build(),
+                                   new AppSearchSchema.Builder("Type2")
+                                           .addProperty(
+                                                   new AppSearchSchema.BooleanPropertyConfig
+                                                           .Builder("booleanProp")
+                                                           .setCardinality(PropertyConfig
+                                                                   .CARDINALITY_OPTIONAL)
+                                                           .build())
+                                           .build())
+                           .build())
+                    .get();
 
-        // Update both types of the schema (changed cardinalities)
-        mDb1.setSchemaAsync(
-                new SetSchemaRequest.Builder()
-                        .addSchemas(
-                                new AppSearchSchema.Builder("Type1")
-                                        .addProperty(
-                                                new AppSearchSchema.BooleanPropertyConfig.Builder(
-                                                        "booleanProp")
-                                                        .setCardinality(
-                                                                PropertyConfig.CARDINALITY_OPTIONAL)
-                                                        .build())
-                                        .build(),
-                                new AppSearchSchema.Builder("Type2")
-                                        .addProperty(
-                                                new AppSearchSchema.BooleanPropertyConfig.Builder(
-                                                        "booleanProp")
-                                                        .setCardinality(
-                                                                PropertyConfig.CARDINALITY_OPTIONAL)
-                                                        .build())
-                                        .build())
-                        .build())
-                .get();
-
-        observer.waitForNotificationCount(1);
-        assertThat(observer.getSchemaChanges()).containsExactly(
-                new SchemaChangeInfo(
-                        mContext.getPackageName(), DB_NAME_1, ImmutableSet.of("Type2")));
-        assertThat(observer.getDocumentChanges()).isEmpty();
+            observer.waitForNotificationCount(1);
+            assertThat(observer.getSchemaChanges()).containsExactly(
+                   new SchemaChangeInfo(
+                           mContext.getPackageName(), DB_NAME_1, ImmutableSet.of("Type2")));
+            assertThat(observer.getDocumentChanges()).isEmpty();
+        } finally {
+            // Clean the observer
+            mGlobalSearchSession.unregisterObserverCallback(mContext.getPackageName(), observer);
+        }
     }
 
     @Test
     public void testRegisterObserver_schemaMigration() throws Exception {
         assumeTrue(mDb1.getFeatures().isFeatureSupported(
                 Features.GLOBAL_SEARCH_SESSION_REGISTER_OBSERVER_CALLBACK));
-
         // Add a schema with two types
         mDb1.setSchemaAsync(new SetSchemaRequest.Builder()
                 .setVersion(1)
@@ -1720,120 +1771,127 @@
                 EXECUTOR,
                 observer);
 
-        // Update both types of the schema with migration to a new property name
-        mDb1.setSchemaAsync(new SetSchemaRequest.Builder()
-                .setVersion(2)
-                .addSchemas(
-                        new AppSearchSchema.Builder("Type1")
-                                .addProperty(
-                                        new AppSearchSchema.StringPropertyConfig.Builder("strProp2")
-                                                .build()
-                                ).build(),
-                        new AppSearchSchema.Builder("Type2")
-                                .addProperty(
-                                        new AppSearchSchema.LongPropertyConfig.Builder("longProp2")
-                                                .build()
-                                ).build()
-                        )
-                .setMigrator("Type1", new Migrator() {
-                    @Override
-                    public boolean shouldMigrate(int currentVersion, int finalVersion) {
-                        assertThat(currentVersion).isEqualTo(1);
-                        assertThat(finalVersion).isEqualTo(2);
-                        return true;
-                    }
+        try {
+            // Update both types of the schema with migration to a new property name
+            mDb1.setSchemaAsync(new SetSchemaRequest.Builder()
+                    .setVersion(2)
+                    .addSchemas(
+                            new AppSearchSchema.Builder("Type1")
+                                    .addProperty(
+                                            new AppSearchSchema.StringPropertyConfig.Builder(
+                                                    "strProp2")
+                                                    .build()
+                                    ).build(),
+                            new AppSearchSchema.Builder("Type2")
+                                    .addProperty(
+                                            new AppSearchSchema.LongPropertyConfig.Builder(
+                                                    "longProp2")
+                                                    .build()
+                                    ).build()
+                    )
+                    .setMigrator("Type1", new Migrator() {
+                        @Override
+                        public boolean shouldMigrate(int currentVersion, int finalVersion) {
+                            assertThat(currentVersion).isEqualTo(1);
+                            assertThat(finalVersion).isEqualTo(2);
+                            return true;
+                        }
 
-                    @NonNull
-                    @Override
-                    public GenericDocument onUpgrade(
-                            int currentVersion,
-                            int finalVersion,
-                            @NonNull GenericDocument document) {
-                        assertThat(currentVersion).isEqualTo(1);
-                        assertThat(finalVersion).isEqualTo(2);
-                        assertThat(document.getSchemaType()).isEqualTo("Type1");
-                        String[] prop = document.getPropertyStringArray("strProp1");
-                        assertThat(prop).isNotNull();
-                        return new GenericDocument.Builder<GenericDocument.Builder<?>>(
-                                document.getNamespace(),
-                                document.getId(),
-                                document.getSchemaType())
-                                .setPropertyString("strProp2", prop)
-                        .build();
-                    }
+                        @NonNull
+                        @Override
+                        public GenericDocument onUpgrade(
+                                int currentVersion,
+                                int finalVersion,
+                                @NonNull GenericDocument document) {
+                            assertThat(currentVersion).isEqualTo(1);
+                            assertThat(finalVersion).isEqualTo(2);
+                            assertThat(document.getSchemaType()).isEqualTo("Type1");
+                            String[] prop = document.getPropertyStringArray("strProp1");
+                            assertThat(prop).isNotNull();
+                            return new GenericDocument.Builder<GenericDocument.Builder<?>>(
+                                    document.getNamespace(),
+                                    document.getId(),
+                                    document.getSchemaType())
+                                    .setPropertyString("strProp2", prop)
+                                    .build();
+                        }
 
-                    @NonNull
-                    @Override
-                    public GenericDocument onDowngrade(
-                            int currentVersion,
-                            int finalVersion,
-                            @NonNull GenericDocument document) {
-                        // Doesn't happen in this test
-                        throw new UnsupportedOperationException();
-                    }
-                }).setMigrator("Type2", new Migrator() {
-                    @Override
-                    public boolean shouldMigrate(int currentVersion, int finalVersion) {
-                        assertThat(currentVersion).isEqualTo(1);
-                        assertThat(finalVersion).isEqualTo(2);
-                        return true;
-                    }
+                        @NonNull
+                        @Override
+                        public GenericDocument onDowngrade(
+                                int currentVersion,
+                                int finalVersion,
+                                @NonNull GenericDocument document) {
+                            // Doesn't happen in this test
+                            throw new UnsupportedOperationException();
+                        }
+                    }).setMigrator("Type2", new Migrator() {
+                        @Override
+                        public boolean shouldMigrate(int currentVersion, int finalVersion) {
+                            assertThat(currentVersion).isEqualTo(1);
+                            assertThat(finalVersion).isEqualTo(2);
+                            return true;
+                        }
 
-                    @NonNull
-                    @Override
-                    public GenericDocument onUpgrade(
-                            int currentVersion,
-                            int finalVersion,
-                            @NonNull GenericDocument document) {
-                        assertThat(currentVersion).isEqualTo(1);
-                        assertThat(finalVersion).isEqualTo(2);
-                        assertThat(document.getSchemaType()).isEqualTo("Type2");
-                        long[] prop = document.getPropertyLongArray("longProp1");
-                        assertThat(prop).isNotNull();
-                        return new GenericDocument.Builder<GenericDocument.Builder<?>>(
-                                document.getNamespace(),
-                                document.getId(),
-                                document.getSchemaType())
-                                .setPropertyLong("longProp2", prop[0] + 1000)
-                        .build();
-                    }
+                        @NonNull
+                        @Override
+                        public GenericDocument onUpgrade(
+                                int currentVersion,
+                                int finalVersion,
+                                @NonNull GenericDocument document) {
+                            assertThat(currentVersion).isEqualTo(1);
+                            assertThat(finalVersion).isEqualTo(2);
+                            assertThat(document.getSchemaType()).isEqualTo("Type2");
+                            long[] prop = document.getPropertyLongArray("longProp1");
+                            assertThat(prop).isNotNull();
+                            return new GenericDocument.Builder<GenericDocument.Builder<?>>(
+                                    document.getNamespace(),
+                                    document.getId(),
+                                    document.getSchemaType())
+                                    .setPropertyLong("longProp2", prop[0] + 1000)
+                                    .build();
+                        }
 
-                    @NonNull
-                    @Override
-                    public GenericDocument onDowngrade(
-                            int currentVersion,
-                            int finalVersion,
-                            @NonNull GenericDocument document) {
-                        // Doesn't happen in this test
-                        throw new UnsupportedOperationException();
-                    }
-                })
-                .build()
-        ).get();
+                        @NonNull
+                        @Override
+                        public GenericDocument onDowngrade(
+                                int currentVersion,
+                                int finalVersion,
+                                @NonNull GenericDocument document) {
+                            // Doesn't happen in this test
+                            throw new UnsupportedOperationException();
+                        }
+                    })
+                    .build()
+            ).get();
 
-        // Make sure the test is valid by checking that migration actually occurred
-        AppSearchBatchResult<String, GenericDocument> getResponse = mDb1.getByDocumentIdAsync(
-                new GetByDocumentIdRequest.Builder("namespace")
-                        .addIds("t1id1", "t1id2", "t2id1", "t2id2")
-                        .build())
-                .get();
-        assertThat(getResponse.isSuccess()).isTrue();
-        assertThat(getResponse.getSuccesses().get("t1id1").getPropertyString("strProp2"))
-                .isEqualTo("t1id1 prop value");
-        assertThat(getResponse.getSuccesses().get("t1id2").getPropertyString("strProp2"))
-                .isEqualTo("t1id2 prop value");
-        assertThat(getResponse.getSuccesses().get("t2id1").getPropertyLong("longProp2"))
-                .isEqualTo(1041);
-        assertThat(getResponse.getSuccesses().get("t2id2").getPropertyLong("longProp2"))
-                .isEqualTo(1042);
+            // Make sure the test is valid by checking that migration actually occurred
+            AppSearchBatchResult<String, GenericDocument> getResponse = mDb1.getByDocumentIdAsync(
+                            new GetByDocumentIdRequest.Builder("namespace")
+                                    .addIds("t1id1", "t1id2", "t2id1", "t2id2")
+                                    .build())
+                    .get();
+            assertThat(getResponse.isSuccess()).isTrue();
+            assertThat(getResponse.getSuccesses().get("t1id1").getPropertyString("strProp2"))
+                    .isEqualTo("t1id1 prop value");
+            assertThat(getResponse.getSuccesses().get("t1id2").getPropertyString("strProp2"))
+                    .isEqualTo("t1id2 prop value");
+            assertThat(getResponse.getSuccesses().get("t2id1").getPropertyLong("longProp2"))
+                    .isEqualTo(1041);
+            assertThat(getResponse.getSuccesses().get("t2id2").getPropertyLong("longProp2"))
+                    .isEqualTo(1042);
 
-        // Per the observer documentation, for schema migrations, individual document changes are
-        // not dispatched. Only SchemaChangeInfo is dispatched.
-        observer.waitForNotificationCount(1);
-        assertThat(observer.getSchemaChanges()).containsExactly(
-                new SchemaChangeInfo(
-                        mContext.getPackageName(), DB_NAME_1, ImmutableSet.of("Type1")));
-        assertThat(observer.getDocumentChanges()).isEmpty();
+            // Per the observer documentation, for schema migrations, individual document changes
+            // are not dispatched. Only SchemaChangeInfo is dispatched.
+            observer.waitForNotificationCount(1);
+            assertThat(observer.getSchemaChanges()).containsExactly(
+                    new SchemaChangeInfo(
+                            mContext.getPackageName(), DB_NAME_1, ImmutableSet.of("Type1")));
+            assertThat(observer.getDocumentChanges()).isEmpty();
+        } finally {
+            // Clean the observer
+            mGlobalSearchSession.unregisterObserverCallback(mContext.getPackageName(), observer);
+        }
     }
 
     @Test
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
index b594507..57cddb5 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
@@ -143,9 +143,10 @@
         project.tasks.withType(Zip::class.java).configureEach { it.configureForHermeticBuild() }
         project.tasks.withType(Copy::class.java).configureEach { it.configureForHermeticBuild() }
 
+        val allHostTests = project.tasks.register("allHostTests").get()
         // copy host side test results to DIST
         project.tasks.withType(AbstractTestTask::class.java) { task ->
-            configureTestTask(project, task)
+            configureTestTask(project, task, allHostTests)
         }
         project.tasks.withType(Test::class.java) { task -> configureJvmTestTask(project, task) }
 
@@ -241,7 +242,12 @@
         }
     }
 
-    private fun configureTestTask(project: Project, task: AbstractTestTask) {
+    private fun configureTestTask(
+        project: Project,
+        task: AbstractTestTask,
+        anchorTask: Task,
+    ) {
+        anchorTask.dependsOn(task)
         val ignoreFailuresProperty =
             project.providers.gradleProperty(TEST_FAILURES_DO_NOT_FAIL_TEST_TASK)
         val ignoreFailures = ignoreFailuresProperty.isPresent
diff --git a/busytown/androidx_host_tests.sh b/busytown/androidx_host_tests.sh
index 3555073..4c0b17b 100755
--- a/busytown/androidx_host_tests.sh
+++ b/busytown/androidx_host_tests.sh
@@ -5,7 +5,7 @@
 
 cd "$(dirname $0)"
 
-impl/build.sh test zipOwnersFiles createModuleInfo \
+impl/build.sh test allHostTests zipOwnersFiles createModuleInfo \
     -Pandroidx.ignoreTestFailures \
     -Pandroidx.displayTestOutput=false \
     "$@"
diff --git a/busytown/androidx_host_tests_max_dep_versions.sh b/busytown/androidx_host_tests_max_dep_versions.sh
index 5dbf2ee..c979e17 100755
--- a/busytown/androidx_host_tests_max_dep_versions.sh
+++ b/busytown/androidx_host_tests_max_dep_versions.sh
@@ -5,7 +5,7 @@
 
 cd "$(dirname $0)"
 
-impl/build.sh test zipOwnersFiles createModuleInfo \
+impl/build.sh test allHostTests zipOwnersFiles createModuleInfo \
     -Pandroidx.useMaxDepVersions \
     -Pandroidx.displayTestOutput=false \
     -Pandroidx.ignoreTestFailures "$@"
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index 692218f..37809e9 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -974,8 +974,23 @@
     property public final long trailingIconColor;
   }
 
+  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Immutable public final class ModalBottomSheetDefaults {
+    method public androidx.compose.material3.ModalBottomSheetProperties properties(optional androidx.compose.ui.window.SecureFlagPolicy securePolicy, optional boolean isFocusable, optional boolean shouldDismissOnBackPress);
+    field public static final androidx.compose.material3.ModalBottomSheetDefaults INSTANCE;
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class ModalBottomSheetProperties {
+    ctor public ModalBottomSheetProperties(androidx.compose.ui.window.SecureFlagPolicy securePolicy, boolean isFocusable, boolean shouldDismissOnBackPress);
+    method public androidx.compose.ui.window.SecureFlagPolicy getSecurePolicy();
+    method public boolean getShouldDismissOnBackPress();
+    method public boolean isFocusable();
+    property public final boolean isFocusable;
+    property public final androidx.compose.ui.window.SecureFlagPolicy securePolicy;
+    property public final boolean shouldDismissOnBackPress;
+  }
+
   public final class ModalBottomSheet_androidKt {
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void ModalBottomSheet(kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SheetState sheetState, optional float sheetMaxWidth, optional androidx.compose.ui.graphics.Shape shape, optional long containerColor, optional long contentColor, optional float tonalElevation, optional long scrimColor, optional kotlin.jvm.functions.Function0<kotlin.Unit>? dragHandle, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.ui.window.SecureFlagPolicy securePolicy, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void ModalBottomSheet(kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SheetState sheetState, optional float sheetMaxWidth, optional androidx.compose.ui.graphics.Shape shape, optional long containerColor, optional long contentColor, optional float tonalElevation, optional long scrimColor, optional kotlin.jvm.functions.Function0<kotlin.Unit>? dragHandle, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.ModalBottomSheetProperties properties, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.SheetState rememberModalBottomSheetState(optional boolean skipPartiallyExpanded, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange);
   }
 
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index 692218f..37809e9 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -974,8 +974,23 @@
     property public final long trailingIconColor;
   }
 
+  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Immutable public final class ModalBottomSheetDefaults {
+    method public androidx.compose.material3.ModalBottomSheetProperties properties(optional androidx.compose.ui.window.SecureFlagPolicy securePolicy, optional boolean isFocusable, optional boolean shouldDismissOnBackPress);
+    field public static final androidx.compose.material3.ModalBottomSheetDefaults INSTANCE;
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public final class ModalBottomSheetProperties {
+    ctor public ModalBottomSheetProperties(androidx.compose.ui.window.SecureFlagPolicy securePolicy, boolean isFocusable, boolean shouldDismissOnBackPress);
+    method public androidx.compose.ui.window.SecureFlagPolicy getSecurePolicy();
+    method public boolean getShouldDismissOnBackPress();
+    method public boolean isFocusable();
+    property public final boolean isFocusable;
+    property public final androidx.compose.ui.window.SecureFlagPolicy securePolicy;
+    property public final boolean shouldDismissOnBackPress;
+  }
+
   public final class ModalBottomSheet_androidKt {
-    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void ModalBottomSheet(kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SheetState sheetState, optional float sheetMaxWidth, optional androidx.compose.ui.graphics.Shape shape, optional long containerColor, optional long contentColor, optional float tonalElevation, optional long scrimColor, optional kotlin.jvm.functions.Function0<kotlin.Unit>? dragHandle, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.ui.window.SecureFlagPolicy securePolicy, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void ModalBottomSheet(kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SheetState sheetState, optional float sheetMaxWidth, optional androidx.compose.ui.graphics.Shape shape, optional long containerColor, optional long contentColor, optional float tonalElevation, optional long scrimColor, optional kotlin.jvm.functions.Function0<kotlin.Unit>? dragHandle, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.ModalBottomSheetProperties properties, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.SheetState rememberModalBottomSheetState(optional boolean skipPartiallyExpanded, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange);
   }
 
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt
index 2c6213d..b130fd2 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt
@@ -1336,7 +1336,8 @@
             ModalBottomSheet(
                 sheetState = sheetState,
                 onDismissRequest = {},
-                windowInsets = windowInsets
+                windowInsets = windowInsets,
+                properties = ModalBottomSheetDefaults.properties(isFocusable = true)
             ) {
                 Box(Modifier.testTag(sheetTag)) {
                     TextField(
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ModalBottomSheet.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ModalBottomSheet.android.kt
index 10c74d8..6ff1fcfc 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ModalBottomSheet.android.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ModalBottomSheet.android.kt
@@ -48,6 +48,7 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionContext
 import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.getValue
@@ -124,8 +125,8 @@
  * @param dragHandle Optional visual marker to swipe the bottom sheet.
  * @param windowInsets window insets to be passed to the bottom sheet window via [PaddingValues]
  * params.
- * @param securePolicy Policy for setting [WindowManager.LayoutParams.FLAG_SECURE] on the bottom
- * sheet's window.
+ * @param properties [ModalBottomSheetProperties] for further customization of this
+ * modal bottom sheet's behavior.
  * @param content The content to be displayed inside the bottom sheet.
  */
 @Composable
@@ -142,7 +143,7 @@
     scrimColor: Color = BottomSheetDefaults.ScrimColor,
     dragHandle: @Composable (() -> Unit)? = { BottomSheetDefaults.DragHandle() },
     windowInsets: WindowInsets = BottomSheetDefaults.windowInsets,
-    securePolicy: SecureFlagPolicy = SecureFlagPolicy.Inherit,
+    properties: ModalBottomSheetProperties = ModalBottomSheetDefaults.properties(),
     content: @Composable ColumnScope.() -> Unit,
 ) {
     // b/291735717 Remove this once deprecated methods without density are removed
@@ -167,7 +168,7 @@
     }
 
     ModalBottomSheetPopup(
-        securePolicy = securePolicy,
+        properties = properties,
         onDismissRequest = {
             if (sheetState.currentValue == Expanded && sheetState.hasPartiallyExpandedState) {
                 scope.launch { sheetState.partialExpand() }
@@ -281,6 +282,70 @@
 }
 
 /**
+ * Properties used to customize the behavior of a [ModalBottomSheet].
+ *
+ * @param securePolicy Policy for setting [WindowManager.LayoutParams.FLAG_SECURE] on the bottom
+ * sheet's window.
+ * @param isFocusable Whether the modal bottom sheet is focusable. When true,
+ * the modal bottom sheet will receive IME events and key presses, such as when
+ * the back button is pressed.
+ * @param shouldDismissOnBackPress Whether the modal bottom sheet can be dismissed by pressing
+ * the back button. If true, pressing the back button will call onDismissRequest.
+ * Note that [isFocusable] must be set to true in order to receive key events such as
+ * the back button - if the modal bottom sheet is not focusable then this property does nothing.
+ */
+@ExperimentalMaterial3Api
+class ModalBottomSheetProperties(
+    val securePolicy: SecureFlagPolicy,
+    val isFocusable: Boolean,
+    val shouldDismissOnBackPress: Boolean
+) {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is ModalBottomSheetProperties) return false
+
+        if (securePolicy != other.securePolicy) return false
+        if (isFocusable != other.isFocusable) return false
+        if (shouldDismissOnBackPress != other.shouldDismissOnBackPress) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = securePolicy.hashCode()
+        result = 31 * result + isFocusable.hashCode()
+        result = 31 * result + shouldDismissOnBackPress.hashCode()
+        return result
+    }
+}
+
+/**
+ * Default values for [ModalBottomSheet]
+ */
+@Immutable
+@ExperimentalMaterial3Api
+object ModalBottomSheetDefaults {
+    /**
+     * Properties used to customize the behavior of a [ModalBottomSheet].
+     *
+     * @param securePolicy Policy for setting [WindowManager.LayoutParams.FLAG_SECURE] on the bottom
+     * sheet's window.
+     * @param isFocusable Whether the modal bottom sheet is focusable. When true,
+     * the modal bottom sheet will receive IME events and key presses, such as when
+     * the back button is pressed.
+     * @param shouldDismissOnBackPress Whether the modal bottom sheet can be dismissed by pressing
+     * the back button. If true, pressing the back button will call onDismissRequest.
+     * Note that [isFocusable] must be set to true in order to receive key events such as
+     * the back button - if the modal bottom sheet is not focusable then this property does nothing.
+     */
+    fun properties(
+        securePolicy: SecureFlagPolicy = SecureFlagPolicy.Inherit,
+        isFocusable: Boolean = true,
+        shouldDismissOnBackPress: Boolean = true
+    ) = ModalBottomSheetProperties(securePolicy, isFocusable, shouldDismissOnBackPress)
+}
+
+/**
  * Create and [remember] a [SheetState] for [ModalBottomSheet].
  *
  * @param skipPartiallyExpanded Whether the partially expanded state, if the sheet is tall enough,
@@ -359,9 +424,10 @@
 /**
  * Popup specific for modal bottom sheet.
  */
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 internal fun ModalBottomSheetPopup(
-    securePolicy: SecureFlagPolicy,
+    properties: ModalBottomSheetProperties,
     onDismissRequest: () -> Unit,
     windowInsets: WindowInsets,
     content: @Composable () -> Unit,
@@ -374,7 +440,7 @@
     val configuration = LocalConfiguration.current
     val modalBottomSheetWindow = remember(configuration) {
         ModalBottomSheetWindow(
-            securePolicy = securePolicy,
+            properties = properties,
             onDismissRequest = onDismissRequest,
             composeView = view,
             saveId = id
@@ -411,11 +477,12 @@
 }
 
 /** Custom compose view for [ModalBottomSheet] */
+@OptIn(ExperimentalMaterial3Api::class)
 private class ModalBottomSheetWindow(
-    private val securePolicy: SecureFlagPolicy,
+    private val properties: ModalBottomSheetProperties,
     private var onDismissRequest: () -> Unit,
     private val composeView: View,
-    saveId: UUID,
+    saveId: UUID
 ) :
     AbstractComposeView(composeView.context),
     ViewTreeObserver.OnGlobalLayoutListener,
@@ -467,12 +534,19 @@
 
             // Security flag
             val secureFlagEnabled =
-                securePolicy.shouldApplySecureFlag(composeView.isFlagSecureEnabled())
+                properties.securePolicy.shouldApplySecureFlag(composeView.isFlagSecureEnabled())
             if (secureFlagEnabled) {
                 flags = flags or WindowManager.LayoutParams.FLAG_SECURE
             } else {
                 flags = flags and (WindowManager.LayoutParams.FLAG_SECURE.inv())
             }
+
+            // Focusable
+            if (!properties.isFocusable) {
+                flags = flags or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+            } else {
+                flags = flags and (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE.inv())
+            }
         }
 
     private var content: @Composable () -> Unit by mutableStateOf({})
@@ -509,7 +583,7 @@
      * Taken from PopupWindow. Calls [onDismissRequest] when back button is pressed.
      */
     override fun dispatchKeyEvent(event: KeyEvent): Boolean {
-        if (event.keyCode == KeyEvent.KEYCODE_BACK) {
+        if (event.keyCode == KeyEvent.KEYCODE_BACK && properties.shouldDismissOnBackPress) {
             if (keyDispatcherState == null) {
                 return super.dispatchKeyEvent(event)
             }
diff --git a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/DesktopTextInputSessionTest.kt b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/DesktopTextInputSessionTest.kt
index f0d005f..a9a9158 100644
--- a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/DesktopTextInputSessionTest.kt
+++ b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/DesktopTextInputSessionTest.kt
@@ -31,6 +31,7 @@
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.test.advanceUntilIdle
 import kotlinx.coroutines.test.runTest
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
@@ -39,6 +40,7 @@
 @RunWith(JUnit4::class)
 class DesktopTextInputSessionTest {
 
+    @Ignore // b/308619798
     @Test
     fun startInputMethod_setsAndClearsRequestsAndListeners() = runTest {
         val inputComponent = TestInputComponent()
diff --git a/glance/glance-appwidget/api/current.txt b/glance/glance-appwidget/api/current.txt
index 23a57ea..f3d101b 100644
--- a/glance/glance-appwidget/api/current.txt
+++ b/glance/glance-appwidget/api/current.txt
@@ -12,7 +12,7 @@
 
   public final class AppWidgetComposerKt {
     method public static suspend Object? compose(androidx.glance.appwidget.GlanceAppWidget, android.content.Context context, optional androidx.glance.GlanceId id, optional android.os.Bundle? options, optional androidx.compose.ui.unit.DpSize? size, optional Object? state, kotlin.coroutines.Continuation<? super android.widget.RemoteViews>);
-    method @SuppressCompatibility @androidx.glance.ExperimentalGlanceApi public static kotlinx.coroutines.flow.Flow<android.widget.RemoteViews> runComposition(androidx.glance.appwidget.GlanceAppWidget, android.content.Context context, optional androidx.glance.GlanceId id, optional android.os.Bundle options, optional java.util.List<androidx.compose.ui.unit.DpSize> sizes, optional Object? state);
+    method @SuppressCompatibility @androidx.glance.ExperimentalGlanceApi public static kotlinx.coroutines.flow.Flow<android.widget.RemoteViews> runComposition(androidx.glance.appwidget.GlanceAppWidget, android.content.Context context, optional androidx.glance.GlanceId id, optional android.os.Bundle options, optional java.util.List<androidx.compose.ui.unit.DpSize>? sizes, optional Object? state);
   }
 
   public final class BackgroundKt {
diff --git a/glance/glance-appwidget/api/restricted_current.txt b/glance/glance-appwidget/api/restricted_current.txt
index 23a57ea..f3d101b 100644
--- a/glance/glance-appwidget/api/restricted_current.txt
+++ b/glance/glance-appwidget/api/restricted_current.txt
@@ -12,7 +12,7 @@
 
   public final class AppWidgetComposerKt {
     method public static suspend Object? compose(androidx.glance.appwidget.GlanceAppWidget, android.content.Context context, optional androidx.glance.GlanceId id, optional android.os.Bundle? options, optional androidx.compose.ui.unit.DpSize? size, optional Object? state, kotlin.coroutines.Continuation<? super android.widget.RemoteViews>);
-    method @SuppressCompatibility @androidx.glance.ExperimentalGlanceApi public static kotlinx.coroutines.flow.Flow<android.widget.RemoteViews> runComposition(androidx.glance.appwidget.GlanceAppWidget, android.content.Context context, optional androidx.glance.GlanceId id, optional android.os.Bundle options, optional java.util.List<androidx.compose.ui.unit.DpSize> sizes, optional Object? state);
+    method @SuppressCompatibility @androidx.glance.ExperimentalGlanceApi public static kotlinx.coroutines.flow.Flow<android.widget.RemoteViews> runComposition(androidx.glance.appwidget.GlanceAppWidget, android.content.Context context, optional androidx.glance.GlanceId id, optional android.os.Bundle options, optional java.util.List<androidx.compose.ui.unit.DpSize>? sizes, optional Object? state);
   }
 
   public final class BackgroundKt {
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/AppWidgetComposer.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/AppWidgetComposer.kt
index 94a8428..c751aa0 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/AppWidgetComposer.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/AppWidgetComposer.kt
@@ -54,7 +54,7 @@
         context = context,
         id = id,
         options = options ?: Bundle(),
-        sizes = listOf(size ?: DpSize.Zero),
+        sizes = size?.let { listOf(size) },
         state = state
     ).first()
 
@@ -70,24 +70,35 @@
  * flow is running. This currently does not support resizing (you have to run the flow again with
  * new [sizes]) or reloading the [androidx.glance.state.GlanceStateDefinition] state value.
  */
+@SuppressLint("PrimitiveInCollection")
 @ExperimentalGlanceApi
 fun GlanceAppWidget.runComposition(
     @Suppress("ContextFirst") context: Context,
     id: GlanceId = createFakeAppWidgetId(),
     options: Bundle = Bundle(),
-    @SuppressLint("PrimitiveInCollection") sizes: List<DpSize> = listOf(DpSize.Zero),
+    sizes: List<DpSize>? = null,
     state: Any? = null,
 ): Flow<RemoteViews> = flow {
     val session = AppWidgetSession(
         widget = this@runComposition,
         id = id as AppWidgetId,
-        initialOptions = optionsBundleOf(sizes).apply { putAll(options) },
+        initialOptions = sizes?.let { optionsBundleOf(it).apply { putAll(options) } } ?: options,
         initialGlanceState = state,
         lambdaReceiver = ComponentName(context, UnmanagedSessionReceiver::class.java),
-        // If not composing for a bound widget, override to SizeMode.Exact so we can use the sizes
-        // provided to this function (by setting app widget options).
-        sizeMode =
-            if (id.isFakeId && sizeMode !is SizeMode.Responsive) SizeMode.Exact else sizeMode,
+        sizeMode = if (sizes != null) {
+            // If sizes are provided to this function, override to SizeMode.Exact so we can use them.
+            SizeMode.Exact
+        } else if (sizeMode is SizeMode.Responsive || id.isRealId) {
+            // If sizes are not provided and the widget is SizeMode.Responsive, use those sizes.
+            // Else if sizes are not provided but this is a bound widget, use the widget's sizeMode
+            // (Single or Exact).
+            sizeMode
+        } else {
+            // When no sizes are provided, the widget is not SizeMode.Responsive, and we are not
+            // composing for a bound widget, use SizeMode.Exact (which means AppWidgetSession will
+            // use DpSize.Zero).
+            SizeMode.Exact
+        },
         shouldPublish = false,
     )
     coroutineScope {
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/AppWidgetSession.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/AppWidgetSession.kt
index bc2e857..022de6d 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/AppWidgetSession.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/AppWidgetSession.kt
@@ -97,7 +97,7 @@
     }
 
     private var glanceState by mutableStateOf(initialGlanceState, neverEqualPolicy())
-    private var options by mutableStateOf(initialOptions ?: Bundle.EMPTY, neverEqualPolicy())
+    private var options by mutableStateOf(initialOptions, neverEqualPolicy())
     private var lambdas = mapOf<String, List<LambdaAction>>()
 
     internal val lastRemoteViews = MutableStateFlow<RemoteViews?>(null)
@@ -108,7 +108,7 @@
         CompositionLocalProvider(
             LocalContext provides context,
             LocalGlanceId provides id,
-            LocalAppWidgetOptions provides options,
+            LocalAppWidgetOptions provides (options ?: Bundle.EMPTY),
             LocalState provides glanceState,
         ) {
             var minSize by remember { mutableStateOf(DpSize.Zero) }
@@ -123,12 +123,17 @@
                             manager,
                             id.appWidgetId
                         )
-                        options = manager.getAppWidgetOptions(id.appWidgetId)
+                        if (options == null) {
+                            options = manager.getAppWidgetOptions(id.appWidgetId)
+                        }
                     }
-                    widget.stateDefinition?.let {
-                        glanceState =
-                            configManager.getValue(context, it, key)
-                    }
+                    // Only get a Glance state value if we did not receive an initial value.
+                    widget.stateDefinition
+                        ?.takeIf { glanceState == null }
+                        ?.let { stateDefinition ->
+                            glanceState =
+                                configManager.getValue(context, stateDefinition, key)
+                        }
                     value = true
                 }
             }
diff --git a/playground-common/playground.properties b/playground-common/playground.properties
index b314955..67db5c3 100644
--- a/playground-common/playground.properties
+++ b/playground-common/playground.properties
@@ -26,5 +26,5 @@
 # Disable docs
 androidx.enableDocumentation=false
 androidx.playground.snapshotBuildId=10956675
-androidx.playground.metalavaBuildId=10969629
+androidx.playground.metalavaBuildId=11029641
 androidx.studio.type=playground
\ No newline at end of file
diff --git a/room/room-runtime/api/api_lint.ignore b/room/room-runtime/api/api_lint.ignore
index 9c5918f..bb9e047 100644
--- a/room/room-runtime/api/api_lint.ignore
+++ b/room/room-runtime/api/api_lint.ignore
@@ -62,9 +62,6 @@
 PairedRegistration: androidx.room.RoomDatabase.Builder#addCallback(androidx.room.RoomDatabase.Callback):
     Found addCallback but not removeCallback in androidx.room.RoomDatabase.Builder
 
-RegistrationName: androidx.room.RoomDatabase.Builder#addCallback(androidx.room.RoomDatabase.Callback):
-    Callback methods should be named register/unregister; was addCallback
-
 
 StaticFinalBuilder: androidx.room.RoomDatabase.Builder:
     Builder must be final: androidx.room.RoomDatabase.Builder