Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f1f376fc authored by Pinyao Ting's avatar Pinyao Ting
Browse files

Shortcut integration with AppSearch (Part 9)

1. Close AppSearchSession after use.

When the device is shutdown with open AppSearchSession, if the data is
not properly persisted to disk, it would lead to a recovery upon reboot,
which could hurt boot performance.

We cannot rely on shutdown signal to close existing session because the
broadcast is not guranteed. Since session creation is light-weight, it
is more performant to simply close the session everytime after use.

2. Refrain from calling setup schema on boot.

To improve the performance of device start up, we want to avoid calling
AppSearchSession#setSchema unnecessarily since it could hurt performance.
In this CL we saved the version of the schema in the xml file which can
serve as an indicator of whether schema setup is required.

Bug: 151359749
Test: atest ShortcutManagerTest1 ShortcutManagerTest2
    ShortcutManagerTest3 ShortcutManagerTest4 ShortcutManagerTest5
    ShortcutManagerTest6 ShortcutManagerTest7 ShortcutManagerTest8
    ShortcutManagerTest9 ShortcutManagerTest10 ShortcutManagerTest11
    ShortcutManagerTest12
Test: atest CtsShortcutManagerTestCases
Change-Id: I2d6b66cef0c152ad99611dec3dee32a473bef4a9
parent ba25bacc
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -52,6 +52,7 @@ public class AppSearchShortcutInfo extends GenericDocument {

    /** The name of the schema type for {@link ShortcutInfo} documents.*/
    public static final String SCHEMA_TYPE = "Shortcut";
    public static final int SCHEMA_VERSION = 1;

    public static final String KEY_ACTIVITY = "activity";
    public static final String KEY_SHORT_LABEL = "shortLabel";
+53 −73
Original line number Diff line number Diff line
@@ -56,7 +56,6 @@ import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
import android.util.Xml;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.infra.AndroidFuture;
import com.android.internal.util.ArrayUtils;
@@ -116,6 +115,7 @@ class ShortcutPackage extends ShortcutPackageItem {
    private static final String ATTR_NAME = "name";
    private static final String ATTR_CALL_COUNT = "call-count";
    private static final String ATTR_LAST_RESET = "last-reset";
    private static final String ATTR_SCHEMA_VERSON = "schema-version";
    private static final String ATTR_ID = "id";
    private static final String ATTR_ACTIVITY = "activity";
    private static final String ATTR_TITLE = "title";
@@ -188,8 +188,7 @@ class ShortcutPackage extends ShortcutPackageItem {
     */
    private final Map<String, PackageIdentifier> mPackageIdentifiers = new ArrayMap<>(0);

    @GuardedBy("mLock")
    private AppSearchSession mAppSearchSession;
    private boolean mIsInitilized;

    private ShortcutPackage(ShortcutUser shortcutUser,
            int packageUserId, String packageName, ShortcutPackageInfo spi) {
@@ -1685,6 +1684,15 @@ class ShortcutPackage extends ShortcutPackageItem {
        ShortcutService.writeAttr(out, ATTR_NAME, getPackageName());
        ShortcutService.writeAttr(out, ATTR_CALL_COUNT, mApiCallCount);
        ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime);
        if (!forBackup) {
            /**
             * Schema version should not be included in the backup because:
             * 1. Schemas in AppSearch are created from scratch on new device
             * 2. Shortcuts are restored from xml file (as opposed to from AppSearch) on new device
             */
            ShortcutService.writeAttr(out, ATTR_SCHEMA_VERSON, (mIsInitilized)
                    ? AppSearchShortcutInfo.SCHEMA_VERSION : 0);
        }
        getPackageInfo().saveToXml(mShortcutUser.mService, out, forBackup);

        if (forBackup) {
@@ -1882,6 +1890,7 @@ class ShortcutPackage extends ShortcutPackageItem {
        final ShortcutPackage ret = new ShortcutPackage(shortcutUser,
                shortcutUser.getUserId(), packageName);

        ret.mIsInitilized = ShortcutService.parseIntAttribute(parser, ATTR_SCHEMA_VERSON, 0) > 0;
        ret.mApiCallCount =
                ShortcutService.parseIntAttribute(parser, ATTR_CALL_COUNT);
        ret.mLastResetTime =
@@ -2231,7 +2240,8 @@ class ShortcutPackage extends ShortcutPackageItem {
        } else {
            mPackageIdentifiers.remove(packageName);
        }
        resetAppSearch(session -> AndroidFuture.completedFuture(true));
        awaitInAppSearch(true, "Update visibility",
                session -> AndroidFuture.completedFuture(true));
    }

    void mutateShortcut(@NonNull final String id, @Nullable final ShortcutInfo shortcut,
@@ -2262,8 +2272,7 @@ class ShortcutPackage extends ShortcutPackageItem {
            // No need to invoke AppSearch when there's nothing to save.
            return;
        }
        ConcurrentUtils.waitForFutureNoInterrupt(
                runInAppSearch(session -> {
        awaitInAppSearch("Saving shortcut", session -> {
            final AndroidFuture<Boolean> future = new AndroidFuture<>();
            session.put(new PutDocumentsRequest.Builder()
                            .addGenericDocuments(
@@ -2282,8 +2291,7 @@ class ShortcutPackage extends ShortcutPackageItem {
                        future.complete(true);
                    });
            return future;
                }),
                "Saving shortcut");
        });
    }

    /**
@@ -2447,68 +2455,40 @@ class ShortcutPackage extends ShortcutPackageItem {
    private <T> T awaitInAppSearch(
            @NonNull final String description,
            @NonNull final Function<AppSearchSession, CompletableFuture<T>> cb) {
        return ConcurrentUtils.waitForFutureNoInterrupt(runInAppSearch(cb), description);
        return awaitInAppSearch(false, description, cb);
    }

    @Nullable
    private <T> CompletableFuture<T> runInAppSearch(
    private <T> T awaitInAppSearch(
            final boolean forceReset,
            @NonNull final String description,
            @NonNull final Function<AppSearchSession, CompletableFuture<T>> cb) {
        synchronized (mLock) {
            final StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
            try {
            final long callingIdentity = Binder.clearCallingIdentity();
            final AppSearchManager.SearchContext searchContext =
                    new AppSearchManager.SearchContext.Builder(getPackageName()).build();
            try (AppSearchSession session = ConcurrentUtils.waitForFutureNoInterrupt(
                    mShortcutUser.getAppSearch(searchContext), "Resetting app search")) {
                StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                        .detectAll()
                        .penaltyLog() // TODO: change this to penaltyDeath to fix the call-site
                        .build());
                if (mAppSearchSession != null) {
                    final long callingIdentity = Binder.clearCallingIdentity();
                    try {
                        return AndroidFuture.supply(() -> mAppSearchSession).thenCompose(cb);
                    } finally {
                        Binder.restoreCallingIdentity(callingIdentity);
                    }
                } else {
                    return resetAppSearch(cb);
                }
            } finally {
                StrictMode.setThreadPolicy(oldPolicy);
            }
        }
                if (!mIsInitilized || forceReset) {
                    ConcurrentUtils.waitForFutureNoInterrupt(
                            setupSchema(session), "Setting up schema");
                }

    private <T> CompletableFuture<T> resetAppSearch(
            @NonNull final Function<AppSearchSession, CompletableFuture<T>> cb) {
        final long callingIdentity = Binder.clearCallingIdentity();
        final AppSearchManager.SearchContext searchContext =
                new AppSearchManager.SearchContext.Builder(getPackageName()).build();
        final AppSearchSession session;
        try {
            session = ConcurrentUtils.waitForFutureNoInterrupt(
                    mShortcutUser.getAppSearch(searchContext), "Resetting app search");
            ConcurrentUtils.waitForFutureNoInterrupt(setupSchema(session), "Setting up schema");
            mAppSearchSession = session;
            return cb.apply(mAppSearchSession);
                mIsInitilized = true;
                return ConcurrentUtils.waitForFutureNoInterrupt(cb.apply(session), description);
            } catch (Exception e) {
                Slog.e(TAG, "Failed to initiate app search for shortcut package "
                        + getPackageName() + " user " + mShortcutUser.getUserId(), e);
            return AndroidFuture.completedFuture(null);
        } finally {
            Binder.restoreCallingIdentity(callingIdentity);
        }
    }

    void closeAppSearchSession() {
        synchronized (mLock) {
            if (mAppSearchSession != null) {
                final long callingIdentity = Binder.clearCallingIdentity();
                try {
                    mAppSearchSession.close();
                return null;
            } finally {
                Binder.restoreCallingIdentity(callingIdentity);
                StrictMode.setThreadPolicy(oldPolicy);
            }
        }
            mAppSearchSession = null;
        }
    }

    @NonNull
+1 −4
Original line number Diff line number Diff line
@@ -1051,10 +1051,7 @@ public class ShortcutService extends IShortcutService.Stub {
            file.failWrite(os);
        }

        final ShortcutUser user = getUserShortcutsLocked(userId);
        // Close AppSearchSession to flush pending changes.
        user.forAllPackages(ShortcutPackage::closeAppSearchSession);
        user.logSharingShortcutStats(mMetricsLogger);
        getUserShortcutsLocked(userId).logSharingShortcutStats(mMetricsLogger);
    }

    @GuardedBy("mLock")