diff options
| author | 2020-01-24 21:08:21 +0000 | |
|---|---|---|
| committer | 2020-01-24 21:08:21 +0000 | |
| commit | 30295f54218bccb71a34c007222d0dc54fa7e69c (patch) | |
| tree | df408b3a5b464be2f7ba2db4e65356fc19e49e1b | |
| parent | 435384cc29d928e46191ed3a0db0e63dadeb9215 (diff) | |
| parent | 0cb5713fd7faf31aa41d8a1c8300268c10833125 (diff) | |
Merge "Add an unsuspend button option to SuspendDialogInfo"
11 files changed, 268 insertions, 41 deletions
diff --git a/api/system-current.txt b/api/system-current.txt index a4e9136e5dce..5691bf3fddbc 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -1830,6 +1830,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"; @@ -2328,6 +2329,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<android.content.pm.SuspendDialogInfo> CREATOR; } @@ -2337,6 +2340,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 5852a93ea221..a07625315a2d 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; @@ -2667,6 +2668,34 @@ public class Intent implements Parcelable, Cloneable { "android.intent.action.SHOW_SUSPENDED_APP_DETAILS"; /** + * Broadcast Action: Sent to indicate that the user unsuspended a package. + * + * <p>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)}. + * + * <p>Includes an extra {@link #EXTRA_PACKAGE_NAME} which is the name of the package that just + * got unsuspended. + * + * <p class="note">This is a protected intent that can only be sent + * by the system. <em>This will be delivered to {@link BroadcastReceiver} components declared in + * the manifest.</em> + * + * @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. * * <p class="note">This is a protected intent that can only be sent 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: * <ul> * <li>The dialog icon, by providing a resource id. * <li>The title text, by providing a resource id. * <li>The text of the dialog's body, by providing a resource id or a string. - * <li>The text on the neutral button which starts the - * {@link android.content.Intent#ACTION_SHOW_SUSPENDED_APP_DETAILS SHOW_SUSPENDED_APP_DETAILS} - * activity, by providing a resource id. + * <li>The text on the neutral button by providing a resource id. + * <li>The action performed on tapping the neutral button. Only {@link #BUTTON_ACTION_UNSUSPEND} + * and {@link #BUTTON_ACTION_MORE_DETAILS} are currently supported. * </ul> * 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 @@ -121,6 +158,15 @@ public final class SuspendDialogInfo implements Parcelable { } /** + * @return The {@link ButtonAction} that happens on tapping this button + * @hide + */ + @ButtonAction + public int getNeutralButtonAction() { + return mNeutralButtonAction; + } + + /** * @hide */ public void saveToXml(XmlSerializer out) throws IOException { @@ -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<SuspendDialogInfo> CREATOR = new Creator<SuspendDialogInfo>() { + public static final @NonNull Creator<SuspendDialogInfo> CREATOR = + new Creator<SuspendDialogInfo>() { @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. * <p> * 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. @@ -368,6 +428,22 @@ public final class SuspendDialogInfo implements Parcelable { } /** + * 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. * * @return The {@link SuspendDialogInfo} object built using this builder. 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 32ba6ecd79ee..c069c82d3a1d 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -53,6 +53,7 @@ <protected-broadcast android:name="android.intent.action.PACKAGE_VERIFIED" /> <protected-broadcast android:name="android.intent.action.PACKAGES_SUSPENDED" /> <protected-broadcast android:name="android.intent.action.PACKAGES_UNSUSPENDED" /> + <protected-broadcast android:name="android.intent.action.PACKAGE_UNSUSPENDED_MANUALLY" /> <protected-broadcast android:name="android.intent.action.DISTRACTING_PACKAGES_CHANGED" /> <protected-broadcast android:name="android.intent.action.ACTION_PREFERRED_ACTIVITY_CHANGED" /> <protected-broadcast android:name="android.intent.action.UID_REMOVED" /> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index f7a9922c4480..209b744f1301 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -4923,6 +4923,8 @@ </string> <!-- Title of the button to show users more details about why the app has been suspended [CHAR LIMIT=50]--> <string name="app_suspended_more_details">Learn more</string> + <!-- Title of the button to unsuspend a suspended app immediately [CHAR LIMIT=50]--> + <string name="app_suspended_unsuspend_message">Unpause app</string> <!-- Title of a dialog. The string is asking if the user wants to turn on their work profile, which contains work apps that are managed by their employer. "Work" is an adjective. [CHAR LIMIT=30] --> <string name="work_mode_off_title">Turn on work profile?</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 2e33b9b7802c..90074b9b38b1 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3039,6 +3039,7 @@ <java-symbol type="string" name="app_suspended_title" /> <java-symbol type="string" name="app_suspended_more_details" /> + <java-symbol type="string" name="app_suspended_unsuspend_message" /> <java-symbol type="string" name="app_suspended_default_message" /> <!-- Used internally for assistant to launch activity transitions --> 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 df97caa76d2d..2fb0ac5fbeaa 100644 --- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java +++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java @@ -174,6 +174,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. @@ -181,8 +186,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(); } @@ -251,9 +255,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 @@ -73,6 +77,25 @@ public class SuspendDialogInfoTest { } @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(); final SuspendDialogInfo.Builder dialogBuilder2 = createDefaultDialogBuilder(); |