diff --git a/core/java/android/app/AlertDialog.java b/core/java/android/app/AlertDialog.java index a44bd0305875f..07b4b9c39e1e9 100644 --- a/core/java/android/app/AlertDialog.java +++ b/core/java/android/app/AlertDialog.java @@ -16,8 +16,6 @@ package android.app; -import com.android.internal.app.AlertController; - import android.annotation.ArrayRes; import android.annotation.AttrRes; import android.annotation.DrawableRes; @@ -30,17 +28,19 @@ import android.database.Cursor; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Message; +import android.text.Layout; +import android.text.method.MovementMethod; import android.util.TypedValue; import android.view.ContextThemeWrapper; import android.view.KeyEvent; import android.view.View; -import android.view.WindowManager; import android.widget.AdapterView; import android.widget.Button; import android.widget.ListAdapter; import android.widget.ListView; import com.android.internal.R; +import com.android.internal.app.AlertController; /** * A subclass of Dialog that can display one, two or three buttons. If you only want to @@ -54,7 +54,7 @@ import com.android.internal.R; * * *

The AlertDialog class takes care of automatically setting - * {@link WindowManager.LayoutParams#FLAG_ALT_FOCUSABLE_IM + * {@link android.view.WindowManager.LayoutParams#FLAG_ALT_FOCUSABLE_IM * WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM} for you based on whether * any views in the dialog return true from {@link View#onCheckIsTextEditor() * View.onCheckIsTextEditor()}. Generally you want this set for a Dialog @@ -266,6 +266,17 @@ public class AlertDialog extends Dialog implements DialogInterface { mAlert.setMessage(message); } + /** @hide */ + public void setMessageMovementMethod(MovementMethod movementMethod) { + mAlert.setMessageMovementMethod(movementMethod); + } + + /** @hide */ + public void setMessageHyphenationFrequency( + @Layout.HyphenationFrequency int hyphenationFrequency) { + mAlert.setMessageHyphenationFrequency(hyphenationFrequency); + } + /** * Set the view to display in that dialog. */ diff --git a/core/java/com/android/internal/app/AlertController.java b/core/java/com/android/internal/app/AlertController.java index 46cb5461b6824..732172111b805 100644 --- a/core/java/com/android/internal/app/AlertController.java +++ b/core/java/com/android/internal/app/AlertController.java @@ -30,7 +30,10 @@ import android.database.Cursor; import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.Message; +import android.text.Layout; import android.text.TextUtils; +import android.text.method.LinkMovementMethod; +import android.text.method.MovementMethod; import android.util.AttributeSet; import android.util.TypedValue; import android.view.Gravity; @@ -101,6 +104,9 @@ public class AlertController { private ImageView mIconView; private TextView mTitleView; protected TextView mMessageView; + private MovementMethod mMessageMovementMethod; + @Layout.HyphenationFrequency + private Integer mMessageHyphenationFrequency; private View mCustomTitleView; private boolean mForceInverseBackground; @@ -290,6 +296,21 @@ public class AlertController { } } + public void setMessageMovementMethod(MovementMethod movementMethod) { + mMessageMovementMethod = movementMethod; + if (mMessageView != null) { + mMessageView.setMovementMethod(movementMethod); + } + } + + public void setMessageHyphenationFrequency( + @Layout.HyphenationFrequency int hyphenationFrequency) { + mMessageHyphenationFrequency = hyphenationFrequency; + if (mMessageView != null) { + mMessageView.setHyphenationFrequency(hyphenationFrequency); + } + } + /** * Set the view resource to display in the dialog. */ @@ -676,6 +697,12 @@ public class AlertController { if (mMessage != null) { mMessageView.setText(mMessage); + if (mMessageMovementMethod != null) { + mMessageView.setMovementMethod(mMessageMovementMethod); + } + if (mMessageHyphenationFrequency != null) { + mMessageView.setHyphenationFrequency(mMessageHyphenationFrequency); + } } else { mMessageView.setVisibility(View.GONE); mScrollView.removeView(mMessageView); diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 395b26935838f..e5bb587cddf4c 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -4514,7 +4514,10 @@ Deleted by your admin - + + To extend your battery life, Battery Saver turns off some device features and restricts apps. Learn More + + To extend your battery life, Battery Saver turns off some device features and restricts apps. diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index d92ae3a9f0d46..c0dadcf3f1af2 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3371,4 +3371,5 @@ + diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index d82b00c316a83..697ab06afb199 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2223,4 +2223,6 @@ Got it + + diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java index 8cc47eba0a9c1..5b76627d9d3cd 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java @@ -19,17 +19,29 @@ package com.android.systemui.power; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; +import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.media.AudioAttributes; +import android.net.Uri; import android.os.Handler; import android.os.Looper; import android.os.PowerManager; import android.os.UserHandle; -import androidx.annotation.VisibleForTesting; +import android.text.Annotation; +import android.text.Layout; +import android.text.SpannableString; +import android.text.SpannableStringBuilder; +import android.text.TextPaint; +import android.text.TextUtils; +import android.text.method.LinkMovementMethod; +import android.text.style.URLSpan; +import android.util.Log; import android.util.Slog; +import android.view.View; +import androidx.annotation.VisibleForTesting; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.settingslib.Utils; @@ -42,6 +54,8 @@ import com.android.systemui.util.NotificationChannels; import java.io.PrintWriter; import java.text.NumberFormat; +import java.util.Locale; +import java.util.Objects; public class PowerNotificationWarnings implements PowerUI.WarningsUI { private static final String TAG = PowerUI.TAG + ".Notification"; @@ -87,6 +101,8 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { private static final String SETTINGS_ACTION_OPEN_BATTERY_SAVER_SETTING = "android.settings.BATTERY_SAVER_SETTINGS"; + private static final String BATTERY_SAVER_DESCRIPTION_URL_KEY = "url"; + private static final AudioAttributes AUDIO_ATTRIBUTES = new AudioAttributes.Builder() .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) @@ -463,7 +479,16 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { if (mSaverConfirmation != null) return; final SystemUIDialog d = new SystemUIDialog(mContext); d.setTitle(R.string.battery_saver_confirmation_title); - d.setMessage(com.android.internal.R.string.battery_saver_description); + d.setMessage(getBatterySaverDescription()); + + // Sad hack for http://b/78261259 and http://b/78298335. Otherwise "Battery" may be split + // into "Bat-tery". + if (isEnglishLocale()) { + d.setMessageHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE); + } + // We need to set LinkMovementMethod to make the link clickable. + d.setMessageMovementMethod(LinkMovementMethod.getInstance()); + d.setNegativeButton(android.R.string.cancel, null); d.setPositiveButton(R.string.battery_saver_confirmation_ok, (dialog, which) -> setSaverMode(true, false)); @@ -473,6 +498,79 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { mSaverConfirmation = d; } + private boolean isEnglishLocale() { + return Objects.equals(Locale.getDefault().getLanguage(), + Locale.ENGLISH.getLanguage()); + } + + /** + * Generates the message for the "want to start battery saver?" dialog with a "learn more" link. + */ + private CharSequence getBatterySaverDescription() { + final String learnMoreUrl = mContext.getText( + R.string.help_uri_battery_saver_learn_more_link_target).toString(); + + // If there's no link, use the string with no "learn more". + if (TextUtils.isEmpty(learnMoreUrl)) { + return mContext.getText( + com.android.internal.R.string.battery_saver_description); + } + + // If we have a link, use the string with the "learn more" link. + final CharSequence rawText = mContext.getText( + com.android.internal.R.string.battery_saver_description_with_learn_more); + final SpannableString message = new SpannableString(rawText); + final SpannableStringBuilder builder = new SpannableStringBuilder(message); + + // Look for the "learn more" part of the string, and set a URL span on it. + // We use a customized URLSpan to add FLAG_RECEIVER_FOREGROUND to the intent, and + // also to close the dialog. + for (Annotation annotation : message.getSpans(0, message.length(), Annotation.class)) { + final String key = annotation.getValue(); + + if (!BATTERY_SAVER_DESCRIPTION_URL_KEY.equals(key)) { + continue; + } + final int start = message.getSpanStart(annotation); + final int end = message.getSpanEnd(annotation); + + // Replace the "learn more" with a custom URL span, with + // - No underline. + // - When clicked, close the dialog and the notification shade. + final URLSpan urlSpan = new URLSpan(learnMoreUrl) { + @Override + public void updateDrawState(TextPaint ds) { + super.updateDrawState(ds); + ds.setUnderlineText(false); + } + + @Override + public void onClick(View widget) { + // Close the parent dialog. + if (mSaverConfirmation != null) { + mSaverConfirmation.dismiss(); + } + // Also close the notification shade, if it's open. + mContext.sendBroadcast( + new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS) + .setFlags(Intent.FLAG_RECEIVER_FOREGROUND)); + + final Uri uri = Uri.parse(getURL()); + Context context = widget.getContext(); + Intent intent = new Intent(Intent.ACTION_VIEW, uri) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + try { + context.startActivity(intent); + } catch (ActivityNotFoundException e) { + Log.w(TAG, "Activity was not found for intent, " + intent.toString()); + } + } + }; + builder.setSpan(urlSpan, start, end, message.getSpanFlags(urlSpan)); + } + return builder; + } + private void showAutoSaverEnabledConfirmation() { if (mSaverEnabledConfirmation != null) return;