DO NOT MERGE Implement USB High Temperature warning dialog(1/N)

When device USB adapter got electric short issue and overheating.
Popup overheat alarm dialog, with beep sound and vibration(optional)
to notify user unplug USB cable, this dialog will keep showing at
foreground until user click OK button or see care steps.

Test: manual adjust threshold to 35 degrees and trigger alarm
Test: atest SystemUITests
Test: atest PowerUITest
Test: atest PowerNotificationWarningsTest
Test: atest OverheatAlarmControllerTest
Test: atest OverheatAlarmDialogTest

Change-Id: I35e4269b2f3f2108043e2756d0f5a53f54d86e7d
Fix: b/117138158
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