1 /* 2 * Copyright (C) 2024 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.appsindexer; 18 19 import android.annotation.BinderThread; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.app.appsearch.AppSearchEnvironment; 23 import android.app.appsearch.AppSearchEnvironmentFactory; 24 import android.app.appsearch.exceptions.AppSearchException; 25 import android.app.appsearch.util.LogUtil; 26 import android.content.Context; 27 import android.os.CancellationSignal; 28 import android.os.UserHandle; 29 import android.util.ArrayMap; 30 import android.util.Log; 31 import android.util.Slog; 32 33 import com.android.internal.annotations.GuardedBy; 34 import com.android.internal.annotations.VisibleForTesting; 35 import com.android.server.LocalManagerRegistry; 36 import com.android.server.SystemService; 37 import com.android.server.appsearch.indexer.IndexerLocalService; 38 39 import java.io.File; 40 import java.io.PrintWriter; 41 import java.util.Map; 42 import java.util.Objects; 43 44 /** 45 * Manages the per device-user AppOpenEventIndexer instance to index apps into AppSearch. 46 * 47 * <p>Unlike apps indexer and contacts indexer, this does not hook into package update, phone 48 * unlock/stop, etc. It only runs when the maintenance job runs. 49 * 50 * <p>This class is thread-safe. 51 * 52 * @hide 53 */ 54 public final class AppOpenEventIndexerManagerService extends SystemService { 55 private static final String TAG = "AppSearchAppOpenEventIn"; 56 private final Context mContext; 57 @VisibleForTesting final LocalService mLocalService; 58 @VisibleForTesting @Nullable final Runnable mCallback; 59 60 private final AppOpenEventIndexerConfig mAppOpenEventIndexerConfig; 61 62 // Map of AppOpenEventIndexerUserInstances indexed by the UserHandle 63 @GuardedBy("mAppOpenEventIndexersLocked") 64 private final Map<UserHandle, AppOpenEventIndexerUserInstance> mAppOpenEventIndexersLocked = 65 new ArrayMap<>(); 66 67 /** Constructs a {@link AppOpenEventIndexerManagerService}. */ AppOpenEventIndexerManagerService( @onNull Context context, @NonNull AppOpenEventIndexerConfig appOpenEventIndexerConfig)68 public AppOpenEventIndexerManagerService( 69 @NonNull Context context, 70 @NonNull AppOpenEventIndexerConfig appOpenEventIndexerConfig) { 71 this(context, appOpenEventIndexerConfig, /* callback= */ null); 72 } 73 74 /** 75 * Constructs a {@link AppOpenEventIndexerManagerService} for testing with a callback for 76 * synchronization. 77 */ 78 @VisibleForTesting AppOpenEventIndexerManagerService( @onNull Context context, @NonNull AppOpenEventIndexerConfig appOpenEventIndexerConfig, @Nullable Runnable callback)79 public AppOpenEventIndexerManagerService( 80 @NonNull Context context, 81 @NonNull AppOpenEventIndexerConfig appOpenEventIndexerConfig, 82 @Nullable Runnable callback) { 83 super(context); 84 mContext = Objects.requireNonNull(context); 85 mAppOpenEventIndexerConfig = Objects.requireNonNull(appOpenEventIndexerConfig); 86 mCallback = callback; 87 mLocalService = new LocalService(); 88 } 89 90 @Override onStart()91 public void onStart() { 92 LocalManagerRegistry.addManager(LocalService.class, mLocalService); 93 } 94 95 /** Schedules the periodic update job for all users we have an instance for. */ 96 @Override onUserUnlocking(@onNull TargetUser user)97 public void onUserUnlocking(@NonNull TargetUser user) { 98 synchronized (mAppOpenEventIndexersLocked) { 99 try { 100 AppOpenEventIndexerUserInstance instance = 101 getOrCreateUserInstance(user.getUserHandle()); 102 if (instance != null) { 103 instance.schedulePeriodicUpdate(); 104 } 105 } catch (RuntimeException e) { 106 Slog.wtf(TAG, "AppOpenEventIndexerManagerService.onUserUnlocking() failed", e); 107 } 108 } 109 } 110 111 /** Handles user stopping by shutting down the instance for the user. */ 112 @Override onUserStopping(@onNull TargetUser user)113 public void onUserStopping(@NonNull TargetUser user) { 114 try { 115 Objects.requireNonNull(user); 116 UserHandle userHandle = user.getUserHandle(); 117 synchronized (mAppOpenEventIndexersLocked) { 118 AppOpenEventIndexerUserInstance instance = 119 mAppOpenEventIndexersLocked.get(userHandle); 120 if (instance != null) { 121 mAppOpenEventIndexersLocked.remove(userHandle); 122 try { 123 instance.shutdown(); 124 } catch (InterruptedException e) { 125 Log.w( 126 TAG, 127 "Failed to shutdown app open event indexer for " + userHandle, 128 e); 129 } 130 } 131 } 132 } catch (RuntimeException e) { 133 Slog.wtf(TAG, "AppOpenEventIndexerManagerService.onUserStopping() failed ", e); 134 } 135 } 136 137 /** Dumps AppOpenEventIndexer internal state for the user. */ 138 @BinderThread dumpAppOpenEventIndexerForUser( @onNull UserHandle userHandle, @NonNull PrintWriter pw)139 public void dumpAppOpenEventIndexerForUser( 140 @NonNull UserHandle userHandle, @NonNull PrintWriter pw) { 141 try { 142 Objects.requireNonNull(userHandle); 143 Objects.requireNonNull(pw); 144 synchronized (mAppOpenEventIndexersLocked) { 145 AppOpenEventIndexerUserInstance instance = 146 mAppOpenEventIndexersLocked.get(userHandle); 147 if (instance != null) { 148 instance.dump(pw); 149 } else { 150 pw.println("AppOpenEventIndexerUserInstance is not created for " + userHandle); 151 } 152 } 153 } catch (RuntimeException e) { 154 Slog.wtf( 155 TAG, 156 "AppOpenEventIndexerManagerService.dumpAppOpenEventIndexerForUser() failed ", 157 e); 158 } 159 } 160 161 /** Retrieves or creates the {@link AppOpenEventIndexerUserInstance} for the specified user. */ getOrCreateUserInstance( @onNull UserHandle userHandle)162 private AppOpenEventIndexerUserInstance getOrCreateUserInstance( 163 @NonNull UserHandle userHandle) { 164 synchronized (mAppOpenEventIndexersLocked) { 165 Objects.requireNonNull(userHandle); 166 AppOpenEventIndexerUserInstance instance = mAppOpenEventIndexersLocked.get(userHandle); 167 168 if (instance == null) { 169 if (LogUtil.INFO) { 170 Log.i(TAG, "Creating AppOpenEventIndexerUserInstance for " + userHandle); 171 } 172 try { 173 AppSearchEnvironment appSearchEnvironment = 174 AppSearchEnvironmentFactory.getEnvironmentInstance(); 175 Context userContext = 176 appSearchEnvironment.createContextAsUser(mContext, userHandle); 177 File appSearchDir = 178 appSearchEnvironment.getAppSearchDir(userContext, userHandle); 179 File appOpenEventDir = new File(appSearchDir, "app-open-events"); 180 instance = 181 AppOpenEventIndexerUserInstance.createInstance( 182 userContext, appOpenEventDir, mAppOpenEventIndexerConfig); 183 mAppOpenEventIndexersLocked.put(userHandle, instance); 184 } catch (AppSearchException e) { 185 Log.e( 186 TAG, 187 "Error while creating AppOpenEventIndexerUserInstance for " 188 + userHandle, 189 e); 190 } 191 } 192 return instance; 193 } 194 } 195 196 class LocalService implements IndexerLocalService { 197 /** Runs an update for a user. */ 198 @Override doUpdateForUser( @onNull UserHandle userHandle, @Nullable CancellationSignal unused)199 public void doUpdateForUser( 200 @NonNull UserHandle userHandle, @Nullable CancellationSignal unused) { 201 202 Objects.requireNonNull(userHandle); 203 try { 204 synchronized (mAppOpenEventIndexersLocked) { 205 // Jobs are created onUserUnlocking, so unless the user is removed, this should 206 // be non-null. 207 AppOpenEventIndexerUserInstance instance = 208 mAppOpenEventIndexersLocked.get(userHandle); 209 if (instance != null) { 210 if (mCallback != null) { 211 instance.updateAsync(mCallback); 212 } else { 213 instance.updateAsync(); 214 } 215 } 216 } 217 } catch (RuntimeException e) { 218 Slog.wtf(TAG, "AppOpenEventIndexerManagerService.doUpdateForUser() failed ", e); 219 } 220 } 221 } 222 } 223