Correctly handle change notifications during schema migrations.

Our documentation promises that only SchemaChangeInfo will be dispatched
during schema migrations, but the implementation was that individual
DocumentChangeInfos were being dispatched for every migrated document,
contrary to our documentation.

Bug: 193494000
Test: GlobalSearchSessionCtsTest#testRegisterObserver_schemaMigration
Change-Id: I73e60876c12e6240f9b7782f9c58fc9888aaef3a
diff --git a/framework/java/android/app/appsearch/aidl/IAppSearchManager.aidl b/framework/java/android/app/appsearch/aidl/IAppSearchManager.aidl
index cbf1282..0f9d7cc 100644
--- a/framework/java/android/app/appsearch/aidl/IAppSearchManager.aidl
+++ b/framework/java/android/app/appsearch/aidl/IAppSearchManager.aidl
@@ -255,6 +255,10 @@
     /**
     * Inserts documents from the given file into the index.
     *
+    * <p>This method does not dispatch change notifications for the individual documents being
+    * inserted, so it is only appropriate to use for batch upload situations where a broader change
+    * notification will indicate what has changed, like schema migration.
+    *
     * @param callerAttributionSource The permission identity of the package that owns this
     *     document.
     * @param databaseName  The name of the database where this document lives.
diff --git a/framework/java/external/android/app/appsearch/GetSchemaResponse.java b/framework/java/external/android/app/appsearch/GetSchemaResponse.java
index dbf9cfc..80a3a88 100644
--- a/framework/java/external/android/app/appsearch/GetSchemaResponse.java
+++ b/framework/java/external/android/app/appsearch/GetSchemaResponse.java
@@ -177,7 +177,6 @@
      *     SetSchemaRequest#READ_HOME_APP_SEARCH_DATA} and {@link
      *     SetSchemaRequest#READ_ASSISTANT_APP_SEARCH_DATA}.
      */
-    @SetSchemaRequest.AppSearchSupportedPermission
     @NonNull
     @SuppressWarnings("deprecation")
     public Map<String, Set<Set<Integer>>> getRequiredPermissionsForSchemaTypeVisibility() {
diff --git a/framework/java/external/android/app/appsearch/SetSchemaRequest.java b/framework/java/external/android/app/appsearch/SetSchemaRequest.java
index 4dd3a3c..983d262 100644
--- a/framework/java/external/android/app/appsearch/SetSchemaRequest.java
+++ b/framework/java/external/android/app/appsearch/SetSchemaRequest.java
@@ -224,7 +224,6 @@
      *     SetSchemaRequest#READ_HOME_APP_SEARCH_DATA} and {@link
      *     SetSchemaRequest#READ_ASSISTANT_APP_SEARCH_DATA}.
      */
-    @SetSchemaRequest.AppSearchSupportedPermission
     @NonNull
     public Map<String, Set<Set<Integer>>> getRequiredPermissionsForSchemaTypeVisibility() {
         return deepCopy(mSchemasVisibleToPermissions);
diff --git a/service/java/com/android/server/appsearch/AppSearchManagerService.java b/service/java/com/android/server/appsearch/AppSearchManagerService.java
index 658b8fc..e18d8e1 100644
--- a/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -479,6 +479,7 @@
                                     callerAttributionSource.getPackageName(),
                                     databaseName,
                                     document,
+                                    /*sendChangeNotifications=*/ true,
                                     instance.getLogger());
                             resultBuilder.setSuccess(document.getId(), /*value=*/ null);
                             ++operationSuccessCount;
@@ -921,10 +922,13 @@
                                 break;
                             }
                             try {
+                                // Per this method's documentation, individual document change
+                                // notifications are not dispatched.
                                 instance.getAppSearchImpl().putDocument(
                                         callerAttributionSource.getPackageName(),
                                         databaseName,
                                         document,
+                                        /*sendChangeNotifications=*/ false,
                                         /*logger=*/ null);
                             } catch (Throwable t) {
                                 migrationFailureBundles.add(new SetSchemaResponse.MigrationFailure(
diff --git a/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java b/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
index 0341b34..6ed6c55 100644
--- a/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
+++ b/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
@@ -557,6 +557,18 @@
                         version,
                         setSchemaStatsBuilder);
 
+        // This check is needed wherever setSchema is called to detect soft errors which do not
+        // throw an exception but also prevent the schema from actually being applied.
+        // TODO(b/229874420): Improve the usability of doSetSchemaNoChangeNotificationLocked() by
+        //  adding documentation that the return value must be checked for these conditions, and by
+        //  making it easier to check for these conditions via one of the ways documented in the
+        //  bug.
+        if (!forceOverride
+                && (!setSchemaResponse.getDeletedTypes().isEmpty()
+                        || !setSchemaResponse.getIncompatibleTypes().isEmpty())) {
+            return setSchemaResponse;
+        }
+
         // Cache some lookup tables to help us work with the new schema
         Map<String, AppSearchSchema> newSchemaNameToType = new ArrayMap<>(schemas.size());
         // Maps unprefixed schema name to the set of listening packages that have visibility into
@@ -903,12 +915,15 @@
      * @param packageName The package name that owns this document.
      * @param databaseName The databaseName this document resides in.
      * @param document The document to index.
+     * @param sendChangeNotifications Whether to dispatch {@link
+     *     android.app.appsearch.observer.DocumentChangeInfo} messages to observers for this change.
      * @throws AppSearchException on IcingSearchEngine error.
      */
     public void putDocument(
             @NonNull String packageName,
             @NonNull String databaseName,
             @NonNull GenericDocument document,
+            boolean sendChangeNotifications,
             @Nullable AppSearchLogger logger)
             throws AppSearchException {
         PutDocumentStats.Builder pStatsBuilder = null;
@@ -967,14 +982,16 @@
             checkSuccess(putResultProto.getStatus());
 
             // Prepare notifications
-            mObserverManager.onDocumentChange(
-                    packageName,
-                    databaseName,
-                    document.getNamespace(),
-                    document.getSchemaType(),
-                    document.getId(),
-                    mVisibilityStoreLocked,
-                    mVisibilityCheckerLocked);
+            if (sendChangeNotifications) {
+                mObserverManager.onDocumentChange(
+                        packageName,
+                        databaseName,
+                        document.getNamespace(),
+                        document.getSchemaType(),
+                        document.getId(),
+                        mVisibilityStoreLocked,
+                        mVisibilityCheckerLocked);
+            }
         } finally {
             mReadWriteLock.writeLock().unlock();
 
diff --git a/service/java/com/android/server/appsearch/external/localstorage/visibilitystore/VisibilityStore.java b/service/java/com/android/server/appsearch/external/localstorage/visibilitystore/VisibilityStore.java
index c7f4eaa..b8cd0ad 100644
--- a/service/java/com/android/server/appsearch/external/localstorage/visibilitystore/VisibilityStore.java
+++ b/service/java/com/android/server/appsearch/external/localstorage/visibilitystore/VisibilityStore.java
@@ -159,6 +159,7 @@
                     VISIBILITY_PACKAGE_NAME,
                     VISIBILITY_DATABASE_NAME,
                     prefixedVisibilityDocument,
+                    /*sendChangeNotifications=*/ false,
                     /*logger=*/ null);
             mVisibilityDocumentMap.put(
                     prefixedVisibilityDocument.getId(), prefixedVisibilityDocument);
@@ -265,6 +266,7 @@
                     VISIBILITY_PACKAGE_NAME,
                     VISIBILITY_DATABASE_NAME,
                     migratedDocument,
+                    /*sendChangeNotifications=*/ false,
                     /*logger=*/ null);
         }
     }
diff --git a/synced_jetpack_sha.txt b/synced_jetpack_sha.txt
index 6c6ee7e..91d5ca7 100644
--- a/synced_jetpack_sha.txt
+++ b/synced_jetpack_sha.txt
@@ -1 +1 @@
-bfb371af66ce470ec9b869751229fdf4ab29cca6
+e0300f00fec97c04adaa867fc2245d7baa66d43b
diff --git a/testing/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java b/testing/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
index 420cbbc..bc07a51 100644
--- a/testing/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
+++ b/testing/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
@@ -465,7 +465,12 @@
 
         // Insert a document and then remove it to generate garbage.
         GenericDocument document = new GenericDocument.Builder<>("namespace", "id", "type").build();
-        mAppSearchImpl.putDocument("package", "database", document, /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package",
+                "database",
+                document,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
         mAppSearchImpl.remove(
                 "package", "database", "namespace", "id", /*removeStatsBuilder=*/ null);
 
@@ -516,7 +521,11 @@
         GenericDocument validDoc =
                 new GenericDocument.Builder<>("namespace1", "id1", "Type1").build();
         mAppSearchImpl.putDocument(
-                mContext.getPackageName(), "database1", validDoc, /*logger=*/ null);
+                mContext.getPackageName(),
+                "database1",
+                validDoc,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
 
         // Query it via global query. We use the same code again later so this is to make sure we
         // have our global query configured right.
@@ -606,7 +615,11 @@
 
         // Insert a valid doc
         mAppSearchImpl.putDocument(
-                mContext.getPackageName(), "database1", validDoc, /*logger=*/ null);
+                mContext.getPackageName(),
+                "database1",
+                validDoc,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
 
         // Query it via global query.
         results =
@@ -661,7 +674,12 @@
         // Insert package1 document
         GenericDocument document =
                 new GenericDocument.Builder<>("namespace", "id", "schema1").build();
-        mAppSearchImpl.putDocument("package1", "database1", document, /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package1",
+                "database1",
+                document,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
 
         // No query filters specified, package2 shouldn't be able to query for package1's documents.
         SearchSpec searchSpec =
@@ -672,7 +690,12 @@
 
         // Insert package2 document
         document = new GenericDocument.Builder<>("namespace", "id", "schema2").build();
-        mAppSearchImpl.putDocument("package2", "database2", document, /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package2",
+                "database2",
+                document,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
 
         // No query filters specified. package2 should only get its own documents back.
         searchResultPage =
@@ -715,7 +738,12 @@
         // Insert package1 document
         GenericDocument document =
                 new GenericDocument.Builder<>("namespace", "id", "schema1").build();
-        mAppSearchImpl.putDocument("package1", "database1", document, /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package1",
+                "database1",
+                document,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
 
         // "package1" filter specified, but package2 shouldn't be able to query for package1's
         // documents.
@@ -730,7 +758,12 @@
 
         // Insert package2 document
         document = new GenericDocument.Builder<>("namespace", "id", "schema2").build();
-        mAppSearchImpl.putDocument("package2", "database2", document, /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package2",
+                "database2",
+                document,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
 
         // "package2" filter specified, package2 should only get its own documents back.
         searchSpec =
@@ -777,8 +810,18 @@
                 new GenericDocument.Builder<>("namespace", "id1", "schema1").build();
         GenericDocument document2 =
                 new GenericDocument.Builder<>("namespace", "id2", "schema1").build();
-        mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null);
-        mAppSearchImpl.putDocument("package1", "database1", document2, /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package1",
+                "database1",
+                document1,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package1",
+                "database1",
+                document2,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
 
         // Query for only 1 result per page
         SearchSpec searchSpec =
@@ -820,8 +863,18 @@
                 new GenericDocument.Builder<>("namespace", "id1", "schema1").build();
         GenericDocument document2 =
                 new GenericDocument.Builder<>("namespace", "id2", "schema1").build();
-        mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null);
-        mAppSearchImpl.putDocument("package1", "database1", document2, /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package1",
+                "database1",
+                document1,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package1",
+                "database1",
+                document2,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
 
         // Query for only 1 result per page
         SearchSpec searchSpec =
@@ -877,8 +930,18 @@
                 new GenericDocument.Builder<>("namespace", "id1", "schema1").build();
         GenericDocument document2 =
                 new GenericDocument.Builder<>("namespace", "id2", "schema1").build();
-        mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null);
-        mAppSearchImpl.putDocument("package1", "database1", document2, /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package1",
+                "database1",
+                document1,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package1",
+                "database1",
+                document2,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
 
         // Query for only 1 result per page
         SearchSpec searchSpec =
@@ -924,8 +987,18 @@
                 new GenericDocument.Builder<>("namespace", "id1", "schema1").build();
         GenericDocument document2 =
                 new GenericDocument.Builder<>("namespace", "id2", "schema1").build();
-        mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null);
-        mAppSearchImpl.putDocument("package1", "database1", document2, /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package1",
+                "database1",
+                document1,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package1",
+                "database1",
+                document2,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
 
         // Query for only 1 result per page
         SearchSpec searchSpec =
@@ -985,8 +1058,18 @@
                 new GenericDocument.Builder<>("namespace", "id1", "schema1").build();
         GenericDocument document2 =
                 new GenericDocument.Builder<>("namespace", "id2", "schema1").build();
-        mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null);
-        mAppSearchImpl.putDocument("package1", "database1", document2, /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package1",
+                "database1",
+                document1,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package1",
+                "database1",
+                document2,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
 
         // Query for only 1 result per page
         SearchSpec searchSpec =
@@ -1037,7 +1120,12 @@
         // Insert one package1 documents
         GenericDocument document1 =
                 new GenericDocument.Builder<>("namespace", "id1", "schema1").build();
-        mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package1",
+                "database1",
+                document1,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
 
         // Query for 2 results per page, so all the results can fit in one page.
         SearchSpec searchSpec =
@@ -1079,8 +1167,18 @@
                 new GenericDocument.Builder<>("namespace", "id1", "schema1").build();
         GenericDocument document2 =
                 new GenericDocument.Builder<>("namespace", "id2", "schema1").build();
-        mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null);
-        mAppSearchImpl.putDocument("package1", "database1", document2, /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package1",
+                "database1",
+                document1,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package1",
+                "database1",
+                document2,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
 
         // Query for only 1 result per page
         SearchSpec searchSpec =
@@ -1134,8 +1232,18 @@
                 new GenericDocument.Builder<>("namespace", "id1", "schema1").build();
         GenericDocument document2 =
                 new GenericDocument.Builder<>("namespace", "id2", "schema1").build();
-        mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null);
-        mAppSearchImpl.putDocument("package1", "database1", document2, /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package1",
+                "database1",
+                document1,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package1",
+                "database1",
+                document2,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
 
         // Query for only 1 result per page
         SearchSpec searchSpec =
@@ -1192,8 +1300,18 @@
                 new GenericDocument.Builder<>("namespace", "id1", "schema1").build();
         GenericDocument document2 =
                 new GenericDocument.Builder<>("namespace", "id2", "schema1").build();
-        mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null);
-        mAppSearchImpl.putDocument("package1", "database1", document2, /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package1",
+                "database1",
+                document1,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package1",
+                "database1",
+                document2,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
 
         // Query for only 1 result per page
         SearchSpec searchSpec =
@@ -1528,7 +1646,12 @@
         // Insert package document
         GenericDocument document =
                 new GenericDocument.Builder<>("namespace", "id", "schema").build();
-        mAppSearchImpl.putDocument("package", "database", document, /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package",
+                "database",
+                document,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
 
         // Verify the document is indexed.
         SearchSpec searchSpec =
@@ -1762,8 +1885,18 @@
                 new GenericDocument.Builder<>("namespace", "id1", "type").build();
         GenericDocument document2 =
                 new GenericDocument.Builder<>("namespace", "id2", "type").build();
-        mAppSearchImpl.putDocument("package", "database", document1, /*logger=*/ null);
-        mAppSearchImpl.putDocument("package", "database", document2, /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package",
+                "database",
+                document1,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package",
+                "database",
+                document2,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
 
         // Report some usages. id1 has 2 app and 1 system usage, id2 has 1 app and 2 system usage.
         mAppSearchImpl.reportUsage(
@@ -1932,7 +2065,12 @@
         // Insert document for "package1"
         GenericDocument document =
                 new GenericDocument.Builder<>("namespace", "id1", "type").build();
-        mAppSearchImpl.putDocument("package1", "database", document, /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package1",
+                "database",
+                document,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
 
         // Insert schema for "package2"
         mAppSearchImpl.setSchema(
@@ -1946,9 +2084,19 @@
 
         // Insert two documents for "package2"
         document = new GenericDocument.Builder<>("namespace", "id1", "type").build();
-        mAppSearchImpl.putDocument("package2", "database", document, /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package2",
+                "database",
+                document,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
         document = new GenericDocument.Builder<>("namespace", "id2", "type").build();
-        mAppSearchImpl.putDocument("package2", "database", document, /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package2",
+                "database",
+                document,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
 
         StorageInfo storageInfo = mAppSearchImpl.getStorageInfoForPackage("package1");
         long size1 = storageInfo.getSizeBytes();
@@ -2046,13 +2194,28 @@
         // Add a document for "package1", "database1"
         GenericDocument document =
                 new GenericDocument.Builder<>("namespace1", "id1", "type").build();
-        mAppSearchImpl.putDocument("package1", "database1", document, /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package1",
+                "database1",
+                document,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
 
         // Add two documents for "package1", "database2"
         document = new GenericDocument.Builder<>("namespace1", "id1", "type").build();
-        mAppSearchImpl.putDocument("package1", "database2", document, /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package1",
+                "database2",
+                document,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
         document = new GenericDocument.Builder<>("namespace1", "id2", "type").build();
-        mAppSearchImpl.putDocument("package1", "database2", document, /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package1",
+                "database2",
+                document,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
 
         StorageInfo storageInfo = mAppSearchImpl.getStorageInfoForDatabase("package1", "database1");
         long size1 = storageInfo.getSizeBytes();
@@ -2115,6 +2278,7 @@
                                 "package",
                                 "database",
                                 new GenericDocument.Builder<>("namespace", "id", "type").build(),
+                                /*sendChangeNotifications=*/ false,
                                 /*logger=*/ null));
 
         assertThrows(
@@ -2212,7 +2376,12 @@
         // Add a document and persist it.
         GenericDocument document =
                 new GenericDocument.Builder<>("namespace1", "id1", "type").build();
-        mAppSearchImpl.putDocument("package", "database", document, /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package",
+                "database",
+                document,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
         mAppSearchImpl.persistToDisk(PersistType.Code.LITE);
 
         GenericDocument getResult =
@@ -2251,10 +2420,20 @@
         // Add two documents and persist them.
         GenericDocument document1 =
                 new GenericDocument.Builder<>("namespace1", "id1", "type").build();
-        mAppSearchImpl.putDocument("package", "database", document1, /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package",
+                "database",
+                document1,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
         GenericDocument document2 =
                 new GenericDocument.Builder<>("namespace1", "id2", "type").build();
-        mAppSearchImpl.putDocument("package", "database", document2, /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package",
+                "database",
+                document2,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
         mAppSearchImpl.persistToDisk(PersistType.Code.LITE);
 
         GenericDocument getResult =
@@ -2323,10 +2502,20 @@
         // Add two documents and persist them.
         GenericDocument document1 =
                 new GenericDocument.Builder<>("namespace1", "id1", "type").build();
-        mAppSearchImpl.putDocument("package", "database", document1, /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package",
+                "database",
+                document1,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
         GenericDocument document2 =
                 new GenericDocument.Builder<>("namespace2", "id2", "type").build();
-        mAppSearchImpl.putDocument("package", "database", document2, /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package",
+                "database",
+                document2,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
         mAppSearchImpl.persistToDisk(PersistType.Code.LITE);
 
         GenericDocument getResult =
@@ -2403,10 +2592,20 @@
         // Add two documents
         GenericDocument document1 =
                 new GenericDocument.Builder<>("namespace1", "id1", "type").build();
-        mAppSearchImpl.putDocument("package", "database", document1, /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package",
+                "database",
+                document1,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
         GenericDocument document2 =
                 new GenericDocument.Builder<>("namespace1", "id2", "type").build();
-        mAppSearchImpl.putDocument("package", "database", document2, /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package",
+                "database",
+                document2,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
 
         StorageInfoProto storageInfo = mAppSearchImpl.getRawStorageInfoProto();
 
@@ -2462,7 +2661,11 @@
                         AppSearchException.class,
                         () ->
                                 mAppSearchImpl.putDocument(
-                                        "package", "database", document, /*logger=*/ null));
+                                        "package",
+                                        "database",
+                                        document,
+                                        /*sendChangeNotifications=*/ false,
+                                        /*logger=*/ null));
         assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
         assertThat(e)
                 .hasMessageThat()
@@ -2474,7 +2677,12 @@
         // index 1 document.
         GenericDocument document2 =
                 new GenericDocument.Builder<>("namespace", "id2", "type").build();
-        mAppSearchImpl.putDocument("package", "database", document2, /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package",
+                "database",
+                document2,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
 
         // Now we should get a failure
         GenericDocument document3 =
@@ -2484,7 +2692,11 @@
                         AppSearchException.class,
                         () ->
                                 mAppSearchImpl.putDocument(
-                                        "package", "database", document3, /*logger=*/ null));
+                                        "package",
+                                        "database",
+                                        document3,
+                                        /*sendChangeNotifications=*/ false,
+                                        /*logger=*/ null));
         assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
         assertThat(e)
                 .hasMessageThat()
@@ -2531,6 +2743,7 @@
                 "package",
                 "database",
                 new GenericDocument.Builder<>("namespace", "id1", "type").build(),
+                /*sendChangeNotifications=*/ false,
                 /*logger=*/ null);
 
         // Now we should get a failure
@@ -2541,7 +2754,11 @@
                         AppSearchException.class,
                         () ->
                                 mAppSearchImpl.putDocument(
-                                        "package", "database", document2, /*logger=*/ null));
+                                        "package",
+                                        "database",
+                                        document2,
+                                        /*sendChangeNotifications=*/ false,
+                                        /*logger=*/ null));
         assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
         assertThat(e)
                 .hasMessageThat()
@@ -2573,7 +2790,11 @@
                         AppSearchException.class,
                         () ->
                                 mAppSearchImpl.putDocument(
-                                        "package", "database", document2, /*logger=*/ null));
+                                        "package",
+                                        "database",
+                                        document2,
+                                        /*sendChangeNotifications=*/ false,
+                                        /*logger=*/ null));
         assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
         assertThat(e)
                 .hasMessageThat()
@@ -2619,16 +2840,19 @@
                 "package",
                 "database",
                 new GenericDocument.Builder<>("namespace", "id1", "type").build(),
+                /*sendChangeNotifications=*/ false,
                 /*logger=*/ null);
         mAppSearchImpl.putDocument(
                 "package",
                 "database",
                 new GenericDocument.Builder<>("namespace", "id2", "type").build(),
+                /*sendChangeNotifications=*/ false,
                 /*logger=*/ null);
         mAppSearchImpl.putDocument(
                 "package",
                 "database",
                 new GenericDocument.Builder<>("namespace", "id3", "type").build(),
+                /*sendChangeNotifications=*/ false,
                 /*logger=*/ null);
 
         // Now we should get a failure
@@ -2639,7 +2863,11 @@
                         AppSearchException.class,
                         () ->
                                 mAppSearchImpl.putDocument(
-                                        "package", "database", document4, /*logger=*/ null));
+                                        "package",
+                                        "database",
+                                        document4,
+                                        /*sendChangeNotifications=*/ false,
+                                        /*logger=*/ null));
         assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
         assertThat(e)
                 .hasMessageThat()
@@ -2662,7 +2890,11 @@
                         AppSearchException.class,
                         () ->
                                 mAppSearchImpl.putDocument(
-                                        "package", "database", document4, /*logger=*/ null));
+                                        "package",
+                                        "database",
+                                        document4,
+                                        /*sendChangeNotifications=*/ false,
+                                        /*logger=*/ null));
         assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
         assertThat(e)
                 .hasMessageThat()
@@ -2673,7 +2905,12 @@
                 "package", "database", "namespace", "id2", /*removeStatsBuilder=*/ null);
 
         // Now doc4 should work
-        mAppSearchImpl.putDocument("package", "database", document4, /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package",
+                "database",
+                document4,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
 
         // The next one should fail again
         e =
@@ -2685,6 +2922,7 @@
                                         "database",
                                         new GenericDocument.Builder<>("namespace", "id5", "type")
                                                 .build(),
+                                        /*sendChangeNotifications=*/ false,
                                         /*logger=*/ null));
         assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
         assertThat(e)
@@ -2756,11 +2994,13 @@
                 "package1",
                 "database1",
                 new GenericDocument.Builder<>("namespace", "id1", "type").build(),
+                /*sendChangeNotifications=*/ false,
                 /*logger=*/ null);
         mAppSearchImpl.putDocument(
                 "package1",
                 "database2",
                 new GenericDocument.Builder<>("namespace", "id2", "type").build(),
+                /*sendChangeNotifications=*/ false,
                 /*logger=*/ null);
 
         // Indexing a third doc into package1 should fail (here we use database3)
@@ -2773,6 +3013,7 @@
                                         "database3",
                                         new GenericDocument.Builder<>("namespace", "id3", "type")
                                                 .build(),
+                                        /*sendChangeNotifications=*/ false,
                                         /*logger=*/ null));
         assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
         assertThat(e)
@@ -2784,6 +3025,7 @@
                 "package2",
                 "database1",
                 new GenericDocument.Builder<>("namespace", "id1", "type").build(),
+                /*sendChangeNotifications=*/ false,
                 /*logger=*/ null);
 
         // Reinitialize to make sure packages are parsed correctly on init
@@ -2816,6 +3058,7 @@
                                         "database4",
                                         new GenericDocument.Builder<>("namespace", "id4", "type")
                                                 .build(),
+                                        /*sendChangeNotifications=*/ false,
                                         /*logger=*/ null));
         assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
         assertThat(e)
@@ -2827,6 +3070,7 @@
                 "package2",
                 "database2",
                 new GenericDocument.Builder<>("namespace", "id2", "type").build(),
+                /*sendChangeNotifications=*/ false,
                 /*logger=*/ null);
 
         // now package2 really is out of space
@@ -2839,6 +3083,7 @@
                                         "database3",
                                         new GenericDocument.Builder<>("namespace", "id3", "type")
                                                 .build(),
+                                        /*sendChangeNotifications=*/ false,
                                         /*logger=*/ null));
         assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
         assertThat(e)
@@ -2898,6 +3143,7 @@
                 new GenericDocument.Builder<>("namespace", "id1", "type")
                         .setPropertyString("body", "tablet")
                         .build(),
+                /*sendChangeNotifications=*/ false,
                 /*logger=*/ null);
         mAppSearchImpl.putDocument(
                 "package",
@@ -2905,6 +3151,7 @@
                 new GenericDocument.Builder<>("namespace", "id2", "type")
                         .setPropertyString("body", "tabby")
                         .build(),
+                /*sendChangeNotifications=*/ false,
                 /*logger=*/ null);
         mAppSearchImpl.putDocument(
                 "package",
@@ -2912,6 +3159,7 @@
                 new GenericDocument.Builder<>("namespace", "id3", "type")
                         .setPropertyString("body", "grabby")
                         .build(),
+                /*sendChangeNotifications=*/ false,
                 /*logger=*/ null);
 
         // Now we should get a failure
@@ -2922,7 +3170,11 @@
                         AppSearchException.class,
                         () ->
                                 mAppSearchImpl.putDocument(
-                                        "package", "database", document4, /*logger=*/ null));
+                                        "package",
+                                        "database",
+                                        document4,
+                                        /*sendChangeNotifications=*/ false,
+                                        /*logger=*/ null));
         assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
         assertThat(e)
                 .hasMessageThat()
@@ -2942,7 +3194,11 @@
                         AppSearchException.class,
                         () ->
                                 mAppSearchImpl.putDocument(
-                                        "package", "database", document4, /*logger=*/ null));
+                                        "package",
+                                        "database",
+                                        document4,
+                                        /*sendChangeNotifications=*/ false,
+                                        /*logger=*/ null));
         assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
         assertThat(e)
                 .hasMessageThat()
@@ -2957,11 +3213,17 @@
                 /*removeStatsBuilder=*/ null);
 
         // Now doc4 and doc5 should work
-        mAppSearchImpl.putDocument("package", "database", document4, /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package",
+                "database",
+                document4,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
         mAppSearchImpl.putDocument(
                 "package",
                 "database",
                 new GenericDocument.Builder<>("namespace", "id5", "type").build(),
+                /*sendChangeNotifications=*/ false,
                 /*logger=*/ null);
 
         // We only deleted 2 docs so the next one should fail again
@@ -2974,6 +3236,7 @@
                                         "database",
                                         new GenericDocument.Builder<>("namespace", "id6", "type")
                                                 .build(),
+                                        /*sendChangeNotifications=*/ false,
                                         /*logger=*/ null));
         assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
         assertThat(e)
@@ -3027,6 +3290,7 @@
                 new GenericDocument.Builder<>("namespace", "id1", "type")
                         .setPropertyString("body", "id1.orig")
                         .build(),
+                /*sendChangeNotifications=*/ false,
                 /*logger=*/ null);
         // Replace it with another doc
         mAppSearchImpl.putDocument(
@@ -3035,6 +3299,7 @@
                 new GenericDocument.Builder<>("namespace", "id1", "type")
                         .setPropertyString("body", "id1.new")
                         .build(),
+                /*sendChangeNotifications=*/ false,
                 /*logger=*/ null);
 
         // Index id2. This should pass but only because we check for replacements.
@@ -3042,6 +3307,7 @@
                 "package",
                 "database",
                 new GenericDocument.Builder<>("namespace", "id2", "type").build(),
+                /*sendChangeNotifications=*/ false,
                 /*logger=*/ null);
 
         // Now we should get a failure on id3
@@ -3052,7 +3318,11 @@
                         AppSearchException.class,
                         () ->
                                 mAppSearchImpl.putDocument(
-                                        "package", "database", document3, /*logger=*/ null));
+                                        "package",
+                                        "database",
+                                        document3,
+                                        /*sendChangeNotifications=*/ false,
+                                        /*logger=*/ null));
         assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
         assertThat(e)
                 .hasMessageThat()
@@ -3106,6 +3376,7 @@
                 new GenericDocument.Builder<>("namespace", "id1", "type")
                         .setPropertyString("body", "id1.orig")
                         .build(),
+                /*sendChangeNotifications=*/ false,
                 /*logger=*/ null);
         // Replace it with another doc
         mAppSearchImpl.putDocument(
@@ -3114,6 +3385,7 @@
                 new GenericDocument.Builder<>("namespace", "id1", "type")
                         .setPropertyString("body", "id1.new")
                         .build(),
+                /*sendChangeNotifications=*/ false,
                 /*logger=*/ null);
 
         // Reinitialize to make sure replacements are correctly accounted for by init
@@ -3141,6 +3413,7 @@
                 "package",
                 "database",
                 new GenericDocument.Builder<>("namespace", "id2", "type").build(),
+                /*sendChangeNotifications=*/ false,
                 /*logger=*/ null);
 
         // Now we should get a failure on id3
@@ -3151,7 +3424,11 @@
                         AppSearchException.class,
                         () ->
                                 mAppSearchImpl.putDocument(
-                                        "package", "database", document3, /*logger=*/ null));
+                                        "package",
+                                        "database",
+                                        document3,
+                                        /*sendChangeNotifications=*/ false,
+                                        /*logger=*/ null));
         assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
         assertThat(e)
                 .hasMessageThat()
@@ -3196,7 +3473,11 @@
         assertThat(observer.getSchemaChanges()).isEmpty();
         assertThat(observer.getDocumentChanges()).isEmpty();
         mAppSearchImpl.putDocument(
-                mContext.getPackageName(), "database1", validDoc, /*logger=*/ null);
+                mContext.getPackageName(),
+                "database1",
+                validDoc,
+                /*sendChangeNotifications=*/ true,
+                /*logger=*/ null);
 
         // Dispatch notifications and empty the observers
         mAppSearchImpl.dispatchAndClearChangeNotifications();
@@ -3207,7 +3488,12 @@
 
         // Index a second document
         GenericDocument doc2 = new GenericDocument.Builder<>("namespace1", "id2", "Type1").build();
-        mAppSearchImpl.putDocument(mContext.getPackageName(), "database1", doc2, /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                mContext.getPackageName(),
+                "database1",
+                doc2,
+                /*sendChangeNotifications=*/ true,
+                /*logger=*/ null);
 
         // Observer should still have received this data from its registration on
         // context.getPackageName(), as we only removed the copy from fakePackage.
@@ -3253,7 +3539,12 @@
         // Add a document and persist it.
         GenericDocument document =
                 new GenericDocument.Builder<>("namespace1", "id1", "type").build();
-        mAppSearchImpl.putDocument("package", "database", document, /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package",
+                "database",
+                document,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
         mAppSearchImpl.persistToDisk(PersistType.Code.LITE);
 
         AppSearchException e =
@@ -3301,7 +3592,12 @@
         // Add a document and persist it.
         GenericDocument document =
                 new GenericDocument.Builder<>("namespace1", "id1", "type").build();
-        mAppSearchImpl.putDocument("package", "database", document, /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package",
+                "database",
+                document,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
         mAppSearchImpl.persistToDisk(PersistType.Code.LITE);
 
         GenericDocument getResult =
@@ -3345,7 +3641,12 @@
         // Add a document and persist it.
         GenericDocument document =
                 new GenericDocument.Builder<>("namespace1", "id1", "type").build();
-        mAppSearchImpl.putDocument("package", "database", document, /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package",
+                "database",
+                document,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
         mAppSearchImpl.persistToDisk(PersistType.Code.LITE);
 
         AppSearchException e =
@@ -3393,7 +3694,12 @@
         // Add a document and persist it.
         GenericDocument document =
                 new GenericDocument.Builder<>("namespace1", "id1", "type").build();
-        mAppSearchImpl.putDocument("package", "database", document, /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                "package",
+                "database",
+                document,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
         mAppSearchImpl.persistToDisk(PersistType.Code.LITE);
 
         AppSearchException unauthorizedException =
@@ -3807,6 +4113,7 @@
                 mContext.getPackageName(),
                 "database1",
                 new GenericDocument.Builder<>("namespace1", "id1", "Type1").build(),
+                /*sendChangeNotifications=*/ true,
                 /*logger=*/ null);
         assertThat(observer.getSchemaChanges()).isEmpty();
         assertThat(observer.getDocumentChanges()).isEmpty();
@@ -3863,6 +4170,7 @@
                 mContext.getPackageName(),
                 "database1",
                 new GenericDocument.Builder<>("namespace1", "id1", "Type1").build(),
+                /*sendChangeNotifications=*/ true,
                 /*logger=*/ null);
         assertThat(observer.getSchemaChanges()).isEmpty();
         assertThat(observer.getDocumentChanges()).isEmpty();
@@ -3907,6 +4215,7 @@
                 mContext.getPackageName(),
                 "database1",
                 new GenericDocument.Builder<>("namespace1", "id1", "Type1").build(),
+                /*sendChangeNotifications=*/ true,
                 /*logger=*/ null);
         assertThat(observer.getSchemaChanges()).isEmpty();
         assertThat(observer.getDocumentChanges()).isEmpty();
@@ -3959,6 +4268,7 @@
                 mContext.getPackageName(),
                 "database1",
                 new GenericDocument.Builder<>("namespace1", "id1", "Type1").build(),
+                /*sendChangeNotifications=*/ true,
                 /*logger=*/ null);
         assertThat(observer.getSchemaChanges()).isEmpty();
         assertThat(observer.getDocumentChanges()).isEmpty();
@@ -4017,6 +4327,7 @@
                 mContext.getPackageName(),
                 "database1",
                 new GenericDocument.Builder<>("namespace1", "id1", "Type1").build(),
+                /*sendChangeNotifications=*/ true,
                 /*logger=*/ null);
         assertThat(observer.getSchemaChanges()).isEmpty();
         assertThat(observer.getDocumentChanges()).isEmpty();
@@ -4116,7 +4427,7 @@
                 "database1",
                 ImmutableList.of(new AppSearchSchema.Builder("Type1").build()),
                 /*visibilityDocuments=*/ Collections.emptyList(),
-                /*forceOverride=*/ false,
+                /*forceOverride=*/ true,
                 /*version=*/ 0,
                 /*setSchemaStatsBuilder=*/ null);
 
@@ -4604,7 +4915,7 @@
                 "database1",
                 ImmutableList.of(new AppSearchSchema.Builder("Type2").build()),
                 /*visibilityDocuments=*/ Collections.emptyList(),
-                /*forceOverride=*/ false,
+                /*forceOverride=*/ true,
                 /*version=*/ 0,
                 /*setSchemaStatsBuilder=*/ null);
 
@@ -4621,7 +4932,7 @@
                 "database1",
                 ImmutableList.of(),
                 /*visibilityDocuments=*/ Collections.emptyList(),
-                /*forceOverride=*/ false,
+                /*forceOverride=*/ true,
                 /*version=*/ 0,
                 /*setSchemaStatsBuilder=*/ null);
         assertThat(observer.getSchemaChanges()).isEmpty();
@@ -4710,7 +5021,7 @@
                 "database1",
                 ImmutableList.of(),
                 /*visibilityDocuments=*/ Collections.emptyList(),
-                /*forceOverride=*/ false,
+                /*forceOverride=*/ true,
                 /*version=*/ 0,
                 /*setSchemaStatsBuilder=*/ null);
 
@@ -4742,4 +5053,102 @@
                                 mContext.getPackageName(), "database1", ImmutableSet.of("Type4")));
         assertThat(observerPkg1FilterType4.getDocumentChanges()).isEmpty();
     }
+
+    @Test
+    public void testAddObserver_schemaChange_noChangeIfIncompatible() throws Exception {
+        // Add a schema with two types.
+        mAppSearchImpl.setSchema(
+                mContext.getPackageName(),
+                "database1",
+                ImmutableList.of(
+                        new AppSearchSchema.Builder("Type1")
+                                .addProperty(
+                                        new AppSearchSchema.StringPropertyConfig.Builder("strProp")
+                                                .setCardinality(
+                                                        AppSearchSchema.PropertyConfig
+                                                                .CARDINALITY_OPTIONAL)
+                                                .build())
+                                .build(),
+                        new AppSearchSchema.Builder("Type2")
+                                .addProperty(
+                                        new AppSearchSchema.StringPropertyConfig.Builder("strProp")
+                                                .setCardinality(
+                                                        AppSearchSchema.PropertyConfig
+                                                                .CARDINALITY_OPTIONAL)
+                                                .build())
+                                .build()),
+                /*visibilityDocuments=*/ Collections.emptyList(),
+                /*forceOverride=*/ false,
+                /*version=*/ 1,
+                /*setSchemaStatsBuilder=*/ null);
+
+        // Register an observer
+        TestObserverCallback observer = new TestObserverCallback();
+        mAppSearchImpl.registerObserverCallback(
+                new CallerAccess(/*callingPackageName=*/ mContext.getPackageName()),
+                /*targetPackageName=*/ mContext.getPackageName(),
+                new ObserverSpec.Builder().build(),
+                MoreExecutors.directExecutor(),
+                observer);
+
+        // Update schema to try to make an incompatible change to Type1, and a compatible change to
+        // Type2.
+        List<AppSearchSchema> updatedSchemaTypes =
+                ImmutableList.of(
+                        new AppSearchSchema.Builder("Type1")
+                                .addProperty(
+                                        new AppSearchSchema.StringPropertyConfig.Builder("strProp")
+                                                .setCardinality(
+                                                        AppSearchSchema.PropertyConfig
+                                                                .CARDINALITY_REQUIRED)
+                                                .build())
+                                .build(),
+                        new AppSearchSchema.Builder("Type2")
+                                .addProperty(
+                                        new AppSearchSchema.StringPropertyConfig.Builder("strProp")
+                                                .setCardinality(
+                                                        AppSearchSchema.PropertyConfig
+                                                                .CARDINALITY_REPEATED)
+                                                .build())
+                                .build());
+        SetSchemaResponse setSchemaResponse =
+                mAppSearchImpl.setSchema(
+                        mContext.getPackageName(),
+                        "database1",
+                        updatedSchemaTypes,
+                        /*visibilityDocuments=*/ Collections.emptyList(),
+                        /*forceOverride=*/ false,
+                        /*version=*/ 2,
+                        /*setSchemaStatsBuilder=*/ null);
+        assertThat(setSchemaResponse.getDeletedTypes()).isEmpty();
+        assertThat(setSchemaResponse.getIncompatibleTypes()).containsExactly("Type1");
+
+        // Dispatch notifications. Nothing should appear since the schema was incompatible and has
+        // not changed.
+        assertThat(observer.getSchemaChanges()).isEmpty();
+        assertThat(observer.getDocumentChanges()).isEmpty();
+        mAppSearchImpl.dispatchAndClearChangeNotifications();
+        assertThat(observer.getSchemaChanges()).isEmpty();
+        assertThat(observer.getDocumentChanges()).isEmpty();
+
+        // Now force apply the schemas Type2. This should cause a notification.
+        mAppSearchImpl.setSchema(
+                mContext.getPackageName(),
+                "database1",
+                updatedSchemaTypes,
+                /*visibilityDocuments=*/ Collections.emptyList(),
+                /*forceOverride=*/ true,
+                /*version=*/ 3,
+                /*setSchemaStatsBuilder=*/ null);
+        assertThat(observer.getSchemaChanges()).isEmpty();
+        assertThat(observer.getDocumentChanges()).isEmpty();
+        mAppSearchImpl.dispatchAndClearChangeNotifications();
+        assertThat(observer.getSchemaChanges())
+                .containsExactly(
+                        new SchemaChangeInfo(
+                                mContext.getPackageName(),
+                                "database1",
+                                ImmutableSet.of("Type1", "Type2")));
+        assertThat(observer.getDocumentChanges()).isEmpty();
+    }
 }
diff --git a/testing/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java b/testing/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java
index 798e710..c555fdd 100644
--- a/testing/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java
+++ b/testing/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java
@@ -392,8 +392,10 @@
                 /* setSchemaStatsBuilder= */ null);
         GenericDocument doc1 = new GenericDocument.Builder<>("namespace", "id1", "Type1").build();
         GenericDocument doc2 = new GenericDocument.Builder<>("namespace", "id2", "Type1").build();
-        appSearchImpl.putDocument(testPackageName, testDatabase, doc1, mLogger);
-        appSearchImpl.putDocument(testPackageName, testDatabase, doc2, mLogger);
+        appSearchImpl.putDocument(
+                testPackageName, testDatabase, doc1, /*sendChangeNotifications=*/ false, mLogger);
+        appSearchImpl.putDocument(
+                testPackageName, testDatabase, doc2, /*sendChangeNotifications=*/ false, mLogger);
         appSearchImpl.close();
 
         // Create another appsearchImpl on the same folder
@@ -452,7 +454,8 @@
 
         // Insert a valid doc
         GenericDocument doc1 = new GenericDocument.Builder<>("namespace", "id1", "Type1").build();
-        appSearchImpl.putDocument(testPackageName, testDatabase, doc1, mLogger);
+        appSearchImpl.putDocument(
+                testPackageName, testDatabase, doc1, /*sendChangeNotifications=*/ false, mLogger);
 
         // Insert the invalid doc with an invalid namespace right into icing
         DocumentProto invalidDoc =
@@ -517,7 +520,12 @@
                         .setPropertyString("subject", "testPut example1")
                         .build();
 
-        mAppSearchImpl.putDocument(testPackageName, testDatabase, document, mLogger);
+        mAppSearchImpl.putDocument(
+                testPackageName,
+                testDatabase,
+                document,
+                /*sendChangeNotifications=*/ false,
+                mLogger);
 
         PutDocumentStats pStats = mLogger.mPutDocumentStats;
         assertThat(pStats).isNotNull();
@@ -568,7 +576,11 @@
                         AppSearchException.class,
                         () ->
                                 mAppSearchImpl.putDocument(
-                                        testPackageName, testDatabase, document, mLogger));
+                                        testPackageName,
+                                        testDatabase,
+                                        document,
+                                        /*sendChangeNotifications=*/ false,
+                                        mLogger));
         assertThat(exception.getResultCode()).isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
 
         PutDocumentStats pStats = mLogger.mPutDocumentStats;
@@ -618,9 +630,24 @@
                 new GenericDocument.Builder<>("namespace", "id3", "type")
                         .setPropertyString("subject", "testPut 3")
                         .build();
-        mAppSearchImpl.putDocument(testPackageName, testDatabase, document1, mLogger);
-        mAppSearchImpl.putDocument(testPackageName, testDatabase, document2, mLogger);
-        mAppSearchImpl.putDocument(testPackageName, testDatabase, document3, mLogger);
+        mAppSearchImpl.putDocument(
+                testPackageName,
+                testDatabase,
+                document1,
+                /*sendChangeNotifications=*/ false,
+                mLogger);
+        mAppSearchImpl.putDocument(
+                testPackageName,
+                testDatabase,
+                document2,
+                /*sendChangeNotifications=*/ false,
+                mLogger);
+        mAppSearchImpl.putDocument(
+                testPackageName,
+                testDatabase,
+                document3,
+                /*sendChangeNotifications=*/ false,
+                mLogger);
 
         // No query filters specified. package2 should only get its own documents back.
         SearchSpec searchSpec =
@@ -716,7 +743,12 @@
                 /* setSchemaStatsBuilder= */ null);
         GenericDocument document =
                 new GenericDocument.Builder<>(testNamespace, testId, "type").build();
-        mAppSearchImpl.putDocument(testPackageName, testDatabase, document, /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                testPackageName,
+                testDatabase,
+                document,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
 
         RemoveStats.Builder rStatsBuilder = new RemoveStats.Builder(testPackageName, testDatabase);
         mAppSearchImpl.remove(testPackageName, testDatabase, testNamespace, testId, rStatsBuilder);
@@ -750,7 +782,12 @@
 
         GenericDocument document =
                 new GenericDocument.Builder<>(testNamespace, testId, "type").build();
-        mAppSearchImpl.putDocument(testPackageName, testDatabase, document, /*logger=*/ null);
+        mAppSearchImpl.putDocument(
+                testPackageName,
+                testDatabase,
+                document,
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
 
         RemoveStats.Builder rStatsBuilder = new RemoveStats.Builder(testPackageName, testDatabase);
 
@@ -796,8 +833,18 @@
                 new GenericDocument.Builder<>(testNamespace, "id1", "type").build();
         GenericDocument document2 =
                 new GenericDocument.Builder<>(testNamespace, "id2", "type").build();
-        mAppSearchImpl.putDocument(testPackageName, testDatabase, document1, mLogger);
-        mAppSearchImpl.putDocument(testPackageName, testDatabase, document2, mLogger);
+        mAppSearchImpl.putDocument(
+                testPackageName,
+                testDatabase,
+                document1,
+                /*sendChangeNotifications=*/ false,
+                mLogger);
+        mAppSearchImpl.putDocument(
+                testPackageName,
+                testDatabase,
+                document2,
+                /*sendChangeNotifications=*/ false,
+                mLogger);
         // No query filters specified. package2 should only get its own documents back.
         SearchSpec searchSpec =
                 new SearchSpec.Builder().setTermMatch(TermMatchType.Code.PREFIX_VALUE).build();
diff --git a/testing/servicestests/src/com/android/server/appsearch/external/localstorage/visibilitystore/VisibilityStoreMigrationHelperFromV0Test.java b/testing/servicestests/src/com/android/server/appsearch/external/localstorage/visibilitystore/VisibilityStoreMigrationHelperFromV0Test.java
index 7008a54..fefddb2 100644
--- a/testing/servicestests/src/com/android/server/appsearch/external/localstorage/visibilitystore/VisibilityStoreMigrationHelperFromV0Test.java
+++ b/testing/servicestests/src/com/android/server/appsearch/external/localstorage/visibilitystore/VisibilityStoreMigrationHelperFromV0Test.java
@@ -128,6 +128,7 @@
                 VisibilityStore.VISIBILITY_PACKAGE_NAME,
                 VisibilityStore.VISIBILITY_DATABASE_NAME,
                 deprecatedVisibilityDocument,
+                /*sendChangeNotifications=*/ false,
                 /*logger=*/ null);
 
         // Persist to disk and re-open the AppSearchImpl
diff --git a/testing/servicestests/src/com/android/server/appsearch/external/localstorage/visibilitystore/VisibilityStoreMigrationHelperFromV1Test.java b/testing/servicestests/src/com/android/server/appsearch/external/localstorage/visibilitystore/VisibilityStoreMigrationHelperFromV1Test.java
index 9504f7c..c2949d3 100644
--- a/testing/servicestests/src/com/android/server/appsearch/external/localstorage/visibilitystore/VisibilityStoreMigrationHelperFromV1Test.java
+++ b/testing/servicestests/src/com/android/server/appsearch/external/localstorage/visibilitystore/VisibilityStoreMigrationHelperFromV1Test.java
@@ -114,6 +114,7 @@
                 VisibilityStore.VISIBILITY_PACKAGE_NAME,
                 VisibilityStore.VISIBILITY_DATABASE_NAME,
                 visibilityDocumentV1,
+                /*sendChangeNotifications=*/ false,
                 /*logger=*/ null);
 
         // Persist to disk and re-open the AppSearchImpl
diff --git a/testing/testutils/src/android/app/appsearch/testutil/external/testutil/TestObserverCallback.java b/testing/testutils/src/android/app/appsearch/testutil/external/testutil/TestObserverCallback.java
index e3e8df9..7a4c7b5 100644
--- a/testing/testutils/src/android/app/appsearch/testutil/external/testutil/TestObserverCallback.java
+++ b/testing/testutils/src/android/app/appsearch/testutil/external/testutil/TestObserverCallback.java
@@ -80,7 +80,11 @@
                                     + expectedCount
                                     + " notifications but there are"
                                     + " already "
-                                    + actualCount);
+                                    + actualCount
+                                    + ".\n  Schema changes: "
+                                    + mSchemaChanges
+                                    + "\n  Document changes: "
+                                    + mDocumentChanges);
                 } else if (actualCount == expectedCount) {
                     return;
                 } else {