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