Add unit tests for RetailDemoModeService.
Bug: 30571518
Change-Id: I33b1ae9f22a609088c0ad0cd247fe91ccc23bdaa
diff --git a/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java b/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java
index 6b273210..1612b99 100644
--- a/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java
+++ b/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java
@@ -26,7 +26,6 @@
import android.app.RetailDemoModeServiceInternal;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
-import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
@@ -63,6 +62,7 @@
import com.android.internal.os.BackgroundThread;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.LocalServices;
@@ -81,7 +81,8 @@
private static final String DEMO_USER_NAME = "Demo";
private static final String ACTION_RESET_DEMO =
"com.android.server.retaildemo.ACTION_RESET_DEMO";
- private static final String SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED = "sys.retaildemo.enabled";
+ @VisibleForTesting
+ static final String SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED = "sys.retaildemo.enabled";
private static final int MSG_TURN_SCREEN_ON = 0;
private static final int MSG_INACTIVITY_TIME_OUT = 1;
@@ -93,7 +94,8 @@
private static final long WARNING_DIALOG_TIMEOUT_DEFAULT = 0;
private static final long MILLIS_PER_SECOND = 1000;
- private static final int[] VOLUME_STREAMS_TO_MUTE = {
+ @VisibleForTesting
+ static final int[] VOLUME_STREAMS_TO_MUTE = {
AudioSystem.STREAM_RING,
AudioSystem.STREAM_MUSIC
};
@@ -106,19 +108,10 @@
int mCurrentUserId = UserHandle.USER_SYSTEM;
long mUserInactivityTimeout;
long mWarningDialogTimeout;
- private ActivityManagerService mAms;
- private ActivityManagerInternal mAmi;
- private AudioManager mAudioManager;
- private NotificationManager mNm;
- private UserManager mUm;
- private PowerManager mPm;
- private PowerManager.WakeLock mWakeLock;
+ private Injector mInjector;
Handler mHandler;
private ServiceThread mHandlerThread;
- private PendingIntent mResetDemoPendingIntent;
- private CameraManager mCameraManager;
private String[] mCameraIdsWithFlash;
- private Configuration mSystemUserConfiguration;
private PreloadAppsInstaller mPreloadAppsInstaller;
final Object mActivityLock = new Object();
@@ -158,10 +151,10 @@
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_TURN_SCREEN_ON:
- if (mWakeLock.isHeld()) {
- mWakeLock.release();
+ if (mInjector.isWakeLockHeld()) {
+ mInjector.releaseWakeLock();
}
- mWakeLock.acquire();
+ mInjector.acquireWakeLock();
break;
case MSG_INACTIVITY_TIME_OUT:
if (isDemoLauncherDisabled()) {
@@ -178,18 +171,19 @@
if (mCurrentUserId != UserHandle.USER_SYSTEM) {
logSessionDuration();
}
- final UserInfo demoUser = getUserManager().createUser(DEMO_USER_NAME,
+ final UserInfo demoUser = mInjector.getUserManager().createUser(DEMO_USER_NAME,
UserInfo.FLAG_DEMO | UserInfo.FLAG_EPHEMERAL);
if (demoUser != null) {
setupDemoUser(demoUser);
- getActivityManager().switchUser(demoUser.id);
+ mInjector.switchUser(demoUser.id);
}
break;
}
}
}
- private class SettingsObserver extends ContentObserver {
+ @VisibleForTesting
+ class SettingsObserver extends ContentObserver {
private final static String KEY_USER_INACTIVITY_TIMEOUT = "user_inactivity_timeout_ms";
private final static String KEY_WARNING_DIALOG_TIMEOUT = "warning_dialog_timeout_ms";
@@ -208,7 +202,7 @@
}
public void register() {
- ContentResolver cr = getContext().getContentResolver();
+ ContentResolver cr = mInjector.getContentResolver();
cr.registerContentObserver(mDeviceDemoModeUri, false, this, UserHandle.USER_SYSTEM);
cr.registerContentObserver(mDeviceProvisionedUri, false, this, UserHandle.USER_SYSTEM);
cr.registerContentObserver(mRetailDemoConstantsUri, false, this,
@@ -226,9 +220,9 @@
if (mDeviceInDemoMode) {
putDeviceInDemoMode();
} else {
- SystemProperties.set(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED, "0");
- if (mWakeLock.isHeld()) {
- mWakeLock.release();
+ mInjector.systemPropertiesSet(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED, "0");
+ if (mInjector.isWakeLockHeld()) {
+ mInjector.releaseWakeLock();
}
}
}
@@ -248,8 +242,8 @@
private void refreshTimeoutConstants() {
try {
- mParser.setString(Settings.Global.getString(getContext().getContentResolver(),
- Settings.Global.RETAIL_DEMO_MODE_CONSTANTS));
+ mParser.setString(Settings.Global.getString(mInjector.getContentResolver(),
+ Settings.Global.RETAIL_DEMO_MODE_CONSTANTS));
} catch (IllegalArgumentException exc) {
Slog.e(TAG, "Invalid string passed to KeyValueListParser");
// Consuming the exception to fall back to default values.
@@ -282,35 +276,21 @@
}
public RetailDemoModeService(Context context) {
- super(context);
+ this(new Injector(context));
+ }
+
+ @VisibleForTesting
+ RetailDemoModeService(Injector injector) {
+ super(injector.getContext());
+
+ mInjector = injector;
synchronized (mActivityLock) {
mFirstUserActivityTime = mLastUserActivityTime = SystemClock.uptimeMillis();
}
}
- private Notification createResetNotification() {
- return new Notification.Builder(getContext())
- .setContentTitle(getContext().getString(R.string.reset_retail_demo_mode_title))
- .setContentText(getContext().getString(R.string.reset_retail_demo_mode_text))
- .setOngoing(true)
- .setSmallIcon(R.drawable.platlogo)
- .setShowWhen(false)
- .setVisibility(Notification.VISIBILITY_PUBLIC)
- .setContentIntent(getResetDemoPendingIntent())
- .setColor(getContext().getColor(R.color.system_notification_accent_color))
- .build();
- }
-
- private PendingIntent getResetDemoPendingIntent() {
- if (mResetDemoPendingIntent == null) {
- Intent intent = new Intent(ACTION_RESET_DEMO);
- mResetDemoPendingIntent = PendingIntent.getBroadcast(getContext(), 0, intent, 0);
- }
- return mResetDemoPendingIntent;
- }
-
boolean isDemoLauncherDisabled() {
- IPackageManager pm = AppGlobals.getPackageManager();
+ IPackageManager pm = mInjector.getIPackageManager();
int enabledState = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
String demoLauncherComponent = getContext().getResources()
.getString(R.string.config_demoModeLauncherComponent);
@@ -325,7 +305,7 @@
}
private void setupDemoUser(UserInfo userInfo) {
- UserManager um = getUserManager();
+ UserManager um = mInjector.getUserManager();
UserHandle user = UserHandle.of(userInfo.id);
um.setUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, true, user);
um.setUserRestriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, true, user);
@@ -336,19 +316,19 @@
// Set this to false because the default is true on user creation
um.setUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS, false, user);
// Disallow rebooting in safe mode - controlled by user 0
- getUserManager().setUserRestriction(UserManager.DISALLOW_SAFE_BOOT, true,
- UserHandle.SYSTEM);
- Settings.Secure.putIntForUser(getContext().getContentResolver(),
+ um.setUserRestriction(UserManager.DISALLOW_SAFE_BOOT, true, UserHandle.SYSTEM);
+ Settings.Secure.putIntForUser(mInjector.getContentResolver(),
Settings.Secure.SKIP_FIRST_USE_HINTS, 1, userInfo.id);
- Settings.Global.putInt(getContext().getContentResolver(),
+ Settings.Global.putInt(mInjector.getContentResolver(),
Settings.Global.PACKAGE_VERIFIER_ENABLE, 0);
+
grantRuntimePermissionToCamera(user);
clearPrimaryCallLog();
}
private void grantRuntimePermissionToCamera(UserHandle user) {
final Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
- final PackageManager pm = getContext().getPackageManager();
+ final PackageManager pm = mInjector.getPackageManager();
final ResolveInfo handler = pm.resolveActivityAsUser(cameraIntent,
PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
user.getIdentifier());
@@ -364,7 +344,7 @@
}
private void clearPrimaryCallLog() {
- final ContentResolver resolver = getContext().getContentResolver();
+ final ContentResolver resolver = mInjector.getContentResolver();
// Deleting primary user call log so that it doesn't get copied to the new demo user
final Uri uri = CallLog.Calls.CONTENT_URI;
@@ -380,37 +360,16 @@
synchronized (mActivityLock) {
sessionDuration = (int) ((mLastUserActivityTime - mFirstUserActivityTime) / 1000);
}
- MetricsLogger.histogram(getContext(), DEMO_SESSION_DURATION, sessionDuration);
- }
-
- private ActivityManagerService getActivityManager() {
- if (mAms == null) {
- mAms = (ActivityManagerService) ActivityManagerNative.getDefault();
- }
- return mAms;
- }
-
- private UserManager getUserManager() {
- if (mUm == null) {
- mUm = getContext().getSystemService(UserManager.class);
- }
- return mUm;
- }
-
- private AudioManager getAudioManager() {
- if (mAudioManager == null) {
- mAudioManager = getContext().getSystemService(AudioManager.class);
- }
- return mAudioManager;
+ mInjector.logSessionDuration(sessionDuration);
}
private boolean isDeviceProvisioned() {
return Settings.Global.getInt(
- getContext().getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+ mInjector.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0;
}
private boolean deletePreloadsFolderContents() {
- final File dir = Environment.getDataPreloadsDirectory();
+ final File dir = mInjector.getDataPreloadsDirectory();
Slog.i(TAG, "Deleting contents of " + dir);
return FileUtils.deleteContents(dir);
}
@@ -424,46 +383,31 @@
private String[] getCameraIdsWithFlash() {
ArrayList<String> cameraIdsList = new ArrayList<String>();
- try {
- for (String cameraId : mCameraManager.getCameraIdList()) {
- CameraCharacteristics c = mCameraManager.getCameraCharacteristics(cameraId);
- if (Boolean.TRUE.equals(c.get(CameraCharacteristics.FLASH_INFO_AVAILABLE))) {
- cameraIdsList.add(cameraId);
+ final CameraManager cm = mInjector.getCameraManager();
+ if (cm != null) {
+ try {
+ for (String cameraId : cm.getCameraIdList()) {
+ CameraCharacteristics c = cm.getCameraCharacteristics(cameraId);
+ if (Boolean.TRUE.equals(c.get(CameraCharacteristics.FLASH_INFO_AVAILABLE))) {
+ cameraIdsList.add(cameraId);
+ }
}
+ } catch (CameraAccessException e) {
+ Slog.e(TAG, "Unable to access camera while getting camera id list", e);
}
- } catch (CameraAccessException e) {
- Slog.e(TAG, "Unable to access camera while getting camera id list", e);
}
return cameraIdsList.toArray(new String[cameraIdsList.size()]);
}
- private void turnOffAllFlashLights() {
- for (String cameraId : mCameraIdsWithFlash) {
- try {
- mCameraManager.setTorchMode(cameraId, false);
- } catch (CameraAccessException e) {
- Slog.e(TAG, "Unable to access camera " + cameraId + " while turning off flash", e);
- }
- }
- }
-
private void muteVolumeStreams() {
for (int stream : VOLUME_STREAMS_TO_MUTE) {
- getAudioManager().setStreamVolume(stream, getAudioManager().getStreamMinVolume(stream),
- 0);
+ mInjector.getAudioManager().setStreamVolume(stream,
+ mInjector.getAudioManager().getStreamMinVolume(stream), 0);
}
}
- private Configuration getSystemUsersConfiguration() {
- if (mSystemUserConfiguration == null) {
- Settings.System.getConfiguration(getContext().getContentResolver(),
- mSystemUserConfiguration = new Configuration());
- }
- return mSystemUserConfiguration;
- }
-
private void putDeviceInDemoMode() {
- SystemProperties.set(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED, "1");
+ mInjector.systemPropertiesSet(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED, "1");
mHandler.sendEmptyMessage(MSG_START_NEW_SESSION);
}
@@ -483,16 +427,8 @@
public void onBootPhase(int bootPhase) {
switch (bootPhase) {
case PHASE_THIRD_PARTY_APPS_CAN_START:
- mPreloadAppsInstaller = new PreloadAppsInstaller(getContext());
- mPm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
- mAmi = LocalServices.getService(ActivityManagerInternal.class);
- mWakeLock = mPm
- .newWakeLock(
- PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP,
- TAG);
- mNm = NotificationManager.from(getContext());
- mCameraManager = (CameraManager) getContext()
- .getSystemService(Context.CAMERA_SERVICE);
+ mPreloadAppsInstaller = mInjector.getPreloadAppsInstaller();
+ mInjector.initializeWakeLock();
mCameraIdsWithFlash = getCameraIdsWithFlash();
SettingsObserver settingsObserver = new SettingsObserver(mHandler);
settingsObserver.register();
@@ -516,27 +452,28 @@
if (DEBUG) {
Slog.d(TAG, "onSwitchUser: " + userId);
}
- final UserInfo ui = getUserManager().getUserInfo(userId);
+ final UserInfo ui = mInjector.getUserManager().getUserInfo(userId);
if (!ui.isDemo()) {
Slog.wtf(TAG, "Should not allow switch to non-demo user in demo mode");
return;
}
- if (!mWakeLock.isHeld()) {
- mWakeLock.acquire();
+ if (!mInjector.isWakeLockHeld()) {
+ mInjector.acquireWakeLock();
}
mCurrentUserId = userId;
- mAmi.updatePersistentConfigurationForUser(getSystemUsersConfiguration(), userId);
- turnOffAllFlashLights();
+ mInjector.getActivityManagerInternal().updatePersistentConfigurationForUser(
+ mInjector.getSystemUsersConfiguration(), userId);
+ mInjector.turnOffAllFlashLights(mCameraIdsWithFlash);
muteVolumeStreams();
// Disable lock screen for demo users.
- LockPatternUtils lockPatternUtils = new LockPatternUtils(getContext());
- lockPatternUtils.setLockScreenDisabled(true, userId);
- mNm.notifyAsUser(TAG, 1, createResetNotification(), UserHandle.of(userId));
+ mInjector.getLockPatternUtils().setLockScreenDisabled(true, userId);
+ mInjector.getNotificationManager().notifyAsUser(TAG,
+ 1, mInjector.createResetNotification(), UserHandle.of(userId));
synchronized (mActivityLock) {
mUserUntouched = true;
}
- MetricsLogger.count(getContext(), DEMO_SESSION_COUNT, 1);
+ mInjector.logSessionCount(1);
mHandler.removeMessages(MSG_INACTIVITY_TIME_OUT);
mHandler.post(new Runnable() {
@Override
@@ -570,4 +507,174 @@
mHandler.sendEmptyMessageDelayed(MSG_INACTIVITY_TIME_OUT, mUserInactivityTimeout);
}
};
+
+ static class Injector {
+ private Context mContext;
+ private UserManager mUm;
+ private PackageManager mPm;
+ private NotificationManager mNm;
+ private ActivityManagerService mAms;
+ private ActivityManagerInternal mAmi;
+ private AudioManager mAudioManager;
+ private PowerManager mPowerManager;
+ private CameraManager mCameraManager;
+ private PowerManager.WakeLock mWakeLock;
+ private Configuration mSystemUserConfiguration;
+ private PendingIntent mResetDemoPendingIntent;
+
+ Injector(Context context) {
+ mContext = context;
+ }
+
+ Context getContext() {
+ return mContext;
+ }
+
+ UserManager getUserManager() {
+ if (mUm == null) {
+ mUm = getContext().getSystemService(UserManager.class);
+ }
+ return mUm;
+ }
+
+ void switchUser(int userId) {
+ if (mAms == null) {
+ mAms = (ActivityManagerService) ActivityManagerNative.getDefault();
+ }
+ mAms.switchUser(userId);
+ }
+
+ AudioManager getAudioManager() {
+ if (mAudioManager == null) {
+ mAudioManager = getContext().getSystemService(AudioManager.class);
+ }
+ return mAudioManager;
+ }
+
+ private PowerManager getPowerManager() {
+ if (mPowerManager == null) {
+ mPowerManager = (PowerManager) getContext().getSystemService(
+ Context.POWER_SERVICE);
+ }
+ return mPowerManager;
+ }
+
+ NotificationManager getNotificationManager() {
+ if (mNm == null) {
+ mNm = NotificationManager.from(getContext());
+ }
+ return mNm;
+ }
+
+ ActivityManagerInternal getActivityManagerInternal() {
+ if (mAmi == null) {
+ mAmi = LocalServices.getService(ActivityManagerInternal.class);
+ }
+ return mAmi;
+ }
+
+ CameraManager getCameraManager() {
+ if (mCameraManager == null) {
+ mCameraManager = (CameraManager) getContext().getSystemService(
+ Context.CAMERA_SERVICE);
+ }
+ return mCameraManager;
+ }
+
+ PackageManager getPackageManager() {
+ if (mPm == null) {
+ mPm = getContext().getPackageManager();
+ }
+ return mPm;
+ }
+
+ IPackageManager getIPackageManager() {
+ return AppGlobals.getPackageManager();
+ }
+
+ ContentResolver getContentResolver() {
+ return getContext().getContentResolver();
+ }
+
+ PreloadAppsInstaller getPreloadAppsInstaller() {
+ return new PreloadAppsInstaller(getContext());
+ }
+
+ void systemPropertiesSet(String key, String value) {
+ SystemProperties.set(key, value);
+ }
+
+ void turnOffAllFlashLights(String[] cameraIdsWithFlash) {
+ for (String cameraId : cameraIdsWithFlash) {
+ try {
+ getCameraManager().setTorchMode(cameraId, false);
+ } catch (CameraAccessException e) {
+ Slog.e(TAG, "Unable to access camera " + cameraId
+ + " while turning off flash", e);
+ }
+ }
+ }
+
+ void initializeWakeLock() {
+ mWakeLock = getPowerManager().newWakeLock(
+ PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, TAG);
+ }
+
+ boolean isWakeLockHeld() {
+ return mWakeLock.isHeld();
+ }
+
+ void acquireWakeLock() {
+ mWakeLock.acquire();
+ }
+
+ void releaseWakeLock() {
+ mWakeLock.release();
+ }
+
+ void logSessionDuration(int duration) {
+ MetricsLogger.histogram(getContext(), DEMO_SESSION_DURATION, duration);
+ }
+
+ void logSessionCount(int count) {
+ MetricsLogger.count(getContext(), DEMO_SESSION_COUNT, count);
+ }
+
+ Configuration getSystemUsersConfiguration() {
+ if (mSystemUserConfiguration == null) {
+ Settings.System.getConfiguration(getContentResolver(),
+ mSystemUserConfiguration = new Configuration());
+ }
+ return mSystemUserConfiguration;
+ }
+
+ LockPatternUtils getLockPatternUtils() {
+ return new LockPatternUtils(getContext());
+ }
+
+ Notification createResetNotification() {
+ return new Notification.Builder(getContext())
+ .setContentTitle(getContext().getString(R.string.reset_retail_demo_mode_title))
+ .setContentText(getContext().getString(R.string.reset_retail_demo_mode_text))
+ .setOngoing(true)
+ .setSmallIcon(R.drawable.platlogo)
+ .setShowWhen(false)
+ .setVisibility(Notification.VISIBILITY_PUBLIC)
+ .setContentIntent(getResetDemoPendingIntent())
+ .setColor(getContext().getColor(R.color.system_notification_accent_color))
+ .build();
+ }
+
+ private PendingIntent getResetDemoPendingIntent() {
+ if (mResetDemoPendingIntent == null) {
+ Intent intent = new Intent(ACTION_RESET_DEMO);
+ mResetDemoPendingIntent = PendingIntent.getBroadcast(getContext(), 0, intent, 0);
+ }
+ return mResetDemoPendingIntent;
+ }
+
+ File getDataPreloadsDirectory() {
+ return Environment.getDataPreloadsDirectory();
+ }
+ }
}
diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk
index fb8d814..9cfc312 100644
--- a/services/tests/servicestests/Android.mk
+++ b/services/tests/servicestests/Android.mk
@@ -17,6 +17,7 @@
services.core \
services.devicepolicy \
services.net \
+ services.retaildemo \
services.usage \
easymocklib \
guava \
diff --git a/services/tests/servicestests/src/com/android/server/retaildemo/PreloadAppsInstallerTest.java b/services/tests/servicestests/src/com/android/server/retaildemo/PreloadAppsInstallerTest.java
new file mode 100644
index 0000000..222436c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/retaildemo/PreloadAppsInstallerTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2016 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.server.retaildemo;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.IPackageInstallObserver2;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.os.FileUtils;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.test.mock.MockContentResolver;
+
+import com.android.internal.util.FakeSettingsProvider;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.util.ArrayList;
+
+@RunWith(JUnit4.class)
+@SmallTest
+public class PreloadAppsInstallerTest {
+ private static final int TEST_DEMO_USER = 111;
+
+ private @Mock Context mContext;
+ private @Mock IPackageManager mIpm;
+ private MockContentResolver mContentResolver;
+ private File mPreloadsAppsDirectory;
+ private String[] mPreloadedApps =
+ new String[] {"test1.apk.preload", "test2.apk.preload", "test3.apk.preload"};
+ private ArrayList<String> mPreloadedAppPaths = new ArrayList<>();
+
+ private PreloadAppsInstaller mInstaller;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mContentResolver = new MockContentResolver(mContext);
+ mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+ when(mContext.getContentResolver()).thenReturn(mContentResolver);
+ initializePreloadedApps();
+ Settings.Secure.putStringForUser(mContentResolver,
+ Settings.Secure.DEMO_USER_SETUP_COMPLETE, "0", TEST_DEMO_USER);
+
+ mInstaller = new PreloadAppsInstaller(mContext, mIpm, mPreloadsAppsDirectory);
+ }
+
+ private void initializePreloadedApps() throws Exception {
+ mPreloadsAppsDirectory = new File(InstrumentationRegistry.getContext().getFilesDir(),
+ "test_preload_apps_dir");
+ mPreloadsAppsDirectory.mkdir();
+ for (String name : mPreloadedApps) {
+ final File f = new File(mPreloadsAppsDirectory, name);
+ f.createNewFile();
+ mPreloadedAppPaths.add(f.getPath());
+ }
+ }
+
+ @After
+ public void tearDown() {
+ FileUtils.deleteContentsAndDir(mPreloadsAppsDirectory);
+ }
+
+ @Test
+ public void testInstallApps() throws Exception {
+ mInstaller.installApps(TEST_DEMO_USER);
+ for (String path : mPreloadedAppPaths) {
+ ArgumentCaptor<IPackageInstallObserver2> observer =
+ ArgumentCaptor.forClass(IPackageInstallObserver2.class);
+ verify(mIpm).installPackageAsUser(eq(path), observer.capture(), anyInt(),
+ anyString(), eq(TEST_DEMO_USER));
+ observer.getValue().onPackageInstalled(path, PackageManager.INSTALL_SUCCEEDED,
+ null, null);
+ // Verify that we try to install the package in system user.
+ verify(mIpm).installExistingPackageAsUser(path, UserHandle.USER_SYSTEM);
+ }
+ assertEquals("DEMO_USER_SETUP should be set to 1 after preloaded apps are installed",
+ "1",
+ Settings.Secure.getStringForUser(mContentResolver,
+ Settings.Secure.DEMO_USER_SETUP_COMPLETE, TEST_DEMO_USER));
+ }
+
+ @Test
+ public void testInstallApps_noPreloads() throws Exception {
+ // Delete all files in preloaded apps directory - no preloaded apps
+ FileUtils.deleteContents(mPreloadsAppsDirectory);
+ mInstaller.installApps(TEST_DEMO_USER);
+ assertEquals("DEMO_USER_SETUP should be set to 1 after preloaded apps are installed",
+ "1",
+ Settings.Secure.getStringForUser(mContentResolver,
+ Settings.Secure.DEMO_USER_SETUP_COMPLETE, TEST_DEMO_USER));
+ }
+
+ @Test
+ public void testInstallApps_installationFails() throws Exception {
+ mInstaller.installApps(TEST_DEMO_USER);
+ for (int i = 0; i < mPreloadedAppPaths.size(); ++i) {
+ ArgumentCaptor<IPackageInstallObserver2> observer =
+ ArgumentCaptor.forClass(IPackageInstallObserver2.class);
+ final String path = mPreloadedAppPaths.get(i);
+ verify(mIpm).installPackageAsUser(eq(path), observer.capture(), anyInt(),
+ anyString(), eq(TEST_DEMO_USER));
+ if (i == 0) {
+ observer.getValue().onPackageInstalled(path, PackageManager.INSTALL_FAILED_DEXOPT,
+ null, null);
+ continue;
+ }
+ observer.getValue().onPackageInstalled(path, PackageManager.INSTALL_SUCCEEDED,
+ null, null);
+ // Verify that we try to install the package in system user.
+ verify(mIpm).installExistingPackageAsUser(path, UserHandle.USER_SYSTEM);
+ }
+ assertEquals("DEMO_USER_SETUP should be set to 1 after preloaded apps are installed",
+ "1",
+ Settings.Secure.getStringForUser(mContentResolver,
+ Settings.Secure.DEMO_USER_SETUP_COMPLETE, TEST_DEMO_USER));
+ }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/retaildemo/RetailDemoModeServiceTest.java b/services/tests/servicestests/src/com/android/server/retaildemo/RetailDemoModeServiceTest.java
new file mode 100644
index 0000000..ddc2b53
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/retaildemo/RetailDemoModeServiceTest.java
@@ -0,0 +1,418 @@
+/*
+ * Copyright (C) 2016 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.server.retaildemo;
+
+import static com.android.server.retaildemo.RetailDemoModeService.SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.Manifest;
+import android.app.ActivityManagerInternal;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.RetailDemoModeServiceInternal;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
+import android.content.res.Configuration;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.CallLog;
+import android.provider.MediaStore;
+import android.provider.Settings;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.test.mock.MockContentProvider;
+import android.test.mock.MockContentResolver;
+import android.util.ArrayMap;
+
+import com.android.internal.util.FakeSettingsProvider;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+import com.android.server.retaildemo.RetailDemoModeService.Injector;
+
+import org.hamcrest.Description;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(JUnit4.class)
+@SmallTest
+public class RetailDemoModeServiceTest {
+ private static final int TEST_DEMO_USER = 111;
+ private static final long SETUP_COMPLETE_TIMEOUT_MS = 2000; // 2 sec
+ private static final String TEST_CAMERA_PKG = "test.cameraapp";
+ private static final String TEST_PRELOADS_DIR_NAME = "test_preloads";
+
+ private @Mock Context mContext;
+ private @Mock UserManager mUm;
+ private @Mock PackageManager mPm;
+ private @Mock IPackageManager mIpm;
+ private @Mock NotificationManager mNm;
+ private @Mock ActivityManagerInternal mAmi;
+ private @Mock AudioManager mAudioManager;
+ private @Mock LockPatternUtils mLockPatternUtils;
+ private PreloadAppsInstaller mPreloadAppsInstaller;
+ private MockContentResolver mContentResolver;
+ private MockContactsProvider mContactsProvider;
+ private Configuration mConfiguration;
+ private File mTestPreloadsDir;
+ private CountDownLatch mLatch;
+
+ private RetailDemoModeService mService;
+ private TestInjector mInjector;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mContentResolver = new MockContentResolver(mContext);
+ mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+ mContactsProvider = new MockContactsProvider(mContext);
+ mContentResolver.addProvider(CallLog.AUTHORITY, mContactsProvider);
+ when(mContext.getContentResolver()).thenReturn(mContentResolver);
+ mPreloadAppsInstaller = new PreloadAppsInstaller(Mockito.mock(Context.class),
+ Mockito.mock(IPackageManager.class), Mockito.mock(File.class));
+ mConfiguration = new Configuration();
+ mTestPreloadsDir = new File(InstrumentationRegistry.getContext().getFilesDir(),
+ TEST_PRELOADS_DIR_NAME);
+
+ Settings.Global.putString(mContentResolver,
+ Settings.Global.RETAIL_DEMO_MODE_CONSTANTS, "");
+ Settings.Global.putInt(mContentResolver,
+ Settings.Global.DEVICE_PROVISIONED, 1);
+ Settings.Global.putInt(mContentResolver,
+ Settings.Global.DEVICE_DEMO_MODE, 1);
+ // Initialize RetailDemoModeService
+ mInjector = new TestInjector();
+ mService = new RetailDemoModeService(mInjector);
+ mService.onStart();
+ }
+
+ @After
+ public void tearDown() {
+ // Remove the RetailDemoModeServiceInternal from LocalServices which would've been
+ // added during initialization of RetailDemoModeService in setUp().
+ LocalServices.removeServiceForTest(RetailDemoModeServiceInternal.class);
+ FileUtils.deleteContentsAndDir(mTestPreloadsDir);
+ }
+
+ @Test
+ public void testDemoUserSetup() throws Exception {
+ mService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
+
+ final ArgumentCaptor<IntentFilter> intentFilter =
+ ArgumentCaptor.forClass(IntentFilter.class);
+ verify(mContext).registerReceiver(any(BroadcastReceiver.class), intentFilter.capture());
+ assertTrue("Not registered for " + Intent.ACTION_SCREEN_OFF,
+ intentFilter.getValue().hasAction(Intent.ACTION_SCREEN_OFF));
+
+ mLatch = new CountDownLatch(1);
+
+ mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+ assertEquals(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED + " property not set",
+ "1", mInjector.systemPropertiesGet(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED));
+
+ final UserInfo userInfo = new UserInfo();
+ userInfo.id = TEST_DEMO_USER;
+ when(mUm.createUser(anyString(), anyInt())).thenReturn(userInfo);
+ mInjector.setDemoUserId(TEST_DEMO_USER);
+ setCameraPackage(TEST_CAMERA_PKG);
+ // Wait for the setup to complete.
+ mLatch.await(SETUP_COMPLETE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ ArgumentCaptor<Integer> flags = ArgumentCaptor.forClass(Integer.class);
+ verify(mUm).createUser(anyString(), flags.capture());
+ assertTrue("FLAG_DEMO not set during user creation",
+ (flags.getValue() & UserInfo.FLAG_DEMO) != 0);
+ assertTrue("FLAG_EPHEMERAL not set during user creation",
+ (flags.getValue() & UserInfo.FLAG_EPHEMERAL) != 0);
+ // Verify if necessary restrictions are being set.
+ final UserHandle user = UserHandle.of(TEST_DEMO_USER);
+ verify(mUm).setUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, true, user);
+ verify(mUm).setUserRestriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, true, user);
+ verify(mUm).setUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, true, user);
+ verify(mUm).setUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER, true, user);
+ verify(mUm).setUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS, true, user);
+ verify(mUm).setUserRestriction(UserManager.DISALLOW_CONFIG_BLUETOOTH, true, user);
+ verify(mUm).setUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS, false, user);
+ verify(mUm).setUserRestriction(UserManager.DISALLOW_SAFE_BOOT, true, UserHandle.SYSTEM);
+ // Verify if necessary settings are updated.
+ assertEquals("SKIP_FIRST_USE_HINTS setting is not set for demo user",
+ Settings.Secure.getIntForUser(mContentResolver,
+ Settings.Secure.SKIP_FIRST_USE_HINTS, TEST_DEMO_USER),
+ 1);
+ assertEquals("PACKAGE_VERIFIER_ENABLE settings should be set to 0 for demo user",
+ Settings.Global.getInt(mContentResolver,
+ Settings.Global.PACKAGE_VERIFIER_ENABLE),
+ 0);
+ // Verify if camera is granted location permission.
+ verify(mPm).grantRuntimePermission(TEST_CAMERA_PKG,
+ Manifest.permission.ACCESS_FINE_LOCATION, user);
+ // Verify call logs are cleared.
+ assertTrue("Call logs should be deleted", mContactsProvider.isCallLogDeleted());
+ }
+
+ @Test
+ public void testSettingsObserver() throws Exception {
+ final RetailDemoModeService.SettingsObserver observer =
+ mService.new SettingsObserver(new Handler(Looper.getMainLooper()));
+ final Uri deviceDemoModeUri = Settings.Global.getUriFor(Settings.Global.DEVICE_DEMO_MODE);
+ // Settings.Global.DEVICE_DEMO_MODE has been set to 1 initially.
+ observer.onChange(false, deviceDemoModeUri);
+ assertEquals(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED + " property not set",
+ "1", mInjector.systemPropertiesGet(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED));
+
+ new File(mTestPreloadsDir, "dir1").mkdirs();
+ new File(mTestPreloadsDir, "file1").createNewFile();
+ Settings.Global.putInt(mContentResolver, Settings.Global.DEVICE_DEMO_MODE, 0);
+ observer.onChange(false, deviceDemoModeUri);
+ Thread.sleep(20); // Wait for the deletion to complete.
+ // verify that the preloaded directory is emptied.
+ assertEquals("Preloads directory is not emptied",
+ 0, mTestPreloadsDir.list().length);
+ }
+
+ @Test
+ public void testSwitchToDemoUser() {
+ // To make the RetailDemoModeService update it's internal state.
+ mService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
+ final RetailDemoModeService.SettingsObserver observer =
+ mService.new SettingsObserver(new Handler(Looper.getMainLooper()));
+ observer.onChange(false, Settings.Global.getUriFor(Settings.Global.DEVICE_DEMO_MODE));
+
+ final UserInfo userInfo = new UserInfo(TEST_DEMO_USER, "demo_user",
+ UserInfo.FLAG_DEMO | UserInfo.FLAG_EPHEMERAL);
+ when(mUm.getUserInfo(TEST_DEMO_USER)).thenReturn(userInfo);
+ final int minVolume = -111;
+ for (int stream : RetailDemoModeService.VOLUME_STREAMS_TO_MUTE) {
+ when(mAudioManager.getStreamMinVolume(stream)).thenReturn(minVolume);
+ }
+
+ mService.onSwitchUser(TEST_DEMO_USER);
+ verify(mAmi).updatePersistentConfigurationForUser(mConfiguration, TEST_DEMO_USER);
+ for (int stream : RetailDemoModeService.VOLUME_STREAMS_TO_MUTE) {
+ verify(mAudioManager).setStreamVolume(stream, minVolume, 0);
+ }
+ verify(mLockPatternUtils).setLockScreenDisabled(true, TEST_DEMO_USER);
+ }
+
+ private void setCameraPackage(String pkgName) {
+ final ResolveInfo ri = new ResolveInfo();
+ final ActivityInfo ai = new ActivityInfo();
+ ai.packageName = pkgName;
+ ri.activityInfo = ai;
+ when(mPm.resolveActivityAsUser(
+ argThat(new IntentMatcher(MediaStore.ACTION_IMAGE_CAPTURE)),
+ anyInt(),
+ eq(TEST_DEMO_USER))).thenReturn(ri);
+ }
+
+ private class IntentMatcher extends ArgumentMatcher<Intent> {
+ private final Intent mIntent;
+
+ IntentMatcher(String action) {
+ mIntent = new Intent(action);
+ }
+
+ @Override
+ public boolean matches(Object argument) {
+ if (argument instanceof Intent) {
+ return ((Intent) argument).filterEquals(mIntent);
+ }
+ return false;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("Expected: " + mIntent);
+ }
+ }
+
+ private class MockContactsProvider extends MockContentProvider {
+ private boolean mCallLogDeleted;
+
+ MockContactsProvider(Context context) {
+ super(context);
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ if (CallLog.Calls.CONTENT_URI.equals(uri)) {
+ mCallLogDeleted = true;
+ }
+ return 0;
+ }
+
+ public boolean isCallLogDeleted() {
+ return mCallLogDeleted;
+ }
+ }
+
+ private class TestInjector extends Injector {
+ private ArrayMap<String, String> mSystemProperties = new ArrayMap<>();
+ private int mDemoUserId = UserHandle.USER_NULL;
+
+ TestInjector() {
+ super(mContext);
+ }
+
+ @Override
+ Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ UserManager getUserManager() {
+ return mUm;
+ }
+
+ @Override
+ void switchUser(int userId) {
+ if (mLatch != null) {
+ mLatch.countDown();
+ }
+ }
+
+ @Override
+ AudioManager getAudioManager() {
+ return mAudioManager;
+ }
+
+ @Override
+ NotificationManager getNotificationManager() {
+ return mNm;
+ }
+
+ @Override
+ ActivityManagerInternal getActivityManagerInternal() {
+ return mAmi;
+ }
+
+ @Override
+ PackageManager getPackageManager() {
+ return mPm;
+ }
+
+ @Override
+ IPackageManager getIPackageManager() {
+ return mIpm;
+ }
+
+ @Override
+ ContentResolver getContentResolver() {
+ return mContentResolver;
+ }
+
+ @Override
+ PreloadAppsInstaller getPreloadAppsInstaller() {
+ return mPreloadAppsInstaller;
+ }
+
+ @Override
+ void systemPropertiesSet(String key, String value) {
+ mSystemProperties.put(key, value);
+ }
+
+ @Override
+ void turnOffAllFlashLights(String[] cameraIdsWithFlash) {
+ }
+
+ @Override
+ void initializeWakeLock() {
+ }
+
+ @Override
+ boolean isWakeLockHeld() {
+ return false;
+ }
+
+ @Override
+ void acquireWakeLock() {
+ }
+
+ @Override
+ void releaseWakeLock() {
+ }
+
+ @Override
+ void logSessionDuration(int duration) {
+ }
+
+ @Override
+ void logSessionCount(int count) {
+ }
+
+ @Override
+ Configuration getSystemUsersConfiguration() {
+ return mConfiguration;
+ }
+
+ @Override
+ LockPatternUtils getLockPatternUtils() {
+ return mLockPatternUtils;
+ }
+
+ @Override
+ Notification createResetNotification() {
+ return null;
+ }
+
+ @Override
+ File getDataPreloadsDirectory() {
+ return mTestPreloadsDir;
+ }
+
+ String systemPropertiesGet(String key) {
+ return mSystemProperties.get(key);
+ }
+
+ void setDemoUserId(int userId) {
+ mDemoUserId = userId;
+ }
+ }
+}
\ No newline at end of file