Merge "Update PowerCycle and GarageMode handling in watchdog." into sc-dev
diff --git a/cpp/watchdog/aidl/android/automotive/watchdog/internal/GarageMode.aidl b/cpp/watchdog/aidl/android/automotive/watchdog/internal/GarageMode.aidl
new file mode 100644
index 0000000..9bb7d86
--- /dev/null
+++ b/cpp/watchdog/aidl/android/automotive/watchdog/internal/GarageMode.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2021 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 android.automotive.watchdog.internal;
+
+/**
+ * Used by ICarWatchdog to describe the garage mode status.
+ */
+@Backing(type="int")
+enum GarageMode {
+  /**
+   * The system is in normal mode.
+   */
+  GARAGE_MODE_OFF,
+
+  /**
+   * The system is in garage mode.
+   */
+  GARAGE_MODE_ON,
+}
diff --git a/cpp/watchdog/aidl/android/automotive/watchdog/internal/PowerCycle.aidl b/cpp/watchdog/aidl/android/automotive/watchdog/internal/PowerCycle.aidl
index 2539892..a23fa4e 100644
--- a/cpp/watchdog/aidl/android/automotive/watchdog/internal/PowerCycle.aidl
+++ b/cpp/watchdog/aidl/android/automotive/watchdog/internal/PowerCycle.aidl
@@ -22,14 +22,14 @@
 @Backing(type="int")
 enum PowerCycle {
   /**
-   * The system is about to shut down.
+   * The system prepares for shutdown or suspend.
    */
-  POWER_CYCLE_SHUTDOWN,
+  POWER_CYCLE_SHUTDOWN_PREPARE,
 
   /**
-   * The system is being suspended.
+   * The system enters shutdown or suspend.
    */
-  POWER_CYCLE_SUSPEND,
+  POWER_CYCLE_SHUTDOWN_ENTER,
 
   /**
    * The system resumes working.
diff --git a/cpp/watchdog/aidl/android/automotive/watchdog/internal/StateType.aidl b/cpp/watchdog/aidl/android/automotive/watchdog/internal/StateType.aidl
index 16a35a7..4ec001e 100644
--- a/cpp/watchdog/aidl/android/automotive/watchdog/internal/StateType.aidl
+++ b/cpp/watchdog/aidl/android/automotive/watchdog/internal/StateType.aidl
@@ -35,4 +35,9 @@
    * Boot phase change.
    */
   BOOT_PHASE,
+
+  /**
+   * Garage mode change.
+   */
+  GARAGE_MODE,
 }
\ No newline at end of file
diff --git a/cpp/watchdog/server/src/WatchdogInternalHandler.cpp b/cpp/watchdog/server/src/WatchdogInternalHandler.cpp
index 68bb489..6da5fb5 100644
--- a/cpp/watchdog/server/src/WatchdogInternalHandler.cpp
+++ b/cpp/watchdog/server/src/WatchdogInternalHandler.cpp
@@ -21,6 +21,7 @@
 #include "WatchdogBinderMediator.h"
 
 #include <android/automotive/watchdog/internal/BootPhase.h>
+#include <android/automotive/watchdog/internal/GarageMode.h>
 #include <android/automotive/watchdog/internal/PowerCycle.h>
 #include <android/automotive/watchdog/internal/UserState.h>
 #include <binder/IPCThreadState.h>
@@ -33,6 +34,7 @@
 namespace aawi = ::android::automotive::watchdog::internal;
 
 using aawi::ComponentType;
+using aawi::GarageMode;
 using aawi::ICarWatchdogServiceForSystem;
 using aawi::PackageResourceOveruseAction;
 using aawi::PowerCycle;
@@ -171,6 +173,13 @@
             }
             return handlePowerCycleChange(powerCycle);
         }
+        case aawi::StateType::GARAGE_MODE: {
+            GarageMode garageMode = static_cast<GarageMode>(static_cast<uint32_t>(arg1));
+            mWatchdogPerfService->setSystemState(garageMode == GarageMode::GARAGE_MODE_OFF
+                                                         ? SystemState::NORMAL_MODE
+                                                         : SystemState::GARAGE_MODE);
+            return Status::ok();
+        }
         case aawi::StateType::USER_STATE: {
             userid_t userId = static_cast<userid_t>(arg1);
             aawi::UserState userState = static_cast<aawi::UserState>(static_cast<uint32_t>(arg2));
@@ -196,19 +205,18 @@
 
 Status WatchdogInternalHandler::handlePowerCycleChange(PowerCycle powerCycle) {
     switch (powerCycle) {
-        case PowerCycle::POWER_CYCLE_SHUTDOWN:
-            ALOGI("Received SHUTDOWN power cycle");
+        case PowerCycle::POWER_CYCLE_SHUTDOWN_PREPARE:
+            ALOGI("Received SHUTDOWN_PREPARE power cycle");
             mWatchdogProcessService->setEnabled(/*isEnabled=*/false);
+            // TODO(b/189508862): Upload resource overuse stats on shutdown prepare.
             break;
-        case PowerCycle::POWER_CYCLE_SUSPEND:
-            ALOGI("Received SUSPEND power cycle");
+        case PowerCycle::POWER_CYCLE_SHUTDOWN_ENTER:
+            ALOGI("Received SHUTDOWN_ENTER power cycle");
             mWatchdogProcessService->setEnabled(/*isEnabled=*/false);
-            mWatchdogPerfService->setSystemState(SystemState::GARAGE_MODE);
             break;
         case PowerCycle::POWER_CYCLE_RESUME:
             ALOGI("Received RESUME power cycle");
             mWatchdogProcessService->setEnabled(/*isEnabled=*/true);
-            mWatchdogPerfService->setSystemState(SystemState::NORMAL_MODE);
             break;
         default:
             ALOGW("Unsupported power cycle: %d", powerCycle);
diff --git a/cpp/watchdog/server/tests/WatchdogInternalHandlerTest.cpp b/cpp/watchdog/server/tests/WatchdogInternalHandlerTest.cpp
index 92a4098..efa9eef 100644
--- a/cpp/watchdog/server/tests/WatchdogInternalHandlerTest.cpp
+++ b/cpp/watchdog/server/tests/WatchdogInternalHandlerTest.cpp
@@ -24,6 +24,7 @@
 
 #include <android-base/result.h>
 #include <android/automotive/watchdog/internal/BootPhase.h>
+#include <android/automotive/watchdog/internal/GarageMode.h>
 #include <android/automotive/watchdog/internal/PowerCycle.h>
 #include <android/automotive/watchdog/internal/UserState.h>
 #include <binder/IBinder.h>
@@ -41,6 +42,7 @@
 
 namespace aawi = ::android::automotive::watchdog::internal;
 
+using aawi::GarageMode;
 using aawi::ICarWatchdogServiceForSystem;
 using aawi::ICarWatchdogServiceForSystemDefault;
 using aawi::PackageResourceOveruseAction;
@@ -291,27 +293,27 @@
     ASSERT_FALSE(status.isOk()) << status;
 }
 
-TEST_F(WatchdogInternalHandlerTest, TestNotifyPowerCycleChangeToSuspend) {
+TEST_F(WatchdogInternalHandlerTest, TestNotifyPowerCycleChangeToShutdownPrepare) {
     setSystemCallingUid();
     EXPECT_CALL(*mMockWatchdogProcessService, setEnabled(/*isEnabled=*/false)).Times(1);
-    EXPECT_CALL(*mMockWatchdogPerfService, setSystemState(SystemState::GARAGE_MODE)).Times(1);
     Status status =
             mWatchdogInternalHandler
                     ->notifySystemStateChange(aawi::StateType::POWER_CYCLE,
-                                              static_cast<int32_t>(PowerCycle::POWER_CYCLE_SUSPEND),
+                                              static_cast<int32_t>(
+                                                      PowerCycle::POWER_CYCLE_SHUTDOWN_PREPARE),
                                               -1);
     ASSERT_TRUE(status.isOk()) << status;
 }
 
-TEST_F(WatchdogInternalHandlerTest, TestNotifyPowerCycleChangeToShutdown) {
+TEST_F(WatchdogInternalHandlerTest, TestNotifyPowerCycleChangeToShutdownEnter) {
     setSystemCallingUid();
     EXPECT_CALL(*mMockWatchdogProcessService, setEnabled(/*isEnabled=*/false)).Times(1);
-    EXPECT_CALL(*mMockWatchdogPerfService, setSystemState(_)).Times(0);
-    Status status = mWatchdogInternalHandler
-                            ->notifySystemStateChange(aawi::StateType::POWER_CYCLE,
-                                                      static_cast<int32_t>(
-                                                              PowerCycle::POWER_CYCLE_SHUTDOWN),
-                                                      -1);
+    Status status =
+            mWatchdogInternalHandler
+                    ->notifySystemStateChange(aawi::StateType::POWER_CYCLE,
+                                              static_cast<int32_t>(
+                                                      PowerCycle::POWER_CYCLE_SHUTDOWN_ENTER),
+                                              -1);
     ASSERT_TRUE(status.isOk()) << status;
 }
 
@@ -339,6 +341,28 @@
     ASSERT_FALSE(status.isOk()) << status;
 }
 
+TEST_F(WatchdogInternalHandlerTest, TestNotifyGarageModeOn) {
+    setSystemCallingUid();
+    EXPECT_CALL(*mMockWatchdogPerfService, setSystemState(SystemState::GARAGE_MODE)).Times(1);
+    Status status =
+            mWatchdogInternalHandler->notifySystemStateChange(aawi::StateType::GARAGE_MODE,
+                                                              static_cast<int32_t>(
+                                                                      GarageMode::GARAGE_MODE_ON),
+                                                              -1);
+    ASSERT_TRUE(status.isOk()) << status;
+}
+
+TEST_F(WatchdogInternalHandlerTest, TestNotifyGarageModeOff) {
+    setSystemCallingUid();
+    EXPECT_CALL(*mMockWatchdogPerfService, setSystemState(SystemState::NORMAL_MODE)).Times(1);
+    Status status =
+            mWatchdogInternalHandler->notifySystemStateChange(aawi::StateType::GARAGE_MODE,
+                                                              static_cast<int32_t>(
+                                                                      GarageMode::GARAGE_MODE_OFF),
+                                                              -1);
+    ASSERT_TRUE(status.isOk()) << status;
+}
+
 TEST_F(WatchdogInternalHandlerTest, TestNotifyUserStateChange) {
     setSystemCallingUid();
     aawi::StateType type = aawi::StateType::USER_STATE;
@@ -390,7 +414,8 @@
     Status status =
             mWatchdogInternalHandler
                     ->notifySystemStateChange(type,
-                                              static_cast<int32_t>(PowerCycle::POWER_CYCLE_SUSPEND),
+                                              static_cast<int32_t>(
+                                                      PowerCycle::POWER_CYCLE_SHUTDOWN_PREPARE),
                                               -1);
     ASSERT_FALSE(status.isOk()) << status;
 }
diff --git a/service/src/com/android/car/watchdog/CarWatchdogService.java b/service/src/com/android/car/watchdog/CarWatchdogService.java
index 86858d4..fccaa8b 100644
--- a/service/src/com/android/car/watchdog/CarWatchdogService.java
+++ b/service/src/com/android/car/watchdog/CarWatchdogService.java
@@ -22,6 +22,7 @@
 import static com.android.car.CarLog.TAG_WATCHDOG;
 
 import android.annotation.NonNull;
+import android.automotive.watchdog.internal.GarageMode;
 import android.automotive.watchdog.internal.ICarWatchdogServiceForSystem;
 import android.automotive.watchdog.internal.PackageInfo;
 import android.automotive.watchdog.internal.PackageIoOveruseStats;
@@ -39,7 +40,10 @@
 import android.car.watchdog.ResourceOveruseConfiguration;
 import android.car.watchdog.ResourceOveruseStats;
 import android.car.watchdoglib.CarWatchdogDaemonHelper;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.UserInfo;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -68,6 +72,10 @@
 public final class CarWatchdogService extends ICarWatchdogService.Stub implements CarServiceBase {
     static final boolean DEBUG = false; // STOPSHIP if true
     static final String TAG = CarLog.tagFor(CarWatchdogService.class);
+    static final String ACTION_GARAGE_MODE_ON =
+            "com.android.server.jobscheduler.GARAGE_MODE_ON";
+    static final String ACTION_GARAGE_MODE_OFF =
+            "com.android.server.jobscheduler.GARAGE_MODE_OFF";
 
     private final Context mContext;
     private final ICarWatchdogServiceForSystemImpl mWatchdogServiceForSystem;
@@ -76,6 +84,35 @@
     private final WatchdogPerfHandler mWatchdogPerfHandler;
     private final CarWatchdogDaemonHelper mCarWatchdogDaemonHelper;
     private final CarWatchdogDaemonHelper.OnConnectionChangeListener mConnectionListener;
+    /*
+     * TODO(b/192481350): Listen for GarageMode change notification rather than depending on the
+     *  system_server broadcast when the CarService internal API for listening GarageMode change is
+     *  implemented.
+     */
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            boolean isGarageMode = false;
+            if (action.equals(ACTION_GARAGE_MODE_ON)) {
+                isGarageMode = true;
+            } else if (!action.equals(ACTION_GARAGE_MODE_OFF)) {
+                return;
+            }
+            try {
+                mCarWatchdogDaemonHelper.notifySystemStateChange(StateType.GARAGE_MODE,
+                        isGarageMode ? GarageMode.GARAGE_MODE_ON : GarageMode.GARAGE_MODE_OFF,
+                        /* arg2= */ -1);
+                if (DEBUG) {
+                    Slogf.d(TAG, "Notified car watchdog daemon of garage mode(%s)",
+                            isGarageMode ? "ON" : "OFF");
+                }
+            } catch (RemoteException | RuntimeException e) {
+                Slogf.w(TAG, "Notifying garage mode state change failed: %s", e);
+            }
+        }
+    };
+
     private final Object mLock = new Object();
     @GuardedBy("mLock")
     private boolean mReadyToRespond;
@@ -105,6 +142,7 @@
         mWatchdogProcessHandler.init();
         subscribePowerCycleChange();
         subscribeUserStateChange();
+        subscribeBroadcastReceiver();
         mCarWatchdogDaemonHelper.addOnConnectionChangeListener(mConnectionListener);
         mCarWatchdogDaemonHelper.connect();
         mWatchdogPerfHandler.init();
@@ -119,6 +157,7 @@
 
     @Override
     public void release() {
+        mContext.unregisterReceiver(mBroadcastReceiver);
         mWatchdogPerfHandler.release();
         unregisterFromDaemon();
         mCarWatchdogDaemonHelper.disconnect();
@@ -355,8 +394,11 @@
                 switch (state) {
                     // SHUTDOWN_PREPARE covers suspend and shutdown.
                     case CarPowerStateListener.SHUTDOWN_PREPARE:
-                        powerCycle = PowerCycle.POWER_CYCLE_SUSPEND;
+                        powerCycle = PowerCycle.POWER_CYCLE_SHUTDOWN_PREPARE;
                         break;
+                    case CarPowerStateListener.SHUTDOWN_ENTER:
+                    case CarPowerStateListener.SUSPEND_ENTER:
+                        powerCycle = PowerCycle.POWER_CYCLE_SHUTDOWN_ENTER;
                     // ON covers resume.
                     case CarPowerStateListener.ON:
                         powerCycle = PowerCycle.POWER_CYCLE_RESUME;
@@ -371,10 +413,10 @@
                     mCarWatchdogDaemonHelper.notifySystemStateChange(StateType.POWER_CYCLE,
                             powerCycle, /* arg2= */ -1);
                     if (DEBUG) {
-                        Slogf.d(TAG, "Notified car watchdog daemon a power cycle(%d)", powerCycle);
+                        Slogf.d(TAG, "Notified car watchdog daemon of power cycle(%d)", powerCycle);
                     }
                 } catch (RemoteException | RuntimeException e) {
-                    Slogf.w(TAG, "Notifying system state change failed: %s", e);
+                    Slogf.w(TAG, "Notifying power cycle state change failed: %s", e);
                 }
             }
         });
@@ -412,11 +454,19 @@
                             userId, userStateDesc);
                 }
             } catch (RemoteException | RuntimeException e) {
-                Slogf.w(TAG, "Notifying system state change failed: %s", e);
+                Slogf.w(TAG, "Notifying user state change failed: %s", e);
             }
         });
     }
 
+    private void subscribeBroadcastReceiver() {
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ACTION_GARAGE_MODE_ON);
+        filter.addAction(ACTION_GARAGE_MODE_OFF);
+
+        mContext.registerReceiverForAllUsers(mBroadcastReceiver, filter, null, null);
+    }
+
     private static final class ICarWatchdogServiceForSystemImpl
             extends ICarWatchdogServiceForSystem.Stub {
         private final WeakReference<CarWatchdogService> mService;
diff --git a/tests/carservice_unit_test/src/android/car/watchdoglib/CarWatchdogDaemonHelperTest.java b/tests/carservice_unit_test/src/android/car/watchdoglib/CarWatchdogDaemonHelperTest.java
index c644f77..c67d3d8 100644
--- a/tests/carservice_unit_test/src/android/car/watchdoglib/CarWatchdogDaemonHelperTest.java
+++ b/tests/carservice_unit_test/src/android/car/watchdoglib/CarWatchdogDaemonHelperTest.java
@@ -153,10 +153,10 @@
     @Test
     public void testIndirectCall_NotifySystemStateChange() throws Exception {
         mCarWatchdogDaemonHelper.notifySystemStateChange(StateType.POWER_CYCLE,
-                PowerCycle.POWER_CYCLE_SUSPEND, -1);
+                PowerCycle.POWER_CYCLE_SHUTDOWN_PREPARE, -1);
 
         verify(mFakeCarWatchdog).notifySystemStateChange(StateType.POWER_CYCLE,
-                PowerCycle.POWER_CYCLE_SUSPEND, -1);
+                PowerCycle.POWER_CYCLE_SHUTDOWN_PREPARE, -1);
     }
 
     @Test
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceUnitTest.java b/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceUnitTest.java
index 0014d57..b12aa27 100644
--- a/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceUnitTest.java
@@ -49,6 +49,7 @@
 import android.automotive.watchdog.ResourceType;
 import android.automotive.watchdog.internal.ApplicationCategoryType;
 import android.automotive.watchdog.internal.ComponentType;
+import android.automotive.watchdog.internal.GarageMode;
 import android.automotive.watchdog.internal.ICarWatchdog;
 import android.automotive.watchdog.internal.ICarWatchdogServiceForSystem;
 import android.automotive.watchdog.internal.PackageIdentifier;
@@ -58,6 +59,7 @@
 import android.automotive.watchdog.internal.PackageResourceOveruseAction;
 import android.automotive.watchdog.internal.PerStateIoOveruseThreshold;
 import android.automotive.watchdog.internal.ResourceSpecificConfiguration;
+import android.automotive.watchdog.internal.StateType;
 import android.automotive.watchdog.internal.UidType;
 import android.car.test.mocks.AbstractExtendedMockitoTestCase;
 import android.car.watchdog.CarWatchdogManager;
@@ -70,7 +72,9 @@
 import android.car.watchdog.PerStateBytes;
 import android.car.watchdog.ResourceOveruseConfiguration;
 import android.car.watchdog.ResourceOveruseStats;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
@@ -128,6 +132,7 @@
     private CarWatchdogService mCarWatchdogService;
     private ICarWatchdogServiceForSystem mWatchdogServiceForSystemImpl;
     private IBinder.DeathRecipient mCarWatchdogDaemonBinderDeathRecipient;
+    private BroadcastReceiver mBroadcastReceiver;
     private final SparseArray<String> mPackageNamesByUids = new SparseArray<>();
     private final SparseArray<String[]> mSharedPackagesByUids = new SparseArray<>();
     private final ArrayMap<String, ApplicationInfo> mApplicationInfosByPackages = new ArrayMap<>();
@@ -153,6 +158,7 @@
         setupUsers();
         mCarWatchdogService.init();
         mWatchdogServiceForSystemImpl = registerCarWatchdogService();
+        captureBroadcastReceiver();
         captureDaemonBinderDeathRecipient();
         mockPackageManager();
     }
@@ -203,6 +209,24 @@
     }
 
     @Test
+    public void testGarageModeStateChangeToOn() throws Exception {
+        mBroadcastReceiver.onReceive(mMockContext,
+                new Intent().setAction(CarWatchdogService.ACTION_GARAGE_MODE_ON));
+        verify(mMockCarWatchdogDaemon)
+                .notifySystemStateChange(
+                        eq(StateType.GARAGE_MODE), eq(GarageMode.GARAGE_MODE_ON), eq(-1));
+    }
+
+    @Test
+    public void testGarageModeStateChangeToOff() throws Exception {
+        mBroadcastReceiver.onReceive(mMockContext,
+                new Intent().setAction(CarWatchdogService.ACTION_GARAGE_MODE_OFF));
+        verify(mMockCarWatchdogDaemon)
+                .notifySystemStateChange(
+                        eq(StateType.GARAGE_MODE), eq(GarageMode.GARAGE_MODE_OFF), eq(-1));
+    }
+
+    @Test
     public void testGetResourceOveruseStats() throws Exception {
         mPackageNamesByUids.put(Binder.getCallingUid(), mMockContext.getPackageName());
 
@@ -1255,6 +1279,16 @@
                 });
     }
 
+    private void captureBroadcastReceiver() {
+        ArgumentCaptor<BroadcastReceiver> receiverArgumentCaptor =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        verify(mMockContext)
+                .registerReceiverForAllUsers(receiverArgumentCaptor.capture(), any(), any(), any());
+        mBroadcastReceiver = receiverArgumentCaptor.getValue();
+        assertWithMessage("Broadcast receiver must be non-null").that(mBroadcastReceiver)
+                .isNotEqualTo(null);
+    }
+
     private void captureDaemonBinderDeathRecipient() throws Exception {
         ArgumentCaptor<IBinder.DeathRecipient> deathRecipientCaptor =
                 ArgumentCaptor.forClass(IBinder.DeathRecipient.class);