RetailDemoModeService.java revision b75fc2b2b7ec5e3d8f45bcd9c7078ec0a8c143fe
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.app.ActivityManagerInternal; 20import android.app.ActivityManagerNative; 21import android.app.AppGlobals; 22import android.app.Notification; 23import android.app.NotificationManager; 24import android.app.PendingIntent; 25import android.app.RetailDemoModeServiceInternal; 26import android.content.BroadcastReceiver; 27import android.content.ComponentName; 28import android.content.ContentResolver; 29import android.content.Context; 30import android.content.DialogInterface; 31import android.content.Intent; 32import android.content.IntentFilter; 33import android.content.pm.IPackageManager; 34import android.content.pm.PackageManager; 35import android.content.pm.UserInfo; 36import android.content.res.Configuration; 37import android.database.ContentObserver; 38import android.hardware.camera2.CameraAccessException; 39import android.hardware.camera2.CameraCharacteristics; 40import android.hardware.camera2.CameraManager; 41import android.media.AudioManager; 42import android.media.AudioSystem; 43import android.net.Uri; 44import android.os.Environment; 45import android.os.FileUtils; 46import android.os.Handler; 47import android.os.Looper; 48import android.os.Message; 49import android.os.PowerManager; 50import android.os.RemoteException; 51import android.os.SystemClock; 52import android.os.UserHandle; 53import android.os.UserManager; 54import android.provider.Settings; 55import android.util.Slog; 56import com.android.internal.os.BackgroundThread; 57import com.android.internal.R; 58import com.android.internal.annotations.GuardedBy; 59import com.android.internal.logging.MetricsLogger; 60import com.android.internal.widget.LockPatternUtils; 61import com.android.server.LocalServices; 62import com.android.server.ServiceThread; 63import com.android.server.SystemService; 64import com.android.server.am.ActivityManagerService; 65import com.android.server.retaildemo.UserInactivityCountdownDialog.OnCountDownExpiredListener; 66 67import java.io.File; 68import java.util.ArrayList; 69 70public class RetailDemoModeService extends SystemService { 71 private static final boolean DEBUG = false; 72 73 private static final String TAG = RetailDemoModeService.class.getSimpleName(); 74 private static final String DEMO_USER_NAME = "Demo"; 75 private static final String ACTION_RESET_DEMO = "com.android.server.am.ACTION_RESET_DEMO"; 76 77 private static final int MSG_TURN_SCREEN_ON = 0; 78 private static final int MSG_INACTIVITY_TIME_OUT = 1; 79 private static final int MSG_START_NEW_SESSION = 2; 80 81 private static final long SCREEN_WAKEUP_DELAY = 2500; 82 private static final long USER_INACTIVITY_TIMEOUT = 30000; 83 private static final long WARNING_DIALOG_TIMEOUT = 6000; 84 private static final long MILLIS_PER_SECOND = 1000; 85 86 private static final int[] VOLUME_STREAMS_TO_MUTE = { 87 AudioSystem.STREAM_RING, 88 AudioSystem.STREAM_MUSIC 89 }; 90 91 // Tron Vars 92 private static final String DEMO_SESSION_COUNT = "retail_demo_session_count"; 93 private static final String DEMO_SESSION_DURATION = "retail_demo_session_duration"; 94 95 boolean mDeviceInDemoMode = false; 96 int mCurrentUserId = UserHandle.USER_SYSTEM; 97 private ActivityManagerService mAms; 98 private ActivityManagerInternal mAmi; 99 private AudioManager mAudioManager; 100 private NotificationManager mNm; 101 private UserManager mUm; 102 private PowerManager mPm; 103 private PowerManager.WakeLock mWakeLock; 104 Handler mHandler; 105 private ServiceThread mHandlerThread; 106 private PendingIntent mResetDemoPendingIntent; 107 private CameraManager mCameraManager; 108 private String[] mCameraIdsWithFlash; 109 private Configuration mPrimaryUserConfiguration; 110 111 final Object mActivityLock = new Object(); 112 // Whether the newly created demo user has interacted with the screen yet 113 @GuardedBy("mActivityLock") 114 boolean mUserUntouched; 115 @GuardedBy("mActivityLock") 116 long mFirstUserActivityTime; 117 @GuardedBy("mActivityLock") 118 long mLastUserActivityTime; 119 120 private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 121 @Override 122 public void onReceive(Context context, Intent intent) { 123 if (!mDeviceInDemoMode) { 124 return; 125 } 126 switch (intent.getAction()) { 127 case Intent.ACTION_SCREEN_OFF: 128 mHandler.removeMessages(MSG_TURN_SCREEN_ON); 129 mHandler.sendEmptyMessageDelayed(MSG_TURN_SCREEN_ON, SCREEN_WAKEUP_DELAY); 130 break; 131 case ACTION_RESET_DEMO: 132 mHandler.sendEmptyMessage(MSG_START_NEW_SESSION); 133 break; 134 } 135 } 136 }; 137 138 final class MainHandler extends Handler { 139 140 MainHandler(Looper looper) { 141 super(looper, null, true); 142 } 143 144 @Override 145 public void handleMessage(Message msg) { 146 switch (msg.what) { 147 case MSG_TURN_SCREEN_ON: 148 if (mWakeLock.isHeld()) { 149 mWakeLock.release(); 150 } 151 mWakeLock.acquire(); 152 break; 153 case MSG_INACTIVITY_TIME_OUT: 154 if (isDemoLauncherDisabled()) { 155 Slog.i(TAG, "User inactivity timeout reached"); 156 showInactivityCountdownDialog(); 157 } 158 break; 159 case MSG_START_NEW_SESSION: 160 if (DEBUG) { 161 Slog.d(TAG, "Switching to a new demo user"); 162 } 163 removeMessages(MSG_START_NEW_SESSION); 164 removeMessages(MSG_INACTIVITY_TIME_OUT); 165 if (mCurrentUserId != UserHandle.USER_SYSTEM) { 166 logSessionDuration(); 167 } 168 final UserInfo demoUser = getUserManager().createUser(DEMO_USER_NAME, 169 UserInfo.FLAG_DEMO | UserInfo.FLAG_EPHEMERAL); 170 if (demoUser != null) { 171 setupDemoUser(demoUser); 172 getActivityManager().switchUser(demoUser.id); 173 } 174 break; 175 } 176 } 177 } 178 179 private void showInactivityCountdownDialog() { 180 UserInactivityCountdownDialog dialog = new UserInactivityCountdownDialog(getContext(), 181 WARNING_DIALOG_TIMEOUT, MILLIS_PER_SECOND); 182 dialog.setPositiveButtonClickListener(null); 183 dialog.setNegativeButtonClickListener(new DialogInterface.OnClickListener() { 184 @Override 185 public void onClick(DialogInterface dialog, int which) { 186 mHandler.sendEmptyMessage(MSG_START_NEW_SESSION); 187 } 188 }); 189 dialog.setOnCountDownExpiredListener(new OnCountDownExpiredListener() { 190 @Override 191 public void onCountDownExpired() { 192 mHandler.sendEmptyMessage(MSG_START_NEW_SESSION); 193 } 194 }); 195 dialog.show(); 196 } 197 198 public RetailDemoModeService(Context context) { 199 super(context); 200 synchronized (mActivityLock) { 201 mFirstUserActivityTime = mLastUserActivityTime = SystemClock.uptimeMillis(); 202 } 203 } 204 205 private Notification createResetNotification() { 206 return new Notification.Builder(getContext()) 207 .setContentTitle(getContext().getString(R.string.reset_retail_demo_mode_title)) 208 .setContentText(getContext().getString(R.string.reset_retail_demo_mode_text)) 209 .setOngoing(true) 210 .setSmallIcon(R.drawable.platlogo) 211 .setShowWhen(false) 212 .setVisibility(Notification.VISIBILITY_PUBLIC) 213 .setContentIntent(getResetDemoPendingIntent()) 214 .setColor(getContext().getColor(R.color.system_notification_accent_color)) 215 .build(); 216 } 217 218 private PendingIntent getResetDemoPendingIntent() { 219 if (mResetDemoPendingIntent == null) { 220 Intent intent = new Intent(ACTION_RESET_DEMO); 221 mResetDemoPendingIntent = PendingIntent.getBroadcast(getContext(), 0, intent, 0); 222 } 223 return mResetDemoPendingIntent; 224 } 225 226 boolean isDemoLauncherDisabled() { 227 IPackageManager pm = AppGlobals.getPackageManager(); 228 int enabledState = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; 229 String demoLauncherComponent = getContext().getResources() 230 .getString(R.string.config_demoModeLauncherComponent); 231 try { 232 enabledState = pm.getComponentEnabledSetting( 233 ComponentName.unflattenFromString(demoLauncherComponent), 234 mCurrentUserId); 235 } catch (RemoteException exc) { 236 Slog.e(TAG, "Unable to talk to Package Manager", exc); 237 } 238 return enabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED; 239 } 240 241 private void setupDemoUser(UserInfo userInfo) { 242 UserManager um = getUserManager(); 243 UserHandle user = UserHandle.of(userInfo.id); 244 LockPatternUtils lockPatternUtils = new LockPatternUtils(getContext()); 245 lockPatternUtils.setLockScreenDisabled(true, userInfo.id); 246 um.setUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, true, user); 247 um.setUserRestriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, true, user); 248 um.setUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, true, user); 249 um.setUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER, true, user); 250 Settings.Secure.putIntForUser(getContext().getContentResolver(), 251 Settings.Secure.SKIP_FIRST_USE_HINTS, 1, userInfo.id); 252 } 253 254 void logSessionDuration() { 255 final int sessionDuration; 256 synchronized (mActivityLock) { 257 sessionDuration = (int) ((mLastUserActivityTime - mFirstUserActivityTime) / 1000); 258 } 259 MetricsLogger.count(getContext(), DEMO_SESSION_DURATION, sessionDuration); 260 } 261 262 private ActivityManagerService getActivityManager() { 263 if (mAms == null) { 264 mAms = (ActivityManagerService) ActivityManagerNative.getDefault(); 265 } 266 return mAms; 267 } 268 269 private UserManager getUserManager() { 270 if (mUm == null) { 271 mUm = getContext().getSystemService(UserManager.class); 272 } 273 return mUm; 274 } 275 276 private AudioManager getAudioManager() { 277 if (mAudioManager == null) { 278 mAudioManager = getContext().getSystemService(AudioManager.class); 279 } 280 return mAudioManager; 281 } 282 283 private void registerSettingsChangeObserver() { 284 final Uri deviceDemoModeUri = Settings.Global.getUriFor(Settings.Global.DEVICE_DEMO_MODE); 285 final Uri deviceProvisionedUri = Settings.Global.getUriFor( 286 Settings.Global.DEVICE_PROVISIONED); 287 final ContentResolver cr = getContext().getContentResolver(); 288 final ContentObserver deviceDemoModeSettingObserver = new ContentObserver(mHandler) { 289 @Override 290 public void onChange(boolean selfChange, Uri uri, int userId) { 291 if (deviceDemoModeUri.equals(uri)) { 292 mDeviceInDemoMode = UserManager.isDeviceInDemoMode(getContext()); 293 if (mDeviceInDemoMode) { 294 mHandler.sendEmptyMessage(MSG_START_NEW_SESSION); 295 } else if (mWakeLock.isHeld()) { 296 mWakeLock.release(); 297 } 298 } 299 // If device is provisioned and left demo mode - run the cleanup in demo folder 300 if (!mDeviceInDemoMode && isDeviceProvisioned()) { 301 // Run on the bg thread to not block the fg thread 302 BackgroundThread.getHandler().post(new Runnable() { 303 @Override 304 public void run() { 305 if (!deleteDemoFolderContents()) { 306 Slog.w(TAG, "Failed to delete demo folder contents"); 307 } 308 } 309 }); 310 } 311 } 312 }; 313 cr.registerContentObserver(deviceDemoModeUri, false, deviceDemoModeSettingObserver, 314 UserHandle.USER_SYSTEM); 315 cr.registerContentObserver(deviceProvisionedUri, false, deviceDemoModeSettingObserver, 316 UserHandle.USER_SYSTEM); 317 } 318 319 private boolean isDeviceProvisioned() { 320 return Settings.Global.getInt( 321 getContext().getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0; 322 } 323 324 private boolean deleteDemoFolderContents() { 325 final File dir = Environment.getDataPreloadsDemoDirectory(); 326 Slog.i(TAG, "Deleting contents of " + dir); 327 return FileUtils.deleteContents(dir); 328 } 329 330 private void registerBroadcastReceiver() { 331 final IntentFilter filter = new IntentFilter(); 332 filter.addAction(Intent.ACTION_SCREEN_OFF); 333 filter.addAction(ACTION_RESET_DEMO); 334 getContext().registerReceiver(mBroadcastReceiver, filter); 335 } 336 337 private String[] getCameraIdsWithFlash() { 338 ArrayList<String> cameraIdsList = new ArrayList<String>(); 339 try { 340 for (String cameraId : mCameraManager.getCameraIdList()) { 341 CameraCharacteristics c = mCameraManager.getCameraCharacteristics(cameraId); 342 if (Boolean.TRUE.equals(c.get(CameraCharacteristics.FLASH_INFO_AVAILABLE))) { 343 cameraIdsList.add(cameraId); 344 } 345 } 346 } catch (CameraAccessException e) { 347 Slog.e(TAG, "Unable to access camera while getting camera id list", e); 348 } 349 return cameraIdsList.toArray(new String[cameraIdsList.size()]); 350 } 351 352 private void turnOffAllFlashLights() { 353 for (String cameraId : mCameraIdsWithFlash) { 354 try { 355 mCameraManager.setTorchMode(cameraId, false); 356 } catch (CameraAccessException e) { 357 Slog.e(TAG, "Unable to access camera " + cameraId + " while turning off flash", e); 358 } 359 } 360 } 361 362 private void muteVolumeStreams() { 363 for (int stream : VOLUME_STREAMS_TO_MUTE) { 364 getAudioManager().setStreamVolume(stream, getAudioManager().getStreamMinVolume(stream), 365 0); 366 } 367 } 368 369 private Configuration getPrimaryUsersConfiguration() { 370 if (mPrimaryUserConfiguration == null) { 371 Settings.System.getConfiguration(getContext().getContentResolver(), 372 mPrimaryUserConfiguration = new Configuration()); 373 } 374 return mPrimaryUserConfiguration; 375 } 376 377 @Override 378 public void onStart() { 379 if (DEBUG) { 380 Slog.d(TAG, "Service starting up"); 381 } 382 mHandlerThread = new ServiceThread(TAG, android.os.Process.THREAD_PRIORITY_FOREGROUND, 383 false); 384 mHandlerThread.start(); 385 mHandler = new MainHandler(mHandlerThread.getLooper()); 386 publishLocalService(RetailDemoModeServiceInternal.class, mLocalService); 387 } 388 389 @Override 390 public void onBootPhase(int bootPhase) { 391 if (bootPhase != PHASE_THIRD_PARTY_APPS_CAN_START) { 392 return; 393 } 394 mPm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE); 395 mAmi = LocalServices.getService(ActivityManagerInternal.class); 396 mWakeLock = mPm 397 .newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, TAG); 398 mNm = NotificationManager.from(getContext()); 399 mCameraManager = (CameraManager) getContext().getSystemService(Context.CAMERA_SERVICE); 400 mCameraIdsWithFlash = getCameraIdsWithFlash(); 401 402 if (UserManager.isDeviceInDemoMode(getContext())) { 403 mDeviceInDemoMode = true; 404 mHandler.sendEmptyMessage(MSG_START_NEW_SESSION); 405 } 406 registerSettingsChangeObserver(); 407 registerBroadcastReceiver(); 408 } 409 410 @Override 411 public void onSwitchUser(int userId) { 412 if (!mDeviceInDemoMode) { 413 return; 414 } 415 if (DEBUG) { 416 Slog.d(TAG, "onSwitchUser: " + userId); 417 } 418 final UserInfo ui = getUserManager().getUserInfo(userId); 419 if (!ui.isDemo()) { 420 Slog.wtf(TAG, "Should not allow switch to non-demo user in demo mode"); 421 return; 422 } 423 if (!mWakeLock.isHeld()) { 424 mWakeLock.acquire(); 425 } 426 mCurrentUserId = userId; 427 mNm.notifyAsUser(TAG, 1, createResetNotification(), UserHandle.of(userId)); 428 turnOffAllFlashLights(); 429 muteVolumeStreams(); 430 mAmi.updatePersistentConfigurationForUser(getPrimaryUsersConfiguration(), userId); 431 synchronized (mActivityLock) { 432 mUserUntouched = true; 433 } 434 MetricsLogger.count(getContext(), DEMO_SESSION_COUNT, 1); 435 mHandler.removeMessages(MSG_INACTIVITY_TIME_OUT); 436 } 437 438 private RetailDemoModeServiceInternal mLocalService = new RetailDemoModeServiceInternal() { 439 private static final long USER_ACTIVITY_DEBOUNCE_TIME = 2000; 440 441 @Override 442 public void onUserActivity() { 443 if (!mDeviceInDemoMode) { 444 return; 445 } 446 long timeOfActivity = SystemClock.uptimeMillis(); 447 synchronized (mActivityLock) { 448 if (timeOfActivity < mLastUserActivityTime + USER_ACTIVITY_DEBOUNCE_TIME) { 449 return; 450 } 451 mLastUserActivityTime = timeOfActivity; 452 if (mUserUntouched && isDemoLauncherDisabled()) { 453 Slog.d(TAG, "retail_demo first touch"); 454 mUserUntouched = false; 455 mFirstUserActivityTime = timeOfActivity; 456 } 457 } 458 mHandler.removeMessages(MSG_INACTIVITY_TIME_OUT); 459 mHandler.sendEmptyMessageDelayed(MSG_INACTIVITY_TIME_OUT, USER_INACTIVITY_TIMEOUT); 460 } 461 }; 462} 463