Refactor CPMS

- Remove Boot Reason
- Rework state machine according to updated design document
- Update CarLocationService with new CPMS states
- Update GarageMode.Controller with new CPMS states
- Update unit tests for CarLocationManager, GarageMode, and CPMS

Bug: 112548962
Test: vhal_emulator.py

(cherry picked from pi-car-dev)

Change-Id: I0e262311626c5032695ec766c34b36f40dc931ec
diff --git a/service/src/com/android/car/CarLocationService.java b/service/src/com/android/car/CarLocationService.java
index e0a55c9..a16c3e3 100644
--- a/service/src/com/android/car/CarLocationService.java
+++ b/service/src/com/android/car/CarLocationService.java
@@ -161,10 +161,9 @@
 
     @Override
     public void onStateChanged(int state, CompletableFuture<Void> future) {
+        logd("onStateChanged: " + state);
         switch (state) {
-            case CarPowerStateListener.SHUTDOWN_ENTER:
-            case CarPowerStateListener.SUSPEND_ENTER:
-                logd("onStateChanged: " + state);
+            case CarPowerStateListener.SHUTDOWN_PREPARE:
                 asyncOperation(() -> {
                     storeLocation();
                     // Notify the CarPowerManager that it may proceed to shutdown or suspend.
@@ -173,8 +172,7 @@
                     }
                 });
                 break;
-            case CarPowerStateListener.SHUTDOWN_CANCELLED:
-            case CarPowerStateListener.SUSPEND_EXIT:
+            default:
                 // This service does not need to do any work for these events but should still
                 // notify the CarPowerManager that it may proceed.
                 if (future != null) {
diff --git a/service/src/com/android/car/CarPowerManagementService.java b/service/src/com/android/car/CarPowerManagementService.java
index 1514056..6ae33cc 100644
--- a/service/src/com/android/car/CarPowerManagementService.java
+++ b/service/src/com/android/car/CarPowerManagementService.java
@@ -20,6 +20,7 @@
 import android.car.hardware.power.ICarPower;
 import android.car.hardware.power.ICarPowerStateListener;
 import android.content.Context;
+import android.hardware.automotive.vehicle.V2_0.VehicleApPowerStateReq;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
@@ -56,7 +57,7 @@
     private final Map<IBinder, Integer> mPowerManagerListenerTokens = new ConcurrentHashMap<>();
 
     @GuardedBy("this")
-    private PowerState mCurrentState;
+    private CpmsState mCurrentState;
     @GuardedBy("this")
     private Timer mTimer;
     @GuardedBy("this")
@@ -64,20 +65,21 @@
     @GuardedBy("this")
     private long mLastSleepEntryTime;
     @GuardedBy("this")
-    private final LinkedList<PowerState> mPendingPowerStates = new LinkedList<>();
+    private final LinkedList<CpmsState> mPendingPowerStates = new LinkedList<>();
     @GuardedBy("this")
     private HandlerThread mHandlerThread;
     @GuardedBy("this")
     private PowerHandler mHandler;
-    private int mBootReason;
-    private boolean mShutdownOnNextSuspend = false;
-    private int mNextWakeupSec;
+    private int mNextWakeupSec = 0;
     private int mTokenValue = 1;
+    private boolean mShutdownOnFinish = false;
 
     // TODO:  Make this OEM configurable.
-    private static final int APP_EXTEND_MAX_MS = 86400000; // 1 day
-    private final static int SHUTDOWN_POLLING_INTERVAL_MS = 2000;
-    private final static int SHUTDOWN_EXTEND_MAX_MS = 5000;
+    private static final int SHUTDOWN_POLLING_INTERVAL_MS = 2000;
+    private static final int SHUTDOWN_EXTEND_MAX_MS = 5000;
+
+    // Use one hour for now
+    private static int sShutdownPrepareTimeMs = 60 * 60 * 1000;
 
     private class PowerManagerCallbackList extends RemoteCallbackList<ICarPowerStateListener> {
         /**
@@ -111,6 +113,16 @@
         mHandler = new PowerHandler(Looper.getMainLooper());
     }
 
+    @VisibleForTesting
+    protected static void setShutdownPrepareTimeout(int timeoutMs) {
+        // Override the timeout to keep testing time short
+        if (timeoutMs < SHUTDOWN_EXTEND_MAX_MS) {
+            sShutdownPrepareTimeMs = SHUTDOWN_EXTEND_MAX_MS;
+        } else {
+            sShutdownPrepareTimeMs = timeoutMs;
+        }
+    }
+
     @Override
     public void init() {
         synchronized (this) {
@@ -121,18 +133,11 @@
 
         mHal.setListener(this);
         if (mHal.isPowerStateSupported()) {
-            mHal.sendBootComplete();
-            PowerState currentState = mHal.getCurrentPowerState();
-            if (currentState != null) {
-                onApPowerStateChange(currentState);
-            } else {
-                Log.w(CarLog.TAG_POWER, "Unable to get get current power state during "
-                        + "initialization");
-            }
+            // Initialize CPMS in WAIT_FOR_VHAL state
+            onApPowerStateChange(CpmsState.WAIT_FOR_VHAL, CarPowerStateListener.WAIT_FOR_VHAL);
         } else {
             Log.w(CarLog.TAG_POWER, "Vehicle hal does not support power state yet.");
-            onApPowerStateChange(new PowerState(PowerHalService.STATE_ON_FULL, 0));
-            mSystemInterface.switchToFullWakeLock();
+            onApPowerStateChange(CpmsState.ON, CarPowerStateListener.ON);
         }
         mSystemInterface.startDisplayStateMonitoring(this);
     }
@@ -158,71 +163,42 @@
         mSystemInterface.releaseAllWakeLocks();
     }
 
-    /**
-     * Notifies earlier completion of power event processing. If some user modules need pre-shutdown
-     * processing time, they will get up to #APP_EXTEND_MAX_MS to complete their tasks. Modules
-     * are expected to finish earlier than that, and this call can be called in such case to trigger
-     * shutdown without waiting further.
-     */
-    public void notifyPowerEventProcessingCompletion() {
-        long processingTime = 0;
-        synchronized (mPowerManagerListenerTokens) {
-            if (!mPowerManagerListenerTokens.isEmpty()) {
-                processingTime += APP_EXTEND_MAX_MS;
-            }
-        }
-        long now = SystemClock.elapsedRealtime();
-        long startTime;
-        boolean shouldShutdown = true;
-        PowerHandler powerHandler;
-        synchronized (this) {
-            startTime = mProcessingStartTime;
-            if (mCurrentState == null) {
-                return;
-            }
-            if (mCurrentState.mState != PowerHalService.STATE_SHUTDOWN_PREPARE) {
-                return;
-            }
-            if (mCurrentState.canEnterDeepSleep() && !mShutdownOnNextSuspend) {
-                shouldShutdown = false;
-                if (mLastSleepEntryTime > mProcessingStartTime && mLastSleepEntryTime < now) {
-                    // already slept
-                    return;
-                }
-            }
-            powerHandler = mHandler;
-        }
-        if ((startTime + processingTime) <= now) {
-            Log.i(CarLog.TAG_POWER, "Processing all done");
-            powerHandler.handleProcessingComplete(shouldShutdown);
-        }
-    }
-
     @Override
     public void dump(PrintWriter writer) {
         writer.println("*PowerManagementService*");
         writer.print("mCurrentState:" + mCurrentState);
         writer.print(",mProcessingStartTime:" + mProcessingStartTime);
-        writer.println(",mLastSleepEntryTime:" + mLastSleepEntryTime);
-    }
-
-    @Override
-    public void onBootReasonReceived(int bootReason) {
-        mBootReason = bootReason;
+        writer.print(",mLastSleepEntryTime:" + mLastSleepEntryTime);
+        writer.print(",mNextWakeupSec:" + mNextWakeupSec);
+        writer.print(",mTokenValue:" + mTokenValue);
+        writer.println(",mShutdownOnFinish:" + mShutdownOnFinish);
     }
 
     @Override
     public void onApPowerStateChange(PowerState state) {
         PowerHandler handler;
         synchronized (this) {
-            mPendingPowerStates.addFirst(state);
+            mPendingPowerStates.addFirst(new CpmsState(state));
+            handler = mHandler;
+        }
+        handler.handlePowerStateChange();
+    }
+
+    /**
+     * Initiate state change from CPMS directly.
+     */
+    private void onApPowerStateChange(int apState, int carPowerStateListenerState) {
+        CpmsState newState = new CpmsState(apState, carPowerStateListenerState);
+        PowerHandler handler;
+        synchronized (this) {
+            mPendingPowerStates.addFirst(newState);
             handler = mHandler;
         }
         handler.handlePowerStateChange();
     }
 
     private void doHandlePowerStateChange() {
-        PowerState state = null;
+        CpmsState state;
         PowerHandler handler;
         synchronized (this) {
             state = mPendingPowerStates.peekFirst();
@@ -230,7 +206,9 @@
             if (state == null) {
                 return;
             }
-            if (!needPowerStateChange(state)) {
+            Log.i(CarLog.TAG_POWER, "doHandlePowerStateChange: newState=" + state.mState);
+            if (!needPowerStateChangeLocked(state)) {
+                Log.d(CarLog.TAG_POWER, "doHandlePowerStateChange no change needed");
                 return;
             }
             // now real power change happens. Whatever was queued before should be all cancelled.
@@ -238,44 +216,66 @@
             handler = mHandler;
         }
         handler.cancelProcessingComplete();
-
-        Log.i(CarLog.TAG_POWER, "Power state change:" + state);
+        Log.i(CarLog.TAG_POWER, "setCurrentState " + state.toString());
+        mCurrentState = state;
         switch (state.mState) {
-            case PowerHalService.STATE_ON_DISP_OFF:
-                handleDisplayOff(state);
+            case CpmsState.WAIT_FOR_VHAL:
+                handleWaitForVhal(state);
                 break;
-            case PowerHalService.STATE_ON_FULL:
-                handleFullOn(state);
+            case CpmsState.ON:
+                handleOn();
                 break;
-            case PowerHalService.STATE_SHUTDOWN_PREPARE:
+            case CpmsState.SHUTDOWN_PREPARE:
                 handleShutdownPrepare(state);
                 break;
+            case CpmsState.WAIT_FOR_FINISH:
+                handleWaitForFinish(state);
+                break;
+            case CpmsState.SUSPEND:
+                // Received FINISH from VHAL
+                handleFinish();
+                break;
+            default:
+                // Illegal state
+                // TODO:  Throw exception?
+                break;
         }
     }
 
-    private void handleDisplayOff(PowerState newState) {
-        setCurrentState(newState);
-        mSystemInterface.setDisplayState(false);
+    private void handleWaitForVhal(CpmsState state) {
+        int carPowerStateListenerState = state.mCarPowerStateListenerState;
+        sendPowerManagerEvent(carPowerStateListenerState, false);
+        // Inspect CarPowerStateListenerState to decide which message to send via VHAL
+        switch (carPowerStateListenerState) {
+            case CarPowerStateListener.WAIT_FOR_VHAL:
+                mHal.sendWaitForVhal();
+                break;
+            case CarPowerStateListener.SHUTDOWN_CANCELLED:
+                mHal.sendShutdownCancel();
+                break;
+            case CarPowerStateListener.SUSPEND_EXIT:
+                mHal.sendSleepExit();
+                break;
+        }
     }
 
-    private void handleFullOn(PowerState newState) {
-        setCurrentState(newState);
+    private void handleOn() {
         mSystemInterface.setDisplayState(true);
+        sendPowerManagerEvent(CarPowerStateListener.ON, false);
+        mHal.sendOn();
     }
 
-    private void handleShutdownPrepare(PowerState newState) {
-        setCurrentState(newState);
-        mSystemInterface.setDisplayState(false);;
-        boolean shouldShutdown = true;
-        if (mHal.isDeepSleepAllowed() && mSystemInterface.isSystemSupportingDeepSleep() &&
-            newState.canEnterDeepSleep() && !mShutdownOnNextSuspend) {
-            Log.i(CarLog.TAG_POWER, "starting sleep");
-            shouldShutdown = false;
-            doHandlePreprocessing(shouldShutdown);
-            return;
-        } else if (newState.canPostponeShutdown()) {
-            Log.i(CarLog.TAG_POWER, "starting shutdown with processing");
-            doHandlePreprocessing(shouldShutdown);
+    private void handleShutdownPrepare(CpmsState newState) {
+        mSystemInterface.setDisplayState(false);
+        // Shutdown on finish if the system doesn't support deep sleep or doesn't allow it.
+        mShutdownOnFinish |= !mHal.isDeepSleepAllowed()
+                || !mSystemInterface.isSystemSupportingDeepSleep()
+                || !newState.mCanSleep;
+        if (newState.mCanPostpone) {
+            Log.i(CarLog.TAG_POWER, "starting shutdown postpone");
+            sendPowerManagerEvent(CarPowerStateListener.SHUTDOWN_PREPARE, true);
+            mHal.sendShutdownPrepare();
+            doHandlePreprocessing();
         } else {
             Log.i(CarLog.TAG_POWER, "starting shutdown immediately");
             synchronized (this) {
@@ -285,6 +285,26 @@
         }
     }
 
+    private void handleWaitForFinish(CpmsState state) {
+        sendPowerManagerEvent(state.mCarPowerStateListenerState, false);
+        switch (state.mCarPowerStateListenerState) {
+            case CarPowerStateListener.SUSPEND_ENTER:
+                mHal.sendSleepEntry(mNextWakeupSec);
+                break;
+            case CarPowerStateListener.SHUTDOWN_ENTER:
+                mHal.sendShutdownStart(mNextWakeupSec);
+                break;
+        }
+    }
+
+    private void handleFinish() {
+        if (mShutdownOnFinish) {
+            doHandleShutdown();
+        } else {
+            doHandleDeepSleep();
+        }
+    }
+
     @GuardedBy("this")
     private void releaseTimerLocked() {
         if (mTimer != null) {
@@ -293,57 +313,45 @@
         mTimer = null;
     }
 
-    private void doHandlePreprocessing(boolean shuttingDown) {
-        // Set time for powerManager events
-        long processingTimeMs = sendPowerManagerEvent(shuttingDown);
-        if (processingTimeMs > 0) {
-            int pollingCount = (int)(processingTimeMs / SHUTDOWN_POLLING_INTERVAL_MS) + 1;
-            Log.i(CarLog.TAG_POWER, "processing before shutdown expected for: "
-                    + processingTimeMs + " ms, adding polling:" + pollingCount);
-            synchronized (this) {
-                mProcessingStartTime = SystemClock.elapsedRealtime();
-                releaseTimerLocked();
-                mTimer = new Timer();
-                mTimer.scheduleAtFixedRate(
-                        new ShutdownProcessingTimerTask(shuttingDown, pollingCount),
-                        0 /*delay*/,
-                        SHUTDOWN_POLLING_INTERVAL_MS);
-            }
-        } else {
-            PowerHandler handler;
-            synchronized (this) {
-                handler = mHandler;
-            }
-            handler.handleProcessingComplete(shuttingDown);
+    private void doHandlePreprocessing() {
+        int pollingCount = (sShutdownPrepareTimeMs / SHUTDOWN_POLLING_INTERVAL_MS) + 1;
+        Log.i(CarLog.TAG_POWER, "processing before shutdown expected for: "
+                + sShutdownPrepareTimeMs + " ms, adding polling:" + pollingCount);
+        synchronized (this) {
+            mProcessingStartTime = SystemClock.elapsedRealtime();
+            releaseTimerLocked();
+            mTimer = new Timer();
+            mTimer.scheduleAtFixedRate(
+                    new ShutdownProcessingTimerTask(pollingCount),
+                    0 /*delay*/,
+                    SHUTDOWN_POLLING_INTERVAL_MS);
         }
     }
 
-    private long sendPowerManagerEvent(boolean shuttingDown) {
-        long processingTimeMs = 0;
-        int newState = shuttingDown ? CarPowerStateListener.SHUTDOWN_ENTER :
-                                      CarPowerStateListener.SUSPEND_ENTER;
+    private void sendPowerManagerEvent(int newState, boolean useTokens) {
         synchronized (mPowerManagerListenerTokens) {
-            mPowerManagerListenerTokens.clear();
+            if (useTokens) {
+                mPowerManagerListenerTokens.clear();
+            }
             int i = mPowerManagerListeners.beginBroadcast();
             while (i-- > 0) {
                 try {
+                    int token = 0;
                     ICarPowerStateListener listener = mPowerManagerListeners.getBroadcastItem(i);
-                    listener.onStateChanged(newState, mTokenValue);
-                    mPowerManagerListenerTokens.put(listener.asBinder(), mTokenValue);
-                    mTokenValue++;
+                    if (useTokens) {
+                        listener.onStateChanged(newState, mTokenValue);
+                        mPowerManagerListenerTokens.put(listener.asBinder(), mTokenValue);
+                        mTokenValue++;
+                    } else {
+                        listener.onStateChanged(newState, 0);
+                    }
                 } catch (RemoteException e) {
                     // Its likely the connection snapped. Let binder death handle the situation.
                     Log.e(CarLog.TAG_POWER, "onStateChanged calling failed: " + e);
                 }
             }
             mPowerManagerListeners.finishBroadcast();
-            if (!mPowerManagerListenerTokens.isEmpty()) {
-                Log.i(CarLog.TAG_POWER,
-                        "mPowerManagerListenerTokens not empty, add APP_EXTEND_MAX_MS");
-                processingTimeMs += APP_EXTEND_MAX_MS;
-            }
         }
-        return processingTimeMs;
     }
 
     private void doHandleDeepSleep() {
@@ -355,61 +363,48 @@
             handler = mHandler;
         }
         handler.cancelProcessingComplete();
-        mHal.sendSleepEntry();
         synchronized (this) {
             mLastSleepEntryTime = SystemClock.elapsedRealtime();
         }
-        if (!mSystemInterface.enterDeepSleep(mNextWakeupSec)) {
-            // System did not suspend.  Need to shutdown
-            // TODO:  Shutdown gracefully
+        if (!mSystemInterface.enterDeepSleep()) {
+            // System did not suspend.  VHAL should transition CPMS to shutdown.
             Log.e(CarLog.TAG_POWER, "Sleep did not succeed.  Need to shutdown");
         }
-        // When we wake up, we reset the next wake up time and if no one will set it
-        // System will suspend / shutdown forever.
+        // On wake, reset nextWakeup time.  If not set again, system will suspend/shutdown forever.
         mNextWakeupSec = 0;
-        mHal.sendSleepExit();
-        // Notify applications
-        int i = mPowerManagerListeners.beginBroadcast();
-        while (i-- > 0) {
-            try {
-                ICarPowerStateListener listener = mPowerManagerListeners.getBroadcastItem(i);
-                listener.onStateChanged(CarPowerStateListener.SUSPEND_EXIT, 0);
-            } catch (RemoteException e) {
-                // Its likely the connection snapped. Let binder death handle the situation.
-                Log.e(CarLog.TAG_POWER, "onStateChanged calling failed: " + e);
-            }
-        }
-        mPowerManagerListeners.finishBroadcast();
 
-        if (mSystemInterface.isWakeupCausedByTimer()) {
-            doHandlePreprocessing(false /*shuttingDown*/);
-        } else {
-            PowerState currentState = mHal.getCurrentPowerState();
-            if (currentState != null && needPowerStateChange(currentState)) {
-                onApPowerStateChange(currentState);
-            } else { // power controller woke-up but no power state change. Just shutdown.
-                Log.w(CarLog.TAG_POWER, "external sleep wake up, but no power state change:" +
-                        currentState);
-                doHandleShutdown();
-            }
-        }
+        onApPowerStateChange(CpmsState.WAIT_FOR_VHAL, CarPowerStateListener.SUSPEND_EXIT);
     }
 
-    private void doHandleNotifyPowerOn() {
-        boolean displayOn = false;
-        synchronized (this) {
-            if (mCurrentState != null && mCurrentState.mState == PowerHalService.STATE_ON_FULL) {
-                displayOn = true;
-            }
-        }
-    }
-
-    private boolean needPowerStateChange(PowerState newState) {
-        synchronized (this) {
-            if (mCurrentState != null && mCurrentState.equals(newState)) {
-                return false;
-            }
+    private boolean needPowerStateChangeLocked(CpmsState newState) {
+        if (newState == null) {
+            return false;
+        } else if (mCurrentState == null) {
             return true;
+        } else if (mCurrentState.equals(newState)) {
+            return false;
+        }
+
+        // The following switch/case enforces the allowed state transitions.
+        switch (mCurrentState.mState) {
+            case CpmsState.WAIT_FOR_VHAL:
+                return (newState.mState == CpmsState.ON)
+                    || (newState.mState == CpmsState.SHUTDOWN_PREPARE);
+            case CpmsState.SUSPEND:
+                return newState.mState == CpmsState.WAIT_FOR_VHAL;
+            case CpmsState.ON:
+                return newState.mState == CpmsState.SHUTDOWN_PREPARE;
+            case CpmsState.SHUTDOWN_PREPARE:
+                // If VHAL sends SHUTDOWN_IMMEDIATELY while in SHUTDOWN_PREPARE state, do it.
+                return ((newState.mState == CpmsState.SHUTDOWN_PREPARE) && !newState.mCanPostpone)
+                    || (newState.mState == CpmsState.WAIT_FOR_FINISH)
+                    || (newState.mState == CpmsState.WAIT_FOR_VHAL);
+            case CpmsState.WAIT_FOR_FINISH:
+                return newState.mState == CpmsState.SUSPEND;
+            default:
+                Log.e(CarLog.TAG_POWER, "Unhandled state transition:  currentState="
+                        + mCurrentState.mState + ", newState=" + newState.mState);
+                return false;
         }
     }
 
@@ -419,24 +414,21 @@
         mSystemInterface.shutdown();
     }
 
-    private void doHandleProcessingComplete(boolean shutdownWhenCompleted) {
+    private void doHandleProcessingComplete() {
         synchronized (this) {
             releaseTimerLocked();
-            if (!shutdownWhenCompleted && mLastSleepEntryTime > mProcessingStartTime) {
+            if (!mShutdownOnFinish && mLastSleepEntryTime > mProcessingStartTime) {
                 // entered sleep after processing start. So this could be duplicate request.
                 Log.w(CarLog.TAG_POWER, "Duplicate sleep entry request, ignore");
                 return;
             }
         }
-        if (shutdownWhenCompleted) {
-            doHandleShutdown();
-        } else {
-            doHandleDeepSleep();
-        }
-    }
 
-    private synchronized void setCurrentState(PowerState state) {
-        mCurrentState = state;
+        if (mShutdownOnFinish) {
+            onApPowerStateChange(CpmsState.WAIT_FOR_FINISH, CarPowerStateListener.SHUTDOWN_ENTER);
+        } else {
+            onApPowerStateChange(CpmsState.WAIT_FOR_FINISH, CarPowerStateListener.SUSPEND_ENTER);
+        }
     }
 
     @Override
@@ -481,6 +473,8 @@
     public void registerListener(ICarPowerStateListener listener) {
         ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_POWER);
         mPowerManagerListeners.register(listener);
+        // TODO:  Need to send current state to newly registered listener?  If so, need to handle
+        //          token for SHUTDOWN_PREPARE state
     }
 
     @Override
@@ -491,7 +485,6 @@
 
     private void doUnregisterListener(ICarPowerStateListener listener) {
         boolean found = mPowerManagerListeners.unregister(listener);
-
         if (found) {
             // Remove outstanding token if there is one
             IBinder binder = listener.asBinder();
@@ -507,14 +500,7 @@
     @Override
     public void requestShutdownOnNextSuspend() {
         ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_POWER);
-        mShutdownOnNextSuspend = true;
-    }
-
-    @Override
-    public int getBootReason() {
-        ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_POWER);
-        // Return the most recent bootReason value
-        return mBootReason;
+        mShutdownOnFinish = true;
     }
 
     @Override
@@ -544,22 +530,30 @@
         if (currentToken == token) {
             mPowerManagerListenerTokens.remove(binder);
             if (mPowerManagerListenerTokens.isEmpty() &&
-                (mCurrentState.mState == PowerHalService.STATE_SHUTDOWN_PREPARE)) {
+                    (mCurrentState.mState == CpmsState.SHUTDOWN_PREPARE)) {
+                PowerHandler powerHandler;
                 // All apps are ready to shutdown/suspend.
-                Log.i(CarLog.TAG_POWER,
-                        "Apps are finished, call notifyPowerEventProcessingCompletion");
-                notifyPowerEventProcessingCompletion();
+                synchronized (this) {
+                    if (!mShutdownOnFinish) {
+                        if (mLastSleepEntryTime > mProcessingStartTime
+                                && mLastSleepEntryTime < SystemClock.elapsedRealtime()) {
+                            Log.i(CarLog.TAG_POWER, "finishedLocked:  Already slept!");
+                            return;
+                        }
+                    }
+                    powerHandler = mHandler;
+                }
+                Log.i(CarLog.TAG_POWER, "Apps are finished, call handleProcessingComplete()");
+                powerHandler.handleProcessingComplete();
             }
         }
     }
 
     private class PowerHandler extends Handler {
-
         private final int MSG_POWER_STATE_CHANGE = 0;
         private final int MSG_DISPLAY_BRIGHTNESS_CHANGE = 1;
         private final int MSG_MAIN_DISPLAY_STATE_CHANGE = 2;
         private final int MSG_PROCESSING_COMPLETE = 3;
-        private final int MSG_NOTIFY_POWER_ON = 4;
 
         // Do not handle this immediately but with some delay as there can be a race between
         // display off due to rear view camera and delivery to here.
@@ -585,14 +579,9 @@
             sendMessageDelayed(msg, MAIN_DISPLAY_EVENT_DELAY_MS);
         }
 
-        private void handleProcessingComplete(boolean shutdownWhenCompleted) {
+        private void handleProcessingComplete() {
             removeMessages(MSG_PROCESSING_COMPLETE);
-            Message msg = obtainMessage(MSG_PROCESSING_COMPLETE, shutdownWhenCompleted ? 1 : 0, 0);
-            sendMessage(msg);
-        }
-
-        private void handlePowerOn() {
-            Message msg = obtainMessage(MSG_NOTIFY_POWER_ON);
+            Message msg = obtainMessage(MSG_PROCESSING_COMPLETE);
             sendMessage(msg);
         }
 
@@ -605,7 +594,6 @@
             removeMessages(MSG_DISPLAY_BRIGHTNESS_CHANGE);
             removeMessages(MSG_MAIN_DISPLAY_STATE_CHANGE);
             removeMessages(MSG_PROCESSING_COMPLETE);
-            removeMessages(MSG_NOTIFY_POWER_ON);
         }
 
         @Override
@@ -621,22 +609,17 @@
                     doHandleMainDisplayStateChange((Boolean) msg.obj);
                     break;
                 case MSG_PROCESSING_COMPLETE:
-                    doHandleProcessingComplete(msg.arg1 == 1);
-                    break;
-                case MSG_NOTIFY_POWER_ON:
-                    doHandleNotifyPowerOn();
+                    doHandleProcessingComplete();
                     break;
             }
         }
     }
 
     private class ShutdownProcessingTimerTask extends TimerTask {
-        private final boolean mShutdownWhenCompleted;
         private final int mExpirationCount;
         private int mCurrentCount;
 
-        private ShutdownProcessingTimerTask(boolean shutdownWhenCompleted, int expirationCount) {
-            mShutdownWhenCompleted = shutdownWhenCompleted;
+        private ShutdownProcessingTimerTask(int expirationCount) {
             mExpirationCount = expirationCount;
             mCurrentCount = 0;
         }
@@ -650,10 +633,124 @@
                     releaseTimerLocked();
                     handler = mHandler;
                 }
-                handler.handleProcessingComplete(mShutdownWhenCompleted);
+                handler.handleProcessingComplete();
             } else {
                 mHal.sendShutdownPostpone(SHUTDOWN_EXTEND_MAX_MS);
             }
         }
     }
+
+    private static class CpmsState {
+        public static final int WAIT_FOR_VHAL = 0;
+        public static final int ON = 1;
+        public static final int SHUTDOWN_PREPARE = 2;
+        public static final int WAIT_FOR_FINISH = 3;
+        public static final int SUSPEND = 4;
+
+        /* Config values from AP_POWER_STATE_REQ */
+        public final boolean mCanPostpone;
+        public final boolean mCanSleep;
+        /* Message sent to CarPowerStateListener in response to this state */
+        public final int mCarPowerStateListenerState;
+        /* One of the above state variables */
+        public final int mState;
+
+        /**
+          * This constructor takes a PowerHalService.PowerState object and creates the corresponding
+          * CPMS state from it.
+          */
+        CpmsState(PowerState halPowerState) {
+            switch (halPowerState.mState) {
+                case VehicleApPowerStateReq.ON:
+                    this.mCanPostpone = false;
+                    this.mCanSleep = false;
+                    this.mCarPowerStateListenerState = cpmsStateToPowerStateListenerState(ON);
+                    this.mState = ON;
+                    break;
+                case VehicleApPowerStateReq.SHUTDOWN_PREPARE:
+                    this.mCanPostpone = halPowerState.canPostponeShutdown();
+                    this.mCanSleep = halPowerState.canEnterDeepSleep();
+                    this.mCarPowerStateListenerState = cpmsStateToPowerStateListenerState(
+                            SHUTDOWN_PREPARE);
+                    this.mState = SHUTDOWN_PREPARE;
+                    break;
+                case VehicleApPowerStateReq.CANCEL_SHUTDOWN:
+                    this.mCanPostpone = false;
+                    this.mCanSleep = false;
+                    this.mCarPowerStateListenerState = CarPowerStateListener.SHUTDOWN_CANCELLED;
+                    this.mState = WAIT_FOR_VHAL;
+                    break;
+                case VehicleApPowerStateReq.FINISHED:
+                    this.mCanPostpone = false;
+                    this.mCanSleep = false;
+                    this.mCarPowerStateListenerState = cpmsStateToPowerStateListenerState(SUSPEND);
+                    this.mState = SUSPEND;
+                    break;
+                default:
+                    // Illegal state from PowerState.  Throw an exception?
+                    this.mCanPostpone = false;
+                    this.mCanSleep = false;
+                    this.mCarPowerStateListenerState = 0;
+                    this.mState = 0;
+                    break;
+            }
+        }
+
+        CpmsState(int state) {
+            this(state, cpmsStateToPowerStateListenerState(state));
+        }
+
+        CpmsState(int state, int carPowerStateListenerState) {
+            this.mCanPostpone = false;
+            this.mCanSleep = false;
+            this.mCarPowerStateListenerState = carPowerStateListenerState;
+            this.mState = state;
+        }
+
+        private static int cpmsStateToPowerStateListenerState(int state) {
+            int powerStateListenerState = 0;
+
+            // Set the CarPowerStateListenerState based on current state
+            switch (state) {
+                case ON:
+                    powerStateListenerState = CarPowerStateListener.ON;
+                    break;
+                case SHUTDOWN_PREPARE:
+                    powerStateListenerState = CarPowerStateListener.SHUTDOWN_PREPARE;
+                    break;
+                case SUSPEND:
+                    powerStateListenerState = CarPowerStateListener.SUSPEND_ENTER;
+                    break;
+                case WAIT_FOR_VHAL:
+                case WAIT_FOR_FINISH:
+                default:
+                    // Illegal state for this constructor.  Throw an exception?
+                    break;
+            }
+            return powerStateListenerState;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (!(o instanceof CpmsState)) {
+                return false;
+            }
+            CpmsState that = (CpmsState) o;
+            return this.mState == that.mState
+                    && this.mCanSleep == that.mCanSleep
+                    && this.mCanPostpone == that.mCanPostpone
+                    && this.mCarPowerStateListenerState == that.mCarPowerStateListenerState;
+        }
+
+        @Override
+        public String toString() {
+            return "CpmsState canSleep:" + mCanSleep + ", canPostpone=" + mCanPostpone
+                    + ", carPowerStateListenerState=" + mCarPowerStateListenerState
+                    + ", CpmsState=" + mState;
+        }
+    }
+
 }
diff --git a/service/src/com/android/car/garagemode/Controller.java b/service/src/com/android/car/garagemode/Controller.java
index e9b2888..e08ad2f 100644
--- a/service/src/com/android/car/garagemode/Controller.java
+++ b/service/src/com/android/car/garagemode/Controller.java
@@ -101,11 +101,15 @@
                 break;
             case CarPowerStateListener.SHUTDOWN_ENTER:
                 LOG.d("CPM state changed to SHUTDOWN_ENTER");
-                handleShutdownEnter(future);
+                handleShutdownEnter();
+                break;
+            case CarPowerStateListener.SHUTDOWN_PREPARE:
+                LOG.d("CPM state changed to SHUTDOWN_PREPARE");
+                handleShutdownPrepare(future);
                 break;
             case CarPowerStateListener.SUSPEND_ENTER:
                 LOG.d("CPM state changed to SUSPEND_ENTER");
-                handleSuspendEnter(future);
+                handleSuspendEnter();
                 break;
             case CarPowerStateListener.SUSPEND_EXIT:
                 LOG.d("CPM state changed to SUSPEND_EXIT");
@@ -190,11 +194,15 @@
         resetGarageMode();
     }
 
-    private void handleSuspendEnter(CompletableFuture<Void> future) {
-        initiateGarageMode(future);
+    private void handleSuspendEnter() {
+        resetGarageMode();
     }
 
-    private void handleShutdownEnter(CompletableFuture<Void> future) {
+    private void handleShutdownEnter() {
+        resetGarageMode();
+    }
+
+    private void handleShutdownPrepare(CompletableFuture<Void> future) {
         initiateGarageMode(future);
     }
 
diff --git a/service/src/com/android/car/hal/PowerHalService.java b/service/src/com/android/car/hal/PowerHalService.java
index 504fbf2..3972984 100644
--- a/service/src/com/android/car/hal/PowerHalService.java
+++ b/service/src/com/android/car/hal/PowerHalService.java
@@ -16,13 +16,11 @@
 package com.android.car.hal;
 
 
-import static android.hardware.automotive.vehicle.V2_0.VehicleProperty.AP_POWER_BOOTUP_REASON;
 import static android.hardware.automotive.vehicle.V2_0.VehicleProperty.AP_POWER_STATE_REPORT;
 import static android.hardware.automotive.vehicle.V2_0.VehicleProperty.AP_POWER_STATE_REQ;
 import static android.hardware.automotive.vehicle.V2_0.VehicleProperty.DISPLAY_BRIGHTNESS;
 
 import android.annotation.Nullable;
-import android.hardware.automotive.vehicle.V2_0.VehicleApPowerBootupReason;
 import android.hardware.automotive.vehicle.V2_0.VehicleApPowerStateConfigFlag;
 import android.hardware.automotive.vehicle.V2_0.VehicleApPowerStateReport;
 import android.hardware.automotive.vehicle.V2_0.VehicleApPowerStateReq;
@@ -43,23 +41,11 @@
 import java.util.List;
 
 public class PowerHalService extends HalServiceBase {
-    // AP Power State constants set by HAL implementation
-    public static final int STATE_OFF = VehicleApPowerStateReq.OFF;
-    public static final int STATE_DEEP_SLEEP = VehicleApPowerStateReq.DEEP_SLEEP;
-    public static final int STATE_ON_DISP_OFF = VehicleApPowerStateReq.ON_DISP_OFF;
-    public static final int STATE_ON_FULL = VehicleApPowerStateReq.ON_FULL;
-    public static final int STATE_SHUTDOWN_PREPARE = VehicleApPowerStateReq.SHUTDOWN_PREPARE;
-
-    // Boot reason set by VMCU
-    public static final int BOOT_REASON_USER_POWER_ON = VehicleApPowerBootupReason.USER_POWER_ON;
-    public static final int BOOT_REASON_USER_UNLOCK = VehicleApPowerBootupReason.USER_UNLOCK;
-    public static final int BOOT_REASON_TIMER = VehicleApPowerBootupReason.TIMER;
-
     // Set display brightness from 0-100%
     public static final int MAX_BRIGHTNESS = 100;
 
     @VisibleForTesting
-    public static final int SET_BOOT_COMPLETE = VehicleApPowerStateReport.BOOT_COMPLETE;
+    public static final int SET_WAIT_FOR_VHAL = VehicleApPowerStateReport.WAIT_FOR_VHAL;
     @VisibleForTesting
     public static final int SET_DEEP_SLEEP_ENTRY = VehicleApPowerStateReport.DEEP_SLEEP_ENTRY;
     @VisibleForTesting
@@ -69,9 +55,11 @@
     @VisibleForTesting
     public static final int SET_SHUTDOWN_START = VehicleApPowerStateReport.SHUTDOWN_START;
     @VisibleForTesting
-    public static final int SET_DISPLAY_ON = VehicleApPowerStateReport.DISPLAY_ON;
+    public static final int SET_ON = VehicleApPowerStateReport.ON;
     @VisibleForTesting
-    public static final int SET_DISPLAY_OFF = VehicleApPowerStateReport.DISPLAY_OFF;
+    public static final int SET_SHUTDOWN_PREPARE = VehicleApPowerStateReport.SHUTDOWN_PREPARE;
+    @VisibleForTesting
+    public static final int SET_SHUTDOWN_CANCELLED = VehicleApPowerStateReport.SHUTDOWN_CANCELLED;
 
     @VisibleForTesting
     public static final int SHUTDOWN_CAN_SLEEP = VehicleApPowerStateShutdownParam.CAN_SLEEP;
@@ -92,11 +80,6 @@
          * @param brightness in percentile. 100% full.
          */
         void onDisplayBrightnessChange(int brightness);
-        /**
-         * Received boot reason.
-         * @param boot reason.
-         */
-        void onBootReasonReceived(int bootReason);
     }
 
     public static final class PowerState {
@@ -118,7 +101,7 @@
          * @throws IllegalStateException
          */
         public boolean canEnterDeepSleep() {
-            if (mState != STATE_SHUTDOWN_PREPARE) {
+            if (mState != VehicleApPowerStateReq.SHUTDOWN_PREPARE) {
                 throw new IllegalStateException("wrong state");
             }
             return (mParam == VehicleApPowerStateShutdownParam.CAN_SLEEP);
@@ -131,7 +114,7 @@
          * @throws IllegalStateException
          */
         public boolean canPostponeShutdown() {
-            if (mState != STATE_SHUTDOWN_PREPARE) {
+            if (mState != VehicleApPowerStateReq.SHUTDOWN_PREPARE) {
                 throw new IllegalStateException("wrong state");
             }
             return (mParam != VehicleApPowerStateShutdownParam.SHUTDOWN_IMMEDIATELY);
@@ -180,32 +163,73 @@
         }
     }
 
-    public void sendBootComplete() {
-        Log.i(CarLog.TAG_POWER, "send boot complete");
-        setPowerState(VehicleApPowerStateReport.BOOT_COMPLETE, 0);
+    /**
+     * Send WaitForVhal message to VHAL
+     */
+    public void sendWaitForVhal() {
+        Log.i(CarLog.TAG_POWER, "send wait for vhal");
+        setPowerState(VehicleApPowerStateReport.WAIT_FOR_VHAL, 0);
     }
 
-    public void sendSleepEntry() {
+   /**
+     * Send SleepEntry message to VHAL
+     * @param wakeupTimeSec Notify VHAL when system wants to be woken from sleep.
+     */
+    public void sendSleepEntry(int wakeupTimeSec) {
         Log.i(CarLog.TAG_POWER, "send sleep entry");
-        setPowerState(VehicleApPowerStateReport.DEEP_SLEEP_ENTRY, 0);
+        setPowerState(VehicleApPowerStateReport.DEEP_SLEEP_ENTRY, wakeupTimeSec);
     }
 
+    /**
+     * Send SleepExit message to VHAL
+     * Notifies VHAL when SOC has woken.
+     */
     public void sendSleepExit() {
         Log.i(CarLog.TAG_POWER, "send sleep exit");
         setPowerState(VehicleApPowerStateReport.DEEP_SLEEP_EXIT, 0);
     }
 
+    /**
+     * Send Shutdown Postpone message to VHAL
+     */
     public void sendShutdownPostpone(int postponeTimeMs) {
         Log.i(CarLog.TAG_POWER, "send shutdown postpone, time:" + postponeTimeMs);
         setPowerState(VehicleApPowerStateReport.SHUTDOWN_POSTPONE, postponeTimeMs);
     }
 
+    /**
+     * Send Shutdown Start message to VHAL
+     */
     public void sendShutdownStart(int wakeupTimeSec) {
         Log.i(CarLog.TAG_POWER, "send shutdown start");
         setPowerState(VehicleApPowerStateReport.SHUTDOWN_START, wakeupTimeSec);
     }
 
     /**
+     * Send On message to VHAL
+     */
+    public void sendOn() {
+        Log.i(CarLog.TAG_POWER, "send on");
+        setPowerState(VehicleApPowerStateReport.ON, 0);
+    }
+
+    /**
+     * Send Shutdown Prepare message to VHAL
+     */
+    public void sendShutdownPrepare() {
+        Log.i(CarLog.TAG_POWER, "send shutdown prepare");
+        setPowerState(VehicleApPowerStateReport.SHUTDOWN_PREPARE, 0);
+    }
+
+    /**
+     * Send Shutdown Cancel message to VHAL
+     */
+    public void sendShutdownCancel() {
+        Log.i(CarLog.TAG_POWER, "send shutdown cancel");
+        setPowerState(VehicleApPowerStateReport.SHUTDOWN_CANCELLED, 0);
+    }
+
+    /**
      * Sets the display brightness for the vehicle.
      * @param brightness value from 0 to 100.
      */
@@ -223,23 +247,15 @@
         }
     }
 
-    public void sendDisplayOn() {
-        Log.i(CarLog.TAG_POWER, "send display on");
-        setPowerState(VehicleApPowerStateReport.DISPLAY_ON, 0);
-    }
-
-    public void sendDisplayOff() {
-        Log.i(CarLog.TAG_POWER, "send display off");
-        setPowerState(VehicleApPowerStateReport.DISPLAY_OFF, 0);
-    }
-
     private void setPowerState(int state, int additionalParam) {
-        int[] values = { state, additionalParam };
-        try {
-            mHal.set(VehicleProperty.AP_POWER_STATE_REPORT, 0).to(values);
-            Log.i(CarLog.TAG_POWER, "setPowerState=" + state + " param=" + additionalParam);
-        } catch (PropertyTimeoutException e) {
-            Log.e(CarLog.TAG_POWER, "cannot set to AP_POWER_STATE_REPORT", e);
+        if (isPowerStateSupported()) {
+            int[] values = { state, additionalParam };
+            try {
+                mHal.set(VehicleProperty.AP_POWER_STATE_REPORT, 0).to(values);
+                Log.i(CarLog.TAG_POWER, "setPowerState=" + state + " param=" + additionalParam);
+            } catch (PropertyTimeoutException e) {
+                Log.e(CarLog.TAG_POWER, "cannot set to AP_POWER_STATE_REPORT", e);
+            }
         }
     }
 
@@ -257,8 +273,8 @@
     }
 
     public synchronized boolean isPowerStateSupported() {
-        VehiclePropConfig config = mProperties.get(VehicleProperty.AP_POWER_STATE_REQ);
-        return config != null;
+        return (mProperties.get(VehicleProperty.AP_POWER_STATE_REQ) != null)
+                && (mProperties.get(VehicleProperty.AP_POWER_STATE_REPORT) != null);
     }
 
     private synchronized boolean isConfigFlagSet(int flag) {
@@ -308,7 +324,6 @@
             Collection<VehiclePropConfig> allProperties) {
         for (VehiclePropConfig config : allProperties) {
             switch (config.prop) {
-                case AP_POWER_BOOTUP_REASON:
                 case AP_POWER_STATE_REQ:
                 case AP_POWER_STATE_REPORT:
                 case DISPLAY_BRIGHTNESS:
@@ -338,10 +353,8 @@
     private void dispatchEvents(List<VehiclePropValue> values, PowerEventListener listener) {
         for (VehiclePropValue v : values) {
             switch (v.prop) {
-                case AP_POWER_BOOTUP_REASON:
-                    int reason = v.value.int32Values.get(0);
-                    Log.i(CarLog.TAG_POWER, "Received AP_POWER_BOOTUP_REASON=" + reason);
-                    listener.onBootReasonReceived(reason);
+                case AP_POWER_STATE_REPORT:
+                    // Should never see this; write-only property
                     break;
                 case AP_POWER_STATE_REQ:
                     int state = v.value.int32Values.get(VehicleApPowerStateReqIndex.STATE);
diff --git a/service/src/com/android/car/systeminterface/SystemInterface.java b/service/src/com/android/car/systeminterface/SystemInterface.java
index b508988..dd7d961 100644
--- a/service/src/com/android/car/systeminterface/SystemInterface.java
+++ b/service/src/com/android/car/systeminterface/SystemInterface.java
@@ -153,8 +153,8 @@
     }
 
     @Override
-    public boolean enterDeepSleep(int wakeupTimeSec) {
-        return mSystemStateInterface.enterDeepSleep(wakeupTimeSec);
+    public boolean enterDeepSleep() {
+        return mSystemStateInterface.enterDeepSleep();
     }
 
     @Override
diff --git a/service/src/com/android/car/systeminterface/SystemStateInterface.java b/service/src/com/android/car/systeminterface/SystemStateInterface.java
index 5fb7185..c963ac0 100644
--- a/service/src/com/android/car/systeminterface/SystemStateInterface.java
+++ b/service/src/com/android/car/systeminterface/SystemStateInterface.java
@@ -16,34 +16,36 @@
 
 package com.android.car.systeminterface;
 
-import java.time.Duration;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-
-import com.android.car.procfsinspector.ProcessInfo;
-import com.android.car.procfsinspector.ProcfsInspector;
-import com.android.internal.car.ICarServiceHelper;
-
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.PowerManager;
-import android.os.SystemClock;
 import android.util.Log;
 import android.util.Pair;
 
+import com.android.car.procfsinspector.ProcessInfo;
+import com.android.car.procfsinspector.ProcfsInspector;
+import com.android.internal.car.ICarServiceHelper;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
 /**
  * Interface that abstracts system status (booted, sleeping, ...) operations
  */
 public interface SystemStateInterface {
     static final String TAG = SystemStateInterface.class.getSimpleName();
     void shutdown();
-    boolean enterDeepSleep(int sleepDurationSec);
+    /**
+     * Put the device into Suspend to RAM mode
+     * @return boolean true if suspend succeeded
+     */
+    boolean enterDeepSleep();
     void scheduleActionForBootCompleted(Runnable action, Duration delay);
 
     default boolean isWakeupCausedByTimer() {
@@ -56,7 +58,7 @@
 
     default boolean isSystemSupportingDeepSleep() {
         //TODO should return by checking some kernel suspend control sysfs, bug: 32061842
-        return false;
+        return true;
     }
 
     default List<ProcessInfo> getRunningProcesses() {
@@ -99,7 +101,7 @@
         }
 
         @Override
-        public boolean enterDeepSleep(int sleepDurationSec) {
+        public boolean enterDeepSleep() {
             boolean deviceEnteredSleep;
             //TODO set wake up time via VHAL, bug: 32061842
             try {