Home | History | Annotate | Download | only in retaildemo
      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 
     17 package com.android.server.retaildemo;
     18 
     19 import android.Manifest;
     20 import android.app.ActivityManagerInternal;
     21 import android.app.ActivityManagerNative;
     22 import android.app.AppGlobals;
     23 import android.app.Notification;
     24 import android.app.NotificationManager;
     25 import android.app.PendingIntent;
     26 import android.app.RetailDemoModeServiceInternal;
     27 import android.content.BroadcastReceiver;
     28 import android.content.ComponentName;
     29 import android.content.ContentProvider;
     30 import android.content.ContentResolver;
     31 import android.content.Context;
     32 import android.content.DialogInterface;
     33 import android.content.Intent;
     34 import android.content.IntentFilter;
     35 import android.content.pm.IPackageManager;
     36 import android.content.pm.PackageManager;
     37 import android.content.pm.ResolveInfo;
     38 import android.content.pm.UserInfo;
     39 import android.content.res.Configuration;
     40 import android.database.ContentObserver;
     41 import android.hardware.camera2.CameraAccessException;
     42 import android.hardware.camera2.CameraCharacteristics;
     43 import android.hardware.camera2.CameraManager;
     44 import android.media.AudioManager;
     45 import android.media.AudioSystem;
     46 import android.net.Uri;
     47 import android.os.Environment;
     48 import android.os.FileUtils;
     49 import android.os.Handler;
     50 import android.os.Looper;
     51 import android.os.Message;
     52 import android.os.PowerManager;
     53 import android.os.RemoteException;
     54 import android.os.SystemClock;
     55 import android.os.SystemProperties;
     56 import android.os.UserHandle;
     57 import android.os.UserManager;
     58 import android.provider.CallLog;
     59 import android.provider.MediaStore;
     60 import android.provider.Settings;
     61 import android.util.KeyValueListParser;
     62 import android.util.Slog;
     63 import com.android.internal.os.BackgroundThread;
     64 import com.android.internal.R;
     65 import com.android.internal.annotations.GuardedBy;
     66 import com.android.internal.logging.MetricsLogger;
     67 import com.android.internal.widget.LockPatternUtils;
     68 import com.android.server.LocalServices;
     69 import com.android.server.ServiceThread;
     70 import com.android.server.SystemService;
     71 import com.android.server.am.ActivityManagerService;
     72 import com.android.server.retaildemo.UserInactivityCountdownDialog.OnCountDownExpiredListener;
     73 
     74 import java.io.File;
     75 import java.util.ArrayList;
     76 
     77 public 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 = 90000;
     93     private static final long WARNING_DIALOG_TIMEOUT_DEFAULT = 0;
     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.Global.putInt(getContext().getContentResolver(),
    344                 Settings.Global.PACKAGE_VERIFIER_ENABLE, 0);
    345         grantRuntimePermissionToCamera(user);
    346         clearPrimaryCallLog();
    347     }
    348 
    349     private void grantRuntimePermissionToCamera(UserHandle user) {
    350         final Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    351         final PackageManager pm = getContext().getPackageManager();
    352         final ResolveInfo handler = pm.resolveActivityAsUser(cameraIntent,
    353                 PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
    354                 user.getIdentifier());
    355         if (handler == null || handler.activityInfo == null) {
    356             return;
    357         }
    358         try {
    359             pm.grantRuntimePermission(handler.activityInfo.packageName,
    360                     Manifest.permission.ACCESS_FINE_LOCATION, user);
    361         } catch (Exception e) {
    362             // Ignore
    363         }
    364     }
    365 
    366     private void clearPrimaryCallLog() {
    367         final ContentResolver resolver = getContext().getContentResolver();
    368 
    369         // Deleting primary user call log so that it doesn't get copied to the new demo user
    370         final Uri uri = CallLog.Calls.CONTENT_URI;
    371         try {
    372             resolver.delete(uri, null, null);
    373         } catch (Exception e) {
    374             Slog.w(TAG, "Deleting call log failed: " + e);
    375         }
    376     }
    377 
    378     void logSessionDuration() {
    379         final int sessionDuration;
    380         synchronized (mActivityLock) {
    381             sessionDuration = (int) ((mLastUserActivityTime - mFirstUserActivityTime) / 1000);
    382         }
    383         MetricsLogger.histogram(getContext(), DEMO_SESSION_DURATION, sessionDuration);
    384     }
    385 
    386     private ActivityManagerService getActivityManager() {
    387         if (mAms == null) {
    388             mAms = (ActivityManagerService) ActivityManagerNative.getDefault();
    389         }
    390         return mAms;
    391     }
    392 
    393     private UserManager getUserManager() {
    394         if (mUm == null) {
    395             mUm = getContext().getSystemService(UserManager.class);
    396         }
    397         return mUm;
    398     }
    399 
    400     private AudioManager getAudioManager() {
    401         if (mAudioManager == null) {
    402             mAudioManager = getContext().getSystemService(AudioManager.class);
    403         }
    404         return mAudioManager;
    405     }
    406 
    407     private boolean isDeviceProvisioned() {
    408         return Settings.Global.getInt(
    409                 getContext().getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0;
    410     }
    411 
    412     private boolean deletePreloadsFolderContents() {
    413         final File dir = Environment.getDataPreloadsDirectory();
    414         Slog.i(TAG, "Deleting contents of " + dir);
    415         return FileUtils.deleteContents(dir);
    416     }
    417 
    418     private void registerBroadcastReceiver() {
    419         final IntentFilter filter = new IntentFilter();
    420         filter.addAction(Intent.ACTION_SCREEN_OFF);
    421         filter.addAction(ACTION_RESET_DEMO);
    422         getContext().registerReceiver(mBroadcastReceiver, filter);
    423     }
    424 
    425     private String[] getCameraIdsWithFlash() {
    426         ArrayList<String> cameraIdsList = new ArrayList<String>();
    427         try {
    428             for (String cameraId : mCameraManager.getCameraIdList()) {
    429                 CameraCharacteristics c = mCameraManager.getCameraCharacteristics(cameraId);
    430                 if (Boolean.TRUE.equals(c.get(CameraCharacteristics.FLASH_INFO_AVAILABLE))) {
    431                     cameraIdsList.add(cameraId);
    432                 }
    433             }
    434         } catch (CameraAccessException e) {
    435             Slog.e(TAG, "Unable to access camera while getting camera id list", e);
    436         }
    437         return cameraIdsList.toArray(new String[cameraIdsList.size()]);
    438     }
    439 
    440     private void turnOffAllFlashLights() {
    441         for (String cameraId : mCameraIdsWithFlash) {
    442             try {
    443                 mCameraManager.setTorchMode(cameraId, false);
    444             } catch (CameraAccessException e) {
    445                 Slog.e(TAG, "Unable to access camera " + cameraId + " while turning off flash", e);
    446             }
    447         }
    448     }
    449 
    450     private void muteVolumeStreams() {
    451         for (int stream : VOLUME_STREAMS_TO_MUTE) {
    452             getAudioManager().setStreamVolume(stream, getAudioManager().getStreamMinVolume(stream),
    453                     0);
    454         }
    455     }
    456 
    457     private Configuration getSystemUsersConfiguration() {
    458         if (mSystemUserConfiguration == null) {
    459             Settings.System.getConfiguration(getContext().getContentResolver(),
    460                     mSystemUserConfiguration = new Configuration());
    461         }
    462         return mSystemUserConfiguration;
    463     }
    464 
    465     private void putDeviceInDemoMode() {
    466         SystemProperties.set(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED, "1");
    467         mHandler.sendEmptyMessage(MSG_START_NEW_SESSION);
    468     }
    469 
    470     @Override
    471     public void onStart() {
    472         if (DEBUG) {
    473             Slog.d(TAG, "Service starting up");
    474         }
    475         mHandlerThread = new ServiceThread(TAG, android.os.Process.THREAD_PRIORITY_FOREGROUND,
    476                 false);
    477         mHandlerThread.start();
    478         mHandler = new MainHandler(mHandlerThread.getLooper());
    479         publishLocalService(RetailDemoModeServiceInternal.class, mLocalService);
    480     }
    481 
    482     @Override
    483     public void onBootPhase(int bootPhase) {
    484         switch (bootPhase) {
    485             case PHASE_THIRD_PARTY_APPS_CAN_START:
    486                 mPreloadAppsInstaller = new PreloadAppsInstaller(getContext());
    487                 mPm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
    488                 mAmi = LocalServices.getService(ActivityManagerInternal.class);
    489                 mWakeLock = mPm
    490                         .newWakeLock(
    491                                 PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP,
    492                                 TAG);
    493                 mNm = NotificationManager.from(getContext());
    494                 mCameraManager = (CameraManager) getContext()
    495                         .getSystemService(Context.CAMERA_SERVICE);
    496                 mCameraIdsWithFlash = getCameraIdsWithFlash();
    497                 SettingsObserver settingsObserver = new SettingsObserver(mHandler);
    498                 settingsObserver.register();
    499                 settingsObserver.refreshTimeoutConstants();
    500                 registerBroadcastReceiver();
    501                 break;
    502             case PHASE_BOOT_COMPLETED:
    503                 if (UserManager.isDeviceInDemoMode(getContext())) {
    504                     mDeviceInDemoMode = true;
    505                     putDeviceInDemoMode();
    506                 }
    507                 break;
    508         }
    509     }
    510 
    511     @Override
    512     public void onSwitchUser(int userId) {
    513         if (!mDeviceInDemoMode) {
    514             return;
    515         }
    516         if (DEBUG) {
    517             Slog.d(TAG, "onSwitchUser: " + userId);
    518         }
    519         final UserInfo ui = getUserManager().getUserInfo(userId);
    520         if (!ui.isDemo()) {
    521             Slog.wtf(TAG, "Should not allow switch to non-demo user in demo mode");
    522             return;
    523         }
    524         if (!mWakeLock.isHeld()) {
    525             mWakeLock.acquire();
    526         }
    527         mCurrentUserId = userId;
    528         mAmi.updatePersistentConfigurationForUser(getSystemUsersConfiguration(), userId);
    529         turnOffAllFlashLights();
    530         muteVolumeStreams();
    531         // Disable lock screen for demo users.
    532         LockPatternUtils lockPatternUtils = new LockPatternUtils(getContext());
    533         lockPatternUtils.setLockScreenDisabled(true, userId);
    534         mNm.notifyAsUser(TAG, 1, createResetNotification(), UserHandle.of(userId));
    535 
    536         synchronized (mActivityLock) {
    537             mUserUntouched = true;
    538         }
    539         MetricsLogger.count(getContext(), DEMO_SESSION_COUNT, 1);
    540         mHandler.removeMessages(MSG_INACTIVITY_TIME_OUT);
    541         mHandler.post(new Runnable() {
    542             @Override
    543             public void run() {
    544                 mPreloadAppsInstaller.installApps(userId);
    545             }
    546         });
    547     }
    548 
    549     private RetailDemoModeServiceInternal mLocalService = new RetailDemoModeServiceInternal() {
    550         private static final long USER_ACTIVITY_DEBOUNCE_TIME = 2000;
    551 
    552         @Override
    553         public void onUserActivity() {
    554             if (!mDeviceInDemoMode) {
    555                 return;
    556             }
    557             long timeOfActivity = SystemClock.uptimeMillis();
    558             synchronized (mActivityLock) {
    559                 if (timeOfActivity < mLastUserActivityTime + USER_ACTIVITY_DEBOUNCE_TIME) {
    560                     return;
    561                 }
    562                 mLastUserActivityTime = timeOfActivity;
    563                 if (mUserUntouched && isDemoLauncherDisabled()) {
    564                     Slog.d(TAG, "retail_demo first touch");
    565                     mUserUntouched = false;
    566                     mFirstUserActivityTime = timeOfActivity;
    567                 }
    568             }
    569             mHandler.removeMessages(MSG_INACTIVITY_TIME_OUT);
    570             mHandler.sendEmptyMessageDelayed(MSG_INACTIVITY_TIME_OUT, mUserInactivityTimeout);
    571         }
    572     };
    573 }
    574