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