diff --git a/apex/jobscheduler/framework/java/android/os/WearModeManagerInternal.java b/apex/jobscheduler/framework/java/android/os/WearModeManagerInternal.java
index 969975762c91f98f036c947a3d4766885fa75ca5..75838c213eb6ccd7af258aa2af6bdb84a4717b84 100644
--- a/apex/jobscheduler/framework/java/android/os/WearModeManagerInternal.java
+++ b/apex/jobscheduler/framework/java/android/os/WearModeManagerInternal.java
@@ -48,6 +48,17 @@ public interface WearModeManagerInternal {
      */
     String QUICK_DOZE_REQUEST_IDENTIFIER = "quick_doze_request";
 
+    /**
+     * Mode manager off body state identifier.
+     *
+     * <p>Unique identifier that can be used as identifier parameter in
+     * registerInternalStateObserver
+     * to listen to changes in quick doze request state from mode manager.
+     *
+     * TODO(b/288276510): convert to int constant
+     */
+    String OFFBODY_STATE_ID = "off_body";
+
     /**
      * StringDef for Mode manager identifiers.
      *
@@ -55,7 +66,8 @@ public interface WearModeManagerInternal {
      */
     @Retention(RetentionPolicy.SOURCE)
     @StringDef({
-            QUICK_DOZE_REQUEST_IDENTIFIER
+            QUICK_DOZE_REQUEST_IDENTIFIER,
+            OFFBODY_STATE_ID
     })
     @Target(ElementType.TYPE_USE)
     @interface Identifier {
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index b8596d5a392644a7b984386287b886107262e2c7..1eaabf5b884f7e29cd371050b2a5dadbf620a887 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -377,7 +377,11 @@ public class DeviceIdleController extends SystemService
     @GuardedBy("this")
     private boolean mModeManagerRequestedQuickDoze;
     @GuardedBy("this")
+    private boolean mIsOffBody;
+    @GuardedBy("this")
     private boolean mForceModeManagerQuickDozeRequest;
+    @GuardedBy("this")
+    private boolean mForceModeManagerOffBodyState;
 
     /** Time in the elapsed realtime timebase when this listener last received a motion event. */
     @GuardedBy("this")
@@ -437,6 +441,7 @@ public class DeviceIdleController extends SystemService
     private static final int ACTIVE_REASON_ALARM = 7;
     private static final int ACTIVE_REASON_EMERGENCY_CALL = 8;
     private static final int ACTIVE_REASON_MODE_MANAGER = 9;
+    private static final int ACTIVE_REASON_ONBODY = 10;
 
     @VisibleForTesting
     static String stateToString(int state) {
@@ -837,7 +842,7 @@ public class DeviceIdleController extends SystemService
     class ModeManagerQuickDozeRequestConsumer implements Consumer<Boolean> {
         @Override
         public void accept(Boolean enabled) {
-            Slog.d(TAG, "Mode manager quick doze request: " + enabled);
+            Slog.i(TAG, "Mode manager quick doze request: " + enabled);
             synchronized (DeviceIdleController.this) {
                 if (!mForceModeManagerQuickDozeRequest
                         && mModeManagerRequestedQuickDoze != enabled) {
@@ -848,13 +853,46 @@ public class DeviceIdleController extends SystemService
         }
 
         @GuardedBy("DeviceIdleController.this")
-        public void onModeManagerRequestChangedLocked() {
+        private void onModeManagerRequestChangedLocked() {
             // Get into quick doze faster when mode manager requests instead of taking
             // traditional multi-stage approach.
+            maybeBecomeActiveOnModeManagerEventsLocked();
             updateQuickDozeFlagLocked();
-            if (!mModeManagerRequestedQuickDoze && !mBatterySaverEnabled) {
-                mActiveReason = ACTIVE_REASON_MODE_MANAGER;
-                becomeActiveLocked("mode_manager", Process.myUid());
+        }
+    }
+
+    @VisibleForTesting
+    class ModeManagerOffBodyStateConsumer implements Consumer<Boolean> {
+        @Override
+        public void accept(Boolean isOffBody) {
+            Slog.i(TAG, "Offbody event from mode manager: " + isOffBody);
+            synchronized (DeviceIdleController.this) {
+                if (!mForceModeManagerOffBodyState && mIsOffBody != isOffBody) {
+                    mIsOffBody = isOffBody;
+                    onModeManagerOffBodyChangedLocked();
+                }
+            }
+        }
+
+        @GuardedBy("DeviceIdleController.this")
+        private void onModeManagerOffBodyChangedLocked() {
+            maybeBecomeActiveOnModeManagerEventsLocked();
+        }
+    }
+
+    @GuardedBy("DeviceIdleController.this")
+    private void maybeBecomeActiveOnModeManagerEventsLocked() {
+        synchronized (DeviceIdleController.this) {
+            if (mQuickDozeActivated) {
+                // Quick doze is enabled so don't turn the device active.
+                return;
+            }
+            // Fall through when quick doze is not requested.
+
+            if (!mIsOffBody) {
+                // Quick doze was not requested and device is on body so turn the device active.
+                mActiveReason = ACTIVE_REASON_ONBODY;
+                becomeActiveLocked("on_body", Process.myUid());
             }
         }
     }
@@ -863,6 +901,10 @@ public class DeviceIdleController extends SystemService
     final ModeManagerQuickDozeRequestConsumer mModeManagerQuickDozeRequestConsumer =
             new ModeManagerQuickDozeRequestConsumer();
 
+    @VisibleForTesting
+    final ModeManagerOffBodyStateConsumer mModeManagerOffBodyStateConsumer =
+            new ModeManagerOffBodyStateConsumer();
+
     @VisibleForTesting
     final class MotionListener extends TriggerEventListener
             implements SensorEventListener {
@@ -2648,6 +2690,12 @@ public class DeviceIdleController extends SystemService
                                 WearModeManagerInternal.QUICK_DOZE_REQUEST_IDENTIFIER,
                                 AppSchedulingModuleThread.getExecutor(),
                                 mModeManagerQuickDozeRequestConsumer);
+
+                        modeManagerInternal.addActiveStateChangeListener(
+                                WearModeManagerInternal.OFFBODY_STATE_ID,
+                                AppSchedulingModuleThread.getExecutor(),
+                                mModeManagerOffBodyStateConsumer
+                        );
                     }
                 }
                 mLocalPowerManager.registerLowPowerModeObserver(ServiceType.QUICK_DOZE,
@@ -4463,7 +4511,7 @@ public class DeviceIdleController extends SystemService
         pw.println(
                 "    Resume normal functioning after force-idle or force-inactive or "
                         + "force-modemanager-quickdoze.");
-        pw.println("  get [light|deep|force|screen|charging|network|offbody|forcebodystate]");
+        pw.println("  get [light|deep|force|screen|charging|network|offbody|forceoffbody]");
         pw.println("    Retrieve the current given state.");
         pw.println("  disable [light|deep|all]");
         pw.println("    Completely disable device idle mode.");
@@ -4500,6 +4548,10 @@ public class DeviceIdleController extends SystemService
         pw.println("  force-modemanager-quickdoze [true|false]");
         pw.println("    Simulate mode manager request to enable (true) or disable (false) "
                 + "quick doze. Mode manager changes will be ignored until unforce is called.");
+        pw.println("  force-modemanager-offbody [true|false]");
+        pw.println("    Force mode manager offbody state, this can be used to simulate "
+                + "device being off-body (true) or on-body (false). Mode manager changes "
+                + "will be ignored until unforce is called.");
     }
 
     class Shell extends ShellCommand {
@@ -4634,6 +4686,9 @@ public class DeviceIdleController extends SystemService
                     mForceModeManagerQuickDozeRequest = false;
                     pw.println("mForceModeManagerQuickDozeRequest: "
                             + mForceModeManagerQuickDozeRequest);
+                    mForceModeManagerOffBodyState = false;
+                    pw.println("mForceModeManagerOffBodyState: "
+                            + mForceModeManagerOffBodyState);
                 } finally {
                     Binder.restoreCallingIdentity(token);
                 }
@@ -4660,6 +4715,8 @@ public class DeviceIdleController extends SystemService
                             case "forcemodemanagerquick":
                                 pw.println(mForceModeManagerQuickDozeRequest);
                                 break;
+                            case "offbody": pw.println(mIsOffBody); break;
+                            case "forceoffbody": pw.println(mForceModeManagerOffBodyState); break;
                             default: pw.println("Unknown get option: " + arg); break;
                         }
                     } finally {
@@ -4982,6 +5039,31 @@ public class DeviceIdleController extends SystemService
                 pw.println("Provide true or false argument after force-modemanager-quickdoze");
                 return -1;
             }
+        } else if ("force-modemanager-offbody".equals(cmd)) {
+            getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
+                    null);
+            String arg = shell.getNextArg();
+
+            if ("true".equalsIgnoreCase(arg) || "false".equalsIgnoreCase(arg)) {
+                boolean isOffBody = Boolean.parseBoolean(arg);
+
+                synchronized (DeviceIdleController.this) {
+                    final long token = Binder.clearCallingIdentity();
+                    try {
+                        mForceModeManagerOffBodyState = true;
+                        pw.println("mForceModeManagerOffBodyState: "
+                                + mForceModeManagerOffBodyState);
+                        mIsOffBody = isOffBody;
+                        pw.println("mIsOffBody: " + mIsOffBody);
+                        mModeManagerOffBodyStateConsumer.onModeManagerOffBodyChangedLocked();
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+                }
+            } else {
+                pw.println("Provide true or false argument after force-modemanager-offbody");
+                return -1;
+            }
         } else {
             return shell.handleDefaultCommands(cmd);
         }
@@ -5233,6 +5315,12 @@ public class DeviceIdleController extends SystemService
             if (mAlarmsActive) {
                 pw.print("  mAlarmsActive="); pw.println(mAlarmsActive);
             }
+            if (mConstants.USE_MODE_MANAGER) {
+                pw.print("  mModeManagerRequestedQuickDoze=");
+                pw.println(mModeManagerRequestedQuickDoze);
+                pw.print("  mIsOffBody");
+                pw.println(mIsOffBody);
+            }
         }
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
index 525bfd75269b1b4434f3f1454d3322552415c18c..5b1508b8393be1e8eff82cb4c40610169d085849 100644
--- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
@@ -16,7 +16,6 @@
 package com.android.server;
 
 import static androidx.test.InstrumentationRegistry.getContext;
-
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -42,7 +41,6 @@ import static com.android.server.DeviceIdleController.STATE_QUICK_DOZE_DELAY;
 import static com.android.server.DeviceIdleController.STATE_SENSING;
 import static com.android.server.DeviceIdleController.lightStateToString;
 import static com.android.server.DeviceIdleController.stateToString;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -2418,7 +2416,7 @@ public class DeviceIdleControllerTest {
     }
 
     @Test
-    public void testModeManager_NoModeManagerLocalService_AddListenerNotCalled() {
+    public void testModeManager_NoModeManagerLocalService_AddQuickDozeListenerNotCalled() {
         mConstants.USE_MODE_MANAGER = true;
         doReturn(null)
                 .when(() -> LocalServices.getService(WearModeManagerInternal.class));
@@ -2429,6 +2427,33 @@ public class DeviceIdleControllerTest {
                 eq(mDeviceIdleController.mModeManagerQuickDozeRequestConsumer));
     }
 
+    @Test
+    public void testModeManager_NoModeManagerLocalService_AddOffBodyListenerNotCalled() {
+        mConstants.USE_MODE_MANAGER = true;
+        doReturn(null)
+                .when(() -> LocalServices.getService(WearModeManagerInternal.class));
+        cleanupDeviceIdleController();
+        setupDeviceIdleController();
+        verify(mWearModeManagerInternal, never()).addActiveStateChangeListener(
+                eq(WearModeManagerInternal.OFFBODY_STATE_ID), any(),
+                eq(mDeviceIdleController.mModeManagerOffBodyStateConsumer));
+    }
+
+    @Test
+    public void testModeManager_USEMODEMANAGERIsFalse_AddListenerNotCalled() {
+        mConstants.USE_MODE_MANAGER = false;
+        doReturn(new Object())
+                .when(() -> LocalServices.getService(WearModeManagerInternal.class));
+        cleanupDeviceIdleController();
+        setupDeviceIdleController();
+        verify(mWearModeManagerInternal, never()).addActiveStateChangeListener(
+                eq(WearModeManagerInternal.OFFBODY_STATE_ID), any(),
+                eq(mDeviceIdleController.mModeManagerOffBodyStateConsumer));
+        verify(mWearModeManagerInternal, never()).addActiveStateChangeListener(
+                eq(WearModeManagerInternal.QUICK_DOZE_REQUEST_IDENTIFIER), any(),
+                eq(mDeviceIdleController.mModeManagerQuickDozeRequestConsumer));
+    }
+
     @Test
     public void testModeManager_NoBatterySaver_QuickDoze() {
         mConstants.USE_MODE_MANAGER = true;
@@ -2469,6 +2494,102 @@ public class DeviceIdleControllerTest {
         assertTrue(mDeviceIdleController.isQuickDozeEnabled());
     }
 
+    @Test
+    public void testModeManager_QuickDozeRequestedBatterySaverEnabledOnBody_QuickDozeEnabled() {
+        mConstants.USE_MODE_MANAGER = true;
+        PowerSaveState powerSaveState = new PowerSaveState.Builder().setBatterySaverEnabled(
+                true).build();
+        when(mPowerManagerInternal.getLowPowerState(anyInt()))
+                .thenReturn(powerSaveState);
+        cleanupDeviceIdleController();
+        setupDeviceIdleController();
+
+        mDeviceIdleController.mModeManagerOffBodyStateConsumer.accept(false);
+        mDeviceIdleController.mModeManagerQuickDozeRequestConsumer.accept(true);
+
+        assertTrue(mDeviceIdleController.isQuickDozeEnabled());
+    }
+
+    @Test
+    public void testModeManager_QuickDozeRequestedBatterySaverEnabledOffBody_QuickDozeEnabled() {
+        mConstants.USE_MODE_MANAGER = true;
+        PowerSaveState powerSaveState = new PowerSaveState.Builder().setBatterySaverEnabled(
+                true).build();
+        when(mPowerManagerInternal.getLowPowerState(anyInt()))
+                .thenReturn(powerSaveState);
+        cleanupDeviceIdleController();
+        setupDeviceIdleController();
+
+        mDeviceIdleController.mModeManagerOffBodyStateConsumer.accept(true);
+        mDeviceIdleController.mModeManagerQuickDozeRequestConsumer.accept(true);
+
+        assertTrue(mDeviceIdleController.isQuickDozeEnabled());
+    }
+
+    @Test
+    public void testModeManager_QuickDozeRequestedBatterySaverDisabledOnBody_QuickDozeEnabled() {
+        mConstants.USE_MODE_MANAGER = true;
+        PowerSaveState powerSaveState = new PowerSaveState.Builder().setBatterySaverEnabled(
+                false).build();
+        when(mPowerManagerInternal.getLowPowerState(anyInt()))
+                .thenReturn(powerSaveState);
+        cleanupDeviceIdleController();
+        setupDeviceIdleController();
+
+        mDeviceIdleController.mModeManagerOffBodyStateConsumer.accept(false);
+        mDeviceIdleController.mModeManagerQuickDozeRequestConsumer.accept(true);
+
+        assertTrue(mDeviceIdleController.isQuickDozeEnabled());
+    }
+
+    @Test
+    public void testModeManager_QuickDozeRequestedBatterySaverDisabledOffBody_QuickDozeEnabled() {
+        mConstants.USE_MODE_MANAGER = true;
+        PowerSaveState powerSaveState = new PowerSaveState.Builder().setBatterySaverEnabled(
+                false).build();
+        when(mPowerManagerInternal.getLowPowerState(anyInt()))
+                .thenReturn(powerSaveState);
+        cleanupDeviceIdleController();
+        setupDeviceIdleController();
+
+        mDeviceIdleController.mModeManagerOffBodyStateConsumer.accept(true);
+        mDeviceIdleController.mModeManagerQuickDozeRequestConsumer.accept(true);
+
+        assertTrue(mDeviceIdleController.isQuickDozeEnabled());
+    }
+
+    @Test
+    public void testModeManager_QuickDozeNotRequestedBatterySaverEnabledOnBody_QuickDozeEnabled() {
+        mConstants.USE_MODE_MANAGER = true;
+        PowerSaveState powerSaveState = new PowerSaveState.Builder().setBatterySaverEnabled(
+                true).build();
+        when(mPowerManagerInternal.getLowPowerState(anyInt()))
+                .thenReturn(powerSaveState);
+        cleanupDeviceIdleController();
+        setupDeviceIdleController();
+
+        mDeviceIdleController.mModeManagerOffBodyStateConsumer.accept(false);
+        mDeviceIdleController.mModeManagerQuickDozeRequestConsumer.accept(false);
+
+        assertTrue(mDeviceIdleController.isQuickDozeEnabled());
+    }
+
+    @Test
+    public void testModeManager_QuickDozeNotRequestedBatterySaverEnabledOffBody_QuickDozeEnabled() {
+        mConstants.USE_MODE_MANAGER = true;
+        PowerSaveState powerSaveState = new PowerSaveState.Builder().setBatterySaverEnabled(
+                true).build();
+        when(mPowerManagerInternal.getLowPowerState(anyInt()))
+                .thenReturn(powerSaveState);
+        cleanupDeviceIdleController();
+        setupDeviceIdleController();
+
+        mDeviceIdleController.mModeManagerOffBodyStateConsumer.accept(true);
+        mDeviceIdleController.mModeManagerQuickDozeRequestConsumer.accept(false);
+
+        assertTrue(mDeviceIdleController.isQuickDozeEnabled());
+    }
+
     private void enterDeepState(int state) {
         switch (state) {
             case STATE_ACTIVE: