Merge "Add notifications for the concurrent display mode"
diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml
index 9e735d0..c3366cf 100644
--- a/core/res/res/values/arrays.xml
+++ b/core/res/res/values/arrays.xml
@@ -207,4 +207,25 @@
          be set by OEMs for devices which use eUICCs. -->
     <integer-array name="non_removable_euicc_slots"></integer-array>
 
+    <!-- Device state identifiers and strings for system notifications. The string arrays must have
+         the same length and order as the identifier array. -->
+    <integer-array name="device_state_notification_state_identifiers">
+        <!-- TODO(b/267231269) change to concurrent display identifier when ready -->
+        <item>-1</item>
+    </integer-array>
+    <string-array name="device_state_notification_names">
+        <item>@string/concurrent_display_notification_name</item>
+    </string-array>
+    <string-array name="device_state_notification_active_titles">
+        <item>@string/concurrent_display_notification_active_title</item>
+    </string-array>
+    <string-array name="device_state_notification_active_contents">
+        <item>@string/concurrent_display_notification_active_content</item>
+    </string-array>
+    <string-array name="device_state_notification_thermal_titles">
+        <item>@string/concurrent_display_notification_thermal_title</item>
+    </string-array>
+    <string-array name="device_state_notification_thermal_contents">
+        <item>@string/concurrent_display_notification_thermal_content</item>
+    </string-array>
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 0dd0a70..da44c2e 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -6241,4 +6241,17 @@
     <string name="mic_access_on_toast">Microphone is available</string>
     <!-- Toast message that is shown when the user mutes the microphone from the keyboard. [CHAR LIMIT=TOAST] -->
     <string name="mic_access_off_toast">Microphone is blocked</string>
+
+    <!-- Name of concurrent display notifications. [CHAR LIMIT=NONE] -->
+    <string name="concurrent_display_notification_name">Dual screen</string>
+    <!-- Title of concurrent display active notification. [CHAR LIMIT=NONE] -->
+    <string name="concurrent_display_notification_active_title">Dual screen is on</string>
+    <!-- Content of concurrent display active notification. [CHAR LIMIT=NONE] -->
+    <string name="concurrent_display_notification_active_content"><xliff:g id="app_name" example="Camera app">%1$s</xliff:g> is using both displays to show content</string>
+    <!-- Title of concurrent display thermal notification. [CHAR LIMIT=NONE] -->
+    <string name="concurrent_display_notification_thermal_title">Device is too warm</string>
+    <!-- Content of concurrent display thermal notification. [CHAR LIMIT=NONE] -->
+    <string name="concurrent_display_notification_thermal_content">Dual Screen is unavailable because your phone is getting too warm</string>
+    <!-- Text of device state notification turn off button. [CHAR LIMIT=NONE] -->
+    <string name="device_state_notification_turn_off_button">Turn off</string>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b29dfb7..d82e898 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4879,6 +4879,18 @@
   <java-symbol type="array" name="config_deviceStatesAvailableForAppRequests" />
   <java-symbol type="array" name="config_serviceStateLocationAllowedPackages" />
   <java-symbol type="integer" name="config_deviceStateRearDisplay"/>
+  <java-symbol type="array" name="device_state_notification_state_identifiers"/>
+  <java-symbol type="array" name="device_state_notification_names"/>
+  <java-symbol type="array" name="device_state_notification_active_titles"/>
+  <java-symbol type="array" name="device_state_notification_active_contents"/>
+  <java-symbol type="array" name="device_state_notification_thermal_titles"/>
+  <java-symbol type="array" name="device_state_notification_thermal_contents"/>
+  <java-symbol type="string" name="concurrent_display_notification_name"/>
+  <java-symbol type="string" name="concurrent_display_notification_active_title"/>
+  <java-symbol type="string" name="concurrent_display_notification_active_content"/>
+  <java-symbol type="string" name="concurrent_display_notification_thermal_title"/>
+  <java-symbol type="string" name="concurrent_display_notification_thermal_content"/>
+  <java-symbol type="string" name="device_state_notification_turn_off_button"/>
   <java-symbol type="bool" name="config_independentLockscreenLiveWallpaper"/>
 
   <!-- For app language picker -->
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 064cd2d..ba96861 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -25,6 +25,7 @@
 import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
 import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_BASE_STATE;
 import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_EMULATED_STATE;
+import static com.android.server.devicestate.OverrideRequestController.FLAG_THERMAL_CRITICAL;
 import static com.android.server.devicestate.OverrideRequestController.STATUS_ACTIVE;
 import static com.android.server.devicestate.OverrideRequestController.STATUS_CANCELED;
 import static com.android.server.devicestate.OverrideRequestController.STATUS_UNKNOWN;
@@ -183,6 +184,9 @@
     ActivityTaskManagerInternal.ScreenObserver mOverrideRequestScreenObserver =
             new OverrideRequestScreenObserver();
 
+    @NonNull
+    private final DeviceStateNotificationController mDeviceStateNotificationController;
+
     public DeviceStateManagerService(@NonNull Context context) {
         this(context, DeviceStatePolicy.Provider
                 .fromResources(context.getResources())
@@ -211,6 +215,13 @@
         mDeviceStatePolicy.getDeviceStateProvider().setListener(mDeviceStateProviderListener);
         mBinderService = new BinderService();
         mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
+        mDeviceStateNotificationController = new DeviceStateNotificationController(
+                context, mHandler,
+                () -> {
+                    synchronized (mLock) {
+                        mActiveOverride.ifPresent(mOverrideRequestController::cancelRequest);
+                    }
+                });
     }
 
     @Override
@@ -346,7 +357,8 @@
         return mBinderService;
     }
 
-    private void updateSupportedStates(DeviceState[] supportedDeviceStates) {
+    private void updateSupportedStates(DeviceState[] supportedDeviceStates,
+            @DeviceStateProvider.SupportedStatesUpdatedReason int reason) {
         synchronized (mLock) {
             final int[] oldStateIdentifiers = getSupportedStateIdentifiersLocked();
 
@@ -370,7 +382,7 @@
                 return;
             }
 
-            mOverrideRequestController.handleNewSupportedStates(newStateIdentifiers);
+            mOverrideRequestController.handleNewSupportedStates(newStateIdentifiers, reason);
             updatePendingStateLocked();
 
             setRearDisplayStateLocked();
@@ -596,7 +608,7 @@
 
     @GuardedBy("mLock")
     private void onOverrideRequestStatusChangedLocked(@NonNull OverrideRequest request,
-            @OverrideRequestController.RequestStatus int status) {
+            @OverrideRequestController.RequestStatus int status, int flags) {
         if (request.getRequestType() == OVERRIDE_REQUEST_TYPE_BASE_STATE) {
             switch (status) {
                 case STATUS_ACTIVE:
@@ -616,10 +628,19 @@
             switch (status) {
                 case STATUS_ACTIVE:
                     mActiveOverride = Optional.of(request);
+                    mDeviceStateNotificationController.showStateActiveNotificationIfNeeded(
+                            request.getRequestedState(), request.getUid());
                     break;
                 case STATUS_CANCELED:
                     if (mActiveOverride.isPresent() && mActiveOverride.get() == request) {
                         mActiveOverride = Optional.empty();
+                        mDeviceStateNotificationController.cancelNotification(
+                                request.getRequestedState());
+                        if ((flags & FLAG_THERMAL_CRITICAL) == FLAG_THERMAL_CRITICAL) {
+                            mDeviceStateNotificationController
+                                    .showThermalCriticalNotificationIfNeeded(
+                                            request.getRequestedState());
+                        }
                     }
                     break;
                 case STATUS_UNKNOWN: // same as default
@@ -700,7 +721,7 @@
         }
     }
 
-    private void requestStateInternal(int state, int flags, int callingPid,
+    private void requestStateInternal(int state, int flags, int callingPid, int callingUid,
             @NonNull IBinder token, boolean hasControlDeviceStatePermission) {
         synchronized (mLock) {
             final ProcessRecord processRecord = mProcessRecords.get(callingPid);
@@ -721,8 +742,8 @@
                         + " is not supported.");
             }
 
-            OverrideRequest request = new OverrideRequest(token, callingPid, state, flags,
-                    OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
+            OverrideRequest request = new OverrideRequest(token, callingPid, callingUid,
+                    state, flags, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
 
             // If we don't have the CONTROL_DEVICE_STATE permission, we want to show the overlay
             if (!hasControlDeviceStatePermission && mRearDisplayState != null
@@ -762,7 +783,7 @@
     }
 
     private void requestBaseStateOverrideInternal(int state, int flags, int callingPid,
-            @NonNull IBinder token) {
+            int callingUid, @NonNull IBinder token) {
         synchronized (mLock) {
             final Optional<DeviceState> deviceState = getStateLocked(state);
             if (!deviceState.isPresent()) {
@@ -782,8 +803,8 @@
                         + " token: " + token);
             }
 
-            OverrideRequest request = new OverrideRequest(token, callingPid, state, flags,
-                    OVERRIDE_REQUEST_TYPE_BASE_STATE);
+            OverrideRequest request = new OverrideRequest(token, callingPid, callingUid,
+                    state, flags, OVERRIDE_REQUEST_TYPE_BASE_STATE);
             mOverrideRequestController.addBaseStateRequest(request);
         }
     }
@@ -953,11 +974,12 @@
         @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int mCurrentBaseState;
 
         @Override
-        public void onSupportedDeviceStatesChanged(DeviceState[] newDeviceStates) {
+        public void onSupportedDeviceStatesChanged(DeviceState[] newDeviceStates,
+                @DeviceStateProvider.SupportedStatesUpdatedReason int reason) {
             if (newDeviceStates.length == 0) {
                 throw new IllegalArgumentException("Supported device states must not be empty");
             }
-            updateSupportedStates(newDeviceStates);
+            updateSupportedStates(newDeviceStates, reason);
         }
 
         @Override
@@ -1085,6 +1107,7 @@
         @Override // Binder call
         public void requestState(IBinder token, int state, int flags) {
             final int callingPid = Binder.getCallingPid();
+            final int callingUid = Binder.getCallingUid();
             // Allow top processes to request a device state change
             // If the calling process ID is not the top app, then we check if this process
             // holds a permission to CONTROL_DEVICE_STATE
@@ -1099,7 +1122,8 @@
 
             final long callingIdentity = Binder.clearCallingIdentity();
             try {
-                requestStateInternal(state, flags, callingPid, token, hasControlStatePermission);
+                requestStateInternal(state, flags, callingPid, callingUid, token,
+                        hasControlStatePermission);
             } finally {
                 Binder.restoreCallingIdentity(callingIdentity);
             }
@@ -1124,6 +1148,7 @@
         @Override // Binder call
         public void requestBaseStateOverride(IBinder token, int state, int flags) {
             final int callingPid = Binder.getCallingPid();
+            final int callingUid = Binder.getCallingUid();
             getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
                     "Permission required to control base state of device.");
 
@@ -1133,7 +1158,7 @@
 
             final long callingIdentity = Binder.clearCallingIdentity();
             try {
-                requestBaseStateOverrideInternal(state, flags, callingPid, token);
+                requestBaseStateOverrideInternal(state, flags, callingPid, callingUid, token);
             } finally {
                 Binder.restoreCallingIdentity(callingIdentity);
             }
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java b/services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java
new file mode 100644
index 0000000..2f14998
--- /dev/null
+++ b/services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2023 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.server.devicestate;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.hardware.devicestate.DeviceStateManager;
+import android.os.Handler;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Manages the user-visible device state notifications.
+ */
+class DeviceStateNotificationController extends BroadcastReceiver {
+    private static final String TAG = "DeviceStateNotificationController";
+
+    @VisibleForTesting static final String INTENT_ACTION_CANCEL_STATE =
+            "com.android.server.devicestate.INTENT_ACTION_CANCEL_STATE";
+    @VisibleForTesting static final int NOTIFICATION_ID = 1;
+    @VisibleForTesting static final String CHANNEL_ID = "DeviceStateManager";
+    @VisibleForTesting static final String NOTIFICATION_TAG = "DeviceStateManager";
+
+    private final Context mContext;
+    private final Handler mHandler;
+    private final NotificationManager mNotificationManager;
+    private final PackageManager mPackageManager;
+
+    // Stores the notification title and content indexed with the device state identifier.
+    private final SparseArray<NotificationInfo> mNotificationInfos;
+
+    // The callback when a device state is requested to be canceled.
+    private final Runnable mCancelStateRunnable;
+
+    DeviceStateNotificationController(@NonNull Context context, @NonNull Handler handler,
+            @NonNull Runnable cancelStateRunnable) {
+        this(context, handler, cancelStateRunnable, getNotificationInfos(context),
+                context.getPackageManager(), context.getSystemService(NotificationManager.class));
+    }
+
+    @VisibleForTesting
+    DeviceStateNotificationController(
+            @NonNull Context context, @NonNull Handler handler,
+            @NonNull Runnable cancelStateRunnable,
+            @NonNull SparseArray<NotificationInfo> notificationInfos,
+            @NonNull PackageManager packageManager,
+            @NonNull NotificationManager notificationManager) {
+        mContext = context;
+        mHandler = handler;
+        mCancelStateRunnable = cancelStateRunnable;
+        mNotificationInfos = notificationInfos;
+        mPackageManager = packageManager;
+        mNotificationManager = notificationManager;
+        mContext.registerReceiver(
+                this,
+                new IntentFilter(INTENT_ACTION_CANCEL_STATE),
+                android.Manifest.permission.CONTROL_DEVICE_STATE,
+                mHandler,
+                Context.RECEIVER_NOT_EXPORTED);
+    }
+
+    /**
+     * Displays the ongoing notification indicating that the device state is active. Does nothing if
+     * the state does not have an active notification.
+     *
+     * @param state the active device state identifier.
+     * @param requestingAppUid the uid of the requesting app used to retrieve the app name.
+     */
+    void showStateActiveNotificationIfNeeded(int state, int requestingAppUid) {
+        NotificationInfo info = mNotificationInfos.get(state);
+        if (info == null || !info.hasActiveNotification()) {
+            return;
+        }
+        String requesterApplicationLabel = getApplicationLabel(requestingAppUid);
+        if (requesterApplicationLabel != null) {
+            showNotification(
+                    info.name, info.activeNotificationTitle,
+                    String.format(info.activeNotificationContent, requesterApplicationLabel),
+                    true /* ongoing */
+            );
+        } else {
+            Slog.e(TAG, "Cannot determine the requesting app name when showing state active "
+                    + "notification. uid=" + requestingAppUid + ", state=" + state);
+        }
+    }
+
+    /**
+     * Displays the notification indicating that the device state is canceled due to thermal
+     * critical condition. Does nothing if the state does not have a thermal critical notification.
+     *
+     * @param state the identifier of the device state being canceled.
+     */
+    void showThermalCriticalNotificationIfNeeded(int state) {
+        NotificationInfo info = mNotificationInfos.get(state);
+        if (info == null || !info.hasThermalCriticalNotification()) {
+            return;
+        }
+        showNotification(
+                info.name, info.thermalCriticalNotificationTitle,
+                info.thermalCriticalNotificationContent, false /* ongoing */
+        );
+    }
+
+    /**
+     * Cancels the notification of the corresponding device state.
+     *
+     * @param state the device state identifier.
+     */
+    void cancelNotification(int state) {
+        if (!mNotificationInfos.contains(state)) {
+            return;
+        }
+        mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
+    }
+
+    @Override
+    public void onReceive(@NonNull Context context, @Nullable Intent intent) {
+        if (intent != null) {
+            if (INTENT_ACTION_CANCEL_STATE.equals(intent.getAction())) {
+                mCancelStateRunnable.run();
+            }
+        }
+    }
+
+    /**
+     * Displays a notification with the specified name, title, and content.
+     *
+     * @param name the name of the notification.
+     * @param title the title of the notification.
+     * @param content the content of the notification.
+     * @param ongoing if true, display an ongoing (sticky) notification with a turn off button.
+     */
+    private void showNotification(
+            @NonNull String name, @NonNull String title, @NonNull String content, boolean ongoing) {
+        final NotificationChannel channel = new NotificationChannel(
+                CHANNEL_ID, name, NotificationManager.IMPORTANCE_HIGH);
+        final Notification.Builder builder = new Notification.Builder(mContext, CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_lock) // TODO(b/266833171) update icons when available.
+                .setContentTitle(title)
+                .setContentText(content)
+                .setSubText(name)
+                .setLocalOnly(true)
+                .setOngoing(ongoing)
+                .setCategory(Notification.CATEGORY_SYSTEM);
+
+        if (ongoing) {
+            final Intent intent = new Intent(INTENT_ACTION_CANCEL_STATE)
+                    .setPackage(mContext.getPackageName());
+            final PendingIntent pendingIntent = PendingIntent.getBroadcast(
+                    mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE);
+            final Notification.Action action = new Notification.Action.Builder(
+                    null /* icon */,
+                    mContext.getString(R.string.device_state_notification_turn_off_button),
+                    pendingIntent)
+                    .build();
+            builder.addAction(action);
+        }
+
+        mNotificationManager.createNotificationChannel(channel);
+        mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, builder.build());
+    }
+
+    /**
+     * Loads the resources for the notifications. The device state identifiers and strings are
+     * stored in arrays. All the string arrays must have the same length and same order as the
+     * identifier array.
+     */
+    private static SparseArray<NotificationInfo> getNotificationInfos(Context context) {
+        final SparseArray<NotificationInfo> notificationInfos = new SparseArray<>();
+
+        final int[] stateIdentifiers =
+                context.getResources().getIntArray(
+                        R.array.device_state_notification_state_identifiers);
+        final String[] names =
+                context.getResources().getStringArray(R.array.device_state_notification_names);
+        final String[] activeNotificationTitles =
+                context.getResources().getStringArray(
+                        R.array.device_state_notification_active_titles);
+        final String[] activeNotificationContents =
+                context.getResources().getStringArray(
+                        R.array.device_state_notification_active_contents);
+        final String[] thermalCriticalNotificationTitles =
+                context.getResources().getStringArray(
+                        R.array.device_state_notification_thermal_titles);
+        final String[] thermalCriticalNotificationContents =
+                context.getResources().getStringArray(
+                        R.array.device_state_notification_thermal_contents);
+
+        if (stateIdentifiers.length != names.length
+                || stateIdentifiers.length != activeNotificationTitles.length
+                || stateIdentifiers.length != activeNotificationContents.length
+                || stateIdentifiers.length != thermalCriticalNotificationTitles.length
+                || stateIdentifiers.length != thermalCriticalNotificationContents.length
+        ) {
+            throw new IllegalStateException(
+                    "The length of state identifiers and notification texts must match!");
+        }
+
+        for (int i = 0; i < stateIdentifiers.length; i++) {
+            int identifier = stateIdentifiers[i];
+            if (identifier == DeviceStateManager.INVALID_DEVICE_STATE) {
+                continue;
+            }
+
+            notificationInfos.put(
+                    identifier,
+                    new NotificationInfo(
+                            names[i], activeNotificationTitles[i], activeNotificationContents[i],
+                            thermalCriticalNotificationTitles[i],
+                            thermalCriticalNotificationContents[i])
+            );
+        }
+
+        return notificationInfos;
+    }
+
+    /**
+     * A helper function to get app name (label) using the app uid.
+     *
+     * @param uid the uid of the app.
+     * @return app name (label) if found, or null otherwise.
+     */
+    @Nullable
+    private String getApplicationLabel(int uid) {
+        String packageName = mPackageManager.getNameForUid(uid);
+        try {
+            ApplicationInfo appInfo = mPackageManager.getApplicationInfo(
+                    packageName, PackageManager.ApplicationInfoFlags.of(0));
+            return appInfo.loadLabel(mPackageManager).toString();
+        } catch (PackageManager.NameNotFoundException e) {
+            return null;
+        }
+    }
+
+    /**
+     * A data class storing string resources of the notification of a device state.
+     */
+    @VisibleForTesting
+    static class NotificationInfo {
+        public final String name;
+        public final String activeNotificationTitle;
+        public final String activeNotificationContent;
+        public final String thermalCriticalNotificationTitle;
+        public final String thermalCriticalNotificationContent;
+
+        NotificationInfo(String name, String activeNotificationTitle,
+                String activeNotificationContent, String thermalCriticalNotificationTitle,
+                String thermalCriticalNotificationContent) {
+
+            this.name = name;
+            this.activeNotificationTitle = activeNotificationTitle;
+            this.activeNotificationContent = activeNotificationContent;
+            this.thermalCriticalNotificationTitle = thermalCriticalNotificationTitle;
+            this.thermalCriticalNotificationContent = thermalCriticalNotificationContent;
+        }
+
+        boolean hasActiveNotification() {
+            return activeNotificationTitle != null && activeNotificationTitle.length() > 0;
+        }
+
+        boolean hasThermalCriticalNotification() {
+            return thermalCriticalNotificationTitle != null
+                    && thermalCriticalNotificationTitle.length() > 0;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
index 109bf63..fecc13f 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
@@ -19,8 +19,12 @@
 import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
 import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
 
+import android.annotation.IntDef;
 import android.annotation.IntRange;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Responsible for providing the set of supported {@link DeviceState device states} as well as the
  * current device state.
@@ -28,12 +32,42 @@
  * @see DeviceStatePolicy
  */
 public interface DeviceStateProvider {
+    int SUPPORTED_DEVICE_STATES_CHANGED_DEFAULT = 0;
+
+    /**
+     * Indicating that the supported device states changed callback is trigger for initial listener
+     * registration.
+     */
+    int SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED = 1;
+
+    /**
+     * Indicating that the supported device states have changed because the thermal condition
+     * returned to normal status from critical status.
+     */
+    int SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL = 2;
+
+    /**
+     * Indicating that the supported device states have changed because of thermal critical
+     * condition.
+     */
+    int SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL = 3;
+
+    @IntDef(prefix = { "SUPPORTED_DEVICE_STATES_CHANGED_" }, value = {
+            SUPPORTED_DEVICE_STATES_CHANGED_DEFAULT,
+            SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED,
+            SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL,
+            SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface SupportedStatesUpdatedReason {}
+
     /**
      * Registers a listener for changes in provider state.
      * <p>
-     * It is <b>required</b> that {@link Listener#onSupportedDeviceStatesChanged(DeviceState[])} be
-     * called followed by {@link Listener#onStateChanged(int)} with the initial values on successful
-     * registration of the listener.
+     * It is <b>required</b> that
+     * {@link Listener#onSupportedDeviceStatesChanged(DeviceState[], int)} be called followed by
+     * {@link Listener#onStateChanged(int)} with the initial values on successful registration of
+     * the listener.
      */
     void setListener(Listener listener);
 
@@ -53,18 +87,20 @@
          * to zero and there must always be at least one supported device state.
          *
          * @param newDeviceStates array of supported device states.
+         * @param reason the reason for the supported device states change.
          *
          * @throws IllegalArgumentException if the list of device states is empty or if one of the
          * provided states contains an invalid identifier.
          */
-        void onSupportedDeviceStatesChanged(DeviceState[] newDeviceStates);
+        void onSupportedDeviceStatesChanged(DeviceState[] newDeviceStates,
+                @SupportedStatesUpdatedReason int reason);
 
         /**
          * Called to notify the listener of a change in current device state. Required to be called
          * once on successful registration of the listener and then once on every subsequent change
          * in device state. Value must have been included in the set of supported device states
          * provided in the most recent call to
-         * {@link #onSupportedDeviceStatesChanged(DeviceState[])}.
+         * {@link #onSupportedDeviceStatesChanged(DeviceState[], int)}.
          *
          * @param identifier the identifier of the new device state.
          *
diff --git a/services/core/java/com/android/server/devicestate/OverrideRequest.java b/services/core/java/com/android/server/devicestate/OverrideRequest.java
index 325e384..74cf184 100644
--- a/services/core/java/com/android/server/devicestate/OverrideRequest.java
+++ b/services/core/java/com/android/server/devicestate/OverrideRequest.java
@@ -31,6 +31,7 @@
 final class OverrideRequest {
     private final IBinder mToken;
     private final int mPid;
+    private final int mUid;
     private final int mRequestedState;
     @DeviceStateRequest.RequestFlags
     private final int mFlags;
@@ -68,10 +69,11 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface OverrideRequestType {}
 
-    OverrideRequest(IBinder token, int pid, int requestedState,
+    OverrideRequest(IBinder token, int pid, int uid, int requestedState,
             @DeviceStateRequest.RequestFlags int flags, @OverrideRequestType int requestType) {
         mToken = token;
         mPid = pid;
+        mUid = uid;
         mRequestedState = requestedState;
         mFlags = flags;
         mRequestType = requestType;
@@ -85,6 +87,10 @@
         return mPid;
     }
 
+    int getUid() {
+        return mUid;
+    }
+
     int getRequestedState() {
         return mRequestedState;
     }
diff --git a/services/core/java/com/android/server/devicestate/OverrideRequestController.java b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
index e39c732..2ed4765 100644
--- a/services/core/java/com/android/server/devicestate/OverrideRequestController.java
+++ b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
@@ -59,6 +59,11 @@
     @Retention(RetentionPolicy.SOURCE)
     @interface RequestStatus {}
 
+    /**
+     * A flag indicating that the status change was triggered by thermal critical status.
+     */
+    static final int FLAG_THERMAL_CRITICAL = 1 << 0;
+
     static String statusToString(@RequestStatus int status) {
         switch (status) {
             case STATUS_ACTIVE:
@@ -106,7 +111,7 @@
     void addRequest(@NonNull OverrideRequest request) {
         OverrideRequest previousRequest = mRequest;
         mRequest = request;
-        mListener.onStatusChanged(request, STATUS_ACTIVE);
+        mListener.onStatusChanged(request, STATUS_ACTIVE, 0 /* flags */);
 
         if (previousRequest != null) {
             cancelRequestLocked(previousRequest);
@@ -116,7 +121,7 @@
     void addBaseStateRequest(@NonNull OverrideRequest request) {
         OverrideRequest previousRequest = mBaseStateRequest;
         mBaseStateRequest = request;
-        mListener.onStatusChanged(request, STATUS_ACTIVE);
+        mListener.onStatusChanged(request, STATUS_ACTIVE, 0 /* flags */);
 
         if (previousRequest != null) {
             cancelRequestLocked(previousRequest);
@@ -219,14 +224,17 @@
      * Notifies the controller that the set of supported states has changed. The controller will
      * notify the listener of all changes to request status as a result of this change.
      */
-    void handleNewSupportedStates(int[] newSupportedStates) {
+    void handleNewSupportedStates(int[] newSupportedStates,
+            @DeviceStateProvider.SupportedStatesUpdatedReason int reason) {
+        boolean isThermalCritical =
+                reason == DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL;
         if (mBaseStateRequest != null && !contains(newSupportedStates,
                 mBaseStateRequest.getRequestedState())) {
-            cancelCurrentBaseStateRequestLocked();
+            cancelCurrentBaseStateRequestLocked(isThermalCritical ? FLAG_THERMAL_CRITICAL : 0);
         }
 
         if (mRequest != null && !contains(newSupportedStates, mRequest.getRequestedState())) {
-            cancelCurrentRequestLocked();
+            cancelCurrentRequestLocked(isThermalCritical ? FLAG_THERMAL_CRITICAL : 0);
         }
     }
 
@@ -244,7 +252,11 @@
     }
 
     private void cancelRequestLocked(@NonNull OverrideRequest requestToCancel) {
-        mListener.onStatusChanged(requestToCancel, STATUS_CANCELED);
+        cancelRequestLocked(requestToCancel, 0 /* flags */);
+    }
+
+    private void cancelRequestLocked(@NonNull OverrideRequest requestToCancel, int flags) {
+        mListener.onStatusChanged(requestToCancel, STATUS_CANCELED, flags);
     }
 
     /**
@@ -252,12 +264,16 @@
      * Notifies the listener of the canceled status as well.
      */
     private void cancelCurrentRequestLocked() {
+        cancelCurrentRequestLocked(0 /* flags */);
+    }
+
+    private void cancelCurrentRequestLocked(int flags) {
         if (mRequest == null) {
             Slog.w(TAG, "Attempted to cancel a null OverrideRequest");
             return;
         }
         mStickyRequest = false;
-        cancelRequestLocked(mRequest);
+        cancelRequestLocked(mRequest, flags);
         mRequest = null;
     }
 
@@ -266,11 +282,15 @@
      * Notifies the listener of the canceled status as well.
      */
     private void cancelCurrentBaseStateRequestLocked() {
+        cancelCurrentBaseStateRequestLocked(0 /* flags */);
+    }
+
+    private void cancelCurrentBaseStateRequestLocked(int flags) {
         if (mBaseStateRequest == null) {
             Slog.w(TAG, "Attempted to cancel a null OverrideRequest");
             return;
         }
-        cancelRequestLocked(mBaseStateRequest);
+        cancelRequestLocked(mBaseStateRequest, flags);
         mBaseStateRequest = null;
     }
 
@@ -284,12 +304,14 @@
     }
 
     public interface StatusChangeListener {
+
         /**
          * Notifies the listener of a change in request status. If a change within the controller
          * causes one request to become active and one to become either suspended or cancelled, this
          * method is guaranteed to be called with the active request first before the suspended or
          * cancelled request.
          */
-        void onStatusChanged(@NonNull OverrideRequest request, @RequestStatus int newStatus);
+        void onStatusChanged(@NonNull OverrideRequest request, @RequestStatus int newStatus,
+                int flags);
     }
 }
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index 7f733ef..8d7f782 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -366,12 +366,12 @@
             }
             mListener = listener;
         }
-        notifySupportedStatesChanged();
+        notifySupportedStatesChanged(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED);
         notifyDeviceStateChangedIfNeeded();
     }
 
     /** Notifies the listener that the set of supported device states has changed. */
-    private void notifySupportedStatesChanged() {
+    private void notifySupportedStatesChanged(@SupportedStatesUpdatedReason int reason) {
         List<DeviceState> supportedStates = new ArrayList<>();
         Listener listener;
         synchronized (mLock) {
@@ -390,7 +390,7 @@
         }
 
         listener.onSupportedDeviceStatesChanged(
-                supportedStates.toArray(new DeviceState[supportedStates.size()]));
+                supportedStates.toArray(new DeviceState[supportedStates.size()]), reason);
     }
 
     /** Computes the current device state and notifies the listener of a change, if needed. */
@@ -688,7 +688,10 @@
         if (isThermalStatusCriticalOrAbove != isPreviousThermalStatusCriticalOrAbove) {
             Slog.i(TAG, "Updating supported device states due to thermal status change."
                     + " isThermalStatusCriticalOrAbove: " + isThermalStatusCriticalOrAbove);
-            notifySupportedStatesChanged();
+            notifySupportedStatesChanged(
+                    isThermalStatusCriticalOrAbove
+                            ? SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL
+                            : SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL);
         }
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index 82f6493..c952609 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -945,13 +945,15 @@
             }
 
             mListener = listener;
-            mListener.onSupportedDeviceStatesChanged(mSupportedDeviceStates);
+            mListener.onSupportedDeviceStatesChanged(mSupportedDeviceStates,
+                    SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED);
             mListener.onStateChanged(mSupportedDeviceStates[0].getIdentifier());
         }
 
         public void notifySupportedDeviceStates(DeviceState[] supportedDeviceStates) {
             mSupportedDeviceStates = supportedDeviceStates;
-            mListener.onSupportedDeviceStatesChanged(supportedDeviceStates);
+            mListener.onSupportedDeviceStatesChanged(supportedDeviceStates,
+                    SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED);
         }
 
         public void setState(int identifier) {
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateNotificationControllerTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateNotificationControllerTest.java
new file mode 100644
index 0000000..8196d6a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateNotificationControllerTest.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2023 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.server.devicestate;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.platform.test.annotations.Presubmit;
+import android.util.SparseArray;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mockito;
+
+/**
+ * Unit tests for {@link DeviceStateNotificationController}.
+ * <p/>
+ * Run with <code>atest com.android.server.devicestate.DeviceStateNotificationControllerTest</code>.
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class DeviceStateNotificationControllerTest {
+
+    private static final int STATE_WITHOUT_NOTIFICATION = 1;
+    private static final int STATE_WITH_ACTIVE_NOTIFICATION = 2;
+    private static final int STATE_WITH_ACTIVE_AND_THERMAL_NOTIFICATION = 3;
+
+    private static final int VALID_APP_UID = 1000;
+    private static final int INVALID_APP_UID = 2000;
+    private static final String VALID_APP_NAME = "Valid app name";
+    private static final String INVALID_APP_NAME = "Invalid app name";
+    private static final String VALID_APP_LABEL = "Valid app label";
+
+    private static final String NAME_1 = "name1";
+    private static final String TITLE_1 = "title1";
+    private static final String CONTENT_1 = "content1:%1$s";
+    private static final String NAME_2 = "name2";
+    private static final String TITLE_2 = "title2";
+    private static final String CONTENT_2 = "content2:%1$s";
+    private static final String THERMAL_TITLE_2 = "thermal_title2";
+    private static final String THERMAL_CONTENT_2 = "thermal_content2";
+
+    private DeviceStateNotificationController mController;
+
+    private final ArgumentCaptor<Notification> mNotificationCaptor = ArgumentCaptor.forClass(
+            Notification.class);
+    private final NotificationManager mNotificationManager = mock(NotificationManager.class);
+
+    @Before
+    public void setup() throws Exception {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        Handler handler = mock(Handler.class);
+        PackageManager packageManager = mock(PackageManager.class);
+        Runnable cancelStateRunnable = mock(Runnable.class);
+        ApplicationInfo applicationInfo = mock(ApplicationInfo.class);
+
+        final SparseArray<DeviceStateNotificationController.NotificationInfo> notificationInfos =
+                new SparseArray<>();
+        notificationInfos.put(STATE_WITH_ACTIVE_NOTIFICATION,
+                new DeviceStateNotificationController.NotificationInfo(
+                        NAME_1, TITLE_1, CONTENT_1,
+                        "", ""));
+        notificationInfos.put(STATE_WITH_ACTIVE_AND_THERMAL_NOTIFICATION,
+                new DeviceStateNotificationController.NotificationInfo(
+                        NAME_2, TITLE_2, CONTENT_2,
+                        THERMAL_TITLE_2, THERMAL_CONTENT_2));
+
+        when(packageManager.getNameForUid(VALID_APP_UID)).thenReturn(VALID_APP_NAME);
+        when(packageManager.getNameForUid(INVALID_APP_UID)).thenReturn(INVALID_APP_NAME);
+        when(packageManager.getApplicationInfo(eq(VALID_APP_NAME), ArgumentMatchers.any()))
+                .thenReturn(applicationInfo);
+        when(packageManager.getApplicationInfo(eq(INVALID_APP_NAME), ArgumentMatchers.any()))
+                .thenThrow(new PackageManager.NameNotFoundException());
+        when(applicationInfo.loadLabel(eq(packageManager))).thenReturn(VALID_APP_LABEL);
+
+        mController = new DeviceStateNotificationController(
+                context, handler, cancelStateRunnable, notificationInfos,
+                packageManager, mNotificationManager);
+    }
+
+    @Test
+    public void test_activeNotification() {
+        mController.showStateActiveNotificationIfNeeded(
+                STATE_WITH_ACTIVE_NOTIFICATION, VALID_APP_UID);
+
+        // Verify that the notification manager is called with correct notification information.
+        verify(mNotificationManager).notify(
+                eq(DeviceStateNotificationController.NOTIFICATION_TAG),
+                eq(DeviceStateNotificationController.NOTIFICATION_ID),
+                mNotificationCaptor.capture());
+        Notification notification = mNotificationCaptor.getValue();
+        assertEquals(TITLE_1, notification.extras.getString(Notification.EXTRA_TITLE));
+        assertEquals(String.format(CONTENT_1, VALID_APP_LABEL),
+                notification.extras.getString(Notification.EXTRA_TEXT));
+        assertEquals(Notification.FLAG_ONGOING_EVENT,
+                notification.flags & Notification.FLAG_ONGOING_EVENT);
+
+        // Verify that the notification action is as expected.
+        Notification.Action[] actions = notification.actions;
+        assertEquals(1, actions.length);
+        Notification.Action action = actions[0];
+        assertEquals(DeviceStateNotificationController.INTENT_ACTION_CANCEL_STATE,
+                action.actionIntent.getIntent().getAction());
+
+        // Verify that the notification is properly canceled.
+        mController.cancelNotification(STATE_WITH_ACTIVE_NOTIFICATION);
+        verify(mNotificationManager).cancel(
+                DeviceStateNotificationController.NOTIFICATION_TAG,
+                DeviceStateNotificationController.NOTIFICATION_ID);
+    }
+
+    @Test
+    public void test_thermalNotification() {
+        // Verify that the active notification is created.
+        mController.showStateActiveNotificationIfNeeded(
+                STATE_WITH_ACTIVE_AND_THERMAL_NOTIFICATION, VALID_APP_UID);
+        verify(mNotificationManager).notify(
+                eq(DeviceStateNotificationController.NOTIFICATION_TAG),
+                eq(DeviceStateNotificationController.NOTIFICATION_ID),
+                mNotificationCaptor.capture());
+        Notification notification = mNotificationCaptor.getValue();
+        assertEquals(TITLE_2, notification.extras.getString(Notification.EXTRA_TITLE));
+        assertEquals(String.format(CONTENT_2, VALID_APP_LABEL),
+                notification.extras.getString(Notification.EXTRA_TEXT));
+        assertEquals(Notification.FLAG_ONGOING_EVENT,
+                notification.flags & Notification.FLAG_ONGOING_EVENT);
+        Mockito.clearInvocations(mNotificationManager);
+
+        // Verify that the thermal critical notification is created.
+        mController.showThermalCriticalNotificationIfNeeded(
+                STATE_WITH_ACTIVE_AND_THERMAL_NOTIFICATION);
+        verify(mNotificationManager).notify(
+                eq(DeviceStateNotificationController.NOTIFICATION_TAG),
+                eq(DeviceStateNotificationController.NOTIFICATION_ID),
+                mNotificationCaptor.capture());
+        notification = mNotificationCaptor.getValue();
+        assertEquals(THERMAL_TITLE_2, notification.extras.getString(Notification.EXTRA_TITLE));
+        assertEquals(THERMAL_CONTENT_2, notification.extras.getString(Notification.EXTRA_TEXT));
+        assertEquals(0, notification.flags & Notification.FLAG_ONGOING_EVENT);
+
+        // Verify that the notification is canceled.
+        mController.cancelNotification(STATE_WITH_ACTIVE_NOTIFICATION);
+        verify(mNotificationManager).cancel(
+                DeviceStateNotificationController.NOTIFICATION_TAG,
+                DeviceStateNotificationController.NOTIFICATION_ID);
+    }
+
+    @Test
+    public void test_deviceStateWithoutNotification() {
+        // Verify that no notification is created.
+        mController.showStateActiveNotificationIfNeeded(
+                STATE_WITHOUT_NOTIFICATION, VALID_APP_UID);
+        verify(mNotificationManager, Mockito.never()).notify(
+                eq(DeviceStateNotificationController.NOTIFICATION_TAG),
+                eq(DeviceStateNotificationController.NOTIFICATION_ID),
+                mNotificationCaptor.capture());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
index 430504c..fe37f42 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
@@ -58,7 +58,7 @@
 
     @Test
     public void addRequest() {
-        OverrideRequest request = new OverrideRequest(new Binder(), 0 /* pid */,
+        OverrideRequest request = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
                 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
         assertNull(mStatusListener.getLastStatus(request));
 
@@ -68,14 +68,14 @@
 
     @Test
     public void addRequest_cancelExistingRequestThroughNewRequest() {
-        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
                 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
         assertNull(mStatusListener.getLastStatus(firstRequest));
 
         mController.addRequest(firstRequest);
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
 
-        OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+        OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
                 1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
         assertNull(mStatusListener.getLastStatus(secondRequest));
 
@@ -86,7 +86,7 @@
 
     @Test
     public void addRequest_cancelActiveRequest() {
-        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
                 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
 
         mController.addRequest(firstRequest);
@@ -100,7 +100,7 @@
 
     @Test
     public void addBaseStateRequest() {
-        OverrideRequest request = new OverrideRequest(new Binder(), 0 /* pid */,
+        OverrideRequest request = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
                 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
         assertNull(mStatusListener.getLastStatus(request));
 
@@ -110,14 +110,14 @@
 
     @Test
     public void addBaseStateRequest_cancelExistingBaseStateRequestThroughNewRequest() {
-        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
                 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
         assertNull(mStatusListener.getLastStatus(firstRequest));
 
         mController.addBaseStateRequest(firstRequest);
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
 
-        OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+        OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
                 1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
         assertNull(mStatusListener.getLastStatus(secondRequest));
 
@@ -128,7 +128,7 @@
 
     @Test
     public void addBaseStateRequest_cancelActiveBaseStateRequest() {
-        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
                 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
 
         mController.addBaseStateRequest(firstRequest);
@@ -142,12 +142,13 @@
 
     @Test
     public void handleBaseStateChanged() {
-        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
                 0 /* requestedState */,
                 DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES /* flags */,
                 OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
 
         OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+                0 /* uid */,
                 0 /* requestedState */,
                 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
 
@@ -167,10 +168,11 @@
 
     @Test
     public void handleProcessDied() {
-        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
                 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
 
         OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+                0 /* uid */,
                 1 /* requestedState */,
                 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
 
@@ -189,10 +191,11 @@
     public void handleProcessDied_stickyRequests() {
         mController.setStickyRequestsAllowed(true);
 
-        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
                 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
 
         OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+                0 /* uid */,
                 1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
 
         mController.addRequest(firstRequest);
@@ -211,10 +214,11 @@
 
     @Test
     public void handleNewSupportedStates() {
-        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
                 1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
 
         OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+                0 /* uid */,
                 1 /* requestedState */,
                 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
 
@@ -224,18 +228,20 @@
         mController.addBaseStateRequest(baseStateRequest);
         assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_ACTIVE);
 
-        mController.handleNewSupportedStates(new int[]{0, 1});
+        mController.handleNewSupportedStates(new int[]{0, 1},
+                DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_DEFAULT);
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
         assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_ACTIVE);
 
-        mController.handleNewSupportedStates(new int[]{0});
+        mController.handleNewSupportedStates(new int[]{0},
+                DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_DEFAULT);
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
         assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_CANCELED);
     }
 
     @Test
     public void cancelOverrideRequestsTest() {
-        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
                 1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
 
         mController.addRequest(firstRequest);
@@ -250,7 +256,7 @@
         private Map<OverrideRequest, Integer> mLastStatusMap = new HashMap<>();
 
         @Override
-        public void onStatusChanged(@NonNull OverrideRequest request, int newStatus) {
+        public void onStatusChanged(@NonNull OverrideRequest request, int newStatus, int flags) {
             mLastStatusMap.put(request, newStatus);
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
index 7fac9b6..7125796 100644
--- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
@@ -19,6 +19,9 @@
 
 import static android.content.Context.SENSOR_SERVICE;
 
+import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED;
+import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL;
+import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL;
 import static com.android.server.policy.DeviceStateProviderImpl.DEFAULT_DEVICE_STATE;
 
 import static org.junit.Assert.assertArrayEquals;
@@ -124,7 +127,8 @@
         DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
         provider.setListener(listener);
 
-        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         assertArrayEquals(new DeviceState[]{DEFAULT_DEVICE_STATE},
                 mDeviceStateArrayCaptor.getValue());
 
@@ -151,7 +155,8 @@
         DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
         provider.setListener(listener);
 
-        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         final DeviceState[] expectedStates = new DeviceState[]{
                 new DeviceState(1, "", 0 /* flags */),
                 new DeviceState(2, "", 0 /* flags */) };
@@ -183,7 +188,8 @@
         DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
         provider.setListener(listener);
 
-        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         final DeviceState[] expectedStates = new DeviceState[]{
                 new DeviceState(1, "", DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS),
                 new DeviceState(2, "", 0 /* flags */) };
@@ -212,7 +218,8 @@
         DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
         provider.setListener(listener);
 
-        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         final DeviceState[] expectedStates = new DeviceState[]{
                 new DeviceState(1, "", 0 /* flags */),
                 new DeviceState(2, "", 0 /* flags */) };
@@ -247,7 +254,8 @@
         DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
         provider.setListener(listener);
 
-        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         final DeviceState[] expectedStates = new DeviceState[]{
                 new DeviceState(1, "", 0 /* flags */),
                 new DeviceState(2, "CLOSED", 0 /* flags */) };
@@ -265,7 +273,8 @@
         Mockito.clearInvocations(listener);
 
         provider.notifyLidSwitchChanged(1, true /* lidOpen */);
-        verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         verify(listener).onStateChanged(mIntegerCaptor.capture());
         assertEquals(1, mIntegerCaptor.getValue().intValue());
     }
@@ -336,7 +345,8 @@
         DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
         provider.setListener(listener);
 
-        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         assertArrayEquals(
                 new DeviceState[]{
                         new DeviceState(1, "CLOSED", 0 /* flags */),
@@ -358,7 +368,8 @@
 
         provider.onSensorChanged(event0);
 
-        verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         verify(listener).onStateChanged(mIntegerCaptor.capture());
         assertEquals(3, mIntegerCaptor.getValue().intValue());
 
@@ -370,7 +381,8 @@
 
         provider.onSensorChanged(event1);
 
-        verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         verify(listener).onStateChanged(mIntegerCaptor.capture());
         assertEquals(2, mIntegerCaptor.getValue().intValue());
 
@@ -382,7 +394,8 @@
 
         provider.onSensorChanged(event2);
 
-        verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         verify(listener).onStateChanged(mIntegerCaptor.capture());
         assertEquals(1, mIntegerCaptor.getValue().intValue());
     }
@@ -397,7 +410,8 @@
         DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
         provider.setListener(listener);
 
-        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         assertArrayEquals(
                 new DeviceState[]{
                         new DeviceState(1, "CLOSED", 0 /* flags */),
@@ -410,12 +424,14 @@
         Mockito.clearInvocations(listener);
 
         provider.onThermalStatusChanged(PowerManager.THERMAL_STATUS_MODERATE);
-        verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         Mockito.clearInvocations(listener);
 
         // The THERMAL_TEST state should be disabled.
         provider.onThermalStatusChanged(PowerManager.THERMAL_STATUS_CRITICAL);
-        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL));
         assertArrayEquals(
                 new DeviceState[]{
                         new DeviceState(1, "CLOSED", 0 /* flags */),
@@ -426,7 +442,8 @@
 
         // The THERMAL_TEST state should be re-enabled.
         provider.onThermalStatusChanged(PowerManager.THERMAL_STATUS_LIGHT);
-        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL));
         assertArrayEquals(
                 new DeviceState[]{
                         new DeviceState(1, "CLOSED", 0 /* flags */),
@@ -468,7 +485,8 @@
 
         provider.onSensorChanged(event2);
 
-        verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         verify(listener, never()).onStateChanged(mIntegerCaptor.capture());
     }
 
@@ -513,7 +531,8 @@
         DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
         provider.setListener(listener);
 
-        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
         assertArrayEquals(
                 new DeviceState[]{
                         new DeviceState(1, "CLOSED", 0 /* flags */),