From 0cb5713fd7faf31aa41d8a1c8300268c10833125 Mon Sep 17 00:00:00 2001 From: Suprabh Shukla Date: Sat, 18 Jan 2020 20:15:08 -0800 Subject: Add an unsuspend button option to SuspendDialogInfo The suspending app can specify if they want to unsuspend the app on neutral button tap. This needs to be done by the system so as to maintain continuity of the users activity tasks. The system then also needs to communicate to the suspending app when such an unsuspend has taken place so it can keep any of its internal data consistent with the current state. Test: atest FrameworksServicesTests:PackageManagerSettingsTests atest FrameworksServicesTests:SuspendDialogInfoTest atest GtsSuspendAppsTestCases:SuspendPackagesTest Bug: 134962563 Change-Id: I587b2643eb41519b82b8e2b371a466a9fb650d4b --- api/system-current.txt | 4 + core/java/android/content/Intent.java | 29 +++++ .../java/android/content/pm/SuspendDialogInfo.java | 102 +++++++++++++++--- .../android/internal/app/SuspendedAppActivity.java | 119 +++++++++++++++++---- core/res/AndroidManifest.xml | 1 + core/res/res/values/strings.xml | 2 + core/res/res/values/symbols.xml | 1 + .../server/appwidget/AppWidgetServiceImpl.java | 5 +- .../server/wm/ActivityStartInterceptor.java | 16 ++- .../server/pm/PackageManagerSettingsTests.java | 5 + .../android/server/pm/SuspendDialogInfoTest.java | 25 ++++- 11 files changed, 268 insertions(+), 41 deletions(-) diff --git a/api/system-current.txt b/api/system-current.txt index e532a3afab8a..d7be7b5b1a71 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -1768,6 +1768,7 @@ package android.content { field @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public static final String ACTION_MANAGE_SPECIAL_APP_ACCESSES = "android.intent.action.MANAGE_SPECIAL_APP_ACCESSES"; field public static final String ACTION_MASTER_CLEAR_NOTIFICATION = "android.intent.action.MASTER_CLEAR_NOTIFICATION"; field public static final String ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION = "android.intent.action.PACKAGE_NEEDS_INTEGRITY_VERIFICATION"; + field public static final String ACTION_PACKAGE_UNSUSPENDED_MANUALLY = "android.intent.action.PACKAGE_UNSUSPENDED_MANUALLY"; field public static final String ACTION_PENDING_INCIDENT_REPORTS_CHANGED = "android.intent.action.PENDING_INCIDENT_REPORTS_CHANGED"; field public static final String ACTION_PRE_BOOT_COMPLETED = "android.intent.action.PRE_BOOT_COMPLETED"; field public static final String ACTION_QUERY_PACKAGE_RESTART = "android.intent.action.QUERY_PACKAGE_RESTART"; @@ -2308,6 +2309,8 @@ package android.content.pm { public final class SuspendDialogInfo implements android.os.Parcelable { method public int describeContents(); method public void writeToParcel(android.os.Parcel, int); + field public static final int BUTTON_ACTION_MORE_DETAILS = 0; // 0x0 + field public static final int BUTTON_ACTION_UNSUSPEND = 1; // 0x1 field @NonNull public static final android.os.Parcelable.Creator CREATOR; } @@ -2317,6 +2320,7 @@ package android.content.pm { method @NonNull public android.content.pm.SuspendDialogInfo.Builder setIcon(@DrawableRes int); method @NonNull public android.content.pm.SuspendDialogInfo.Builder setMessage(@NonNull String); method @NonNull public android.content.pm.SuspendDialogInfo.Builder setMessage(@StringRes int); + method @NonNull public android.content.pm.SuspendDialogInfo.Builder setNeutralButtonAction(int); method @NonNull public android.content.pm.SuspendDialogInfo.Builder setNeutralButtonText(@StringRes int); method @NonNull public android.content.pm.SuspendDialogInfo.Builder setTitle(@StringRes int); } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index ee758024fb9f..e89a8ae9e4ec 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -36,6 +36,7 @@ import android.content.pm.ComponentInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ShortcutInfo; +import android.content.pm.SuspendDialogInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Rect; @@ -2666,6 +2667,34 @@ public class Intent implements Parcelable, Cloneable { public static final String ACTION_SHOW_SUSPENDED_APP_DETAILS = "android.intent.action.SHOW_SUSPENDED_APP_DETAILS"; + /** + * Broadcast Action: Sent to indicate that the user unsuspended a package. + * + *

This can happen when the user taps on the neutral button of the + * {@linkplain SuspendDialogInfo suspend-dialog} which was created by using + * {@link SuspendDialogInfo#BUTTON_ACTION_UNSUSPEND}. This broadcast is only sent to the + * suspending app that originally specified this dialog while calling + * {@link PackageManager#setPackagesSuspended(String[], boolean, PersistableBundle, + * PersistableBundle, SuspendDialogInfo)}. + * + *

Includes an extra {@link #EXTRA_PACKAGE_NAME} which is the name of the package that just + * got unsuspended. + * + *

This is a protected intent that can only be sent + * by the system. This will be delivered to {@link BroadcastReceiver} components declared in + * the manifest. + * + * @see PackageManager#setPackagesSuspended(String[], boolean, PersistableBundle, + * PersistableBundle, SuspendDialogInfo) + * @see PackageManager#isPackageSuspended() + * @see SuspendDialogInfo#BUTTON_ACTION_MORE_DETAILS + * @hide + */ + @SystemApi + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_PACKAGE_UNSUSPENDED_MANUALLY = + "android.intent.action.PACKAGE_UNSUSPENDED_MANUALLY"; + /** * Broadcast Action: Sent to a package that has been unsuspended. * diff --git a/core/java/android/content/pm/SuspendDialogInfo.java b/core/java/android/content/pm/SuspendDialogInfo.java index 73b75df80e5b..851a08116f56 100644 --- a/core/java/android/content/pm/SuspendDialogInfo.java +++ b/core/java/android/content/pm/SuspendDialogInfo.java @@ -19,6 +19,7 @@ package android.content.pm; import static android.content.res.Resources.ID_NULL; import android.annotation.DrawableRes; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringRes; @@ -36,20 +37,21 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlSerializer; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Locale; import java.util.Objects; /** * A container to describe the dialog to be shown when the user tries to launch a suspended - * application. - * The suspending app can customize the dialog's following attributes: + * application. The suspending app can customize the dialog's following attributes: *

* System defaults are used whenever any of these are not provided, or any of the provided resource * ids cannot be resolved at the time of displaying the dialog. @@ -67,12 +69,47 @@ public final class SuspendDialogInfo implements Parcelable { private static final String XML_ATTR_DIALOG_MESSAGE_RES_ID = "dialogMessageResId"; private static final String XML_ATTR_DIALOG_MESSAGE = "dialogMessage"; private static final String XML_ATTR_BUTTON_TEXT_RES_ID = "buttonTextResId"; + private static final String XML_ATTR_BUTTON_ACTION = "buttonAction"; private final int mIconResId; private final int mTitleResId; private final int mDialogMessageResId; private final String mDialogMessage; private final int mNeutralButtonTextResId; + private final int mNeutralButtonAction; + + /** + * Used with {@link Builder#setNeutralButtonAction(int)} to create a neutral button that + * starts the {@link android.content.Intent#ACTION_SHOW_SUSPENDED_APP_DETAILS} activity. + * @see Builder#setNeutralButtonAction(int) + */ + public static final int BUTTON_ACTION_MORE_DETAILS = 0; + + /** + * Used with {@link Builder#setNeutralButtonAction(int)} to create a neutral button that + * unsuspends the app that the user was trying to launch and continues with the launch. The + * system also sends the broadcast + * {@link android.content.Intent#ACTION_PACKAGE_UNSUSPENDED_MANUALLY} to the suspending app + * when this happens. + * @see Builder#setNeutralButtonAction(int) + * @see android.content.Intent#ACTION_PACKAGE_UNSUSPENDED_MANUALLY + */ + public static final int BUTTON_ACTION_UNSUSPEND = 1; + + /** + * Button actions to specify what happens when the user taps on the neutral button. + * To be used with {@link Builder#setNeutralButtonAction(int)}. + * + * @hide + * @see Builder#setNeutralButtonAction(int) + */ + @IntDef(flag = true, prefix = {"BUTTON_ACTION_"}, value = { + BUTTON_ACTION_MORE_DETAILS, + BUTTON_ACTION_UNSUSPEND + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ButtonAction { + } /** * @return the resource id of the icon to be used with the dialog @@ -102,8 +139,8 @@ public final class SuspendDialogInfo implements Parcelable { } /** - * @return the text to be shown in the dialog's body. Returns {@code null} if - * {@link #getDialogMessageResId()} returns a valid resource id. + * @return the text to be shown in the dialog's body. Returns {@code null} if {@link + * #getDialogMessageResId()} returns a valid resource id * @hide */ @Nullable @@ -120,6 +157,15 @@ public final class SuspendDialogInfo implements Parcelable { return mNeutralButtonTextResId; } + /** + * @return The {@link ButtonAction} that happens on tapping this button + * @hide + */ + @ButtonAction + public int getNeutralButtonAction() { + return mNeutralButtonAction; + } + /** * @hide */ @@ -138,6 +184,7 @@ public final class SuspendDialogInfo implements Parcelable { if (mNeutralButtonTextResId != ID_NULL) { XmlUtils.writeIntAttribute(out, XML_ATTR_BUTTON_TEXT_RES_ID, mNeutralButtonTextResId); } + XmlUtils.writeIntAttribute(out, XML_ATTR_BUTTON_ACTION, mNeutralButtonAction); } /** @@ -150,6 +197,8 @@ public final class SuspendDialogInfo implements Parcelable { final int titleId = XmlUtils.readIntAttribute(in, XML_ATTR_TITLE_RES_ID, ID_NULL); final int buttonTextId = XmlUtils.readIntAttribute(in, XML_ATTR_BUTTON_TEXT_RES_ID, ID_NULL); + final int buttonAction = XmlUtils.readIntAttribute(in, XML_ATTR_BUTTON_ACTION, + BUTTON_ACTION_MORE_DETAILS); final int dialogMessageResId = XmlUtils.readIntAttribute( in, XML_ATTR_DIALOG_MESSAGE_RES_ID, ID_NULL); final String dialogMessage = XmlUtils.readStringAttribute(in, XML_ATTR_DIALOG_MESSAGE); @@ -168,6 +217,7 @@ public final class SuspendDialogInfo implements Parcelable { } else if (dialogMessage != null) { dialogInfoBuilder.setMessage(dialogMessage); } + dialogInfoBuilder.setNeutralButtonAction(buttonAction); } catch (Exception e) { Slog.e(TAG, "Exception while parsing from xml. Some fields may default", e); } @@ -181,6 +231,7 @@ public final class SuspendDialogInfo implements Parcelable { hashCode = 31 * hashCode + mNeutralButtonTextResId; hashCode = 31 * hashCode + mDialogMessageResId; hashCode = 31 * hashCode + Objects.hashCode(mDialogMessage); + hashCode = 31 * hashCode + mNeutralButtonAction; return hashCode; } @@ -197,6 +248,7 @@ public final class SuspendDialogInfo implements Parcelable { && mTitleResId == otherDialogInfo.mTitleResId && mDialogMessageResId == otherDialogInfo.mDialogMessageResId && mNeutralButtonTextResId == otherDialogInfo.mNeutralButtonTextResId + && mNeutralButtonAction == otherDialogInfo.mNeutralButtonAction && Objects.equals(mDialogMessage, otherDialogInfo.mDialogMessage); } @@ -228,6 +280,8 @@ public final class SuspendDialogInfo implements Parcelable { builder.append(mDialogMessage); builder.append("\" "); } + builder.append("mNeutralButtonAction = "); + builder.append(mNeutralButtonAction); builder.append("}"); return builder.toString(); } @@ -244,6 +298,7 @@ public final class SuspendDialogInfo implements Parcelable { dest.writeInt(mDialogMessageResId); dest.writeString(mDialogMessage); dest.writeInt(mNeutralButtonTextResId); + dest.writeInt(mNeutralButtonAction); } private SuspendDialogInfo(Parcel source) { @@ -252,6 +307,7 @@ public final class SuspendDialogInfo implements Parcelable { mDialogMessageResId = source.readInt(); mDialogMessage = source.readString(); mNeutralButtonTextResId = source.readInt(); + mNeutralButtonAction = source.readInt(); } SuspendDialogInfo(Builder b) { @@ -260,9 +316,11 @@ public final class SuspendDialogInfo implements Parcelable { mDialogMessageResId = b.mDialogMessageResId; mDialogMessage = (mDialogMessageResId == ID_NULL) ? b.mDialogMessage : null; mNeutralButtonTextResId = b.mNeutralButtonTextResId; + mNeutralButtonAction = b.mNeutralButtonAction; } - public static final @android.annotation.NonNull Creator CREATOR = new Creator() { + public static final @NonNull Creator CREATOR = + new Creator() { @Override public SuspendDialogInfo createFromParcel(Parcel source) { return new SuspendDialogInfo(source); @@ -283,6 +341,7 @@ public final class SuspendDialogInfo implements Parcelable { private int mTitleResId = ID_NULL; private int mIconResId = ID_NULL; private int mNeutralButtonTextResId = ID_NULL; + private int mNeutralButtonAction = BUTTON_ACTION_MORE_DETAILS; /** * Set the resource id of the icon to be used. If not provided, no icon will be shown. @@ -333,8 +392,8 @@ public final class SuspendDialogInfo implements Parcelable { /** * Set the resource id of the dialog message to be shown. If no dialog message is provided - * via either this method or {@link #setMessage(String)}, the system will use a - * default message. + * via either this method or {@link #setMessage(String)}, the system will use a default + * message. *

* The system will use {@link android.content.res.Resources#getString(int, Object...) * getString} to insert the suspended app name into the message, so an example format string @@ -353,9 +412,10 @@ public final class SuspendDialogInfo implements Parcelable { } /** - * Set the resource id of text to be shown on the neutral button. Tapping this button starts - * the {@link android.content.Intent#ACTION_SHOW_SUSPENDED_APP_DETAILS} activity. If this is - * not provided, the system will use a default text. + * Set the resource id of text to be shown on the neutral button. Tapping this button would + * perform the {@link ButtonAction action} specified through + * {@link #setNeutralButtonAction(int)}. If this is not provided, the system will use a + * default text. * * @param resId The resource id of the button text * @return this builder object. @@ -367,6 +427,22 @@ public final class SuspendDialogInfo implements Parcelable { return this; } + /** + * Set the action expected to happen on neutral button tap. Defaults to + * {@link #BUTTON_ACTION_MORE_DETAILS} if this is not provided. + * + * @param buttonAction Either {@link #BUTTON_ACTION_MORE_DETAILS} or + * {@link #BUTTON_ACTION_UNSUSPEND}. + * @return this builder object + */ + @NonNull + public Builder setNeutralButtonAction(@ButtonAction int buttonAction) { + Preconditions.checkArgument(buttonAction == BUTTON_ACTION_MORE_DETAILS + || buttonAction == BUTTON_ACTION_UNSUSPEND, "Invalid button action"); + mNeutralButtonAction = buttonAction; + return this; + } + /** * Build the final object based on given inputs. * diff --git a/core/java/com/android/internal/app/SuspendedAppActivity.java b/core/java/com/android/internal/app/SuspendedAppActivity.java index c610ac4503c9..0589baa76b8a 100644 --- a/core/java/com/android/internal/app/SuspendedAppActivity.java +++ b/core/java/com/android/internal/app/SuspendedAppActivity.java @@ -18,23 +18,31 @@ package com.android.internal.app; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; +import static android.content.pm.SuspendDialogInfo.BUTTON_ACTION_MORE_DETAILS; +import static android.content.pm.SuspendDialogInfo.BUTTON_ACTION_UNSUSPEND; import static android.content.res.Resources.ID_NULL; import android.Manifest; +import android.annotation.Nullable; import android.app.AlertDialog; +import android.app.AppGlobals; import android.content.DialogInterface; import android.content.Intent; +import android.content.IntentSender; +import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.SuspendDialogInfo; import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.os.Bundle; +import android.os.RemoteException; import android.os.UserHandle; import android.util.Slog; import android.view.WindowManager; import com.android.internal.R; +import com.android.internal.util.ArrayUtils; public class SuspendedAppActivity extends AlertActivity implements DialogInterface.OnClickListener { @@ -46,8 +54,13 @@ public class SuspendedAppActivity extends AlertActivity PACKAGE_NAME + ".extra.SUSPENDING_PACKAGE"; public static final String EXTRA_DIALOG_INFO = PACKAGE_NAME + ".extra.DIALOG_INFO"; public static final String EXTRA_ACTIVITY_OPTIONS = PACKAGE_NAME + ".extra.ACTIVITY_OPTIONS"; + public static final String EXTRA_UNSUSPEND_INTENT = PACKAGE_NAME + ".extra.UNSUSPEND_INTENT"; private Intent mMoreDetailsIntent; + private IntentSender mOnUnsuspend; + private String mSuspendedPackage; + private String mSuspendingPackage; + private int mNeutralButtonAction; private int mUserId; private PackageManager mPm; private Resources mSuspendingAppResources; @@ -63,16 +76,15 @@ public class SuspendedAppActivity extends AlertActivity return packageName; } - private Intent getMoreDetailsActivity(String suspendingPackage, String suspendedPackage, - int userId) { + private Intent getMoreDetailsActivity() { final Intent moreDetailsIntent = new Intent(Intent.ACTION_SHOW_SUSPENDED_APP_DETAILS) - .setPackage(suspendingPackage); + .setPackage(mSuspendingPackage); final String requiredPermission = Manifest.permission.SEND_SHOW_SUSPENDED_APP_DETAILS; final ResolveInfo resolvedInfo = mPm.resolveActivityAsUser(moreDetailsIntent, - MATCH_DIRECT_BOOT_UNAWARE | MATCH_DIRECT_BOOT_AWARE, userId); + MATCH_DIRECT_BOOT_UNAWARE | MATCH_DIRECT_BOOT_AWARE, mUserId); if (resolvedInfo != null && resolvedInfo.activityInfo != null && requiredPermission.equals(resolvedInfo.activityInfo.permission)) { - moreDetailsIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, suspendedPackage) + moreDetailsIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, mSuspendedPackage) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); return moreDetailsIntent; } @@ -105,8 +117,8 @@ public class SuspendedAppActivity extends AlertActivity return getString(R.string.app_suspended_title); } - private String resolveDialogMessage(String suspendingPkg, String suspendedPkg) { - final CharSequence suspendedAppLabel = getAppLabel(suspendedPkg); + private String resolveDialogMessage() { + final CharSequence suspendedAppLabel = getAppLabel(mSuspendedPackage); if (mSuppliedDialogInfo != null) { final int messageId = mSuppliedDialogInfo.getDialogMessageResId(); final String message = mSuppliedDialogInfo.getDialogMessage(); @@ -122,10 +134,30 @@ public class SuspendedAppActivity extends AlertActivity } } return getString(R.string.app_suspended_default_message, suspendedAppLabel, - getAppLabel(suspendingPkg)); + getAppLabel(mSuspendingPackage)); } + /** + * Returns a text to be displayed on the neutral button or {@code null} if the button should + * not be shown. + */ + @Nullable private String resolveNeutralButtonText() { + final int defaultButtonTextId; + switch (mNeutralButtonAction) { + case BUTTON_ACTION_MORE_DETAILS: + if (mMoreDetailsIntent == null) { + return null; + } + defaultButtonTextId = R.string.app_suspended_more_details; + break; + case BUTTON_ACTION_UNSUSPEND: + defaultButtonTextId = R.string.app_suspended_unsuspend_message; + break; + default: + Slog.w(TAG, "Unknown neutral button action: " + mNeutralButtonAction); + return null; + } final int buttonTextId = (mSuppliedDialogInfo != null) ? mSuppliedDialogInfo.getNeutralButtonTextResId() : ID_NULL; if (buttonTextId != ID_NULL && mSuspendingAppResources != null) { @@ -135,7 +167,7 @@ public class SuspendedAppActivity extends AlertActivity Slog.e(TAG, "Could not resolve string resource id " + buttonTextId); } } - return getString(R.string.app_suspended_more_details); + return getString(defaultButtonTextId); } @Override @@ -152,27 +184,29 @@ public class SuspendedAppActivity extends AlertActivity finish(); return; } - final String suspendedPackage = intent.getStringExtra(EXTRA_SUSPENDED_PACKAGE); - final String suspendingPackage = intent.getStringExtra(EXTRA_SUSPENDING_PACKAGE); + mSuspendedPackage = intent.getStringExtra(EXTRA_SUSPENDED_PACKAGE); + mSuspendingPackage = intent.getStringExtra(EXTRA_SUSPENDING_PACKAGE); mSuppliedDialogInfo = intent.getParcelableExtra(EXTRA_DIALOG_INFO); + mOnUnsuspend = intent.getParcelableExtra(EXTRA_UNSUSPEND_INTENT); if (mSuppliedDialogInfo != null) { try { - mSuspendingAppResources = mPm.getResourcesForApplicationAsUser(suspendingPackage, + mSuspendingAppResources = mPm.getResourcesForApplicationAsUser(mSuspendingPackage, mUserId); } catch (PackageManager.NameNotFoundException ne) { - Slog.e(TAG, "Could not find resources for " + suspendingPackage, ne); + Slog.e(TAG, "Could not find resources for " + mSuspendingPackage, ne); } } + mNeutralButtonAction = (mSuppliedDialogInfo != null) + ? mSuppliedDialogInfo.getNeutralButtonAction() : BUTTON_ACTION_MORE_DETAILS; + mMoreDetailsIntent = (mNeutralButtonAction == BUTTON_ACTION_MORE_DETAILS) + ? getMoreDetailsActivity() : null; final AlertController.AlertParams ap = mAlertParams; ap.mIcon = resolveIcon(); ap.mTitle = resolveTitle(); - ap.mMessage = resolveDialogMessage(suspendingPackage, suspendedPackage); + ap.mMessage = resolveDialogMessage(); ap.mPositiveButtonText = getString(android.R.string.ok); - mMoreDetailsIntent = getMoreDetailsActivity(suspendingPackage, suspendedPackage, mUserId); - if (mMoreDetailsIntent != null) { - ap.mNeutralButtonText = resolveNeutralButtonText(); - } + ap.mNeutralButtonText = resolveNeutralButtonText(); ap.mPositiveButtonListener = ap.mNeutralButtonListener = this; setupAlert(); } @@ -181,21 +215,62 @@ public class SuspendedAppActivity extends AlertActivity public void onClick(DialogInterface dialog, int which) { switch (which) { case AlertDialog.BUTTON_NEUTRAL: - startActivityAsUser(mMoreDetailsIntent, mOptions, UserHandle.of(mUserId)); - Slog.i(TAG, "Started activity: " + mMoreDetailsIntent.getAction() - + " in user " + mUserId); + switch (mNeutralButtonAction) { + case BUTTON_ACTION_MORE_DETAILS: + if (mMoreDetailsIntent != null) { + startActivityAsUser(mMoreDetailsIntent, mOptions, + UserHandle.of(mUserId)); + } else { + Slog.wtf(TAG, "Neutral button should not have existed!"); + } + break; + case BUTTON_ACTION_UNSUSPEND: + final IPackageManager ipm = AppGlobals.getPackageManager(); + try { + final String[] errored = ipm.setPackagesSuspendedAsUser( + new String[]{mSuspendedPackage}, false, null, null, null, + mSuspendingPackage, mUserId); + if (ArrayUtils.contains(errored, mSuspendedPackage)) { + Slog.e(TAG, "Could not unsuspend " + mSuspendedPackage); + break; + } + } catch (RemoteException re) { + Slog.e(TAG, "Can't talk to system process", re); + break; + } + final Intent reportUnsuspend = new Intent() + .setAction(Intent.ACTION_PACKAGE_UNSUSPENDED_MANUALLY) + .putExtra(Intent.EXTRA_PACKAGE_NAME, mSuspendedPackage) + .setPackage(mSuspendingPackage) + .addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + sendBroadcastAsUser(reportUnsuspend, UserHandle.of(mUserId)); + + if (mOnUnsuspend != null) { + try { + mOnUnsuspend.sendIntent(this, 0, null, null, null); + } catch (IntentSender.SendIntentException e) { + Slog.e(TAG, "Error while starting intent " + mOnUnsuspend, e); + } + } + break; + default: + Slog.e(TAG, "Unexpected action on neutral button: " + mNeutralButtonAction); + break; + } break; } finish(); } public static Intent createSuspendedAppInterceptIntent(String suspendedPackage, - String suspendingPackage, SuspendDialogInfo dialogInfo, Bundle options, int userId) { + String suspendingPackage, SuspendDialogInfo dialogInfo, Bundle options, + IntentSender onUnsuspend, int userId) { return new Intent() .setClassName("android", SuspendedAppActivity.class.getName()) .putExtra(EXTRA_SUSPENDED_PACKAGE, suspendedPackage) .putExtra(EXTRA_DIALOG_INFO, dialogInfo) .putExtra(EXTRA_SUSPENDING_PACKAGE, suspendingPackage) + .putExtra(EXTRA_UNSUSPEND_INTENT, onUnsuspend) .putExtra(EXTRA_ACTIVITY_OPTIONS, options) .putExtra(Intent.EXTRA_USER_ID, userId) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index f8c51666d19a..23f2db2835bb 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -53,6 +53,7 @@ + diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index a81565a81670..5ccfcf42b798 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -4919,6 +4919,8 @@ Learn more + + Unpause app Turn on work profile? diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 669b41e53ba1..2090a29617d2 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3034,6 +3034,7 @@ + diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index 26245b15f92b..28298cb51f6e 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -634,8 +634,11 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku final SuspendDialogInfo dialogInfo = mPackageManagerInternal.getSuspendedDialogInfo(providerPackage, suspendingPackage, providerUserId); + // TODO(b/148035643): Send the original widget intent or ACTION_MAIN as an + // IntentSender to SuspendedAppActivity. onClickIntent = SuspendedAppActivity.createSuspendedAppInterceptIntent( - providerPackage, suspendingPackage, dialogInfo, null, providerUserId); + providerPackage, suspendingPackage, dialogInfo, null, null, + providerUserId); } } else if (provider.maskedByQuietProfile) { showBadge = true; diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java index d61d29d1084e..76aa1d115ef6 100644 --- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java +++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java @@ -178,6 +178,11 @@ class ActivityStartInterceptor { return interceptWorkProfileChallengeIfNeeded(); } + private boolean hasCrossProfileAnimation() { + return mActivityOptions != null + && mActivityOptions.getAnimationType() == ANIM_OPEN_CROSS_PROFILE_APPS; + } + /** * If the activity option is the {@link ActivityOptions#ANIM_OPEN_CROSS_PROFILE_APPS} one, * defer the animation until the original intent is started. @@ -185,8 +190,7 @@ class ActivityStartInterceptor { * @return the activity option used to start the original intent. */ private Bundle deferCrossProfileAppsAnimationIfNecessary() { - if (mActivityOptions != null - && mActivityOptions.getAnimationType() == ANIM_OPEN_CROSS_PROFILE_APPS) { + if (hasCrossProfileAnimation()) { mActivityOptions = null; return ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle(); } @@ -255,9 +259,13 @@ class ActivityStartInterceptor { } final SuspendDialogInfo dialogInfo = pmi.getSuspendedDialogInfo(suspendedPackage, suspendingPackage, mUserId); + final Bundle crossProfileOptions = hasCrossProfileAnimation() + ? ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle() + : null; + final IntentSender target = createIntentSenderForOriginalIntent(mCallingUid, + FLAG_IMMUTABLE); mIntent = SuspendedAppActivity.createSuspendedAppInterceptIntent(suspendedPackage, - suspendingPackage, dialogInfo, deferCrossProfileAppsAnimationIfNecessary(), - mUserId); + suspendingPackage, dialogInfo, crossProfileOptions, target, mUserId); mCallingPid = mRealCallingPid; mCallingUid = mRealCallingUid; mResolvedType = null; diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java index 8329227360e7..cf51fa31fad3 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java @@ -20,6 +20,8 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; +import static android.content.pm.SuspendDialogInfo.BUTTON_ACTION_MORE_DETAILS; +import static android.content.pm.SuspendDialogInfo.BUTTON_ACTION_UNSUSPEND; import static android.content.res.Resources.ID_NULL; import static org.hamcrest.CoreMatchers.is; @@ -217,6 +219,7 @@ public class PackageManagerSettingsTests { assertThat(params.dialogInfo.getTitleResId(), is(ID_NULL)); assertThat(params.dialogInfo.getIconResId(), is(TEST_RESOURCE_ID)); assertThat(params.dialogInfo.getNeutralButtonTextResId(), is(ID_NULL)); + assertThat(params.dialogInfo.getNeutralButtonAction(), is(BUTTON_ACTION_MORE_DETAILS)); assertThat(params.dialogInfo.getDialogMessageResId(), is(ID_NULL)); } @@ -243,12 +246,14 @@ public class PackageManagerSettingsTests { .setTitle(0x11220002) .setMessage("1st message") .setNeutralButtonText(0x11220003) + .setNeutralButtonAction(BUTTON_ACTION_MORE_DETAILS) .build(); final SuspendDialogInfo dialogInfo2 = new SuspendDialogInfo.Builder() .setIcon(0x22220001) .setTitle(0x22220002) .setMessage("2nd message") .setNeutralButtonText(0x22220003) + .setNeutralButtonAction(BUTTON_ACTION_UNSUSPEND) .build(); ps1.addOrUpdateSuspension("suspendingPackage1", dialogInfo1, appExtras1, launcherExtras1, diff --git a/services/tests/servicestests/src/com/android/server/pm/SuspendDialogInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/SuspendDialogInfoTest.java index 7eccd6728533..322e448d983f 100644 --- a/services/tests/servicestests/src/com/android/server/pm/SuspendDialogInfoTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/SuspendDialogInfoTest.java @@ -16,6 +16,9 @@ package com.android.server.pm; +import static android.content.pm.SuspendDialogInfo.BUTTON_ACTION_MORE_DETAILS; +import static android.content.pm.SuspendDialogInfo.BUTTON_ACTION_UNSUSPEND; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNull; @@ -39,7 +42,8 @@ public class SuspendDialogInfoTest { .setIcon(VALID_TEST_RES_ID_1) .setTitle(VALID_TEST_RES_ID_1) .setMessage(VALID_TEST_RES_ID_1) - .setNeutralButtonText(VALID_TEST_RES_ID_1); + .setNeutralButtonText(VALID_TEST_RES_ID_1) + .setNeutralButtonAction(BUTTON_ACTION_MORE_DETAILS); } @Test @@ -72,6 +76,25 @@ public class SuspendDialogInfoTest { assertNotEquals(dialogBuilder1.build(), dialogBuilder2.build()); } + @Test + public void equalsComparesButtonAction() { + final SuspendDialogInfo.Builder dialogBuilder1 = createDefaultDialogBuilder(); + final SuspendDialogInfo.Builder dialogBuilder2 = createDefaultDialogBuilder(); + assertEquals(dialogBuilder1.build(), dialogBuilder2.build()); + // Only button action is different + dialogBuilder2.setNeutralButtonAction(BUTTON_ACTION_UNSUSPEND); + assertNotEquals(dialogBuilder1.build(), dialogBuilder2.build()); + } + + @Test + public void defaultButtonAction() { + final SuspendDialogInfo.Builder dialogBuilder = new SuspendDialogInfo.Builder() + .setIcon(VALID_TEST_RES_ID_1) + .setTitle(VALID_TEST_RES_ID_1) + .setMessage(VALID_TEST_RES_ID_1); + assertEquals(BUTTON_ACTION_MORE_DETAILS, dialogBuilder.build().getNeutralButtonAction()); + } + @Test public void equalsComparesMessageIds() { final SuspendDialogInfo.Builder dialogBuilder1 = createDefaultDialogBuilder(); -- cgit v1.2.3-59-g8ed1b