Merge "Added dialog to notify user of thermal shutdown" into oc-dev
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index b715780..037cccf 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -55,6 +55,7 @@
     void rebootSafeMode(boolean confirm, boolean wait);
     void shutdown(boolean confirm, String reason, boolean wait);
     void crash(String message);
+    int getLastShutdownReason();
 
     void setStayOnSetting(int val);
     void boostScreenBrightness(long time);
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index a713eef..7d1369f 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -16,10 +16,13 @@
 
 package android.os;
 
+import android.annotation.IntDef;
 import android.annotation.SdkConstant;
 import android.annotation.SystemApi;
 import android.content.Context;
 import android.util.Log;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
  * This class gives you control of the power state of the device.
@@ -432,6 +435,49 @@
      */
     public static final String SHUTDOWN_USER_REQUESTED = "userrequested";
 
+    /**
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            SHUTDOWN_REASON_UNKNOWN,
+            SHUTDOWN_REASON_SHUTDOWN,
+            SHUTDOWN_REASON_REBOOT,
+            SHUTDOWN_REASON_USER_REQUESTED,
+            SHUTDOWN_REASON_THERMAL_SHUTDOWN
+    })
+    public @interface ShutdownReason {}
+
+    /**
+     * constant for shutdown reason being unknown.
+     * @hide
+     */
+    public static final int SHUTDOWN_REASON_UNKNOWN = 0;
+
+    /**
+     * constant for shutdown reason being normal shutdown.
+     * @hide
+     */
+    public static final int SHUTDOWN_REASON_SHUTDOWN = 1;
+
+    /**
+     * constant for shutdown reason being reboot.
+     * @hide
+     */
+    public static final int SHUTDOWN_REASON_REBOOT = 2;
+
+    /**
+     * constant for shutdown reason being user requested.
+     * @hide
+     */
+    public static final int SHUTDOWN_REASON_USER_REQUESTED = 3;
+
+    /**
+     * constant for shutdown reason being overheating.
+     * @hide
+     */
+    public static final int SHUTDOWN_REASON_THERMAL_SHUTDOWN = 4;
+
     final Context mContext;
     final IPowerManager mService;
     final Handler mHandler;
@@ -1085,6 +1131,22 @@
     }
 
     /**
+     * Returns the reason the phone was last shutdown. Calling app must have the
+     * {@link android.Manifest.permission#DEVICE_POWER} permission to request this information.
+     * @return Reason for shutdown as an int, {@link #SHUTDOWN_REASON_UNKNOWN} if the file could
+     * not be accessed.
+     * @hide
+     */
+    @ShutdownReason
+    public int getLastShutdownReason() {
+        try {
+            return mService.getLastShutdownReason();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Intent that is broadcast when the state of {@link #isPowerSaveMode()} changes.
      * This broadcast is only sent to registered receivers.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index daf0622..6b3daa3 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -36,6 +36,7 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.support.annotation.VisibleForTesting;
 import android.util.Slog;
 
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
@@ -71,6 +72,10 @@
     private static final String ACTION_DISMISSED_WARNING = "PNW.dismissedWarning";
     private static final String ACTION_CLICKED_TEMP_WARNING = "PNW.clickedTempWarning";
     private static final String ACTION_DISMISSED_TEMP_WARNING = "PNW.dismissedTempWarning";
+    private static final String ACTION_CLICKED_THERMAL_SHUTDOWN_WARNING =
+            "PNW.clickedThermalShutdownWarning";
+    private static final String ACTION_DISMISSED_THERMAL_SHUTDOWN_WARNING =
+            "PNW.dismissedThermalShutdownWarning";
 
     private static final AudioAttributes AUDIO_ATTRIBUTES = new AudioAttributes.Builder()
             .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
@@ -95,8 +100,9 @@
     private boolean mPlaySound;
     private boolean mInvalidCharger;
     private SystemUIDialog mSaverConfirmation;
-    private boolean mTempWarning;
+    private boolean mHighTempWarning;
     private SystemUIDialog mHighTempDialog;
+    private SystemUIDialog mThermalShutdownDialog;
 
     public PowerNotificationWarnings(Context context, NotificationManager notificationManager,
             StatusBar statusBar) {
@@ -113,8 +119,10 @@
         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("mTempWarning="); pw.println(mTempWarning);
+        pw.print("mHighTempWarning="); pw.println(mHighTempWarning);
         pw.print("mHighTempDialog="); pw.println(mHighTempDialog != null ? "not null" : null);
+        pw.print("mThermalShutdownDialog=");
+        pw.println(mThermalShutdownDialog != null ? "not null" : null);
     }
 
     @Override
@@ -212,29 +220,29 @@
     }
 
     @Override
-    public void dismissTemperatureWarning() {
-        if (!mTempWarning) {
+    public void dismissHighTemperatureWarning() {
+        if (!mHighTempWarning) {
             return;
         }
-        mTempWarning = false;
-        dismissTemperatureWarningInternal();
+        mHighTempWarning = false;
+        dismissHighTemperatureWarningInternal();
     }
 
     /**
-     * Internal only version of {@link #dismissTemperatureWarning()} that simply dismisses
+     * Internal only version of {@link #dismissHighTemperatureWarning()} that simply dismisses
      * the notification. As such, the notification will not show again until
-     * {@link #dismissTemperatureWarning()} is called.
+     * {@link #dismissHighTemperatureWarning()} is called.
      */
-    private void dismissTemperatureWarningInternal() {
+    private void dismissHighTemperatureWarningInternal() {
         mNoMan.cancelAsUser(TAG_TEMPERATURE, SystemMessage.NOTE_HIGH_TEMP, UserHandle.ALL);
     }
 
     @Override
-    public void showTemperatureWarning() {
-        if (mTempWarning) {
+    public void showHighTemperatureWarning() {
+        if (mHighTempWarning) {
             return;
         }
-        mTempWarning = true;
+        mHighTempWarning = true;
         final Notification.Builder nb =
                 new Notification.Builder(mContext, NotificationChannels.ALERTS)
                         .setSmallIcon(R.drawable.ic_device_thermostat_24)
@@ -249,10 +257,9 @@
         SystemUI.overrideNotificationAppName(mContext, nb);
         final Notification n = nb.build();
         mNoMan.notifyAsUser(TAG_TEMPERATURE, SystemMessage.NOTE_HIGH_TEMP, n, UserHandle.ALL);
-
     }
 
-    private void showTemperatureDialog() {
+    private void showHighTemperatureDialog() {
         if (mHighTempDialog != null) return;
         final SystemUIDialog d = new SystemUIDialog(mContext);
         d.setIconAttribute(android.R.attr.alertDialogIcon);
@@ -265,6 +272,44 @@
         mHighTempDialog = d;
     }
 
+    @VisibleForTesting
+    void dismissThermalShutdownWarning() {
+        mNoMan.cancelAsUser(TAG_TEMPERATURE, SystemMessage.NOTE_THERMAL_SHUTDOWN, UserHandle.ALL);
+    }
+
+    private void showThermalShutdownDialog() {
+        if (mThermalShutdownDialog != null) return;
+        final SystemUIDialog d = new SystemUIDialog(mContext);
+        d.setIconAttribute(android.R.attr.alertDialogIcon);
+        d.setTitle(R.string.thermal_shutdown_title);
+        d.setMessage(R.string.thermal_shutdown_dialog_message);
+        d.setPositiveButton(com.android.internal.R.string.ok, null);
+        d.setShowForAllUsers(true);
+        d.setOnDismissListener(dialog -> mThermalShutdownDialog = null);
+        d.show();
+        mThermalShutdownDialog = d;
+    }
+
+    @Override
+    public void showThermalShutdownWarning() {
+        final Notification.Builder nb =
+                new Notification.Builder(mContext, NotificationChannels.ALERTS)
+                        .setSmallIcon(R.drawable.ic_device_thermostat_24)
+                        .setWhen(0)
+                        .setShowWhen(false)
+                        .setContentTitle(mContext.getString(R.string.thermal_shutdown_title))
+                        .setContentText(mContext.getString(R.string.thermal_shutdown_message))
+                        .setVisibility(Notification.VISIBILITY_PUBLIC)
+                        .setContentIntent(pendingBroadcast(ACTION_CLICKED_THERMAL_SHUTDOWN_WARNING))
+                        .setDeleteIntent(
+                                pendingBroadcast(ACTION_DISMISSED_THERMAL_SHUTDOWN_WARNING))
+                        .setColor(Utils.getColorAttr(mContext, android.R.attr.colorError));
+        SystemUI.overrideNotificationAppName(mContext, nb);
+        final Notification n = nb.build();
+        mNoMan.notifyAsUser(
+                TAG_TEMPERATURE, SystemMessage.NOTE_THERMAL_SHUTDOWN, n, UserHandle.ALL);
+    }
+
     @Override
     public void updateLowBatteryWarning() {
         updateNotification();
@@ -380,6 +425,8 @@
             filter.addAction(ACTION_DISMISSED_WARNING);
             filter.addAction(ACTION_CLICKED_TEMP_WARNING);
             filter.addAction(ACTION_DISMISSED_TEMP_WARNING);
+            filter.addAction(ACTION_CLICKED_THERMAL_SHUTDOWN_WARNING);
+            filter.addAction(ACTION_DISMISSED_THERMAL_SHUTDOWN_WARNING);
             mContext.registerReceiverAsUser(this, UserHandle.ALL, filter,
                     android.Manifest.permission.STATUS_BAR_SERVICE, mHandler);
         }
@@ -397,10 +444,15 @@
             } else if (action.equals(ACTION_DISMISSED_WARNING)) {
                 dismissLowBatteryWarning();
             } else if (ACTION_CLICKED_TEMP_WARNING.equals(action)) {
-                dismissTemperatureWarningInternal();
-                showTemperatureDialog();
+                dismissHighTemperatureWarningInternal();
+                showHighTemperatureDialog();
             } else if (ACTION_DISMISSED_TEMP_WARNING.equals(action)) {
-                dismissTemperatureWarningInternal();
+                dismissHighTemperatureWarningInternal();
+            } else if (ACTION_CLICKED_THERMAL_SHUTDOWN_WARNING.equals(action)) {
+                dismissThermalShutdownWarning();
+                showThermalShutdownDialog();
+            } else if (ACTION_DISMISSED_THERMAL_SHUTDOWN_WARNING.equals(action)) {
+                dismissThermalShutdownWarning();
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 471c3ae..a642077 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -31,6 +31,7 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.text.TextUtils;
 import android.text.format.DateUtils;
 import android.util.Log;
 import android.util.Slog;
@@ -93,6 +94,10 @@
         updateBatteryWarningLevels();
         mReceiver.init();
 
+        // Check to see if we need to let the user know that the phone previously shut down due
+        // to the temperature being too high.
+        showThermalShutdownDialog();
+
         initTemperatureWarning();
     }
 
@@ -256,6 +261,13 @@
         updateTemperatureWarning();
     }
 
+    private void showThermalShutdownDialog() {
+        if (mPowerManager.getLastShutdownReason()
+                == PowerManager.SHUTDOWN_REASON_THERMAL_SHUTDOWN) {
+            mWarnings.showThermalShutdownWarning();
+        }
+    }
+
     private void updateTemperatureWarning() {
         float[] temps = mHardwarePropertiesManager.getDeviceTemperatures(
                 HardwarePropertiesManager.DEVICE_TEMPERATURE_SKIN,
@@ -268,9 +280,9 @@
             if (statusBar != null && !statusBar.isDeviceInVrMode()
                     && temp >= mThresholdTemp) {
                 logAtTemperatureThreshold(temp);
-                mWarnings.showTemperatureWarning();
+                mWarnings.showHighTemperatureWarning();
             } else {
-                mWarnings.dismissTemperatureWarning();
+                mWarnings.dismissHighTemperatureWarning();
             }
         }
 
@@ -369,8 +381,9 @@
         void showInvalidChargerWarning();
         void updateLowBatteryWarning();
         boolean isInvalidChargerWarningShowing();
-        void dismissTemperatureWarning();
-        void showTemperatureWarning();
+        void dismissHighTemperatureWarning();
+        void showHighTemperatureWarning();
+        void showThermalShutdownWarning();
         void dump(PrintWriter pw);
         void userSwitched();
     }
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 34cfa7b..eb59a34 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
@@ -24,7 +24,6 @@
 
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
@@ -129,17 +128,32 @@
     }
 
     @Test
-    public void testShowTemperatureWarning_NotifyAsUser() {
-        mPowerNotificationWarnings.showTemperatureWarning();
+    public void testShowHighTemperatureWarning_NotifyAsUser() {
+        mPowerNotificationWarnings.showHighTemperatureWarning();
         verify(mMockNotificationManager, times(1))
                 .notifyAsUser(anyString(), eq(SystemMessage.NOTE_HIGH_TEMP), any(), any());
     }
 
     @Test
-    public void testDismissTemperatureWarning_CancelAsUser() {
-        mPowerNotificationWarnings.showTemperatureWarning();
-        mPowerNotificationWarnings.dismissTemperatureWarning();
+    public void testDismissHighTemperatureWarning_CancelAsUser() {
+        mPowerNotificationWarnings.showHighTemperatureWarning();
+        mPowerNotificationWarnings.dismissHighTemperatureWarning();
         verify(mMockNotificationManager, times(1)).cancelAsUser(anyString(),
                 eq(SystemMessage.NOTE_HIGH_TEMP), any());
     }
+
+    @Test
+    public void testShowThermalShutdownWarning_NotifyAsUser() {
+        mPowerNotificationWarnings.showThermalShutdownWarning();
+        verify(mMockNotificationManager, times(1))
+                .notifyAsUser(anyString(), eq(SystemMessage.NOTE_THERMAL_SHUTDOWN), any(), any());
+    }
+
+    @Test
+    public void testDismissThermalShutdownWarning_CancelAsUser() {
+        mPowerNotificationWarnings.showThermalShutdownWarning();
+        mPowerNotificationWarnings.dismissThermalShutdownWarning();
+        verify(mMockNotificationManager, times(1)).cancelAsUser(anyString(),
+                eq(SystemMessage.NOTE_THERMAL_SHUTDOWN), any());
+    }
 }
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 61ed72d..8c3d80f 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -65,6 +65,7 @@
 import android.service.vr.IVrStateCallbacks;
 import android.util.EventLog;
 import android.util.KeyValueListParser;
+import android.util.Log;
 import android.util.PrintWriterPrinter;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -91,6 +92,10 @@
 import com.android.server.lights.Light;
 import com.android.server.lights.LightsManager;
 import com.android.server.power.BatterySaverPolicy.ServiceType;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
 import libcore.util.Objects;
 
 import java.io.FileDescriptor;
@@ -191,6 +196,12 @@
     // System property indicating that the screen should remain off until an explicit user action
     private static final String SYSTEM_PROPERTY_QUIESCENT = "ro.boot.quiescent";
 
+    // Possible reasons for shutting down for use in data/misc/reboot/last_shutdown_reason
+    private static final String REASON_SHUTDOWN = "shutdown";
+    private static final String REASON_REBOOT = "reboot";
+    private static final String REASON_USERREQUESTED = "userrequested";
+    private static final String REASON_THERMAL_SHUTDOWN = "thermal-shutdown";
+
     private static final String TRACE_SCREEN_ON = "Screen turning on";
 
     /** If turning screen on takes more than this long, we show a warning on logcat. */
@@ -204,6 +215,9 @@
     private static final int HALT_MODE_REBOOT = 1;
     private static final int HALT_MODE_REBOOT_SAFE_MODE = 2;
 
+    // File location for last reboot reason
+    private static final String LAST_REBOOT_LOCATION = "/data/misc/reboot/last_reboot_reason";
+
     private final Context mContext;
     private final ServiceThread mHandlerThread;
     private final PowerManagerHandler mHandler;
@@ -4340,6 +4354,25 @@
         }
 
         /**
+         * Gets the reason for the last time the phone had to reboot.
+         *
+         * @return The reason the phone last shut down as an int or
+         * {@link PowerManager.SHUTDOWN_REASON_UNKNOWN} if the file could not be opened.
+         */
+        @Override // Binder call
+        public int getLastShutdownReason() {
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.DEVICE_POWER, null);
+
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                return getLastShutdownReasonInternal(new File(LAST_REBOOT_LOCATION));
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        /**
          * Reboots the device.
          *
          * @param confirm If true, shows a reboot confirmation dialog.
@@ -4566,6 +4599,28 @@
         }
     }
 
+    @VisibleForTesting
+    int getLastShutdownReasonInternal(File lastRebootReason) {
+        String line = "";
+        try (BufferedReader bufferedReader = new BufferedReader(new FileReader(lastRebootReason))){
+            line = bufferedReader.readLine();
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to read last_reboot_reason file", e);
+        }
+        switch (line) {
+            case REASON_SHUTDOWN:
+                return PowerManager.SHUTDOWN_REASON_SHUTDOWN;
+            case REASON_REBOOT:
+                return PowerManager.SHUTDOWN_REASON_REBOOT;
+            case REASON_USERREQUESTED:
+                return PowerManager.SHUTDOWN_REASON_USER_REQUESTED;
+            case REASON_THERMAL_SHUTDOWN:
+                return PowerManager.SHUTDOWN_REASON_THERMAL_SHUTDOWN;
+            default:
+                return PowerManager.SHUTDOWN_REASON_UNKNOWN;
+        }
+    }
+
     private final class LocalService extends PowerManagerInternal {
         @Override
         public void setScreenBrightnessOverrideFromWindowManager(int screenBrightness) {
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index 967b0a4..d12c07a 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -16,10 +16,19 @@
 
 package com.android.server.power;
 
+import android.content.Context;
 import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+import android.os.PowerManager;
 import android.os.PowerSaveState;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.text.TextUtils;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import org.junit.Rule;
+import org.junit.rules.TemporaryFolder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -35,11 +44,16 @@
     private static final float PRECISION = 0.001f;
     private static final float BRIGHTNESS_FACTOR = 0.7f;
     private static final boolean BATTERY_SAVER_ENABLED = true;
+    private static final String LAST_REBOOT_REASON = "last_reboot_reason";
 
     private @Mock BatterySaverPolicy mBatterySaverPolicy;
     private PowerManagerService mService;
     private PowerSaveState mPowerSaveState;
     private DisplayPowerRequest mDisplayPowerRequest;
+    private File mTempReason;
+
+    @Rule
+    public TemporaryFolder temporaryFolder = new TemporaryFolder();
 
     public void setUp() throws Exception {
         super.setUp();
@@ -54,6 +68,8 @@
                 .thenReturn(mPowerSaveState);
         mDisplayPowerRequest = new DisplayPowerRequest();
         mService = new PowerManagerService(getContext(), mBatterySaverPolicy);
+        temporaryFolder.create();
+        mTempReason = temporaryFolder.newFile(LAST_REBOOT_REASON);
     }
 
     @SmallTest
@@ -63,4 +79,17 @@
         assertThat(mDisplayPowerRequest.screenLowPowerBrightnessFactor)
                 .isWithin(PRECISION).of(BRIGHTNESS_FACTOR);
     }
+
+    @SmallTest
+    public void testGetLastShutdownReasonInternal() {
+        try {
+            FileWriter writer = new FileWriter(mTempReason);
+            writer.append("thermal-shutdown\n");
+            writer.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        int reason = mService.getLastShutdownReasonInternal(mTempReason);
+        assertThat(reason).isEqualTo(PowerManager.SHUTDOWN_REASON_THERMAL_SHUTDOWN);
+    }
 }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java
index 339019d..ed428ec 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java
@@ -18,6 +18,7 @@
 
 import android.os.IBinder;
 import android.os.IPowerManager;
+import android.os.PowerManager;
 import android.os.PowerSaveState;
 import android.os.RemoteException;
 import android.os.WorkSource;
@@ -170,4 +171,9 @@
     public boolean isScreenBrightnessBoosted() throws RemoteException {
         return false;
     }
+
+    @Override
+    public int getLastShutdownReason() {
+        return PowerManager.SHUTDOWN_REASON_UNKNOWN;
+    }
 }