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

Commit 106ff7a0 authored by Makoto Onuki's avatar Makoto Onuki
Browse files

ShortcutManager: Floating shortcuts shouldn't have target activities.

Previously, even floating shortcuts (pinned but not dynamic nor
manifest) had target activities.  Now we're going to allow headless
apps to have pinned shortcuts, in which cases shortcuts won't have
target activities, let's just always remove target activities
from floating shortcuts.

Test: adb shell am instrument -e class com.android.server.pm.ShortcutManagerTest1 -w com.android.frameworks.servicestests
... to Test8.

Test: cts-tradefed run cts --skip-device-info --skip-preconditions --skip-system-status-check com.android.compatibility.common.tradefed.targetprep.NetworkConnectivityChecker -a armeabi-v7a -m CtsShortcutManagerTestCases
Test: cts-tradefed run cts --skip-device-info --skip-preconditions --skip-system-status-check com.android.compatibility.common.tradefed.targetprep.NetworkConnectivityChecker -a armeabi-v7a -m CtsShortcutHostTestCases

Change-Id: I10e5b87338cafb90e6566b3526f892c5330c73e9
parent c66cc516
Loading
Loading
Loading
Loading
+10 −3
Original line number Diff line number Diff line
@@ -314,9 +314,11 @@ public final class ShortcutInfo implements Parcelable {
     *
     * @hide
     */
    public void enforceMandatoryFields() {
    public void enforceMandatoryFields(boolean forPinned) {
        Preconditions.checkStringNotEmpty(mId, "Shortcut ID must be provided");
        if (!forPinned) {
            Preconditions.checkNotNull(mActivity, "Activity must be provided");
        }
        if (mTitle == null && mTitleResId == 0) {
            throw new IllegalArgumentException("Short label must be provided");
        }
@@ -1055,6 +1057,11 @@ public final class ShortcutInfo implements Parcelable {
     * Launcher apps should show the launcher icon for the returned activity alongside
     * this shortcut.
     *
     * <p>When a shortcut is dynamic or manifest
     * (i.e. either {@link #isDynamic()} or {@link #isDeclaredInManifest()} returns {@code TRUE}),
     * then it should always have a non-null target activity.
     * Otherwise it will return null.
     *
     * @see Builder#setActivity
     */
    @Nullable
@@ -1361,7 +1368,7 @@ public final class ShortcutInfo implements Parcelable {
    }

    /**
     * @return true if pinned but neither static nor dynamic.
     * Return {@code TRUE} if a shortcut is pinned but neither manifest nor dynamic.
     * @hide
     */
    public boolean isFloating() {
+24 −5
Original line number Diff line number Diff line
@@ -259,6 +259,11 @@ class ShortcutPackage extends ShortcutPackageItem {
        for (int i = mShortcuts.size() - 1; i >= 0; i--) {
            final ShortcutInfo si = mShortcuts.valueAt(i);

            if (si.isFloating()) {
                si.setRank(0);
                si.setActivity(null);
            }

            if (si.isAlive()) continue;

            if (removeList == null) {
@@ -288,6 +293,7 @@ class ShortcutPackage extends ShortcutPackageItem {
                si.setTimestamp(now);
                si.clearFlags(ShortcutInfo.FLAG_DYNAMIC);
                si.setRank(0); // It may still be pinned, so clear the rank.
                si.setActivity(null);
            }
        }
        if (changed) {
@@ -355,6 +361,7 @@ class ShortcutPackage extends ShortcutPackageItem {
        if (oldShortcut.isPinned()) {

            oldShortcut.setRank(0);
            oldShortcut.setActivity(null);
            oldShortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_MANIFEST);
            if (disable) {
                oldShortcut.addFlags(ShortcutInfo.FLAG_DISABLED);
@@ -595,6 +602,10 @@ class ShortcutPackage extends ShortcutPackageItem {

        for (int i = mShortcuts.size() - 1; i >= 0; i--) {
            final ShortcutInfo si = mShortcuts.valueAt(i);

            if (si.isFloating()) {
                continue; // Ignore floating shortcuts, which are not tied to any activities.
            }
            final ComponentName activity = si.getActivity();

            if (checked.contains(activity)) {
@@ -1356,6 +1367,10 @@ class ShortcutPackage extends ShortcutPackageItem {
                    case TAG_SHORTCUT:
                        final ShortcutInfo si = parseShortcut(parser, packageName,
                                shortcutUser.getUserId());
                        // Floating shortcut used to have target activities, but not anymore.
                        if (si.isFloating()) { // Not really needed by just in case.
                            si.setActivity(null);
                        }

                        // Don't use addShortcut(), we don't need to save the icon.
                        ret.mShortcuts.put(si.getId(), si);
@@ -1462,7 +1477,6 @@ class ShortcutPackage extends ShortcutPackageItem {
            intents.clear();
            intents.add(intentLegacy);
        }

        return new ShortcutInfo(
                userId, id, packageName, activityComponent, /* icon =*/ null,
                title, titleResId, titleResName, text, textResId, textResName,
@@ -1553,12 +1567,17 @@ class ShortcutPackage extends ShortcutPackageItem {
                Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
                        + " is both dynamic and manifest at the same time.");
            }
            if (si.getActivity() == null) {
            if (!si.isFloating() && si.getActivity() == null) {
                failed = true;
                Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
                        + " is not floating, but has null activity.");
            }
            if (si.isFloating() && si.getActivity() != null) {
                failed = true;
                Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
                        + " has null activity.");
                        + " is floating, but has non-null activity.");
            }
            if ((si.isDynamic() || si.isManifestShortcut()) && !si.isEnabled()) {
            if (!si.isFloating() && !si.isEnabled()) {
                failed = true;
                Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
                        + " is not floating, but is disabled.");
@@ -1581,7 +1600,7 @@ class ShortcutPackage extends ShortcutPackageItem {
        }

        if (failed) {
            throw new IllegalStateException("See logcat for errors");
            mShortcutUser.mService.verifyError();
        }
    }

+13 −3
Original line number Diff line number Diff line
@@ -124,6 +124,7 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -401,6 +402,9 @@ public class ShortcutService extends IShortcutService.Stub {

    @VisibleForTesting
    ShortcutService(Context context, Looper looper, boolean onlyForPackageManagerApis) {
        if (DEBUG) {
            Binder.LOG_RUNTIME_EXCEPTION = true;
        }
        mContext = Preconditions.checkNotNull(context);
        LocalServices.addService(ShortcutServiceInternal.class, new LocalService());
        mHandler = new Handler(looper);
@@ -1604,7 +1608,7 @@ public class ShortcutService extends IShortcutService.Stub {
        }

        if (!forUpdate) {
            shortcut.enforceMandatoryFields();
            shortcut.enforceMandatoryFields(/* forPinned= */ false);
            Preconditions.checkArgument(
                    injectIsMainActivity(shortcut.getActivity(), shortcut.getUserId()),
                    "Cannot publish shortcut: " + shortcut.getActivity() + " is not main activity");
@@ -1752,6 +1756,9 @@ public class ShortcutService extends IShortcutService.Stub {

                // Note copyNonNullFieldsFrom() does the "updatable with?" check too.
                target.copyNonNullFieldsFrom(source);
                if (target.isFloating()) {
                    target.setActivity(null);
                }
                target.setTimestamp(injectCurrentTimeMillis());

                if (replacingIcon) {
@@ -2320,8 +2327,7 @@ public class ShortcutService extends IShortcutService.Stub {
                            return false;
                        }
                        if (componentName != null) {
                            if (si.getActivity() != null
                                    && !si.getActivity().equals(componentName)) {
                            if (!Objects.equals(componentName, si.getActivity())) {
                                return false;
                            }
                        }
@@ -3761,4 +3767,8 @@ public class ShortcutService extends IShortcutService.Stub {
            forEachLoadedUserLocked(u -> u.forAllPackageItems(ShortcutPackageItem::verifyStates));
        }
    }

    void verifyError() {
        Slog.e(TAG, "See logcat for errors");
    }
}
+6 −0
Original line number Diff line number Diff line
@@ -376,6 +376,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {

        @Override
        boolean injectIsActivityEnabledAndExported(ComponentName activity, @UserIdInt int userId) {
            assertNotNull(activity);
            return mEnabledActivityChecker.test(activity, userId);
        }

@@ -416,6 +417,11 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
            // During tests, WTF is fatal.
            fail(message + "  exception: " + th + "\n" + Log.getStackTraceString(th));
        }

        @Override
        void verifyError() {
            fail("Verify error");
        }
    }

    /** ShortcutManager with injection override methods. */
+37 −1
Original line number Diff line number Diff line
@@ -1292,7 +1292,43 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
                    /* activity =*/ null, /* flags */ 0), getCallingUser());
                });

        // TODO More tests: pinned but dynamic.
        // Make sure floating shortcuts don't match with an activity.
        // At this point, s1 is dynamic and pinned, so it still has a target activity.
        runWithCaller(LAUNCHER_1, USER_0, () -> {
            assertWith(mLauncherApps.getShortcuts(buildQuery(
                    /* time =*/ 0,
                    CALLING_PACKAGE_2,
                    /* activity =*/ new ComponentName(CALLING_PACKAGE_2,
                            ShortcutActivity2.class.getName()),
                    ShortcutQuery.FLAG_GET_PINNED),
                    getCallingUser()))
                    .haveIds("s3")
                    .areAllPinned()
                    .areAllDynamic()
                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_2,
                            ShortcutActivity2.class.getName()));
        });

        // Now remove as a dynamic, making it floating.
        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
            mManager.removeDynamicShortcuts(list("s3"));
            assertWith(mManager.getPinnedShortcuts())
                    .selectFloating()
                    .areAllWithNoActivity();
        });

        runWithCaller(LAUNCHER_1, USER_0, () -> {
            // This shouldn't match now.
            assertWith(mLauncherApps.getShortcuts(buildQuery(
                    /* time =*/ 0,
                    CALLING_PACKAGE_2,
                    /* activity =*/ new ComponentName(CALLING_PACKAGE_2,
                            ShortcutActivity2.class.getName()),
                    ShortcutQuery.FLAG_GET_PINNED),
                    getCallingUser()))
                    .isEmpty();
        });

    }

    public void testGetShortcuts_shortcutKinds() throws Exception {
Loading