1 /* 2 * Copyright (C) 2019 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 17 package com.android.server.appsearch; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.app.appsearch.AppSearchEnvironmentFactory; 22 import android.app.appsearch.exceptions.AppSearchException; 23 import android.app.appsearch.util.LogUtil; 24 import android.content.Context; 25 import android.os.SystemClock; 26 import android.os.UserHandle; 27 import android.util.ArrayMap; 28 import android.util.Log; 29 30 import com.android.internal.annotations.GuardedBy; 31 import com.android.server.appsearch.external.localstorage.AppSearchImpl; 32 import com.android.server.appsearch.external.localstorage.stats.InitializeStats; 33 import com.android.server.appsearch.external.localstorage.visibilitystore.VisibilityChecker; 34 35 import java.io.File; 36 import java.util.ArrayList; 37 import java.util.List; 38 import java.util.Map; 39 import java.util.Objects; 40 41 /** 42 * Manages the lifecycle of AppSearch classes that should only be initialized once per device-user 43 * and make up the core of the AppSearch system. 44 * 45 * @hide 46 */ 47 public final class AppSearchUserInstanceManager { 48 private static final String TAG = "AppSearchUserInstanceMa"; 49 50 private static volatile AppSearchUserInstanceManager sAppSearchUserInstanceManager; 51 52 @GuardedBy("mInstancesLocked") 53 private final Map<UserHandle, AppSearchUserInstance> mInstancesLocked = new ArrayMap<>(); 54 55 @GuardedBy("mStorageInfoLocked") 56 private final Map<UserHandle, UserStorageInfo> mStorageInfoLocked = new ArrayMap<>(); 57 AppSearchUserInstanceManager()58 private AppSearchUserInstanceManager() {} 59 60 /** 61 * Gets an instance of AppSearchUserInstanceManager to be used. 62 * 63 * <p>If no instance has been initialized yet, a new one will be created. Otherwise, the 64 * existing instance will be returned. 65 */ 66 @NonNull getInstance()67 public static AppSearchUserInstanceManager getInstance() { 68 if (sAppSearchUserInstanceManager == null) { 69 synchronized (AppSearchUserInstanceManager.class) { 70 if (sAppSearchUserInstanceManager == null) { 71 sAppSearchUserInstanceManager = new AppSearchUserInstanceManager(); 72 } 73 } 74 } 75 return sAppSearchUserInstanceManager; 76 } 77 78 /** 79 * Gets an instance of AppSearchUserInstance for the given user, or creates one if none exists. 80 * 81 * <p>If no AppSearchUserInstance exists for the unlocked user, Icing will be initialized and 82 * one will be created. 83 * 84 * @param userContext Context of the user calling AppSearch 85 * @param userHandle The multi-user handle of the device user calling AppSearch 86 * @param config Flag manager for AppSearch 87 * @return An initialized {@link AppSearchUserInstance} for this user 88 */ 89 @NonNull getOrCreateUserInstance( @onNull Context userContext, @NonNull UserHandle userHandle, @NonNull ServiceAppSearchConfig config)90 public AppSearchUserInstance getOrCreateUserInstance( 91 @NonNull Context userContext, 92 @NonNull UserHandle userHandle, 93 @NonNull ServiceAppSearchConfig config) 94 throws AppSearchException { 95 Objects.requireNonNull(userContext); 96 Objects.requireNonNull(userHandle); 97 Objects.requireNonNull(config); 98 99 synchronized (mInstancesLocked) { 100 AppSearchUserInstance instance = mInstancesLocked.get(userHandle); 101 if (instance == null) { 102 instance = createUserInstance(userContext, userHandle, config); 103 mInstancesLocked.put(userHandle, instance); 104 } 105 return instance; 106 } 107 } 108 109 /** 110 * Closes and removes an {@link AppSearchUserInstance} for the given user. 111 * 112 * <p>All mutations applied to the underlying {@link AppSearchImpl} will be persisted to disk. 113 * 114 * @param userHandle The multi-user user handle of the user that need to be removed. 115 */ closeAndRemoveUserInstance(@onNull UserHandle userHandle)116 public void closeAndRemoveUserInstance(@NonNull UserHandle userHandle) { 117 Objects.requireNonNull(userHandle); 118 synchronized (mInstancesLocked) { 119 AppSearchUserInstance instance = mInstancesLocked.remove(userHandle); 120 if (instance != null) { 121 instance.getAppSearchImpl().close(); 122 } 123 } 124 synchronized (mStorageInfoLocked) { 125 mStorageInfoLocked.remove(userHandle); 126 } 127 } 128 129 /** 130 * Gets an {@link AppSearchUserInstance} for the given user. 131 * 132 * <p>This method should only be called by an initialized SearchSession, which has already 133 * called {@link #getOrCreateUserInstance} before. 134 * 135 * @param userHandle The multi-user handle of the device user calling AppSearch 136 * @return An initialized {@link AppSearchUserInstance} for this user 137 * @throws IllegalStateException if {@link AppSearchUserInstance} haven't created for the given 138 * user. 139 */ 140 @NonNull getUserInstance(@onNull UserHandle userHandle)141 public AppSearchUserInstance getUserInstance(@NonNull UserHandle userHandle) { 142 Objects.requireNonNull(userHandle); 143 synchronized (mInstancesLocked) { 144 AppSearchUserInstance instance = mInstancesLocked.get(userHandle); 145 if (instance == null) { 146 // Impossible scenario, user cannot call an uninitialized SearchSession, 147 // getInstance should always find the instance for the given user and never try to 148 // create an instance for this user again. 149 throw new IllegalStateException( 150 "AppSearchUserInstance has never been created for: " + userHandle); 151 } 152 return instance; 153 } 154 } 155 156 /** 157 * Returns the initialized {@link AppSearchUserInstance} for the given user, or {@code null} if 158 * no such instance exists. 159 * 160 * @param userHandle The multi-user handle of the device user calling AppSearch 161 */ 162 @Nullable getUserInstanceOrNull(@onNull UserHandle userHandle)163 public AppSearchUserInstance getUserInstanceOrNull(@NonNull UserHandle userHandle) { 164 Objects.requireNonNull(userHandle); 165 synchronized (mInstancesLocked) { 166 return mInstancesLocked.get(userHandle); 167 } 168 } 169 170 /** 171 * Gets an {@link UserStorageInfo} for the given user. 172 * 173 * @param userContext Context for the user. 174 * @param userHandle The multi-user handle of the device user 175 * @return An initialized {@link UserStorageInfo} for this user 176 */ 177 @NonNull getOrCreateUserStorageInfoInstance( @onNull Context userContext, @NonNull UserHandle userHandle)178 public UserStorageInfo getOrCreateUserStorageInfoInstance( 179 @NonNull Context userContext, @NonNull UserHandle userHandle) { 180 Objects.requireNonNull(userContext); 181 Objects.requireNonNull(userHandle); 182 synchronized (mStorageInfoLocked) { 183 UserStorageInfo userStorageInfo = mStorageInfoLocked.get(userHandle); 184 if (userStorageInfo == null) { 185 File appSearchDir = 186 AppSearchEnvironmentFactory.getEnvironmentInstance() 187 .getAppSearchDir(userContext, userHandle); 188 userStorageInfo = new UserStorageInfo(appSearchDir); 189 mStorageInfoLocked.put(userHandle, userStorageInfo); 190 } 191 return userStorageInfo; 192 } 193 } 194 195 /** 196 * Returns the list of all {@link UserHandle}s. 197 * 198 * <p>It can return an empty list if there is no {@link AppSearchUserInstance} created yet. 199 */ 200 @NonNull getAllUserHandles()201 public List<UserHandle> getAllUserHandles() { 202 synchronized (mInstancesLocked) { 203 return new ArrayList<>(mInstancesLocked.keySet()); 204 } 205 } 206 207 @NonNull createUserInstance( @onNull Context userContext, @NonNull UserHandle userHandle, @NonNull ServiceAppSearchConfig config)208 private AppSearchUserInstance createUserInstance( 209 @NonNull Context userContext, 210 @NonNull UserHandle userHandle, 211 @NonNull ServiceAppSearchConfig config) 212 throws AppSearchException { 213 long totalLatencyStartMillis = SystemClock.elapsedRealtime(); 214 InitializeStats.Builder initStatsBuilder = new InitializeStats.Builder(); 215 216 // Initialize the classes that make up AppSearchUserInstance 217 InternalAppSearchLogger logger = 218 AppSearchComponentFactory.createLoggerInstance(userContext, config); 219 220 File appSearchDir = 221 AppSearchEnvironmentFactory.getEnvironmentInstance() 222 .getAppSearchDir(userContext, userHandle); 223 File icingDir = new File(appSearchDir, "icing"); 224 if (LogUtil.INFO) { 225 Log.i(TAG, "Creating new AppSearch instance at: " + icingDir); 226 } 227 VisibilityChecker visibilityCheckerImpl = 228 AppSearchComponentFactory.createVisibilityCheckerInstance(userContext); 229 AppSearchImpl appSearchImpl = 230 AppSearchImpl.create( 231 icingDir, 232 config, 233 initStatsBuilder, 234 visibilityCheckerImpl, 235 new ServiceOptimizeStrategy(config)); 236 237 // Update storage info file 238 UserStorageInfo userStorageInfo = 239 getOrCreateUserStorageInfoInstance(userContext, userHandle); 240 userStorageInfo.updateStorageInfoFile(appSearchImpl); 241 242 initStatsBuilder.setTotalLatencyMillis( 243 (int) (SystemClock.elapsedRealtime() - totalLatencyStartMillis)); 244 logger.logStats(initStatsBuilder.build()); 245 246 return new AppSearchUserInstance(logger, appSearchImpl, visibilityCheckerImpl); 247 } 248 } 249