Merge "Add unit tests for SparseArrayStream." into sc-dev
diff --git a/car_product/build/preinstalled-packages-product-car-base.xml b/car_product/build/preinstalled-packages-product-car-base.xml
index 6745012..8b75bcb 100644
--- a/car_product/build/preinstalled-packages-product-car-base.xml
+++ b/car_product/build/preinstalled-packages-product-car-base.xml
@@ -307,18 +307,11 @@
     <install-in-user-type package="com.android.certinstaller">
         <install-in user-type="FULL" />
     </install-in-user-type>
-    <!-- TODO(b/189246976) STOPSHIP Remove for SYSTEM user since this package
-    is needed for SYSTEM user only if the device supports feature_device_admin
-    and it's used by some device owner APIs-->
     <install-in-user-type package="com.android.pacprocessor">
         <install-in user-type="FULL" />
-        <install-in user-type="SYSTEM" />
     </install-in-user-type>
-    <!-- TODO(b/189246976) STOPSHIP Remove for SYSTEM user same as previous
-    package -->
     <install-in-user-type package="com.android.proxyhandler">
         <install-in user-type="FULL" />
-        <install-in user-type="SYSTEM" />
     </install-in-user-type>
     <install-in-user-type package="com.android.vpndialogs">
         <install-in user-type="FULL" />
diff --git a/cpp/powerpolicy/server/carpowerpolicyd.xml b/cpp/powerpolicy/server/carpowerpolicyd.xml
index e8df893..0d2462f 100644
--- a/cpp/powerpolicy/server/carpowerpolicyd.xml
+++ b/cpp/powerpolicy/server/carpowerpolicyd.xml
@@ -16,6 +16,7 @@
 <manifest version="1.0" type="framework">
     <hal format="aidl">
         <name>android.frameworks.automotive.powerpolicy</name>
+        <version>1</version>
         <interface>
             <name>ICarPowerPolicyServer</name>
             <instance>default</instance>
diff --git a/cpp/watchdog/server/carwatchdogd.xml b/cpp/watchdog/server/carwatchdogd.xml
index c0c8933..c7adc27 100644
--- a/cpp/watchdog/server/carwatchdogd.xml
+++ b/cpp/watchdog/server/carwatchdogd.xml
@@ -16,6 +16,7 @@
 <manifest version="1.0" type="framework">
     <hal format="aidl">
         <name>android.automotive.watchdog</name>
+        <version>3</version>
         <interface>
             <name>ICarWatchdog</name>
             <instance>default</instance>
diff --git a/service/src/com/android/car/CanBusErrorNotifier.java b/service/src/com/android/car/CanBusErrorNotifier.java
deleted file mode 100644
index e4770af..0000000
--- a/service/src/com/android/car/CanBusErrorNotifier.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * 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.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.content.Context;
-import android.os.Build;
-import android.util.Log;
-import android.util.Slog;
-
-import com.android.internal.annotations.GuardedBy;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Class used to notify user about CAN bus failure.
- */
-final class CanBusErrorNotifier {
-    private static final String TAG = CarLog.tagFor(CanBusErrorNotifier.class);
-    private static final int NOTIFICATION_ID = 1;
-    private static final boolean IS_RELEASE_BUILD = "user".equals(Build.TYPE);
-
-    private final Context mContext;
-    private final NotificationManager mNotificationManager;
-
-    // Contains a set of objects that reported failure. The notification will be hidden only when
-    // this set is empty (all reported objects are in love and peace with the vehicle).
-    @GuardedBy("this")
-    private final Set<Object> mReportedObjects = new HashSet<>();
-
-    CanBusErrorNotifier(Context context) {
-        mNotificationManager = (NotificationManager) context.getSystemService(
-                Context.NOTIFICATION_SERVICE);
-        mContext = context;
-    }
-
-    public void removeFailureReport(Object sender) {
-        setCanBusFailure(false, sender);
-    }
-
-    public void reportFailure(Object sender) {
-        setCanBusFailure(true, sender);
-    }
-
-    private void setCanBusFailure(boolean failed, Object sender) {
-        boolean shouldShowNotification;
-        synchronized (this) {
-            boolean changed = failed
-                    ? mReportedObjects.add(sender) : mReportedObjects.remove(sender);
-
-            if (!changed) {
-                return;
-            }
-
-            shouldShowNotification = !mReportedObjects.isEmpty();
-        }
-
-        if (Log.isLoggable(TAG, Log.INFO)) {
-            Slog.i(TAG, "Changing CAN bus failure state to " + shouldShowNotification);
-        }
-
-        if (shouldShowNotification) {
-            showNotification();
-        } else {
-            hideNotification();
-        }
-    }
-
-    private void showNotification() {
-        if (IS_RELEASE_BUILD) {
-            // TODO: for user, we should show message to take car to the dealer. bug:32096297
-            return;
-        }
-        Notification notification =
-                new Notification.Builder(mContext, NotificationChannel.DEFAULT_CHANNEL_ID)
-                        .setContentTitle(mContext.getString(R.string.car_can_bus_failure))
-                        .setContentText(mContext.getString(R.string.car_can_bus_failure_desc))
-                        .setSmallIcon(R.drawable.car_ic_error)
-                        .setOngoing(true)
-                        .build();
-        mNotificationManager.notify(TAG, NOTIFICATION_ID, notification);
-    }
-
-    private void hideNotification() {
-        if (IS_RELEASE_BUILD) {
-            // TODO: for user, we should show message to take car to the dealer. bug:32096297
-            return;
-        }
-        mNotificationManager.cancel(TAG, NOTIFICATION_ID);
-    }
-}
diff --git a/service/src/com/android/car/CarService.java b/service/src/com/android/car/CarService.java
index 6a90f9c..7aa2cca 100644
--- a/service/src/com/android/car/CarService.java
+++ b/service/src/com/android/car/CarService.java
@@ -54,28 +54,11 @@
 
     private static final boolean IS_USER_BUILD = "user".equals(Build.TYPE);
 
-    private CanBusErrorNotifier mCanBusErrorNotifier;
     private ICarImpl mICarImpl;
     private IVehicle mVehicle;
 
     private String mVehicleInterfaceName;
 
-    // If 10 crashes of Vehicle HAL occurred within 10 minutes then thrown an exception in
-    // Car Service.
-    private final CrashTracker mVhalCrashTracker = new CrashTracker(
-            10,  // Max crash count.
-            10 * 60 * 1000,  // 10 minutes - sliding time window.
-            () -> {
-                if (IS_USER_BUILD) {
-                    Slog.e(CarLog.TAG_SERVICE, "Vehicle HAL keeps crashing, notifying user...");
-                    mCanBusErrorNotifier.reportFailure(CarService.this);
-                } else {
-                    throw new RuntimeException(
-                            "Vehicle HAL crashed too many times in a given time frame");
-                }
-            }
-    );
-
     private final VehicleDeathRecipient mVehicleDeathRecipient = new VehicleDeathRecipient();
 
     @Override
@@ -84,8 +67,6 @@
                 Trace.TRACE_TAG_SYSTEM_SERVER, CAR_SERVICE_INIT_TIMING_MIN_DURATION_MS);
         initTiming.traceBegin("CarService.onCreate");
 
-        mCanBusErrorNotifier = new CanBusErrorNotifier(this /* context */);
-
         initTiming.traceBegin("getVehicle");
         mVehicle = getVehicle();
         initTiming.traceEnd();
@@ -107,7 +88,6 @@
         mICarImpl = new ICarImpl(this,
                 mVehicle,
                 SystemInterface.Builder.defaultSystemInterface(this).build(),
-                mCanBusErrorNotifier,
                 mVehicleInterfaceName);
         mICarImpl.init();
 
@@ -129,7 +109,6 @@
         EventLog.writeEvent(EventLogTags.CAR_SERVICE_CREATE, mVehicle == null ? 0 : 1);
         Slog.i(CarLog.TAG_SERVICE, "Service onDestroy");
         mICarImpl.release();
-        mCanBusErrorNotifier.removeFailureReport(this);
 
         if (mVehicle != null) {
             try {
@@ -176,10 +155,6 @@
             vehicle = getVehicle();
         }
 
-        if (vehicle != null) {
-            mCanBusErrorNotifier.removeFailureReport(this);
-        }
-
         return vehicle;
     }
 
@@ -202,34 +177,8 @@
         @Override
         public void serviceDied(long cookie) {
             EventLog.writeEvent(EventLogTags.CAR_SERVICE_VHAL_DIED, cookie);
-            if (RESTART_CAR_SERVICE_WHEN_VHAL_CRASH) {
-                Slog.wtf(CarLog.TAG_SERVICE, "***Vehicle HAL died. Car service will restart***");
-                Process.killProcess(Process.myPid());
-                return;
-            }
-
-            Slog.wtf(CarLog.TAG_SERVICE, "***Vehicle HAL died.***");
-
-            try {
-                mVehicle.unlinkToDeath(this);
-            } catch (RemoteException e) {
-                Slog.e(CarLog.TAG_SERVICE, "Failed to unlinkToDeath", e); // Log and continue.
-            }
-            mVehicle = null;
-
-            mVhalCrashTracker.crashDetected();
-
-            Slog.i(CarLog.TAG_SERVICE,
-                    "Trying to reconnect to Vehicle HAL: " + mVehicleInterfaceName);
-            mVehicle = getVehicleWithTimeout(WAIT_FOR_VEHICLE_HAL_TIMEOUT_MS);
-            if (mVehicle == null) {
-                throw new IllegalStateException("Failed to reconnect to Vehicle HAL");
-            }
-
-            linkToDeath(mVehicle, this);
-
-            Slog.i(CarLog.TAG_SERVICE, "Notifying car service Vehicle HAL reconnected...");
-            mICarImpl.vehicleHalReconnected(mVehicle);
+            Slog.wtf(CarLog.TAG_SERVICE, "***Vehicle HAL died. Car service will restart***");
+            Process.killProcess(Process.myPid());
         }
     }
 
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index e2c2b08..78ff99d 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -159,15 +159,15 @@
     private final ICarSystemServerClientImpl mICarSystemServerClientImpl;
 
     public ICarImpl(Context serviceContext, IVehicle vehicle, SystemInterface systemInterface,
-            CanBusErrorNotifier errorNotifier, String vehicleInterfaceName) {
-        this(serviceContext, vehicle, systemInterface, errorNotifier, vehicleInterfaceName,
+            String vehicleInterfaceName) {
+        this(serviceContext, vehicle, systemInterface, vehicleInterfaceName,
                 /* carUserService= */ null, /* carWatchdogService= */ null,
                 /* powerPolicyDaemon= */ null);
     }
 
     @VisibleForTesting
     ICarImpl(Context serviceContext, IVehicle vehicle, SystemInterface systemInterface,
-            CanBusErrorNotifier errorNotifier, String vehicleInterfaceName,
+            String vehicleInterfaceName,
             @Nullable CarUserService carUserService,
             @Nullable CarWatchdogService carWatchdogService,
             @Nullable ICarPowerPolicySystemNotification powerPolicyDaemon) {
diff --git a/service/src/com/android/car/PerUserCarService.java b/service/src/com/android/car/PerUserCarService.java
index ee4dafe..6a9291a 100644
--- a/service/src/com/android/car/PerUserCarService.java
+++ b/service/src/com/android/car/PerUserCarService.java
@@ -76,7 +76,7 @@
 
         if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN)) {
             mPerUserCarDevicePolicyService = PerUserCarDevicePolicyService.getInstance(context);
-            mPerUserCarDevicePolicyService.registerBroadcastReceiver();
+            mPerUserCarDevicePolicyService.onCreate();
         } else if (DBG) {
             Slogf.d(TAG, "Not setting PerUserCarDevicePolicyService because device doesn't have %s",
                     PackageManager.FEATURE_DEVICE_ADMIN);
diff --git a/service/src/com/android/car/admin/NewUserDisclaimerActivity.java b/service/src/com/android/car/admin/NewUserDisclaimerActivity.java
index 4101c34..8a186a4 100644
--- a/service/src/com/android/car/admin/NewUserDisclaimerActivity.java
+++ b/service/src/com/android/car/admin/NewUserDisclaimerActivity.java
@@ -30,6 +30,7 @@
 import com.android.car.CarLog;
 import com.android.car.R;
 import com.android.car.admin.ui.ManagedDeviceTextView;
+import com.android.internal.annotations.VisibleForTesting;
 
 // TODO(b/171603586): STOPSHIP move UI related activities to CarSettings
 /**
@@ -65,6 +66,11 @@
         // and/or integrate it with UserNoticeService
     }
 
+    @VisibleForTesting
+    Button getAcceptButton() {
+        return mAcceptButton;
+    }
+
     private void accept() {
         if (DEBUG) Slog.d(TAG, "user accepted");
 
@@ -72,13 +78,8 @@
         finish();
     }
 
-    private static Intent newIntent(Context context) {
-        return new Intent(context, NewUserDisclaimerActivity.class);
-    }
-
     static void showNotification(Context context) {
-        PendingIntent pendingIntent = PendingIntent.getActivity(context, NOTIFICATION_ID,
-                newIntent(context), PendingIntent.FLAG_IMMUTABLE, null);
+        PendingIntent pendingIntent = getPendingIntent(context, /* extraFlags= */ 0);
 
         Notification notification = NotificationHelper
                 .newNotificationBuilder(context, NotificationManager.IMPORTANCE_DEFAULT)
@@ -104,7 +105,13 @@
                     + context.getUserId());
         }
         context.getSystemService(NotificationManager.class).cancel(NOTIFICATION_ID);
-        PendingIntent.getActivity(context, NOTIFICATION_ID, newIntent(context),
-                PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT).cancel();
+        getPendingIntent(context, PendingIntent.FLAG_UPDATE_CURRENT).cancel();
+    }
+
+    @VisibleForTesting
+    static PendingIntent getPendingIntent(Context context, int extraFlags) {
+        return PendingIntent.getActivity(context, NOTIFICATION_ID,
+                new Intent(context, NewUserDisclaimerActivity.class),
+                PendingIntent.FLAG_IMMUTABLE | extraFlags);
     }
 }
diff --git a/service/src/com/android/car/admin/PerUserCarDevicePolicyService.java b/service/src/com/android/car/admin/PerUserCarDevicePolicyService.java
index 4373a74..50ff4f7 100644
--- a/service/src/com/android/car/admin/PerUserCarDevicePolicyService.java
+++ b/service/src/com/android/car/admin/PerUserCarDevicePolicyService.java
@@ -16,6 +16,7 @@
 package com.android.car.admin;
 
 import static com.android.car.admin.CarDevicePolicyService.DEBUG;
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
 
 import android.annotation.IntDef;
 import android.app.admin.DevicePolicyManager;
@@ -28,12 +29,14 @@
 import android.util.Slog;
 
 import com.android.car.CarLog;
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
 
-// TODO(b/175057848) add unit tests
 /**
  * User-specific {@code CarDevicePolicyManagerService}.
  */
@@ -92,6 +95,8 @@
      * Gests the singleton instance, creating it if necessary.
      */
     public static PerUserCarDevicePolicyService getInstance(Context context) {
+        Objects.requireNonNull(context, "context cannot be null");
+
         synchronized (SLOCK) {
             if (sInstance == null) {
                 sInstance = new PerUserCarDevicePolicyService(context.getApplicationContext());
@@ -102,14 +107,15 @@
         }
     }
 
-    private PerUserCarDevicePolicyService(Context context) {
+    @VisibleForTesting
+    PerUserCarDevicePolicyService(Context context) {
         mContext = context;
     }
 
     /**
-     * Register a broadcast receiver to receive the proper events.
+     * Callback for when the service is created.
      */
-    public void registerBroadcastReceiver() {
+    public void onCreate() {
         if (DEBUG) Slog.d(TAG, "registering BroadcastReceiver");
 
         mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(
@@ -130,6 +136,7 @@
     /**
      * Dump its contents.
      */
+    @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
     public void dump(IndentingPrintWriter pw) {
         synchronized (SLOCK) {
             pw.printf("mNewUserDisclaimerStatus: %s\n",
@@ -166,7 +173,16 @@
         }
     }
 
-    private String newUserDisclaimerStatusToString(@NewUserDisclaimerStatus int status) {
+    @VisibleForTesting
+    @NewUserDisclaimerStatus
+    int getNewUserDisclaimerStatus() {
+        synchronized (SLOCK) {
+            return mNewUserDisclaimerStatus;
+        }
+    }
+
+    @VisibleForTesting
+    static String newUserDisclaimerStatusToString(@NewUserDisclaimerStatus int status) {
         return DebugUtils.constantToString(PerUserCarDevicePolicyService.class,
                 PREFIX_NEW_USER_DISCLAIMER_STATUS, status);
     }
diff --git a/tests/carservice_test/src/com/android/car/ICarImplTest.java b/tests/carservice_test/src/com/android/car/ICarImplTest.java
index 574b8ed..c0ba62a 100644
--- a/tests/carservice_test/src/com/android/car/ICarImplTest.java
+++ b/tests/carservice_test/src/com/android/car/ICarImplTest.java
@@ -165,7 +165,7 @@
         doThrow(new NullPointerException()).when(mContext).getDataDir();
 
         ICarImpl carImpl = new ICarImpl(mContext, mMockVehicle, mFakeSystemInterface,
-                /* errorNotifier= */ null, "MockedCar", /* carUserService= */ null,
+                "MockedCar", /* carUserService= */ null,
                 mCarWatchdogService, new MockedCarTestBase.FakeCarPowerPolicyDaemon());
         carImpl.init();
         Car mCar = new Car(mContext, carImpl, /* handler= */ null);
diff --git a/tests/carservice_test/src/com/android/car/MockedCarTestBase.java b/tests/carservice_test/src/com/android/car/MockedCarTestBase.java
index 9794b19..4119b8c 100644
--- a/tests/carservice_test/src/com/android/car/MockedCarTestBase.java
+++ b/tests/carservice_test/src/com/android/car/MockedCarTestBase.java
@@ -232,7 +232,7 @@
         // This should be done here as feature property is accessed inside the constructor.
         initMockedHal();
         mCarImpl = new ICarImpl(mMockedCarTestContext, mMockedVehicleHal, mFakeSystemInterface,
-                /* errorNotifier= */ null , "MockedCar", mCarUserService, mCarWatchdogService,
+                "MockedCar", mCarUserService, mCarWatchdogService,
                 mPowerPolicyDaemon);
 
         spyOnBeforeCarImplInit();
diff --git a/tests/carservice_test/src/com/android/car/garagemode/GarageModeTest.java b/tests/carservice_test/src/com/android/car/garagemode/GarageModeTest.java
index 499a95f..6133baf 100644
--- a/tests/carservice_test/src/com/android/car/garagemode/GarageModeTest.java
+++ b/tests/carservice_test/src/com/android/car/garagemode/GarageModeTest.java
@@ -98,10 +98,12 @@
         ArrayList<Integer> userToStartInBackground = new ArrayList<>(Arrays.asList(101, 102, 103));
         when(mCarUserService.startAllBackgroundUsersInGarageMode())
                 .thenReturn(userToStartInBackground);
-        mGarageMode.enterGarageMode(/* future= */ null);
+
         CountDownLatch latch = new CountDownLatch(3); // 3 for three users
         mockCarUserServiceStopUserCall(getEventListener(), latch);
 
+        mGarageMode.enterGarageMode(/* future= */ null);
+
         mGarageMode.cancel();
 
         waitForHandlerThreadToFinish(latch);
diff --git a/tests/carservice_unit_test/src/com/android/car/admin/NewUserDisclaimerActivityTest.java b/tests/carservice_unit_test/src/com/android/car/admin/NewUserDisclaimerActivityTest.java
new file mode 100644
index 0000000..b1e8b72
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/admin/NewUserDisclaimerActivityTest.java
@@ -0,0 +1,188 @@
+/*
+ * 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 com.android.car.admin;
+
+import static android.app.Notification.EXTRA_TEXT;
+import static android.app.Notification.EXTRA_TITLE;
+import static android.app.Notification.FLAG_ONGOING_EVENT;
+
+import static com.android.car.admin.NotificationHelper.CHANNEL_ID_DEFAULT;
+import static com.android.car.admin.NotificationHelper.NEW_USER_DISCLAIMER_NOTIFICATION_ID;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.UiAutomation;
+import android.car.test.mocks.AbstractExtendedMockitoTestCase;
+import android.car.test.mocks.JavaMockitoHelper;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+import android.widget.Button;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.car.R;
+import com.android.car.admin.ui.ManagedDeviceTextView;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import java.util.concurrent.CountDownLatch;
+
+@RunWith(AndroidJUnit4.class)
+public final class NewUserDisclaimerActivityTest extends AbstractExtendedMockitoTestCase {
+
+    private static final String TAG = NewUserDisclaimerActivityTest.class.getSimpleName();
+
+    private static final long TIMEOUT_MS = 1_000;
+
+    private final Context mRealContext = InstrumentationRegistry.getInstrumentation()
+            .getContext();
+
+    private final UiAutomation mUiAutomation =
+            InstrumentationRegistry.getInstrumentation().getUiAutomation();
+
+    // NOTE: Cannot launch activity automatically as we need to mock
+    // PerUserCarDevicePolicyService.getInstance() first
+    @Rule
+    public ActivityTestRule<NewUserDisclaimerActivity> mActivityRule = new ActivityTestRule(
+            NewUserDisclaimerActivity.class,  /* initialTouchMode= */ false,
+            /* launchActivity= */ false);
+
+    private NewUserDisclaimerActivity mActivity;
+
+    private Context mSpiedContext;
+
+    @Mock
+    private PerUserCarDevicePolicyService mService;
+
+    @Mock
+    private NotificationManager mNotificationManager;
+
+    @Override
+    protected void onSessionBuilder(CustomMockitoSessionBuilder session) {
+        session.spyStatic(PerUserCarDevicePolicyService.class);
+    }
+
+    @Before
+    public void setFixtures() {
+        Log.v(TAG, "setFixtures(): mocking PerUserCarDevicePolicyService.getInstance()");
+        doReturn(mService).when(() -> PerUserCarDevicePolicyService.getInstance(any()));
+        mSpiedContext = spy(mRealContext);
+
+        when(mSpiedContext.getSystemService(NotificationManager.class))
+                .thenReturn(mNotificationManager);
+
+        Log.v(TAG, "setFixtures(): launching activitiy");
+        mActivity = mActivityRule.launchActivity(/* intent= */ null);
+
+        // It's called onResume()
+        verify(mService).setShown();
+    }
+
+    @Test
+    public void testAccept() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        mActivity.runOnUiThread(() -> {
+            mActivity.onCreate(/* savedInstanceState= */ null);
+            Button button = mActivity.getAcceptButton();
+            Log.d(TAG, "Clicking accept button: " + button);
+            button.performClick();
+            latch.countDown();
+        });
+        JavaMockitoHelper.await(latch, TIMEOUT_MS);
+
+        verify(mService).setAcknowledged();
+        assertWithMessage("activity is finishing").that(mActivity.isFinishing()).isTrue();
+    }
+
+    @Test
+    public void testShowNotification() {
+        NewUserDisclaimerActivity.showNotification(mSpiedContext);
+
+        ArgumentCaptor<Notification> captor = ArgumentCaptor.forClass(Notification.class);
+        verify(mNotificationManager).notify(eq(NEW_USER_DISCLAIMER_NOTIFICATION_ID),
+                captor.capture());
+
+        Notification notification = captor.getValue();
+        assertWithMessage("notification").that(notification).isNotNull();
+        assertNotificationContents(notification);
+    }
+
+    @Test
+    public void testCancelNotification() throws Exception {
+        PendingIntent pendingIntent = NewUserDisclaimerActivity.getPendingIntent(mSpiedContext,
+                /* extraFlags = */ 0);
+        CountDownLatch cancelLatch = new CountDownLatch(1);
+        pendingIntent.registerCancelListener(pi -> cancelLatch.countDown());
+
+        NewUserDisclaimerActivity.cancelNotification(mSpiedContext);
+
+        verify(mNotificationManager).cancel(NEW_USER_DISCLAIMER_NOTIFICATION_ID);
+
+        // Assert pending intent was canceled (latch is counted down by the CancelListener)
+        JavaMockitoHelper.await(cancelLatch, TIMEOUT_MS);
+    }
+
+    private void assertNotificationContents(Notification notification) {
+        assertWithMessage("notification icon").that(notification.getSmallIcon()).isNotNull();
+        assertWithMessage("notification channel").that(notification.getChannelId())
+                .isEqualTo(CHANNEL_ID_DEFAULT);
+        assertWithMessage("notification flags has FLAG_ONGOING_EVENT")
+                .that(notification.flags & FLAG_ONGOING_EVENT).isEqualTo(FLAG_ONGOING_EVENT);
+
+        assertWithMessage("notification content pending intent")
+                .that(notification.contentIntent)
+                .isNotNull();
+        assertWithMessage("notification content pending intent is immutable")
+                .that(notification.contentIntent.isImmutable()).isTrue();
+        // Need android.permission.GET_INTENT_SENDER_INTENT to get the Intent
+        Intent intent;
+        mUiAutomation.adoptShellPermissionIdentity();
+        try {
+            intent = notification.contentIntent.getIntent();
+        } finally {
+            mUiAutomation.dropShellPermissionIdentity();
+        }
+        assertWithMessage("content intent").that(intent).isNotNull();
+        assertWithMessage("content intent component").that(intent.getComponent())
+                .isEqualTo(mActivity.getComponentName());
+
+        assertWithMessage("notification extras").that(notification.extras).isNotNull();
+        assertWithMessage("value of extra %s", EXTRA_TITLE)
+                .that(notification.extras.getString(EXTRA_TITLE))
+                .isEqualTo(mRealContext.getString(R.string.new_user_managed_notification_title));
+        assertWithMessage("value of extra %s", EXTRA_TEXT)
+                .that(notification.extras.getString(EXTRA_TEXT))
+                .isEqualTo(ManagedDeviceTextView.getManagedDeviceText(mRealContext));
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/admin/NotificationHelperTest.java b/tests/carservice_unit_test/src/com/android/car/admin/NotificationHelperTest.java
index c4c481f..8610265 100644
--- a/tests/carservice_unit_test/src/com/android/car/admin/NotificationHelperTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/admin/NotificationHelperTest.java
@@ -30,7 +30,6 @@
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnitRunner;
 
-
 @RunWith(MockitoJUnitRunner.class)
 public final class NotificationHelperTest {
 
diff --git a/tests/carservice_unit_test/src/com/android/car/admin/NotificationHelperValidImportanceTest.java b/tests/carservice_unit_test/src/com/android/car/admin/NotificationHelperValidImportanceTest.java
index daefcaf..7997954 100644
--- a/tests/carservice_unit_test/src/com/android/car/admin/NotificationHelperValidImportanceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/admin/NotificationHelperValidImportanceTest.java
@@ -62,7 +62,7 @@
     @Rule
     public final MockitoRule mMockitoRule = MockitoJUnit.rule();
 
-    private Context mRealContext = InstrumentationRegistry.getInstrumentation().getContext();
+    private final Context mRealContext = InstrumentationRegistry.getInstrumentation().getContext();
 
     private Context mSpiedContext;
 
diff --git a/tests/carservice_unit_test/src/com/android/car/admin/PerUserCarDevicePolicyServiceTest.java b/tests/carservice_unit_test/src/com/android/car/admin/PerUserCarDevicePolicyServiceTest.java
new file mode 100644
index 0000000..796afd5
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/admin/PerUserCarDevicePolicyServiceTest.java
@@ -0,0 +1,166 @@
+/*
+ * 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 com.android.car.admin;
+
+import static android.app.admin.DevicePolicyManager.ACTION_SHOW_NEW_USER_DISCLAIMER;
+
+import static com.android.car.admin.PerUserCarDevicePolicyService.NEW_USER_DISCLAIMER_STATUS_ACKED;
+import static com.android.car.admin.PerUserCarDevicePolicyService.NEW_USER_DISCLAIMER_STATUS_NEVER_RECEIVED;
+import static com.android.car.admin.PerUserCarDevicePolicyService.NEW_USER_DISCLAIMER_STATUS_NOTIFICATION_SENT;
+import static com.android.car.admin.PerUserCarDevicePolicyService.NEW_USER_DISCLAIMER_STATUS_RECEIVED;
+import static com.android.car.admin.PerUserCarDevicePolicyService.NEW_USER_DISCLAIMER_STATUS_SHOWN;
+import static com.android.car.admin.PerUserCarDevicePolicyService.newUserDisclaimerStatusToString;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.expectThrows;
+
+import android.app.admin.DevicePolicyManager;
+import android.car.test.mocks.AbstractExtendedMockitoTestCase;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import com.android.car.admin.PerUserCarDevicePolicyService.NewUserDisclaimerStatus;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+public final class PerUserCarDevicePolicyServiceTest extends AbstractExtendedMockitoTestCase {
+
+    @Mock
+    private Context mContext;
+
+    private PerUserCarDevicePolicyService mInstance;
+
+    @Mock
+    private DevicePolicyManager mDpm;
+
+    @Override
+    protected void onSessionBuilder(CustomMockitoSessionBuilder session) {
+        session.spyStatic(NewUserDisclaimerActivity.class);
+    }
+
+    @Before
+    public void setFixtures() {
+        when(mContext.getApplicationContext()).thenReturn(mContext);
+        when(mContext.getSystemService(DevicePolicyManager.class)).thenReturn(mDpm);
+
+        mInstance = new PerUserCarDevicePolicyService(mContext);
+        assertStatusString(NEW_USER_DISCLAIMER_STATUS_NEVER_RECEIVED);
+    }
+
+    @Test
+    public void testGetInstance() {
+        PerUserCarDevicePolicyService instance1 = PerUserCarDevicePolicyService
+                .getInstance(mContext);
+        assertWithMessage("getInstance()#1").that(instance1).isNotNull();
+        assertWithMessage("getInstance()#1").that(instance1).isNotSameInstanceAs(mInstance);
+
+        PerUserCarDevicePolicyService instance2 = PerUserCarDevicePolicyService
+                .getInstance(mContext);
+        assertWithMessage("getInstance()#2").that(instance2).isNotNull();
+        assertWithMessage("getInstance()#2").that(instance2).isNotSameInstanceAs(mInstance);
+
+        assertWithMessage("getInstance()#2").that(instance2).isSameInstanceAs(instance1);
+        assertWithMessage("getInstance()#1").that(instance1).isSameInstanceAs(instance2);
+    }
+
+    @Test
+    public void testGetInstance_nullContext() {
+        NullPointerException exception = expectThrows(NullPointerException.class,
+                () -> PerUserCarDevicePolicyService.getInstance(null));
+        assertWithMessage("exception message").that(exception.getMessage()).contains("context");
+    }
+
+    @Test
+    public void testCreateAndDestroy() {
+        BroadcastReceiver receiver = callOnCreate();
+
+        callOnDestroy(receiver);
+    }
+
+    @Test
+    public void testShowWhenIntentReceived() {
+        doAnswer((inv) -> {
+            assertStatusString(NEW_USER_DISCLAIMER_STATUS_RECEIVED);
+            return null;
+        }).when(() -> NewUserDisclaimerActivity.showNotification(any()));
+        BroadcastReceiver receiver  = callOnCreate();
+
+        sendShowNewUserDisclaimerBroadcast(receiver);
+
+        assertStatusString(NEW_USER_DISCLAIMER_STATUS_NOTIFICATION_SENT);
+        verify(() -> NewUserDisclaimerActivity.showNotification(mContext));
+    }
+
+    @Test
+    public void testSetShown() {
+        mInstance.setShown();
+
+        assertStatusString(NEW_USER_DISCLAIMER_STATUS_SHOWN);
+    }
+
+    @Test
+    public void testSetAcknowledged() {
+        doNothing().when(() -> NewUserDisclaimerActivity.cancelNotification(any()));
+
+        mInstance.setAcknowledged();
+
+        assertStatusString(NEW_USER_DISCLAIMER_STATUS_ACKED);
+        verify(() -> NewUserDisclaimerActivity.cancelNotification(mContext));
+
+        verify(mDpm).resetNewUserDisclaimer();
+    }
+
+    private BroadcastReceiver callOnCreate() {
+        ArgumentCaptor<BroadcastReceiver> captor = ArgumentCaptor.forClass(BroadcastReceiver.class);
+
+        mInstance.onCreate();
+
+        verify(mContext).registerReceiver(captor.capture(), any());
+        BroadcastReceiver receiver = captor.getValue();
+        assertWithMessage("BroadcastReceiver captured on onCreate()").that(receiver).isNotNull();
+
+        return receiver;
+    }
+
+    private void callOnDestroy(BroadcastReceiver receiver) {
+        mInstance.onDestroy();
+
+        verify(mContext).unregisterReceiver(receiver);
+    }
+
+    private void sendShowNewUserDisclaimerBroadcast(BroadcastReceiver receiver) {
+        receiver.onReceive(mContext, new Intent(ACTION_SHOW_NEW_USER_DISCLAIMER));
+    }
+
+    private void assertStatusString(@NewUserDisclaimerStatus int expectedStatus) {
+        int actualStatus = mInstance.getNewUserDisclaimerStatus();
+        assertWithMessage("newUserDisclaimerStatus (%s=%s, %s=%s)",
+                expectedStatus, newUserDisclaimerStatusToString(expectedStatus),
+                actualStatus, newUserDisclaimerStatusToString(actualStatus))
+                        .that(actualStatus).isEqualTo(expectedStatus);
+    }
+}