From 241955129a0a4d5e89436e8e557f830299a74e61 Mon Sep 17 00:00:00 2001 From: Suprabh Shukla Date: Wed, 24 Mar 2021 15:21:19 -0700 Subject: [PATCH] Accept strings for more parameters in Suspend Dialog Allowing passing raw texts offers more flexibility to callers in some cases. However, in this case, the callers must handle locale changes on their own. Test: atest FrameworksServicesTests:SuspendDialogInfoTest atest FrameworksServicesTests:PackageManagerSettingsTest atest CtsSuspendAppsTestCases:DialogTests Fixes: 170653551 Bug: 170653208 Change-Id: Iaa51e1d3f260ad553db8b33fddc935c478a40c9c --- core/api/system-current.txt | 2 + .../android/content/pm/SuspendDialogInfo.java | 91 ++++++++++++++++++- .../internal/app/SuspendedAppActivity.java | 36 +++++--- .../pm/PackageManagerSettingsTests.java | 4 +- .../server/pm/SuspendDialogInfoTest.java | 68 +++++++++++++- 5 files changed, 180 insertions(+), 21 deletions(-) diff --git a/core/api/system-current.txt b/core/api/system-current.txt index edc6ab8767a58..1c629119f0a62 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -2804,7 +2804,9 @@ package android.content.pm { 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 setNeutralButtonText(@NonNull String); method @NonNull public android.content.pm.SuspendDialogInfo.Builder setTitle(@StringRes int); + method @NonNull public android.content.pm.SuspendDialogInfo.Builder setTitle(@NonNull String); } } diff --git a/core/java/android/content/pm/SuspendDialogInfo.java b/core/java/android/content/pm/SuspendDialogInfo.java index 60f321883e985..23945eebf546a 100644 --- a/core/java/android/content/pm/SuspendDialogInfo.java +++ b/core/java/android/content/pm/SuspendDialogInfo.java @@ -65,16 +65,20 @@ public final class SuspendDialogInfo implements Parcelable { private static final String TAG = SuspendDialogInfo.class.getSimpleName(); private static final String XML_ATTR_ICON_RES_ID = "iconResId"; private static final String XML_ATTR_TITLE_RES_ID = "titleResId"; + private static final String XML_ATTR_TITLE = "title"; 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_TEXT = "buttonText"; private static final String XML_ATTR_BUTTON_ACTION = "buttonAction"; private final int mIconResId; private final int mTitleResId; + private final String mTitle; private final int mDialogMessageResId; private final String mDialogMessage; private final int mNeutralButtonTextResId; + private final String mNeutralButtonText; private final int mNeutralButtonAction; /** @@ -128,6 +132,16 @@ public final class SuspendDialogInfo implements Parcelable { return mTitleResId; } + /** + * @return the title to be shown on the dialog. Returns {@code null} if {@link #getTitleResId()} + * returns a valid resource id + * @hide + */ + @Nullable + public String getTitle() { + return mTitle; + } + /** * @return the resource id of the text to be shown in the dialog's body * @hide @@ -148,7 +162,7 @@ public final class SuspendDialogInfo implements Parcelable { } /** - * @return the text to be shown + * @return the text to be shown on the neutral button * @hide */ @StringRes @@ -156,6 +170,16 @@ public final class SuspendDialogInfo implements Parcelable { return mNeutralButtonTextResId; } + /** + * @return the text to be shown on the neutral button. Returns {@code null} if + * {@link #getNeutralButtonTextResId()} returns a valid resource id + * @hide + */ + @Nullable + public String getNeutralButtonText() { + return mNeutralButtonText; + } + /** * @return The {@link ButtonAction} that happens on tapping this button * @hide @@ -174,6 +198,8 @@ public final class SuspendDialogInfo implements Parcelable { } if (mTitleResId != ID_NULL) { out.attributeInt(null, XML_ATTR_TITLE_RES_ID, mTitleResId); + } else { + XmlUtils.writeStringAttribute(out, XML_ATTR_TITLE, mTitle); } if (mDialogMessageResId != ID_NULL) { out.attributeInt(null, XML_ATTR_DIALOG_MESSAGE_RES_ID, mDialogMessageResId); @@ -182,6 +208,8 @@ public final class SuspendDialogInfo implements Parcelable { } if (mNeutralButtonTextResId != ID_NULL) { out.attributeInt(null, XML_ATTR_BUTTON_TEXT_RES_ID, mNeutralButtonTextResId); + } else { + XmlUtils.writeStringAttribute(out, XML_ATTR_BUTTON_TEXT, mNeutralButtonText); } out.attributeInt(null, XML_ATTR_BUTTON_ACTION, mNeutralButtonAction); } @@ -194,8 +222,10 @@ public final class SuspendDialogInfo implements Parcelable { try { final int iconId = in.getAttributeInt(null, XML_ATTR_ICON_RES_ID, ID_NULL); final int titleId = in.getAttributeInt(null, XML_ATTR_TITLE_RES_ID, ID_NULL); + final String title = XmlUtils.readStringAttribute(in, XML_ATTR_TITLE); final int buttonTextId = in.getAttributeInt(null, XML_ATTR_BUTTON_TEXT_RES_ID, ID_NULL); + final String buttonText = XmlUtils.readStringAttribute(in, XML_ATTR_BUTTON_TEXT); final int buttonAction = in.getAttributeInt(null, XML_ATTR_BUTTON_ACTION, BUTTON_ACTION_MORE_DETAILS); final int dialogMessageResId = @@ -207,9 +237,13 @@ public final class SuspendDialogInfo implements Parcelable { } if (titleId != ID_NULL) { dialogInfoBuilder.setTitle(titleId); + } else if (title != null) { + dialogInfoBuilder.setTitle(title); } if (buttonTextId != ID_NULL) { dialogInfoBuilder.setNeutralButtonText(buttonTextId); + } else if (buttonText != null) { + dialogInfoBuilder.setNeutralButtonText(buttonText); } if (dialogMessageResId != ID_NULL) { dialogInfoBuilder.setMessage(dialogMessageResId); @@ -227,7 +261,9 @@ public final class SuspendDialogInfo implements Parcelable { public int hashCode() { int hashCode = mIconResId; hashCode = 31 * hashCode + mTitleResId; + hashCode = 31 * hashCode + Objects.hashCode(mTitle); hashCode = 31 * hashCode + mNeutralButtonTextResId; + hashCode = 31 * hashCode + Objects.hashCode(mNeutralButtonText); hashCode = 31 * hashCode + mDialogMessageResId; hashCode = 31 * hashCode + Objects.hashCode(mDialogMessage); hashCode = 31 * hashCode + mNeutralButtonAction; @@ -245,10 +281,12 @@ public final class SuspendDialogInfo implements Parcelable { final SuspendDialogInfo otherDialogInfo = (SuspendDialogInfo) obj; return mIconResId == otherDialogInfo.mIconResId && mTitleResId == otherDialogInfo.mTitleResId + && Objects.equals(mTitle, otherDialogInfo.mTitle) && mDialogMessageResId == otherDialogInfo.mDialogMessageResId + && Objects.equals(mDialogMessage, otherDialogInfo.mDialogMessage) && mNeutralButtonTextResId == otherDialogInfo.mNeutralButtonTextResId - && mNeutralButtonAction == otherDialogInfo.mNeutralButtonAction - && Objects.equals(mDialogMessage, otherDialogInfo.mDialogMessage); + && Objects.equals(mNeutralButtonText, otherDialogInfo.mNeutralButtonText) + && mNeutralButtonAction == otherDialogInfo.mNeutralButtonAction; } @NonNull @@ -264,11 +302,19 @@ public final class SuspendDialogInfo implements Parcelable { builder.append("mTitleResId = 0x"); builder.append(Integer.toHexString(mTitleResId)); builder.append(" "); + } else if (mTitle != null) { + builder.append("mTitle = \""); + builder.append(mTitle); + builder.append("\""); } if (mNeutralButtonTextResId != ID_NULL) { builder.append("mNeutralButtonTextResId = 0x"); builder.append(Integer.toHexString(mNeutralButtonTextResId)); builder.append(" "); + } else if (mNeutralButtonText != null) { + builder.append("mNeutralButtonText = \""); + builder.append(mNeutralButtonText); + builder.append("\""); } if (mDialogMessageResId != ID_NULL) { builder.append("mDialogMessageResId = 0x"); @@ -294,27 +340,33 @@ public final class SuspendDialogInfo implements Parcelable { public void writeToParcel(Parcel dest, int parcelableFlags) { dest.writeInt(mIconResId); dest.writeInt(mTitleResId); + dest.writeString(mTitle); dest.writeInt(mDialogMessageResId); dest.writeString(mDialogMessage); dest.writeInt(mNeutralButtonTextResId); + dest.writeString(mNeutralButtonText); dest.writeInt(mNeutralButtonAction); } private SuspendDialogInfo(Parcel source) { mIconResId = source.readInt(); mTitleResId = source.readInt(); + mTitle = source.readString(); mDialogMessageResId = source.readInt(); mDialogMessage = source.readString(); mNeutralButtonTextResId = source.readInt(); + mNeutralButtonText = source.readString(); mNeutralButtonAction = source.readInt(); } SuspendDialogInfo(Builder b) { mIconResId = b.mIconResId; mTitleResId = b.mTitleResId; + mTitle = (mTitleResId == ID_NULL) ? b.mTitle : null; mDialogMessageResId = b.mDialogMessageResId; mDialogMessage = (mDialogMessageResId == ID_NULL) ? b.mDialogMessage : null; mNeutralButtonTextResId = b.mNeutralButtonTextResId; + mNeutralButtonText = (mNeutralButtonTextResId == ID_NULL) ? b.mNeutralButtonText : null; mNeutralButtonAction = b.mNeutralButtonAction; } @@ -338,8 +390,10 @@ public final class SuspendDialogInfo implements Parcelable { private int mDialogMessageResId = ID_NULL; private String mDialogMessage; private int mTitleResId = ID_NULL; + private String mTitle; private int mIconResId = ID_NULL; private int mNeutralButtonTextResId = ID_NULL; + private String mNeutralButtonText; private int mNeutralButtonAction = BUTTON_ACTION_MORE_DETAILS; /** @@ -369,6 +423,21 @@ public final class SuspendDialogInfo implements Parcelable { return this; } + /** + * Set the title text of the dialog. Ignored if a resource id is set via + * {@link #setTitle(int)} + * + * @param title The title of the dialog. + * @return this builder object. + * @see #setTitle(int) + */ + @NonNull + public Builder setTitle(@NonNull String title) { + Preconditions.checkStringNotEmpty(title, "Title cannot be null or empty"); + mTitle = title; + return this; + } + /** * Set the text to show in the body of the dialog. Ignored if a resource id is set via * {@link #setMessage(int)}. @@ -426,6 +495,22 @@ public final class SuspendDialogInfo implements Parcelable { return this; } + /** + * Set the text to be shown on the neutral button. Ignored if a resource id is set via + * {@link #setNeutralButtonText(int)} + * + * @param neutralButtonText The title of the dialog. + * @return this builder object. + * @see #setNeutralButtonText(int) + */ + @NonNull + public Builder setNeutralButtonText(@NonNull String neutralButtonText) { + Preconditions.checkStringNotEmpty(neutralButtonText, + "Button text cannot be null or empty"); + mNeutralButtonText = neutralButtonText; + return this; + } + /** * Set the action expected to happen on neutral button tap. Defaults to * {@link #BUTTON_ACTION_MORE_DETAILS} if this is not provided. diff --git a/core/java/com/android/internal/app/SuspendedAppActivity.java b/core/java/com/android/internal/app/SuspendedAppActivity.java index 762297d15e6d9..86f29a8f07ef9 100644 --- a/core/java/com/android/internal/app/SuspendedAppActivity.java +++ b/core/java/com/android/internal/app/SuspendedAppActivity.java @@ -106,13 +106,17 @@ public class SuspendedAppActivity extends AlertActivity } private String resolveTitle() { - final int titleId = (mSuppliedDialogInfo != null) ? mSuppliedDialogInfo.getTitleResId() - : ID_NULL; - if (titleId != ID_NULL && mSuspendingAppResources != null) { - try { - return mSuspendingAppResources.getString(titleId); - } catch (Resources.NotFoundException nfe) { - Slog.e(TAG, "Could not resolve string resource id " + titleId); + if (mSuppliedDialogInfo != null) { + final int titleId = mSuppliedDialogInfo.getTitleResId(); + final String title = mSuppliedDialogInfo.getTitle(); + if (titleId != ID_NULL && mSuspendingAppResources != null) { + try { + return mSuspendingAppResources.getString(titleId); + } catch (Resources.NotFoundException nfe) { + Slog.e(TAG, "Could not resolve string resource id " + titleId); + } + } else if (title != null) { + return title; } } return getString(R.string.app_suspended_title); @@ -159,13 +163,17 @@ public class SuspendedAppActivity extends AlertActivity 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) { - try { - return mSuspendingAppResources.getString(buttonTextId); - } catch (Resources.NotFoundException nfe) { - Slog.e(TAG, "Could not resolve string resource id " + buttonTextId); + if (mSuppliedDialogInfo != null) { + final int buttonTextId = mSuppliedDialogInfo.getNeutralButtonTextResId(); + final String buttonText = mSuppliedDialogInfo.getNeutralButtonText(); + if (buttonTextId != ID_NULL && mSuspendingAppResources != null) { + try { + return mSuspendingAppResources.getString(buttonTextId); + } catch (Resources.NotFoundException nfe) { + Slog.e(TAG, "Could not resolve string resource id " + buttonTextId); + } + } else if (buttonText != null) { + return buttonText; } } return getString(defaultButtonTextId); 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 d63a4674a83db..a2311690744e3 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java @@ -287,7 +287,7 @@ public class PackageManagerSettingsTests { final SuspendDialogInfo dialogInfo1 = new SuspendDialogInfo.Builder() .setIcon(0x11220001) - .setTitle(0x11220002) + .setTitle("String Title") .setMessage("1st message") .setNeutralButtonText(0x11220003) .setNeutralButtonAction(BUTTON_ACTION_MORE_DETAILS) @@ -296,7 +296,7 @@ public class PackageManagerSettingsTests { .setIcon(0x22220001) .setTitle(0x22220002) .setMessage("2nd message") - .setNeutralButtonText(0x22220003) + .setNeutralButtonText("String button text") .setNeutralButtonAction(BUTTON_ACTION_UNSUSPEND) .build(); 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 322e448d983f5..826a8d446e1da 100644 --- a/services/tests/servicestests/src/com/android/server/pm/SuspendDialogInfoTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/SuspendDialogInfoTest.java @@ -57,7 +57,7 @@ public class SuspendDialogInfoTest { } @Test - public void equalsComparesTitle() { + public void equalsComparesTitleIds() { final SuspendDialogInfo.Builder dialogBuilder1 = createDefaultDialogBuilder(); final SuspendDialogInfo.Builder dialogBuilder2 = createDefaultDialogBuilder(); assertEquals(dialogBuilder1.build(), dialogBuilder2.build()); @@ -67,7 +67,39 @@ public class SuspendDialogInfoTest { } @Test - public void equalsComparesButtonText() { + public void equalsIgnoresTitleStringsWhenIdsSet() { + final SuspendDialogInfo.Builder dialogBuilder1 = new SuspendDialogInfo.Builder() + .setTitle(VALID_TEST_RES_ID_1) + .setTitle("1st title"); + final SuspendDialogInfo.Builder dialogBuilder2 = new SuspendDialogInfo.Builder() + .setTitle(VALID_TEST_RES_ID_1) + .setTitle("2nd title"); + // String titles different but should get be ignored when resource ids are set + assertEquals(dialogBuilder1.build(), dialogBuilder2.build()); + } + + @Test + public void equalsComparesTitleStringsWhenNoIdsSet() { + final SuspendDialogInfo.Builder dialogBuilder1 = new SuspendDialogInfo.Builder() + .setTitle("1st title"); + final SuspendDialogInfo.Builder dialogBuilder2 = new SuspendDialogInfo.Builder() + .setTitle("2nd title"); + // Both have different titles, which are not ignored as resource ids aren't set + assertNotEquals(dialogBuilder1.build(), dialogBuilder2.build()); + } + + @Test + public void titleStringClearedWhenResIdSet() { + final SuspendDialogInfo dialogInfo = new SuspendDialogInfo.Builder() + .setTitle(VALID_TEST_RES_ID_2) + .setTitle("Should be cleared on build") + .build(); + assertNull(dialogInfo.getTitle()); + assertEquals(VALID_TEST_RES_ID_2, dialogInfo.getTitleResId()); + } + + @Test + public void equalsComparesButtonTextIds() { final SuspendDialogInfo.Builder dialogBuilder1 = createDefaultDialogBuilder(); final SuspendDialogInfo.Builder dialogBuilder2 = createDefaultDialogBuilder(); assertEquals(dialogBuilder1.build(), dialogBuilder2.build()); @@ -76,6 +108,38 @@ public class SuspendDialogInfoTest { assertNotEquals(dialogBuilder1.build(), dialogBuilder2.build()); } + @Test + public void equalsIgnoresButtonStringsWhenIdsSet() { + final SuspendDialogInfo.Builder dialogBuilder1 = new SuspendDialogInfo.Builder() + .setNeutralButtonText(VALID_TEST_RES_ID_1) + .setNeutralButtonText("1st button text"); + final SuspendDialogInfo.Builder dialogBuilder2 = new SuspendDialogInfo.Builder() + .setNeutralButtonText(VALID_TEST_RES_ID_1) + .setNeutralButtonText("2nd button text"); + // Button strings different but should get be ignored when resource ids are set + assertEquals(dialogBuilder1.build(), dialogBuilder2.build()); + } + + @Test + public void equalsComparesButtonStringsWhenNoIdsSet() { + final SuspendDialogInfo.Builder dialogBuilder1 = new SuspendDialogInfo.Builder() + .setNeutralButtonText("1st button text"); + final SuspendDialogInfo.Builder dialogBuilder2 = new SuspendDialogInfo.Builder() + .setNeutralButtonText("2nd button text"); + // Both have different button texts, which are not ignored as resource ids aren't set + assertNotEquals(dialogBuilder1.build(), dialogBuilder2.build()); + } + + @Test + public void buttonStringClearedWhenResIdSet() { + final SuspendDialogInfo dialogInfo = new SuspendDialogInfo.Builder() + .setNeutralButtonText(VALID_TEST_RES_ID_2) + .setNeutralButtonText("Should be cleared on build") + .build(); + assertNull(dialogInfo.getNeutralButtonText()); + assertEquals(VALID_TEST_RES_ID_2, dialogInfo.getNeutralButtonTextResId()); + } + @Test public void equalsComparesButtonAction() { final SuspendDialogInfo.Builder dialogBuilder1 = createDefaultDialogBuilder();