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