Merge "Adding a warning dialog before restarting demo session" into nyc-mr1-dev
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index c036c36..ae2a9e4 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4372,15 +4372,21 @@
     <string name="negative_duration">\u2212<xliff:g id="time" example="1:14">%1$s</xliff:g></string>
 
     <!-- Title of notification to start a new demo session when device is in retail mode [CHAR LIMIT=NONE] -->
-    <string name="reset_retail_demo_mode_title">Restart Session</string>
+    <string name="reset_retail_demo_mode_title">Reset device?</string>
     <!-- Text of notification to start a new demo session when device is in retail mode [CHAR LIMIT=NONE] -->
-    <string name="reset_retail_demo_mode_text">Tap to start a new demo session</string>
+    <string name="reset_retail_demo_mode_text">Tap to reset device</string>
     <!-- Text of dialog shown when starting a demo user for the first time [CHAR LIMIT=40] -->
-    <string name="demo_starting_message">Starting demo</string>
+    <string name="demo_starting_message">Starting demo\u2026</string>
     <!-- Text of dialog shown when starting a new demo user in retail demo mode [CHAR LIMIT=40] -->
-    <string name="demo_restarting_message">Restarting session</string>
-
-
+    <string name="demo_restarting_message">Resetting device\u2026</string>
+    <!-- Title of the dialog shown when user inactivity times out in retail demo mode [CHAR LIMIT=40] -->
+    <string name="demo_user_inactivity_timeout_title">Reset device?</string>
+    <!-- Warning message shown when user inactivity times out in retail demo mode [CHAR LIMIT=none] -->
+    <string name="demo_user_inactivity_timeout_countdown">You\'ll lose any changes and the demo will start again in <xliff:g id="timeout" example="9">%1$s</xliff:g> seconds\u2026</string>
+    <!-- Text of button to allow user to abort countdown and continue current session in retail demo mode [CHAR LIMIT=40] -->
+    <string name="demo_user_inactivity_timeout_left_button">Cancel</string>
+    <!-- Text of button to allow user to abort countdown and immediately start another session in retail demo mode [CHAR LIMIT=40] -->
+    <string name="demo_user_inactivity_timeout_right_button">Reset now</string>
 
     <!-- Title of notification shown when device has been forced to safe mode after a security compromise. -->
     <string name="audit_safemode_notification">Factory reset to use this device without restrictions</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 385c61a..6b7d32d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1892,6 +1892,10 @@
   <java-symbol type="string" name="audit_safemode_notification_details" />
   <java-symbol type="string" name="reset_retail_demo_mode_title" />
   <java-symbol type="string" name="reset_retail_demo_mode_text" />
+  <java-symbol type="string" name="demo_user_inactivity_timeout_title" />
+  <java-symbol type="string" name="demo_user_inactivity_timeout_countdown" />
+  <java-symbol type="string" name="demo_user_inactivity_timeout_left_button" />
+  <java-symbol type="string" name="demo_user_inactivity_timeout_right_button" />
 
   <java-symbol type="layout" name="resolver_list" />
   <java-symbol type="id" name="resolver_list" />
diff --git a/services/core/java/com/android/server/am/RetailDemoModeService.java b/services/core/java/com/android/server/am/RetailDemoModeService.java
index 6a5ec96..bcf1bd4 100644
--- a/services/core/java/com/android/server/am/RetailDemoModeService.java
+++ b/services/core/java/com/android/server/am/RetailDemoModeService.java
@@ -25,6 +25,7 @@
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.IPackageManager;
@@ -44,12 +45,13 @@
 import android.os.UserManager;
 import android.provider.Settings;
 import android.util.Slog;
-
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.R;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.ServiceThread;
 import com.android.server.SystemService;
+import com.android.server.am.UserInactivityCountdownDialog.OnCountDownExpiredListener;
+
 import java.io.File;
 
 public class RetailDemoModeService extends SystemService {
@@ -65,6 +67,8 @@
 
     private static final long SCREEN_WAKEUP_DELAY = 2500;
     private static final long USER_INACTIVITY_TIMEOUT = 30000;
+    private static final long WARNING_DIALOG_TIMEOUT = 6000;
+    private static final long MILLIS_PER_SECOND = 1000;
 
     boolean mDeviceInDemoMode = false;
     private ActivityManagerService mAms;
@@ -110,7 +114,7 @@
                     mWakeLock.acquire();
                     break;
                 case MSG_INACTIVITY_TIME_OUT:
-                    IPackageManager pm = AppGlobals.getPackageManager();
+                    final IPackageManager pm = AppGlobals.getPackageManager();
                     int enabledState = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
                     String demoLauncherComponent = getContext().getResources()
                             .getString(R.string.config_demoModeLauncherComponent);
@@ -122,8 +126,8 @@
                         // 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);
+                        Slog.i(TAG, "User inactivity timeout reached");
+                        showInactivityCountdownDialog();
                     }
                     break;
                 case MSG_START_NEW_SESSION:
@@ -131,7 +135,8 @@
                         Slog.d(TAG, "Switching to a new demo user");
                     }
                     removeMessages(MSG_START_NEW_SESSION);
-                    UserInfo demoUser = getUserManager().createUser(DEMO_USER_NAME,
+                    removeMessages(MSG_INACTIVITY_TIME_OUT);
+                    final UserInfo demoUser = getUserManager().createUser(DEMO_USER_NAME,
                             UserInfo.FLAG_DEMO | UserInfo.FLAG_EPHEMERAL);
                     if (demoUser != null) {
                         setupDemoUser(demoUser);
@@ -142,6 +147,25 @@
         }
     }
 
+    private void showInactivityCountdownDialog() {
+        UserInactivityCountdownDialog dialog = new UserInactivityCountdownDialog(getContext(),
+                WARNING_DIALOG_TIMEOUT, MILLIS_PER_SECOND);
+        dialog.setPositiveButtonClickListener(null);
+        dialog.setNegativeButtonClickListener(new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                mHandler.sendEmptyMessage(MSG_START_NEW_SESSION);
+            }
+        });
+        dialog.setOnCountDownExpiredListener(new OnCountDownExpiredListener() {
+            @Override
+            public void onCountDownExpired() {
+                mHandler.sendEmptyMessage(MSG_START_NEW_SESSION);
+            }
+        });
+        dialog.show();
+    }
+
     public RetailDemoModeService(Context context) {
         super(context);
     }
@@ -167,7 +191,7 @@
         return mResetDemoPendingIntent;
     }
 
-    void setupDemoUser(UserInfo userInfo) {
+    private void setupDemoUser(UserInfo userInfo) {
         UserManager um = getUserManager();
         UserHandle user = UserHandle.of(userInfo.id);
         LockPatternUtils lockPatternUtils = new LockPatternUtils(getContext());
@@ -230,13 +254,13 @@
                 UserHandle.USER_SYSTEM);
     }
 
-    boolean isDeviceProvisioned() {
+    private boolean isDeviceProvisioned() {
         return Settings.Global.getInt(
                 getContext().getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0;
     }
 
     private boolean deleteDemoFolderContents() {
-        File dir = Environment.getDataPreloadsDemoDirectory();
+        final File dir = Environment.getDataPreloadsDemoDirectory();
         Slog.i(TAG, "Deleting contents of " + dir);
         return FileUtils.deleteContents(dir);
     }
@@ -286,7 +310,7 @@
         if (DEBUG) {
             Slog.d(TAG, "onSwitchUser: " + userId);
         }
-        UserInfo ui = getUserManager().getUserInfo(userId);
+        final UserInfo ui = getUserManager().getUserInfo(userId);
         if (!ui.isDemo()) {
             Slog.wtf(TAG, "Should not allow switch to non-demo user in demo mode");
             return;
diff --git a/services/core/java/com/android/server/am/UserInactivityCountdownDialog.java b/services/core/java/com/android/server/am/UserInactivityCountdownDialog.java
new file mode 100644
index 0000000..1a1e85c
--- /dev/null
+++ b/services/core/java/com/android/server/am/UserInactivityCountdownDialog.java
@@ -0,0 +1,109 @@
+/*
+ * 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;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.os.CountDownTimer;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import com.android.internal.R;
+
+public class UserInactivityCountdownDialog extends AlertDialog {
+
+    private OnCountDownExpiredListener mOnCountDownExpiredListener;
+    private View mDialogView;
+    private CountDownTimer mCountDownTimer;
+    private long mCountDownDuration;
+    private long mRefreshInterval;
+
+    protected UserInactivityCountdownDialog(Context context, long duration, long refreshInterval) {
+        super(context);
+
+        mCountDownDuration = duration;
+        mRefreshInterval = refreshInterval;
+        mDialogView = LayoutInflater.from(context).inflate(R.layout.alert_dialog, null);
+        String msg = context.getString(R.string.demo_user_inactivity_timeout_countdown, duration);
+        getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+        WindowManager.LayoutParams attrs = getWindow().getAttributes();
+        attrs.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+        getWindow().setAttributes(attrs);
+        setTitle(R.string.demo_user_inactivity_timeout_title);
+        setView(mDialogView);
+        setMessage(msg);
+    }
+
+    public void setOnCountDownExpiredListener(
+            OnCountDownExpiredListener onCountDownExpiredListener) {
+        mOnCountDownExpiredListener = onCountDownExpiredListener;
+    }
+
+    public void setPositiveButtonClickListener(OnClickListener onClickListener) {
+        setButton(Dialog.BUTTON_POSITIVE,
+                getContext().getString(R.string.demo_user_inactivity_timeout_left_button),
+                onClickListener);
+    }
+
+    public void setNegativeButtonClickListener(OnClickListener onClickListener) {
+        setButton(Dialog.BUTTON_NEGATIVE,
+                getContext().getString(R.string.demo_user_inactivity_timeout_right_button),
+                onClickListener);
+    }
+
+    @Override
+    public void show() {
+        super.show();
+        mDialogView.post(new Runnable() {
+            @Override
+            public void run() {
+                mCountDownTimer = new CountDownTimer(mCountDownDuration, mRefreshInterval) {
+
+                    @Override
+                    public void onTick(long millisUntilFinished) {
+                        String msg = getContext().getResources().getString(
+                                R.string.demo_user_inactivity_timeout_countdown,
+                                millisUntilFinished / 1000);
+                        ((TextView) mDialogView.findViewById(R.id.message)).setText(msg);
+                    }
+
+                    @Override
+                    public void onFinish() {
+                        dismiss();
+                        if (mOnCountDownExpiredListener != null)
+                            mOnCountDownExpiredListener.onCountDownExpired();
+                    }
+                }.start();
+            }
+        });
+    }
+
+    @Override
+    public void dismiss() {
+        super.dismiss();
+        if (mCountDownTimer != null) {
+            mCountDownTimer.cancel();
+        }
+    }
+
+    interface OnCountDownExpiredListener {
+        void onCountDownExpired();
+    }
+}