Merge "DO NOT MERGE Implement USB High Temperature warning dialog(1/N)" into pi-dev
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 0000000..d78272f
--- /dev/null
+++ b/packages/SystemUI/res/layout/overheat_dialog_content.xml
@@ -0,0 +1,39 @@
+<!--
+     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.
+-->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/contentPanel"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="48dp">
+
+    <ScrollView
+        android:id="@+id/scrollView"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingTop="@dimen/alarm_dialog_panel_padding"
+        android:paddingEnd="?android:attr/dialogPreferredPadding"
+        android:paddingStart="?android:attr/dialogPreferredPadding"
+        android:clipToPadding="false">
+
+        <TextView
+            android:id="@android:id/message"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/high_temp_alarm_notify_message"
+            style="@android:style/TextAppearance.Material.Subhead"/>
+    </ScrollView>
+</FrameLayout>
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 0000000..65a512a
--- /dev/null
+++ b/packages/SystemUI/res/layout/overheat_dialog_title.xml
@@ -0,0 +1,46 @@
+<!--
+     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.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/title_template"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal"
+    android:gravity="center_vertical|start"
+    android:paddingStart="?android:attr/dialogPreferredPadding"
+    android:paddingEnd="?android:attr/dialogPreferredPadding"
+    android:paddingTop="@dimen/alarm_dialog_panel_padding">
+
+    <ImageView
+        android:id="@android:id/icon"
+        android:layout_width="32dip"
+        android:layout_height="32dip"
+        android:layout_marginEnd="8dip"
+        android:scaleType="fitCenter"
+        android:tint="?android:attr/colorError"
+        android:src="?android:attr/alertDialogIcon"/>
+
+    <com.android.internal.widget.DialogTitle
+        android:id="@+id/alertTitle"
+        android:singleLine="true"
+        android:ellipsize="end"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textAlignment="viewStart"
+        android:text="@string/high_temp_alarm_title"
+        android:importantForAccessibility="noHideDescendants"
+        style="?android:attr/windowTitleStyle"/>
+</LinearLayout>
diff --git a/packages/SystemUI/res/raw/overheat_alarm.ogg b/packages/SystemUI/res/raw/overheat_alarm.ogg
new file mode 100644
index 0000000..5624f42
--- /dev/null
+++ b/packages/SystemUI/res/raw/overheat_alarm.ogg
Binary files differ
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index a68ba9b..317fe31 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -368,17 +368,31 @@
 
     <bool name="quick_settings_show_full_alarm">false</bool>
 
+    <!-- Whether or not beep sound should be when overheat -->
+    <bool name="config_alarmTemperatureBeepSound">false</bool>
+
     <!-- Whether to show a warning notification when the device reaches a certain temperature. -->
     <integer name="config_showTemperatureWarning">0</integer>
 
+    <!-- Whether to show a alarm dialog when device of usb cable reaches a certain temperature. -->
+    <integer name="config_showTemperatureAlarm">0</integer>
+
     <!-- Temp at which to show a warning notification if config_showTemperatureWarning is true.
          If < 0, uses the skin temperature sensor shutdown value from
          HardwarePropertiesManager#getDeviceTemperatures - config_warningTemperatureTolerance. -->
     <integer name="config_warningTemperature">-1</integer>
 
+    <!-- Temp at which to show a alarm dialog if config_showTemperatureAlarm is true.
+         If < 0, uses the skin temperature sensor shutdown value of index[1] from
+         HardwarePropertiesManager#getDeviceTemperatures -->
+    <integer name="config_alarmTemperature">60</integer>
+
     <!-- Fudge factor for how much below the shutdown temp to show the warning. -->
     <integer name="config_warningTemperatureTolerance">2</integer>
 
+    <!-- Fudge factor for how much below the overheat temp to dismiss alarm. -->
+    <integer name="config_alarmTemperatureTolerance">5</integer>
+
     <!-- Accessibility actions -->
     <item type="id" name="action_split_task_to_left" />
     <item type="id" name="action_split_task_to_right" />
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 79e1fae..f5f198d 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1023,4 +1023,11 @@
     <!-- How much we expand the touchable region of the status bar below the notch to catch touches
          that just start below the notch. -->
     <dimen name="display_cutout_touchable_region_size">12dp</dimen>
+
+    <!-- Padding for overheat alarm dialog message of content -->
+    <dimen name="alarm_dialog_panel_padding">24dp</dimen>
+
+    <!-- Padding for overheat alarm dialog message of content -->
+    <dimen name="alarm_dialog_message_space">28dp</dimen>
+
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 18f378e..114fbe4 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2067,6 +2067,14 @@
     <string name="high_temp_notif_message">Some features limited while phone cools down</string>
     <!-- Text body for dialog alerting user that their phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=300] -->
     <string name="high_temp_dialog_message">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.</string>
+    <!-- Title for alarm dialog alerting user the usb adapter has reached a certain temperature that should disconnect charging cable immediately. [CHAR LIMIT=30] -->
+    <string name="high_temp_alarm_title">Unplug charger</string>
+    <!-- Text body for dialog alerting user the usb adapter has reached a certain temperature that should disconnect charging cable immediately. [CHAR LIMIT=300] -->
+    <string name="high_temp_alarm_notify_message">There\u2019s an issue charging this device. Unplug the power adapter and take care as the cable may be warm.</string>
+    <!-- Text link for user to see more detail about overheat alarm. [CHAR LIMIT=300] -->
+    <string name="high_temp_alarm_help_care_steps">See care steps</string>
+    <!-- Help link of context for usb overheat web page -->
+    <string name="high_temp_alarm_help_url" translatable="false">help_uri_usb_warm</string>
 
     <!-- SysUI Tuner: Button to select lock screen shortcut [CHAR LIMIT=60] -->
     <string name="lockscreen_shortcut_left">Left shortcut</string>
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 0000000..db15909
--- /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 0000000..1ecec51
--- /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 288f058..c32a443 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.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 @@
     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 @@
     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 @@
         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 @@
             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 @@
             } 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 0b9067e..80ebb6e 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.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 @@
     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 @@
     // 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 @@
         // to the temperature being too high.
         showThermalShutdownDialog();
 
-        initTemperatureWarning();
+        initTemperature();
     }
 
     @Override
@@ -135,7 +145,7 @@
 
         // 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 @@
             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 @@
                 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 @@
         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 @@
                     || 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 @@
         mHandler.removeCallbacks(mUpdateTempCallback);
 
         // We have passed all of the checks, start checking the temp
-        updateTemperatureWarning();
+        updateTemperature();
     }
 
     private void showThermalShutdownDialog() {
@@ -426,38 +474,110 @@
         }
     }
 
+    /**
+     * 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 @@
      * 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 @@
                 }
             }
 
-            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 @@
                 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 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 @@
         @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 ca55e1f..96e8310 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 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 @@
             "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 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 @@
                 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 1be8322..c40d72d 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -49,6 +49,7 @@
     <uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW" />
     <uses-permission android:name="android.permission.NETWORK_SETTINGS" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.VIBRATE" />
 
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner" />
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 0000000..651d2b7
--- /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 0000000..04d3163
--- /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 bf6cc53..d37c285 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.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.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 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 @@
         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 a9d49f9..054bdee 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 @@
 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 @@
     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 @@
         mContext.addMockSystemService(Context.HARDWARE_PROPERTIES_SERVICE, mHardProps);
         mContext.addMockSystemService(Context.POWER_SERVICE, mPowerManager);
 
+        setUnderThreshold();
         createPowerUi();
     }
 
@@ -153,11 +156,95 @@
         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);
         when(mEnhancedEstimates.getLowWarningThreshold())
@@ -495,7 +582,12 @@
 
     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 d79d833..4ebf9aa 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -6117,6 +6117,11 @@
     // 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