RetailDemoModeService.java revision 707a9fc0027b76942e07f3c1aca8ff3db46b719c
1/* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17package com.android.server.retaildemo; 18 19import android.Manifest; 20import android.app.ActivityManagerInternal; 21import android.app.ActivityManagerNative; 22import android.app.AppGlobals; 23import android.app.Notification; 24import android.app.NotificationManager; 25import android.app.PendingIntent; 26import android.app.RetailDemoModeServiceInternal; 27import android.content.BroadcastReceiver; 28import android.content.ComponentName; 29import android.content.ContentResolver; 30import android.content.Context; 31import android.content.DialogInterface; 32import android.content.Intent; 33import android.content.IntentFilter; 34import android.content.pm.IPackageManager; 35import android.content.pm.PackageManager; 36import android.content.pm.ResolveInfo; 37import android.content.pm.UserInfo; 38import android.content.res.Configuration; 39import android.database.ContentObserver; 40import android.hardware.camera2.CameraAccessException; 41import android.hardware.camera2.CameraCharacteristics; 42import android.hardware.camera2.CameraManager; 43import android.media.AudioManager; 44import android.media.AudioSystem; 45import android.net.Uri; 46import android.os.Environment; 47import android.os.FileUtils; 48import android.os.Handler; 49import android.os.Looper; 50import android.os.Message; 51import android.os.PowerManager; 52import android.os.RemoteException; 53import android.os.SystemClock; 54import android.os.SystemProperties; 55import android.os.UserHandle; 56import android.os.UserManager; 57import android.provider.MediaStore; 58import android.provider.Settings; 59import android.util.KeyValueListParser; 60import android.util.Slog; 61import com.android.internal.os.BackgroundThread; 62import com.android.internal.R; 63import com.android.internal.annotations.GuardedBy; 64import com.android.internal.logging.MetricsLogger; 65import com.android.internal.widget.LockPatternUtils; 66import com.android.server.LocalServices; 67import com.android.server.ServiceThread; 68import com.android.server.SystemService; 69import com.android.server.am.ActivityManagerService; 70import com.android.server.retaildemo.UserInactivityCountdownDialog.OnCountDownExpiredListener; 71 72import java.io.File; 73import java.util.ArrayList; 74 75public class RetailDemoModeService extends SystemService { 76 private static final boolean DEBUG = false; 77 78 private static final String TAG = RetailDemoModeService.class.getSimpleName(); 79 private static final String DEMO_USER_NAME = "Demo"; 80 private static final String ACTION_RESET_DEMO = 81 "com.android.server.retaildemo.ACTION_RESET_DEMO"; 82 private static final String SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED = "sys.retaildemo.enabled"; 83 84 private static final int MSG_TURN_SCREEN_ON = 0; 85 private static final int MSG_INACTIVITY_TIME_OUT = 1; 86 private static final int MSG_START_NEW_SESSION = 2; 87 88 private static final long SCREEN_WAKEUP_DELAY = 2500; 89 private static final long USER_INACTIVITY_TIMEOUT_MIN = 10000; 90 private static final long USER_INACTIVITY_TIMEOUT_DEFAULT = 30000; 91 private static final long WARNING_DIALOG_TIMEOUT_DEFAULT = 6000; 92 private static final long MILLIS_PER_SECOND = 1000; 93 94 private static final int[] VOLUME_STREAMS_TO_MUTE = { 95 AudioSystem.STREAM_RING, 96 AudioSystem.STREAM_MUSIC 97 }; 98 99 // Tron Vars 100 private static final String DEMO_SESSION_COUNT = "retail_demo_session_count"; 101 private static final String DEMO_SESSION_DURATION = "retail_demo_session_duration"; 102 103 boolean mDeviceInDemoMode = false; 104 int mCurrentUserId = UserHandle.USER_SYSTEM; 105 long mUserInactivityTimeout; 106 long mWarningDialogTimeout; 107 private ActivityManagerService mAms; 108 private ActivityManagerInternal mAmi; 109 private AudioManager mAudioManager; 110 private NotificationManager mNm; 111 private UserManager mUm; 112 private PowerManager mPm; 113 private PowerManager.WakeLock mWakeLock; 114 Handler mHandler; 115 private ServiceThread mHandlerThread; 116 private PendingIntent mResetDemoPendingIntent; 117 private CameraManager mCameraManager; 118 private String[] mCameraIdsWithFlash; 119 private Configuration mSystemUserConfiguration; 120 private PreloadAppsInstaller mPreloadAppsInstaller; 121 122 final Object mActivityLock = new Object(); 123 // Whether the newly created demo user has interacted with the screen yet 124 @GuardedBy("mActivityLock") 125 boolean mUserUntouched; 126 @GuardedBy("mActivityLock") 127 long mFirstUserActivityTime; 128 @GuardedBy("mActivityLock") 129 long mLastUserActivityTime; 130 131 private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 132 @Override 133 public void onReceive(Context context, Intent intent) { 134 if (!mDeviceInDemoMode) { 135 return; 136 } 137 switch (intent.getAction()) { 138 case Intent.ACTION_SCREEN_OFF: 139 mHandler.removeMessages(MSG_TURN_SCREEN_ON); 140 mHandler.sendEmptyMessageDelayed(MSG_TURN_SCREEN_ON, SCREEN_WAKEUP_DELAY); 141 break; 142 case ACTION_RESET_DEMO: 143 mHandler.sendEmptyMessage(MSG_START_NEW_SESSION); 144 break; 145 } 146 } 147 }; 148 149 final class MainHandler extends Handler { 150 151 MainHandler(Looper looper) { 152 super(looper, null, true); 153 } 154 155 @Override 156 public void handleMessage(Message msg) { 157 switch (msg.what) { 158 case MSG_TURN_SCREEN_ON: 159 if (mWakeLock.isHeld()) { 160 mWakeLock.release(); 161 } 162 mWakeLock.acquire(); 163 break; 164 case MSG_INACTIVITY_TIME_OUT: 165 if (isDemoLauncherDisabled()) { 166 Slog.i(TAG, "User inactivity timeout reached"); 167 showInactivityCountdownDialog(); 168 } 169 break; 170 case MSG_START_NEW_SESSION: 171 if (DEBUG) { 172 Slog.d(TAG, "Switching to a new demo user"); 173 } 174 removeMessages(MSG_START_NEW_SESSION); 175 removeMessages(MSG_INACTIVITY_TIME_OUT); 176 if (mCurrentUserId != UserHandle.USER_SYSTEM) { 177 logSessionDuration(); 178 } 179 final UserInfo demoUser = getUserManager().createUser(DEMO_USER_NAME, 180 UserInfo.FLAG_DEMO | UserInfo.FLAG_EPHEMERAL); 181 if (demoUser != null) { 182 setupDemoUser(demoUser); 183 getActivityManager().switchUser(demoUser.id); 184 } 185 break; 186 } 187 } 188 } 189 190 private class SettingsObserver extends ContentObserver { 191 192 private final static String KEY_USER_INACTIVITY_TIMEOUT = "user_inactivity_timeout_ms"; 193 private final static String KEY_WARNING_DIALOG_TIMEOUT = "warning_dialog_timeout_ms"; 194 195 private final Uri mDeviceDemoModeUri = Settings.Global 196 .getUriFor(Settings.Global.DEVICE_DEMO_MODE); 197 private final Uri mDeviceProvisionedUri = Settings.Global 198 .getUriFor(Settings.Global.DEVICE_PROVISIONED); 199 private final Uri mRetailDemoConstantsUri = Settings.Global 200 .getUriFor(Settings.Global.RETAIL_DEMO_MODE_CONSTANTS); 201 202 private final KeyValueListParser mParser = new KeyValueListParser(','); 203 204 public SettingsObserver(Handler handler) { 205 super(handler); 206 } 207 208 public void register() { 209 ContentResolver cr = getContext().getContentResolver(); 210 cr.registerContentObserver(mDeviceDemoModeUri, false, this, UserHandle.USER_SYSTEM); 211 cr.registerContentObserver(mDeviceProvisionedUri, false, this, UserHandle.USER_SYSTEM); 212 cr.registerContentObserver(mRetailDemoConstantsUri, false, this, 213 UserHandle.USER_SYSTEM); 214 } 215 216 @Override 217 public void onChange(boolean selfChange, Uri uri) { 218 if (mRetailDemoConstantsUri.equals(uri)) { 219 refreshTimeoutConstants(); 220 return; 221 } 222 if (mDeviceDemoModeUri.equals(uri)) { 223 mDeviceInDemoMode = UserManager.isDeviceInDemoMode(getContext()); 224 if (mDeviceInDemoMode) { 225 SystemProperties.set(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED, "1"); 226 mHandler.sendEmptyMessage(MSG_START_NEW_SESSION); 227 } else { 228 SystemProperties.set(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED, "0"); 229 if (mWakeLock.isHeld()) { 230 mWakeLock.release(); 231 } 232 } 233 } 234 // If device is provisioned and left demo mode - run the cleanup in demo folder 235 if (!mDeviceInDemoMode && isDeviceProvisioned()) { 236 // Run on the bg thread to not block the fg thread 237 BackgroundThread.getHandler().post(new Runnable() { 238 @Override 239 public void run() { 240 if (!deletePreloadsFolderContents()) { 241 Slog.w(TAG, "Failed to delete preloads folder contents"); 242 } 243 } 244 }); 245 } 246 } 247 248 private void refreshTimeoutConstants() { 249 try { 250 mParser.setString(Settings.Global.getString(getContext().getContentResolver(), 251 Settings.Global.RETAIL_DEMO_MODE_CONSTANTS)); 252 } catch (IllegalArgumentException exc) { 253 Slog.e(TAG, "Invalid string passed to KeyValueListParser"); 254 // Consuming the exception to fall back to default values. 255 } 256 mWarningDialogTimeout = mParser.getLong(KEY_WARNING_DIALOG_TIMEOUT, 257 WARNING_DIALOG_TIMEOUT_DEFAULT); 258 mUserInactivityTimeout = mParser.getLong(KEY_USER_INACTIVITY_TIMEOUT, 259 USER_INACTIVITY_TIMEOUT_DEFAULT); 260 mUserInactivityTimeout = Math.max(mUserInactivityTimeout, USER_INACTIVITY_TIMEOUT_MIN); 261 } 262 } 263 264 private void showInactivityCountdownDialog() { 265 UserInactivityCountdownDialog dialog = new UserInactivityCountdownDialog(getContext(), 266 mWarningDialogTimeout, MILLIS_PER_SECOND); 267 dialog.setNegativeButtonClickListener(null); 268 dialog.setPositiveButtonClickListener(new DialogInterface.OnClickListener() { 269 @Override 270 public void onClick(DialogInterface dialog, int which) { 271 mHandler.sendEmptyMessage(MSG_START_NEW_SESSION); 272 } 273 }); 274 dialog.setOnCountDownExpiredListener(new OnCountDownExpiredListener() { 275 @Override 276 public void onCountDownExpired() { 277 mHandler.sendEmptyMessage(MSG_START_NEW_SESSION); 278 } 279 }); 280 dialog.show(); 281 } 282 283 public RetailDemoModeService(Context context) { 284 super(context); 285 synchronized (mActivityLock) { 286 mFirstUserActivityTime = mLastUserActivityTime = SystemClock.uptimeMillis(); 287 } 288 mPreloadAppsInstaller = new PreloadAppsInstaller(); 289 } 290 291 private Notification createResetNotification() { 292 return new Notification.Builder(getContext()) 293 .setContentTitle(getContext().getString(R.string.reset_retail_demo_mode_title)) 294 .setContentText(getContext().getString(R.string.reset_retail_demo_mode_text)) 295 .setOngoing(true) 296 .setSmallIcon(R.drawable.platlogo) 297 .setShowWhen(false) 298 .setVisibility(Notification.VISIBILITY_PUBLIC) 299 .setContentIntent(getResetDemoPendingIntent()) 300 .setColor(getContext().getColor(R.color.system_notification_accent_color)) 301 .build(); 302 } 303 304 private PendingIntent getResetDemoPendingIntent() { 305 if (mResetDemoPendingIntent == null) { 306 Intent intent = new Intent(ACTION_RESET_DEMO); 307 mResetDemoPendingIntent = PendingIntent.getBroadcast(getContext(), 0, intent, 0); 308 } 309 return mResetDemoPendingIntent; 310 } 311 312 boolean isDemoLauncherDisabled() { 313 IPackageManager pm = AppGlobals.getPackageManager(); 314 int enabledState = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; 315 String demoLauncherComponent = getContext().getResources() 316 .getString(R.string.config_demoModeLauncherComponent); 317 try { 318 enabledState = pm.getComponentEnabledSetting( 319 ComponentName.unflattenFromString(demoLauncherComponent), 320 mCurrentUserId); 321 } catch (RemoteException exc) { 322 Slog.e(TAG, "Unable to talk to Package Manager", exc); 323 } 324 return enabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED; 325 } 326 327 private void setupDemoUser(UserInfo userInfo) { 328 UserManager um = getUserManager(); 329 UserHandle user = UserHandle.of(userInfo.id); 330 um.setUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, true, user); 331 um.setUserRestriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, true, user); 332 um.setUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, true, user); 333 um.setUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER, true, user); 334 um.setUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS, true, user); 335 um.setUserRestriction(UserManager.DISALLOW_CONFIG_BLUETOOTH, true, user); 336 // Disallow rebooting in safe mode - controlled by user 0 337 getUserManager().setUserRestriction(UserManager.DISALLOW_SAFE_BOOT, true, 338 UserHandle.SYSTEM); 339 Settings.Secure.putIntForUser(getContext().getContentResolver(), 340 Settings.Secure.SKIP_FIRST_USE_HINTS, 1, userInfo.id); 341 Settings.Secure.putIntForUser(getContext().getContentResolver(), 342 Settings.Global.PACKAGE_VERIFIER_ENABLE, 0, userInfo.id); 343 344 grantRuntimePermissionToCamera(userInfo.getUserHandle()); 345 } 346 347 private void grantRuntimePermissionToCamera(UserHandle user) { 348 final Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); 349 final PackageManager pm = getContext().getPackageManager(); 350 final ResolveInfo handler = pm.resolveActivityAsUser(cameraIntent, 351 PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, 352 user.getIdentifier()); 353 if (handler == null || handler.activityInfo == null) { 354 return; 355 } 356 try { 357 pm.grantRuntimePermission(handler.activityInfo.packageName, 358 Manifest.permission.ACCESS_FINE_LOCATION, user); 359 } catch (Exception e) { 360 // Ignore 361 } 362 363 } 364 365 void logSessionDuration() { 366 final int sessionDuration; 367 synchronized (mActivityLock) { 368 sessionDuration = (int) ((mLastUserActivityTime - mFirstUserActivityTime) / 1000); 369 } 370 MetricsLogger.histogram(getContext(), DEMO_SESSION_DURATION, sessionDuration); 371 } 372 373 private ActivityManagerService getActivityManager() { 374 if (mAms == null) { 375 mAms = (ActivityManagerService) ActivityManagerNative.getDefault(); 376 } 377 return mAms; 378 } 379 380 private UserManager getUserManager() { 381 if (mUm == null) { 382 mUm = getContext().getSystemService(UserManager.class); 383 } 384 return mUm; 385 } 386 387 private AudioManager getAudioManager() { 388 if (mAudioManager == null) { 389 mAudioManager = getContext().getSystemService(AudioManager.class); 390 } 391 return mAudioManager; 392 } 393 394 private boolean isDeviceProvisioned() { 395 return Settings.Global.getInt( 396 getContext().getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0; 397 } 398 399 private boolean deletePreloadsFolderContents() { 400 final File dir = Environment.getDataPreloadsDirectory(); 401 Slog.i(TAG, "Deleting contents of " + dir); 402 return FileUtils.deleteContents(dir); 403 } 404 405 private void registerBroadcastReceiver() { 406 final IntentFilter filter = new IntentFilter(); 407 filter.addAction(Intent.ACTION_SCREEN_OFF); 408 filter.addAction(ACTION_RESET_DEMO); 409 getContext().registerReceiver(mBroadcastReceiver, filter); 410 } 411 412 private String[] getCameraIdsWithFlash() { 413 ArrayList<String> cameraIdsList = new ArrayList<String>(); 414 try { 415 for (String cameraId : mCameraManager.getCameraIdList()) { 416 CameraCharacteristics c = mCameraManager.getCameraCharacteristics(cameraId); 417 if (Boolean.TRUE.equals(c.get(CameraCharacteristics.FLASH_INFO_AVAILABLE))) { 418 cameraIdsList.add(cameraId); 419 } 420 } 421 } catch (CameraAccessException e) { 422 Slog.e(TAG, "Unable to access camera while getting camera id list", e); 423 } 424 return cameraIdsList.toArray(new String[cameraIdsList.size()]); 425 } 426 427 private void turnOffAllFlashLights() { 428 for (String cameraId : mCameraIdsWithFlash) { 429 try { 430 mCameraManager.setTorchMode(cameraId, false); 431 } catch (CameraAccessException e) { 432 Slog.e(TAG, "Unable to access camera " + cameraId + " while turning off flash", e); 433 } 434 } 435 } 436 437 private void muteVolumeStreams() { 438 for (int stream : VOLUME_STREAMS_TO_MUTE) { 439 getAudioManager().setStreamVolume(stream, getAudioManager().getStreamMinVolume(stream), 440 0); 441 } 442 } 443 444 private Configuration getSystemUsersConfiguration() { 445 if (mSystemUserConfiguration == null) { 446 Settings.System.getConfiguration(getContext().getContentResolver(), 447 mSystemUserConfiguration = new Configuration()); 448 } 449 return mSystemUserConfiguration; 450 } 451 452 @Override 453 public void onStart() { 454 if (DEBUG) { 455 Slog.d(TAG, "Service starting up"); 456 } 457 mHandlerThread = new ServiceThread(TAG, android.os.Process.THREAD_PRIORITY_FOREGROUND, 458 false); 459 mHandlerThread.start(); 460 mHandler = new MainHandler(mHandlerThread.getLooper()); 461 publishLocalService(RetailDemoModeServiceInternal.class, mLocalService); 462 } 463 464 @Override 465 public void onBootPhase(int bootPhase) { 466 if (bootPhase != PHASE_THIRD_PARTY_APPS_CAN_START) { 467 return; 468 } 469 mPm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE); 470 mAmi = LocalServices.getService(ActivityManagerInternal.class); 471 mWakeLock = mPm 472 .newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, TAG); 473 mNm = NotificationManager.from(getContext()); 474 mCameraManager = (CameraManager) getContext().getSystemService(Context.CAMERA_SERVICE); 475 mCameraIdsWithFlash = getCameraIdsWithFlash(); 476 477 if (UserManager.isDeviceInDemoMode(getContext())) { 478 mDeviceInDemoMode = true; 479 SystemProperties.set(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED, "1"); 480 mHandler.sendEmptyMessage(MSG_START_NEW_SESSION); 481 } 482 SettingsObserver settingsObserver = new SettingsObserver(mHandler); 483 settingsObserver.register(); 484 settingsObserver.refreshTimeoutConstants(); 485 registerBroadcastReceiver(); 486 } 487 488 @Override 489 public void onSwitchUser(int userId) { 490 if (!mDeviceInDemoMode) { 491 return; 492 } 493 if (DEBUG) { 494 Slog.d(TAG, "onSwitchUser: " + userId); 495 } 496 final UserInfo ui = getUserManager().getUserInfo(userId); 497 if (!ui.isDemo()) { 498 Slog.wtf(TAG, "Should not allow switch to non-demo user in demo mode"); 499 return; 500 } 501 if (!mWakeLock.isHeld()) { 502 mWakeLock.acquire(); 503 } 504 mCurrentUserId = userId; 505 mAmi.updatePersistentConfigurationForUser(getSystemUsersConfiguration(), userId); 506 turnOffAllFlashLights(); 507 muteVolumeStreams(); 508 // Disable lock screen for demo users. 509 LockPatternUtils lockPatternUtils = new LockPatternUtils(getContext()); 510 lockPatternUtils.setLockScreenDisabled(true, userId); 511 mNm.notifyAsUser(TAG, 1, createResetNotification(), UserHandle.of(userId)); 512 513 synchronized (mActivityLock) { 514 mUserUntouched = true; 515 } 516 MetricsLogger.count(getContext(), DEMO_SESSION_COUNT, 1); 517 mHandler.removeMessages(MSG_INACTIVITY_TIME_OUT); 518 mHandler.post(new Runnable() { 519 @Override 520 public void run() { 521 mPreloadAppsInstaller.installApps(userId); 522 } 523 }); 524 } 525 526 private RetailDemoModeServiceInternal mLocalService = new RetailDemoModeServiceInternal() { 527 private static final long USER_ACTIVITY_DEBOUNCE_TIME = 2000; 528 529 @Override 530 public void onUserActivity() { 531 if (!mDeviceInDemoMode) { 532 return; 533 } 534 long timeOfActivity = SystemClock.uptimeMillis(); 535 synchronized (mActivityLock) { 536 if (timeOfActivity < mLastUserActivityTime + USER_ACTIVITY_DEBOUNCE_TIME) { 537 return; 538 } 539 mLastUserActivityTime = timeOfActivity; 540 if (mUserUntouched && isDemoLauncherDisabled()) { 541 Slog.d(TAG, "retail_demo first touch"); 542 mUserUntouched = false; 543 mFirstUserActivityTime = timeOfActivity; 544 } 545 } 546 mHandler.removeMessages(MSG_INACTIVITY_TIME_OUT); 547 mHandler.sendEmptyMessageDelayed(MSG_INACTIVITY_TIME_OUT, mUserInactivityTimeout); 548 } 549 }; 550} 551