Loading apex/appsearch/framework/java/android/app/appsearch/AppSearchMigrationHelper.java +28 −15 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package android.app.appsearch; import static android.app.appsearch.AppSearchResult.RESULT_INVALID_SCHEMA; import static android.os.ParcelFileDescriptor.MODE_READ_ONLY; import static android.os.ParcelFileDescriptor.MODE_WRITE_ONLY; Loading @@ -27,6 +28,7 @@ import android.os.Bundle; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.util.ArraySet; import com.android.internal.infra.AndroidFuture; Loading @@ -39,8 +41,8 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.ExecutionException; /** Loading @@ -55,23 +57,23 @@ public class AppSearchMigrationHelper implements Closeable { private final String mDatabaseName; private final int mUserId; private final File mMigratedFile; private final Map<String, Integer> mCurrentVersionMap; private final Map<String, Integer> mFinalVersionMap; private final Set<String> mDestinationTypes; private boolean mAreDocumentsMigrated = false; AppSearchMigrationHelper(@NonNull IAppSearchManager service, @UserIdInt int userId, @NonNull Map<String, Integer> currentVersionMap, @NonNull Map<String, Integer> finalVersionMap, @NonNull String packageName, @NonNull String databaseName) throws IOException { @NonNull String databaseName, @NonNull Set<AppSearchSchema> newSchemas) throws IOException { mService = Objects.requireNonNull(service); mCurrentVersionMap = Objects.requireNonNull(currentVersionMap); mFinalVersionMap = Objects.requireNonNull(finalVersionMap); mPackageName = Objects.requireNonNull(packageName); mDatabaseName = Objects.requireNonNull(databaseName); mUserId = userId; mMigratedFile = File.createTempFile(/*prefix=*/"appsearch", /*suffix=*/null); mDestinationTypes = new ArraySet<>(newSchemas.size()); for (AppSearchSchema newSchema : newSchemas) { mDestinationTypes.add(newSchema.getSchemaType()); } } /** Loading @@ -87,7 +89,8 @@ public class AppSearchMigrationHelper implements Closeable { * GenericDocument} to new version. */ @WorkerThread public void queryAndTransform(@NonNull String schemaType, @NonNull Migrator migrator) public void queryAndTransform(@NonNull String schemaType, @NonNull Migrator migrator, int currentVersion, int finalVersion) throws IOException, AppSearchException, InterruptedException, ExecutionException { File queryFile = File.createTempFile(/*prefix=*/"appsearch", /*suffix=*/null); try (ParcelFileDescriptor fileDescriptor = Loading @@ -111,7 +114,7 @@ public class AppSearchMigrationHelper implements Closeable { if (!result.isSuccess()) { throw new AppSearchException(result.getResultCode(), result.getErrorMessage()); } readAndTransform(queryFile, migrator); readAndTransform(queryFile, migrator, currentVersion, finalVersion); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } finally { Loading Loading @@ -173,8 +176,9 @@ public class AppSearchMigrationHelper implements Closeable { * * <p>Save migrated {@link GenericDocument}s to the {@link #mMigratedFile}. */ private void readAndTransform(@NonNull File file, @NonNull Migrator migrator) throws IOException { private void readAndTransform(@NonNull File file, @NonNull Migrator migrator, int currentVersion, int finalVersion) throws IOException, AppSearchException { try (DataInputStream inputStream = new DataInputStream(new FileInputStream(file)); DataOutputStream outputStream = new DataOutputStream(new FileOutputStream( mMigratedFile, /*append=*/ true))) { Loading @@ -187,9 +191,6 @@ public class AppSearchMigrationHelper implements Closeable { // Nothing wrong. We just finished reading. } int currentVersion = mCurrentVersionMap.get(document.getSchemaType()); int finalVersion = mFinalVersionMap.get(document.getSchemaType()); GenericDocument newDocument; if (currentVersion < finalVersion) { newDocument = migrator.onUpgrade(currentVersion, finalVersion, document); Loading @@ -197,6 +198,18 @@ public class AppSearchMigrationHelper implements Closeable { // currentVersion == finalVersion case won't trigger migration and get here. newDocument = migrator.onDowngrade(currentVersion, finalVersion, document); } if (!mDestinationTypes.contains(newDocument.getSchemaType())) { // we exit before the new schema has been set to AppSearch. So no // observable changes will be applied to stored schemas and documents. // And the temp file will be deleted at close(), which will be triggered at // the end of try-with-resources block of SearchSessionImpl. throw new AppSearchException( RESULT_INVALID_SCHEMA, "Receive a migrated document with schema type: " + newDocument.getSchemaType() + ". But the schema types doesn't exist in the request"); } writeBundleToOutputStream(outputStream, newDocument.getBundle()); } mAreDocumentsMigrated = true; Loading apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java +32 −55 Original line number Diff line number Diff line Loading @@ -19,7 +19,6 @@ package android.app.appsearch; import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.appsearch.exceptions.AppSearchException; import android.app.appsearch.util.SchemaMigrationUtil; import android.os.Bundle; import android.os.ParcelableException; Loading Loading @@ -647,8 +646,8 @@ public final class AppSearchSession implements Closeable { new ArrayList<>(request.getSchemasNotDisplayedBySystem()), schemasPackageAccessibleBundles, request.isForceOverride(), mUserId, request.getVersion(), mUserId, new IAppSearchResultCallback.Stub() { public void onResult(AppSearchResult result) { executor.execute(() -> { Loading @@ -661,7 +660,7 @@ public final class AppSearchSession implements Closeable { // Throw exception if there is any deleted types or // incompatible types. That's the only case we swallowed // in the AppSearchImpl#setSchema(). checkDeletedAndIncompatible( SchemaMigrationUtil.checkDeletedAndIncompatible( setSchemaResponse.getDeletedTypes(), setSchemaResponse.getIncompatibleTypes()); } Loading Loading @@ -698,7 +697,7 @@ public final class AppSearchSession implements Closeable { workExecutor.execute(() -> { try { // Migration process // 1. Generate the current and the final version map. // 1. Validate and retrieve all active migrators. AndroidFuture<AppSearchResult<GetSchemaResponse>> getSchemaFuture = new AndroidFuture<>(); getSchema(callbackExecutor, getSchemaFuture::complete); Loading @@ -709,11 +708,18 @@ public final class AppSearchSession implements Closeable { return; } GetSchemaResponse getSchemaResponse = getSchemaResult.getResultValue(); Set<AppSearchSchema> currentSchemas = getSchemaResponse.getSchemas(); Map<String, Integer> currentVersionMap = SchemaMigrationUtil.buildVersionMap( currentSchemas, getSchemaResponse.getVersion()); Map<String, Integer> finalVersionMap = SchemaMigrationUtil.buildVersionMap( request.getSchemas(), request.getVersion()); int currentVersion = getSchemaResponse.getVersion(); int finalVersion = request.getVersion(); Map<String, Migrator> activeMigrators = SchemaMigrationUtil.getActiveMigrators( getSchemaResponse.getSchemas(), request.getMigrators(), currentVersion, finalVersion); // No need to trigger migration if no migrator is active. if (activeMigrators.isEmpty()) { setSchemaNoMigrations(request, schemaBundles, schemasPackageAccessibleBundles, callbackExecutor, callback); return; } // 2. SetSchema with forceOverride=false, to retrieve the list of // incompatible/deleted types. Loading @@ -725,8 +731,8 @@ public final class AppSearchSession implements Closeable { new ArrayList<>(request.getSchemasNotDisplayedBySystem()), schemasPackageAccessibleBundles, /*forceOverride=*/ false, mUserId, request.getVersion(), mUserId, new IAppSearchResultCallback.Stub() { public void onResult(AppSearchResult result) { setSchemaFuture.complete(result); Loading @@ -741,46 +747,27 @@ public final class AppSearchSession implements Closeable { SetSchemaResponse setSchemaResponse = new SetSchemaResponse(setSchemaResult.getResultValue()); // 1. If forceOverride is false, check that all incompatible types will be migrated. // 3. If forceOverride is false, check that all incompatible types will be migrated. // If some aren't we must throw an error, rather than proceeding and deleting those // types. if (!request.isForceOverride()) { Set<String> unmigratedTypes = SchemaMigrationUtil.getUnmigratedIncompatibleTypes( setSchemaResponse.getIncompatibleTypes(), request.getMigrators(), currentVersionMap, finalVersionMap); // check if there are any unmigrated types or deleted types. If there are, we // will throw an exception. // Since the force override is false, the schema will not have been set if there // are any incompatible or deleted types. checkDeletedAndIncompatible( setSchemaResponse.getDeletedTypes(), unmigratedTypes); } try (AppSearchMigrationHelper migrationHelper = new AppSearchMigrationHelper( mService, mUserId, currentVersionMap, finalVersionMap, mPackageName, mDatabaseName)) { Map<String, Migrator> migratorMap = request.getMigrators(); // 2. Trigger migration for all migrators. SchemaMigrationUtil.checkDeletedAndIncompatibleAfterMigration(setSchemaResponse, activeMigrators.keySet()); } try (AppSearchMigrationHelper migrationHelper = new AppSearchMigrationHelper( mService, mUserId, mPackageName, mDatabaseName, request.getSchemas())) { // 4. Trigger migration for all migrators. // TODO(b/177266929) trigger migration for all types together rather than // separately. Set<String> migratedTypes = new ArraySet<>(); for (Map.Entry<String, Migrator> entry : migratorMap.entrySet()) { String schemaType = entry.getKey(); Migrator migrator = entry.getValue(); if (SchemaMigrationUtil.shouldTriggerMigration( schemaType, migrator, currentVersionMap, finalVersionMap)) { migrationHelper.queryAndTransform(schemaType, migrator); migratedTypes.add(schemaType); } for (Map.Entry<String, Migrator> entry : activeMigrators.entrySet()) { migrationHelper.queryAndTransform(/*schemaType=*/ entry.getKey(), /*migrator=*/ entry.getValue(), currentVersion, finalVersion); } // 3. SetSchema a second time with forceOverride=true if the first attempted // 5. SetSchema a second time with forceOverride=true if the first attempted // failed. if (!setSchemaResponse.getIncompatibleTypes().isEmpty() || !setSchemaResponse.getDeletedTypes().isEmpty()) { Loading Loading @@ -809,13 +796,16 @@ public final class AppSearchSession implements Closeable { // error in the first setSchema call, all other errors will be thrown at // the first time. callbackExecutor.execute(() -> callback.accept( AppSearchResult.newFailedResult(setSchemaResult))); AppSearchResult.newFailedResult(setSchema2Result))); return; } } SetSchemaResponse.Builder responseBuilder = setSchemaResponse.toBuilder() .addMigratedTypes(migratedTypes); .addMigratedTypes(activeMigrators.keySet()); // 6. Put all the migrated documents into the index, now that the new schema is // set. AppSearchResult<SetSchemaResponse> putResult = migrationHelper.putMigratedDocuments(responseBuilder); callbackExecutor.execute(() -> callback.accept(putResult)); Loading @@ -826,17 +816,4 @@ public final class AppSearchSession implements Closeable { } }); } /** Checks the setSchema() call won't delete any types or has incompatible types. */ //TODO(b/177266929) move this method to util private void checkDeletedAndIncompatible(Set<String> deletedTypes, Set<String> incompatibleTypes) throws AppSearchException { if (!deletedTypes.isEmpty() || !incompatibleTypes.isEmpty()) { String newMessage = "Schema is incompatible." + "\n Deleted types: " + deletedTypes + "\n Incompatible types: " + incompatibleTypes; throw new AppSearchException(AppSearchResult.RESULT_INVALID_SCHEMA, newMessage); } } } apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl +2 −1 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ interface IAppSearchManager { * packages. The value List contains PackageIdentifier Bundles. * @param forceOverride Whether to apply the new schema even if it is incompatible. All * incompatible documents will be deleted. * @param schemaVersion The overall schema version number of the request. * @param userId Id of the calling user * @param callback {@link IAppSearchResultCallback#onResult} will be called with an * {@link AppSearchResult}<{@link Bundle}>, where the value are Loading @@ -52,8 +53,8 @@ interface IAppSearchManager { in List<String> schemasNotDisplayedBySystem, in Map<String, List<Bundle>> schemasPackageAccessibleBundles, boolean forceOverride, in int userId, in int schemaVersion, in int userId, in IAppSearchResultCallback callback); /** Loading apex/appsearch/framework/java/external/android/app/appsearch/util/SchemaMigrationUtil.java +52 −67 Original line number Diff line number Diff line Loading @@ -20,12 +20,11 @@ import android.annotation.NonNull; import android.app.appsearch.AppSearchResult; import android.app.appsearch.AppSearchSchema; import android.app.appsearch.Migrator; import android.app.appsearch.SetSchemaResponse; import android.app.appsearch.exceptions.AppSearchException; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.Set; Loading @@ -36,84 +35,70 @@ import java.util.Set; * @hide */ public final class SchemaMigrationUtil { private static final String TAG = "AppSearchMigrateUtil"; private SchemaMigrationUtil() {} /** * Finds out which incompatible schema type won't be migrated by comparing its current and final * version number. */ /** Returns all active {@link Migrator}s that need to be triggered in this migration. */ @NonNull public static Set<String> getUnmigratedIncompatibleTypes( @NonNull Set<String> incompatibleSchemaTypes, public static Map<String, Migrator> getActiveMigrators( @NonNull Set<AppSearchSchema> existingSchemas, @NonNull Map<String, Migrator> migrators, @NonNull Map<String, Integer> currentVersionMap, @NonNull Map<String, Integer> finalVersionMap) throws AppSearchException { Set<String> unmigratedSchemaTypes = new ArraySet<>(); for (String unmigratedSchemaType : incompatibleSchemaTypes) { Integer currentVersion = currentVersionMap.get(unmigratedSchemaType); Integer finalVersion = finalVersionMap.get(unmigratedSchemaType); if (currentVersion == null) { // impossible, we have done something wrong. throw new AppSearchException( AppSearchResult.RESULT_UNKNOWN_ERROR, "Cannot find the current version number for schema type: " + unmigratedSchemaType); int currentVersion, int finalVersion) { if (currentVersion == finalVersion) { return Collections.emptyMap(); } if (finalVersion == null) { // The schema doesn't exist in the SetSchemaRequest. unmigratedSchemaTypes.add(unmigratedSchemaType); continue; Set<String> existingTypes = new ArraySet<>(existingSchemas.size()); for (AppSearchSchema schema : existingSchemas) { existingTypes.add(schema.getSchemaType()); } // we don't have migrator or won't trigger migration for this schema type. Migrator migrator = migrators.get(unmigratedSchemaType); if (migrator == null || !migrator.shouldMigrate(currentVersion, finalVersion)) { unmigratedSchemaTypes.add(unmigratedSchemaType); Map<String, Migrator> activeMigrators = new ArrayMap<>(); for (Map.Entry<String, Migrator> entry : migrators.entrySet()) { // The device contains the source type, and we should trigger migration for the type. String schemaType = entry.getKey(); Migrator migrator = entry.getValue(); if (existingTypes.contains(schemaType) && migrator.shouldMigrate(currentVersion, finalVersion)) { activeMigrators.put(schemaType, migrator); } } return Collections.unmodifiableSet(unmigratedSchemaTypes); return activeMigrators; } /** * Triggers upgrade or downgrade migration for the given schema type if its version stored in * AppSearch is different with the version in the request. * * @return {@code True} if we trigger the migration for the given type. * Checks the setSchema() call won't delete any types or has incompatible types after all {@link * Migrator} has been triggered.. */ public static boolean shouldTriggerMigration( @NonNull String schemaType, @NonNull Migrator migrator, @NonNull Map<String, Integer> currentVersionMap, @NonNull Map<String, Integer> finalVersionMap) public static void checkDeletedAndIncompatibleAfterMigration( @NonNull SetSchemaResponse setSchemaResponse, @NonNull Set<String> activeMigrators) throws AppSearchException { Integer currentVersion = currentVersionMap.get(schemaType); Integer finalVersion = finalVersionMap.get(schemaType); if (currentVersion == null) { Log.d(TAG, "The SchemaType: " + schemaType + " not present in AppSearch."); return false; } if (finalVersion == null) { throw new AppSearchException( AppSearchResult.RESULT_INVALID_ARGUMENT, "Receive a migrator for schema type : " + schemaType + ", but the schema doesn't exist in the request."); } return migrator.shouldMigrate(currentVersion, finalVersion); Set<String> unmigratedIncompatibleTypes = new ArraySet<>(setSchemaResponse.getIncompatibleTypes()); unmigratedIncompatibleTypes.removeAll(activeMigrators); Set<String> unmigratedDeletedTypes = new ArraySet<>(setSchemaResponse.getDeletedTypes()); unmigratedDeletedTypes.removeAll(activeMigrators); // check if there are any unmigrated incompatible types or deleted types. If there // are, we will getActiveMigratorsthrow an exception. That's the only case we // swallowed in the AppSearchImpl#setSchema(). // Since the force override is false, the schema will not have been set if there are // any incompatible or deleted types. checkDeletedAndIncompatible(unmigratedDeletedTypes, unmigratedIncompatibleTypes); } /** Builds a Map of SchemaType and its version of given set of {@link AppSearchSchema}. */ //TODO(b/182620003) remove this method once support migrate to another type @NonNull public static Map<String, Integer> buildVersionMap( @NonNull Collection<AppSearchSchema> schemas, int version) { Map<String, Integer> currentVersionMap = new ArrayMap<>(schemas.size()); for (AppSearchSchema currentSchema : schemas) { currentVersionMap.put(currentSchema.getSchemaType(), version); /** Checks the setSchema() call won't delete any types or has incompatible types. */ public static void checkDeletedAndIncompatible( @NonNull Set<String> deletedTypes, @NonNull Set<String> incompatibleTypes) throws AppSearchException { if (deletedTypes.size() > 0 || incompatibleTypes.size() > 0) { String newMessage = "Schema is incompatible." + "\n Deleted types: " + deletedTypes + "\n Incompatible types: " + incompatibleTypes; throw new AppSearchException(AppSearchResult.RESULT_INVALID_SCHEMA, newMessage); } return currentVersionMap; } } apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java +1 −1 Original line number Diff line number Diff line Loading @@ -169,8 +169,8 @@ public class AppSearchManagerService extends SystemService { @NonNull List<String> schemasNotDisplayedBySystem, @NonNull Map<String, List<Bundle>> schemasPackageAccessibleBundles, boolean forceOverride, @UserIdInt int userId, int schemaVersion, @UserIdInt int userId, @NonNull IAppSearchResultCallback callback) { Preconditions.checkNotNull(packageName); Preconditions.checkNotNull(databaseName); Loading Loading
apex/appsearch/framework/java/android/app/appsearch/AppSearchMigrationHelper.java +28 −15 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package android.app.appsearch; import static android.app.appsearch.AppSearchResult.RESULT_INVALID_SCHEMA; import static android.os.ParcelFileDescriptor.MODE_READ_ONLY; import static android.os.ParcelFileDescriptor.MODE_WRITE_ONLY; Loading @@ -27,6 +28,7 @@ import android.os.Bundle; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.util.ArraySet; import com.android.internal.infra.AndroidFuture; Loading @@ -39,8 +41,8 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.ExecutionException; /** Loading @@ -55,23 +57,23 @@ public class AppSearchMigrationHelper implements Closeable { private final String mDatabaseName; private final int mUserId; private final File mMigratedFile; private final Map<String, Integer> mCurrentVersionMap; private final Map<String, Integer> mFinalVersionMap; private final Set<String> mDestinationTypes; private boolean mAreDocumentsMigrated = false; AppSearchMigrationHelper(@NonNull IAppSearchManager service, @UserIdInt int userId, @NonNull Map<String, Integer> currentVersionMap, @NonNull Map<String, Integer> finalVersionMap, @NonNull String packageName, @NonNull String databaseName) throws IOException { @NonNull String databaseName, @NonNull Set<AppSearchSchema> newSchemas) throws IOException { mService = Objects.requireNonNull(service); mCurrentVersionMap = Objects.requireNonNull(currentVersionMap); mFinalVersionMap = Objects.requireNonNull(finalVersionMap); mPackageName = Objects.requireNonNull(packageName); mDatabaseName = Objects.requireNonNull(databaseName); mUserId = userId; mMigratedFile = File.createTempFile(/*prefix=*/"appsearch", /*suffix=*/null); mDestinationTypes = new ArraySet<>(newSchemas.size()); for (AppSearchSchema newSchema : newSchemas) { mDestinationTypes.add(newSchema.getSchemaType()); } } /** Loading @@ -87,7 +89,8 @@ public class AppSearchMigrationHelper implements Closeable { * GenericDocument} to new version. */ @WorkerThread public void queryAndTransform(@NonNull String schemaType, @NonNull Migrator migrator) public void queryAndTransform(@NonNull String schemaType, @NonNull Migrator migrator, int currentVersion, int finalVersion) throws IOException, AppSearchException, InterruptedException, ExecutionException { File queryFile = File.createTempFile(/*prefix=*/"appsearch", /*suffix=*/null); try (ParcelFileDescriptor fileDescriptor = Loading @@ -111,7 +114,7 @@ public class AppSearchMigrationHelper implements Closeable { if (!result.isSuccess()) { throw new AppSearchException(result.getResultCode(), result.getErrorMessage()); } readAndTransform(queryFile, migrator); readAndTransform(queryFile, migrator, currentVersion, finalVersion); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } finally { Loading Loading @@ -173,8 +176,9 @@ public class AppSearchMigrationHelper implements Closeable { * * <p>Save migrated {@link GenericDocument}s to the {@link #mMigratedFile}. */ private void readAndTransform(@NonNull File file, @NonNull Migrator migrator) throws IOException { private void readAndTransform(@NonNull File file, @NonNull Migrator migrator, int currentVersion, int finalVersion) throws IOException, AppSearchException { try (DataInputStream inputStream = new DataInputStream(new FileInputStream(file)); DataOutputStream outputStream = new DataOutputStream(new FileOutputStream( mMigratedFile, /*append=*/ true))) { Loading @@ -187,9 +191,6 @@ public class AppSearchMigrationHelper implements Closeable { // Nothing wrong. We just finished reading. } int currentVersion = mCurrentVersionMap.get(document.getSchemaType()); int finalVersion = mFinalVersionMap.get(document.getSchemaType()); GenericDocument newDocument; if (currentVersion < finalVersion) { newDocument = migrator.onUpgrade(currentVersion, finalVersion, document); Loading @@ -197,6 +198,18 @@ public class AppSearchMigrationHelper implements Closeable { // currentVersion == finalVersion case won't trigger migration and get here. newDocument = migrator.onDowngrade(currentVersion, finalVersion, document); } if (!mDestinationTypes.contains(newDocument.getSchemaType())) { // we exit before the new schema has been set to AppSearch. So no // observable changes will be applied to stored schemas and documents. // And the temp file will be deleted at close(), which will be triggered at // the end of try-with-resources block of SearchSessionImpl. throw new AppSearchException( RESULT_INVALID_SCHEMA, "Receive a migrated document with schema type: " + newDocument.getSchemaType() + ". But the schema types doesn't exist in the request"); } writeBundleToOutputStream(outputStream, newDocument.getBundle()); } mAreDocumentsMigrated = true; Loading
apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java +32 −55 Original line number Diff line number Diff line Loading @@ -19,7 +19,6 @@ package android.app.appsearch; import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.appsearch.exceptions.AppSearchException; import android.app.appsearch.util.SchemaMigrationUtil; import android.os.Bundle; import android.os.ParcelableException; Loading Loading @@ -647,8 +646,8 @@ public final class AppSearchSession implements Closeable { new ArrayList<>(request.getSchemasNotDisplayedBySystem()), schemasPackageAccessibleBundles, request.isForceOverride(), mUserId, request.getVersion(), mUserId, new IAppSearchResultCallback.Stub() { public void onResult(AppSearchResult result) { executor.execute(() -> { Loading @@ -661,7 +660,7 @@ public final class AppSearchSession implements Closeable { // Throw exception if there is any deleted types or // incompatible types. That's the only case we swallowed // in the AppSearchImpl#setSchema(). checkDeletedAndIncompatible( SchemaMigrationUtil.checkDeletedAndIncompatible( setSchemaResponse.getDeletedTypes(), setSchemaResponse.getIncompatibleTypes()); } Loading Loading @@ -698,7 +697,7 @@ public final class AppSearchSession implements Closeable { workExecutor.execute(() -> { try { // Migration process // 1. Generate the current and the final version map. // 1. Validate and retrieve all active migrators. AndroidFuture<AppSearchResult<GetSchemaResponse>> getSchemaFuture = new AndroidFuture<>(); getSchema(callbackExecutor, getSchemaFuture::complete); Loading @@ -709,11 +708,18 @@ public final class AppSearchSession implements Closeable { return; } GetSchemaResponse getSchemaResponse = getSchemaResult.getResultValue(); Set<AppSearchSchema> currentSchemas = getSchemaResponse.getSchemas(); Map<String, Integer> currentVersionMap = SchemaMigrationUtil.buildVersionMap( currentSchemas, getSchemaResponse.getVersion()); Map<String, Integer> finalVersionMap = SchemaMigrationUtil.buildVersionMap( request.getSchemas(), request.getVersion()); int currentVersion = getSchemaResponse.getVersion(); int finalVersion = request.getVersion(); Map<String, Migrator> activeMigrators = SchemaMigrationUtil.getActiveMigrators( getSchemaResponse.getSchemas(), request.getMigrators(), currentVersion, finalVersion); // No need to trigger migration if no migrator is active. if (activeMigrators.isEmpty()) { setSchemaNoMigrations(request, schemaBundles, schemasPackageAccessibleBundles, callbackExecutor, callback); return; } // 2. SetSchema with forceOverride=false, to retrieve the list of // incompatible/deleted types. Loading @@ -725,8 +731,8 @@ public final class AppSearchSession implements Closeable { new ArrayList<>(request.getSchemasNotDisplayedBySystem()), schemasPackageAccessibleBundles, /*forceOverride=*/ false, mUserId, request.getVersion(), mUserId, new IAppSearchResultCallback.Stub() { public void onResult(AppSearchResult result) { setSchemaFuture.complete(result); Loading @@ -741,46 +747,27 @@ public final class AppSearchSession implements Closeable { SetSchemaResponse setSchemaResponse = new SetSchemaResponse(setSchemaResult.getResultValue()); // 1. If forceOverride is false, check that all incompatible types will be migrated. // 3. If forceOverride is false, check that all incompatible types will be migrated. // If some aren't we must throw an error, rather than proceeding and deleting those // types. if (!request.isForceOverride()) { Set<String> unmigratedTypes = SchemaMigrationUtil.getUnmigratedIncompatibleTypes( setSchemaResponse.getIncompatibleTypes(), request.getMigrators(), currentVersionMap, finalVersionMap); // check if there are any unmigrated types or deleted types. If there are, we // will throw an exception. // Since the force override is false, the schema will not have been set if there // are any incompatible or deleted types. checkDeletedAndIncompatible( setSchemaResponse.getDeletedTypes(), unmigratedTypes); } try (AppSearchMigrationHelper migrationHelper = new AppSearchMigrationHelper( mService, mUserId, currentVersionMap, finalVersionMap, mPackageName, mDatabaseName)) { Map<String, Migrator> migratorMap = request.getMigrators(); // 2. Trigger migration for all migrators. SchemaMigrationUtil.checkDeletedAndIncompatibleAfterMigration(setSchemaResponse, activeMigrators.keySet()); } try (AppSearchMigrationHelper migrationHelper = new AppSearchMigrationHelper( mService, mUserId, mPackageName, mDatabaseName, request.getSchemas())) { // 4. Trigger migration for all migrators. // TODO(b/177266929) trigger migration for all types together rather than // separately. Set<String> migratedTypes = new ArraySet<>(); for (Map.Entry<String, Migrator> entry : migratorMap.entrySet()) { String schemaType = entry.getKey(); Migrator migrator = entry.getValue(); if (SchemaMigrationUtil.shouldTriggerMigration( schemaType, migrator, currentVersionMap, finalVersionMap)) { migrationHelper.queryAndTransform(schemaType, migrator); migratedTypes.add(schemaType); } for (Map.Entry<String, Migrator> entry : activeMigrators.entrySet()) { migrationHelper.queryAndTransform(/*schemaType=*/ entry.getKey(), /*migrator=*/ entry.getValue(), currentVersion, finalVersion); } // 3. SetSchema a second time with forceOverride=true if the first attempted // 5. SetSchema a second time with forceOverride=true if the first attempted // failed. if (!setSchemaResponse.getIncompatibleTypes().isEmpty() || !setSchemaResponse.getDeletedTypes().isEmpty()) { Loading Loading @@ -809,13 +796,16 @@ public final class AppSearchSession implements Closeable { // error in the first setSchema call, all other errors will be thrown at // the first time. callbackExecutor.execute(() -> callback.accept( AppSearchResult.newFailedResult(setSchemaResult))); AppSearchResult.newFailedResult(setSchema2Result))); return; } } SetSchemaResponse.Builder responseBuilder = setSchemaResponse.toBuilder() .addMigratedTypes(migratedTypes); .addMigratedTypes(activeMigrators.keySet()); // 6. Put all the migrated documents into the index, now that the new schema is // set. AppSearchResult<SetSchemaResponse> putResult = migrationHelper.putMigratedDocuments(responseBuilder); callbackExecutor.execute(() -> callback.accept(putResult)); Loading @@ -826,17 +816,4 @@ public final class AppSearchSession implements Closeable { } }); } /** Checks the setSchema() call won't delete any types or has incompatible types. */ //TODO(b/177266929) move this method to util private void checkDeletedAndIncompatible(Set<String> deletedTypes, Set<String> incompatibleTypes) throws AppSearchException { if (!deletedTypes.isEmpty() || !incompatibleTypes.isEmpty()) { String newMessage = "Schema is incompatible." + "\n Deleted types: " + deletedTypes + "\n Incompatible types: " + incompatibleTypes; throw new AppSearchException(AppSearchResult.RESULT_INVALID_SCHEMA, newMessage); } } }
apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl +2 −1 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ interface IAppSearchManager { * packages. The value List contains PackageIdentifier Bundles. * @param forceOverride Whether to apply the new schema even if it is incompatible. All * incompatible documents will be deleted. * @param schemaVersion The overall schema version number of the request. * @param userId Id of the calling user * @param callback {@link IAppSearchResultCallback#onResult} will be called with an * {@link AppSearchResult}<{@link Bundle}>, where the value are Loading @@ -52,8 +53,8 @@ interface IAppSearchManager { in List<String> schemasNotDisplayedBySystem, in Map<String, List<Bundle>> schemasPackageAccessibleBundles, boolean forceOverride, in int userId, in int schemaVersion, in int userId, in IAppSearchResultCallback callback); /** Loading
apex/appsearch/framework/java/external/android/app/appsearch/util/SchemaMigrationUtil.java +52 −67 Original line number Diff line number Diff line Loading @@ -20,12 +20,11 @@ import android.annotation.NonNull; import android.app.appsearch.AppSearchResult; import android.app.appsearch.AppSearchSchema; import android.app.appsearch.Migrator; import android.app.appsearch.SetSchemaResponse; import android.app.appsearch.exceptions.AppSearchException; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.Set; Loading @@ -36,84 +35,70 @@ import java.util.Set; * @hide */ public final class SchemaMigrationUtil { private static final String TAG = "AppSearchMigrateUtil"; private SchemaMigrationUtil() {} /** * Finds out which incompatible schema type won't be migrated by comparing its current and final * version number. */ /** Returns all active {@link Migrator}s that need to be triggered in this migration. */ @NonNull public static Set<String> getUnmigratedIncompatibleTypes( @NonNull Set<String> incompatibleSchemaTypes, public static Map<String, Migrator> getActiveMigrators( @NonNull Set<AppSearchSchema> existingSchemas, @NonNull Map<String, Migrator> migrators, @NonNull Map<String, Integer> currentVersionMap, @NonNull Map<String, Integer> finalVersionMap) throws AppSearchException { Set<String> unmigratedSchemaTypes = new ArraySet<>(); for (String unmigratedSchemaType : incompatibleSchemaTypes) { Integer currentVersion = currentVersionMap.get(unmigratedSchemaType); Integer finalVersion = finalVersionMap.get(unmigratedSchemaType); if (currentVersion == null) { // impossible, we have done something wrong. throw new AppSearchException( AppSearchResult.RESULT_UNKNOWN_ERROR, "Cannot find the current version number for schema type: " + unmigratedSchemaType); int currentVersion, int finalVersion) { if (currentVersion == finalVersion) { return Collections.emptyMap(); } if (finalVersion == null) { // The schema doesn't exist in the SetSchemaRequest. unmigratedSchemaTypes.add(unmigratedSchemaType); continue; Set<String> existingTypes = new ArraySet<>(existingSchemas.size()); for (AppSearchSchema schema : existingSchemas) { existingTypes.add(schema.getSchemaType()); } // we don't have migrator or won't trigger migration for this schema type. Migrator migrator = migrators.get(unmigratedSchemaType); if (migrator == null || !migrator.shouldMigrate(currentVersion, finalVersion)) { unmigratedSchemaTypes.add(unmigratedSchemaType); Map<String, Migrator> activeMigrators = new ArrayMap<>(); for (Map.Entry<String, Migrator> entry : migrators.entrySet()) { // The device contains the source type, and we should trigger migration for the type. String schemaType = entry.getKey(); Migrator migrator = entry.getValue(); if (existingTypes.contains(schemaType) && migrator.shouldMigrate(currentVersion, finalVersion)) { activeMigrators.put(schemaType, migrator); } } return Collections.unmodifiableSet(unmigratedSchemaTypes); return activeMigrators; } /** * Triggers upgrade or downgrade migration for the given schema type if its version stored in * AppSearch is different with the version in the request. * * @return {@code True} if we trigger the migration for the given type. * Checks the setSchema() call won't delete any types or has incompatible types after all {@link * Migrator} has been triggered.. */ public static boolean shouldTriggerMigration( @NonNull String schemaType, @NonNull Migrator migrator, @NonNull Map<String, Integer> currentVersionMap, @NonNull Map<String, Integer> finalVersionMap) public static void checkDeletedAndIncompatibleAfterMigration( @NonNull SetSchemaResponse setSchemaResponse, @NonNull Set<String> activeMigrators) throws AppSearchException { Integer currentVersion = currentVersionMap.get(schemaType); Integer finalVersion = finalVersionMap.get(schemaType); if (currentVersion == null) { Log.d(TAG, "The SchemaType: " + schemaType + " not present in AppSearch."); return false; } if (finalVersion == null) { throw new AppSearchException( AppSearchResult.RESULT_INVALID_ARGUMENT, "Receive a migrator for schema type : " + schemaType + ", but the schema doesn't exist in the request."); } return migrator.shouldMigrate(currentVersion, finalVersion); Set<String> unmigratedIncompatibleTypes = new ArraySet<>(setSchemaResponse.getIncompatibleTypes()); unmigratedIncompatibleTypes.removeAll(activeMigrators); Set<String> unmigratedDeletedTypes = new ArraySet<>(setSchemaResponse.getDeletedTypes()); unmigratedDeletedTypes.removeAll(activeMigrators); // check if there are any unmigrated incompatible types or deleted types. If there // are, we will getActiveMigratorsthrow an exception. That's the only case we // swallowed in the AppSearchImpl#setSchema(). // Since the force override is false, the schema will not have been set if there are // any incompatible or deleted types. checkDeletedAndIncompatible(unmigratedDeletedTypes, unmigratedIncompatibleTypes); } /** Builds a Map of SchemaType and its version of given set of {@link AppSearchSchema}. */ //TODO(b/182620003) remove this method once support migrate to another type @NonNull public static Map<String, Integer> buildVersionMap( @NonNull Collection<AppSearchSchema> schemas, int version) { Map<String, Integer> currentVersionMap = new ArrayMap<>(schemas.size()); for (AppSearchSchema currentSchema : schemas) { currentVersionMap.put(currentSchema.getSchemaType(), version); /** Checks the setSchema() call won't delete any types or has incompatible types. */ public static void checkDeletedAndIncompatible( @NonNull Set<String> deletedTypes, @NonNull Set<String> incompatibleTypes) throws AppSearchException { if (deletedTypes.size() > 0 || incompatibleTypes.size() > 0) { String newMessage = "Schema is incompatible." + "\n Deleted types: " + deletedTypes + "\n Incompatible types: " + incompatibleTypes; throw new AppSearchException(AppSearchResult.RESULT_INVALID_SCHEMA, newMessage); } return currentVersionMap; } }
apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java +1 −1 Original line number Diff line number Diff line Loading @@ -169,8 +169,8 @@ public class AppSearchManagerService extends SystemService { @NonNull List<String> schemasNotDisplayedBySystem, @NonNull Map<String, List<Bundle>> schemasPackageAccessibleBundles, boolean forceOverride, @UserIdInt int userId, int schemaVersion, @UserIdInt int userId, @NonNull IAppSearchResultCallback callback) { Preconditions.checkNotNull(packageName); Preconditions.checkNotNull(databaseName); Loading