Merge "DO NOT MERGE - Remove RetailDemoModeService" into oc-dr1-dev
am: 484115b77d
Change-Id: Id9f50aeab430db43e9f18bcdfd80b6dcac2c43a7
This commit is contained in:
@@ -10079,22 +10079,6 @@ public final class Settings {
|
||||
*/
|
||||
public static final String DEVICE_DEMO_MODE = "device_demo_mode";
|
||||
|
||||
/**
|
||||
* Retail mode specific settings. This is encoded as a key=value list, separated by commas.
|
||||
* Ex: "user_inactivity_timeout_ms=30000,warning_dialog_timeout_ms=10000". The following
|
||||
* keys are supported:
|
||||
*
|
||||
* <pre>
|
||||
* user_inactivity_timeout_ms (long)
|
||||
* warning_dialog_timeout_ms (long)
|
||||
* </pre>
|
||||
* <p>
|
||||
* Type: string
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final String RETAIL_DEMO_MODE_CONSTANTS = "retail_demo_mode_constants";
|
||||
|
||||
/**
|
||||
* Indicates the maximum time that an app is blocked for the network rules to get updated.
|
||||
*
|
||||
|
||||
@@ -315,7 +315,6 @@ message GlobalSettingsProto {
|
||||
SettingProto boot_count = 270;
|
||||
SettingProto safe_boot_disallowed = 271;
|
||||
SettingProto device_demo_mode = 272;
|
||||
SettingProto retail_demo_mode_constants = 273;
|
||||
SettingProto database_downgrade_reason = 274;
|
||||
SettingProto contacts_database_wal_enabled = 275;
|
||||
SettingProto multi_sim_voice_call_subscription = 276;
|
||||
|
||||
@@ -4590,22 +4590,10 @@
|
||||
<!-- The representation of a time duration when negative. An example is -1:14. This can be used with a countdown timer for example.-->
|
||||
<string name="negative_duration">\u2212<xliff:g id="time" example="1:14">%1$s</xliff:g></string>
|
||||
|
||||
<!-- Title of notification to start a new demo session when device is in retail mode [CHAR LIMIT=NONE] -->
|
||||
<string name="reset_retail_demo_mode_title">Reset device?</string>
|
||||
<!-- Text of notification to start a new demo session when device is in retail mode [CHAR LIMIT=NONE] -->
|
||||
<string name="reset_retail_demo_mode_text">Tap to reset device</string>
|
||||
<!-- Text of dialog shown when starting a demo user for the first time [CHAR LIMIT=40] -->
|
||||
<string name="demo_starting_message">Starting demo\u2026</string>
|
||||
<!-- Text of dialog shown when starting a new demo user in retail demo mode [CHAR LIMIT=40] -->
|
||||
<string name="demo_restarting_message">Resetting device\u2026</string>
|
||||
<!-- Title of the dialog shown when user inactivity times out in retail demo mode [CHAR LIMIT=40] -->
|
||||
<string name="demo_user_inactivity_timeout_title">Reset device?</string>
|
||||
<!-- Warning message shown when user inactivity times out in retail demo mode [CHAR LIMIT=none] -->
|
||||
<string name="demo_user_inactivity_timeout_countdown">You\u2019ll lose any changes and the demo will start again in <xliff:g id="timeout" example="9">%1$s</xliff:g> seconds\u2026</string>
|
||||
<!-- Text of button to allow user to abort countdown and continue current session in retail demo mode [CHAR LIMIT=40] -->
|
||||
<string name="demo_user_inactivity_timeout_left_button">Cancel</string>
|
||||
<!-- Text of button to allow user to abort countdown and immediately start another session in retail demo mode [CHAR LIMIT=40] -->
|
||||
<string name="demo_user_inactivity_timeout_right_button">Reset now</string>
|
||||
|
||||
<!-- Accessibilty string added to a widget that has been suspended [CHAR LIMIT=20] -->
|
||||
<string name="suspended_widget_accessibility">Disabled <xliff:g id="label" example="Calendar">%1$s</xliff:g></string>
|
||||
|
||||
@@ -298,7 +298,6 @@ public class SettingsBackupTest {
|
||||
Settings.Global.RECOMMENDED_NETWORK_EVALUATOR_CACHE_EXPIRY_MS,
|
||||
Settings.Global.READ_EXTERNAL_STORAGE_ENFORCED_DEFAULT,
|
||||
Settings.Global.REQUIRE_PASSWORD_TO_DECRYPT,
|
||||
Settings.Global.RETAIL_DEMO_MODE_CONSTANTS,
|
||||
Settings.Global.SAFE_BOOT_DISALLOWED,
|
||||
Settings.Global.SAMPLING_PROFILER_MS,
|
||||
Settings.Global.SELINUX_STATUS,
|
||||
|
||||
@@ -914,9 +914,6 @@ class SettingsProtoDumpUtil {
|
||||
dumpSetting(s, p,
|
||||
Settings.Global.DEVICE_DEMO_MODE,
|
||||
GlobalSettingsProto.DEVICE_DEMO_MODE);
|
||||
dumpSetting(s, p,
|
||||
Settings.Global.RETAIL_DEMO_MODE_CONSTANTS,
|
||||
GlobalSettingsProto.RETAIL_DEMO_MODE_CONSTANTS);
|
||||
dumpSetting(s, p,
|
||||
Settings.Global.DATABASE_DOWNGRADE_REASON,
|
||||
GlobalSettingsProto.DATABASE_DOWNGRADE_REASON);
|
||||
|
||||
@@ -34,7 +34,6 @@ services := \
|
||||
net \
|
||||
print \
|
||||
restrictions \
|
||||
retaildemo \
|
||||
usage \
|
||||
usb \
|
||||
voiceinteraction
|
||||
|
||||
@@ -53,6 +53,7 @@ import android.os.SystemClock;
|
||||
import android.os.SystemProperties;
|
||||
import android.os.Trace;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.os.WorkSource;
|
||||
import android.provider.Settings;
|
||||
import android.provider.Settings.SettingNotFoundException;
|
||||
@@ -197,6 +198,9 @@ public final class PowerManagerService extends SystemService
|
||||
// System property indicating that the screen should remain off until an explicit user action
|
||||
private static final String SYSTEM_PROPERTY_QUIESCENT = "ro.boot.quiescent";
|
||||
|
||||
// System Property indicating that retail demo mode is currently enabled.
|
||||
private static final String SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED = "sys.retaildemo.enabled";
|
||||
|
||||
// Possible reasons for shutting down for use in data/misc/reboot/last_shutdown_reason
|
||||
private static final String REASON_SHUTDOWN = "shutdown";
|
||||
private static final String REASON_REBOOT = "reboot";
|
||||
@@ -805,6 +809,9 @@ public final class PowerManagerService extends SystemService
|
||||
resolver.registerContentObserver(Settings.Secure.getUriFor(
|
||||
Settings.Secure.DOUBLE_TAP_TO_WAKE),
|
||||
false, mSettingsObserver, UserHandle.USER_ALL);
|
||||
resolver.registerContentObserver(Settings.Global.getUriFor(
|
||||
Settings.Global.DEVICE_DEMO_MODE),
|
||||
false, mSettingsObserver, UserHandle.USER_SYSTEM);
|
||||
IVrManager vrManager = (IVrManager) getBinderService(Context.VR_SERVICE);
|
||||
if (vrManager != null) {
|
||||
try {
|
||||
@@ -912,6 +919,11 @@ public final class PowerManagerService extends SystemService
|
||||
}
|
||||
}
|
||||
|
||||
final String retailDemoValue = UserManager.isDeviceInDemoMode(mContext) ? "1" : "0";
|
||||
if (!retailDemoValue.equals(SystemProperties.get(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED))) {
|
||||
SystemProperties.set(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED, retailDemoValue);
|
||||
}
|
||||
|
||||
final int oldScreenBrightnessSetting = getCurrentBrightnessSettingLocked();
|
||||
|
||||
mScreenBrightnessForVrSetting = Settings.System.getIntForUser(resolver,
|
||||
|
||||
@@ -99,7 +99,6 @@ import com.android.server.power.PowerManagerService;
|
||||
import com.android.server.power.ShutdownThread;
|
||||
import com.android.server.radio.RadioService;
|
||||
import com.android.server.restrictions.RestrictionsManagerService;
|
||||
import com.android.server.retaildemo.RetailDemoModeService;
|
||||
import com.android.server.security.KeyAttestationApplicationIdProviderService;
|
||||
import com.android.server.security.KeyChainSystemService;
|
||||
import com.android.server.soundtrigger.SoundTriggerService;
|
||||
@@ -1521,10 +1520,6 @@ public final class SystemServer {
|
||||
mmsService = mSystemServiceManager.startService(MmsServiceBroker.class);
|
||||
traceEnd();
|
||||
|
||||
traceBeginAndSlog("StartRetailDemoModeService");
|
||||
mSystemServiceManager.startService(RetailDemoModeService.class);
|
||||
traceEnd();
|
||||
|
||||
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOFILL)) {
|
||||
traceBeginAndSlog("StartAutoFillService");
|
||||
mSystemServiceManager.startService(AUTO_FILL_MANAGER_SERVICE_CLASS);
|
||||
|
||||
@@ -5085,11 +5085,6 @@ Lcom/android/server/RescueParty$BootThreshold;
|
||||
Lcom/android/server/RescueParty$Threshold;
|
||||
Lcom/android/server/restrictions/RestrictionsManagerService;
|
||||
Lcom/android/server/restrictions/RestrictionsManagerService$RestrictionsManagerImpl;
|
||||
Lcom/android/server/retaildemo/RetailDemoModeService;
|
||||
Lcom/android/server/retaildemo/RetailDemoModeService$1;
|
||||
Lcom/android/server/retaildemo/RetailDemoModeService$Injector;
|
||||
Lcom/android/server/retaildemo/RetailDemoModeService$MainHandler;
|
||||
Lcom/android/server/retaildemo/RetailDemoModeService$SettingsObserver;
|
||||
Lcom/android/server/SamplingProfilerService;
|
||||
Lcom/android/server/SamplingProfilerService$1;
|
||||
Lcom/android/server/SamplingProfilerService$SamplingProfilerSettingsObserver;
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_MODULE := services.retaildemo
|
||||
|
||||
LOCAL_SRC_FILES += \
|
||||
$(call all-java-files-under,java)
|
||||
|
||||
LOCAL_JAVA_LIBRARIES := services.core
|
||||
|
||||
include $(BUILD_STATIC_JAVA_LIBRARY)
|
||||
@@ -1,178 +0,0 @@
|
||||
/*
|
||||
* 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 android.app.AppGlobals;
|
||||
import android.app.PackageInstallObserver;
|
||||
import android.content.Context;
|
||||
import android.content.pm.IPackageManager;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Helper class for installing preloaded APKs
|
||||
*/
|
||||
class PreloadAppsInstaller {
|
||||
private static final String SYSTEM_SERVER_PACKAGE_NAME = "android";
|
||||
private static String TAG = PreloadAppsInstaller.class.getSimpleName();
|
||||
private static final String PRELOAD_APK_EXT = ".apk.preload";
|
||||
private static boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
|
||||
|
||||
private final IPackageManager mPackageManager;
|
||||
private final File preloadsAppsDirectory;
|
||||
private final Context mContext;
|
||||
|
||||
private final Map<String, String> mApkToPackageMap;
|
||||
|
||||
PreloadAppsInstaller(Context context) {
|
||||
this(context, AppGlobals.getPackageManager(), Environment.getDataPreloadsAppsDirectory());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
PreloadAppsInstaller(Context context, IPackageManager packageManager, File preloadsAppsDirectory) {
|
||||
mContext = context;
|
||||
mPackageManager = packageManager;
|
||||
mApkToPackageMap = Collections.synchronizedMap(new ArrayMap<>());
|
||||
this.preloadsAppsDirectory = preloadsAppsDirectory;
|
||||
}
|
||||
|
||||
void installApps(int userId) {
|
||||
File[] files = preloadsAppsDirectory.listFiles();
|
||||
AppInstallCounter counter = new AppInstallCounter(mContext, userId);
|
||||
if (ArrayUtils.isEmpty(files)) {
|
||||
counter.setExpectedAppsCount(0);
|
||||
return;
|
||||
}
|
||||
int expectedCount = 0;
|
||||
for (File file : files) {
|
||||
String apkName = file.getName();
|
||||
if (apkName.endsWith(PRELOAD_APK_EXT) && file.isFile()) {
|
||||
String packageName = mApkToPackageMap.get(apkName);
|
||||
if (packageName != null) {
|
||||
try {
|
||||
expectedCount++;
|
||||
installExistingPackage(packageName, userId, counter);
|
||||
} catch (Exception e) {
|
||||
Slog.e(TAG, "Failed to install existing package " + packageName, e);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
installPackage(file, userId, counter);
|
||||
expectedCount++;
|
||||
} catch (Exception e) {
|
||||
Slog.e(TAG, "Failed to install package from " + file, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
counter.setExpectedAppsCount(expectedCount);
|
||||
}
|
||||
|
||||
private void installExistingPackage(String packageName, int userId,
|
||||
AppInstallCounter counter) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "installExistingPackage " + packageName + " u" + userId);
|
||||
}
|
||||
try {
|
||||
mPackageManager.installExistingPackageAsUser(packageName, userId,
|
||||
0 /*installFlags*/, PackageManager.INSTALL_REASON_UNKNOWN);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
} finally {
|
||||
counter.appInstallFinished();
|
||||
}
|
||||
}
|
||||
|
||||
private void installPackage(File file, final int userId, AppInstallCounter counter)
|
||||
throws IOException, RemoteException {
|
||||
final String apkName = file.getName();
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "installPackage " + apkName + " u" + userId);
|
||||
}
|
||||
mPackageManager.installPackageAsUser(file.getPath(), new PackageInstallObserver() {
|
||||
@Override
|
||||
public void onPackageInstalled(String basePackageName, int returnCode, String msg,
|
||||
Bundle extras) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Package " + basePackageName + " installed u" + userId
|
||||
+ " returnCode: " + returnCode + " msg: " + msg);
|
||||
}
|
||||
// Don't notify the counter for now, we'll do it in installExistingPackage
|
||||
if (returnCode == PackageManager.INSTALL_SUCCEEDED) {
|
||||
mApkToPackageMap.put(apkName, basePackageName);
|
||||
// Install on user 0 so that the package is cached when demo user is re-created
|
||||
installExistingPackage(basePackageName, UserHandle.USER_SYSTEM, counter);
|
||||
} else if (returnCode == PackageManager.INSTALL_FAILED_ALREADY_EXISTS) {
|
||||
// This can only happen in first session after a reboot
|
||||
if (!mApkToPackageMap.containsKey(apkName)) {
|
||||
mApkToPackageMap.put(apkName, basePackageName);
|
||||
}
|
||||
installExistingPackage(basePackageName, userId, counter);
|
||||
} else {
|
||||
Log.e(TAG, "Package " + basePackageName + " cannot be installed from "
|
||||
+ apkName + ": " + msg + " (returnCode " + returnCode + ")");
|
||||
counter.appInstallFinished();
|
||||
}
|
||||
}
|
||||
}.getBinder(), 0, SYSTEM_SERVER_PACKAGE_NAME, userId);
|
||||
}
|
||||
|
||||
private static class AppInstallCounter {
|
||||
private int expectedCount = -1; // -1 means expectedCount not set
|
||||
private int finishedCount;
|
||||
private final Context mContext;
|
||||
private final int userId;
|
||||
|
||||
AppInstallCounter(Context context, int userId) {
|
||||
mContext = context;
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
synchronized void appInstallFinished() {
|
||||
this.finishedCount++;
|
||||
checkIfAllFinished();
|
||||
}
|
||||
|
||||
synchronized void setExpectedAppsCount(int expectedCount) {
|
||||
this.expectedCount = expectedCount;
|
||||
checkIfAllFinished();
|
||||
}
|
||||
|
||||
private void checkIfAllFinished() {
|
||||
if (expectedCount == finishedCount) {
|
||||
Log.i(TAG, "All preloads finished installing for user " + userId);
|
||||
Settings.Secure.putStringForUser(mContext.getContentResolver(),
|
||||
Settings.Secure.DEMO_USER_SETUP_COMPLETE, "1", userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,868 +0,0 @@
|
||||
/*
|
||||
* 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 android.Manifest;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityManagerInternal;
|
||||
import android.app.AppGlobals;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.RetailDemoModeServiceInternal;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
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.database.ContentObserver;
|
||||
import android.hardware.camera2.CameraAccessException;
|
||||
import android.hardware.camera2.CameraCharacteristics;
|
||||
import android.hardware.camera2.CameraManager;
|
||||
import android.media.AudioManager;
|
||||
import android.media.AudioSystem;
|
||||
import android.net.Uri;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.os.Environment;
|
||||
import android.os.FileUtils;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.PowerManager;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemClock;
|
||||
import android.os.SystemProperties;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.provider.CallLog;
|
||||
import android.provider.MediaStore;
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
import android.util.KeyValueListParser;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
|
||||
import com.android.internal.notification.SystemNotificationChannels;
|
||||
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;
|
||||
import com.android.server.PreloadsFileCacheExpirationJobService;
|
||||
import com.android.server.ServiceThread;
|
||||
import com.android.server.SystemService;
|
||||
import com.android.server.am.ActivityManagerService;
|
||||
import com.android.server.retaildemo.UserInactivityCountdownDialog.OnCountDownExpiredListener;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class RetailDemoModeService extends SystemService {
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private static final String TAG = RetailDemoModeService.class.getSimpleName();
|
||||
private static final String DEMO_USER_NAME = "Demo";
|
||||
private static final String ACTION_RESET_DEMO =
|
||||
"com.android.server.retaildemo.ACTION_RESET_DEMO";
|
||||
@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;
|
||||
private static final int MSG_START_NEW_SESSION = 2;
|
||||
|
||||
private static final long SCREEN_WAKEUP_DELAY = 2500;
|
||||
private static final long USER_INACTIVITY_TIMEOUT_MIN = 10000;
|
||||
private static final long USER_INACTIVITY_TIMEOUT_DEFAULT = 90000;
|
||||
private static final long WARNING_DIALOG_TIMEOUT_DEFAULT = 0;
|
||||
private static final long MILLIS_PER_SECOND = 1000;
|
||||
|
||||
@VisibleForTesting
|
||||
static final int[] VOLUME_STREAMS_TO_MUTE = {
|
||||
AudioSystem.STREAM_RING,
|
||||
AudioSystem.STREAM_MUSIC
|
||||
};
|
||||
|
||||
// Tron Vars
|
||||
private static final String DEMO_SESSION_COUNT = "retail_demo_session_count";
|
||||
private static final String DEMO_SESSION_DURATION = "retail_demo_session_duration";
|
||||
|
||||
boolean mDeviceInDemoMode;
|
||||
boolean mIsCarrierDemoMode;
|
||||
int mCurrentUserId = UserHandle.USER_SYSTEM;
|
||||
long mUserInactivityTimeout;
|
||||
long mWarningDialogTimeout;
|
||||
private Injector mInjector;
|
||||
Handler mHandler;
|
||||
private ServiceThread mHandlerThread;
|
||||
private String[] mCameraIdsWithFlash;
|
||||
private PreloadAppsInstaller mPreloadAppsInstaller;
|
||||
|
||||
final Object mActivityLock = new Object();
|
||||
// Whether the newly created demo user has interacted with the screen yet
|
||||
@GuardedBy("mActivityLock")
|
||||
boolean mUserUntouched;
|
||||
@GuardedBy("mActivityLock")
|
||||
long mFirstUserActivityTime;
|
||||
@GuardedBy("mActivityLock")
|
||||
long mLastUserActivityTime;
|
||||
|
||||
private boolean mSafeBootRestrictionInitialState;
|
||||
private int mPackageVerifierEnableInitialState;
|
||||
|
||||
private IntentReceiver mBroadcastReceiver = null;
|
||||
|
||||
private final class IntentReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (!mDeviceInDemoMode) {
|
||||
return;
|
||||
}
|
||||
final String action = intent.getAction();
|
||||
switch (action) {
|
||||
case Intent.ACTION_SCREEN_OFF:
|
||||
mHandler.removeMessages(MSG_TURN_SCREEN_ON);
|
||||
mHandler.sendEmptyMessageDelayed(MSG_TURN_SCREEN_ON, SCREEN_WAKEUP_DELAY);
|
||||
break;
|
||||
case ACTION_RESET_DEMO:
|
||||
mHandler.sendEmptyMessage(MSG_START_NEW_SESSION);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
final class MainHandler extends Handler {
|
||||
|
||||
MainHandler(Looper looper) {
|
||||
super(looper, null, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
if (!mDeviceInDemoMode) {
|
||||
return;
|
||||
}
|
||||
switch (msg.what) {
|
||||
case MSG_TURN_SCREEN_ON:
|
||||
if (mInjector.isWakeLockHeld()) {
|
||||
mInjector.releaseWakeLock();
|
||||
}
|
||||
mInjector.acquireWakeLock();
|
||||
break;
|
||||
case MSG_INACTIVITY_TIME_OUT:
|
||||
if (!mIsCarrierDemoMode && isDemoLauncherDisabled()) {
|
||||
Slog.i(TAG, "User inactivity timeout reached");
|
||||
showInactivityCountdownDialog();
|
||||
}
|
||||
break;
|
||||
case MSG_START_NEW_SESSION:
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Switching to a new demo user");
|
||||
}
|
||||
removeMessages(MSG_START_NEW_SESSION);
|
||||
removeMessages(MSG_INACTIVITY_TIME_OUT);
|
||||
if (!mIsCarrierDemoMode && mCurrentUserId != UserHandle.USER_SYSTEM) {
|
||||
logSessionDuration();
|
||||
}
|
||||
|
||||
final UserManager um = mInjector.getUserManager();
|
||||
UserInfo demoUser = null;
|
||||
if (mIsCarrierDemoMode) {
|
||||
// Re-use the existing demo user in carrier demo mode.
|
||||
for (UserInfo user : um.getUsers()) {
|
||||
if (user.isDemo()) {
|
||||
demoUser = user;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (demoUser == null) {
|
||||
// User in carrier demo mode should survive reboots.
|
||||
final int flags = UserInfo.FLAG_DEMO
|
||||
| (mIsCarrierDemoMode ? 0 : UserInfo.FLAG_EPHEMERAL);
|
||||
demoUser = um.createUser(DEMO_USER_NAME, flags);
|
||||
}
|
||||
|
||||
if (demoUser != null && mCurrentUserId != demoUser.id) {
|
||||
setupDemoUser(demoUser);
|
||||
mInjector.switchUser(demoUser.id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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";
|
||||
|
||||
private final Uri mDeviceDemoModeUri = Settings.Global
|
||||
.getUriFor(Settings.Global.DEVICE_DEMO_MODE);
|
||||
private final Uri mDeviceProvisionedUri = Settings.Global
|
||||
.getUriFor(Settings.Global.DEVICE_PROVISIONED);
|
||||
private final Uri mRetailDemoConstantsUri = Settings.Global
|
||||
.getUriFor(Settings.Global.RETAIL_DEMO_MODE_CONSTANTS);
|
||||
|
||||
private final KeyValueListParser mParser = new KeyValueListParser(',');
|
||||
|
||||
public SettingsObserver(Handler handler) {
|
||||
super(handler);
|
||||
}
|
||||
|
||||
public void register() {
|
||||
final 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,
|
||||
UserHandle.USER_SYSTEM);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(boolean selfChange, Uri uri) {
|
||||
if (mRetailDemoConstantsUri.equals(uri)) {
|
||||
refreshTimeoutConstants();
|
||||
return;
|
||||
}
|
||||
|
||||
// If device is provisioned and left demo mode - run the cleanup in demo folder
|
||||
if (isDeviceProvisioned()) {
|
||||
if (UserManager.isDeviceInDemoMode(getContext())) {
|
||||
startDemoMode();
|
||||
} else {
|
||||
mInjector.systemPropertiesSet(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED, "0");
|
||||
|
||||
// Run on the bg thread to not block the fg thread
|
||||
BackgroundThread.getHandler().post(() -> {
|
||||
if (!deletePreloadsFolderContents()) {
|
||||
Slog.w(TAG, "Failed to delete preloads folder contents");
|
||||
}
|
||||
PreloadsFileCacheExpirationJobService.schedule(mInjector.getContext());
|
||||
});
|
||||
|
||||
stopDemoMode();
|
||||
|
||||
if (mInjector.isWakeLockHeld()) {
|
||||
mInjector.releaseWakeLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshTimeoutConstants() {
|
||||
try {
|
||||
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.
|
||||
}
|
||||
mWarningDialogTimeout = mParser.getLong(KEY_WARNING_DIALOG_TIMEOUT,
|
||||
WARNING_DIALOG_TIMEOUT_DEFAULT);
|
||||
mUserInactivityTimeout = mParser.getLong(KEY_USER_INACTIVITY_TIMEOUT,
|
||||
USER_INACTIVITY_TIMEOUT_DEFAULT);
|
||||
mUserInactivityTimeout = Math.max(mUserInactivityTimeout, USER_INACTIVITY_TIMEOUT_MIN);
|
||||
}
|
||||
}
|
||||
|
||||
private void showInactivityCountdownDialog() {
|
||||
UserInactivityCountdownDialog dialog = new UserInactivityCountdownDialog(getContext(),
|
||||
mWarningDialogTimeout, MILLIS_PER_SECOND);
|
||||
dialog.setNegativeButtonClickListener(null);
|
||||
dialog.setPositiveButtonClickListener(new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
mHandler.sendEmptyMessage(MSG_START_NEW_SESSION);
|
||||
}
|
||||
});
|
||||
dialog.setOnCountDownExpiredListener(new OnCountDownExpiredListener() {
|
||||
@Override
|
||||
public void onCountDownExpired() {
|
||||
mHandler.sendEmptyMessage(MSG_START_NEW_SESSION);
|
||||
}
|
||||
});
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
public RetailDemoModeService(Context context) {
|
||||
this(new Injector(context));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
RetailDemoModeService(Injector injector) {
|
||||
super(injector.getContext());
|
||||
|
||||
mInjector = injector;
|
||||
synchronized (mActivityLock) {
|
||||
mFirstUserActivityTime = mLastUserActivityTime = SystemClock.uptimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
boolean isDemoLauncherDisabled() {
|
||||
int enabledState = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
|
||||
try {
|
||||
final IPackageManager iPm = mInjector.getIPackageManager();
|
||||
final String demoLauncherComponent =
|
||||
getContext().getString(R.string.config_demoModeLauncherComponent);
|
||||
enabledState = iPm.getComponentEnabledSetting(
|
||||
ComponentName.unflattenFromString(demoLauncherComponent), mCurrentUserId);
|
||||
} catch (RemoteException re) {
|
||||
Slog.e(TAG, "Error retrieving demo launcher enabled setting", re);
|
||||
}
|
||||
return enabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
|
||||
}
|
||||
|
||||
private void setupDemoUser(UserInfo userInfo) {
|
||||
final UserManager um = mInjector.getUserManager();
|
||||
final UserHandle user = UserHandle.of(userInfo.id);
|
||||
um.setUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, true, user);
|
||||
um.setUserRestriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, true, user);
|
||||
um.setUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, true, user);
|
||||
um.setUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER, true, user);
|
||||
um.setUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS, true, user);
|
||||
um.setUserRestriction(UserManager.DISALLOW_CONFIG_BLUETOOTH, true, user);
|
||||
// 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
|
||||
um.setUserRestriction(UserManager.DISALLOW_SAFE_BOOT, true, UserHandle.SYSTEM);
|
||||
if (mIsCarrierDemoMode) {
|
||||
// Enable SMS in carrier demo mode.
|
||||
um.setUserRestriction(UserManager.DISALLOW_SMS, false, user);
|
||||
}
|
||||
|
||||
Settings.Secure.putIntForUser(mInjector.getContentResolver(),
|
||||
Settings.Secure.SKIP_FIRST_USE_HINTS, 1, userInfo.id);
|
||||
Settings.Global.putInt(mInjector.getContentResolver(),
|
||||
Settings.Global.PACKAGE_VERIFIER_ENABLE, 0);
|
||||
|
||||
grantRuntimePermissionToCamera(user);
|
||||
clearPrimaryCallLog();
|
||||
|
||||
if (!mIsCarrierDemoMode) {
|
||||
// Enable demo launcher.
|
||||
final String demoLauncher = getContext().getString(
|
||||
R.string.config_demoModeLauncherComponent);
|
||||
if (!TextUtils.isEmpty(demoLauncher)) {
|
||||
final ComponentName componentToEnable =
|
||||
ComponentName.unflattenFromString(demoLauncher);
|
||||
final String packageName = componentToEnable.getPackageName();
|
||||
try {
|
||||
final IPackageManager iPm = AppGlobals.getPackageManager();
|
||||
iPm.setComponentEnabledSetting(componentToEnable,
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0, userInfo.id);
|
||||
iPm.setApplicationEnabledSetting(packageName,
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0, userInfo.id, null);
|
||||
} catch (RemoteException re) {
|
||||
// Internal, shouldn't happen
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Set the carrier demo mode setting for the demo user.
|
||||
final String carrierDemoModeSetting = getContext().getString(
|
||||
R.string.config_carrierDemoModeSetting);
|
||||
Settings.Secure.putIntForUser(getContext().getContentResolver(),
|
||||
carrierDemoModeSetting, 1, userInfo.id);
|
||||
|
||||
// Enable packages for carrier demo mode.
|
||||
final String packageList = getContext().getString(
|
||||
R.string.config_carrierDemoModePackages);
|
||||
final String[] packageNames = packageList == null ? new String[0]
|
||||
: TextUtils.split(packageList, ",");
|
||||
final IPackageManager iPm = AppGlobals.getPackageManager();
|
||||
for (String packageName : packageNames) {
|
||||
try {
|
||||
iPm.setApplicationEnabledSetting(packageName,
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0, userInfo.id, null);
|
||||
} catch (RemoteException re) {
|
||||
Slog.e(TAG, "Error enabling application: " + packageName, re);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void grantRuntimePermissionToCamera(UserHandle user) {
|
||||
final Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
||||
final PackageManager pm = mInjector.getPackageManager();
|
||||
final ResolveInfo handler = pm.resolveActivityAsUser(cameraIntent,
|
||||
PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
|
||||
user.getIdentifier());
|
||||
if (handler == null || handler.activityInfo == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
pm.grantRuntimePermission(handler.activityInfo.packageName,
|
||||
Manifest.permission.ACCESS_FINE_LOCATION, user);
|
||||
} catch (Exception e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
private void clearPrimaryCallLog() {
|
||||
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;
|
||||
try {
|
||||
resolver.delete(uri, null, null);
|
||||
} catch (Exception e) {
|
||||
Slog.w(TAG, "Deleting call log failed: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
void logSessionDuration() {
|
||||
final int sessionDuration;
|
||||
synchronized (mActivityLock) {
|
||||
sessionDuration = (int) ((mLastUserActivityTime - mFirstUserActivityTime) / 1000);
|
||||
}
|
||||
mInjector.logSessionDuration(sessionDuration);
|
||||
}
|
||||
|
||||
private boolean isDeviceProvisioned() {
|
||||
return Settings.Global.getInt(
|
||||
mInjector.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes contents of {@link Environment#getDataPreloadsDirectory()},
|
||||
* but leave {@link Environment#getDataPreloadsFileCacheDirectory()}
|
||||
* @return true if contents was sucessfully deleted
|
||||
*/
|
||||
private boolean deletePreloadsFolderContents() {
|
||||
final File dir = mInjector.getDataPreloadsDirectory();
|
||||
final File[] files = FileUtils.listFilesOrEmpty(dir);
|
||||
final File fileCacheDirectory = mInjector.getDataPreloadsFileCacheDirectory();
|
||||
Slog.i(TAG, "Deleting contents of " + dir);
|
||||
boolean success = true;
|
||||
for (File file : files) {
|
||||
if (file.isFile()) {
|
||||
if (!file.delete()) {
|
||||
success = false;
|
||||
Slog.w(TAG, "Cannot delete file " + file);
|
||||
}
|
||||
} else {
|
||||
// Do not remove file_cache dir
|
||||
if (!file.equals(fileCacheDirectory)) {
|
||||
if (!FileUtils.deleteContentsAndDir(file)) {
|
||||
success = false;
|
||||
Slog.w(TAG, "Cannot delete dir and its content " + file);
|
||||
}
|
||||
} else {
|
||||
Slog.i(TAG, "Skipping directory with file cache " + file);
|
||||
}
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
private void registerBroadcastReceiver() {
|
||||
if (mBroadcastReceiver != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final IntentFilter filter = new IntentFilter();
|
||||
if (!mIsCarrierDemoMode) {
|
||||
filter.addAction(Intent.ACTION_SCREEN_OFF);
|
||||
}
|
||||
filter.addAction(ACTION_RESET_DEMO);
|
||||
mBroadcastReceiver = new IntentReceiver();
|
||||
getContext().registerReceiver(mBroadcastReceiver, filter);
|
||||
}
|
||||
|
||||
private void unregisterBroadcastReceiver() {
|
||||
if (mBroadcastReceiver != null) {
|
||||
getContext().unregisterReceiver(mBroadcastReceiver);
|
||||
mBroadcastReceiver = null;
|
||||
}
|
||||
}
|
||||
|
||||
private String[] getCameraIdsWithFlash() {
|
||||
ArrayList<String> cameraIdsList = new ArrayList<String>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
return cameraIdsList.toArray(new String[cameraIdsList.size()]);
|
||||
}
|
||||
|
||||
private void muteVolumeStreams() {
|
||||
for (int stream : VOLUME_STREAMS_TO_MUTE) {
|
||||
mInjector.getAudioManager().setStreamVolume(stream,
|
||||
mInjector.getAudioManager().getStreamMinVolume(stream), 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void startDemoMode() {
|
||||
mDeviceInDemoMode = true;
|
||||
|
||||
mPreloadAppsInstaller = mInjector.getPreloadAppsInstaller();
|
||||
mInjector.initializeWakeLock();
|
||||
if (mCameraIdsWithFlash == null) {
|
||||
mCameraIdsWithFlash = getCameraIdsWithFlash();
|
||||
}
|
||||
registerBroadcastReceiver();
|
||||
|
||||
final String carrierDemoModeSetting =
|
||||
getContext().getString(R.string.config_carrierDemoModeSetting);
|
||||
mIsCarrierDemoMode = !TextUtils.isEmpty(carrierDemoModeSetting)
|
||||
&& (Settings.Secure.getInt(getContext().getContentResolver(),
|
||||
carrierDemoModeSetting, 0) == 1);
|
||||
|
||||
mInjector.systemPropertiesSet(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED, "1");
|
||||
mHandler.sendEmptyMessage(MSG_START_NEW_SESSION);
|
||||
|
||||
mSafeBootRestrictionInitialState = mInjector.getUserManager().hasUserRestriction(
|
||||
UserManager.DISALLOW_SAFE_BOOT, UserHandle.SYSTEM);
|
||||
mPackageVerifierEnableInitialState = Settings.Global.getInt(mInjector.getContentResolver(),
|
||||
Settings.Global.PACKAGE_VERIFIER_ENABLE, 1);
|
||||
}
|
||||
|
||||
private void stopDemoMode() {
|
||||
mPreloadAppsInstaller = null;
|
||||
mCameraIdsWithFlash = null;
|
||||
mInjector.destroyWakeLock();
|
||||
unregisterBroadcastReceiver();
|
||||
|
||||
if (mDeviceInDemoMode) {
|
||||
mInjector.getUserManager().setUserRestriction(UserManager.DISALLOW_SAFE_BOOT,
|
||||
mSafeBootRestrictionInitialState, UserHandle.SYSTEM);
|
||||
Settings.Global.putInt(mInjector.getContentResolver(),
|
||||
Settings.Global.PACKAGE_VERIFIER_ENABLE,
|
||||
mPackageVerifierEnableInitialState);
|
||||
}
|
||||
|
||||
mDeviceInDemoMode = false;
|
||||
mIsCarrierDemoMode = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Service starting up");
|
||||
}
|
||||
mHandlerThread = new ServiceThread(TAG, android.os.Process.THREAD_PRIORITY_FOREGROUND,
|
||||
false);
|
||||
mHandlerThread.start();
|
||||
mHandler = new MainHandler(mHandlerThread.getLooper());
|
||||
mInjector.publishLocalService(this, mLocalService);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBootPhase(int bootPhase) {
|
||||
switch (bootPhase) {
|
||||
case PHASE_THIRD_PARTY_APPS_CAN_START:
|
||||
final SettingsObserver settingsObserver = new SettingsObserver(mHandler);
|
||||
settingsObserver.register();
|
||||
settingsObserver.refreshTimeoutConstants();
|
||||
break;
|
||||
case PHASE_BOOT_COMPLETED:
|
||||
if (UserManager.isDeviceInDemoMode(getContext())) {
|
||||
startDemoMode();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwitchUser(int userId) {
|
||||
if (!mDeviceInDemoMode) {
|
||||
return;
|
||||
}
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "onSwitchUser: " + 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 (!mIsCarrierDemoMode && !mInjector.isWakeLockHeld()) {
|
||||
mInjector.acquireWakeLock();
|
||||
}
|
||||
mCurrentUserId = userId;
|
||||
mInjector.getActivityManagerInternal().updatePersistentConfigurationForUser(
|
||||
mInjector.getSystemUsersConfiguration(), userId);
|
||||
|
||||
mInjector.turnOffAllFlashLights(mCameraIdsWithFlash);
|
||||
muteVolumeStreams();
|
||||
if (!mInjector.getWifiManager().isWifiEnabled()) {
|
||||
mInjector.getWifiManager().setWifiEnabled(true);
|
||||
}
|
||||
|
||||
// Disable lock screen for demo users.
|
||||
mInjector.getLockPatternUtils().setLockScreenDisabled(true, userId);
|
||||
|
||||
if (!mIsCarrierDemoMode) {
|
||||
// Show reset notification (except in carrier demo mode).
|
||||
mInjector.getNotificationManager().notifyAsUser(TAG, SystemMessage.NOTE_RETAIL_RESET,
|
||||
mInjector.createResetNotification(), UserHandle.of(userId));
|
||||
|
||||
synchronized (mActivityLock) {
|
||||
mUserUntouched = true;
|
||||
}
|
||||
mInjector.logSessionCount(1);
|
||||
mHandler.removeMessages(MSG_INACTIVITY_TIME_OUT);
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mPreloadAppsInstaller.installApps(userId);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private RetailDemoModeServiceInternal mLocalService = new RetailDemoModeServiceInternal() {
|
||||
private static final long USER_ACTIVITY_DEBOUNCE_TIME = 2000;
|
||||
|
||||
@Override
|
||||
public void onUserActivity() {
|
||||
if (!mDeviceInDemoMode || mIsCarrierDemoMode) {
|
||||
return;
|
||||
}
|
||||
long timeOfActivity = SystemClock.uptimeMillis();
|
||||
synchronized (mActivityLock) {
|
||||
if (timeOfActivity < mLastUserActivityTime + USER_ACTIVITY_DEBOUNCE_TIME) {
|
||||
return;
|
||||
}
|
||||
mLastUserActivityTime = timeOfActivity;
|
||||
if (mUserUntouched && isDemoLauncherDisabled()) {
|
||||
Slog.d(TAG, "retail_demo first touch");
|
||||
mUserUntouched = false;
|
||||
mFirstUserActivityTime = timeOfActivity;
|
||||
}
|
||||
}
|
||||
mHandler.removeMessages(MSG_INACTIVITY_TIME_OUT);
|
||||
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 WifiManager mWifiManager;
|
||||
private Configuration mSystemUserConfiguration;
|
||||
private PendingIntent mResetDemoPendingIntent;
|
||||
private PreloadAppsInstaller mPreloadAppsInstaller;
|
||||
|
||||
Injector(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
Context getContext() {
|
||||
return mContext;
|
||||
}
|
||||
|
||||
WifiManager getWifiManager() {
|
||||
if (mWifiManager == null) {
|
||||
mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
|
||||
}
|
||||
return mWifiManager;
|
||||
}
|
||||
|
||||
UserManager getUserManager() {
|
||||
if (mUm == null) {
|
||||
mUm = getContext().getSystemService(UserManager.class);
|
||||
}
|
||||
return mUm;
|
||||
}
|
||||
|
||||
void switchUser(int userId) {
|
||||
if (mAms == null) {
|
||||
mAms = (ActivityManagerService) ActivityManager.getService();
|
||||
}
|
||||
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() {
|
||||
if (mPreloadAppsInstaller == null) {
|
||||
mPreloadAppsInstaller = new PreloadAppsInstaller(getContext());
|
||||
}
|
||||
return mPreloadAppsInstaller;
|
||||
}
|
||||
|
||||
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() {
|
||||
if (mWakeLock == null) {
|
||||
mWakeLock = getPowerManager().newWakeLock(
|
||||
PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, TAG);
|
||||
}
|
||||
}
|
||||
|
||||
void destroyWakeLock() {
|
||||
mWakeLock = null;
|
||||
}
|
||||
|
||||
boolean isWakeLockHeld() {
|
||||
return mWakeLock != null && 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(), SystemNotificationChannels.RETAIL_MODE)
|
||||
.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();
|
||||
}
|
||||
|
||||
File getDataPreloadsFileCacheDirectory() {
|
||||
return Environment.getDataPreloadsFileCacheDirectory();
|
||||
}
|
||||
|
||||
void publishLocalService(RetailDemoModeService service,
|
||||
RetailDemoModeServiceInternal localService) {
|
||||
service.publishLocalService(RetailDemoModeServiceInternal.class, localService);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
/*
|
||||
* 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 android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.os.CountDownTimer;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.internal.R;
|
||||
|
||||
public class UserInactivityCountdownDialog extends AlertDialog {
|
||||
|
||||
private OnCountDownExpiredListener mOnCountDownExpiredListener;
|
||||
private CountDownTimer mCountDownTimer;
|
||||
private long mCountDownDuration;
|
||||
private long mRefreshInterval;
|
||||
|
||||
UserInactivityCountdownDialog(Context context, long duration, long refreshInterval) {
|
||||
super(context);
|
||||
mCountDownDuration = duration;
|
||||
mRefreshInterval = refreshInterval;
|
||||
|
||||
getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
|
||||
WindowManager.LayoutParams attrs = getWindow().getAttributes();
|
||||
attrs.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
|
||||
getWindow().setAttributes(attrs);
|
||||
|
||||
setTitle(R.string.demo_user_inactivity_timeout_title);
|
||||
setMessage(getContext().getString(R.string.demo_user_inactivity_timeout_countdown,
|
||||
duration));
|
||||
}
|
||||
|
||||
public void setOnCountDownExpiredListener(
|
||||
OnCountDownExpiredListener onCountDownExpiredListener) {
|
||||
mOnCountDownExpiredListener = onCountDownExpiredListener;
|
||||
}
|
||||
|
||||
public void setPositiveButtonClickListener(OnClickListener onClickListener) {
|
||||
setButton(Dialog.BUTTON_POSITIVE,
|
||||
getContext().getString(R.string.demo_user_inactivity_timeout_right_button),
|
||||
onClickListener);
|
||||
}
|
||||
|
||||
public void setNegativeButtonClickListener(OnClickListener onClickListener) {
|
||||
setButton(Dialog.BUTTON_NEGATIVE,
|
||||
getContext().getString(R.string.demo_user_inactivity_timeout_left_button),
|
||||
onClickListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
super.show();
|
||||
final TextView messageView = findViewById(R.id.message);
|
||||
messageView.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mCountDownTimer = new CountDownTimer(mCountDownDuration, mRefreshInterval) {
|
||||
|
||||
@Override
|
||||
public void onTick(long millisUntilFinished) {
|
||||
String msg = getContext().getString(
|
||||
R.string.demo_user_inactivity_timeout_countdown,
|
||||
millisUntilFinished / 1000);
|
||||
messageView.setText(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish() {
|
||||
dismiss();
|
||||
if (mOnCountDownExpiredListener != null)
|
||||
mOnCountDownExpiredListener.onCountDownExpired();
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
if (mCountDownTimer != null) {
|
||||
mCountDownTimer.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
interface OnCountDownExpiredListener {
|
||||
void onCountDownExpired();
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
|
||||
services.core \
|
||||
services.devicepolicy \
|
||||
services.net \
|
||||
services.retaildemo \
|
||||
services.usage \
|
||||
guava \
|
||||
android-support-test \
|
||||
|
||||
@@ -19,7 +19,6 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
|
||||
services.core \
|
||||
services.devicepolicy \
|
||||
services.net \
|
||||
services.retaildemo \
|
||||
services.usage \
|
||||
guava \
|
||||
android-support-test \
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
/*
|
||||
* 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.ContextWrapper;
|
||||
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.support.test.runner.AndroidJUnit4;
|
||||
import android.test.mock.MockContentResolver;
|
||||
|
||||
import com.android.internal.util.test.FakeSettingsProvider;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class PreloadAppsInstallerTest {
|
||||
private static final int TEST_DEMO_USER = 111;
|
||||
|
||||
private 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;
|
||||
|
||||
@BeforeClass
|
||||
@AfterClass
|
||||
public static void clearSettingsProvider() {
|
||||
FakeSettingsProvider.clearSettingsProvider();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
|
||||
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() {
|
||||
if (mPreloadsAppsDirectory != null) {
|
||||
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,
|
||||
0 /*installFlags*/, PackageManager.INSTALL_REASON_UNKNOWN);
|
||||
}
|
||||
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,
|
||||
0 /*installFlags*/, PackageManager.INSTALL_REASON_UNKNOWN);
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -1,484 +0,0 @@
|
||||
/*
|
||||
* 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.app.job.JobInfo;
|
||||
import android.app.job.JobScheduler;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
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.net.wifi.WifiManager;
|
||||
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.support.test.runner.AndroidJUnit4;
|
||||
import android.test.mock.MockContentProvider;
|
||||
import android.test.mock.MockContentResolver;
|
||||
import android.util.ArrayMap;
|
||||
|
||||
import com.android.internal.util.test.FakeSettingsProvider;
|
||||
import com.android.internal.widget.LockPatternUtils;
|
||||
import com.android.server.SystemService;
|
||||
import com.android.server.retaildemo.RetailDemoModeService.Injector;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.compat.ArgumentMatcher;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@RunWith(AndroidJUnit4.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 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 WifiManager mWifiManager;
|
||||
private @Mock LockPatternUtils mLockPatternUtils;
|
||||
private @Mock JobScheduler mJobScheduler;
|
||||
private MockPreloadAppsInstaller mPreloadAppsInstaller;
|
||||
private MockContentResolver mContentResolver;
|
||||
private MockContactsProvider mContactsProvider;
|
||||
private Configuration mConfiguration;
|
||||
private File mTestPreloadsDir;
|
||||
private CountDownLatch mLatch;
|
||||
|
||||
private RetailDemoModeService mService;
|
||||
private TestInjector mInjector;
|
||||
|
||||
@BeforeClass
|
||||
@AfterClass
|
||||
public static void clearSettingsProvider() {
|
||||
FakeSettingsProvider.clearSettingsProvider();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
|
||||
when(mContext.getSystemServiceName(eq(JobScheduler.class))).thenReturn(
|
||||
Context.JOB_SCHEDULER_SERVICE);
|
||||
when(mContext.getSystemService(Context.JOB_SCHEDULER_SERVICE)).thenReturn(mJobScheduler);
|
||||
when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUm);
|
||||
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 MockPreloadAppsInstaller(mContext);
|
||||
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() {
|
||||
if (mTestPreloadsDir != null) {
|
||||
FileUtils.deleteContentsAndDir(mTestPreloadsDir);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDemoUserSetup() throws Exception {
|
||||
mService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
|
||||
|
||||
mLatch = new CountDownLatch(1);
|
||||
final UserInfo userInfo = new UserInfo();
|
||||
userInfo.id = TEST_DEMO_USER;
|
||||
when(mUm.createUser(anyString(), anyInt())).thenReturn(userInfo);
|
||||
|
||||
setCameraPackage(TEST_CAMERA_PKG);
|
||||
mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
|
||||
assertEquals(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED + " property not set",
|
||||
"1", mInjector.systemPropertiesGet(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED));
|
||||
|
||||
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));
|
||||
|
||||
// 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_disableDemoMode() throws Exception {
|
||||
final RetailDemoModeService.SettingsObserver observer =
|
||||
mService.new SettingsObserver(new Handler(Looper.getMainLooper()));
|
||||
final Uri deviceDemoModeUri = Settings.Global.getUriFor(Settings.Global.DEVICE_DEMO_MODE);
|
||||
when(mUm.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT, UserHandle.SYSTEM))
|
||||
.thenReturn(false);
|
||||
Settings.Global.putInt(mContentResolver, Settings.Global.PACKAGE_VERIFIER_ENABLE, 1);
|
||||
// Settings.Global.DEVICE_DEMO_MODE has been set to 1 initially.
|
||||
observer.onChange(false, deviceDemoModeUri);
|
||||
final ArgumentCaptor<BroadcastReceiver> receiver =
|
||||
ArgumentCaptor.forClass(BroadcastReceiver.class);
|
||||
verify(mContext).registerReceiver(receiver.capture(), any(IntentFilter.class));
|
||||
|
||||
Settings.Global.putInt(mContentResolver, Settings.Global.PACKAGE_VERIFIER_ENABLE, 0);
|
||||
new File(mTestPreloadsDir, "dir1").mkdirs();
|
||||
new File(mTestPreloadsDir, "file1").createNewFile();
|
||||
Settings.Global.putInt(mContentResolver, Settings.Global.DEVICE_DEMO_MODE, 0);
|
||||
observer.onChange(false, deviceDemoModeUri);
|
||||
verify(mContext).unregisterReceiver(receiver.getValue());
|
||||
verify(mUm).setUserRestriction(UserManager.DISALLOW_SAFE_BOOT, false, UserHandle.SYSTEM);
|
||||
assertEquals("Package verifier enable value has not been reset", 1,
|
||||
Settings.Global.getInt(mContentResolver, Settings.Global.PACKAGE_VERIFIER_ENABLE));
|
||||
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);
|
||||
// Verify that the expiration job was scheduled
|
||||
verify(mJobScheduler).schedule(any(JobInfo.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSettingsObserver_enableDemoMode() 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));
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
@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);
|
||||
when(mWifiManager.isWifiEnabled()).thenReturn(false);
|
||||
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);
|
||||
verify(mWifiManager).setWifiEnabled(true);
|
||||
}
|
||||
|
||||
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 matchesObject(Object argument) {
|
||||
if (argument instanceof Intent) {
|
||||
return ((Intent) argument).filterEquals(mIntent);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "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 MockPreloadAppsInstaller extends PreloadAppsInstaller {
|
||||
MockPreloadAppsInstaller(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void installApps(int userId) {
|
||||
}
|
||||
}
|
||||
|
||||
private class TestInjector extends Injector {
|
||||
private ArrayMap<String, String> mSystemProperties = new ArrayMap<>();
|
||||
|
||||
TestInjector() {
|
||||
super(mContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
Context getContext() {
|
||||
return mContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
UserManager getUserManager() {
|
||||
return mUm;
|
||||
}
|
||||
|
||||
@Override
|
||||
WifiManager getWifiManager() {
|
||||
return mWifiManager;
|
||||
}
|
||||
|
||||
@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
|
||||
void destroyWakeLock() {
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
@Override
|
||||
File getDataPreloadsFileCacheDirectory() {
|
||||
return new File(mTestPreloadsDir, "file_cache");
|
||||
}
|
||||
|
||||
@Override
|
||||
void publishLocalService(RetailDemoModeService service,
|
||||
RetailDemoModeServiceInternal localService) {
|
||||
}
|
||||
|
||||
String systemPropertiesGet(String key) {
|
||||
return mSystemProperties.get(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user