1 /* 2 * Copyright 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.server.appsearch.external.localstorage.visibilitystore; 17 18 import static android.app.appsearch.AppSearchResult.RESULT_NOT_FOUND; 19 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.app.appsearch.AppSearchResult; 23 import android.app.appsearch.AppSearchSchema; 24 import android.app.appsearch.GenericDocument; 25 import android.app.appsearch.GetSchemaResponse; 26 import android.app.appsearch.InternalSetSchemaResponse; 27 import android.app.appsearch.VisibilityDocument; 28 import android.app.appsearch.VisibilityPermissionDocument; 29 import android.app.appsearch.exceptions.AppSearchException; 30 import android.util.ArrayMap; 31 import android.util.Log; 32 33 import com.android.server.appsearch.external.localstorage.AppSearchImpl; 34 import com.android.server.appsearch.external.localstorage.util.PrefixUtil; 35 36 import com.google.android.icing.proto.PersistType; 37 38 import java.util.Arrays; 39 import java.util.Collections; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.Objects; 43 import java.util.Set; 44 45 /** 46 * Stores all visibility settings for all databases that AppSearchImpl knows about. Persists the 47 * visibility settings and reloads them on initialization. 48 * 49 * <p>The VisibilityStore creates a {@link VisibilityDocument} for each schema. This document holds 50 * the visibility settings that apply to that schema. The VisibilityStore also creates a schema for 51 * these documents and has its own package and database so that its data doesn't interfere with any 52 * clients' data. It persists the document and schema through AppSearchImpl. 53 * 54 * <p>These visibility settings won't be used in AppSearch Jetpack, we only store them for clients 55 * to look up. 56 * 57 * @hide 58 */ 59 public class VisibilityStore { 60 private static final String TAG = "AppSearchVisibilityStor"; 61 /** 62 * These cannot have any of the special characters used by AppSearchImpl (e.g. {@code 63 * AppSearchImpl#PACKAGE_DELIMITER} or {@code AppSearchImpl#DATABASE_DELIMITER}. 64 */ 65 public static final String VISIBILITY_PACKAGE_NAME = "VS#Pkg"; 66 67 public static final String VISIBILITY_DATABASE_NAME = "VS#Db"; 68 69 /** 70 * Map of PrefixedSchemaType and VisibilityDocument stores visibility information for each 71 * schema type. 72 */ 73 private final Map<String, VisibilityDocument> mVisibilityDocumentMap = new ArrayMap<>(); 74 75 private final AppSearchImpl mAppSearchImpl; 76 VisibilityStore(@onNull AppSearchImpl appSearchImpl)77 public VisibilityStore(@NonNull AppSearchImpl appSearchImpl) throws AppSearchException { 78 mAppSearchImpl = Objects.requireNonNull(appSearchImpl); 79 80 GetSchemaResponse getSchemaResponse = 81 mAppSearchImpl.getSchema( 82 VISIBILITY_PACKAGE_NAME, 83 VISIBILITY_DATABASE_NAME, 84 new CallerAccess(/*callingPackageName=*/ VISIBILITY_PACKAGE_NAME)); 85 List<VisibilityDocumentV1> visibilityDocumentsV1s = null; 86 switch (getSchemaResponse.getVersion()) { 87 case VisibilityDocument.SCHEMA_VERSION_DOC_PER_PACKAGE: 88 // TODO (b/202194495) add VisibilityDocument in version 0 back instead of using 89 // GenericDocument. 90 List<GenericDocument> visibilityDocumentsV0s = 91 VisibilityStoreMigrationHelperFromV0.getVisibilityDocumentsInVersion0( 92 getSchemaResponse, mAppSearchImpl); 93 visibilityDocumentsV1s = 94 VisibilityStoreMigrationHelperFromV0.toVisibilityDocumentV1( 95 visibilityDocumentsV0s); 96 // fall through 97 case VisibilityDocument.SCHEMA_VERSION_DOC_PER_SCHEMA: 98 if (visibilityDocumentsV1s == null) { 99 // We need to read VisibilityDocument in Version 1 from AppSearch instead of 100 // taking from the above step. 101 visibilityDocumentsV1s = 102 VisibilityStoreMigrationHelperFromV1.getVisibilityDocumentsInVersion1( 103 mAppSearchImpl); 104 } 105 setLatestSchemaAndDocuments( 106 VisibilityStoreMigrationHelperFromV1.toVisibilityDocumentsV2( 107 visibilityDocumentsV1s)); 108 break; 109 case VisibilityDocument.SCHEMA_VERSION_LATEST: 110 Set<AppSearchSchema> existingVisibilitySchema = getSchemaResponse.getSchemas(); 111 if (existingVisibilitySchema.contains(VisibilityDocument.SCHEMA) 112 && existingVisibilitySchema.contains(VisibilityPermissionDocument.SCHEMA)) { 113 // The latest Visibility schema is in AppSearch, we must find our schema type. 114 // Extract all stored Visibility Document into mVisibilityDocumentMap. 115 loadVisibilityDocumentMap(); 116 } else { 117 // We must have a broken schema. Reset it to the latest version. 118 // Do NOT set forceOverride to be true here, see comment below. 119 InternalSetSchemaResponse internalSetSchemaResponse = 120 mAppSearchImpl.setSchema( 121 VISIBILITY_PACKAGE_NAME, 122 VISIBILITY_DATABASE_NAME, 123 Arrays.asList( 124 VisibilityDocument.SCHEMA, 125 VisibilityPermissionDocument.SCHEMA), 126 /*visibilityDocuments=*/ Collections.emptyList(), 127 /*forceOverride=*/ false, 128 /*version=*/ VisibilityDocument.SCHEMA_VERSION_LATEST, 129 /*setSchemaStatsBuilder=*/ null); 130 if (!internalSetSchemaResponse.isSuccess()) { 131 // If you hit problem here it means you made a incompatible change in 132 // Visibility Schema without update the version number. You should bump 133 // the version number and create a VisibilityStoreMigrationHelper which 134 // can analyse the different between the old version and the new version 135 // to migration user's visibility settings. 136 throw new AppSearchException( 137 AppSearchResult.RESULT_INTERNAL_ERROR, 138 "Fail to set the latest visibility schema to AppSearch. " 139 + "You may need to update the visibility schema version " 140 + "number."); 141 } 142 } 143 break; 144 default: 145 // We must did something wrong. 146 throw new AppSearchException( 147 AppSearchResult.RESULT_INTERNAL_ERROR, 148 "Found unsupported visibility version: " + getSchemaResponse.getVersion()); 149 } 150 } 151 152 /** 153 * Sets visibility settings for the given {@link VisibilityDocument}s. Any previous {@link 154 * VisibilityDocument}s with same prefixed schema type will be overwritten. 155 * 156 * @param prefixedVisibilityDocuments List of prefixed {@link VisibilityDocument} which contains 157 * schema type's visibility information. 158 * @throws AppSearchException on AppSearchImpl error. 159 */ setVisibility(@onNull List<VisibilityDocument> prefixedVisibilityDocuments)160 public void setVisibility(@NonNull List<VisibilityDocument> prefixedVisibilityDocuments) 161 throws AppSearchException { 162 Objects.requireNonNull(prefixedVisibilityDocuments); 163 // Save new setting. 164 for (int i = 0; i < prefixedVisibilityDocuments.size(); i++) { 165 // put VisibilityDocument to AppSearchImpl and mVisibilityDocumentMap. If there is a 166 // VisibilityDocument with same prefixed schema exists, it will be replaced by new 167 // VisibilityDocument in both AppSearch and memory look up map. 168 VisibilityDocument prefixedVisibilityDocument = prefixedVisibilityDocuments.get(i); 169 mAppSearchImpl.putDocument( 170 VISIBILITY_PACKAGE_NAME, 171 VISIBILITY_DATABASE_NAME, 172 prefixedVisibilityDocument, 173 /*sendChangeNotifications=*/ false, 174 /*logger=*/ null); 175 mVisibilityDocumentMap.put( 176 prefixedVisibilityDocument.getId(), prefixedVisibilityDocument); 177 } 178 // Now that the visibility document has been written. Persist the newly written data. 179 mAppSearchImpl.persistToDisk(PersistType.Code.LITE); 180 } 181 182 /** 183 * Remove the visibility setting for the given prefixed schema type from both AppSearch and 184 * memory look up map. 185 */ removeVisibility(@onNull Set<String> prefixedSchemaTypes)186 public void removeVisibility(@NonNull Set<String> prefixedSchemaTypes) 187 throws AppSearchException { 188 for (String prefixedSchemaType : prefixedSchemaTypes) { 189 if (mVisibilityDocumentMap.remove(prefixedSchemaType) != null) { 190 // The deleted schema is not all-default setting, we need to remove its 191 // VisibilityDocument from Icing. 192 try { 193 mAppSearchImpl.remove( 194 VISIBILITY_PACKAGE_NAME, 195 VISIBILITY_DATABASE_NAME, 196 VisibilityDocument.NAMESPACE, 197 prefixedSchemaType, 198 /*removeStatsBuilder=*/ null); 199 } catch (AppSearchException e) { 200 if (e.getResultCode() == RESULT_NOT_FOUND) { 201 // We are trying to remove this visibility setting, so it's weird but seems 202 // to be fine if we cannot find it. 203 Log.e( 204 TAG, 205 "Cannot find visibility document for " 206 + prefixedSchemaType 207 + " to remove."); 208 return; 209 } 210 throw e; 211 } 212 } 213 } 214 } 215 216 /** Gets the {@link VisibilityDocument} for the given prefixed schema type. */ 217 @Nullable getVisibility(@onNull String prefixedSchemaType)218 public VisibilityDocument getVisibility(@NonNull String prefixedSchemaType) { 219 return mVisibilityDocumentMap.get(prefixedSchemaType); 220 } 221 222 /** 223 * Loads all stored latest {@link VisibilityDocument} from Icing, and put them into {@link 224 * #mVisibilityDocumentMap}. 225 */ loadVisibilityDocumentMap()226 private void loadVisibilityDocumentMap() throws AppSearchException { 227 // Populate visibility settings set 228 List<String> cachedSchemaTypes = mAppSearchImpl.getAllPrefixedSchemaTypes(); 229 for (int i = 0; i < cachedSchemaTypes.size(); i++) { 230 String prefixedSchemaType = cachedSchemaTypes.get(i); 231 String packageName = PrefixUtil.getPackageName(prefixedSchemaType); 232 if (packageName.equals(VISIBILITY_PACKAGE_NAME)) { 233 continue; // Our own package. Skip. 234 } 235 236 VisibilityDocument visibilityDocument; 237 try { 238 // Note: We use the other clients' prefixed schema type as ids 239 visibilityDocument = 240 new VisibilityDocument( 241 mAppSearchImpl.getDocument( 242 VISIBILITY_PACKAGE_NAME, 243 VISIBILITY_DATABASE_NAME, 244 VisibilityDocument.NAMESPACE, 245 /*id=*/ prefixedSchemaType, 246 /*typePropertyPaths=*/ Collections.emptyMap())); 247 } catch (AppSearchException e) { 248 if (e.getResultCode() == RESULT_NOT_FOUND) { 249 // The schema has all default setting and we won't have a VisibilityDocument for 250 // it. 251 continue; 252 } 253 // Otherwise, this is some other error we should pass up. 254 throw e; 255 } 256 mVisibilityDocumentMap.put(prefixedSchemaType, visibilityDocument); 257 } 258 } 259 260 /** Set the latest version of {@link VisibilityDocument} and its schema to AppSearch. */ setLatestSchemaAndDocuments(@onNull List<VisibilityDocument> migratedDocuments)261 private void setLatestSchemaAndDocuments(@NonNull List<VisibilityDocument> migratedDocuments) 262 throws AppSearchException { 263 // The latest schema type doesn't exist yet. Add it. Set forceOverride true to 264 // delete old schema. 265 InternalSetSchemaResponse internalSetSchemaResponse = 266 mAppSearchImpl.setSchema( 267 VISIBILITY_PACKAGE_NAME, 268 VISIBILITY_DATABASE_NAME, 269 Arrays.asList( 270 VisibilityDocument.SCHEMA, VisibilityPermissionDocument.SCHEMA), 271 /*visibilityDocuments=*/ Collections.emptyList(), 272 /*forceOverride=*/ true, 273 /*version=*/ VisibilityDocument.SCHEMA_VERSION_LATEST, 274 /*setSchemaStatsBuilder=*/ null); 275 if (!internalSetSchemaResponse.isSuccess()) { 276 // Impossible case, we just set forceOverride to be true, we should never 277 // fail in incompatible changes. 278 throw new AppSearchException( 279 AppSearchResult.RESULT_INTERNAL_ERROR, 280 internalSetSchemaResponse.getErrorMessage()); 281 } 282 for (int i = 0; i < migratedDocuments.size(); i++) { 283 VisibilityDocument migratedDocument = migratedDocuments.get(i); 284 mVisibilityDocumentMap.put(migratedDocument.getId(), migratedDocument); 285 mAppSearchImpl.putDocument( 286 VISIBILITY_PACKAGE_NAME, 287 VISIBILITY_DATABASE_NAME, 288 migratedDocument, 289 /*sendChangeNotifications=*/ false, 290 /*logger=*/ null); 291 } 292 } 293 } 294