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