blob: 2e5eb342347bcf41efcba7c997216eef5b7cd46e [file] [log] [blame]
John Spurlock3332ba52014-03-10 17:44:07 -04001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui.power;
18
John Spurlock3332ba52014-03-10 17:44:07 -040019import android.app.Notification;
20import android.app.NotificationManager;
21import android.app.PendingIntent;
Makoto Onuki778ce662018-04-20 14:04:50 -070022import android.content.ActivityNotFoundException;
John Spurlock3332ba52014-03-10 17:44:07 -040023import android.content.BroadcastReceiver;
John Spurlock3332ba52014-03-10 17:44:07 -040024import android.content.Context;
John Spurlock3332ba52014-03-10 17:44:07 -040025import android.content.Intent;
26import android.content.IntentFilter;
John Spurlock1bb480a2014-08-02 17:12:43 -040027import android.media.AudioAttributes;
Makoto Onuki778ce662018-04-20 14:04:50 -070028import android.net.Uri;
John Spurlock3332ba52014-03-10 17:44:07 -040029import android.os.Handler;
Geoffrey Pitsch4a7931d2016-09-15 13:11:47 -040030import android.os.Looper;
John Spurlock8d4e6cb2014-09-14 11:10:22 -040031import android.os.PowerManager;
John Spurlock3332ba52014-03-10 17:44:07 -040032import android.os.UserHandle;
Aurimas Liutikasb8616dc2018-04-17 09:50:46 -070033import androidx.annotation.VisibleForTesting;
Makoto Onuki778ce662018-04-20 14:04:50 -070034import android.text.Annotation;
35import android.text.Layout;
36import android.text.SpannableString;
37import android.text.SpannableStringBuilder;
38import android.text.TextPaint;
39import android.text.TextUtils;
40import android.text.method.LinkMovementMethod;
41import android.text.style.URLSpan;
42import android.util.Log;
John Spurlock3332ba52014-03-10 17:44:07 -040043import android.util.Slog;
Makoto Onuki778ce662018-04-20 14:04:50 -070044import android.view.View;
John Spurlock3332ba52014-03-10 17:44:07 -040045
Chris Wren5e6c0ff2017-01-05 12:57:06 -050046import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
Jason Monk58be7a62017-02-01 20:17:51 -050047import com.android.settingslib.Utils;
Makoto Onuki16a0dd22018-03-20 10:40:37 -070048import com.android.settingslib.fuelgauge.BatterySaverUtils;
Salvador Martinezeb9ab292018-01-19 17:50:24 -080049import com.android.settingslib.utils.PowerUtil;
John Spurlock3332ba52014-03-10 17:44:07 -040050import com.android.systemui.R;
Adrian Roose25c18d2016-06-17 15:59:49 -070051import com.android.systemui.SystemUI;
John Spurlock1bb480a2014-08-02 17:12:43 -040052import com.android.systemui.statusbar.phone.SystemUIDialog;
Dan Sandler8e032e12017-01-25 13:41:38 -050053import com.android.systemui.util.NotificationChannels;
John Spurlock3332ba52014-03-10 17:44:07 -040054
55import java.io.PrintWriter;
Elliott Hughes88d25512014-10-03 12:06:17 -070056import java.text.NumberFormat;
Makoto Onuki778ce662018-04-20 14:04:50 -070057import java.util.Locale;
58import java.util.Objects;
John Spurlock3332ba52014-03-10 17:44:07 -040059
60public class PowerNotificationWarnings implements PowerUI.WarningsUI {
61 private static final String TAG = PowerUI.TAG + ".Notification";
62 private static final boolean DEBUG = PowerUI.DEBUG;
63
Chris Wren5e6c0ff2017-01-05 12:57:06 -050064 private static final String TAG_BATTERY = "low_battery";
65 private static final String TAG_TEMPERATURE = "high_temp";
Makoto Onuki52c62952018-03-22 10:43:03 -070066 private static final String TAG_AUTO_SAVER = "auto_saver";
John Spurlock3332ba52014-03-10 17:44:07 -040067
68 private static final int SHOWING_NOTHING = 0;
69 private static final int SHOWING_WARNING = 1;
John Spurlock3332ba52014-03-10 17:44:07 -040070 private static final int SHOWING_INVALID_CHARGER = 3;
Makoto Onuki52c62952018-03-22 10:43:03 -070071 private static final int SHOWING_AUTO_SAVER_SUGGESTION = 4;
John Spurlock3332ba52014-03-10 17:44:07 -040072 private static final String[] SHOWING_STRINGS = {
73 "SHOWING_NOTHING",
74 "SHOWING_WARNING",
75 "SHOWING_SAVER",
76 "SHOWING_INVALID_CHARGER",
Makoto Onuki52c62952018-03-22 10:43:03 -070077 "SHOWING_AUTO_SAVER_SUGGESTION",
John Spurlock3332ba52014-03-10 17:44:07 -040078 };
79
John Spurlock3332ba52014-03-10 17:44:07 -040080 private static final String ACTION_SHOW_BATTERY_SETTINGS = "PNW.batterySettings";
81 private static final String ACTION_START_SAVER = "PNW.startSaver";
John Spurlock42bfc9a2014-10-29 11:13:01 -040082 private static final String ACTION_DISMISSED_WARNING = "PNW.dismissedWarning";
Andrew Sappersteinb7caf1d2016-12-14 15:39:20 -080083 private static final String ACTION_CLICKED_TEMP_WARNING = "PNW.clickedTempWarning";
84 private static final String ACTION_DISMISSED_TEMP_WARNING = "PNW.dismissedTempWarning";
Salvador Martineza6f7b252017-04-10 10:46:15 -070085 private static final String ACTION_CLICKED_THERMAL_SHUTDOWN_WARNING =
86 "PNW.clickedThermalShutdownWarning";
87 private static final String ACTION_DISMISSED_THERMAL_SHUTDOWN_WARNING =
88 "PNW.dismissedThermalShutdownWarning";
Makoto Onuki16a0dd22018-03-20 10:40:37 -070089 private static final String ACTION_SHOW_START_SAVER_CONFIRMATION =
90 BatterySaverUtils.ACTION_SHOW_START_SAVER_CONFIRMATION;
Makoto Onuki52c62952018-03-22 10:43:03 -070091 private static final String ACTION_SHOW_AUTO_SAVER_SUGGESTION =
92 BatterySaverUtils.ACTION_SHOW_AUTO_SAVER_SUGGESTION;
93 private static final String ACTION_DISMISS_AUTO_SAVER_SUGGESTION =
94 "PNW.dismissAutoSaverSuggestion";
95
96 private static final String ACTION_ENABLE_AUTO_SAVER =
97 "PNW.enableAutoSaver";
98 private static final String ACTION_AUTO_SAVER_NO_THANKS =
99 "PNW.autoSaverNoThanks";
100
101 private static final String SETTINGS_ACTION_OPEN_BATTERY_SAVER_SETTING =
102 "android.settings.BATTERY_SAVER_SETTINGS";
John Spurlock1bb480a2014-08-02 17:12:43 -0400103
Makoto Onuki778ce662018-04-20 14:04:50 -0700104 private static final String BATTERY_SAVER_DESCRIPTION_URL_KEY = "url";
105
John Spurlock1bb480a2014-08-02 17:12:43 -0400106 private static final AudioAttributes AUDIO_ATTRIBUTES = new AudioAttributes.Builder()
107 .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
108 .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
109 .build();
John Spurlock3332ba52014-03-10 17:44:07 -0400110
111 private final Context mContext;
John Spurlock3332ba52014-03-10 17:44:07 -0400112 private final NotificationManager mNoMan;
John Spurlock8d4e6cb2014-09-14 11:10:22 -0400113 private final PowerManager mPowerMan;
Geoffrey Pitsch4a7931d2016-09-15 13:11:47 -0400114 private final Handler mHandler = new Handler(Looper.getMainLooper());
John Spurlock3332ba52014-03-10 17:44:07 -0400115 private final Receiver mReceiver = new Receiver();
116 private final Intent mOpenBatterySettings = settings(Intent.ACTION_POWER_USAGE_SUMMARY);
John Spurlock3332ba52014-03-10 17:44:07 -0400117
118 private int mBatteryLevel;
119 private int mBucket;
120 private long mScreenOffTime;
121 private int mShowing;
122
Salvador Martinezf9e47502018-01-04 13:45:48 -0800123 private long mWarningTriggerTimeMs;
Christoph Studer65fa0a92014-06-26 16:50:09 +0200124
Salvador Martinezf9e47502018-01-04 13:45:48 -0800125 private Estimate mEstimate;
Salvador Martinezbb902fc2018-01-22 19:46:55 -0800126 private long mLowWarningThreshold;
127 private long mSevereWarningThreshold;
John Spurlock3332ba52014-03-10 17:44:07 -0400128 private boolean mWarning;
Makoto Onuki52c62952018-03-22 10:43:03 -0700129 private boolean mShowAutoSaverSuggestion;
John Spurlock3332ba52014-03-10 17:44:07 -0400130 private boolean mPlaySound;
131 private boolean mInvalidCharger;
John Spurlock1bb480a2014-08-02 17:12:43 -0400132 private SystemUIDialog mSaverConfirmation;
Makoto Onuki52c62952018-03-22 10:43:03 -0700133 private SystemUIDialog mSaverEnabledConfirmation;
Salvador Martineza6f7b252017-04-10 10:46:15 -0700134 private boolean mHighTempWarning;
Andrew Sappersteinb7caf1d2016-12-14 15:39:20 -0800135 private SystemUIDialog mHighTempDialog;
Salvador Martineza6f7b252017-04-10 10:46:15 -0700136 private SystemUIDialog mThermalShutdownDialog;
John Spurlock3332ba52014-03-10 17:44:07 -0400137
Jason Monkd819c312017-08-11 12:53:36 -0400138 public PowerNotificationWarnings(Context context) {
John Spurlock3332ba52014-03-10 17:44:07 -0400139 mContext = context;
Jason Monkd819c312017-08-11 12:53:36 -0400140 mNoMan = mContext.getSystemService(NotificationManager.class);
John Spurlock8d4e6cb2014-09-14 11:10:22 -0400141 mPowerMan = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
John Spurlock3332ba52014-03-10 17:44:07 -0400142 mReceiver.init();
143 }
144
145 @Override
146 public void dump(PrintWriter pw) {
John Spurlock3332ba52014-03-10 17:44:07 -0400147 pw.print("mWarning="); pw.println(mWarning);
148 pw.print("mPlaySound="); pw.println(mPlaySound);
149 pw.print("mInvalidCharger="); pw.println(mInvalidCharger);
150 pw.print("mShowing="); pw.println(SHOWING_STRINGS[mShowing]);
John Spurlock1bb480a2014-08-02 17:12:43 -0400151 pw.print("mSaverConfirmation="); pw.println(mSaverConfirmation != null ? "not null" : null);
Makoto Onuki52c62952018-03-22 10:43:03 -0700152 pw.print("mSaverEnabledConfirmation=");
153 pw.println(mSaverEnabledConfirmation != null ? "not null" : null);
Salvador Martineza6f7b252017-04-10 10:46:15 -0700154 pw.print("mHighTempWarning="); pw.println(mHighTempWarning);
Andrew Sappersteinb7caf1d2016-12-14 15:39:20 -0800155 pw.print("mHighTempDialog="); pw.println(mHighTempDialog != null ? "not null" : null);
Salvador Martineza6f7b252017-04-10 10:46:15 -0700156 pw.print("mThermalShutdownDialog=");
157 pw.println(mThermalShutdownDialog != null ? "not null" : null);
John Spurlock3332ba52014-03-10 17:44:07 -0400158 }
159
Makoto Onuki52c62952018-03-22 10:43:03 -0700160 private int getLowBatteryAutoTriggerDefaultLevel() {
161 return mContext.getResources().getInteger(
162 com.android.internal.R.integer.config_lowBatteryAutoTriggerDefaultLevel);
163 }
164
John Spurlock3332ba52014-03-10 17:44:07 -0400165 @Override
166 public void update(int batteryLevel, int bucket, long screenOffTime) {
167 mBatteryLevel = batteryLevel;
Christoph Studer65fa0a92014-06-26 16:50:09 +0200168 if (bucket >= 0) {
Salvador Martinezf9e47502018-01-04 13:45:48 -0800169 mWarningTriggerTimeMs = 0;
Christoph Studer65fa0a92014-06-26 16:50:09 +0200170 } else if (bucket < mBucket) {
Salvador Martinezf9e47502018-01-04 13:45:48 -0800171 mWarningTriggerTimeMs = System.currentTimeMillis();
Christoph Studer65fa0a92014-06-26 16:50:09 +0200172 }
John Spurlock3332ba52014-03-10 17:44:07 -0400173 mBucket = bucket;
174 mScreenOffTime = screenOffTime;
John Spurlock3332ba52014-03-10 17:44:07 -0400175 }
176
Salvador Martinezf9e47502018-01-04 13:45:48 -0800177 @Override
178 public void updateEstimate(Estimate estimate) {
179 mEstimate = estimate;
Salvador Martinezbb902fc2018-01-22 19:46:55 -0800180 if (estimate.estimateMillis <= mLowWarningThreshold) {
Salvador Martinezf9e47502018-01-04 13:45:48 -0800181 mWarningTriggerTimeMs = System.currentTimeMillis();
182 }
183 }
184
Salvador Martinezbb902fc2018-01-22 19:46:55 -0800185 @Override
186 public void updateThresholds(long lowThreshold, long severeThreshold) {
187 mLowWarningThreshold = lowThreshold;
188 mSevereWarningThreshold = severeThreshold;
189 }
190
John Spurlock3332ba52014-03-10 17:44:07 -0400191 private void updateNotification() {
John Spurlock86c3de82014-08-19 13:37:44 -0400192 if (DEBUG) Slog.d(TAG, "updateNotification mWarning=" + mWarning + " mPlaySound="
Jason Monkc06fbb12016-01-08 14:12:18 -0500193 + mPlaySound + " mInvalidCharger=" + mInvalidCharger);
John Spurlock3332ba52014-03-10 17:44:07 -0400194 if (mInvalidCharger) {
John Spurlock3332ba52014-03-10 17:44:07 -0400195 showInvalidChargerNotification();
196 mShowing = SHOWING_INVALID_CHARGER;
197 } else if (mWarning) {
John Spurlock3332ba52014-03-10 17:44:07 -0400198 showWarningNotification();
199 mShowing = SHOWING_WARNING;
Makoto Onuki52c62952018-03-22 10:43:03 -0700200 } else if (mShowAutoSaverSuggestion) {
Makoto Onuki60b8f182018-05-07 13:56:40 -0700201 // Once we showed the notification, don't show it again until it goes SHOWING_NOTHING.
202 // This shouldn't be needed, because we have a delete intent on this notification
203 // so when it's dismissed we should notice it and clear mShowAutoSaverSuggestion,
204 // However we double check here just in case the dismiss intent broadcast is delayed.
205 if (mShowing != SHOWING_AUTO_SAVER_SUGGESTION) {
206 showAutoSaverSuggestionNotification();
207 }
Makoto Onuki52c62952018-03-22 10:43:03 -0700208 mShowing = SHOWING_AUTO_SAVER_SUGGESTION;
John Spurlock3332ba52014-03-10 17:44:07 -0400209 } else {
Chris Wren5e6c0ff2017-01-05 12:57:06 -0500210 mNoMan.cancelAsUser(TAG_BATTERY, SystemMessage.NOTE_BAD_CHARGER, UserHandle.ALL);
211 mNoMan.cancelAsUser(TAG_BATTERY, SystemMessage.NOTE_POWER_LOW, UserHandle.ALL);
Makoto Onuki52c62952018-03-22 10:43:03 -0700212 mNoMan.cancelAsUser(TAG_AUTO_SAVER,
213 SystemMessage.NOTE_AUTO_SAVER_SUGGESTION, UserHandle.ALL);
John Spurlock3332ba52014-03-10 17:44:07 -0400214 mShowing = SHOWING_NOTHING;
215 }
216 }
217
218 private void showInvalidChargerNotification() {
Geoffrey Pitsch1dc93bc2017-01-31 16:38:11 -0500219 final Notification.Builder nb =
220 new Notification.Builder(mContext, NotificationChannels.ALERTS)
221 .setSmallIcon(R.drawable.ic_power_low)
222 .setWhen(0)
223 .setShowWhen(false)
224 .setOngoing(true)
225 .setContentTitle(mContext.getString(R.string.invalid_charger_title))
226 .setContentText(mContext.getString(R.string.invalid_charger_text))
227 .setColor(mContext.getColor(
228 com.android.internal.R.color.system_notification_accent_color));
Julia Reynolds037d8082018-03-18 15:25:19 -0400229 SystemUI.overrideNotificationAppName(mContext, nb, false);
John Spurlock3332ba52014-03-10 17:44:07 -0400230 final Notification n = nb.build();
Chris Wren5e6c0ff2017-01-05 12:57:06 -0500231 mNoMan.cancelAsUser(TAG_BATTERY, SystemMessage.NOTE_POWER_LOW, UserHandle.ALL);
232 mNoMan.notifyAsUser(TAG_BATTERY, SystemMessage.NOTE_BAD_CHARGER, n, UserHandle.ALL);
John Spurlock3332ba52014-03-10 17:44:07 -0400233 }
234
Salvador Martinezf9e47502018-01-04 13:45:48 -0800235 protected void showWarningNotification() {
Salvador Martinezbb902fc2018-01-22 19:46:55 -0800236 final String percentage = NumberFormat.getPercentInstance()
237 .format((double) mBatteryLevel / 100.0);
Beverly334bc5f2017-07-31 10:37:17 -0400238
Salvador Martinezf9e47502018-01-04 13:45:48 -0800239 // get standard notification copy
240 String title = mContext.getString(R.string.battery_low_title);
241 String contentText = mContext.getString(R.string.battery_low_percent_format, percentage);
242
243 // override notification copy if hybrid notification enabled
244 if (mEstimate != null) {
Salvador Martinezeb9ab292018-01-19 17:50:24 -0800245 contentText = getHybridContentString(percentage);
Salvador Martinezf9e47502018-01-04 13:45:48 -0800246 }
247
Geoffrey Pitsch1dc93bc2017-01-31 16:38:11 -0500248 final Notification.Builder nb =
Beverly334bc5f2017-07-31 10:37:17 -0400249 new Notification.Builder(mContext, NotificationChannels.BATTERY)
Geoffrey Pitsch1dc93bc2017-01-31 16:38:11 -0500250 .setSmallIcon(R.drawable.ic_power_low)
251 // Bump the notification when the bucket dropped.
Salvador Martinezf9e47502018-01-04 13:45:48 -0800252 .setWhen(mWarningTriggerTimeMs)
Geoffrey Pitsch1dc93bc2017-01-31 16:38:11 -0500253 .setShowWhen(false)
Salvador Martinezf9e47502018-01-04 13:45:48 -0800254 .setContentText(contentText)
Salvador Martinez086ab742018-04-03 13:05:39 -0700255 .setContentTitle(title)
Geoffrey Pitsch1dc93bc2017-01-31 16:38:11 -0500256 .setOnlyAlertOnce(true)
257 .setDeleteIntent(pendingBroadcast(ACTION_DISMISSED_WARNING))
Salvador Martinez086ab742018-04-03 13:05:39 -0700258 .setStyle(new Notification.BigTextStyle().bigText(contentText))
Salvador Martinezf9e47502018-01-04 13:45:48 -0800259 .setVisibility(Notification.VISIBILITY_PUBLIC);
John Spurlock3332ba52014-03-10 17:44:07 -0400260 if (hasBatterySettings()) {
261 nb.setContentIntent(pendingBroadcast(ACTION_SHOW_BATTERY_SETTINGS));
262 }
Salvador Martinezf9e47502018-01-04 13:45:48 -0800263 // Make the notification red if the percentage goes below a certain amount or the time
264 // remaining estimate is disabled
Salvador Martinezbb902fc2018-01-22 19:46:55 -0800265 if (mEstimate == null || mBucket < 0
266 || mEstimate.estimateMillis < mSevereWarningThreshold) {
Salvador Martinezf9e47502018-01-04 13:45:48 -0800267 nb.setColor(Utils.getColorAttr(mContext, android.R.attr.colorError));
268 }
Salvador Martinezc50a9bd2018-08-03 14:07:44 -0700269
270 if (!mPowerMan.isPowerSaveMode()) {
271 nb.addAction(0,
272 mContext.getString(R.string.battery_saver_start_action),
273 pendingBroadcast(ACTION_START_SAVER));
274 }
Beverly334bc5f2017-07-31 10:37:17 -0400275 nb.setOnlyAlertOnce(!mPlaySound);
276 mPlaySound = false;
Julia Reynolds037d8082018-03-18 15:25:19 -0400277 SystemUI.overrideNotificationAppName(mContext, nb, false);
Chris Wren5e6c0ff2017-01-05 12:57:06 -0500278 final Notification n = nb.build();
279 mNoMan.cancelAsUser(TAG_BATTERY, SystemMessage.NOTE_BAD_CHARGER, UserHandle.ALL);
280 mNoMan.notifyAsUser(TAG_BATTERY, SystemMessage.NOTE_POWER_LOW, n, UserHandle.ALL);
John Spurlock3332ba52014-03-10 17:44:07 -0400281 }
282
Makoto Onuki52c62952018-03-22 10:43:03 -0700283 private void showAutoSaverSuggestionNotification() {
284 final Notification.Builder nb =
285 new Notification.Builder(mContext, NotificationChannels.HINTS)
286 .setSmallIcon(R.drawable.ic_power_saver)
287 .setWhen(0)
288 .setShowWhen(false)
289 .setContentTitle(mContext.getString(R.string.auto_saver_title))
290 .setContentText(mContext.getString(R.string.auto_saver_text,
291 getLowBatteryAutoTriggerDefaultLevel()));
292 nb.setContentIntent(pendingBroadcast(ACTION_ENABLE_AUTO_SAVER));
293 nb.setDeleteIntent(pendingBroadcast(ACTION_DISMISS_AUTO_SAVER_SUGGESTION));
294 nb.addAction(0,
295 mContext.getString(R.string.no_auto_saver_action),
296 pendingBroadcast(ACTION_AUTO_SAVER_NO_THANKS));
297
298 SystemUI.overrideNotificationAppName(mContext, nb, false);
299
300 final Notification n = nb.build();
301 mNoMan.notifyAsUser(
302 TAG_AUTO_SAVER, SystemMessage.NOTE_AUTO_SAVER_SUGGESTION, n, UserHandle.ALL);
303 }
304
Salvador Martinezeb9ab292018-01-19 17:50:24 -0800305 private String getHybridContentString(String percentage) {
306 return PowerUtil.getBatteryRemainingStringFormatted(
307 mContext,
308 mEstimate.estimateMillis,
309 percentage,
310 mEstimate.isBasedOnUsage);
Salvador Martinezf9e47502018-01-04 13:45:48 -0800311 }
312
John Spurlock3332ba52014-03-10 17:44:07 -0400313 private PendingIntent pendingBroadcast(String action) {
Makoto Onuki52c62952018-03-22 10:43:03 -0700314 return PendingIntent.getBroadcastAsUser(mContext, 0,
Makoto Onuki60b8f182018-05-07 13:56:40 -0700315 new Intent(action).setPackage(mContext.getPackageName())
316 .setFlags(Intent.FLAG_RECEIVER_FOREGROUND),
317 0, UserHandle.CURRENT);
John Spurlock3332ba52014-03-10 17:44:07 -0400318 }
319
320 private static Intent settings(String action) {
321 return new Intent(action).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
322 | Intent.FLAG_ACTIVITY_MULTIPLE_TASK
323 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
324 | Intent.FLAG_ACTIVITY_NO_HISTORY
325 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
326 }
327
328 @Override
329 public boolean isInvalidChargerWarningShowing() {
330 return mInvalidCharger;
331 }
332
333 @Override
Salvador Martineza6f7b252017-04-10 10:46:15 -0700334 public void dismissHighTemperatureWarning() {
335 if (!mHighTempWarning) {
Andrew Sappersteinb7caf1d2016-12-14 15:39:20 -0800336 return;
337 }
Salvador Martineza6f7b252017-04-10 10:46:15 -0700338 mHighTempWarning = false;
339 dismissHighTemperatureWarningInternal();
Andrew Sapperstein97bfa0f2017-01-24 16:38:50 -0800340 }
341
342 /**
Salvador Martineza6f7b252017-04-10 10:46:15 -0700343 * Internal only version of {@link #dismissHighTemperatureWarning()} that simply dismisses
Andrew Sapperstein97bfa0f2017-01-24 16:38:50 -0800344 * the notification. As such, the notification will not show again until
Salvador Martineza6f7b252017-04-10 10:46:15 -0700345 * {@link #dismissHighTemperatureWarning()} is called.
Andrew Sapperstein97bfa0f2017-01-24 16:38:50 -0800346 */
Salvador Martineza6f7b252017-04-10 10:46:15 -0700347 private void dismissHighTemperatureWarningInternal() {
Andrew Sapperstein97bfa0f2017-01-24 16:38:50 -0800348 mNoMan.cancelAsUser(TAG_TEMPERATURE, SystemMessage.NOTE_HIGH_TEMP, UserHandle.ALL);
Andrew Sappersteinb7caf1d2016-12-14 15:39:20 -0800349 }
350
351 @Override
Salvador Martineza6f7b252017-04-10 10:46:15 -0700352 public void showHighTemperatureWarning() {
353 if (mHighTempWarning) {
Andrew Sappersteinb7caf1d2016-12-14 15:39:20 -0800354 return;
355 }
Salvador Martineza6f7b252017-04-10 10:46:15 -0700356 mHighTempWarning = true;
Geoffrey Pitsch1dc93bc2017-01-31 16:38:11 -0500357 final Notification.Builder nb =
358 new Notification.Builder(mContext, NotificationChannels.ALERTS)
359 .setSmallIcon(R.drawable.ic_device_thermostat_24)
360 .setWhen(0)
361 .setShowWhen(false)
362 .setContentTitle(mContext.getString(R.string.high_temp_title))
363 .setContentText(mContext.getString(R.string.high_temp_notif_message))
364 .setVisibility(Notification.VISIBILITY_PUBLIC)
365 .setContentIntent(pendingBroadcast(ACTION_CLICKED_TEMP_WARNING))
366 .setDeleteIntent(pendingBroadcast(ACTION_DISMISSED_TEMP_WARNING))
Jason Monk58be7a62017-02-01 20:17:51 -0500367 .setColor(Utils.getColorAttr(mContext, android.R.attr.colorError));
Julia Reynolds037d8082018-03-18 15:25:19 -0400368 SystemUI.overrideNotificationAppName(mContext, nb, false);
Andrew Sappersteinb7caf1d2016-12-14 15:39:20 -0800369 final Notification n = nb.build();
Chris Wren5e6c0ff2017-01-05 12:57:06 -0500370 mNoMan.notifyAsUser(TAG_TEMPERATURE, SystemMessage.NOTE_HIGH_TEMP, n, UserHandle.ALL);
Andrew Sappersteinb7caf1d2016-12-14 15:39:20 -0800371 }
372
Salvador Martineza6f7b252017-04-10 10:46:15 -0700373 private void showHighTemperatureDialog() {
Andrew Sappersteinb7caf1d2016-12-14 15:39:20 -0800374 if (mHighTempDialog != null) return;
375 final SystemUIDialog d = new SystemUIDialog(mContext);
Andrew Sappersteine26dc3d2017-01-04 11:25:20 -0800376 d.setIconAttribute(android.R.attr.alertDialogIcon);
Andrew Sappersteinb7caf1d2016-12-14 15:39:20 -0800377 d.setTitle(R.string.high_temp_title);
378 d.setMessage(R.string.high_temp_dialog_message);
379 d.setPositiveButton(com.android.internal.R.string.ok, null);
380 d.setShowForAllUsers(true);
381 d.setOnDismissListener(dialog -> mHighTempDialog = null);
382 d.show();
383 mHighTempDialog = d;
384 }
385
Salvador Martineza6f7b252017-04-10 10:46:15 -0700386 @VisibleForTesting
387 void dismissThermalShutdownWarning() {
388 mNoMan.cancelAsUser(TAG_TEMPERATURE, SystemMessage.NOTE_THERMAL_SHUTDOWN, UserHandle.ALL);
389 }
390
391 private void showThermalShutdownDialog() {
392 if (mThermalShutdownDialog != null) return;
393 final SystemUIDialog d = new SystemUIDialog(mContext);
394 d.setIconAttribute(android.R.attr.alertDialogIcon);
395 d.setTitle(R.string.thermal_shutdown_title);
396 d.setMessage(R.string.thermal_shutdown_dialog_message);
397 d.setPositiveButton(com.android.internal.R.string.ok, null);
398 d.setShowForAllUsers(true);
399 d.setOnDismissListener(dialog -> mThermalShutdownDialog = null);
400 d.show();
401 mThermalShutdownDialog = d;
402 }
403
404 @Override
405 public void showThermalShutdownWarning() {
406 final Notification.Builder nb =
407 new Notification.Builder(mContext, NotificationChannels.ALERTS)
408 .setSmallIcon(R.drawable.ic_device_thermostat_24)
409 .setWhen(0)
410 .setShowWhen(false)
411 .setContentTitle(mContext.getString(R.string.thermal_shutdown_title))
412 .setContentText(mContext.getString(R.string.thermal_shutdown_message))
413 .setVisibility(Notification.VISIBILITY_PUBLIC)
414 .setContentIntent(pendingBroadcast(ACTION_CLICKED_THERMAL_SHUTDOWN_WARNING))
415 .setDeleteIntent(
416 pendingBroadcast(ACTION_DISMISSED_THERMAL_SHUTDOWN_WARNING))
417 .setColor(Utils.getColorAttr(mContext, android.R.attr.colorError));
Julia Reynolds037d8082018-03-18 15:25:19 -0400418 SystemUI.overrideNotificationAppName(mContext, nb, false);
Salvador Martineza6f7b252017-04-10 10:46:15 -0700419 final Notification n = nb.build();
420 mNoMan.notifyAsUser(
421 TAG_TEMPERATURE, SystemMessage.NOTE_THERMAL_SHUTDOWN, n, UserHandle.ALL);
422 }
423
Andrew Sappersteinb7caf1d2016-12-14 15:39:20 -0800424 @Override
John Spurlock3332ba52014-03-10 17:44:07 -0400425 public void updateLowBatteryWarning() {
426 updateNotification();
John Spurlock3332ba52014-03-10 17:44:07 -0400427 }
428
429 @Override
430 public void dismissLowBatteryWarning() {
John Spurlock3ff2de62014-06-16 13:32:48 -0400431 if (DEBUG) Slog.d(TAG, "dismissing low battery warning: level=" + mBatteryLevel);
John Spurlock3332ba52014-03-10 17:44:07 -0400432 dismissLowBatteryNotification();
John Spurlock3332ba52014-03-10 17:44:07 -0400433 }
434
435 private void dismissLowBatteryNotification() {
John Spurlock3ff2de62014-06-16 13:32:48 -0400436 if (mWarning) Slog.i(TAG, "dismissing low battery notification");
John Spurlock3332ba52014-03-10 17:44:07 -0400437 mWarning = false;
438 updateNotification();
439 }
440
441 private boolean hasBatterySettings() {
442 return mOpenBatterySettings.resolveActivity(mContext.getPackageManager()) != null;
443 }
444
John Spurlock3332ba52014-03-10 17:44:07 -0400445 @Override
446 public void showLowBatteryWarning(boolean playSound) {
447 Slog.i(TAG,
448 "show low battery warning: level=" + mBatteryLevel
Beverly334bc5f2017-07-31 10:37:17 -0400449 + " [" + mBucket + "] playSound=" + playSound);
John Spurlock3332ba52014-03-10 17:44:07 -0400450 mPlaySound = playSound;
451 mWarning = true;
452 updateNotification();
John Spurlock3332ba52014-03-10 17:44:07 -0400453 }
454
John Spurlock3332ba52014-03-10 17:44:07 -0400455 @Override
456 public void dismissInvalidChargerWarning() {
John Spurlockeb44a7d2014-06-12 13:00:55 -0400457 dismissInvalidChargerNotification();
John Spurlockeb44a7d2014-06-12 13:00:55 -0400458 }
459
460 private void dismissInvalidChargerNotification() {
John Spurlock3ff2de62014-06-16 13:32:48 -0400461 if (mInvalidCharger) Slog.i(TAG, "dismissing invalid charger notification");
John Spurlock3332ba52014-03-10 17:44:07 -0400462 mInvalidCharger = false;
463 updateNotification();
464 }
465
466 @Override
467 public void showInvalidChargerWarning() {
468 mInvalidCharger = true;
469 updateNotification();
470 }
471
Makoto Onuki52c62952018-03-22 10:43:03 -0700472 private void showAutoSaverSuggestion() {
473 mShowAutoSaverSuggestion = true;
474 updateNotification();
475 }
476
477 private void dismissAutoSaverSuggestion() {
478 mShowAutoSaverSuggestion = false;
479 updateNotification();
480 }
481
John Spurlockecbc5e82014-10-22 09:05:51 -0400482 @Override
483 public void userSwitched() {
484 updateNotification();
485 }
486
John Spurlock3332ba52014-03-10 17:44:07 -0400487 private void showStartSaverConfirmation() {
John Spurlock1bb480a2014-08-02 17:12:43 -0400488 if (mSaverConfirmation != null) return;
489 final SystemUIDialog d = new SystemUIDialog(mContext);
490 d.setTitle(R.string.battery_saver_confirmation_title);
Makoto Onuki778ce662018-04-20 14:04:50 -0700491 d.setMessage(getBatterySaverDescription());
492
493 // Sad hack for http://b/78261259 and http://b/78298335. Otherwise "Battery" may be split
494 // into "Bat-tery".
495 if (isEnglishLocale()) {
496 d.setMessageHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE);
497 }
498 // We need to set LinkMovementMethod to make the link clickable.
499 d.setMessageMovementMethod(LinkMovementMethod.getInstance());
500
John Spurlock1bb480a2014-08-02 17:12:43 -0400501 d.setNegativeButton(android.R.string.cancel, null);
Makoto Onuki52c62952018-03-22 10:43:03 -0700502 d.setPositiveButton(R.string.battery_saver_confirmation_ok,
503 (dialog, which) -> setSaverMode(true, false));
John Spurlock1bb480a2014-08-02 17:12:43 -0400504 d.setShowForAllUsers(true);
Makoto Onuki52c62952018-03-22 10:43:03 -0700505 d.setOnDismissListener((dialog) -> mSaverConfirmation = null);
John Spurlock3332ba52014-03-10 17:44:07 -0400506 d.show();
John Spurlock1bb480a2014-08-02 17:12:43 -0400507 mSaverConfirmation = d;
John Spurlock3332ba52014-03-10 17:44:07 -0400508 }
509
Makoto Onuki778ce662018-04-20 14:04:50 -0700510 private boolean isEnglishLocale() {
511 return Objects.equals(Locale.getDefault().getLanguage(),
512 Locale.ENGLISH.getLanguage());
513 }
514
515 /**
516 * Generates the message for the "want to start battery saver?" dialog with a "learn more" link.
517 */
518 private CharSequence getBatterySaverDescription() {
519 final String learnMoreUrl = mContext.getText(
520 R.string.help_uri_battery_saver_learn_more_link_target).toString();
521
522 // If there's no link, use the string with no "learn more".
523 if (TextUtils.isEmpty(learnMoreUrl)) {
524 return mContext.getText(
525 com.android.internal.R.string.battery_saver_description);
526 }
527
528 // If we have a link, use the string with the "learn more" link.
529 final CharSequence rawText = mContext.getText(
530 com.android.internal.R.string.battery_saver_description_with_learn_more);
531 final SpannableString message = new SpannableString(rawText);
532 final SpannableStringBuilder builder = new SpannableStringBuilder(message);
533
534 // Look for the "learn more" part of the string, and set a URL span on it.
535 // We use a customized URLSpan to add FLAG_RECEIVER_FOREGROUND to the intent, and
536 // also to close the dialog.
537 for (Annotation annotation : message.getSpans(0, message.length(), Annotation.class)) {
538 final String key = annotation.getValue();
539
540 if (!BATTERY_SAVER_DESCRIPTION_URL_KEY.equals(key)) {
541 continue;
542 }
543 final int start = message.getSpanStart(annotation);
544 final int end = message.getSpanEnd(annotation);
545
546 // Replace the "learn more" with a custom URL span, with
547 // - No underline.
548 // - When clicked, close the dialog and the notification shade.
549 final URLSpan urlSpan = new URLSpan(learnMoreUrl) {
550 @Override
551 public void updateDrawState(TextPaint ds) {
552 super.updateDrawState(ds);
553 ds.setUnderlineText(false);
554 }
555
556 @Override
557 public void onClick(View widget) {
558 // Close the parent dialog.
559 if (mSaverConfirmation != null) {
560 mSaverConfirmation.dismiss();
561 }
562 // Also close the notification shade, if it's open.
563 mContext.sendBroadcast(
564 new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)
565 .setFlags(Intent.FLAG_RECEIVER_FOREGROUND));
566
567 final Uri uri = Uri.parse(getURL());
568 Context context = widget.getContext();
569 Intent intent = new Intent(Intent.ACTION_VIEW, uri)
570 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
571 try {
572 context.startActivity(intent);
573 } catch (ActivityNotFoundException e) {
574 Log.w(TAG, "Activity was not found for intent, " + intent.toString());
575 }
576 }
577 };
578 builder.setSpan(urlSpan, start, end, message.getSpanFlags(urlSpan));
579 }
580 return builder;
581 }
582
Makoto Onuki52c62952018-03-22 10:43:03 -0700583 private void showAutoSaverEnabledConfirmation() {
584 if (mSaverEnabledConfirmation != null) return;
585
586 // Open the Battery Saver setting page.
587 final Intent actionBatterySaverSetting =
588 new Intent(SETTINGS_ACTION_OPEN_BATTERY_SAVER_SETTING)
589 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
590
591 final SystemUIDialog d = new SystemUIDialog(mContext);
592 d.setTitle(R.string.auto_saver_enabled_title);
593 d.setMessage(mContext.getString(R.string.auto_saver_enabled_text,
594 getLowBatteryAutoTriggerDefaultLevel()));
595
Makoto Onuki41d5ccf2018-04-03 11:46:02 -0700596 // "Got it". Just close the dialog. Automatic battery has been enabled already.
597 d.setPositiveButton(R.string.auto_saver_okay_action,
598 (dialog, which) -> onAutoSaverEnabledConfirmationClosed());
599
600 // "Settings" -> Opens the battery saver settings activity.
601 d.setNeutralButton(R.string.open_saver_setting_action, (dialog, which) -> {
602 mContext.startActivity(actionBatterySaverSetting);
603 onAutoSaverEnabledConfirmationClosed();
604 });
Makoto Onuki52c62952018-03-22 10:43:03 -0700605 d.setShowForAllUsers(true);
Makoto Onuki41d5ccf2018-04-03 11:46:02 -0700606 d.setOnDismissListener((dialog) -> onAutoSaverEnabledConfirmationClosed());
Makoto Onuki52c62952018-03-22 10:43:03 -0700607 d.show();
608 mSaverEnabledConfirmation = d;
609 }
610
Makoto Onuki41d5ccf2018-04-03 11:46:02 -0700611 private void onAutoSaverEnabledConfirmationClosed() {
612 mSaverEnabledConfirmation = null;
613 }
Makoto Onuki52c62952018-03-22 10:43:03 -0700614
Makoto Onuki16a0dd22018-03-20 10:40:37 -0700615 private void setSaverMode(boolean mode, boolean needFirstTimeWarning) {
616 BatterySaverUtils.setPowerSaveMode(mContext, mode, needFirstTimeWarning);
John Spurlock3332ba52014-03-10 17:44:07 -0400617 }
618
Makoto Onuki52c62952018-03-22 10:43:03 -0700619 private void scheduleAutoBatterySaver() {
620 int autoTriggerThreshold = mContext.getResources().getInteger(
621 com.android.internal.R.integer.config_lowBatteryWarningLevel);
622 if (autoTriggerThreshold == 0) {
623 autoTriggerThreshold = 15;
624 }
625
Makoto Onuki59727732018-04-04 12:44:05 -0700626 BatterySaverUtils.ensureAutoBatterySaver(mContext, autoTriggerThreshold);
Makoto Onuki52c62952018-03-22 10:43:03 -0700627 showAutoSaverEnabledConfirmation();
628 }
629
John Spurlock3332ba52014-03-10 17:44:07 -0400630 private final class Receiver extends BroadcastReceiver {
631
632 public void init() {
633 IntentFilter filter = new IntentFilter();
John Spurlock3332ba52014-03-10 17:44:07 -0400634 filter.addAction(ACTION_SHOW_BATTERY_SETTINGS);
635 filter.addAction(ACTION_START_SAVER);
John Spurlock42bfc9a2014-10-29 11:13:01 -0400636 filter.addAction(ACTION_DISMISSED_WARNING);
Andrew Sappersteinb7caf1d2016-12-14 15:39:20 -0800637 filter.addAction(ACTION_CLICKED_TEMP_WARNING);
638 filter.addAction(ACTION_DISMISSED_TEMP_WARNING);
Salvador Martineza6f7b252017-04-10 10:46:15 -0700639 filter.addAction(ACTION_CLICKED_THERMAL_SHUTDOWN_WARNING);
640 filter.addAction(ACTION_DISMISSED_THERMAL_SHUTDOWN_WARNING);
Makoto Onuki16a0dd22018-03-20 10:40:37 -0700641 filter.addAction(ACTION_SHOW_START_SAVER_CONFIRMATION);
Makoto Onuki52c62952018-03-22 10:43:03 -0700642 filter.addAction(ACTION_SHOW_AUTO_SAVER_SUGGESTION);
643 filter.addAction(ACTION_ENABLE_AUTO_SAVER);
644 filter.addAction(ACTION_AUTO_SAVER_NO_THANKS);
Makoto Onuki5ac8d5e2018-04-13 15:53:57 -0700645 filter.addAction(ACTION_DISMISS_AUTO_SAVER_SUGGESTION);
John Spurlock05e07052015-06-01 10:56:42 -0400646 mContext.registerReceiverAsUser(this, UserHandle.ALL, filter,
Makoto Onuki16a0dd22018-03-20 10:40:37 -0700647 android.Manifest.permission.DEVICE_POWER, mHandler);
John Spurlock3332ba52014-03-10 17:44:07 -0400648 }
649
650 @Override
651 public void onReceive(Context context, Intent intent) {
652 final String action = intent.getAction();
John Spurlockeb44a7d2014-06-12 13:00:55 -0400653 Slog.i(TAG, "Received " + action);
John Spurlock86c3de82014-08-19 13:37:44 -0400654 if (action.equals(ACTION_SHOW_BATTERY_SETTINGS)) {
John Spurlock3332ba52014-03-10 17:44:07 -0400655 dismissLowBatteryNotification();
656 mContext.startActivityAsUser(mOpenBatterySettings, UserHandle.CURRENT);
657 } else if (action.equals(ACTION_START_SAVER)) {
Makoto Onuki16a0dd22018-03-20 10:40:37 -0700658 setSaverMode(true, true);
659 dismissLowBatteryNotification();
660 } else if (action.equals(ACTION_SHOW_START_SAVER_CONFIRMATION)) {
John Spurlock3332ba52014-03-10 17:44:07 -0400661 dismissLowBatteryNotification();
662 showStartSaverConfirmation();
John Spurlock42bfc9a2014-10-29 11:13:01 -0400663 } else if (action.equals(ACTION_DISMISSED_WARNING)) {
664 dismissLowBatteryWarning();
Andrew Sappersteinb7caf1d2016-12-14 15:39:20 -0800665 } else if (ACTION_CLICKED_TEMP_WARNING.equals(action)) {
Salvador Martineza6f7b252017-04-10 10:46:15 -0700666 dismissHighTemperatureWarningInternal();
667 showHighTemperatureDialog();
Andrew Sappersteinb7caf1d2016-12-14 15:39:20 -0800668 } else if (ACTION_DISMISSED_TEMP_WARNING.equals(action)) {
Salvador Martineza6f7b252017-04-10 10:46:15 -0700669 dismissHighTemperatureWarningInternal();
670 } else if (ACTION_CLICKED_THERMAL_SHUTDOWN_WARNING.equals(action)) {
671 dismissThermalShutdownWarning();
672 showThermalShutdownDialog();
673 } else if (ACTION_DISMISSED_THERMAL_SHUTDOWN_WARNING.equals(action)) {
674 dismissThermalShutdownWarning();
Makoto Onuki52c62952018-03-22 10:43:03 -0700675 } else if (ACTION_SHOW_AUTO_SAVER_SUGGESTION.equals(action)) {
676 showAutoSaverSuggestion();
677 } else if (ACTION_DISMISS_AUTO_SAVER_SUGGESTION.equals(action)) {
678 dismissAutoSaverSuggestion();
679 } else if (ACTION_ENABLE_AUTO_SAVER.equals(action)) {
680 dismissAutoSaverSuggestion();
681 scheduleAutoBatterySaver();
682 } else if (ACTION_AUTO_SAVER_NO_THANKS.equals(action)) {
683 dismissAutoSaverSuggestion();
684 BatterySaverUtils.suppressAutoBatterySaver(context);
John Spurlock3332ba52014-03-10 17:44:07 -0400685 }
686 }
687 }
John Spurlock3332ba52014-03-10 17:44:07 -0400688}