diff --git a/packages/SystemUI/res/layout/overheat_dialog_content.xml b/packages/SystemUI/res/layout/overheat_dialog_content.xml
new file mode 100644
index 0000000000000..d78272fe6abc9
--- /dev/null
+++ b/packages/SystemUI/res/layout/overheat_dialog_content.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
diff --git a/packages/SystemUI/res/layout/overheat_dialog_title.xml b/packages/SystemUI/res/layout/overheat_dialog_title.xml
new file mode 100644
index 0000000000000..65a512ae085be
--- /dev/null
+++ b/packages/SystemUI/res/layout/overheat_dialog_title.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
diff --git a/packages/SystemUI/res/raw/overheat_alarm.ogg b/packages/SystemUI/res/raw/overheat_alarm.ogg
new file mode 100644
index 0000000000000..5624f42a5c500
Binary files /dev/null and b/packages/SystemUI/res/raw/overheat_alarm.ogg differ
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index a68ba9b2b411b..317fe31f9b007 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -368,17 +368,31 @@
false
+
+ false
+
0
+
+ 0
+
-1
+
+ 60
+
2
+
+ 5
+
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 79e1fae98c216..f5f198d442575 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1023,4 +1023,11 @@
12dp
+
+
+ 24dp
+
+
+ 28dp
+
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 18f378e5919b1..114fbe4daaf7e 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2067,6 +2067,14 @@
Some features limited while phone cools down
Your phone will automatically try to cool down. You can still use your phone, but it may run slower.\n\nOnce your phone has cooled down, it will run normally.
+
+ Unplug charger
+
+ There\u2019s an issue charging this device. Unplug the power adapter and take care as the cable may be warm.
+
+ See care steps
+
+ help_uri_usb_warm
Left shortcut
diff --git a/packages/SystemUI/src/com/android/systemui/power/OverheatAlarmController.java b/packages/SystemUI/src/com/android/systemui/power/OverheatAlarmController.java
new file mode 100644
index 0000000000000..db15909196c7d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/power/OverheatAlarmController.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.power;
+
+import static android.content.Context.VIBRATOR_SERVICE;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.R;
+import com.android.systemui.media.NotificationPlayer;
+
+/**
+ * A Controller handle beep sound, vibration and TTS depend on state of OverheatAlarmDialog.
+ */
+public class OverheatAlarmController {
+ private static final String TAG = OverheatAlarmController.class.getSimpleName();
+
+ private static final int VIBRATION_INTERVAL = 2000;
+ private static final long[] VIBRATION_PATTERN = new long[]{0, 400, 200, 400, 200, 400, 200};
+
+ private static OverheatAlarmController sInstance;
+
+ private final Vibrator mVibrator;
+
+ private NotificationPlayer mPlayer;
+ private VibrationEffect mVibrationEffect;
+
+ private boolean mShouldVibrate;
+
+ /**
+ * The constructor only used to create singleton sInstance.
+ */
+ private OverheatAlarmController(Context context) {
+ mVibrator = (Vibrator) context.getSystemService(VIBRATOR_SERVICE);
+ }
+
+ /**
+ * Get singleton OverheatAlarmController instance.
+ */
+ public static OverheatAlarmController getInstance(Context context) {
+ if (context == null) {
+ throw new IllegalArgumentException();
+ }
+ if (sInstance == null) {
+ sInstance = new OverheatAlarmController(context);
+ }
+ return sInstance;
+ }
+
+ /**
+ * Starting alarm beep sound and vibration.
+ */
+ @VisibleForTesting
+ public void startAlarm(Context context) {
+ if (mPlayer != null) {
+ return;
+ }
+ playSound(context);
+ startVibrate();
+ }
+
+ /**
+ * Stop alarming beep sound, vibrating, and TTS if initialized.
+ */
+ @VisibleForTesting
+ public void stopAlarm() {
+ stopPlayer();
+ stopVibrate();
+ }
+
+ @VisibleForTesting
+ protected void playSound(Context context) {
+ Uri uri = new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
+ .authority(context.getBasePackageName())
+ .appendPath(Integer.toString(R.raw.overheat_alarm)).build();
+
+ if (mPlayer == null) {
+ mPlayer = new NotificationPlayer(TAG);
+ }
+ mPlayer.setUsesWakeLock(context);
+ mPlayer.play(context, uri, true /* looping */, getAlertAudioAttributes());
+ }
+
+ @VisibleForTesting
+ protected void stopPlayer() {
+ if (mPlayer != null) {
+ mPlayer.stop();
+ mPlayer = null;
+ }
+ }
+
+ @VisibleForTesting
+ protected void startVibrate() {
+ mShouldVibrate = true;
+ if (mVibrationEffect == null) {
+ mVibrationEffect = VibrationEffect.createWaveform(VIBRATION_PATTERN, -1);
+ }
+ performVibrate();
+ }
+
+ @VisibleForTesting
+ protected void performVibrate() {
+ if (mShouldVibrate && mVibrator != null) {
+ mVibrator.vibrate(mVibrationEffect, getAlertAudioAttributes());
+ Handler.getMain().sendMessageDelayed(
+ obtainMessage(OverheatAlarmController::performVibrate, this),
+ VIBRATION_INTERVAL);
+ }
+ }
+
+ @VisibleForTesting
+ protected void stopVibrate() {
+ if (mVibrator != null) {
+ mVibrator.cancel();
+ }
+ mShouldVibrate = false;
+ }
+
+ /**
+ * Build AudioAttributes for mPlayer(NotificationPlayer) and vibrator
+ * Use the alarm channel so it can vibrate in DnD mode, unless alarms are
+ * specifically disabled in DnD.
+ */
+ private static AudioAttributes getAlertAudioAttributes() {
+ AudioAttributes.Builder builder = new AudioAttributes.Builder();
+ builder.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION);
+ builder.setUsage(AudioAttributes.USAGE_ALARM);
+ // Set FLAG_BYPASS_INTERRUPTION_POLICY and FLAG_BYPASS_MUTE so that it enables
+ // audio in any DnD mode, even in total silence DnD mode (requires MODIFY_PHONE_STATE).
+ builder.setFlags(
+ AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY | AudioAttributes.FLAG_BYPASS_MUTE);
+ return builder.build();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/power/OverheatAlarmDialog.java b/packages/SystemUI/src/com/android/systemui/power/OverheatAlarmDialog.java
new file mode 100644
index 0000000000000..1ecec51162661
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/power/OverheatAlarmDialog.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.power;
+
+import android.app.AlertDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.TextView;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.android.systemui.R;
+
+/**
+ * The alarm dialog shown when the device overheats.
+ * When the temperature exceeds a threshold, we're showing this dialog to notify the user.
+ * Once the dialog shows, a sound and vibration will be played until the user touches the dialog.
+ */
+public class OverheatAlarmDialog extends AlertDialog {
+ private final OverheatDialogDelegate mOverheatDialogDelegate;
+ private final View mContentView, mTitleView;
+
+ private static boolean sHasUserInteracted;
+
+ private OverheatAlarmDialog.PowerEventReceiver mPowerEventReceiver;
+
+ /**
+ * OverheatAlarmDialog should appear over system panels and keyguard.
+ */
+ public OverheatAlarmDialog(Context context) {
+ super(context, R.style.Theme_SystemUI_Dialog_Alert);
+ mOverheatDialogDelegate = new OverheatDialogDelegate();
+
+ // Setup custom views, the purpose of set custom title and message is inject
+ // AccessibilityDelegate to solve beep sound and talk back mix problem
+ final LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ mContentView = inflater.inflate(R.layout.overheat_dialog_content, null);
+ mTitleView = inflater.inflate(R.layout.overheat_dialog_title, null);
+ setView(mContentView);
+ setCustomTitle(mTitleView);
+
+ setupDialog();
+ }
+
+ @Override
+ public void dismiss() {
+ sHasUserInteracted = false;
+ getContext().unregisterReceiver(mPowerEventReceiver);
+ super.dismiss();
+ }
+
+ private void setupDialog() {
+ getWindow().getAttributes().privateFlags |=
+ WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ getWindow().setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
+ | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+
+ // Register ACTION_SCREEN_OFF for power Key event.
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ mPowerEventReceiver = new OverheatAlarmDialog.PowerEventReceiver();
+ getContext().registerReceiverAsUser(mPowerEventReceiver, UserHandle.CURRENT, filter, null,
+ null);
+ }
+
+ @Override
+ public void setTitle(CharSequence title) {
+ if (mTitleView == null) {
+ super.setTitle(title);
+ return;
+ }
+ final TextView titleTextView = mTitleView.findViewById(R.id.alertTitle);
+ if (titleTextView != null) {
+ titleTextView.setText(title);
+ titleTextView.setAccessibilityDelegate(mOverheatDialogDelegate);
+ }
+ }
+
+ @Override
+ public void setMessage(CharSequence message) {
+ if (mContentView == null) {
+ super.setMessage(message);
+ return;
+ }
+ final TextView messageView = mContentView.findViewById(android.R.id.message);
+ if (messageView != null) {
+ messageView.setAccessibilityDelegate(mOverheatDialogDelegate);
+ messageView.requestAccessibilityFocus();
+ messageView.setText(message);
+ }
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (!sHasUserInteracted) {
+ switch (event.getKeyCode()) {
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ case KeyEvent.KEYCODE_BACK:
+ notifyAlarmBeepSoundChange();
+ sHasUserInteracted = true;
+ break;
+ }
+ }
+ return super.dispatchKeyEvent(event);
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ // Stop beep sound when touch alarm dialog.
+ if (!sHasUserInteracted) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN
+ || ev.getAction() == MotionEvent.ACTION_OUTSIDE) {
+ notifyAlarmBeepSoundChange();
+ sHasUserInteracted = true;
+ }
+ }
+ return super.dispatchTouchEvent(ev);
+ }
+
+ @VisibleForTesting
+ protected void notifyAlarmBeepSoundChange() {
+ this.getContext().sendBroadcast(new Intent(Intent.ACTION_ALARM_CHANGED).setPackage(
+ this.getContext().getPackageName())
+ .setFlags(Intent.FLAG_RECEIVER_FOREGROUND));
+ }
+
+ private final class PowerEventReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!sHasUserInteracted) {
+ notifyAlarmBeepSoundChange();
+ sHasUserInteracted = true;
+ }
+ }
+ }
+
+ /**
+ * Implement AccessibilityDelegate to stop beep sound while title or message view get
+ * accessibility focus, in case the alarm beep sound mix up talk back description.
+ */
+ private final class OverheatDialogDelegate extends View.AccessibilityDelegate {
+ @Override
+ public boolean performAccessibilityAction(View host, int action, Bundle args) {
+ if (action == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS && !sHasUserInteracted) {
+ notifyAlarmBeepSoundChange();
+ sHasUserInteracted = true;
+ }
+ return super.performAccessibilityAction(host, action, args);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index 288f058b529ad..c32a4435fa72f 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -16,6 +16,10 @@
package com.android.systemui.power;
+import static android.content.DialogInterface.BUTTON_NEGATIVE;
+import static android.content.DialogInterface.BUTTON_POSITIVE;
+
+import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -47,10 +51,13 @@ import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.settingslib.Utils;
import com.android.settingslib.fuelgauge.BatterySaverUtils;
import com.android.settingslib.utils.PowerUtil;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
+import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.util.NotificationChannels;
+import com.android.systemui.volume.Events;
import java.io.PrintWriter;
import java.text.NumberFormat;
@@ -111,6 +118,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
private final Context mContext;
private final NotificationManager mNoMan;
private final PowerManager mPowerMan;
+ private final KeyguardManager mKeyguard;
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final Receiver mReceiver = new Receiver();
private final Intent mOpenBatterySettings = settings(Intent.ACTION_POWER_USAGE_SUMMARY);
@@ -134,25 +142,37 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
private boolean mHighTempWarning;
private SystemUIDialog mHighTempDialog;
private SystemUIDialog mThermalShutdownDialog;
+ @VisibleForTesting
+ protected OverheatAlarmDialog mOverheatAlarmDialog;
public PowerNotificationWarnings(Context context) {
mContext = context;
mNoMan = mContext.getSystemService(NotificationManager.class);
mPowerMan = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ mKeyguard = mContext.getSystemService(KeyguardManager.class);
mReceiver.init();
}
@Override
public void dump(PrintWriter pw) {
- pw.print("mWarning="); pw.println(mWarning);
- pw.print("mPlaySound="); pw.println(mPlaySound);
- pw.print("mInvalidCharger="); pw.println(mInvalidCharger);
- pw.print("mShowing="); pw.println(SHOWING_STRINGS[mShowing]);
- pw.print("mSaverConfirmation="); pw.println(mSaverConfirmation != null ? "not null" : null);
+ pw.print("mWarning=");
+ pw.println(mWarning);
+ pw.print("mPlaySound=");
+ pw.println(mPlaySound);
+ pw.print("mInvalidCharger=");
+ pw.println(mInvalidCharger);
+ pw.print("mShowing=");
+ pw.println(SHOWING_STRINGS[mShowing]);
+ pw.print("mSaverConfirmation=");
+ pw.println(mSaverConfirmation != null ? "not null" : null);
pw.print("mSaverEnabledConfirmation=");
pw.println(mSaverEnabledConfirmation != null ? "not null" : null);
- pw.print("mHighTempWarning="); pw.println(mHighTempWarning);
- pw.print("mHighTempDialog="); pw.println(mHighTempDialog != null ? "not null" : null);
+ pw.print("mHighTempWarning=");
+ pw.println(mHighTempWarning);
+ pw.print("mHighTempDialog=");
+ pw.println(mHighTempDialog != null ? "not null" : null);
+ pw.print("mOverheatAlarmDialog=");
+ pw.println(mOverheatAlarmDialog != null ? "not null" : null);
pw.print("mThermalShutdownDialog=");
pw.println(mThermalShutdownDialog != null ? "not null" : null);
}
@@ -370,6 +390,81 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
mNoMan.notifyAsUser(TAG_TEMPERATURE, SystemMessage.NOTE_HIGH_TEMP, n, UserHandle.ALL);
}
+ /**
+ * PowerUI detect thermal overheat, notify to popup alarm dialog.
+ * Alarm with beep sound, showing overheat alarm dialog until user click OK or link of help.
+ * Do not auto dismiss even temperature drop.
+ *
+ * @param overheat true if device overheat, temperature >= threshold.
+ * false if device temperature <= threshold tolerance after overheat
+ * alarmed.
+ * @param shouldBeepSound true alarm beep sound until user interactive with device
+ * false showing alarm dialog only
+ */
+ @Override
+ public void notifyHighTemperatureAlarm(boolean overheat, boolean shouldBeepSound) {
+ // Overheat and non-null dialog are XOR(exclusive or) relationship
+ if (overheat ^ (mOverheatAlarmDialog != null)) {
+ // b/120188825 Since notifyHighTemperatureAlarm() could be triggered by
+ // non-ui thread such as ThermalEventListener.notifyThrottling()
+ mHandler.post(() -> setOverheatAlarmDialogShowing(overheat));
+ setAlarmShouldSound(shouldBeepSound);
+ }
+ }
+
+ /**
+ * Showing overheat alarm dialog until user click OK button or link of help to dismiss
+ *
+ * @param shouldShow whether to show overheat alarm dialog.
+ */
+ protected void setOverheatAlarmDialogShowing(boolean shouldShow) {
+ if (shouldShow && mOverheatAlarmDialog == null) {
+ OverheatAlarmDialog d = new OverheatAlarmDialog(mContext);
+ d.setCancelable(false);
+ d.setButton(BUTTON_NEGATIVE,
+ mContext.getString(R.string.high_temp_alarm_help_care_steps),
+ (dialogInterface, i) -> {
+ final String contextString = mContext.getString(
+ R.string.high_temp_alarm_help_url);
+ final Intent helpIntent = new Intent();
+ helpIntent.setClassName("com.android.settings",
+ "com.android.settings.HelpTrampoline");
+ helpIntent.putExtra(Intent.EXTRA_TEXT, contextString);
+ Dependency.get(ActivityStarter.class).startActivity(helpIntent,
+ true /* dismissShade */, resultCode -> {
+ mOverheatAlarmDialog = null;
+ });
+ });
+ d.setButton(BUTTON_POSITIVE, mContext.getString(com.android.internal.R.string.ok),
+ (dialogInterface, i) -> mOverheatAlarmDialog = null);
+ d.setOnDismissListener(dialogInterface -> {
+ mOverheatAlarmDialog = null;
+ Events.writeEvent(mContext, Events.EVENT_DISMISS_OVERHEAT_ALARM,
+ Events.DISMISS_REASON_DONE_CLICKED,
+ mKeyguard.isKeyguardLocked());
+ });
+ d.show();
+ Events.writeEvent(mContext, Events.EVENT_SHOW_OVERHEAT_ALARM,
+ Events.SHOW_REASON_OVERHEAD_ALARM_CHANGED,
+ mKeyguard.isKeyguardLocked());
+ mOverheatAlarmDialog = d;
+ }
+ }
+
+ /**
+ * Whether to alarm beep sound when overheat dialog showing.
+ *
+ * @param shouldSound whether to alarm beep sound.
+ */
+ protected void setAlarmShouldSound(boolean shouldSound) {
+ Log.d(TAG, "setAlarmShouldSound, " + shouldSound);
+ if (shouldSound) {
+ OverheatAlarmController.getInstance(mContext).startAlarm(mContext);
+ } else {
+ OverheatAlarmController.getInstance(mContext).stopAlarm();
+ }
+ }
+
private void showHighTemperatureDialog() {
if (mHighTempDialog != null) return;
final SystemUIDialog d = new SystemUIDialog(mContext);
@@ -643,6 +738,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
filter.addAction(ACTION_ENABLE_AUTO_SAVER);
filter.addAction(ACTION_AUTO_SAVER_NO_THANKS);
filter.addAction(ACTION_DISMISS_AUTO_SAVER_SUGGESTION);
+ filter.addAction(Intent.ACTION_ALARM_CHANGED);
mContext.registerReceiverAsUser(this, UserHandle.ALL, filter,
android.Manifest.permission.DEVICE_POWER, mHandler);
}
@@ -682,6 +778,8 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
} else if (ACTION_AUTO_SAVER_NO_THANKS.equals(action)) {
dismissAutoSaverSuggestion();
BatterySaverUtils.suppressAutoBatterySaver(context);
+ } else if (Intent.ACTION_ALARM_CHANGED.equals(action)) {
+ setAlarmShouldSound(false /* mHasUserInteracted */);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 0b9067e5dc1ff..80ebb6ee9acdc 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -54,12 +54,15 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.time.Duration;
import java.util.Arrays;
+import java.util.Locale;
public class PowerUI extends SystemUI {
static final String TAG = "PowerUI";
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final long TEMPERATURE_INTERVAL = 30 * DateUtils.SECOND_IN_MILLIS;
private static final long TEMPERATURE_LOGGING_INTERVAL = DateUtils.HOUR_IN_MILLIS;
+ private static final int TEMPERATURE_OVERHEAT_WARNING = 0;
+ private static final int TEMPERATURE_OVERHEAT_ALARM = 1;
private static final int MAX_RECENT_TEMPS = 125; // TEMPERATURE_LOGGING_INTERVAL plus a buffer
static final long THREE_HOURS_IN_MILLIS = DateUtils.HOUR_IN_MILLIS * 3;
private static final int CHARGE_CYCLE_PERCENT_RESET = 45;
@@ -80,15 +83,22 @@ public class PowerUI extends SystemUI {
private Estimate mLastEstimate;
private boolean mLowWarningShownThisChargeCycle;
private boolean mSevereWarningShownThisChargeCycle;
+ private boolean mEnableTemperatureWarning;
+ private boolean mEnableTemperatureAlarm;
+ private boolean mIsOverheatAlarming;
private int mLowBatteryAlertCloseLevel;
private final int[] mLowBatteryReminderLevels = new int[2];
private long mScreenOffTime = -1;
- private float mThresholdTemp;
- private float[] mRecentTemps = new float[MAX_RECENT_TEMPS];
- private int mNumTemps;
+ private float mThresholdWarningTemp;
+ private float mThresholdAlarmTemp;
+ private float mThresholdAlarmTempTolerance;
+ private float[] mRecentSkinTemps = new float[MAX_RECENT_TEMPS];
+ private float[] mRecentAlarmTemps = new float[MAX_RECENT_TEMPS];
+ private int mWarningNumTemps;
+ private int mAlarmNumTemps;
private long mNextLogTime;
private IThermalService mThermalService;
@@ -98,7 +108,7 @@ public class PowerUI extends SystemUI {
// by using the same instance (method references are not guaranteed to be the same object
// We create a method reference here so that we are guaranteed that we can remove a callback
// each time they are created).
- private final Runnable mUpdateTempCallback = this::updateTemperatureWarning;
+ private final Runnable mUpdateTempCallback = this::updateTemperature;
public void start() {
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
@@ -126,7 +136,7 @@ public class PowerUI extends SystemUI {
// to the temperature being too high.
showThermalShutdownDialog();
- initTemperatureWarning();
+ initTemperature();
}
@Override
@@ -135,7 +145,7 @@ public class PowerUI extends SystemUI {
// Safe to modify mLastConfiguration here as it's only updated by the main thread (here).
if ((mLastConfiguration.updateFrom(newConfig) & mask) != 0) {
- mHandler.post(this::initTemperatureWarning);
+ mHandler.post(this::initTemperature);
}
}
@@ -193,6 +203,7 @@ public class PowerUI extends SystemUI {
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_USER_SWITCHED);
+ filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
mContext.registerReceiver(this, filter, null, mHandler);
}
@@ -258,6 +269,8 @@ public class PowerUI extends SystemUI {
mScreenOffTime = -1;
} else if (Intent.ACTION_USER_SWITCHED.equals(action)) {
mWarnings.userSwitched();
+ } else if (Intent.ACTION_POWER_DISCONNECTED.equals(action)) {
+ updateTemperature();
} else {
Slog.w(TAG, "unknown intent: " + intent);
}
@@ -364,18 +377,51 @@ public class PowerUI extends SystemUI {
return canShowWarning || canShowSevereWarning;
}
- private void initTemperatureWarning() {
- ContentResolver resolver = mContext.getContentResolver();
- Resources resources = mContext.getResources();
- if (Settings.Global.getInt(resolver, Settings.Global.SHOW_TEMPERATURE_WARNING,
- resources.getInteger(R.integer.config_showTemperatureWarning)) == 0) {
+ private void initTemperature() {
+ initTemperatureWarning();
+ initTemperatureAlarm();
+ if (mEnableTemperatureWarning || mEnableTemperatureAlarm) {
+ bindThermalService();
+ }
+ }
+
+ private void initTemperatureAlarm() {
+ mEnableTemperatureAlarm = mContext.getResources().getInteger(
+ R.integer.config_showTemperatureAlarm) != 0;
+ if (!mEnableTemperatureAlarm) {
return;
}
- mThresholdTemp = Settings.Global.getFloat(resolver, Settings.Global.WARNING_TEMPERATURE,
+ float[] throttlingTemps = mHardwarePropertiesManager.getDeviceTemperatures(
+ HardwarePropertiesManager.DEVICE_TEMPERATURE_SKIN,
+ HardwarePropertiesManager.TEMPERATURE_THROTTLING);
+ if (throttlingTemps == null || throttlingTemps.length < TEMPERATURE_OVERHEAT_ALARM + 1) {
+ mThresholdAlarmTemp = mContext.getResources().getInteger(
+ R.integer.config_alarmTemperature);
+ } else {
+ mThresholdAlarmTemp = throttlingTemps[TEMPERATURE_OVERHEAT_ALARM];
+ }
+ mThresholdAlarmTempTolerance = mThresholdAlarmTemp - mContext.getResources().getInteger(
+ R.integer.config_alarmTemperatureTolerance);
+ Log.d(TAG, "mThresholdAlarmTemp=" + mThresholdAlarmTemp + ", mThresholdAlarmTempTolerance="
+ + mThresholdAlarmTempTolerance);
+ }
+
+ private void initTemperatureWarning() {
+ ContentResolver resolver = mContext.getContentResolver();
+ Resources resources = mContext.getResources();
+ mEnableTemperatureWarning = Settings.Global.getInt(resolver,
+ Settings.Global.SHOW_TEMPERATURE_WARNING,
+ resources.getInteger(R.integer.config_showTemperatureWarning)) != 0;
+ if (!mEnableTemperatureWarning) {
+ return;
+ }
+
+ mThresholdWarningTemp = Settings.Global.getFloat(resolver,
+ Settings.Global.WARNING_TEMPERATURE,
resources.getInteger(R.integer.config_warningTemperature));
- if (mThresholdTemp < 0f) {
+ if (mThresholdWarningTemp < 0f) {
// Get the shutdown temperature, adjust for warning tolerance.
float[] throttlingTemps = mHardwarePropertiesManager.getDeviceTemperatures(
HardwarePropertiesManager.DEVICE_TEMPERATURE_SKIN,
@@ -385,10 +431,12 @@ public class PowerUI extends SystemUI {
|| throttlingTemps[0] == HardwarePropertiesManager.UNDEFINED_TEMPERATURE) {
return;
}
- mThresholdTemp = throttlingTemps[0] -
- resources.getInteger(R.integer.config_warningTemperatureTolerance);
+ mThresholdWarningTemp = throttlingTemps[0] - resources.getInteger(
+ R.integer.config_warningTemperatureTolerance);
}
+ }
+ private void bindThermalService() {
if (mThermalService == null) {
// Enable push notifications of throttling from vendor thermal
// management subsystem via thermalservice, in addition to our
@@ -416,7 +464,7 @@ public class PowerUI extends SystemUI {
mHandler.removeCallbacks(mUpdateTempCallback);
// We have passed all of the checks, start checking the temp
- updateTemperatureWarning();
+ updateTemperature();
}
private void showThermalShutdownDialog() {
@@ -426,38 +474,110 @@ public class PowerUI extends SystemUI {
}
}
+ /**
+ * Update temperature depend on config, there are type types of messages by design
+ * TEMPERATURE_OVERHEAT_WARNING Send generic notification to notify user
+ * TEMPERATURE_OVERHEAT_ALARM popup emergency Dialog for user
+ */
@VisibleForTesting
- protected void updateTemperatureWarning() {
+ protected void updateTemperature() {
+ if (!mEnableTemperatureWarning && !mEnableTemperatureAlarm) {
+ return;
+ }
+
float[] temps = mHardwarePropertiesManager.getDeviceTemperatures(
HardwarePropertiesManager.DEVICE_TEMPERATURE_SKIN,
HardwarePropertiesManager.TEMPERATURE_CURRENT);
+ if (temps == null) {
+ Log.e(TAG, "Can't query current temperature value from HardProps SKIN type");
+ return;
+ }
+
+ final float[] temps_compat;
+ if (temps.length < TEMPERATURE_OVERHEAT_ALARM + 1) {
+ temps_compat = new float[] { temps[0], temps[0] };
+ } else {
+ temps_compat = temps;
+ }
+
+ if (mEnableTemperatureWarning) {
+ updateTemperatureWarning(temps_compat);
+ logTemperatureStats(TEMPERATURE_OVERHEAT_WARNING);
+ }
+
+ if (mEnableTemperatureAlarm) {
+ updateTemperatureAlarm(temps_compat);
+ logTemperatureStats(TEMPERATURE_OVERHEAT_ALARM);
+ }
+
+ mHandler.postDelayed(mUpdateTempCallback, TEMPERATURE_INTERVAL);
+ }
+
+ /**
+ * Update legacy overheat warning notification from skin temperatures
+ *
+ * @param temps the array include two types of value from DEVICE_TEMPERATURE_SKIN
+ * this function only obtain the value of temps[TEMPERATURE_OVERHEAT_WARNING]
+ */
+ @VisibleForTesting
+ protected void updateTemperatureWarning(float[] temps) {
if (temps.length != 0) {
- float temp = temps[0];
- mRecentTemps[mNumTemps++] = temp;
+ float temp = temps[TEMPERATURE_OVERHEAT_WARNING];
+ mRecentSkinTemps[mWarningNumTemps++] = temp;
StatusBar statusBar = getComponent(StatusBar.class);
if (statusBar != null && !statusBar.isDeviceInVrMode()
- && temp >= mThresholdTemp) {
- logAtTemperatureThreshold(temp);
+ && temp >= mThresholdWarningTemp) {
+ logAtTemperatureThreshold(TEMPERATURE_OVERHEAT_WARNING, temp,
+ mThresholdWarningTemp);
mWarnings.showHighTemperatureWarning();
} else {
mWarnings.dismissHighTemperatureWarning();
}
}
-
- logTemperatureStats();
-
- mHandler.postDelayed(mUpdateTempCallback, TEMPERATURE_INTERVAL);
}
- private void logAtTemperatureThreshold(float temp) {
+ /**
+ * Update overheat alarm from skin temperatures
+ * OEM can config alarm with beep sound by config_alarmTemperatureBeepSound
+ *
+ * @param temps the array include two types of value from DEVICE_TEMPERATURE_SKIN
+ * this function only obtain the value of temps[TEMPERATURE_OVERHEAT_ALARM]
+ */
+ @VisibleForTesting
+ protected void updateTemperatureAlarm(float[] temps) {
+ if (temps.length != 0) {
+ final float temp = temps[TEMPERATURE_OVERHEAT_ALARM];
+ final boolean shouldBeepSound = mContext.getResources().getBoolean(
+ R.bool.config_alarmTemperatureBeepSound);
+ mRecentAlarmTemps[mAlarmNumTemps++] = temp;
+ if (temp >= mThresholdAlarmTemp && !mIsOverheatAlarming) {
+ mWarnings.notifyHighTemperatureAlarm(true /* overheat */, shouldBeepSound);
+ mIsOverheatAlarming = true;
+ } else if (temp <= mThresholdAlarmTempTolerance && mIsOverheatAlarming) {
+ mWarnings.notifyHighTemperatureAlarm(false /* overheat */, false /* beepSound */);
+ mIsOverheatAlarming = false;
+ }
+ logAtTemperatureThreshold(TEMPERATURE_OVERHEAT_ALARM, temp, mThresholdAlarmTemp);
+ }
+ }
+
+ private void logAtTemperatureThreshold(int type, float temp, float thresholdTemp) {
StringBuilder sb = new StringBuilder();
sb.append("currentTemp=").append(temp)
- .append(",thresholdTemp=").append(mThresholdTemp)
+ .append(",overheatType=").append(type)
+ .append(",isOverheatAlarm=").append(mIsOverheatAlarming)
+ .append(",thresholdTemp=").append(thresholdTemp)
.append(",batteryStatus=").append(mBatteryStatus)
.append(",recentTemps=");
- for (int i = 0; i < mNumTemps; i++) {
- sb.append(mRecentTemps[i]).append(',');
+ if (type == TEMPERATURE_OVERHEAT_WARNING) {
+ for (int i = 0; i < mWarningNumTemps; i++) {
+ sb.append(mRecentSkinTemps[i]).append(',');
+ }
+ } else {
+ for (int i = 0; i < mAlarmNumTemps; i++) {
+ sb.append(mRecentAlarmTemps[i]).append(',');
+ }
}
Slog.i(TAG, sb.toString());
}
@@ -466,16 +586,20 @@ public class PowerUI extends SystemUI {
* Calculates and logs min, max, and average
* {@link HardwarePropertiesManager#DEVICE_TEMPERATURE_SKIN} over the past
* {@link #TEMPERATURE_LOGGING_INTERVAL}.
+ * @param type TEMPERATURE_OVERHEAT_WARNING Send generic notification to notify user
+ * TEMPERATURE_OVERHEAT_ALARM Popup emergency Dialog for user
*/
- private void logTemperatureStats() {
- if (mNextLogTime > System.currentTimeMillis() && mNumTemps != MAX_RECENT_TEMPS) {
+ private void logTemperatureStats(int type) {
+ int numTemp = type == TEMPERATURE_OVERHEAT_ALARM ? mAlarmNumTemps : mWarningNumTemps;
+ if (mNextLogTime > System.currentTimeMillis() && numTemp != MAX_RECENT_TEMPS) {
return;
}
-
- if (mNumTemps > 0) {
- float sum = mRecentTemps[0], min = mRecentTemps[0], max = mRecentTemps[0];
- for (int i = 1; i < mNumTemps; i++) {
- float temp = mRecentTemps[i];
+ float[] recentTemps =
+ type == TEMPERATURE_OVERHEAT_ALARM ? mRecentAlarmTemps : mRecentSkinTemps;
+ if (numTemp > 0) {
+ float sum = recentTemps[0], min = recentTemps[0], max = recentTemps[0];
+ for (int i = 1; i < numTemp; i++) {
+ float temp = recentTemps[i];
sum += temp;
if (temp > max) {
max = temp;
@@ -485,14 +609,22 @@ public class PowerUI extends SystemUI {
}
}
- float avg = sum / mNumTemps;
- Slog.i(TAG, "avg=" + avg + ",min=" + min + ",max=" + max);
- MetricsLogger.histogram(mContext, "device_skin_temp_avg", (int) avg);
- MetricsLogger.histogram(mContext, "device_skin_temp_min", (int) min);
- MetricsLogger.histogram(mContext, "device_skin_temp_max", (int) max);
+ float avg = sum / numTemp;
+ Slog.i(TAG, "Type=" + type + ",avg=" + avg + ",min=" + min + ",max=" + max);
+ String t = type == TEMPERATURE_OVERHEAT_WARNING ? "skin" : "alarm";
+ MetricsLogger.histogram(mContext,
+ String.format(Locale.ENGLISH, "device_%1$s_temp_avg", t), (int) avg);
+ MetricsLogger.histogram(mContext,
+ String.format(Locale.ENGLISH, "device_%1$s_temp_min", t), (int) min);
+ MetricsLogger.histogram(mContext,
+ String.format(Locale.ENGLISH, "device_%1$s_temp_max", t), (int) max);
}
setNextLogTime();
- mNumTemps = 0;
+ if (type == TEMPERATURE_OVERHEAT_ALARM) {
+ mAlarmNumTemps = 0;
+ } else {
+ mWarningNumTemps = 0;
+ }
}
private void setNextLogTime() {
@@ -525,8 +657,10 @@ public class PowerUI extends SystemUI {
Settings.Global.LOW_BATTERY_SOUND_TIMEOUT, 0));
pw.print("bucket: ");
pw.println(Integer.toString(findBatteryLevelBucket(mBatteryLevel)));
- pw.print("mThresholdTemp=");
- pw.println(Float.toString(mThresholdTemp));
+ pw.print("mThresholdWarningTemp=");
+ pw.println(Float.toString(mThresholdWarningTemp));
+ pw.print("mThresholdAlarmTemp=");
+ pw.println(Float.toString(mThresholdAlarmTemp));
pw.print("mNextLogTime=");
pw.println(Long.toString(mNextLogTime));
mWarnings.dump(pw);
@@ -534,18 +668,41 @@ public class PowerUI extends SystemUI {
public interface WarningsUI {
void update(int batteryLevel, int bucket, long screenOffTime);
+
void updateEstimate(Estimate estimate);
+
void updateThresholds(long lowThreshold, long severeThreshold);
+
void dismissLowBatteryWarning();
+
void showLowBatteryWarning(boolean playSound);
+
void dismissInvalidChargerWarning();
+
void showInvalidChargerWarning();
+
void updateLowBatteryWarning();
+
boolean isInvalidChargerWarningShowing();
+
void dismissHighTemperatureWarning();
+
void showHighTemperatureWarning();
+
+ /**
+ * PowerUI detect thermal overheat, notify to popup alert dialog strongly.
+ * Alarm with beep sound with un-dismissible dialog until device cool down below threshold,
+ * then popup another dismissible dialog to user.
+ *
+ * @param overheat whether device temperature over threshold
+ * @param beepSound should beep sound once overheat
+ */
+ void notifyHighTemperatureAlarm(boolean overheat, boolean beepSound);
+
void showThermalShutdownWarning();
+
void dump(PrintWriter pw);
+
void userSwitched();
}
@@ -554,9 +711,9 @@ public class PowerUI extends SystemUI {
@Override public void notifyThrottling(boolean isThrottling, Temperature temp) {
// Trigger an update of the temperature warning. Only one
// callback can be enabled at a time, so remove any existing
- // callback; updateTemperatureWarning will schedule another one.
+ // callback; updateTemperature will schedule another one.
mHandler.removeCallbacks(mUpdateTempCallback);
- updateTemperatureWarning();
+ updateTemperature();
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/Events.java b/packages/SystemUI/src/com/android/systemui/volume/Events.java
index ca55e1f23d206..96e8310a1c625 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/Events.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/Events.java
@@ -53,6 +53,8 @@ public class Events {
public static final int EVENT_TOUCH_LEVEL_DONE = 16; // (stream|int) (level|bool)
public static final int EVENT_ZEN_CONFIG_CHANGED = 17; // (allow/disallow|string)
public static final int EVENT_RINGER_TOGGLE = 18; // (ringer_mode)
+ public static final int EVENT_SHOW_OVERHEAT_ALARM = 19; // (reason|int) (keyguard|bool)
+ public static final int EVENT_DISMISS_OVERHEAT_ALARM = 20; // (reason|int) (keyguard|bool)
private static final String[] EVENT_TAGS = {
"show_dialog",
@@ -73,7 +75,9 @@ public class Events {
"mute_changed",
"touch_level_done",
"zen_mode_config_changed",
- "ringer_toggle"
+ "ringer_toggle",
+ "show_overheat_alarm",
+ "dismiss_overheat_alarm"
};
public static final int DISMISS_REASON_UNKNOWN = 0;
@@ -100,10 +104,12 @@ public class Events {
public static final int SHOW_REASON_UNKNOWN = 0;
public static final int SHOW_REASON_VOLUME_CHANGED = 1;
public static final int SHOW_REASON_REMOTE_VOLUME_CHANGED = 2;
+ public static final int SHOW_REASON_OVERHEAD_ALARM_CHANGED = 3;
public static final String[] SHOW_REASONS = {
"unknown",
"volume_changed",
- "remote_volume_changed"
+ "remote_volume_changed",
+ "overheat_alarm_changed"
};
public static final int ICON_STATE_UNKNOWN = 0;
@@ -181,6 +187,19 @@ public class Events {
case EVENT_SUPPRESSOR_CHANGED:
sb.append(list[0]).append(' ').append(list[1]);
break;
+ case EVENT_SHOW_OVERHEAT_ALARM:
+ MetricsLogger.visible(context, MetricsEvent.POWER_OVERHEAT_ALARM);
+ MetricsLogger.histogram(context, "show_overheat_alarm",
+ (Boolean) list[1] ? 1 : 0);
+ sb.append(SHOW_REASONS[(Integer) list[0]]).append(" keyguard=").append(list[1]);
+ break;
+ case EVENT_DISMISS_OVERHEAT_ALARM:
+ MetricsLogger.hidden(context, MetricsEvent.POWER_OVERHEAT_ALARM);
+ MetricsLogger.histogram(context, "dismiss_overheat_alarm",
+ (Boolean) list[1] ? 1 : 0);
+ sb.append(DISMISS_REASONS[(Integer) list[0]]).append(" keyguard=").append(
+ list[1]);
+ break;
default:
sb.append(Arrays.asList(list));
break;
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index 1be83229cf22a..c40d72d483a00 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -49,6 +49,7 @@
+
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/OverheatAlarmControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/OverheatAlarmControllerTest.java
new file mode 100644
index 0000000000000..651d2b7af7db3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/OverheatAlarmControllerTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.power;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+@SmallTest
+public class OverheatAlarmControllerTest extends SysuiTestCase{
+ OverheatAlarmController mOverheatAlarmController;
+ OverheatAlarmController mSpyOverheatAlarmController;
+
+ @Before
+ public void setUp() {
+ mOverheatAlarmController = OverheatAlarmController.getInstance(mContext);
+ mSpyOverheatAlarmController = spy(mOverheatAlarmController);
+ }
+
+ @After
+ public void tearDown() {
+ mSpyOverheatAlarmController.stopAlarm();
+ }
+
+ @Test
+ public void testGetInstance() {
+ assertThat(mOverheatAlarmController).isNotNull();
+ }
+
+ @Test
+ public void testStartAlarm_PlaySound() {
+ mSpyOverheatAlarmController.startAlarm(mContext);
+ verify(mSpyOverheatAlarmController).playSound(mContext);
+ }
+
+ @Test
+ public void testStartAlarm_InitVibrate() {
+ mSpyOverheatAlarmController.startAlarm(mContext);
+ verify(mSpyOverheatAlarmController).startVibrate();
+ }
+
+ @Test
+ public void testStartAlarm_StartVibrate() {
+ mSpyOverheatAlarmController.startAlarm(mContext);
+ verify(mSpyOverheatAlarmController).performVibrate();
+ }
+
+ @Test
+ public void testStopAlarm_StopPlayer() {
+ mSpyOverheatAlarmController.stopAlarm();
+ verify(mSpyOverheatAlarmController).stopPlayer();
+ }
+
+ @Test
+ public void testStopAlarm_StopVibrate() {
+ mSpyOverheatAlarmController.stopAlarm();
+ verify(mSpyOverheatAlarmController).stopVibrate();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/OverheatAlarmDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/OverheatAlarmDialogTest.java
new file mode 100644
index 0000000000000..04d3163f05742
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/OverheatAlarmDialogTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.power;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.os.SystemClock;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.WindowManager;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper(setAsMainLooper = true)
+@SmallTest
+public class OverheatAlarmDialogTest extends SysuiTestCase {
+
+ private OverheatAlarmDialog mDialog, mSpyDialog;
+
+ @Before
+ public void setup() {
+ mDialog = new OverheatAlarmDialog(mContext);
+ mSpyDialog = spy(mDialog);
+ }
+
+ @After
+ public void tearDown() {
+ mSpyDialog = mDialog = null;
+ }
+
+ @Test
+ public void testFlagShowForAllUsers() {
+ assertThat((mDialog.getWindow().getAttributes().privateFlags
+ & WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS) != 0).isTrue();
+ }
+
+ @Test
+ public void testFlagShowWhenLocked() {
+ assertThat((mDialog.getWindow().getAttributes().flags
+ & WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED) != 0).isTrue();
+ }
+
+ @Test
+ public void testFlagTurnScreenOn() {
+ assertThat((mDialog.getWindow().getAttributes().flags
+ & WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON) != 0).isTrue();
+ }
+
+ @Test
+ public void testFlagKeepScreenOn() {
+ assertThat((mDialog.getWindow().getAttributes().flags
+ & WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) != 0).isTrue();
+ }
+
+ @Test
+ public void testTouchOutsideDialog_NotifyAlarmBeepSoundIntent_ShouldStopBeepSound() {
+ final long currentTime = SystemClock.uptimeMillis();
+ mSpyDialog.show();
+ MotionEvent ev = createMotionEvent(MotionEvent.ACTION_DOWN, currentTime, 0, 0);
+ mSpyDialog.dispatchTouchEvent(ev);
+
+ verify(mSpyDialog, atLeastOnce()).notifyAlarmBeepSoundChange();
+ mSpyDialog.dismiss();
+ }
+
+ @Test
+ public void testPressBackKey_NotifyAlarmBeepSoundIntent_ShouldStopBeepSound() {
+ mSpyDialog.show();
+ KeyEvent ev = new KeyEvent(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK, 0,
+ 0);
+ mSpyDialog.dispatchKeyEvent(ev);
+
+ verify(mSpyDialog, atLeastOnce()).notifyAlarmBeepSoundChange();
+ mSpyDialog.dismiss();
+ }
+
+ @Test
+ public void testPressVolumeUp_NotifyAlarmBeepSoundIntent_ShouldStopBeepSound() {
+ mSpyDialog.show();
+ KeyEvent ev = new KeyEvent(0, 0, MotionEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_VOLUME_UP, 0, 0);
+ mSpyDialog.dispatchKeyEvent(ev);
+
+ verify(mSpyDialog, atLeastOnce()).notifyAlarmBeepSoundChange();
+ mSpyDialog.dismiss();
+ }
+
+ @Test
+ public void testPressVolumeDown_NotifyAlarmBeepSoundIntent_ShouldStopBeepSound() {
+ mSpyDialog.show();
+ KeyEvent ev = new KeyEvent(0, 0, MotionEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_VOLUME_DOWN, 0, 0);
+ mSpyDialog.dispatchKeyEvent(ev);
+
+ verify(mSpyDialog, atLeastOnce()).notifyAlarmBeepSoundChange();
+ mSpyDialog.dismiss();
+ }
+
+ private MotionEvent createMotionEvent(int action, long eventTime, float x, float y) {
+ return MotionEvent.obtain(0, eventTime, action, x, y, 0);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
index bf6cc53aa5e90..d37c2852bc1f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
@@ -16,9 +16,8 @@
package com.android.systemui.power;
-import static android.test.MoreAsserts.assertNotEqual;
+import static com.google.common.truth.Truth.assertThat;
-import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
@@ -26,6 +25,8 @@ import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -38,7 +39,7 @@ import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.util.NotificationChannels;
-import java.util.concurrent.TimeUnit;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -51,13 +52,22 @@ public class PowerNotificationWarningsTest extends SysuiTestCase {
public static final String FORMATTED_45M = "0h 45m";
public static final String FORMATTED_HOUR = "1h 0m";
private final NotificationManager mMockNotificationManager = mock(NotificationManager.class);
- private PowerNotificationWarnings mPowerNotificationWarnings;
+ private PowerNotificationWarnings mPowerNotificationWarnings, mSpyPowerNotificationWarnings;
@Before
public void setUp() throws Exception {
// Test Instance.
mContext.addMockSystemService(NotificationManager.class, mMockNotificationManager);
mPowerNotificationWarnings = new PowerNotificationWarnings(mContext);
+ mSpyPowerNotificationWarnings = spy(mPowerNotificationWarnings);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mSpyPowerNotificationWarnings.mOverheatAlarmDialog != null) {
+ mSpyPowerNotificationWarnings.mOverheatAlarmDialog.dismiss();
+ mSpyPowerNotificationWarnings.mOverheatAlarmDialog = null;
+ }
}
@Test
@@ -151,4 +161,146 @@ public class PowerNotificationWarningsTest extends SysuiTestCase {
verify(mMockNotificationManager, times(1)).cancelAsUser(anyString(),
eq(SystemMessage.NOTE_THERMAL_SHUTDOWN), any());
}
+
+ @Test
+ public void testSetOverheatAlarmDialog_Overheat_ShouldShowing() {
+ final boolean overheat = true;
+ final boolean shouldBeepSound = false;
+ mContext.getMainThreadHandler().post(
+ () -> mSpyPowerNotificationWarnings.notifyHighTemperatureAlarm(overheat,
+ shouldBeepSound));
+ waitForIdleSync();
+ verify(mSpyPowerNotificationWarnings, times(1)).setOverheatAlarmDialogShowing(overheat);
+ verify(mSpyPowerNotificationWarnings, times(1)).setAlarmShouldSound(shouldBeepSound);
+ }
+
+ @Test
+ public void testSetOverheatAlarmDialog_Overheat_ShouldShowingWithBeepSound() {
+ final boolean overheat = true;
+ final boolean shouldBeepSound = true;
+ mContext.getMainThreadHandler().post(
+ () -> mSpyPowerNotificationWarnings.notifyHighTemperatureAlarm(overheat,
+ shouldBeepSound));
+ waitForIdleSync();
+ verify(mSpyPowerNotificationWarnings, times(1)).setOverheatAlarmDialogShowing(overheat);
+ verify(mSpyPowerNotificationWarnings, times(1)).setAlarmShouldSound(shouldBeepSound);
+ }
+
+ @Test
+ public void testSetOverheatAlarmDialog_NotOverheat_ShouldNotShowing() {
+ final boolean overheat = false;
+ final boolean shouldBeepSound = false;
+ mContext.getMainThreadHandler().post(
+ () -> mSpyPowerNotificationWarnings.notifyHighTemperatureAlarm(overheat,
+ shouldBeepSound));
+ waitForIdleSync();
+ verify(mSpyPowerNotificationWarnings, never()).setOverheatAlarmDialogShowing(overheat);
+ verify(mSpyPowerNotificationWarnings, never()).setAlarmShouldSound(shouldBeepSound);
+ }
+
+ @Test
+ public void testSetOverheatAlarmDialog_NotOverheat_ShouldNotAlarmBeepSound() {
+ final boolean overheat = false;
+ final boolean configBeepSound = true;
+ mContext.getMainThreadHandler().post(
+ () -> mSpyPowerNotificationWarnings.notifyHighTemperatureAlarm(overheat,
+ configBeepSound));
+ waitForIdleSync();
+ verify(mSpyPowerNotificationWarnings, never()).setOverheatAlarmDialogShowing(overheat);
+ verify(mSpyPowerNotificationWarnings, never()).setAlarmShouldSound(configBeepSound);
+ }
+
+ @Test
+ public void testSetAlarmShouldSound_OverheatDrop_ShouldNotSound() {
+ final boolean overheat = true;
+ final boolean shouldBeepSound = true;
+ mContext.getMainThreadHandler().post(
+ () -> mSpyPowerNotificationWarnings.notifyHighTemperatureAlarm(overheat,
+ shouldBeepSound));
+ waitForIdleSync();
+ // First time overheat, show overheat alarm dialog with alarm beep sound
+ verify(mSpyPowerNotificationWarnings, times(1)).setOverheatAlarmDialogShowing(overheat);
+ verify(mSpyPowerNotificationWarnings, times(1)).setAlarmShouldSound(shouldBeepSound);
+
+ // After disconnected cable or temperature drop
+ mContext.getMainThreadHandler().post(
+ () -> mSpyPowerNotificationWarnings.notifyHighTemperatureAlarm(!overheat,
+ !shouldBeepSound));
+ waitForIdleSync();
+ verify(mSpyPowerNotificationWarnings, times(1)).setOverheatAlarmDialogShowing(!overheat);
+ verify(mSpyPowerNotificationWarnings, times(1)).setAlarmShouldSound(!shouldBeepSound);
+ }
+
+ @Test
+ public void testSetAlarmShouldSound_Overheat_Twice_ShouldShowOverheatDialogAgain() {
+ final boolean overheat = true;
+ final boolean shouldBeepSound = true;
+ // First time overheat, show mAlarmDialog and alarm beep sound
+ mContext.getMainThreadHandler().post(
+ () -> mSpyPowerNotificationWarnings.notifyHighTemperatureAlarm(overheat,
+ shouldBeepSound));
+ waitForIdleSync();
+ verify(mSpyPowerNotificationWarnings, times(1)).setOverheatAlarmDialogShowing(overheat);
+ verify(mSpyPowerNotificationWarnings, times(1)).setAlarmShouldSound(shouldBeepSound);
+
+ // After disconnected cable or temperature drop, stop beep sound
+ mContext.getMainThreadHandler().post(
+ () -> mSpyPowerNotificationWarnings.notifyHighTemperatureAlarm(!overheat,
+ !shouldBeepSound));
+ waitForIdleSync();
+ verify(mSpyPowerNotificationWarnings, times(1)).setOverheatAlarmDialogShowing(!overheat);
+ verify(mSpyPowerNotificationWarnings, times(1)).setAlarmShouldSound(!shouldBeepSound);
+
+ // Overheat again, ensure the previous dialog do not auto-dismiss
+ mContext.getMainThreadHandler().post(
+ () -> mSpyPowerNotificationWarnings.notifyHighTemperatureAlarm(overheat,
+ shouldBeepSound));
+ waitForIdleSync();
+ verify(mSpyPowerNotificationWarnings, times(1)).setOverheatAlarmDialogShowing(overheat);
+ verify(mSpyPowerNotificationWarnings, times(1)).setAlarmShouldSound(shouldBeepSound);
+ }
+
+ @Test
+ public void testOverheatAlarmDialogShowing() {
+ final boolean overheat = true;
+ final boolean shouldBeepSound = false;
+ mContext.getMainThreadHandler().post(
+ () -> mSpyPowerNotificationWarnings.notifyHighTemperatureAlarm(overheat,
+ shouldBeepSound));
+ waitForIdleSync();
+ assertThat(mSpyPowerNotificationWarnings.mOverheatAlarmDialog).isNotNull();
+ }
+
+ @Test
+ public void testOverheatAlarmDialogShowingWithBeepSound() {
+ final boolean overheat = true;
+ final boolean shouldBeepSound = true;
+ mContext.getMainThreadHandler().post(
+ () -> mSpyPowerNotificationWarnings.notifyHighTemperatureAlarm(overheat,
+ shouldBeepSound));
+ waitForIdleSync();
+ assertThat(mSpyPowerNotificationWarnings.mOverheatAlarmDialog).isNotNull();
+ }
+
+ @Test
+ public void testOverheatAlarmDialogNotShowing() {
+ final boolean overheat = false;
+ final boolean shouldBeepSound = false;
+ mContext.getMainThreadHandler().post(
+ () -> mSpyPowerNotificationWarnings.notifyHighTemperatureAlarm(overheat,
+ shouldBeepSound));
+ waitForIdleSync();
+ assertThat(mSpyPowerNotificationWarnings.mOverheatAlarmDialog).isNull();
+ }
+
+ @Test
+ public void testOverheatAlarmDialogNotShowingWithBeepSound() {
+ final boolean overheat = false;
+ final boolean shouldBeepSound = true;
+ mContext.getMainThreadHandler().post(
+ () -> mSpyPowerNotificationWarnings.notifyHighTemperatureAlarm(overheat,
+ shouldBeepSound));
+ waitForIdleSync();
+ assertThat(mSpyPowerNotificationWarnings.mOverheatAlarmDialog).isNull();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
index a9d49f91e44ea..054bdee9fd97d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -17,6 +17,7 @@ package com.android.systemui.power;
import static android.os.HardwarePropertiesManager.DEVICE_TEMPERATURE_SKIN;
import static android.os.HardwarePropertiesManager.TEMPERATURE_CURRENT;
import static android.os.HardwarePropertiesManager.TEMPERATURE_SHUTDOWN;
+import static android.os.HardwarePropertiesManager.TEMPERATURE_THROTTLING;
import static android.provider.Settings.Global.SHOW_TEMPERATURE_WARNING;
import static junit.framework.Assert.assertFalse;
@@ -69,6 +70,7 @@ public class PowerUITest extends SysuiTestCase {
private static final long ABOVE_CHARGE_CYCLE_THRESHOLD = Duration.ofHours(8).toMillis();
private static final int OLD_BATTERY_LEVEL_NINE = 9;
private static final int OLD_BATTERY_LEVEL_10 = 10;
+ private static final int DEFAULT_OVERHEAT_ALARM_THRESHOLD = 58;
private HardwarePropertiesManager mHardProps;
private WarningsUI mMockWarnings;
private PowerUI mPowerUI;
@@ -86,6 +88,7 @@ public class PowerUITest extends SysuiTestCase {
mContext.addMockSystemService(Context.HARDWARE_PROPERTIES_SERVICE, mHardProps);
mContext.addMockSystemService(Context.POWER_SERVICE, mPowerManager);
+ setUnderThreshold();
createPowerUi();
}
@@ -153,10 +156,94 @@ public class PowerUITest extends SysuiTestCase {
verify(mMockWarnings, never()).showHighTemperatureWarning();
setCurrentTemp(56); // Above threshold.
- mPowerUI.updateTemperatureWarning();
+ mPowerUI.updateTemperature();
verify(mMockWarnings).showHighTemperatureWarning();
}
+ @Test
+ public void testNoConfig_noAlarms() {
+ setOverThreshold();
+ final Boolean overheat = false;
+ final Boolean shouldBeepSound = false;
+ TestableResources resources = mContext.getOrCreateTestableResources();
+ resources.addOverride(R.integer.config_showTemperatureWarning, 0);
+ resources.addOverride(R.integer.config_alarmTemperature, 55);
+ resources.addOverride(R.bool.config_alarmTemperatureBeepSound, shouldBeepSound);
+
+ mPowerUI.start();
+ verify(mMockWarnings, never()).notifyHighTemperatureAlarm(overheat, shouldBeepSound);
+ }
+
+ @Test
+ public void testConfig_noAlarms() {
+ setUnderThreshold();
+ final Boolean overheat = false;
+ final Boolean shouldBeepSound = false;
+ TestableResources resources = mContext.getOrCreateTestableResources();
+ resources.addOverride(R.integer.config_showTemperatureAlarm, 1);
+ resources.addOverride(R.integer.config_alarmTemperature, 58);
+ resources.addOverride(R.bool.config_alarmTemperatureBeepSound, shouldBeepSound);
+
+ mPowerUI.start();
+ verify(mMockWarnings, never()).notifyHighTemperatureAlarm(overheat, shouldBeepSound);
+ }
+
+ @Test
+ public void testConfig_alarms() {
+ setOverThreshold();
+ final Boolean overheat = true;
+ final Boolean shouldBeepSound = false;
+ TestableResources resources = mContext.getOrCreateTestableResources();
+ resources.addOverride(R.integer.config_showTemperatureAlarm, 1);
+ resources.addOverride(R.integer.config_alarmTemperature, 58);
+ resources.addOverride(R.bool.config_alarmTemperatureBeepSound, shouldBeepSound);
+
+ mPowerUI.start();
+ verify(mMockWarnings).notifyHighTemperatureAlarm(overheat, shouldBeepSound);
+ }
+
+ @Test
+ public void testConfig_alarmsWithBeepSound() {
+ setOverThreshold();
+ final Boolean overheat = true;
+ final Boolean shouldBeepSound = true;
+ TestableResources resources = mContext.getOrCreateTestableResources();
+ resources.addOverride(R.integer.config_showTemperatureAlarm, 1);
+ resources.addOverride(R.integer.config_alarmTemperature, 58);
+ resources.addOverride(R.bool.config_alarmTemperatureBeepSound, shouldBeepSound);
+
+ mPowerUI.start();
+ verify(mMockWarnings).notifyHighTemperatureAlarm(overheat, shouldBeepSound);
+ }
+
+ @Test
+ public void testHardPropsThrottlingThreshold_alarms() {
+ setThrottlingThreshold(DEFAULT_OVERHEAT_ALARM_THRESHOLD);
+ setOverThreshold();
+ final Boolean overheat = true;
+ final Boolean shouldBeepSound = false;
+ TestableResources resources = mContext.getOrCreateTestableResources();
+ resources.addOverride(R.integer.config_showTemperatureAlarm, 1);
+ resources.addOverride(R.bool.config_alarmTemperatureBeepSound, shouldBeepSound);
+
+ mPowerUI.start();
+ verify(mMockWarnings).notifyHighTemperatureAlarm(overheat, shouldBeepSound);
+ }
+
+ @Test
+ public void testHardPropsThrottlingThreshold_noAlarms() {
+ setThrottlingThreshold(DEFAULT_OVERHEAT_ALARM_THRESHOLD);
+ setUnderThreshold();
+ final Boolean overheat = false;
+ final Boolean shouldBeepSound = false;
+ TestableResources resources = mContext.getOrCreateTestableResources();
+ resources.addOverride(R.integer.config_showTemperatureAlarm, 1);
+ resources.addOverride(R.bool.config_alarmTemperatureBeepSound, shouldBeepSound);
+
+ mPowerUI.start();
+ verify(mMockWarnings, never()).notifyHighTemperatureAlarm(overheat, shouldBeepSound);
+ }
+
@Test
public void testShouldShowLowBatteryWarning_showHybridOnly_overrideThresholdHigh_returnsNoShow() {
when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
@@ -495,7 +582,12 @@ public class PowerUITest extends SysuiTestCase {
private void setCurrentTemp(float temp) {
when(mHardProps.getDeviceTemperatures(DEVICE_TEMPERATURE_SKIN, TEMPERATURE_CURRENT))
- .thenReturn(new float[] { temp });
+ .thenReturn(new float[] { temp, temp });
+ }
+
+ private void setThrottlingThreshold(float temp) {
+ when(mHardProps.getDeviceTemperatures(DEVICE_TEMPERATURE_SKIN, TEMPERATURE_THROTTLING))
+ .thenReturn(new float[] { temp, temp });
}
private void setOverThreshold() {
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index d79d833f22336..4ebf9aa005d89 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -6117,6 +6117,11 @@ message MetricsEvent {
// OS: P
FIELD_AUTOFILL_SESSION_ID = 1456;
+ // FIELD: Device USB overheat alarm trigger.
+ // CATEGORY: GLOBAL_SYSTEM_UI
+ // OS: P
+ POWER_OVERHEAT_ALARM = 1457;
+
// NOTIFICATION_SINCE_INTERRUPTION_MILLIS added to P
// NOTIFICATION_INTERRUPTION added to P