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