From 72ec44830708f8bc6607be20f47ddc26291c2e44 Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Sun, 12 Feb 2017 03:21:25 -0700 Subject: [PATCH] Switch RecoverableSE over to using RemoteAction. Now that RemoteAction supports PendingIntent, we should be using it instead of rolling our own fields. Continue offering legacy constructor for some existing unit tests. Create a notification channel for each remote app that throws at a client app. All apps targeting O must now use channels, which give the user better control over notifications. Mention in docs that strings should be localized. Test: builds Bug: 33749182, 35012253, 34676491 Change-Id: Ic57e32025bc6caf784d3746d9f97f6595b0edb69 --- api/current.txt | 5 +- api/system-current.txt | 5 +- api/test-current.txt | 5 +- .../app/RecoverableSecurityException.java | 74 ++++++++++--------- 4 files changed, 47 insertions(+), 42 deletions(-) diff --git a/api/current.txt b/api/current.txt index 302b99a8ff836..39afdd1b68dd1 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5674,10 +5674,9 @@ package android.app { } public final class RecoverableSecurityException extends java.lang.SecurityException implements android.os.Parcelable { - ctor public RecoverableSecurityException(java.lang.Throwable, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent); + ctor public RecoverableSecurityException(java.lang.Throwable, java.lang.CharSequence, android.app.RemoteAction); method public int describeContents(); - method public android.app.PendingIntent getUserAction(); - method public java.lang.CharSequence getUserActionTitle(); + method public android.app.RemoteAction getUserAction(); method public java.lang.CharSequence getUserMessage(); method public void showAsDialog(android.app.Activity); method public void showAsNotification(android.content.Context); diff --git a/api/system-current.txt b/api/system-current.txt index 486944008408f..0e46c33e919d7 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -5870,10 +5870,9 @@ package android.app { } public final class RecoverableSecurityException extends java.lang.SecurityException implements android.os.Parcelable { - ctor public RecoverableSecurityException(java.lang.Throwable, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent); + ctor public RecoverableSecurityException(java.lang.Throwable, java.lang.CharSequence, android.app.RemoteAction); method public int describeContents(); - method public android.app.PendingIntent getUserAction(); - method public java.lang.CharSequence getUserActionTitle(); + method public android.app.RemoteAction getUserAction(); method public java.lang.CharSequence getUserMessage(); method public void showAsDialog(android.app.Activity); method public void showAsNotification(android.content.Context); diff --git a/api/test-current.txt b/api/test-current.txt index 7c06b9e67f2c8..b2e4d1529f1d3 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -5685,10 +5685,9 @@ package android.app { } public final class RecoverableSecurityException extends java.lang.SecurityException implements android.os.Parcelable { - ctor public RecoverableSecurityException(java.lang.Throwable, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent); + ctor public RecoverableSecurityException(java.lang.Throwable, java.lang.CharSequence, android.app.RemoteAction); method public int describeContents(); - method public android.app.PendingIntent getUserAction(); - method public java.lang.CharSequence getUserActionTitle(); + method public android.app.RemoteAction getUserAction(); method public java.lang.CharSequence getUserMessage(); method public void showAsDialog(android.app.Activity); method public void showAsNotification(android.content.Context); diff --git a/core/java/android/app/RecoverableSecurityException.java b/core/java/android/app/RecoverableSecurityException.java index 1f015a607be8d..540d1cda1edd1 100644 --- a/core/java/android/app/RecoverableSecurityException.java +++ b/core/java/android/app/RecoverableSecurityException.java @@ -17,6 +17,8 @@ package android.app; import android.content.Context; +import android.content.res.Resources; +import android.graphics.drawable.Icon; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -40,13 +42,12 @@ public final class RecoverableSecurityException extends SecurityException implem private static final String TAG = "RecoverableSecurityException"; private final CharSequence mUserMessage; - private final CharSequence mUserActionTitle; - private final PendingIntent mUserAction; + private final RemoteAction mUserAction; /** {@hide} */ public RecoverableSecurityException(Parcel in) { - this(new SecurityException(in.readString()), in.readCharSequence(), in.readCharSequence(), - PendingIntent.CREATOR.createFromParcel(in)); + this(new SecurityException(in.readString()), in.readCharSequence(), + RemoteAction.CREATOR.createFromParcel(in)); } /** @@ -56,26 +57,35 @@ public final class RecoverableSecurityException extends SecurityException implem * audiences. * @param userMessage short message describing the issue for end user * audiences, which may be shown in a notification or dialog. - * This should be less than 64 characters. For example: PIN - * required to access Document.pdf - * @param userActionTitle short title describing the primary action. This - * should be less than 24 characters. For example: Enter - * PIN - * @param userAction primary action that will initiate the recovery. This - * must launch an activity that is expected to set + * This should be localized and less than 64 characters. For + * example: PIN required to access Document.pdf + * @param userAction primary action that will initiate the recovery. The + * title should be localized and less than 24 characters. For + * example: Enter PIN. This action must launch an + * activity that is expected to set * {@link Activity#setResult(int)} before finishing to * communicate the final status of the recovery. For example, * apps that observe {@link Activity#RESULT_OK} may choose to * immediately retry their operation. */ public RecoverableSecurityException(Throwable cause, CharSequence userMessage, - CharSequence userActionTitle, PendingIntent userAction) { + RemoteAction userAction) { super(cause.getMessage()); mUserMessage = Preconditions.checkNotNull(userMessage); - mUserActionTitle = Preconditions.checkNotNull(userActionTitle); mUserAction = Preconditions.checkNotNull(userAction); } + /** {@hide} */ + @Deprecated + public RecoverableSecurityException(Throwable cause, CharSequence userMessage, + CharSequence userActionTitle, PendingIntent userAction) { + this(cause, userMessage, + new RemoteAction( + Icon.createWithResource("android", + com.android.internal.R.drawable.ic_restart), + userActionTitle, userActionTitle, userAction)); + } + /** * Return short message describing the issue for end user audiences, which * may be shown in a notification or dialog. @@ -84,17 +94,10 @@ public final class RecoverableSecurityException extends SecurityException implem return mUserMessage; } - /** - * Return short title describing the primary action. - */ - public CharSequence getUserActionTitle() { - return mUserActionTitle; - } - /** * Return primary action that will initiate the recovery. */ - public PendingIntent getUserAction() { + public RemoteAction getUserAction() { return mUserAction; } @@ -113,15 +116,21 @@ public final class RecoverableSecurityException extends SecurityException implem * remote UID; notifications from older exceptions will always be replaced. */ public void showAsNotification(Context context) { - final Notification.Builder builder = new Notification.Builder(context) - .setSmallIcon(com.android.internal.R.drawable.ic_print_error) - .setContentTitle(mUserActionTitle) - .setContentText(mUserMessage) - .setContentIntent(mUserAction) - .setCategory(Notification.CATEGORY_ERROR); - final NotificationManager nm = context.getSystemService(NotificationManager.class); - nm.notify(TAG, mUserAction.getCreatorUid(), builder.build()); + + // Create a channel per-sender, since we don't want one poorly behaved + // remote app to cause all of our notifications to be blocked + final String tag = TAG + "_" + mUserAction.getActionIntent().getCreatorUid(); + nm.createNotificationChannel(new NotificationChannel(tag, TAG, + NotificationManager.IMPORTANCE_DEFAULT)); + + final Notification.Builder builder = new Notification.Builder(context, tag) + .setSmallIcon(com.android.internal.R.drawable.ic_print_error) + .setContentTitle(mUserAction.getTitle()) + .setContentText(mUserMessage) + .setContentIntent(mUserAction.getActionIntent()) + .setCategory(Notification.CATEGORY_ERROR); + nm.notify(tag, 0, builder.build()); } /** @@ -144,7 +153,7 @@ public final class RecoverableSecurityException extends SecurityException implem args.putParcelable(TAG, this); dialog.setArguments(args); - final String tag = TAG + "_" + mUserAction.getCreatorUid(); + final String tag = TAG + "_" + mUserAction.getActionIntent().getCreatorUid(); final FragmentManager fm = activity.getFragmentManager(); final FragmentTransaction ft = fm.beginTransaction(); final Fragment old = fm.findFragmentByTag(tag); @@ -162,9 +171,9 @@ public final class RecoverableSecurityException extends SecurityException implem final RecoverableSecurityException e = getArguments().getParcelable(TAG); return new AlertDialog.Builder(getActivity()) .setMessage(e.mUserMessage) - .setPositiveButton(e.mUserActionTitle, (dialog, which) -> { + .setPositiveButton(e.mUserAction.getTitle(), (dialog, which) -> { try { - e.mUserAction.send(); + e.mUserAction.getActionIntent().send(); } catch (PendingIntent.CanceledException ignored) { } }) @@ -182,7 +191,6 @@ public final class RecoverableSecurityException extends SecurityException implem public void writeToParcel(Parcel dest, int flags) { dest.writeString(getMessage()); dest.writeCharSequence(mUserMessage); - dest.writeCharSequence(mUserActionTitle); mUserAction.writeToParcel(dest, flags); }