Adding an idle timeout to restart demo session

Tracking user activity from RetailModeManagerService to start and switch
to a new demo user when there has been no activity for a long time and
the demo video player has been disabled.

Bug: 27280140
Change-Id: Iebf37b5d04a659e4bfda0e4016111c7b9e5f4eb9
diff --git a/services/core/java/com/android/server/am/RetailDemoModeService.java b/services/core/java/com/android/server/am/RetailDemoModeService.java
index a0d1c24..6a5ec96 100644
--- a/services/core/java/com/android/server/am/RetailDemoModeService.java
+++ b/services/core/java/com/android/server/am/RetailDemoModeService.java
@@ -17,22 +17,29 @@
 package com.android.server.am;
 
 import android.app.ActivityManagerNative;
+import android.app.AppGlobals;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
 import android.os.PowerManager;
-import android.os.ServiceManager;
+import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -43,8 +50,6 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.ServiceThread;
 import com.android.server.SystemService;
-import com.android.server.pm.UserManagerService;
-
 import java.io.File;
 
 public class RetailDemoModeService extends SystemService {
@@ -54,42 +59,89 @@
     private static final String DEMO_USER_NAME = "Demo";
     private static final String ACTION_RESET_DEMO = "com.android.server.am.ACTION_RESET_DEMO";
 
-    private static final long SCREEN_WAKEUP_DELAY = 5000;
+    private static final int MSG_TURN_SCREEN_ON = 0;
+    private static final int MSG_INACTIVITY_TIME_OUT = 1;
+    private static final int MSG_START_NEW_SESSION = 2;
 
+    private static final long SCREEN_WAKEUP_DELAY = 2500;
+    private static final long USER_INACTIVITY_TIMEOUT = 30000;
+
+    boolean mDeviceInDemoMode = false;
     private ActivityManagerService mAms;
     private NotificationManager mNm;
     private UserManager mUm;
     private PowerManager mPm;
     private PowerManager.WakeLock mWakeLock;
-    private Handler mHandler;
+    Handler mHandler;
     private ServiceThread mHandlerThread;
     private PendingIntent mResetDemoPendingIntent;
 
     private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (!UserManager.isDeviceInDemoMode(getContext())) {
+            if (!mDeviceInDemoMode) {
                 return;
             }
             switch (intent.getAction()) {
                 case Intent.ACTION_SCREEN_OFF:
-                    mHandler.postDelayed(new Runnable() {
-                        @Override
-                        public void run() {
-                            if (mWakeLock.isHeld()) {
-                                mWakeLock.release();
-                            }
-                            mWakeLock.acquire();
-                        }
-                    }, SCREEN_WAKEUP_DELAY);
+                    mHandler.removeMessages(MSG_TURN_SCREEN_ON);
+                    mHandler.sendEmptyMessageDelayed(MSG_TURN_SCREEN_ON, SCREEN_WAKEUP_DELAY);
                     break;
                 case ACTION_RESET_DEMO:
-                    createAndSwitchToDemoUser();
+                    mHandler.sendEmptyMessage(MSG_START_NEW_SESSION);
                     break;
             }
         }
     };
 
+    final class MainHandler extends Handler {
+
+        MainHandler(Looper looper) {
+            super(looper, null, true);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_TURN_SCREEN_ON:
+                    if (mWakeLock.isHeld()) {
+                        mWakeLock.release();
+                    }
+                    mWakeLock.acquire();
+                    break;
+                case MSG_INACTIVITY_TIME_OUT:
+                    IPackageManager pm = AppGlobals.getPackageManager();
+                    int enabledState = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+                    String demoLauncherComponent = getContext().getResources()
+                            .getString(R.string.config_demoModeLauncherComponent);
+                    try {
+                        enabledState = pm.getComponentEnabledSetting(
+                                ComponentName.unflattenFromString(demoLauncherComponent),
+                            getActivityManager().getCurrentUser().id);
+                    } catch (RemoteException exc) {
+                        // XXX: shouldn't happen
+                    }
+                    if (enabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED) {
+                        Slog.i(TAG, "Restarting session due to user inactivity timeout");
+                        sendEmptyMessage(MSG_START_NEW_SESSION);
+                    }
+                    break;
+                case MSG_START_NEW_SESSION:
+                    if (DEBUG) {
+                        Slog.d(TAG, "Switching to a new demo user");
+                    }
+                    removeMessages(MSG_START_NEW_SESSION);
+                    UserInfo demoUser = getUserManager().createUser(DEMO_USER_NAME,
+                            UserInfo.FLAG_DEMO | UserInfo.FLAG_EPHEMERAL);
+                    if (demoUser != null) {
+                        setupDemoUser(demoUser);
+                        getActivityManager().switchUser(demoUser.id);
+                    }
+                    break;
+            }
+        }
+    }
+
     public RetailDemoModeService(Context context) {
         super(context);
     }
@@ -103,6 +155,7 @@
                 .setShowWhen(false)
                 .setVisibility(Notification.VISIBILITY_PUBLIC)
                 .setContentIntent(getResetDemoPendingIntent())
+                .setColor(getContext().getColor(R.color.system_notification_accent_color))
                 .build();
     }
 
@@ -114,23 +167,6 @@
         return mResetDemoPendingIntent;
     }
 
-    private void createAndSwitchToDemoUser() {
-        if (DEBUG) {
-            Slog.d(TAG, "Switching to a new demo user");
-        }
-        mHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                UserInfo demoUser = getUserManager().createUser(DEMO_USER_NAME,
-                        UserInfo.FLAG_DEMO | UserInfo.FLAG_EPHEMERAL);
-                if (demoUser != null) {
-                    setupDemoUser(demoUser);
-                    getActivityManager().switchUser(demoUser.id);
-                }
-            }
-        });
-    }
-
     void setupDemoUser(UserInfo userInfo) {
         UserManager um = getUserManager();
         UserHandle user = UserHandle.of(userInfo.id);
@@ -166,14 +202,16 @@
         final ContentObserver deviceDemoModeSettingObserver = new ContentObserver(mHandler) {
             @Override
             public void onChange(boolean selfChange, Uri uri, int userId) {
-                boolean deviceInDemoMode = UserManager.isDeviceInDemoMode(getContext());
                 if (deviceDemoModeUri.equals(uri)) {
-                    if (deviceInDemoMode) {
-                        createAndSwitchToDemoUser();
+                    mDeviceInDemoMode = UserManager.isDeviceInDemoMode(getContext());
+                    if (mDeviceInDemoMode) {
+                        mHandler.sendEmptyMessage(MSG_START_NEW_SESSION);
+                    } else if (mWakeLock.isHeld()) {
+                        mWakeLock.release();
                     }
                 }
                 // If device is provisioned and left demo mode - run the cleanup in demo folder
-                if (!deviceInDemoMode && isDeviceProvisioned()) {
+                if (!mDeviceInDemoMode && isDeviceProvisioned()) {
                     // Run on the bg thread to not block the fg thread
                     BackgroundThread.getHandler().post(new Runnable() {
                         @Override
@@ -218,7 +256,8 @@
         mHandlerThread = new ServiceThread(TAG, android.os.Process.THREAD_PRIORITY_FOREGROUND,
                 false);
         mHandlerThread.start();
-        mHandler = new Handler(mHandlerThread.getLooper(), null, true);
+        mHandler = new MainHandler(mHandlerThread.getLooper());
+        publishLocalService(RetailDemoModeServiceInternal.class, mLocalService);
     }
 
     @Override
@@ -232,7 +271,8 @@
         mNm = NotificationManager.from(getContext());
 
         if (UserManager.isDeviceInDemoMode(getContext())) {
-            createAndSwitchToDemoUser();
+            mDeviceInDemoMode = true;
+            mHandler.sendEmptyMessage(MSG_START_NEW_SESSION);
         }
         registerSettingsChangeObserver();
         registerBroadcastReceiver();
@@ -240,16 +280,15 @@
 
     @Override
     public void onSwitchUser(int userId) {
+        if (!mDeviceInDemoMode) {
+            return;
+        }
         if (DEBUG) {
             Slog.d(TAG, "onSwitchUser: " + userId);
         }
         UserInfo ui = getUserManager().getUserInfo(userId);
         if (!ui.isDemo()) {
-            if (UserManager.isDeviceInDemoMode(getContext())) {
-                Slog.wtf(TAG, "Should not allow switch to non-demo user in demo mode");
-            } else if (mWakeLock.isHeld()) {
-                mWakeLock.release();
-            }
+            Slog.wtf(TAG, "Should not allow switch to non-demo user in demo mode");
             return;
         }
         if (!mWakeLock.isHeld()) {
@@ -257,4 +296,23 @@
         }
         mNm.notifyAsUser(TAG, 1, createResetNotification(), UserHandle.of(userId));
     }
+
+    public RetailDemoModeServiceInternal mLocalService = new RetailDemoModeServiceInternal() {
+        private static final long USER_ACTIVITY_DEBOUNCE_TIME = 2000;
+        private long mLastUserActivityTime = 0;
+
+        @Override
+        public void onUserActivity() {
+            if (!mDeviceInDemoMode) {
+                return;
+            }
+            long timeOfActivity = SystemClock.uptimeMillis();
+            if (timeOfActivity < mLastUserActivityTime + USER_ACTIVITY_DEBOUNCE_TIME) {
+                return;
+            }
+            mLastUserActivityTime = timeOfActivity;
+            mHandler.removeMessages(MSG_INACTIVITY_TIME_OUT);
+            mHandler.sendEmptyMessageDelayed(MSG_INACTIVITY_TIME_OUT, USER_INACTIVITY_TIMEOUT);
+        }
+    };
 }
diff --git a/services/core/java/com/android/server/am/RetailDemoModeServiceInternal.java b/services/core/java/com/android/server/am/RetailDemoModeServiceInternal.java
new file mode 100644
index 0000000..32de03a
--- /dev/null
+++ b/services/core/java/com/android/server/am/RetailDemoModeServiceInternal.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.am;
+
+public interface RetailDemoModeServiceInternal {
+    public void onUserActivity();
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 7108f4a..3ed6ec9 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -23,6 +23,7 @@
 import com.android.internal.app.IBatteryStats;
 import com.android.server.EventLogTags;
 import com.android.server.LocalServices;
+import com.android.server.am.RetailDemoModeServiceInternal;
 
 import android.app.ActivityManagerNative;
 import android.content.BroadcastReceiver;
@@ -91,6 +92,7 @@
     private final ActivityManagerInternal mActivityManagerInternal;
     private final InputManagerInternal mInputManagerInternal;
     private final InputMethodManagerInternal mInputMethodManagerInternal;
+    private final RetailDemoModeServiceInternal mRetailDemoModeServiceInternal;
 
     private final NotifierHandler mHandler;
     private final Intent mScreenOnIntent;
@@ -136,6 +138,7 @@
         mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
         mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
         mInputMethodManagerInternal = LocalServices.getService(InputMethodManagerInternal.class);
+        mRetailDemoModeServiceInternal = LocalServices.getService(RetailDemoModeServiceInternal.class);
 
         mHandler = new NotifierHandler(looper);
         mScreenOnIntent = new Intent(Intent.ACTION_SCREEN_ON);
@@ -534,7 +537,9 @@
             }
             mUserActivityPending = false;
         }
-
+        if (mRetailDemoModeServiceInternal != null) {
+            mRetailDemoModeServiceInternal.onUserActivity();
+        }
         mPolicy.userActivity();
     }