diff options
6 files changed, 107 insertions, 11 deletions
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java index 0ec16237879a..0d6a23bcdc96 100644 --- a/core/java/android/content/pm/ShortcutInfo.java +++ b/core/java/android/content/pm/ShortcutInfo.java @@ -941,7 +941,8 @@ public final class ShortcutInfo implements Parcelable { } /** - * Sets the intent of a shortcut. + * Sets the intent of a shortcut. Alternatively, {@link #setIntents(Intent[])} can be used + * to launch an activity with other activities in the back stack. * * <p>This is a mandatory field when publishing a new shortcut with * {@link ShortcutManager#addDynamicShortcuts(List)} or @@ -965,7 +966,9 @@ public final class ShortcutInfo implements Parcelable { } /** - * Sets multiple intents instead of a single intent. + * Sets multiple intents instead of a single intent, in order to launch an activity with + * other activities in back stack. Use {@link TaskStackBuilder} to build intents. + * See the {@link ShortcutManager} javadoc for details. * * @see Builder#setIntent(Intent) * @see ShortcutInfo#getIntents() diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java index cfd3442cc07b..81302714d2b6 100644 --- a/core/java/android/content/pm/ShortcutManager.java +++ b/core/java/android/content/pm/ShortcutManager.java @@ -18,6 +18,7 @@ package android.content.pm; import android.annotation.NonNull; import android.annotation.TestApi; import android.annotation.UserIdInt; +import android.app.Activity; import android.app.usage.UsageStatsManager; import android.content.Context; import android.content.Intent; @@ -30,7 +31,7 @@ import com.android.internal.annotations.VisibleForTesting; import java.util.List; /** - * ShortcutManager manages "launcher shortcuts" (or simply "shortcuts"). Shortcuts provide user + * ShortcutManager manages "launcher shortcuts" (or simply "shortcuts"). Shortcuts provide users * with quick * ways to access activities other than the main activity from the launcher to users. For example, * an email application may publish the "compose new email" action which will directly open the @@ -183,6 +184,7 @@ import java.util.List; * android:action="android.intent.action.VIEW" * android:targetPackage="com.example.myapplication" * android:targetClass="com.example.myapplication.ComposeActivity" /> + * <!-- more intents can go here; see below --> * <categories android:name="android.shortcut.conversation" /> * </shortcut> * <!-- more shortcut can go here --> @@ -209,9 +211,37 @@ import java.util.List; * * <li>{@code intent} Intent to launch. {@code android:action} is mandatory. * See <a href="{@docRoot}guide/topics/ui/settings.html#Intents">Using intents</a> for the - * other supported tags. + * other supported tags. Multiple intents can be provided for a single shortcut, so that + * an activity will be launched with other activities in the back stack. + * See {@link android.app.TaskStackBuilder} for details. * </ul> * + * <h3>Shortcut Intents</h3> + * Dynamic shortcuts can be published with any {@link Intent#addFlags Intent flags}. Typically, + * {@link Intent#FLAG_ACTIVITY_CLEAR_TASK} is specified possibly with other flags; otherwise, + * if the application is already running, the application is simply brought to the foreground + * and the target activity may not show up. + * + * <p>{@link ShortcutInfo.Builder#setIntents(Intent[])} can be used (instead of + * {@link ShortcutInfo.Builder#setIntent(Intent)}) with + * {@link android.app.TaskStackBuilder} in order to launch an activity with other activities + * in the back stack, so that when the user presses the back key, a "parent" activity will be shown + * instead of the user being navigated back to the launcher. + * + * <p>Manifest shortcuts can have multiple intents too to achieve the same effect. In order to + * specify multiple {@link Intent}s to a shortcut, simply list multiple <intent>s within + * a single <shortcut>. The last intent is what the user will see when a shortcut is + * launched. + * + * <p>Manifest shortcuts <b>cannot</b> have custom intent flags. The first intent of a manifest + * shortcut will always have {@link Intent#FLAG_ACTIVITY_NEW_TASK} and + * {@link Intent#FLAG_ACTIVITY_CLEAR_TASK} set. This means, when the application is already + * running, all the existing activities will be destroyed when a manifest shortcut is launched. + * If this behavior is not desirable, one can use a "trampoline" activity (an activity + * that starts another activity in {@link Activity#onCreate} and then calls + * {@link Activity#finish()}) with {@code android:taskAffinity=""} in AndroidManifest.xml and point + * at this activity in a manifest shortcut's intent. + * * <h3>Updating Shortcuts v.s. Re-publishing New One with Different ID</h3> * In order to avoid users' confusion, {@link #updateShortcuts(List)} should not be used to update * a shortcut to something that is conceptually different. @@ -267,6 +297,8 @@ import java.util.List; * <li>When the user performs "inline reply" on a notification. * </ul> * + * <p>When rate-limiting is active, {@link #isRateLimitingActive()} returns {@code true}. + * * <h4>Resetting rate-limiting for testing</h4> * * If your application is rate-limited during development or testing, you can use the diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index b4dd5877278d..698e7622b2fa 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -459,9 +459,8 @@ public class LauncherAppsService extends SystemService { } // Note the target activity doesn't have to be exported. - // TODO Use sourceBounds - - intents[0].addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intents[0].setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intents[0].setSourceBounds(sourceBounds); return startShortcutIntentsAsPublisher( intents, packageName, startActivityOptions, userId); diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index e6a97394e5b5..185a6a2aadd8 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -842,12 +842,17 @@ public class ShortcutService extends IShortcutService.Stub { getLastResetTimeLocked(); } + @VisibleForTesting + final File getUserFile(@UserIdInt int userId) { + return new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES); + } + private void saveUserLocked(@UserIdInt int userId) { - final File path = new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES); + final File path = getUserFile(userId); if (DEBUG) { Slog.d(TAG, "Saving to " + path); } - path.mkdirs(); + path.getParentFile().mkdirs(); final AtomicFile file = new AtomicFile(path); FileOutputStream os = null; try { @@ -890,7 +895,7 @@ public class ShortcutService extends IShortcutService.Stub { @Nullable private ShortcutUser loadUserLocked(@UserIdInt int userId) { - final File path = new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES); + final File path = getUserFile(userId); if (DEBUG) { Slog.d(TAG, "Loading from " + path); } @@ -1466,7 +1471,7 @@ public class ShortcutService extends IShortcutService.Stub { * Clean up / validate an incoming shortcut. * - Make sure all mandatory fields are set. * - Make sure the intent's extras are persistable, and them to set - * {@link ShortcutInfo#mIntentPersistableExtras}. Also clear its extras. + * {@link ShortcutInfo#mIntentPersistableExtrases}. Also clear its extras. * - Clear flags. * * TODO Detailed unit tests diff --git a/services/tests/servicestests/assets/shortcut/shortcut_legacy_file.xml b/services/tests/servicestests/assets/shortcut/shortcut_legacy_file.xml new file mode 100644 index 000000000000..872dc3a26773 --- /dev/null +++ b/services/tests/servicestests/assets/shortcut/shortcut_legacy_file.xml @@ -0,0 +1,25 @@ +<?xml version='1.0' encoding='utf-8' standalone='yes' ?> +<!-- Copyright (C) 2016 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. +--> +<user locales="en-US" last-app-scan-time="3113976673"> + <package name="com.android.test.1" call-count="0" last-reset="1468976368772"> + <package-info version="25" last_udpate_time="1230796800000" /> + <shortcut id="manifest-shortcut-storage" activity="com.android.test.1/com.android.test.1.Settings" title="Storage" titleid="2131625197" titlename="storage_settings" textid="0" dmessageid="0" intent="#Intent;action=android.settings.INTERNAL_STORAGE_SETTINGS;end" timestamp="1469050672334" rank="4" flags="420" icon-res="2130837747" icon-resname="drawable/ic_shortcut_storage" > + <intent-extras> + <int name="key" value="12345" /> + </intent-extras> + </shortcut> + </package> +</user> diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java index f97355a54837..54b2a15230f7 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java @@ -48,9 +48,11 @@ import com.android.frameworks.servicestests.R; import com.android.server.pm.ShortcutService.ConfigConstants; import java.io.File; +import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; +import java.io.Writer; import java.util.Locale; /** @@ -100,6 +102,12 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertExpectException( RuntimeException.class, + "action must be set", + () -> new ShortcutInfo.Builder(getTestContext(), "id") + .setIntents(new Intent[]{new Intent("action"), new Intent()})); + + assertExpectException( + RuntimeException.class, "activity cannot be null", () -> new ShortcutInfo.Builder(getTestContext(), "id").setActivity(null)); @@ -1967,4 +1975,28 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { mService.dump(null, new PrintWriter(new StringWriter()), null); }); } + + /** + * Make sure the legacy file format that only supported a single intent per shortcut + * can still be read. + */ + public void testLoadLegacySavedFile() throws Exception { + final File path = mService.getUserFile(USER_0); + path.getParentFile().mkdirs(); + try (Writer w = new FileWriter(path)) { + w.write(readTestAsset("shortcut/shortcut_legacy_file.xml")); + }; + initService(); + mService.handleUnlockUser(USER_0); + + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + assertWith(getCallerShortcuts()) + .haveIds("manifest-shortcut-storage") + .forShortcutWithId("manifest-shortcut-storage", si -> { + assertEquals("android.settings.INTERNAL_STORAGE_SETTINGS", + si.getIntent().getAction()); + assertEquals(12345, si.getIntent().getIntExtra("key", 0)); + }); + }); + } } |