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

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

Shortcut integration with AppSearch (Part 8)

Reduce search space in AppSearch with following changes:
1. Introduce inverted flag upon saving document.
2. Implement AppSearch queries for more general cases.

Bug: 151359749
Test: atest ShortcutManagerTest1 ShortcutManagerTest2
    ShortcutManagerTest3 ShortcutManagerTest4 ShortcutManagerTest5
    ShortcutManagerTest6 ShortcutManagerTest7 ShortcutManagerTest8
    ShortcutManagerTest9 ShortcutManagerTest10 ShortcutManagerTest11
    ShortcutManagerTest12
Test: atest CtsShortcutManagerTestCases
Change-Id: I94ec6a2b04c83ef3689c65d63741340b01c9ff50
parent 221060b2
Loading
Loading
Loading
Loading
+88 −41
Original line number Diff line number Diff line
@@ -213,42 +213,55 @@ public class AppSearchShortcutInfo extends GenericDocument {

            ).build();

    private static final String IS_DYNAMIC = "isDynamic";
    private static final String NOT_DYNAMIC = "notDynamic";
    private static final String IS_PINNED = "isPinned";
    private static final String NOT_PINNED = "notPinned";
    private static final String HAS_ICON_RES = "hasIconRes";
    private static final String NO_ICON_RES = "noIconRes";
    private static final String HAS_ICON_FILE = "hasIconFile";
    private static final String NO_ICON_FILE = "noIconFile";
    private static final String IS_KEY_FIELD_ONLY = "isKeyFieldOnly";
    private static final String NOT_KEY_FIELD_ONLY = "notKeyFieldOnly";
    private static final String IS_MANIFEST = "isManifest";
    private static final String NOT_MANIFEST = "notManifest";
    private static final String IS_DISABLED = "isDisabled";
    private static final String NOT_DISABLED = "notDisabled";
    private static final String ARE_STRINGS_RESOLVED = "areStringsResolved";
    private static final String NOT_STRINGS_RESOLVED = "notStringsResolved";
    private static final String IS_IMMUTABLE = "isImmutable";
    private static final String NOT_IMMUTABLE = "notImmutable";
    private static final String HAS_ADAPTIVE_BITMAP = "hasAdaptiveBitmap";
    private static final String NO_ADAPTIVE_BITMAP = "noAdaptiveBitmap";
    private static final String IS_RETURNED_BY_SERVICE = "isReturnedByService";
    private static final String NOT_RETURNED_BY_SERVICE = "notReturnedByService";
    private static final String HAS_ICON_FILE_PENDING_SAVE = "hasIconFilePendingSave";
    private static final String NO_ICON_FILE_PENDING_SAVE = "noIconFilePendingSave";
    private static final String IS_SHADOW = "isShadow";
    private static final String NOT_SHADOW = "notShadow";
    private static final String IS_LONG_LIVED = "isLongLived";
    private static final String NOT_LONG_LIVED = "notLongLived";
    private static final String HAS_ICON_URI = "hasIconUri";
    private static final String NO_ICON_URI = "noIconUri";
    private static final String IS_CACHED_NOTIFICATION = "isCachedNotification";
    private static final String NOT_CACHED_NOTIFICATION = "notCachedNotification";
    private static final String IS_CACHED_BUBBLE = "isCachedBubble";
    private static final String NOT_CACHED_BUBBLE = "notCachedBubble";
    private static final String IS_CACHED_PEOPLE_TITLE = "isCachedPeopleTitle";
    private static final String NOT_CACHED_PEOPLE_TITLE = "notCachedPeopleTitle";
    /**
     * The string representation of every flag within {@link ShortcutInfo}. Note that its value
     * needs to be camelCase since AppSearch's tokenizer will break the word when it sees
     * underscore.
     */
    private static final String IS_DYNAMIC = "Dyn";
    private static final String NOT_DYNAMIC = "nDyn";
    private static final String IS_PINNED = "Pin";
    private static final String NOT_PINNED = "nPin";
    private static final String HAS_ICON_RES = "IcR";
    private static final String NO_ICON_RES = "nIcR";
    private static final String HAS_ICON_FILE = "IcF";
    private static final String NO_ICON_FILE = "nIcF";
    private static final String IS_KEY_FIELD_ONLY = "Key";
    private static final String NOT_KEY_FIELD_ONLY = "nKey";
    private static final String IS_MANIFEST = "Man";
    private static final String NOT_MANIFEST = "nMan";
    private static final String IS_DISABLED = "Dis";
    private static final String NOT_DISABLED = "nDis";
    private static final String ARE_STRINGS_RESOLVED = "Str";
    private static final String NOT_STRINGS_RESOLVED = "nStr";
    private static final String IS_IMMUTABLE = "Im";
    private static final String NOT_IMMUTABLE = "nIm";
    private static final String HAS_ADAPTIVE_BITMAP = "IcA";
    private static final String NO_ADAPTIVE_BITMAP = "nIcA";
    private static final String IS_RETURNED_BY_SERVICE = "Rets";
    private static final String NOT_RETURNED_BY_SERVICE = "nRets";
    private static final String HAS_ICON_FILE_PENDING_SAVE = "Pens";
    private static final String NO_ICON_FILE_PENDING_SAVE = "nPens";
    private static final String IS_SHADOW = "Sdw";
    private static final String NOT_SHADOW = "nSdw";
    private static final String IS_LONG_LIVED = "Liv";
    private static final String NOT_LONG_LIVED = "nLiv";
    private static final String HAS_ICON_URI = "IcU";
    private static final String NO_ICON_URI = "nIcU";
    private static final String IS_CACHED_NOTIFICATION = "CaN";
    private static final String NOT_CACHED_NOTIFICATION = "nCaN";
    private static final String IS_CACHED_BUBBLE = "CaB";
    private static final String NOT_CACHED_BUBBLE = "nCaB";
    private static final String IS_CACHED_PEOPLE_TITLE = "CaPT";
    private static final String NOT_CACHED_PEOPLE_TITLE = "nCaPT";

    /**
     * Following flags are not store within ShortcutInfo, but book-keeping states to reduce search
     * space when performing queries against AppSearch.
     */
    private static final String HAS_BITMAP_PATH = "hBiP";
    private static final String HAS_STRING_RESOURCE = "hStr";
    private static final String HAS_NON_ZERO_RANK = "hRan";

    public static final String QUERY_IS_DYNAMIC = KEY_FLAGS + ":" + IS_DYNAMIC;
    public static final String QUERY_IS_NOT_DYNAMIC = KEY_FLAGS + ":" + NOT_DYNAMIC;
@@ -272,7 +285,7 @@ public class AppSearchShortcutInfo extends GenericDocument {
    public static final String QUERY_IS_NOT_FLOATING =
            "((" + QUERY_IS_NOT_PINNED + " " + QUERY_IS_NOT_CACHED + ") OR "
                    + QUERY_IS_DYNAMIC + " OR " + QUERY_IS_MANIFEST + ")";
    public static final String QUERY_IS_NOT_DISABLED_DUE_TO_RESTORE =
    public static final String QUERY_IS_VISIBLE_TO_PUBLISHER =
            "(" + KEY_DISABLED_REASON + ":" + ShortcutInfo.DISABLED_REASON_NOT_DISABLED
                    + " OR " + KEY_DISABLED_REASON + ":"
                    + ShortcutInfo.DISABLED_REASON_BY_APP
@@ -282,6 +295,20 @@ public class AppSearchShortcutInfo extends GenericDocument {
                    + ShortcutInfo.DISABLED_REASON_UNKNOWN + ")";
    public static final String QUERY_DISABLED_REASON_VERSION_LOWER =
            KEY_DISABLED_REASON + ":" + ShortcutInfo.DISABLED_REASON_VERSION_LOWER;
    public static final String QUERY_IS_NON_MANIFEST_VISIBLE =
            "(" + QUERY_IS_NOT_MANIFEST + " " + QUERY_IS_VISIBLE_TO_PUBLISHER + " ("
                    + QUERY_IS_PINNED + " OR " + QUERY_IS_CACHED + " OR " + QUERY_IS_DYNAMIC + "))";
    public static final String QUERY_IS_VISIBLE_CACHED_OR_PINNED =
            "(" + QUERY_IS_VISIBLE_TO_PUBLISHER + " " + QUERY_IS_DYNAMIC
                    + " (" + QUERY_IS_CACHED + " OR " + QUERY_IS_PINNED + "))";
    public static final String QUERY_IS_VISIBLE_PINNED_ONLY =
            "(" + QUERY_IS_VISIBLE_TO_PUBLISHER + " " + QUERY_IS_PINNED + " " + QUERY_IS_NOT_CACHED
            + " " + QUERY_IS_NOT_DYNAMIC + " " + QUERY_IS_NOT_MANIFEST + ")";
    public static final String QUERY_HAS_BITMAP_PATH = KEY_FLAGS + ":" + HAS_BITMAP_PATH;
    public static final String QUERY_HAS_STRING_RESOURCE = KEY_FLAGS + ":" + HAS_STRING_RESOURCE;
    public static final String QUERY_HAS_NON_ZERO_RANK = KEY_FLAGS + ":" + HAS_NON_ZERO_RANK;
    public static final String QUERY_IS_FLOATING_AND_HAS_RANK =
            "(" + QUERY_IS_FLOATING + " " + QUERY_HAS_NON_ZERO_RANK + ")";

    public AppSearchShortcutInfo(@NonNull GenericDocument document) {
        super(document);
@@ -420,6 +447,9 @@ public class AppSearchShortcutInfo extends GenericDocument {
    @VisibleForTesting
    public static class Builder extends GenericDocument.Builder<Builder> {

        private List<String> mFlags = new ArrayList<>(1);
        private boolean mHasStringResource = false;

        public Builder(String packageName, String id) {
            super(/*namespace=*/ packageName, id, SCHEMA_TYPE);
        }
@@ -462,8 +492,11 @@ public class AppSearchShortcutInfo extends GenericDocument {
         * @hide
         */
        @NonNull
        public Builder setShortLabelResId(@Nullable final int shortLabelResId) {
        public Builder setShortLabelResId(final int shortLabelResId) {
            setPropertyLong(KEY_SHORT_LABEL_RES_ID, shortLabelResId);
            if (shortLabelResId != 0) {
                mHasStringResource = true;
            }
            return this;
        }

@@ -493,8 +526,11 @@ public class AppSearchShortcutInfo extends GenericDocument {
         * @hide
         */
        @NonNull
        public Builder setLongLabelResId(@Nullable final int longLabelResId) {
        public Builder setLongLabelResId(final int longLabelResId) {
            setPropertyLong(KEY_LONG_LABEL_RES_ID, longLabelResId);
            if (longLabelResId != 0) {
                mHasStringResource = true;
            }
            return this;
        }

@@ -524,8 +560,11 @@ public class AppSearchShortcutInfo extends GenericDocument {
         * @hide
         */
        @NonNull
        public Builder setDisabledMessageResId(@Nullable final int disabledMessageResId) {
        public Builder setDisabledMessageResId(final int disabledMessageResId) {
            setPropertyLong(KEY_DISABLED_MESSAGE_RES_ID, disabledMessageResId);
            if (disabledMessageResId != 0) {
                mHasStringResource = true;
            }
            return this;
        }

@@ -623,6 +662,9 @@ public class AppSearchShortcutInfo extends GenericDocument {
        public Builder setRank(final int rank) {
            Preconditions.checkArgument((0 <= rank), "Rank cannot be negative");
            setPropertyString(KEY_RANK, String.valueOf(rank));
            if (rank != 0) {
                mFlags.add(HAS_NON_ZERO_RANK);
            }
            return this;
        }

@@ -652,7 +694,7 @@ public class AppSearchShortcutInfo extends GenericDocument {
        public Builder setFlags(@ShortcutInfo.ShortcutFlags final int flags) {
            final String[] flagArray = flattenFlags(flags);
            if (flagArray != null && flagArray.length > 0) {
                setPropertyString(KEY_FLAGS, flagArray);
                mFlags.addAll(Arrays.asList(flagArray));
            }
            return this;
        }
@@ -682,6 +724,7 @@ public class AppSearchShortcutInfo extends GenericDocument {
        public Builder setBitmapPath(@Nullable final String bitmapPath) {
            if (!TextUtils.isEmpty(bitmapPath)) {
                setPropertyString(KEY_BITMAP_PATH, bitmapPath);
                mFlags.add(HAS_BITMAP_PATH);
            }
            return this;
        }
@@ -710,6 +753,10 @@ public class AppSearchShortcutInfo extends GenericDocument {
        @NonNull
        @Override
        public AppSearchShortcutInfo build() {
            if (mHasStringResource) {
                mFlags.add(HAS_STRING_RESOURCE);
            }
            setPropertyString(KEY_FLAGS, mFlags.toArray(new String[0]));
            return new AppSearchShortcutInfo(super.build());
        }
    }
+7 −1
Original line number Diff line number Diff line
@@ -148,7 +148,13 @@ public final class ShortcutInfo implements Parcelable {
    public static final int FLAG_CACHED_ALL =
            FLAG_CACHED_NOTIFICATIONS | FLAG_CACHED_BUBBLES | FLAG_CACHED_PEOPLE_TILE;

    /** @hide */
    /**
     * Bitmask-based flags indicating different states associated with the shortcut. Note that if
     * new value is added here, consider adding also the corresponding string representation and
     * queries in {@link AppSearchShortcutInfo}.
     *
     * @hide
     */
    @IntDef(flag = true, prefix = { "FLAG_" }, value = {
            FLAG_DYNAMIC,
            FLAG_PINNED,
+131 −82
Original line number Diff line number Diff line
@@ -262,8 +262,9 @@ class ShortcutPackage extends ShortcutPackageItem {
     * Note this does *not* provide a correct view to the calling launcher.
     */
    @Nullable
    public ShortcutInfo findShortcutById(String id) {
        final List<ShortcutInfo> ret = getShortcutById(id);
    public ShortcutInfo findShortcutById(@Nullable final String id) {
        if (id == null) return null;
        final List<ShortcutInfo> ret = getShortcutById(Collections.singleton(id));
        return ret.isEmpty() ? null : ret.get(0);
    }

@@ -491,7 +492,7 @@ class ShortcutPackage extends ShortcutPackageItem {
        } else {
            query = String.format("%s %s",
                    AppSearchShortcutInfo.QUERY_IS_DYNAMIC,
                    AppSearchShortcutInfo.QUERY_IS_NOT_DISABLED_DUE_TO_RESTORE);
                    AppSearchShortcutInfo.QUERY_IS_VISIBLE_TO_PUBLISHER);
        }
        final boolean[] changed = new boolean[1];
        forEachShortcutMutateIf(query, si -> {
@@ -662,7 +663,7 @@ class ShortcutPackage extends ShortcutPackageItem {
            pinnedShortcuts.addAll(pinned);
        });
        // Then, update the pinned state if necessary.
        final List<ShortcutInfo> pinned = getShortcutById(pinnedShortcuts.toArray(new String[0]));
        final List<ShortcutInfo> pinned = getShortcutById(pinnedShortcuts);
        pinned.forEach(si -> {
            if (!si.isPinned()) {
                si.addFlags(ShortcutInfo.FLAG_PINNED);
@@ -765,9 +766,9 @@ class ShortcutPackage extends ShortcutPackageItem {
    /**
     * Find all shortcuts that match {@code query}.
     */
    public void findAll(@NonNull List<ShortcutInfo> result,
            @Nullable Predicate<ShortcutInfo> query, int cloneFlag) {
        findAll(result, query, cloneFlag, null, 0, /*getPinnedByAnyLauncher=*/ false);
    public void findAll(@NonNull List<ShortcutInfo> result, @Nullable String query,
            @Nullable Predicate<ShortcutInfo> filter, int cloneFlag) {
        findAll(result, query, filter, cloneFlag, null, 0, /*getPinnedByAnyLauncher=*/ false);
    }

    /**
@@ -778,7 +779,7 @@ class ShortcutPackage extends ShortcutPackageItem {
     * adjusted for the caller too.
     */
    public void findAll(@NonNull List<ShortcutInfo> result,
            @Nullable Predicate<ShortcutInfo> query, int cloneFlag,
            @Nullable String query, @Nullable Predicate<ShortcutInfo> filter, int cloneFlag,
            @Nullable String callingLauncher, int launcherUserId, boolean getPinnedByAnyLauncher) {
        if (getPackageInfo().isShadow()) {
            // Restored and the app not installed yet, so don't return any.
@@ -790,7 +791,53 @@ class ShortcutPackage extends ShortcutPackageItem {
        final ArraySet<String> pinnedByCallerSet = (callingLauncher == null) ? null
                : s.getLauncherShortcutsLocked(callingLauncher, getPackageUserId(), launcherUserId)
                        .getPinnedShortcutIds(getPackageName(), getPackageUserId());
        forEachShortcut(si -> {
        forEachShortcut(query == null ? "" : query, si ->
                filter(result, filter, cloneFlag, callingLauncher, pinnedByCallerSet,
                        getPinnedByAnyLauncher, si));
    }

    /**
     * Find all shortcuts that has id matching {@code ids}.
     */
    public void findAllByIds(@NonNull final List<ShortcutInfo> result,
            @NonNull final Collection<String> ids, @Nullable final Predicate<ShortcutInfo> filter,
            final int cloneFlag) {
        findAllByIds(result, ids, filter, cloneFlag, null, 0, /*getPinnedByAnyLauncher=*/ false);
    }

    /**
     * Find all shortcuts that has id matching {@code ids}.
     *
     * This will also provide a "view" for each launcher -- a non-dynamic shortcut that's not pinned
     * by the calling launcher will not be included in the result, and also "isPinned" will be
     * adjusted for the caller too.
     */
    public void findAllByIds(@NonNull List<ShortcutInfo> result,
            @NonNull final Collection<String> ids, @Nullable final Predicate<ShortcutInfo> query,
            int cloneFlag, @Nullable String callingLauncher, int launcherUserId,
            boolean getPinnedByAnyLauncher) {
        if (getPackageInfo().isShadow()) {
            // Restored and the app not installed yet, so don't return any.
            return;
        }
        final ShortcutService s = mShortcutUser.mService;

        // Set of pinned shortcuts by the calling launcher.
        final ArraySet<String> pinnedByCallerSet = (callingLauncher == null) ? null
                : s.getLauncherShortcutsLocked(callingLauncher, getPackageUserId(), launcherUserId)
                        .getPinnedShortcutIds(getPackageName(), getPackageUserId());
        final List<ShortcutInfo> shortcuts = getShortcutById(ids);
        for (ShortcutInfo si : shortcuts) {
            filter(result, query, cloneFlag, callingLauncher, pinnedByCallerSet,
                    getPinnedByAnyLauncher, si);
        }
    }

    private void filter(@NonNull final List<ShortcutInfo> result,
            @Nullable final Predicate<ShortcutInfo> query, final int cloneFlag,
            @Nullable final String callingLauncher,
            @NonNull final ArraySet<String> pinnedByCallerSet,
            final boolean getPinnedByAnyLauncher, @NonNull final ShortcutInfo si) {
        // Need to adjust PINNED flag depending on the caller.
        // Basically if the caller is a launcher (callingLauncher != null) and the launcher
        // isn't pinning it, then we need to clear PINNED for this caller.
@@ -820,7 +867,6 @@ class ShortcutPackage extends ShortcutPackageItem {
            }
            result.add(clone);
        }
        });
    }

    public void resetThrottling() {
@@ -851,8 +897,8 @@ class ShortcutPackage extends ShortcutPackageItem {

        // Get the list of all dynamic shortcuts in this package.
        final ArrayList<ShortcutInfo> shortcuts = new ArrayList<>();
        findAll(shortcuts, ShortcutInfo::isNonManifestVisible,
                ShortcutInfo.CLONE_REMOVE_FOR_APP_PREDICTION);
        findAll(shortcuts, AppSearchShortcutInfo.QUERY_IS_NON_MANIFEST_VISIBLE,
                ShortcutInfo::isNonManifestVisible, ShortcutInfo.CLONE_REMOVE_FOR_APP_PREDICTION);

        final List<ShortcutManager.ShareShortcutInfo> result = new ArrayList<>();
        for (int i = 0; i < shortcuts.size(); i++) {
@@ -896,8 +942,8 @@ class ShortcutPackage extends ShortcutPackageItem {

        // Get the list of all dynamic shortcuts in this package
        final ArrayList<ShortcutInfo> shortcuts = new ArrayList<>();
        findAll(shortcuts, ShortcutInfo::isNonManifestVisible,
                ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);
        findAll(shortcuts, AppSearchShortcutInfo.QUERY_IS_NON_MANIFEST_VISIBLE,
                ShortcutInfo::isNonManifestVisible, ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);

        int sharingShortcutCount = 0;
        for (int i = 0; i < shortcuts.size(); i++) {
@@ -929,8 +975,7 @@ class ShortcutPackage extends ShortcutPackageItem {
     */
    public ArraySet<String> getUsedBitmapFiles() {
        final ArraySet<String> usedFiles = new ArraySet<>(1);
        forEachShortcut(si -> {
            // TODO: Find a good way to query shortcuts with null bitmap path
        forEachShortcut(AppSearchShortcutInfo.QUERY_HAS_BITMAP_PATH, si -> {
            if (si.getBitmapPath() != null) {
                usedFiles.add(getFileName(si.getBitmapPath()));
            }
@@ -1402,8 +1447,7 @@ class ShortcutPackage extends ShortcutPackageItem {
        final List<ShortcutInfo> changedShortcuts = new ArrayList<>(1);

        if (publisherRes != null) {
            forEachShortcutMutateIf(si -> {
                // TODO: Find a way to search for Shortcut with null resources
            forEachShortcutMutateIf(AppSearchShortcutInfo.QUERY_HAS_STRING_RESOURCE, si -> {
                if (!si.hasStringResources()) return false;
                si.resolveResourceStrings(publisherRes);
                si.setTimestamp(s.injectCurrentTimeMillis());
@@ -1458,8 +1502,7 @@ class ShortcutPackage extends ShortcutPackageItem {
        final long now = s.injectCurrentTimeMillis();

        // First, clear ranks for floating shortcuts.
        // TODO: Implement query with non-zero rank
        forEachShortcutMutateIf(AppSearchShortcutInfo.QUERY_IS_FLOATING, si -> {
        forEachShortcutMutateIf(AppSearchShortcutInfo.QUERY_IS_FLOATING_AND_HAS_RANK, si -> {
            if (si.isFloating() && si.getRank() != 0) {
                si.setTimestamp(now);
                si.setRank(0);
@@ -2233,14 +2276,14 @@ class ShortcutPackage extends ShortcutPackageItem {
                                        Slog.e(TAG, k.getErrorMessage());
                                    }
                                    future.completeExceptionally(new RuntimeException(
                                            "failed to save shortcuts"));
                                            "Failed to save shortcuts"));
                                    return;
                                }
                                future.complete(true);
                            });
                    return future;
                }),
                "saving shortcut");
                "Saving shortcut");
    }

    /**
@@ -2249,12 +2292,7 @@ class ShortcutPackage extends ShortcutPackageItem {
    void removeShortcuts() {
        awaitInAppSearch("Removing all shortcuts from " + getPackageName(), session -> {
            final AndroidFuture<Boolean> future = new AndroidFuture<>();
            session.remove("",
                    new SearchSpec.Builder()
                            .addFilterSchemas(AppSearchShortcutInfo.SCHEMA_TYPE)
                            .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
                            .build(),
                    mShortcutUser.mExecutor, result -> {
            session.remove("", getSearchSpec(), mShortcutUser.mExecutor, result -> {
                if (!result.isSuccess()) {
                    future.completeExceptionally(
                            new RuntimeException(result.getErrorMessage()));
@@ -2268,7 +2306,7 @@ class ShortcutPackage extends ShortcutPackageItem {

    private void removeShortcut(@NonNull final String id) {
        Objects.requireNonNull(id);
        awaitInAppSearch("removing shortcut with id=" + id, session -> {
        awaitInAppSearch("Removing shortcut with id=" + id, session -> {
            final AndroidFuture<Boolean> future = new AndroidFuture<>();
            session.remove(new RemoveByUriRequest.Builder(getPackageName()).addUris(id).build(),
                    mShortcutUser.mExecutor, result -> {
@@ -2289,24 +2327,28 @@ class ShortcutPackage extends ShortcutPackageItem {
        });
    }

    private List<ShortcutInfo> getShortcutById(String... id) {
        return awaitInAppSearch("getting shortcut by id", session -> {
    @NonNull
    private List<ShortcutInfo> getShortcutById(@NonNull final Collection<String> ids) {
        final List<String> shortcutIds = new ArrayList<>(1);
        for (String id : ids) {
            if (id != null) {
                shortcutIds.add(id);
            }
        }
        return awaitInAppSearch("Getting shortcut by id", session -> {
            final AndroidFuture<List<ShortcutInfo>> future = new AndroidFuture<>();
            session.getByUri(
                    new GetByUriRequest.Builder(getPackageName()).addUris(id).build(),
                    new GetByUriRequest.Builder(getPackageName()).addUris(shortcutIds).build(),
                    mShortcutUser.mExecutor,
                    results -> {
                        if (results.isSuccess()) {
                        final List<ShortcutInfo> ret = new ArrayList<>(1);
                        Map<String, GenericDocument> documents = results.getSuccesses();
                            final List<ShortcutInfo> ret = new ArrayList<>();
                        for (GenericDocument doc : documents.values()) {
                            final ShortcutInfo info = new AppSearchShortcutInfo(doc)
                                    .toShortcutInfo(mShortcutUser.getUserId());
                            ret.add(info);
                        }
                        future.complete(ret);
                        }
                        future.complete(new ArrayList<>());
                    });
            return future;
        });
@@ -2337,9 +2379,8 @@ class ShortcutPackage extends ShortcutPackageItem {

    private void forEachShortcutMutateIf(@NonNull final String query,
            @NonNull final Function<ShortcutInfo, Boolean> cb) {
        final SearchResults res = awaitInAppSearch("mutating shortcuts", session ->
                AndroidFuture.completedFuture(session.search(query, new SearchSpec.Builder()
                    .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY).build())));
        final SearchResults res = awaitInAppSearch("Mutating shortcuts", session ->
                AndroidFuture.completedFuture(session.search(query, getSearchSpec())));
        if (res == null) return;
        List<ShortcutInfo> shortcuts = getNextPage(res);
        while (!shortcuts.isEmpty()) {
@@ -2359,9 +2400,8 @@ class ShortcutPackage extends ShortcutPackageItem {

    private void forEachShortcutStopWhen(
            @NonNull final String query, @NonNull final Function<ShortcutInfo, Boolean> cb) {
        final SearchResults res = awaitInAppSearch("iterating shortcuts", session ->
                AndroidFuture.completedFuture(session.search(query, new SearchSpec.Builder()
                        .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY).build())));
        final SearchResults res = awaitInAppSearch("Iterating shortcuts", session ->
                AndroidFuture.completedFuture(session.search(query, getSearchSpec())));
        if (res == null) return;
        List<ShortcutInfo> shortcuts = getNextPage(res);
        while (!shortcuts.isEmpty()) {
@@ -2397,7 +2437,7 @@ class ShortcutPackage extends ShortcutPackageItem {
                future.complete(ret);
            });
            return ConcurrentUtils.waitForFutureNoInterrupt(future,
                    "getting next batch of shortcuts");
                    "Getting next batch of shortcuts");
        } finally {
            Binder.restoreCallingIdentity(callingIdentity);
        }
@@ -2444,8 +2484,8 @@ class ShortcutPackage extends ShortcutPackageItem {
        final AppSearchSession session;
        try {
            session = ConcurrentUtils.waitForFutureNoInterrupt(
                    mShortcutUser.getAppSearch(searchContext), "resetting app search");
            ConcurrentUtils.waitForFutureNoInterrupt(setupSchema(session), "setting up schema");
                    mShortcutUser.getAppSearch(searchContext), "Resetting app search");
            ConcurrentUtils.waitForFutureNoInterrupt(setupSchema(session), "Setting up schema");
            mAppSearchSession = session;
            return cb.apply(mAppSearchSession);
        } catch (Exception e) {
@@ -2496,6 +2536,15 @@ class ShortcutPackage extends ShortcutPackageItem {
        return future;
    }

    @NonNull
    private SearchSpec getSearchSpec() {
        return new SearchSpec.Builder()
                .addFilterSchemas(AppSearchShortcutInfo.SCHEMA_TYPE)
                .addFilterNamespaces(getPackageName())
                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
                .build();
    }

    /**
     * Merge/replace shortcuts parsed from xml file.
     */
+98 −47

File changed.

Preview size limit exceeded, changes collapsed.