basic power management service
- handle boot complete and shutdown
- skeleton code for deep sleep added. Complete
implementation requires kernel interaction which is TBD.
- handle display on / off with keeping display on with wakelock
- added interface for implementing garage mode
- tested with hal mocking only
bug: 25348217
Change-Id: Iae64ee7a16c861edd5473e392589e7e7213bd52b
(cherry picked from commit 099bc004632a2d274a11a5c436d718cba362ddc6)
diff --git a/service/src/com/android/car/CarPowerManagementService.java b/service/src/com/android/car/CarPowerManagementService.java
new file mode 100644
index 0000000..9c86400
--- /dev/null
+++ b/service/src/com/android/car/CarPowerManagementService.java
@@ -0,0 +1,557 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.Display;
+
+import com.android.car.hal.PowerHalService;
+import com.android.car.hal.PowerHalService.PowerState;
+import com.android.car.hal.VehicleHal;
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+public class CarPowerManagementService implements CarServiceBase,
+ PowerHalService.PowerEventListener {
+
+ /**
+ * Listener for other services to monitor power events.
+ */
+ public interface PowerServiceEventListener {
+ /**
+ * Shutdown is happening
+ */
+ void onShutdown();
+
+ /**
+ * Entering deep sleep.
+ */
+ void onSleepEntry();
+
+ /**
+ * Got out of deep sleep.
+ */
+ void onSleepExit();
+ }
+
+ /**
+ * Interface for components requiring processing time before shutting-down or
+ * entering sleep, and wake-up after shut-down.
+ */
+ public interface PowerEventProcessingHandler {
+ /**
+ * Called before shutdown or sleep entry to allow running some processing. This call
+ * should only queue such task in different thread and should return quickly.
+ * Blocking inside this call can trigger watchdog timer which can terminate the
+ * whole system.
+ * @param shuttingDown whether system is shutting down or not (= sleep entry).
+ * @return time necessary to run processing in ms. should return 0 if there is no
+ * processing necessary.
+ */
+ long onPrePowerEvent(boolean shuttingDown);
+
+ /**
+ * Returns wake up time after system is fully shutdown. Power controller will power on
+ * the system after this time. This power on is meant for regular maintenance kind of
+ * operation.
+ * @return 0 of wake up is not necessary.
+ */
+ int getWakeupTime();
+ }
+
+ private final Context mContext;
+ private final PowerHalService mHal;
+ private final PowerManager mPowerManager;
+ private final DisplayManager mDisplayManager;
+ private final HandlerThread mHandlerThread;
+ private final PowerHandler mHandler;
+ private final WakeLock mFullWakeLock;
+ private final WakeLock mPartialWakeLock;
+ private final CopyOnWriteArrayList<PowerServiceEventListener> mListeners =
+ new CopyOnWriteArrayList<>();
+ private final CopyOnWriteArrayList<PowerEventProcessingHandler> mPowerEcentProcessingHandlers =
+ new CopyOnWriteArrayList<>();
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private PowerState mCurrentState;
+ @GuardedBy("mLock")
+ private boolean mRearviewCameraActive = false; //TODO plumb this from rearview camera HAL
+ @GuardedBy("mLock")
+ private Timer mTimer;
+
+ private final int SHUTDOWN_POLLING_INTERVAL_MS = 2000;
+ private final int SHUTDOWN_EXTEND_MAX_MS = 5000;
+
+ public CarPowerManagementService(Context context) {
+ mContext = context;
+ mHal = VehicleHal.getInstance().getPowerHal();
+ mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ mDisplayManager = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
+ mHandlerThread = new HandlerThread(CarLog.TAG_POWER);
+ mHandlerThread.start();
+ mHandler = new PowerHandler(mHandlerThread.getLooper());
+ mFullWakeLock = mPowerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, CarLog.TAG_POWER);
+ mPartialWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ CarLog.TAG_POWER);
+ }
+
+ @Override
+ public void init() {
+ mHal.setListener(this);
+ if (mHal.isPowerStateSupported()) {
+ mHal.sendBootComplete();
+ PowerState currentState = mHal.getCurrentPowerState();
+ mHandler.handlePowerStateChange(currentState);
+ } else {
+ Log.w(CarLog.TAG_POWER, "Vehicle hal does not support power state yet.");
+ mFullWakeLock.acquire();
+ }
+ //TODO monitor display state
+ }
+
+ @Override
+ public void release() {
+ synchronized (mLock) {
+ releaseTimerLocked();
+ mCurrentState = null;
+ }
+ mHandler.cancelAll();
+ mListeners.clear();
+ mPowerEcentProcessingHandlers.clear();
+ releaseAllWakeLocks();
+ }
+
+ /**
+ * Register listener to monitor power event. There is no unregister counter-part and the list
+ * will be cleared when the service is released.
+ * @param listener
+ */
+ public void registerPowerEventListener(PowerServiceEventListener listener) {
+ mListeners.add(listener);
+ }
+
+ /**
+ * Register PowerEventPreprocessingHandler to run pre-processing before shutdown or
+ * sleep entry. There is no unregister counter-part and the list
+ * will be cleared when the service is released.
+ * @param handler
+ */
+ public void registerPowerEventProcessingHandler(PowerEventProcessingHandler handler) {
+ mPowerEcentProcessingHandlers.add(handler);
+ }
+
+ /**
+ * Notifies earlier completion of power event processing. PowerEventProcessingHandler quotes
+ * time necessary from onPrePowerEvent() call, but actual processing can finish earlier than
+ * that, and this call can be called in such case to trigger shutdown without waiting further.
+ *
+ * @param handler PowerEventProcessingHandler that was already registered with
+ * {@link #registerPowerEventListener(PowerServiceEventListener)} call. If it was not
+ * registered before, this call will be ignored.
+ */
+ public void notifyPowerEventProcessingCompletion(PowerEventProcessingHandler handler) {
+ //TODO
+ }
+
+ @Override
+ public void dump(PrintWriter writer) {
+ writer.println("*PowerManagementService*");
+ writer.println("mCurrentState:" + mCurrentState);
+ }
+
+ @Override
+ public void onApPowerStateChange(PowerState state) {
+ mHandler.handlePowerStateChange(state);
+ }
+
+ private void doHandlePowerStateChange(PowerState state) {
+ if (!isPowerStateChanging(state)) {
+ return;
+ }
+ Log.i(CarLog.TAG_POWER, "Power state change:" + state);
+ switch (state.state) {
+ case PowerHalService.STATE_ON_DISP_OFF:
+ handleDisplayOff(state);
+ break;
+ case PowerHalService.STATE_ON_FULL:
+ if (isRearviewCameraActive()) {
+ handleFullOnWithRearviewCameraActive(state);
+ } else {
+ handleFullOn(state);
+ }
+ break;
+ case PowerHalService.STATE_SHUTDOWN_PREPARE:
+ handleShutdownPrepare(state);
+ break;
+ }
+ }
+
+ private void handleDisplayOff(PowerState newState) {
+ setCurrentState(newState);
+ doDisplayOff();
+ }
+
+ private void handleFullOn(PowerState newState) {
+ setCurrentState(newState);
+ doDisplayOn();
+ }
+
+ private void handleFullOnWithRearviewCameraActive(PowerState newState) {
+ setCurrentState(newState);
+ // just hold the wakelock but do not turn display on.
+ if (!mFullWakeLock.isHeld()) {
+ mFullWakeLock.acquire();
+ }
+ }
+
+ private void handleShutdownPrepare(PowerState newState) {
+ setCurrentState(newState);
+ doDisplayOff();
+ boolean shouldShutdown = true;
+ if (mHal.isDeepSleepAllowed() && isSystemSupportingDeepSleep() &&
+ newState.canEnterDeepSleep()) {
+ 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);
+ } else {
+ Log.i(CarLog.TAG_POWER, "starting shutdown immediately");
+ synchronized (mLock) {
+ releaseTimerLocked();
+ }
+ doHandleShutdown();
+ }
+ }
+
+ private void releaseTimerLocked() {
+ if (mTimer != null) {
+ mTimer.cancel();
+ }
+ mTimer = null;
+ }
+
+ private void doHandlePreprocessing(boolean shuttingDown) {
+ long processingTimeMs = 0;
+ for (PowerEventProcessingHandler handler : mPowerEcentProcessingHandlers) {
+ long handlerProcessingTime = handler.onPrePowerEvent(shuttingDown);
+ if (handlerProcessingTime > processingTimeMs) {
+ processingTimeMs = handlerProcessingTime;
+ }
+ }
+ if (processingTimeMs > 0) {
+ int pollingCount = (int)(processingTimeMs / SHUTDOWN_POLLING_INTERVAL_MS) + 2;
+ Log.i(CarLog.TAG_POWER, "processing before shutdown expected for :" + processingTimeMs +
+ " ms, adding polling:" + pollingCount);
+ synchronized (this) {
+ releaseTimerLocked();
+ mTimer = new Timer();
+ mTimer.scheduleAtFixedRate(new ShutdownProcessingTimerTask(shuttingDown,
+ pollingCount),
+ 0 /*delay*/,
+ SHUTDOWN_POLLING_INTERVAL_MS);
+ }
+ } else {
+ mHandler.handleProcessingComplete(shuttingDown);
+ }
+ }
+
+ private void doHandleDeepSleep() {
+ for (PowerServiceEventListener listener : mListeners) {
+ listener.onSleepEntry();
+ }
+ int wakeupTimeSec = getWakeupTime();
+ mHal.sendSleepEntry();
+ releaseAllWakeLocks();
+ if (!shouldDoFakeShutdown()) { // if it is mocked, do not enter sleep.
+ //TODO enter sleep with given wake up time
+ }
+ switchToPartialWakeLock();
+ mHal.sendSleepExit();
+ for (PowerServiceEventListener listener : mListeners) {
+ listener.onSleepExit();
+ }
+ if (isWakeupCausedByTimer()) {
+ doHandlePreprocessing(false /*shuttingDown*/);
+ } else {
+ PowerState currentState = mHal.getCurrentPowerState();
+ if (isPowerStateChanging(currentState)) {
+ mHandler.handlePowerStateChange(currentState);
+ } else { // power controller woke-up but no power state change. Just shutdown.
+ Log.w(CarLog.TAG_POWER, "externa sleep wake up, but no power state change:" +
+ currentState);
+ doHandleShutdown();
+ }
+ }
+ }
+
+ private boolean isPowerStateChanging(PowerState newState) {
+ synchronized (mLock) {
+ if (mCurrentState != null && mCurrentState.equals(newState)) {
+ return false;
+ }
+ return true;
+ }
+ }
+
+ private boolean isWakeupCausedByTimer() {
+ //TODO check wake up reason and do necessary operation information should come from kernel
+ // it can be either power on or wake up for maintenance
+ // power on will involve GPIO trigger from power controller
+ // its own wakeup will involve timer expiration.
+ return false;
+ }
+
+ private void doHandleShutdown() {
+ // now shutdown
+ for (PowerServiceEventListener listener : mListeners) {
+ listener.onShutdown();
+ }
+ int wakeupTimeSec = 0;
+ if (mHal.isTimedWakeupAllowed()) {
+ wakeupTimeSec = getWakeupTime();
+ }
+ mHal.sendShutdownStart(wakeupTimeSec);
+ if (!shouldDoFakeShutdown()) {
+ mPowerManager.shutdown(false /* no confirm*/, null, true /* true */);
+ }
+ }
+
+ private int getWakeupTime() {
+ int wakeupTimeSec = 0;
+ for (PowerEventProcessingHandler handler : mPowerEcentProcessingHandlers) {
+ int t = handler.getWakeupTime();
+ if (t > wakeupTimeSec) {
+ wakeupTimeSec = t;
+ }
+ }
+ return wakeupTimeSec;
+ }
+
+ private void doHandleProcessingComplete(boolean shutdownWhenCompleted) {
+ synchronized (mLock) {
+ releaseTimerLocked();
+ }
+ if (shutdownWhenCompleted) {
+ doHandleShutdown();
+ } else {
+ doHandleDeepSleep();
+ }
+ }
+
+ private boolean isSystemSupportingDeepSleep() {
+ //TODO should return by checking some kernel suspend control sysfs
+ return false;
+ }
+
+ private synchronized void setCurrentState(PowerState state) {
+ mCurrentState = state;
+ }
+
+ @Override
+ public void onDisplayBrightnessChange(int brightness) {
+ // TODO
+ }
+
+ private void doHandleDisplayBrightnessChange(int brightness) {
+ //TODO
+ }
+
+ private void doHandleMainDisplayStateChange() {
+ //TODO
+ }
+
+ private boolean isMainDisplayOn() {
+ Display disp = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
+ return disp.getState() != Display.STATE_OFF;
+ }
+
+ private synchronized boolean isRearviewCameraActive() {
+ return mRearviewCameraActive;
+ }
+
+ private void doDisplayOn() {
+ switchToFullWakeLock();
+ if (!isMainDisplayOn()) {
+ mPowerManager.wakeUp(SystemClock.uptimeMillis());
+ }
+ }
+
+ private void doDisplayOff() {
+ switchToPartialWakeLock();
+ if (isMainDisplayOn()) {
+ mPowerManager.goToSleep(SystemClock.uptimeMillis());
+ }
+ }
+
+ private void switchToPartialWakeLock() {
+ if (!mPartialWakeLock.isHeld()) {
+ mPartialWakeLock.acquire();
+ }
+ if (mFullWakeLock.isHeld()) {
+ mFullWakeLock.release();
+ }
+ }
+
+ private void switchToFullWakeLock() {
+ if (!mFullWakeLock.isHeld()) {
+ mFullWakeLock.acquire();
+ }
+ if (mPartialWakeLock.isHeld()) {
+ mPartialWakeLock.release();
+ }
+ }
+
+ private void releaseAllWakeLocks() {
+ if (mPartialWakeLock.isHeld()) {
+ mPartialWakeLock.release();
+ }
+ if (mFullWakeLock.isHeld()) {
+ mFullWakeLock.release();
+ }
+ }
+
+ private boolean shouldDoFakeShutdown() {
+ ICarImpl carImpl = ICarImpl.getInstance(mContext);
+ if (!carImpl.isInMocking()) {
+ return false;
+ }
+ CarTestService testService = (CarTestService) carImpl.getCarService(
+ CarSystemTest.TEST_SERVICE);
+ return !testService.shouldDoRealShutdownInMocking();
+ }
+
+ 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;
+
+ // 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.
+ private final long MAIN_DISPLAY_EVENT_DELAY_MS = 500;
+
+ private PowerHandler(Looper looper) {
+ super(looper);
+ }
+
+ private void handlePowerStateChange(PowerState state) {
+ Message msg = obtainMessage(MSG_POWER_STATE_CHANGE, state);
+ sendMessage(msg);
+ }
+
+ private void handleDisplayBrightnessChange(int brightness) {
+ Message msg = obtainMessage(MSG_DISPLAY_BRIGHTNESS_CHANGE, brightness, 0);
+ sendMessage(msg);
+ }
+
+ private void handleMainDisplayStateChange() {
+ removeMessages(MSG_MAIN_DISPLAY_STATE_CHANGE);
+ Message msg = obtainMessage(MSG_MAIN_DISPLAY_STATE_CHANGE);
+ sendMessageDelayed(msg, MAIN_DISPLAY_EVENT_DELAY_MS);
+ }
+
+ private void handleProcessingComplete(boolean shutdownWhenCompleted) {
+ Message msg = obtainMessage(MSG_PROCESSING_COMPLETE, shutdownWhenCompleted ? 1 : 0, 0);
+ sendMessage(msg);
+ }
+
+ private void cancelAll() {
+ removeMessages(MSG_POWER_STATE_CHANGE);
+ removeMessages(MSG_DISPLAY_BRIGHTNESS_CHANGE);
+ removeMessages(MSG_MAIN_DISPLAY_STATE_CHANGE);
+ removeMessages(MSG_PROCESSING_COMPLETE);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_POWER_STATE_CHANGE:
+ doHandlePowerStateChange((PowerState) msg.obj);
+ break;
+ case MSG_DISPLAY_BRIGHTNESS_CHANGE:
+ doHandleDisplayBrightnessChange(msg.arg1);
+ break;
+ case MSG_MAIN_DISPLAY_STATE_CHANGE:
+ doHandleMainDisplayStateChange();
+ case MSG_PROCESSING_COMPLETE:
+ doHandleProcessingComplete(msg.arg1 == 1);
+ break;
+ }
+ }
+ }
+
+ private class DisplayStateListener implements DisplayManager.DisplayListener {
+
+ @Override
+ public void onDisplayAdded(int displayId) {
+ //ignore
+ }
+
+ @Override
+ public void onDisplayChanged(int displayId) {
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ mHandler.handleMainDisplayStateChange();
+ }
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ //ignore
+ }
+ }
+
+ private class ShutdownProcessingTimerTask extends TimerTask {
+ private final boolean mShutdownWhenCompleted;
+ private final int mExpirationCount;
+ private int mCurrentCount;
+
+ private ShutdownProcessingTimerTask(boolean shutdownWhenCompleted, int expirationCount) {
+ mShutdownWhenCompleted = shutdownWhenCompleted;
+ mExpirationCount = expirationCount;
+ mCurrentCount = 0;
+ }
+
+ @Override
+ public void run() {
+ mCurrentCount++;
+ if (mCurrentCount > mExpirationCount) {
+ synchronized (mLock) {
+ releaseTimerLocked();
+ }
+ mHandler.handleProcessingComplete(mShutdownWhenCompleted);
+ } else {
+ mHal.sendShutdownPostpone(SHUTDOWN_EXTEND_MAX_MS);
+ }
+ }
+ }
+}