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