/* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.appsearch.appsindexer; import android.annotation.BinderThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.appsearch.AppSearchEnvironment; import android.app.appsearch.AppSearchEnvironmentFactory; import android.app.appsearch.exceptions.AppSearchException; import android.app.appsearch.util.LogUtil; import android.content.Context; import android.os.CancellationSignal; import android.os.UserHandle; import android.util.ArrayMap; import android.util.Log; import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.server.LocalManagerRegistry; import com.android.server.SystemService; import com.android.server.appsearch.indexer.IndexerLocalService; import java.io.File; import java.io.PrintWriter; import java.util.Map; import java.util.Objects; /** * Manages the per device-user AppOpenEventIndexer instance to index apps into AppSearch. * *

Unlike apps indexer and contacts indexer, this does not hook into package update, phone * unlock/stop, etc. It only runs when the maintenance job runs. * *

This class is thread-safe. * * @hide */ public final class AppOpenEventIndexerManagerService extends SystemService { private static final String TAG = "AppSearchAppOpenEventIn"; private final Context mContext; @VisibleForTesting final LocalService mLocalService; @VisibleForTesting @Nullable final Runnable mCallback; private final AppOpenEventIndexerConfig mAppOpenEventIndexerConfig; // Map of AppOpenEventIndexerUserInstances indexed by the UserHandle @GuardedBy("mAppOpenEventIndexersLocked") private final Map mAppOpenEventIndexersLocked = new ArrayMap<>(); /** Constructs a {@link AppOpenEventIndexerManagerService}. */ public AppOpenEventIndexerManagerService( @NonNull Context context, @NonNull AppOpenEventIndexerConfig appOpenEventIndexerConfig) { this(context, appOpenEventIndexerConfig, /* callback= */ null); } /** * Constructs a {@link AppOpenEventIndexerManagerService} for testing with a callback for * synchronization. */ @VisibleForTesting public AppOpenEventIndexerManagerService( @NonNull Context context, @NonNull AppOpenEventIndexerConfig appOpenEventIndexerConfig, @Nullable Runnable callback) { super(context); mContext = Objects.requireNonNull(context); mAppOpenEventIndexerConfig = Objects.requireNonNull(appOpenEventIndexerConfig); mCallback = callback; mLocalService = new LocalService(); } @Override public void onStart() { LocalManagerRegistry.addManager(LocalService.class, mLocalService); } /** Schedules the periodic update job for all users we have an instance for. */ @Override public void onUserUnlocking(@NonNull TargetUser user) { synchronized (mAppOpenEventIndexersLocked) { try { AppOpenEventIndexerUserInstance instance = getOrCreateUserInstance(user.getUserHandle()); if (instance != null) { instance.schedulePeriodicUpdate(); } } catch (RuntimeException e) { Slog.wtf(TAG, "AppOpenEventIndexerManagerService.onUserUnlocking() failed", e); } } } /** Handles user stopping by shutting down the instance for the user. */ @Override public void onUserStopping(@NonNull TargetUser user) { try { Objects.requireNonNull(user); UserHandle userHandle = user.getUserHandle(); synchronized (mAppOpenEventIndexersLocked) { AppOpenEventIndexerUserInstance instance = mAppOpenEventIndexersLocked.get(userHandle); if (instance != null) { mAppOpenEventIndexersLocked.remove(userHandle); try { instance.shutdown(); } catch (InterruptedException e) { Log.w( TAG, "Failed to shutdown app open event indexer for " + userHandle, e); } } } } catch (RuntimeException e) { Slog.wtf(TAG, "AppOpenEventIndexerManagerService.onUserStopping() failed ", e); } } /** Dumps AppOpenEventIndexer internal state for the user. */ @BinderThread public void dumpAppOpenEventIndexerForUser( @NonNull UserHandle userHandle, @NonNull PrintWriter pw) { try { Objects.requireNonNull(userHandle); Objects.requireNonNull(pw); synchronized (mAppOpenEventIndexersLocked) { AppOpenEventIndexerUserInstance instance = mAppOpenEventIndexersLocked.get(userHandle); if (instance != null) { instance.dump(pw); } else { pw.println("AppOpenEventIndexerUserInstance is not created for " + userHandle); } } } catch (RuntimeException e) { Slog.wtf( TAG, "AppOpenEventIndexerManagerService.dumpAppOpenEventIndexerForUser() failed ", e); } } /** Retrieves or creates the {@link AppOpenEventIndexerUserInstance} for the specified user. */ private AppOpenEventIndexerUserInstance getOrCreateUserInstance( @NonNull UserHandle userHandle) { synchronized (mAppOpenEventIndexersLocked) { Objects.requireNonNull(userHandle); AppOpenEventIndexerUserInstance instance = mAppOpenEventIndexersLocked.get(userHandle); if (instance == null) { if (LogUtil.INFO) { Log.i(TAG, "Creating AppOpenEventIndexerUserInstance for " + userHandle); } try { AppSearchEnvironment appSearchEnvironment = AppSearchEnvironmentFactory.getEnvironmentInstance(); Context userContext = appSearchEnvironment.createContextAsUser(mContext, userHandle); File appSearchDir = appSearchEnvironment.getAppSearchDir(userContext, userHandle); File appOpenEventDir = new File(appSearchDir, "app-open-events"); instance = AppOpenEventIndexerUserInstance.createInstance( userContext, appOpenEventDir, mAppOpenEventIndexerConfig); mAppOpenEventIndexersLocked.put(userHandle, instance); } catch (AppSearchException e) { Log.e( TAG, "Error while creating AppOpenEventIndexerUserInstance for " + userHandle, e); } } return instance; } } class LocalService implements IndexerLocalService { /** Runs an update for a user. */ @Override public void doUpdateForUser( @NonNull UserHandle userHandle, @Nullable CancellationSignal unused) { Objects.requireNonNull(userHandle); try { synchronized (mAppOpenEventIndexersLocked) { // Jobs are created onUserUnlocking, so unless the user is removed, this should // be non-null. AppOpenEventIndexerUserInstance instance = mAppOpenEventIndexersLocked.get(userHandle); if (instance != null) { if (mCallback != null) { instance.updateAsync(mCallback); } else { instance.updateAsync(); } } } } catch (RuntimeException e) { Slog.wtf(TAG, "AppOpenEventIndexerManagerService.doUpdateForUser() failed ", e); } } } }