| /* |
| * 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); |
| } |
| } |
| } |