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/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;
+        }
+    }
+
 }