Merge "Add disable add call during video call extra and carrier config key" into nyc-mr1-dev
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index c6834f9..32a8088 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -922,6 +922,8 @@
                 flags |= UserInfo.FLAG_EPHEMERAL;
             } else if ("--guest".equals(opt)) {
                 flags |= UserInfo.FLAG_GUEST;
+            } else if ("--demo".equals(opt)) {
+                flags |= UserInfo.FLAG_DEMO;
             } else {
                 System.err.println("Error: unknown option " + opt);
                 return showUsage();
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index eaea989..05f49c5 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -39,6 +39,7 @@
 import android.media.session.MediaSession;
 import android.net.Uri;
 import android.os.BadParcelableException;
+import android.os.BaseBundle;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Parcel;
@@ -53,6 +54,7 @@
 import android.text.style.CharacterStyle;
 import android.text.style.RelativeSizeSpan;
 import android.text.style.TextAppearanceSpan;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.Gravity;
@@ -63,6 +65,7 @@
 import android.widget.RemoteViews;
 
 import com.android.internal.R;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.NotificationColorUtil;
 
 import java.lang.annotation.Retention;
@@ -70,6 +73,7 @@
 import java.lang.reflect.Constructor;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
@@ -758,6 +762,16 @@
     public Bundle extras = new Bundle();
 
     /**
+     * All pending intents in the notification extras (notification extras, actions extras,
+     * and remote input extras) as the system needs to be able to access them but touching
+     * the extras bundle in the system process is not safe because the bundle may contain
+     * custom parcelable objects.
+     *
+     * @hide
+     */
+    public ArraySet<PendingIntent> extrasPendingIntents;
+
+    /**
      * {@link #extras} key: this is the title of the notification,
      * as supplied to {@link Builder#setContentTitle(CharSequence)}.
      */
@@ -1573,7 +1587,16 @@
     /**
      * Unflatten the notification from a parcel.
      */
-    public Notification(Parcel parcel)
+    @SuppressWarnings("unchecked")
+    public Notification(Parcel parcel) {
+        // IMPORTANT: Add unmarshaling code in readFromParcel as the pending
+        // intents in extras are always written as the last entry.
+        readFromParcelImpl(parcel);
+        // Must be read last!
+        extrasPendingIntents = (ArraySet<PendingIntent>) parcel.readArraySet(null);
+    }
+
+    private void readFromParcelImpl(Parcel parcel)
     {
         int version = parcel.readInt();
 
@@ -1728,6 +1751,10 @@
             }
         }
 
+        if (!ArrayUtils.isEmpty(extrasPendingIntents)) {
+            that.extrasPendingIntents = new ArraySet<>(extrasPendingIntents);
+        }
+
         if (this.actions != null) {
             that.actions = new Action[this.actions.length];
             for(int i=0; i<this.actions.length; i++) {
@@ -1843,8 +1870,40 @@
     /**
      * Flatten this notification into a parcel.
      */
-    public void writeToParcel(Parcel parcel, int flags)
-    {
+    public void writeToParcel(Parcel parcel, int flags) {
+        // We need to mark all pending intents getting into the notification
+        // system as being put there to later allow the notification ranker
+        // to launch them and by doing so add the app to the battery saver white
+        // list for a short period of time. The problem is that the system
+        // cannot look into the extras as there may be parcelables there that
+        // the platform does not know how to handle. To go around that we have
+        // an explicit list of the pending intents in the extras bundle.
+        final boolean collectPendingIntents = (extrasPendingIntents == null);
+        if (collectPendingIntents) {
+            PendingIntent.setOnMarshaledListener(
+                    (PendingIntent intent, Parcel out, int outFlags) -> {
+                if (parcel == out) {
+                    if (extrasPendingIntents == null) {
+                        extrasPendingIntents = new ArraySet<>();
+                    }
+                    extrasPendingIntents.add(intent);
+                }
+            });
+        }
+        try {
+            // IMPORTANT: Add marshaling code in writeToParcelImpl as we
+            // want to intercept all pending events written to the pacel.
+            writeToParcelImpl(parcel, flags);
+            // Must be written last!
+            parcel.writeArraySet(extrasPendingIntents);
+        } finally {
+            if (collectPendingIntents) {
+                PendingIntent.setOnMarshaledListener(null);
+            }
+        }
+    }
+
+    private void writeToParcelImpl(Parcel parcel, int flags) {
         parcel.writeInt(1);
 
         parcel.writeLong(when);
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index cb15392..cfa242b 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -242,6 +242,36 @@
     }
 
     /**
+     * Listener for observing when pending intents are written to a parcel.
+     *
+     * @hide
+     */
+    public interface OnMarshaledListener {
+        /**
+         * Called when a pending intent is written to a parcel.
+         *
+         * @param intent The pending intent.
+         * @param parcel The parcel to which it was written.
+         * @param flags The parcel flags when it was written.
+         */
+        void onMarshaled(PendingIntent intent, Parcel parcel, int flags);
+    }
+
+    private static final ThreadLocal<OnMarshaledListener> sOnMarshaledListener
+            = new ThreadLocal<>();
+
+    /**
+     * Registers an listener for pending intents being written to a parcel.
+     *
+     * @param listener The listener, null to clear.
+     *
+     * @hide
+     */
+    public static void setOnMarshaledListener(OnMarshaledListener listener) {
+        sOnMarshaledListener.set(listener);
+    }
+
+    /**
      * Retrieve a PendingIntent that will start a new activity, like calling
      * {@link Context#startActivity(Intent) Context.startActivity(Intent)}.
      * Note that the activity will be started outside of the context of an
@@ -1016,6 +1046,11 @@
 
     public void writeToParcel(Parcel out, int flags) {
         out.writeStrongBinder(mTarget.asBinder());
+        OnMarshaledListener listener = sOnMarshaledListener.get();
+        if (listener != null) {
+            listener.onMarshaled(this, out, flags);
+        }
+
     }
 
     public static final Parcelable.Creator<PendingIntent> CREATOR
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 2631247..74dcc07 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -17,8 +17,10 @@
 package android.os;
 
 import android.annotation.IntegerRes;
+import android.annotation.Nullable;
 import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.Size;
 import android.util.SizeF;
@@ -734,6 +736,21 @@
     }
 
     /**
+     * Write an array set to the parcel.
+     *
+     * @param val The array set to write.
+     *
+     * @hide
+     */
+    public void writeArraySet(@Nullable ArraySet<? extends Object> val) {
+        final int size = (val != null) ? val.size() : -1;
+        writeInt(size);
+        for (int i = 0; i < size; i++) {
+            writeValue(val.valueAt(i));
+        }
+    }
+
+    /**
      * Flatten a Bundle into the parcel at the current dataPosition(),
      * growing dataCapacity() if needed.
      */
@@ -2735,6 +2752,26 @@
         readArrayMapInternal(outVal, N, loader);
     }
 
+    /**
+     * Reads an array set.
+     *
+     * @param loader The class loader to use.
+     *
+     * @hide
+     */
+    public @Nullable ArraySet<? extends Object> readArraySet(ClassLoader loader) {
+        final int size = readInt();
+        if (size < 0) {
+            return null;
+        }
+        ArraySet<Object> result = new ArraySet<>(size);
+        for (int i = 0; i < size; i++) {
+            Object value = readValue(loader);
+            result.append(value);
+        }
+        return result;
+    }
+
     private void readListInternal(List outVal, int N,
         ClassLoader loader) {
         while (N > 0) {
diff --git a/core/java/android/util/ArraySet.java b/core/java/android/util/ArraySet.java
index 9e9314f..d39e91f 100644
--- a/core/java/android/util/ArraySet.java
+++ b/core/java/android/util/ArraySet.java
@@ -390,6 +390,32 @@
     }
 
     /**
+     * Special fast path for appending items to the end of the array without validation.
+     * The array must already be large enough to contain the item.
+     * @hide
+     */
+    public void append(E value) {
+        final int index = mSize;
+        final int hash = value == null ? 0
+                : (mIdentityHashCode ? System.identityHashCode(value) : value.hashCode());
+        if (index >= mHashes.length) {
+            throw new IllegalStateException("Array is full");
+        }
+        if (index > 0 && mHashes[index - 1] > hash) {
+            RuntimeException e = new RuntimeException("here");
+            e.fillInStackTrace();
+            Log.w(TAG, "New hash " + hash
+                    + " is before end of array hash " + mHashes[index - 1]
+                    + " at index " + index, e);
+            add(value);
+            return;
+        }
+        mSize = index + 1;
+        mHashes[index] = hash;
+        mArray[index] = value;
+    }
+
+    /**
      * Perform a {@link #add(Object)} of all values in <var>array</var>
      * @param array The array whose contents are to be retrieved.
      */
diff --git a/core/jni/android_view_RenderNode.cpp b/core/jni/android_view_RenderNode.cpp
index 4fc546c..b0028e1 100644
--- a/core/jni/android_view_RenderNode.cpp
+++ b/core/jni/android_view_RenderNode.cpp
@@ -573,8 +573,9 @@
                 bounds.roundOut();
             }
 
+            incStrong(0);
             auto functor = std::bind(
-                std::mem_fn(&SurfaceViewPositionUpdater::doUpdatePosition), this,
+                std::mem_fn(&SurfaceViewPositionUpdater::doUpdatePositionAsync), this,
                 (jlong) info.canvasContext.getFrameNumber(),
                 (jint) bounds.left, (jint) bounds.top,
                 (jint) bounds.right, (jint) bounds.bottom);
@@ -585,15 +586,18 @@
         virtual void onPositionLost(RenderNode& node, const TreeInfo* info) override {
             if (CC_UNLIKELY(!mWeakRef || (info && !info->updateWindowPositions))) return;
 
-            if (info) {
-                auto functor = std::bind(
-                    std::mem_fn(&SurfaceViewPositionUpdater::doNotifyPositionLost), this,
-                    (jlong) info->canvasContext.getFrameNumber());
-
-                info->canvasContext.enqueueFrameWork(std::move(functor));
-            } else {
-                doNotifyPositionLost(0);
+            ATRACE_NAME("SurfaceView position lost");
+            JNIEnv* env = jnienv();
+            jobject localref = env->NewLocalRef(mWeakRef);
+            if (CC_UNLIKELY(!localref)) {
+                jnienv()->DeleteWeakGlobalRef(mWeakRef);
+                mWeakRef = nullptr;
+                return;
             }
+
+            env->CallVoidMethod(localref, gSurfaceViewPositionLostMethod,
+                    info ? info->canvasContext.getFrameNumber() : 0);
+            env->DeleteLocalRef(localref);
         }
 
     private:
@@ -605,36 +609,23 @@
             return env;
         }
 
-        void doUpdatePosition(jlong frameNumber, jint left, jint top,
+        void doUpdatePositionAsync(jlong frameNumber, jint left, jint top,
                 jint right, jint bottom) {
             ATRACE_NAME("Update SurfaceView position");
 
             JNIEnv* env = jnienv();
             jobject localref = env->NewLocalRef(mWeakRef);
             if (CC_UNLIKELY(!localref)) {
-                jnienv()->DeleteWeakGlobalRef(mWeakRef);
+                env->DeleteWeakGlobalRef(mWeakRef);
                 mWeakRef = nullptr;
-                return;
+            } else {
+                env->CallVoidMethod(localref, gSurfaceViewPositionUpdateMethod,
+                        frameNumber, left, top, right, bottom);
+                env->DeleteLocalRef(localref);
             }
 
-            env->CallVoidMethod(localref, gSurfaceViewPositionUpdateMethod,
-                    frameNumber, left, top, right, bottom);
-            env->DeleteLocalRef(localref);
-        }
-
-        void doNotifyPositionLost(jlong frameNumber) {
-            ATRACE_NAME("SurfaceView position lost");
-
-            JNIEnv* env = jnienv();
-            jobject localref = env->NewLocalRef(mWeakRef);
-            if (CC_UNLIKELY(!localref)) {
-                jnienv()->DeleteWeakGlobalRef(mWeakRef);
-                mWeakRef = nullptr;
-                return;
-            }
-
-            env->CallVoidMethod(localref, gSurfaceViewPositionLostMethod, frameNumber);
-            env->DeleteLocalRef(localref);
+            // We need to release ourselves here
+            decStrong(0);
         }
 
         JavaVM* mVm;
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index f80be5e..47fef6d 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -232,7 +232,7 @@
     // the frameNumber to appropriately batch/synchronize these transactions.
     // There is no other filtering/batching to ensure that only the "final"
     // state called once per frame.
-    class ANDROID_API PositionListener {
+    class ANDROID_API PositionListener : public VirtualLightRefBase {
     public:
         virtual ~PositionListener() {}
         // Called when the RenderNode's position changes
@@ -247,7 +247,7 @@
     // before the RenderNode is used for drawing.
     // RenderNode takes ownership of the pointer
     ANDROID_API void setPositionListener(PositionListener* listener) {
-        mPositionListener.reset(listener);
+        mPositionListener = listener;
     }
 
     // This is only modified in MODE_FULL, so it can be safely accessed
@@ -366,7 +366,7 @@
     // mDisplayList, not mStagingDisplayList.
     uint32_t mParentCount;
 
-    std::unique_ptr<PositionListener> mPositionListener;
+    sp<PositionListener> mPositionListener;
 }; // class RenderNode
 
 } /* namespace uirenderer */
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index d4956be..ceef9c7 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -784,6 +784,7 @@
     }
     sp<FuncTask> task(new FuncTask());
     task->func = func;
+    mFrameFences.push_back(task);
     mFrameWorkProcessor->add(task);
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index d5ff0b35..42f398d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -153,7 +153,8 @@
         if (mKeyguardUserSwitcher == null) {
             // If we have no keyguard switcher, the screen width is under 600dp. In this case,
             // we don't show the multi-user avatar unless there is more than 1 user on the device.
-            if (mUserSwitcherController.getSwitchableUserCount() > 1) {
+            if (mUserSwitcherController != null
+                    && mUserSwitcherController.getSwitchableUserCount() > 1) {
                 mMultiUserSwitch.setVisibility(View.VISIBLE);
             } else {
                 mMultiUserSwitch.setVisibility(View.GONE);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 59de263..9209d3d 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2598,7 +2598,6 @@
         final long duration = LocalServices.getService(DeviceIdleController.LocalService.class)
                 .getNotificationWhitelistDuration();
 
-        int size = 0;
         if (notification.contentIntent != null) {
             am.setPendingIntentWhitelistDuration(notification.contentIntent.getTarget(), duration);
         }
@@ -2615,45 +2614,19 @@
                     continue;
                 }
                 am.setPendingIntentWhitelistDuration(action.actionIntent.getTarget(), duration);
-                setPendingIntentWhitelistDuration(am, duration, action.getExtras());
-                final RemoteInput[] remoteInputs = action.getRemoteInputs();
-                if (remoteInputs != null) {
-                    for (RemoteInput remoteInput : remoteInputs) {
-                        setPendingIntentWhitelistDuration(am, duration, remoteInput.getExtras());
-                    }
-                }
             }
         }
-    }
-
-    private static void setPendingIntentWhitelistDuration(ActivityManagerInternal am, long duration,
-            Bundle extras) {
-        for (String key : extras.keySet()) {
-            final Object value = extras.get(key);
-            if (value instanceof Parcelable) {
-                setPendingIntentWhitelistDuration(am, duration, (Parcelable) value);
-            } else if (value instanceof Parcelable[]) {
-                for (Parcelable parcelable : (Parcelable[]) value) {
-                    setPendingIntentWhitelistDuration(am, duration, parcelable);
-                }
-            } else if (value instanceof List) {
-                for (Object element : (List <?>) value) {
-                    if (element instanceof Parcelable) {
-                        setPendingIntentWhitelistDuration(am, duration, (Parcelable) element);
-                    }
+        if (notification.extrasPendingIntents != null) {
+            final int intentCount = notification.extrasPendingIntents.size();
+            for (int i = 0; i < intentCount; i++) {
+                PendingIntent pendingIntent = notification.extrasPendingIntents.valueAt(i);
+                if (pendingIntent != null) {
+                    am.setPendingIntentWhitelistDuration(pendingIntent.getTarget(), duration);
                 }
             }
         }
     }
 
-    private static void setPendingIntentWhitelistDuration(ActivityManagerInternal am, long duration,
-            Parcelable parcelable) {
-        if (parcelable instanceof PendingIntent) {
-            am.setPendingIntentWhitelistDuration(((PendingIntent) parcelable).getTarget(),
-                    duration);
-        }
-    }
-
     private class EnqueueNotificationRunnable implements Runnable {
         private final NotificationRecord r;
         private final int userId;
diff --git a/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java b/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java
index 96e6da74..51b1e38 100644
--- a/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java
+++ b/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java
@@ -55,6 +55,8 @@
 import android.util.Slog;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.logging.MetricsLogger;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.LocalServices;
 import com.android.server.ServiceThread;
@@ -86,8 +88,12 @@
             AudioSystem.STREAM_MUSIC
     };
 
+    // Tron Vars
+    private static final String DEMO_SESSION_COUNT = "retail_demo_session_count";
+    private static final String DEMO_SESSION_DURATION = "retail_demo_session_duration";
+
     boolean mDeviceInDemoMode = false;
-    int mCurrentUserId;
+    int mCurrentUserId = UserHandle.USER_SYSTEM;
     private ActivityManagerService mAms;
     private ActivityManagerInternal mAmi;
     private AudioManager mAudioManager;
@@ -102,6 +108,15 @@
     private String[] mCameraIdsWithFlash;
     private Configuration mPrimaryUserConfiguration;
 
+    final Object mActivityLock = new Object();
+    // Whether the newly created demo user has interacted with the screen yet
+    @GuardedBy("mActivityLock")
+    boolean mUserUntouched;
+    @GuardedBy("mActivityLock")
+    long mFirstUserActivityTime;
+    @GuardedBy("mActivityLock")
+    long mLastUserActivityTime;
+
     private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -136,18 +151,7 @@
                     mWakeLock.acquire();
                     break;
                 case MSG_INACTIVITY_TIME_OUT:
-                    final 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),
-                                mCurrentUserId);
-                    } catch (RemoteException exc) {
-                        Slog.e(TAG, "Unable to talk to Package Manager", exc);
-                    }
-                    if (enabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED) {
+                    if (isDemoLauncherDisabled()) {
                         Slog.i(TAG, "User inactivity timeout reached");
                         showInactivityCountdownDialog();
                     }
@@ -158,6 +162,9 @@
                     }
                     removeMessages(MSG_START_NEW_SESSION);
                     removeMessages(MSG_INACTIVITY_TIME_OUT);
+                    if (mCurrentUserId != UserHandle.USER_SYSTEM) {
+                        logSessionDuration();
+                    }
                     final UserInfo demoUser = getUserManager().createUser(DEMO_USER_NAME,
                             UserInfo.FLAG_DEMO | UserInfo.FLAG_EPHEMERAL);
                     if (demoUser != null) {
@@ -190,6 +197,9 @@
 
     public RetailDemoModeService(Context context) {
         super(context);
+        synchronized (mActivityLock) {
+            mFirstUserActivityTime = mLastUserActivityTime = SystemClock.uptimeMillis();
+        }
     }
 
     private Notification createResetNotification() {
@@ -213,6 +223,21 @@
         return mResetDemoPendingIntent;
     }
 
+    boolean isDemoLauncherDisabled() {
+        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),
+                    mCurrentUserId);
+        } catch (RemoteException exc) {
+            Slog.e(TAG, "Unable to talk to Package Manager", exc);
+        }
+        return enabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+    }
+
     private void setupDemoUser(UserInfo userInfo) {
         UserManager um = getUserManager();
         UserHandle user = UserHandle.of(userInfo.id);
@@ -226,6 +251,14 @@
                 Settings.Secure.SKIP_FIRST_USE_HINTS, 1, userInfo.id);
     }
 
+    void logSessionDuration() {
+        final int sessionDuration;
+        synchronized (mActivityLock) {
+            sessionDuration = (int) ((mLastUserActivityTime - mFirstUserActivityTime) / 1000);
+        }
+        MetricsLogger.count(getContext(), DEMO_SESSION_DURATION, sessionDuration);
+    }
+
     private ActivityManagerService getActivityManager() {
         if (mAms == null) {
             mAms = (ActivityManagerService) ActivityManagerNative.getDefault();
@@ -395,11 +428,15 @@
         turnOffAllFlashLights();
         muteVolumeStreams();
         mAmi.updatePersistentConfigurationForUser(getPrimaryUsersConfiguration(), userId);
+        synchronized (mActivityLock) {
+            mUserUntouched = true;
+        }
+        MetricsLogger.count(getContext(), DEMO_SESSION_COUNT, 1);
+        mHandler.removeMessages(MSG_INACTIVITY_TIME_OUT);
     }
 
     private RetailDemoModeServiceInternal mLocalService = new RetailDemoModeServiceInternal() {
         private static final long USER_ACTIVITY_DEBOUNCE_TIME = 2000;
-        private long mLastUserActivityTime = 0;
 
         @Override
         public void onUserActivity() {
@@ -407,10 +444,17 @@
                 return;
             }
             long timeOfActivity = SystemClock.uptimeMillis();
-            if (timeOfActivity < mLastUserActivityTime + USER_ACTIVITY_DEBOUNCE_TIME) {
-                return;
+            synchronized (mActivityLock) {
+                if (timeOfActivity < mLastUserActivityTime + USER_ACTIVITY_DEBOUNCE_TIME) {
+                    return;
+                }
+                mLastUserActivityTime = timeOfActivity;
+                if (mUserUntouched && isDemoLauncherDisabled()) {
+                    Slog.d(TAG, "retail_demo first touch");
+                    mUserUntouched = false;
+                    mFirstUserActivityTime = timeOfActivity;
+                }
             }
-            mLastUserActivityTime = timeOfActivity;
             mHandler.removeMessages(MSG_INACTIVITY_TIME_OUT);
             mHandler.sendEmptyMessageDelayed(MSG_INACTIVITY_TIME_OUT, USER_INACTIVITY_TIMEOUT);
         }