diff options
| author | 2016-08-20 02:46:26 +0000 | |
|---|---|---|
| committer | 2016-08-20 02:46:29 +0000 | |
| commit | 3cceca4d58d5c7869caa059f4987fc2b5d6b00f5 (patch) | |
| tree | 776ce310f31de2b5def7e5328b5fc366f4579852 | |
| parent | 3a5b710684de9505372be692b7dc75d7fba6ca33 (diff) | |
| parent | d9fcb69b81e49cc106d6c53884fdc3aee4822e81 (diff) | |
Merge "Add unit tests for RetailDemoModeService."
4 files changed, 800 insertions, 125 deletions
diff --git a/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java b/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java index 6b273210263d..1612b99b1fa5 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.PendingIntent; 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 android.util.Slog; 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 @@ public class RetailDemoModeService extends SystemService { 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 @@ public class RetailDemoModeService extends SystemService { 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 @@ public class RetailDemoModeService extends SystemService { 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 class RetailDemoModeService extends SystemService { 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 @@ public class RetailDemoModeService extends SystemService { 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 class RetailDemoModeService extends SystemService { } 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 @@ public class RetailDemoModeService extends SystemService { 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 @@ public class RetailDemoModeService extends SystemService { 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 class RetailDemoModeService extends SystemService { } public RetailDemoModeService(Context context) { - super(context); - synchronized (mActivityLock) { - mFirstUserActivityTime = mLastUserActivityTime = SystemClock.uptimeMillis(); - } + this(new Injector(context)); } - 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(); - } + @VisibleForTesting + RetailDemoModeService(Injector injector) { + super(injector.getContext()); - private PendingIntent getResetDemoPendingIntent() { - if (mResetDemoPendingIntent == null) { - Intent intent = new Intent(ACTION_RESET_DEMO); - mResetDemoPendingIntent = PendingIntent.getBroadcast(getContext(), 0, intent, 0); + mInjector = injector; + synchronized (mActivityLock) { + mFirstUserActivityTime = mLastUserActivityTime = SystemClock.uptimeMillis(); } - 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 @@ public class RetailDemoModeService extends SystemService { } 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 @@ public class RetailDemoModeService extends SystemService { // 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 @@ public class RetailDemoModeService extends SystemService { } 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 @@ public class RetailDemoModeService extends SystemService { 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 @@ public class RetailDemoModeService extends SystemService { 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); - } - } - } 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) { + final CameraManager cm = mInjector.getCameraManager(); + if (cm != null) { try { - mCameraManager.setTorchMode(cameraId, false); + 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 " + cameraId + " while turning off flash", e); + Slog.e(TAG, "Unable to access camera while getting camera id list", e); } } + return cameraIdsList.toArray(new String[cameraIdsList.size()]); } private void muteVolumeStreams() { for (int stream : VOLUME_STREAMS_TO_MUTE) { - getAudioManager().setStreamVolume(stream, getAudioManager().getStreamMinVolume(stream), - 0); - } - } - - private Configuration getSystemUsersConfiguration() { - if (mSystemUserConfiguration == null) { - Settings.System.getConfiguration(getContext().getContentResolver(), - mSystemUserConfiguration = new Configuration()); + mInjector.getAudioManager().setStreamVolume(stream, + mInjector.getAudioManager().getStreamMinVolume(stream), 0); } - 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 class RetailDemoModeService extends SystemService { 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 @@ public class RetailDemoModeService extends SystemService { 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 @@ public class RetailDemoModeService extends SystemService { 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 fb8d814b6597..9cfc3124c808 100644 --- a/services/tests/servicestests/Android.mk +++ b/services/tests/servicestests/Android.mk @@ -17,6 +17,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ 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 000000000000..222436cf7b06 --- /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 000000000000..ddc2b53fdcd3 --- /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 |