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