summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xapi/system-current.txt4
-rw-r--r--core/java/android/content/Intent.java29
-rw-r--r--core/java/android/content/pm/SuspendDialogInfo.java102
-rw-r--r--core/java/com/android/internal/app/SuspendedAppActivity.java119
-rw-r--r--core/res/AndroidManifest.xml1
-rw-r--r--core/res/res/values/strings.xml2
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java5
-rw-r--r--services/core/java/com/android/server/wm/ActivityStartInterceptor.java16
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/SuspendDialogInfoTest.java25
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();