Merge "Integration into other unlock mechanisms"
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 20ac8d8..5c9fd51 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -163,6 +163,7 @@
                 "       am task drag-task-test <TASK_ID> <STEP_SIZE> [DELAY_MS] \n" +
                 "       am task size-task-test <TASK_ID> <STEP_SIZE> [DELAY_MS] \n" +
                 "       am get-config\n" +
+                "       am suppress-resize-config-changes <true|false>\n" +
                 "       am set-inactive [--user <USER_ID>] <PACKAGE> true|false\n" +
                 "       am get-inactive [--user <USER_ID>] <PACKAGE>\n" +
                 "       am send-trim-memory [--user <USER_ID>] <PROCESS>\n" +
@@ -326,6 +327,8 @@
                 "\n" +
                 "am get-config: retrieve the configuration and any recent configurations\n" +
                 "  of the device.\n" +
+                "am suppress-resize-config-changes: suppresses configuration changes due to\n" +
+                "  user resizing an activity/task.\n" +
                 "\n" +
                 "am set-inactive: sets the inactive state of an app.\n" +
                 "\n" +
@@ -453,6 +456,8 @@
             runTask();
         } else if (op.equals("get-config")) {
             runGetConfig();
+        } else if (op.equals("suppress-resize-config-changes")) {
+            runSuppressResizeConfigChanges();
         } else if (op.equals("set-inactive")) {
             runSetInactive();
         } else if (op.equals("get-inactive")) {
@@ -2606,6 +2611,16 @@
         }
     }
 
+    private void runSuppressResizeConfigChanges() throws Exception {
+        boolean suppress = Boolean.valueOf(nextArgRequired());
+
+        try {
+            mAm.suppressResizeConfigChanges(suppress);
+        } catch (RemoteException e) {
+            System.err.println("Error suppressing resize config changes: " + e);
+        }
+    }
+
     private void runSetInactive() throws Exception {
         int userId = UserHandle.USER_CURRENT;
 
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 3864a4b..cb1a89f 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -2682,6 +2682,13 @@
             reportSizeConfigurations(token, horizontal, vertical);
             return true;
         }
+        case SUPPRESS_RESIZE_CONFIG_CHANGES_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            final boolean suppress = data.readInt() == 1;
+            suppressResizeConfigChanges(suppress);
+            reply.writeNoException();
+            return true;
+        }
         }
 
         return super.onTransact(code, data, reply, flags);
@@ -6216,5 +6223,17 @@
         reply.recycle();
     }
 
+    @Override
+    public void suppressResizeConfigChanges(boolean suppress) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeInt(suppress ? 1 : 0);
+        mRemote.transact(SUPPRESS_RESIZE_CONFIG_CHANGES_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+
     private IBinder mRemote;
 }
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 2180bcc..478fdd1 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -535,6 +535,8 @@
     public int getActivityStackId(IBinder token) throws RemoteException;
     public void moveActivityToStack(IBinder token, int stackId) throws RemoteException;
 
+    public void suppressResizeConfigChanges(boolean suppress) throws RemoteException;
+
     /*
      * Private non-Binder interfaces
      */
@@ -891,4 +893,5 @@
     int MOVE_ACTIVITY_TO_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 344;
     int REPORT_SIZE_CONFIGURATIONS = IBinder.FIRST_CALL_TRANSACTION + 345;
     int MOVE_TASK_TO_DOCKED_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 346;
+    int SUPPRESS_RESIZE_CONFIG_CHANGES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 347;
 }
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 31d1ab7..5e8ad68 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -90,6 +90,9 @@
     public static final int WINDOW_STATE_HIDING = 1;
     public static final int WINDOW_STATE_HIDDEN = 2;
 
+    public static final int CAMERA_LAUNCH_SOURCE_WIGGLE = 0;
+    public static final int CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP = 1;
+
     private Context mContext;
     private IStatusBarService mService;
     private IBinder mToken = new Binder();
diff --git a/core/java/android/view/BatchedInputEventReceiver.java b/core/java/android/view/BatchedInputEventReceiver.java
new file mode 100644
index 0000000..b1d28e0
--- /dev/null
+++ b/core/java/android/view/BatchedInputEventReceiver.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2015 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 android.view;
+
+import android.os.Looper;
+
+/**
+ * Similar to {@link InputEventReceiver}, but batches events to vsync boundaries when possible.
+ * @hide
+ */
+public class BatchedInputEventReceiver extends InputEventReceiver {
+    Choreographer mChoreographer;
+    private boolean mBatchedInputScheduled;
+
+    public BatchedInputEventReceiver(
+            InputChannel inputChannel, Looper looper, Choreographer choreographer) {
+        super(inputChannel, looper);
+        mChoreographer = choreographer;
+    }
+
+    @Override
+    public void onBatchedInputEventPending() {
+        scheduleBatchedInput();
+    }
+
+    @Override
+    public void dispose() {
+        unscheduleBatchedInput();
+        super.dispose();
+    }
+
+    void doConsumeBatchedInput(long frameTimeNanos) {
+        if (mBatchedInputScheduled) {
+            mBatchedInputScheduled = false;
+            if (consumeBatchedInputEvents(frameTimeNanos) && frameTimeNanos != -1) {
+                // If we consumed a batch here, we want to go ahead and schedule the
+                // consumption of batched input events on the next frame. Otherwise, we would
+                // wait until we have more input events pending and might get starved by other
+                // things occurring in the process. If the frame time is -1, however, then
+                // we're in a non-batching mode, so there's no need to schedule this.
+                scheduleBatchedInput();
+            }
+        }
+    }
+
+    private void scheduleBatchedInput() {
+        if (!mBatchedInputScheduled) {
+            mBatchedInputScheduled = true;
+            mChoreographer.postCallback(Choreographer.CALLBACK_INPUT, mBatchedInputRunnable, null);
+        }
+    }
+
+    private void unscheduleBatchedInput() {
+        if (mBatchedInputScheduled) {
+            mBatchedInputScheduled = false;
+            mChoreographer.removeCallbacks(
+                    Choreographer.CALLBACK_INPUT, mBatchedInputRunnable, null);
+        }
+    }
+
+    private final class BatchedInputRunnable implements Runnable {
+        @Override
+        public void run() {
+            doConsumeBatchedInput(mChoreographer.getFrameTimeNanos());
+        }
+    }
+    private final BatchedInputRunnable mBatchedInputRunnable = new BatchedInputRunnable();
+}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 7ca3339..ca1b211 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -31,6 +31,7 @@
 import android.content.res.ColorStateList;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
@@ -55,6 +56,8 @@
 import android.widget.AdapterView.OnItemClickListener;
 import libcore.util.Objects;
 
+import com.android.internal.R;
+
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -206,14 +209,22 @@
 
     /** @hide */
     public static class OnClickHandler {
+
+        private int mEnterAnimationId;
+
         public boolean onClickHandler(View view, PendingIntent pendingIntent,
                 Intent fillInIntent) {
             try {
                 // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT?
                 Context context = view.getContext();
-                ActivityOptions opts = ActivityOptions.makeScaleUpAnimation(view,
-                        0, 0,
-                        view.getMeasuredWidth(), view.getMeasuredHeight());
+                ActivityOptions opts;
+                if (mEnterAnimationId != 0) {
+                    opts = ActivityOptions.makeCustomAnimation(context, mEnterAnimationId, 0);
+                } else {
+                    opts = ActivityOptions.makeScaleUpAnimation(view,
+                            0, 0,
+                            view.getMeasuredWidth(), view.getMeasuredHeight());
+                }
                 context.startIntentSender(
                         pendingIntent.getIntentSender(), fillInIntent,
                         Intent.FLAG_ACTIVITY_NEW_TASK,
@@ -228,6 +239,10 @@
             }
             return true;
         }
+
+        public void setEnterAnimationId(int enterAnimationId) {
+            mEnterAnimationId = enterAnimationId;
+        }
     }
 
     /**
@@ -2761,11 +2776,31 @@
         inflater.setFilter(this);
         result = inflater.inflate(rvToApply.getLayoutId(), parent, false);
 
+        loadTransitionOverride(context, handler);
+
         rvToApply.performApply(result, parent, handler);
 
         return result;
     }
 
+    private static void loadTransitionOverride(Context context,
+            RemoteViews.OnClickHandler handler) {
+        if (handler != null && context.getResources().getBoolean(
+                com.android.internal.R.bool.config_overrideRemoteViewsActivityTransition)) {
+            TypedArray windowStyle = context.getTheme().obtainStyledAttributes(
+                    com.android.internal.R.styleable.Window);
+            int windowAnimations = windowStyle.getResourceId(
+                    com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
+            TypedArray windowAnimationStyle = context.obtainStyledAttributes(
+                    windowAnimations, com.android.internal.R.styleable.WindowAnimation);
+            handler.setEnterAnimationId(windowAnimationStyle.getResourceId(
+                    com.android.internal.R.styleable.
+                            WindowAnimation_activityOpenRemoteViewsEnterAnimation, 0));
+            windowStyle.recycle();
+            windowAnimationStyle.recycle();
+        }
+    }
+
     /**
      * Applies all of the actions to the provided view.
      *
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index ec41447..3353d16 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -2292,7 +2292,7 @@
         }
     }
 
-    private static final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
+    private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
 
         /* package */int mDefaultOpacity = PixelFormat.OPAQUE;
 
@@ -2362,8 +2362,6 @@
 
         private int mRootScrollY = 0;
 
-        private PhoneWindow mWindow;
-
         public DecorView(Context context, int featureId) {
             super(context);
             mFeatureId = featureId;
@@ -2385,7 +2383,7 @@
         @Override
         public void onDraw(Canvas c) {
             super.onDraw(c);
-            mBackgroundFallback.draw(mWindow.mContentRoot, c, mWindow.mContentParent);
+            mBackgroundFallback.draw(mContentRoot, c, mContentParent);
         }
 
         @Override
@@ -2397,7 +2395,7 @@
             if (isDown && (event.getRepeatCount() == 0)) {
                 // First handle chording of panel key: if a panel key is held
                 // but not released, try to execute a shortcut in it.
-                if ((mWindow.mPanelChordingKey > 0) && (mWindow.mPanelChordingKey != keyCode)) {
+                if ((mPanelChordingKey > 0) && (mPanelChordingKey != keyCode)) {
                     boolean handled = dispatchKeyShortcutEvent(event);
                     if (handled) {
                         return true;
@@ -2406,15 +2404,15 @@
 
                 // If a panel is open, perform a shortcut on it without the
                 // chorded panel key
-                if ((mWindow.mPreparedPanel != null) && mWindow.mPreparedPanel.isOpen) {
-                    if (mWindow.performPanelShortcut(mWindow.mPreparedPanel, keyCode, event, 0)) {
+                if ((mPreparedPanel != null) && mPreparedPanel.isOpen) {
+                    if (performPanelShortcut(mPreparedPanel, keyCode, event, 0)) {
                         return true;
                     }
                 }
             }
 
-            if (!mWindow.isDestroyed()) {
-                final Callback cb = mWindow.getCallback();
+            if (!isDestroyed()) {
+                final Callback cb = getCallback();
                 final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
                         : super.dispatchKeyEvent(event);
                 if (handled) {
@@ -2422,28 +2420,28 @@
                 }
             }
 
-            return isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event)
-                    : mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event);
+            return isDown ? PhoneWindow.this.onKeyDown(mFeatureId, event.getKeyCode(), event)
+                    : PhoneWindow.this.onKeyUp(mFeatureId, event.getKeyCode(), event);
         }
 
         @Override
         public boolean dispatchKeyShortcutEvent(KeyEvent ev) {
             // If the panel is already prepared, then perform the shortcut using it.
             boolean handled;
-            if (mWindow.mPreparedPanel != null) {
-                handled = mWindow.performPanelShortcut(mWindow.mPreparedPanel, ev.getKeyCode(), ev,
+            if (mPreparedPanel != null) {
+                handled = performPanelShortcut(mPreparedPanel, ev.getKeyCode(), ev,
                         Menu.FLAG_PERFORM_NO_CLOSE);
                 if (handled) {
-                    if (mWindow.mPreparedPanel != null) {
-                        mWindow.mPreparedPanel.isHandled = true;
+                    if (mPreparedPanel != null) {
+                        mPreparedPanel.isHandled = true;
                     }
                     return true;
                 }
             }
 
             // Shortcut not handled by the panel.  Dispatch to the view hierarchy.
-            final Callback cb = mWindow.getCallback();
-            handled = cb != null && !mWindow.isDestroyed() && mFeatureId < 0
+            final Callback cb = getCallback();
+            handled = cb != null && !isDestroyed() && mFeatureId < 0
                     ? cb.dispatchKeyShortcutEvent(ev) : super.dispatchKeyShortcutEvent(ev);
             if (handled) {
                 return true;
@@ -2453,10 +2451,10 @@
             // combination such as Control+C.  Temporarily prepare the panel then mark it
             // unprepared again when finished to ensure that the panel will again be prepared
             // the next time it is shown for real.
-            PanelFeatureState st = mWindow.getPanelState(FEATURE_OPTIONS_PANEL, false);
-            if (st != null && mWindow.mPreparedPanel == null) {
-                mWindow.preparePanel(st, ev);
-                handled = mWindow.performPanelShortcut(st, ev.getKeyCode(), ev,
+            PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
+            if (st != null && mPreparedPanel == null) {
+                preparePanel(st, ev);
+                handled = performPanelShortcut(st, ev.getKeyCode(), ev,
                         Menu.FLAG_PERFORM_NO_CLOSE);
                 st.isPrepared = false;
                 if (handled) {
@@ -2468,23 +2466,23 @@
 
         @Override
         public boolean dispatchTouchEvent(MotionEvent ev) {
-            final Callback cb = mWindow.getCallback();
-            return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
-                    ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
+            final Callback cb = getCallback();
+            return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev)
+                    : super.dispatchTouchEvent(ev);
         }
 
         @Override
         public boolean dispatchTrackballEvent(MotionEvent ev) {
-            final Callback cb = mWindow.getCallback();
-            return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
-                    ? cb.dispatchTrackballEvent(ev) : super.dispatchTrackballEvent(ev);
+            final Callback cb = getCallback();
+            return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTrackballEvent(ev)
+                    : super.dispatchTrackballEvent(ev);
         }
 
         @Override
         public boolean dispatchGenericMotionEvent(MotionEvent ev) {
-            final Callback cb = mWindow.getCallback();
-            return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
-                    ? cb.dispatchGenericMotionEvent(ev) : super.dispatchGenericMotionEvent(ev);
+            final Callback cb = getCallback();
+            return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchGenericMotionEvent(ev)
+                    : super.dispatchGenericMotionEvent(ev);
         }
 
         public boolean superDispatchKeyEvent(KeyEvent event) {
@@ -2536,7 +2534,7 @@
         @Override
         public boolean onInterceptTouchEvent(MotionEvent event) {
             int action = event.getAction();
-            if (mHasNonClientDecor && mWindow.mNonClientDecorView.mVisible) {
+            if (mHasNonClientDecor && mNonClientDecorView.mVisible) {
                 // Don't dispatch ACTION_DOWN to the non client decor if the window is
                 // resizable and the event was (starting) outside the window.
                 // Window resizing events should be handled by WindowManager.
@@ -2559,7 +2557,7 @@
                     int x = (int)event.getX();
                     int y = (int)event.getY();
                     if (isOutOfBounds(x, y)) {
-                        mWindow.closePanel(mFeatureId);
+                        closePanel(mFeatureId);
                         return true;
                     }
                 }
@@ -2585,7 +2583,7 @@
                 if (action == MotionEvent.ACTION_MOVE) {
                     if (y > (mDownY+30)) {
                         Log.i(TAG, "Closing!");
-                        mWindow.closePanel(mFeatureId);
+                        closePanel(mFeatureId);
                         mWatchingForMenu = false;
                         return true;
                     }
@@ -2601,7 +2599,7 @@
 
             if (action == MotionEvent.ACTION_DOWN) {
                 int y = (int)event.getY();
-                if (y >= (getHeight()-5) && !mWindow.hasChildren()) {
+                if (y >= (getHeight()-5) && !hasChildren()) {
                     Log.i(TAG, "Watchiing!");
                     mWatchingForMenu = true;
                 }
@@ -2616,7 +2614,7 @@
             if (action == MotionEvent.ACTION_MOVE) {
                 if (y < (getHeight()-30)) {
                     Log.i(TAG, "Opening!");
-                    mWindow.openPanel(FEATURE_OPTIONS_PANEL, new KeyEvent(
+                    openPanel(FEATURE_OPTIONS_PANEL, new KeyEvent(
                             KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU));
                     mWatchingForMenu = false;
                     return true;
@@ -2650,8 +2648,8 @@
 
         @Override
         public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
-            final Callback cb = mWindow.getCallback();
-            if (cb != null && !mWindow.isDestroyed()) {
+            final Callback cb = getCallback();
+            if (cb != null && !isDestroyed()) {
                 if (cb.dispatchPopulateAccessibilityEvent(event)) {
                     return true;
                 }
@@ -2688,7 +2686,7 @@
 
                 if (SWEEP_OPEN_MENU) {
                     if (mMenuBackground == null && mFeatureId < 0
-                            && mWindow.getAttributes().height
+                            && getAttributes().height
                             == WindowManager.LayoutParams.MATCH_PARENT) {
                         mMenuBackground = getContext().getDrawable(
                                 R.drawable.menu_background);
@@ -2713,8 +2711,7 @@
 
             boolean fixedWidth = false;
             if (widthMode == AT_MOST) {
-                final TypedValue tvw = isPortrait ? mWindow.mFixedWidthMinor
-                        : mWindow.mFixedWidthMajor;
+                final TypedValue tvw = isPortrait ? mFixedWidthMinor : mFixedWidthMajor;
                 if (tvw != null && tvw.type != TypedValue.TYPE_NULL) {
                     final int w;
                     if (tvw.type == TypedValue.TYPE_DIMENSION) {
@@ -2735,8 +2732,7 @@
             }
 
             if (heightMode == AT_MOST) {
-                final TypedValue tvh = isPortrait ? mWindow.mFixedHeightMajor
-                        : mWindow.mFixedHeightMinor;
+                final TypedValue tvh = isPortrait ? mFixedHeightMajor : mFixedHeightMinor;
                 if (tvh != null && tvh.type != TypedValue.TYPE_NULL) {
                     final int h;
                     if (tvh.type == TypedValue.TYPE_DIMENSION) {
@@ -2754,21 +2750,21 @@
                 }
             }
 
-            getOutsets(mWindow.mOutsets);
-            if (mWindow.mOutsets.top > 0 || mWindow.mOutsets.bottom > 0) {
+            getOutsets(mOutsets);
+            if (mOutsets.top > 0 || mOutsets.bottom > 0) {
                 int mode = MeasureSpec.getMode(heightMeasureSpec);
                 if (mode != MeasureSpec.UNSPECIFIED) {
                     int height = MeasureSpec.getSize(heightMeasureSpec);
                     heightMeasureSpec = MeasureSpec.makeMeasureSpec(
-                            height + mWindow.mOutsets.top + mWindow.mOutsets.bottom, mode);
+                            height + mOutsets.top + mOutsets.bottom, mode);
                 }
             }
-            if (mWindow.mOutsets.left > 0 || mWindow.mOutsets.right > 0) {
+            if (mOutsets.left > 0 || mOutsets.right > 0) {
                 int mode = MeasureSpec.getMode(widthMeasureSpec);
                 if (mode != MeasureSpec.UNSPECIFIED) {
                     int width = MeasureSpec.getSize(widthMeasureSpec);
                     widthMeasureSpec = MeasureSpec.makeMeasureSpec(
-                            width + mWindow.mOutsets.left + mWindow.mOutsets.right, mode);
+                            width + mOutsets.left + mOutsets.right, mode);
                 }
             }
 
@@ -2780,7 +2776,7 @@
             widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, EXACTLY);
 
             if (!fixedWidth && widthMode == AT_MOST) {
-                final TypedValue tv = isPortrait ? mWindow.mMinWidthMinor : mWindow.mMinWidthMajor;
+                final TypedValue tv = isPortrait ? mMinWidthMinor : mMinWidthMajor;
                 if (tv.type != TypedValue.TYPE_NULL) {
                     final int min;
                     if (tv.type == TypedValue.TYPE_DIMENSION) {
@@ -2808,12 +2804,12 @@
         @Override
         protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
             super.onLayout(changed, left, top, right, bottom);
-            getOutsets(mWindow.mOutsets);
-            if (mWindow.mOutsets.left > 0) {
-                offsetLeftAndRight(-mWindow.mOutsets.left);
+            getOutsets(mOutsets);
+            if (mOutsets.left > 0) {
+                offsetLeftAndRight(-mOutsets.left);
             }
-            if (mWindow.mOutsets.top > 0) {
-                offsetTopAndBottom(-mWindow.mOutsets.top);
+            if (mOutsets.top > 0) {
+                offsetTopAndBottom(-mOutsets.top);
             }
         }
 
@@ -2829,23 +2825,23 @@
         @Override
         public boolean showContextMenuForChild(View originalView) {
             // Reuse the context menu builder
-            if (mWindow.mContextMenu == null) {
-                mWindow.mContextMenu = new ContextMenuBuilder(getContext());
-                mWindow.mContextMenu.setCallback(mWindow.mContextMenuCallback);
+            if (mContextMenu == null) {
+                mContextMenu = new ContextMenuBuilder(getContext());
+                mContextMenu.setCallback(mContextMenuCallback);
             } else {
-                mWindow.mContextMenu.clearAll();
+                mContextMenu.clearAll();
             }
 
-            final MenuDialogHelper helper = mWindow.mContextMenu.show(originalView,
+            final MenuDialogHelper helper = mContextMenu.show(originalView,
                     originalView.getWindowToken());
             if (helper != null) {
-                helper.setPresenterCallback(mWindow.mContextMenuCallback);
-            } else if (mWindow.mContextMenuHelper != null) {
+                helper.setPresenterCallback(mContextMenuCallback);
+            } else if (mContextMenuHelper != null) {
                 // No menu to show, but if we have a menu currently showing it just became blank.
                 // Close it.
-                mWindow.mContextMenuHelper.dismiss();
+                mContextMenuHelper.dismiss();
             }
-            mWindow.mContextMenuHelper = helper;
+            mContextMenuHelper = helper;
             return helper != null;
         }
 
@@ -2875,15 +2871,14 @@
                 View originatingView, ActionMode.Callback callback, int type) {
             ActionMode.Callback2 wrappedCallback = new ActionModeCallback2Wrapper(callback);
             ActionMode mode = null;
-            if (mWindow.getCallback() != null && !mWindow.isDestroyed()) {
+            if (getCallback() != null && !isDestroyed()) {
                 try {
-                    mode = mWindow.getCallback().onWindowStartingActionMode(wrappedCallback, type);
+                    mode = getCallback().onWindowStartingActionMode(wrappedCallback, type);
                 } catch (AbstractMethodError ame) {
                     // Older apps might not implement the typed version of this method.
                     if (type == ActionMode.TYPE_PRIMARY) {
                         try {
-                            mode = mWindow.getCallback().onWindowStartingActionMode(
-                                    wrappedCallback);
+                            mode = getCallback().onWindowStartingActionMode(wrappedCallback);
                         } catch (AbstractMethodError ame2) {
                             // Older apps might not implement this callback method at all.
                         }
@@ -2908,9 +2903,9 @@
                     mode = null;
                 }
             }
-            if (mode != null && mWindow.getCallback() != null && !mWindow.isDestroyed()) {
+            if (mode != null && getCallback() != null && !isDestroyed()) {
                 try {
-                    mWindow.getCallback().onActionModeStarted(mode);
+                    getCallback().onActionModeStarted(mode);
                 } catch (AbstractMethodError ame) {
                     // Older apps might not implement this callback method.
                 }
@@ -2999,10 +2994,10 @@
         }
 
         private WindowInsets updateColorViews(WindowInsets insets, boolean animate) {
-            WindowManager.LayoutParams attrs = mWindow.getAttributes();
+            WindowManager.LayoutParams attrs = getAttributes();
             int sysUiVisibility = attrs.systemUiVisibility | getWindowSystemUiVisibility();
 
-            if (!mWindow.mIsFloating && ActivityManager.isHighEndGfx()) {
+            if (!mIsFloating && ActivityManager.isHighEndGfx()) {
                 boolean disallowAnimate = !isLaidOut();
                 disallowAnimate |= ((mLastWindowFlags ^ attrs.flags)
                         & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
@@ -3034,14 +3029,14 @@
 
                 boolean navBarToRightEdge = mLastBottomInset == 0 && mLastRightInset > 0;
                 int navBarSize = navBarToRightEdge ? mLastRightInset : mLastBottomInset;
-                updateColorViewInt(mNavigationColorViewState, sysUiVisibility,
-                        mWindow.mNavigationBarColor, navBarSize, navBarToRightEdge,
-                        0 /* rightInset */, animate && !disallowAnimate);
+                updateColorViewInt(mNavigationColorViewState, sysUiVisibility, mNavigationBarColor,
+                        navBarSize, navBarToRightEdge, 0 /* rightInset */,
+                        animate && !disallowAnimate);
 
                 boolean statusBarNeedsRightInset = navBarToRightEdge
                         && mNavigationColorViewState.present;
                 int statusBarRightInset = statusBarNeedsRightInset ? mLastRightInset : 0;
-                updateColorViewInt(mStatusColorViewState, sysUiVisibility, mWindow.mStatusBarColor,
+                updateColorViewInt(mStatusColorViewState, sysUiVisibility, mStatusBarColor,
                         mLastTopInset, false /* matchVertical */, statusBarRightInset,
                         animate && !disallowAnimate);
             }
@@ -3058,13 +3053,13 @@
             int consumedRight = consumingNavBar ? mLastRightInset : 0;
             int consumedBottom = consumingNavBar ? mLastBottomInset : 0;
 
-            if (mWindow.mContentRoot != null
-                    && mWindow.mContentRoot.getLayoutParams() instanceof MarginLayoutParams) {
-                MarginLayoutParams lp = (MarginLayoutParams) mWindow.mContentRoot.getLayoutParams();
+            if (mContentRoot != null
+                    && mContentRoot.getLayoutParams() instanceof MarginLayoutParams) {
+                MarginLayoutParams lp = (MarginLayoutParams) mContentRoot.getLayoutParams();
                 if (lp.rightMargin != consumedRight || lp.bottomMargin != consumedBottom) {
                     lp.rightMargin = consumedRight;
                     lp.bottomMargin = consumedBottom;
-                    mWindow.mContentRoot.setLayoutParams(lp);
+                    mContentRoot.setLayoutParams(lp);
 
                     if (insets == null) {
                         // The insets have changed, but we're not currently in the process
@@ -3102,11 +3097,11 @@
         private void updateColorViewInt(final ColorViewState state, int sysUiVis, int color,
                 int size, boolean verticalBar, int rightMargin, boolean animate) {
             state.present = size > 0 && (sysUiVis & state.systemUiHideFlag) == 0
-                    && (mWindow.getAttributes().flags & state.hideWindowFlag) == 0
-                    && (mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
+                    && (getAttributes().flags & state.hideWindowFlag) == 0
+                    && (getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
             boolean show = state.present
                     && (color & Color.BLACK) != 0
-                    && (mWindow.getAttributes().flags & state.translucentFlag) == 0;
+                    && (getAttributes().flags & state.translucentFlag) == 0;
 
             boolean visibilityChanged = false;
             View view = state.view;
@@ -3198,14 +3193,14 @@
                             mPrimaryActionModeView.getLayoutParams();
                     boolean mlpChanged = false;
                     if (mPrimaryActionModeView.isShown()) {
-                        if (mWindow.mTempRect == null) {
-                            mWindow.mTempRect = new Rect();
+                        if (mTempRect == null) {
+                            mTempRect = new Rect();
                         }
-                        final Rect rect = mWindow.mTempRect;
+                        final Rect rect = mTempRect;
 
                         // If the parent doesn't consume the insets, manually
                         // apply the default system window insets.
-                        mWindow.mContentParent.computeSystemWindowInsets(insets, rect);
+                        mContentParent.computeSystemWindowInsets(insets, rect);
                         final int newMargin = rect.top == 0 ? insets.getSystemWindowInsetTop() : 0;
                         if (mlp.topMargin != newMargin) {
                             mlpChanged = true;
@@ -3236,7 +3231,7 @@
                         // mode is overlaid on the app content (e.g. it's
                         // sitting in a FrameLayout, see
                         // screen_simple_overlay_action_mode.xml).
-                        final boolean nonOverlay = (mWindow.getLocalFeatures()
+                        final boolean nonOverlay = (getLocalFeatures()
                                 & (1 << FEATURE_ACTION_MODE_OVERLAY)) == 0;
                         insets = insets.consumeSystemWindowInsets(
                                 false, nonOverlay && showStatusGuard /* top */, false, false);
@@ -3260,14 +3255,14 @@
 
         private void updateNavigationGuard(WindowInsets insets) {
             // IMEs lay out below the nav bar, but the content view must not (for back compat)
-            if (mWindow.getAttributes().type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
+            if (getAttributes().type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
                 // prevent the content view from including the nav bar height
-                if (mWindow.mContentParent != null) {
-                    if (mWindow.mContentParent.getLayoutParams() instanceof MarginLayoutParams) {
+                if (mContentParent != null) {
+                    if (mContentParent.getLayoutParams() instanceof MarginLayoutParams) {
                         MarginLayoutParams mlp =
-                                (MarginLayoutParams) mWindow.mContentParent.getLayoutParams();
+                                (MarginLayoutParams) mContentParent.getLayoutParams();
                         mlp.bottomMargin = insets.getSystemWindowInsetBottom();
-                        mWindow.mContentParent.setLayoutParams(mlp);
+                        mContentParent.setLayoutParams(mlp);
                     }
                 }
                 // position the navigation guard view, creating it if necessary
@@ -3349,7 +3344,7 @@
 
             mDefaultOpacity = opacity;
             if (mFeatureId < 0) {
-                mWindow.setDefaultWindowFormat(opacity);
+                setDefaultWindowFormat(opacity);
             }
         }
 
@@ -3359,13 +3354,12 @@
 
             // If the user is chording a menu shortcut, release the chord since
             // this window lost focus
-            if (mWindow.hasFeature(FEATURE_OPTIONS_PANEL) && !hasWindowFocus
-                    && mWindow.mPanelChordingKey != 0) {
-                mWindow.closePanel(FEATURE_OPTIONS_PANEL);
+            if (hasFeature(FEATURE_OPTIONS_PANEL) && !hasWindowFocus && mPanelChordingKey != 0) {
+                closePanel(FEATURE_OPTIONS_PANEL);
             }
 
-            final Callback cb = mWindow.getCallback();
-            if (cb != null && !mWindow.isDestroyed() && mFeatureId < 0) {
+            final Callback cb = getCallback();
+            if (cb != null && !isDestroyed() && mFeatureId < 0) {
                 cb.onWindowFocusChanged(hasWindowFocus);
             }
 
@@ -3381,8 +3375,8 @@
         protected void onAttachedToWindow() {
             super.onAttachedToWindow();
 
-            final Callback cb = mWindow.getCallback();
-            if (cb != null && !mWindow.isDestroyed() && mFeatureId < 0) {
+            final Callback cb = getCallback();
+            if (cb != null && !isDestroyed() && mFeatureId < 0) {
                 cb.onAttachedToWindow();
             }
 
@@ -3394,7 +3388,7 @@
                  * menu was open. When the activity is recreated, the menu
                  * should be shown again.
                  */
-                mWindow.openPanelsAfterRestore();
+                openPanelsAfterRestore();
             }
         }
 
@@ -3402,13 +3396,13 @@
         protected void onDetachedFromWindow() {
             super.onDetachedFromWindow();
 
-            final Callback cb = mWindow.getCallback();
+            final Callback cb = getCallback();
             if (cb != null && mFeatureId < 0) {
                 cb.onDetachedFromWindow();
             }
 
-            if (mWindow.mDecorContentParent != null) {
-                mWindow.mDecorContentParent.dismissPopups();
+            if (mDecorContentParent != null) {
+                mDecorContentParent.dismissPopups();
             }
 
             if (mPrimaryActionModePopup != null) {
@@ -3423,7 +3417,7 @@
                 mFloatingToolbar = null;
             }
 
-            PanelFeatureState st = mWindow.getPanelState(FEATURE_OPTIONS_PANEL, false);
+            PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
             if (st != null && st.menu != null && mFeatureId < 0) {
                 st.menu.close();
             }
@@ -3432,29 +3426,29 @@
         @Override
         public void onCloseSystemDialogs(String reason) {
             if (mFeatureId >= 0) {
-                mWindow.closeAllPanels();
+                closeAllPanels();
             }
         }
 
         public android.view.SurfaceHolder.Callback2 willYouTakeTheSurface() {
-            return mFeatureId < 0 ? mWindow.mTakeSurfaceCallback : null;
+            return mFeatureId < 0 ? mTakeSurfaceCallback : null;
         }
 
         public InputQueue.Callback willYouTakeTheInputQueue() {
-            return mFeatureId < 0 ? mWindow.mTakeInputQueueCallback : null;
+            return mFeatureId < 0 ? mTakeInputQueueCallback : null;
         }
 
         public void setSurfaceType(int type) {
-            mWindow.setType(type);
+            PhoneWindow.this.setType(type);
         }
 
         public void setSurfaceFormat(int format) {
-            mWindow.setFormat(format);
+            PhoneWindow.this.setFormat(format);
         }
 
         public void setSurfaceKeepScreenOn(boolean keepOn) {
-            if (keepOn) mWindow.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-            else mWindow.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+            if (keepOn) PhoneWindow.this.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+            else PhoneWindow.this.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
         }
 
         @Override
@@ -3486,7 +3480,7 @@
             endOnGoingFadeAnimation();
             cleanupPrimaryActionMode();
             if (mPrimaryActionModeView == null) {
-                if (mWindow.isFloating()) {
+                if (isFloating()) {
                     // Use the action bar theme.
                     final TypedValue outValue = new TypedValue();
                     final Theme baseTheme = mContext.getTheme();
@@ -3633,7 +3627,7 @@
 
         private void setHandledFloatingActionMode(ActionMode mode) {
             mFloatingActionMode = mode;
-            mFloatingToolbar = new FloatingToolbar(mContext, mWindow);
+            mFloatingToolbar = new FloatingToolbar(mContext, PhoneWindow.this);
             ((FloatingActionMode) mFloatingActionMode).setFloatingToolbar(mFloatingToolbar);
             mFloatingActionMode.invalidate();  // Will show the floating toolbar if necessary.
             mFloatingActionModeOriginatingView.getViewTreeObserver()
@@ -3672,10 +3666,6 @@
             return windowHasNonClientDecor() && getElevation() > 0;
         }
 
-        void setWindow(PhoneWindow phoneWindow) {
-            mWindow = phoneWindow;
-        }
-
         /**
          * Clears out internal references when the action mode is destroyed.
          */
@@ -3764,9 +3754,9 @@
                     cleanupFloatingActionModeViews();
                     mFloatingActionMode = null;
                 }
-                if (mWindow.getCallback() != null && !mWindow.isDestroyed()) {
+                if (getCallback() != null && !isDestroyed()) {
                     try {
-                        mWindow.getCallback().onActionModeFinished(mode);
+                        getCallback().onActionModeFinished(mode);
                     } catch (AbstractMethodError ame) {
                         // Older apps might not implement this callback method.
                     }
@@ -3786,14 +3776,7 @@
     }
 
     protected DecorView generateDecor(int featureId) {
-        // System process doesn't have application context and in that case we need to directly use
-        // the context we have. Otherwise we want the application context, so we don't cling to the
-        // activity.
-        Context context = getContext().getApplicationContext();
-        if (context == null) {
-            context = getContext();
-        }
-        return new DecorView(context, featureId);
+        return new DecorView(getContext(), featureId);
     }
 
     protected void setFeatureFromAttrs(int featureId, TypedArray attrs,
@@ -4164,17 +4147,11 @@
             if (nonClientDecorView == null) {
                 TypedValue value = new TypedValue();
                 getContext().getTheme().resolveAttribute(R.attr.colorPrimary, value, true);
-                // We can't use the application context inside the general inflater, because some
-                // views might depend on the fact that they get Activity or even specific activity.
-                // We control the NonClientDecor, so we know that application context should be
-                // safe enough.
-                LayoutInflater inflater =
-                        mLayoutInflater.cloneInContext(getContext().getApplicationContext());
                 if (Color.luminance(value.data) < 0.5) {
-                    nonClientDecorView = (NonClientDecorView) inflater.inflate(
+                    nonClientDecorView = (NonClientDecorView) mLayoutInflater.inflate(
                             R.layout.non_client_decor_dark, null);
                 } else {
-                    nonClientDecorView = (NonClientDecorView) inflater.inflate(
+                    nonClientDecorView = (NonClientDecorView) mLayoutInflater.inflate(
                             R.layout.non_client_decor_light, null);
                 }
             }
@@ -4196,14 +4173,12 @@
         mForceDecorInstall = false;
         if (mDecor == null) {
             mDecor = generateDecor(-1);
-            mDecor.setWindow(this);
             mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
             mDecor.setIsRootNamespace(true);
             if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                 mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
             }
         }
-        mDecor.setWindow(this);
         if (mContentParent == null) {
             mContentParent = generateLayout(mDecor);
 
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index ab3ec98..11ef18b 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -74,7 +74,9 @@
 
     /**
      * Notifies the status bar that a camera launch gesture has been detected.
+     *
+     * @param source the identifier for the gesture, see {@link StatusBarManager}
      */
-    void onCameraLaunchGestureDetected();
+    void onCameraLaunchGestureDetected(int source);
 }
 
diff --git a/core/res/res/values-watch/config.xml b/core/res/res/values-watch/config.xml
index 41b05ea..919519e 100644
--- a/core/res/res/values-watch/config.xml
+++ b/core/res/res/values-watch/config.xml
@@ -54,4 +54,7 @@
 
     <!-- Do not show the message saying USB is connected in charging mode. -->
     <bool name="config_usbChargingMessage">false</bool>
+
+    <!-- Use a custom transition for RemoteViews. -->
+    <bool name="config_overrideRemoteViewsActivityTransition">true</bool>
 </resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index dc96df4..83d9a3c 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2150,6 +2150,13 @@
               (which is exiting the screen).  The wallpaper remains
               static behind the animation. -->
         <attr name="wallpaperIntraCloseExitAnimation" format="reference" />
+
+        <!--  When opening a new activity from a RemoteViews, this is the
+              animation that is run on the next activity (which is entering the
+              screen). Requires config_overrideRemoteViewsActivityTransition to
+              be true. -->
+        <attr name="activityOpenRemoteViewsEnterAnimation" format="reference" />
+
     </declare-styleable>
 
     <!-- ============================= -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 9b1a613..24d760f 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2214,6 +2214,10 @@
     <bool name="config_defaultWindowFeatureOptionsPanel">true</bool>
     <bool name="config_defaultWindowFeatureContextMenu">true</bool>
 
+    <!-- If true, the transition for a RemoteViews is read from a resource instead of using the
+         default scale-up transition. -->
+    <bool name="config_overrideRemoteViewsActivityTransition">false</bool>
+
     <!-- This config is used to check if the carrier requires converting destination
          number before sending out a SMS.
          Formats for this configuration as below:
diff --git a/core/res/res/values/styles_micro.xml b/core/res/res/values/styles_micro.xml
index c6052ff..05835e7 100644
--- a/core/res/res/values/styles_micro.xml
+++ b/core/res/res/values/styles_micro.xml
@@ -18,6 +18,7 @@
 
     <style name="Animation.Micro.Activity" parent="Animation.Material.Activity">
         <item name="activityOpenEnterAnimation">@anim/slide_in_micro</item>
+        <item name="activityOpenRemoteViewsEnterAnimation">@anim/slide_in_micro</item>
         <item name="activityOpenExitAnimation">@null</item>
         <item name="activityCloseEnterAnimation">@null</item>
         <item name="activityCloseExitAnimation">@anim/slide_out_micro</item>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b5efbfd..028b95d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2191,6 +2191,7 @@
   <java-symbol type="bool" name="config_sms_force_7bit_encoding" />
   <java-symbol type="bool" name="config_defaultWindowFeatureOptionsPanel" />
   <java-symbol type="bool" name="config_defaultWindowFeatureContextMenu" />
+  <java-symbol type="bool" name="config_overrideRemoteViewsActivityTransition" />
 
   <java-symbol type="layout" name="simple_account_item" />
   <java-symbol type="array" name="config_sms_convert_destination_number_support" />
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
index b37688f..ce9ae02 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -17,6 +17,9 @@
 package android.widget;
 
 import static android.widget.espresso.TextViewActions.clickOnTextAtIndex;
+import static android.widget.espresso.TextViewActions.doubleTapAndDragOnText;
+import static android.widget.espresso.TextViewActions.longPressAndDragOnText;
+import static android.widget.espresso.TextViewAssertions.hasSelection;
 import static android.support.test.espresso.Espresso.onView;
 import static android.support.test.espresso.action.ViewActions.click;
 import static android.support.test.espresso.action.ViewActions.pressKey;
@@ -63,4 +66,28 @@
         onView(withId(R.id.textview)).perform(pressKey(KeyEvent.KEYCODE_FORWARD_DEL));
         onView(withId(R.id.textview)).check(matches(withText("Hello orld!")));
     }
+
+    @SmallTest
+    public void testLongPressAndDragToSelect() throws Exception {
+        getActivity();
+
+        final String helloWorld = "Hello little handsome boy!";
+        onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(helloWorld));
+        onView(withId(R.id.textview)).perform(
+                longPressAndDragOnText(helloWorld.indexOf("little"), helloWorld.indexOf(" boy!")));
+
+        onView(withId(R.id.textview)).check(hasSelection("little handsome"));
+    }
+
+    @SmallTest
+    public void testDoubleTapAndDragToSelect() throws Exception {
+        getActivity();
+
+        final String helloWorld = "Hello young beautiful girl!";
+        onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(helloWorld));
+        onView(withId(R.id.textview)).perform(
+                doubleTapAndDragOnText(helloWorld.indexOf("young"), helloWorld.indexOf(" girl!")));
+
+        onView(withId(R.id.textview)).check(hasSelection("young beautiful"));
+    }
 }
diff --git a/core/tests/coretests/src/android/widget/espresso/DragOnTextViewActions.java b/core/tests/coretests/src/android/widget/espresso/DragOnTextViewActions.java
new file mode 100644
index 0000000..a0cd848
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/espresso/DragOnTextViewActions.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2015 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 android.widget.espresso;
+
+import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
+import static android.support.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed;
+import static com.android.internal.util.Preconditions.checkNotNull;
+import static org.hamcrest.Matchers.allOf;
+
+import android.annotation.Nullable;
+import android.os.SystemClock;
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.PerformException;
+import android.support.test.espresso.ViewAction;
+import android.support.test.espresso.action.CoordinatesProvider;
+import android.support.test.espresso.action.GeneralClickAction;
+import android.support.test.espresso.action.MotionEvents;
+import android.support.test.espresso.action.PrecisionDescriber;
+import android.support.test.espresso.action.Press;
+import android.support.test.espresso.action.Swiper;
+import android.support.test.espresso.action.Tap;
+import android.support.test.espresso.util.HumanReadables;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.widget.TextView;
+import org.hamcrest.Matcher;
+
+
+/**
+ * Drags on text in a TextView using touch events.<br>
+ * <br>
+ * View constraints:
+ * <ul>
+ * <li>must be a TextView displayed on screen
+ * <ul>
+ */
+public final class DragOnTextViewActions implements ViewAction {
+
+    /**
+     * Executes different "drag on text" types to given positions.
+     */
+    public enum Drag implements Swiper {
+
+        /**
+         * Starts a drag with a long-press.
+         */
+        LONG_PRESS {
+            private DownMotionPerformer downMotion = new DownMotionPerformer() {
+                @Override
+                public MotionEvent perform(
+                        UiController uiController, float[] coordinates, float[] precision) {
+                    MotionEvent downEvent = MotionEvents.sendDown(
+                            uiController, coordinates, precision)
+                            .down;
+                    // Duration before a press turns into a long press.
+                    // Factor 1.5 is needed, otherwise a long press is not safely detected.
+                    // See android.test.TouchUtils longClickView
+                    long longPressTimeout = (long) (ViewConfiguration.getLongPressTimeout() * 1.5f);
+                    uiController.loopMainThreadForAtLeast(longPressTimeout);
+                    return downEvent;
+                }
+            };
+
+            @Override
+            public Status sendSwipe(
+                    UiController uiController,
+                    float[] startCoordinates, float[] endCoordinates, float[] precision) {
+                return sendLinearDrag(
+                        uiController, downMotion, startCoordinates, endCoordinates, precision);
+            }
+
+            @Override
+            public String toString() {
+                return "long press and drag to select";
+            }
+        },
+
+        /**
+         * Starts a drag with a double-tap.
+         */
+        DOUBLE_TAP {
+            private DownMotionPerformer downMotion = new DownMotionPerformer() {
+                @Override
+                @Nullable
+                public MotionEvent perform(
+                        UiController uiController,  float[] coordinates, float[] precision) {
+                    MotionEvent downEvent = MotionEvents.sendDown(
+                            uiController, coordinates, precision)
+                            .down;
+                    try {
+                        if (!MotionEvents.sendUp(uiController, downEvent)) {
+                            String logMessage = "Injection of up event as part of the double tap " +
+                                    "failed. Sending cancel event.";
+                            Log.d(TAG, logMessage);
+                            MotionEvents.sendCancel(uiController, downEvent);
+                            return null;
+                        }
+
+                        long doubleTapMinimumTimeout = ViewConfiguration.getDoubleTapMinTime();
+                        uiController.loopMainThreadForAtLeast(doubleTapMinimumTimeout);
+
+                        return MotionEvents.sendDown(uiController, coordinates, precision).down;
+                    } finally {
+                        downEvent.recycle();
+                    }
+                }
+            };
+
+            @Override
+            public Status sendSwipe(
+                    UiController uiController,
+                    float[] startCoordinates, float[] endCoordinates, float[] precision) {
+                return sendLinearDrag(
+                        uiController, downMotion, startCoordinates, endCoordinates, precision);
+            }
+
+            @Override
+            public String toString() {
+                return "double-tap and drag to select";
+            }
+        };
+
+        private static final String TAG = Drag.class.getSimpleName();
+
+        /** The number of move events to send for each drag. */
+        private static final int DRAG_STEP_COUNT = 10;
+
+        /** Length of time a drag should last for, in milliseconds. */
+        private static final int DRAG_DURATION = 1500;
+
+        private static Status sendLinearDrag(
+                UiController uiController, DownMotionPerformer downMotion,
+                float[] startCoordinates, float[] endCoordinates, float[] precision) {
+            float[][] steps = interpolate(startCoordinates, endCoordinates);
+            final int delayBetweenMovements = DRAG_DURATION / steps.length;
+
+            MotionEvent downEvent = downMotion.perform(uiController, startCoordinates, precision);
+            if (downEvent == null) {
+                return Status.FAILURE;
+            }
+
+            try {
+                for (int i = 0; i < steps.length; i++) {
+                    if (!MotionEvents.sendMovement(uiController, downEvent, steps[i])) {
+                        String logMessage = "Injection of move event as part of the drag failed. " +
+                                "Sending cancel event.";
+                        Log.e(TAG, logMessage);
+                        MotionEvents.sendCancel(uiController, downEvent);
+                        return Status.FAILURE;
+                    }
+
+                    long desiredTime = downEvent.getDownTime() + delayBetweenMovements * i;
+                    long timeUntilDesired = desiredTime - SystemClock.uptimeMillis();
+                    if (timeUntilDesired > 10) {
+                        // If the wait time until the next event isn't long enough, skip the wait
+                        // and execute the next event.
+                        uiController.loopMainThreadForAtLeast(timeUntilDesired);
+                    }
+                }
+
+                if (!MotionEvents.sendUp(uiController, downEvent, endCoordinates)) {
+                    String logMessage = "Injection of up event as part of the drag failed. " +
+                            "Sending cancel event.";
+                    Log.e(TAG, logMessage);
+                    MotionEvents.sendCancel(uiController, downEvent);
+                    return Status.FAILURE;
+                }
+            } finally {
+                downEvent.recycle();
+            }
+            return Status.SUCCESS;
+        }
+
+        private static float[][] interpolate(float[] start, float[] end) {
+            float[][] res = new float[DRAG_STEP_COUNT][2];
+
+            for (int i = 1; i < DRAG_STEP_COUNT + 1; i++) {
+                res[i - 1][0] = start[0] + (end[0] - start[0]) * i / (DRAG_STEP_COUNT + 2f);
+                res[i - 1][1] = start[1] + (end[1] - start[1]) * i / (DRAG_STEP_COUNT + 2f);
+            }
+
+            return res;
+        }
+    }
+
+    /**
+     * Interface to implement different "down motion" types.
+     */
+    private interface DownMotionPerformer {
+        /**
+         * Performs and returns a down motion.
+         *
+         * @param uiController a UiController to use to send MotionEvents to the screen.
+         * @param coordinates a float[] with x and y values of center of the tap.
+         * @param precision  a float[] with x and y values of precision of the tap.
+         * @return the down motion event or null if the down motion event failed.
+         */
+        @Nullable
+        MotionEvent perform(UiController uiController, float[] coordinates, float[] precision);
+    }
+
+    private final Swiper mDragger;
+    private final CoordinatesProvider mStartCoordinatesProvider;
+    private final CoordinatesProvider mEndCoordinatesProvider;
+    private final PrecisionDescriber mPrecisionDescriber;
+
+    public DragOnTextViewActions(
+            Swiper dragger,
+            CoordinatesProvider startCoordinatesProvider,
+            CoordinatesProvider endCoordinatesProvider,
+            PrecisionDescriber precisionDescriber) {
+        mDragger = checkNotNull(dragger);
+        mStartCoordinatesProvider = checkNotNull(startCoordinatesProvider);
+        mEndCoordinatesProvider = checkNotNull(endCoordinatesProvider);
+        mPrecisionDescriber = checkNotNull(precisionDescriber);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public Matcher<View> getConstraints() {
+        return allOf(isCompletelyDisplayed(), isAssignableFrom(TextView.class));
+    }
+
+    @Override
+    public void perform(UiController uiController, View view) {
+        checkNotNull(uiController);
+        checkNotNull(view);
+
+        float[] startCoordinates = mStartCoordinatesProvider.calculateCoordinates(view);
+        float[] endCoordinates = mEndCoordinatesProvider.calculateCoordinates(view);
+        float[] precision = mPrecisionDescriber.describePrecision();
+
+        Swiper.Status status;
+
+        try {
+            status = mDragger.sendSwipe(
+                    uiController, startCoordinates, endCoordinates, precision);
+        } catch (RuntimeException re) {
+            throw new PerformException.Builder()
+                    .withActionDescription(this.getDescription())
+                    .withViewDescription(HumanReadables.describe(view))
+                    .withCause(re)
+                    .build();
+        }
+
+        int duration = ViewConfiguration.getPressedStateDuration();
+        // ensures that all work enqueued to process the swipe has been run.
+        if (duration > 0) {
+            uiController.loopMainThreadForAtLeast(duration);
+        }
+
+        if (status == Swiper.Status.FAILURE) {
+            throw new PerformException.Builder()
+                    .withActionDescription(getDescription())
+                    .withViewDescription(HumanReadables.describe(view))
+                    .withCause(new RuntimeException(getDescription() + " failed"))
+                    .build();
+        }
+    }
+
+    @Override
+    public String getDescription() {
+        return mDragger.toString();
+    }
+}
diff --git a/core/tests/coretests/src/android/widget/espresso/TextViewActions.java b/core/tests/coretests/src/android/widget/espresso/TextViewActions.java
index 425dccd..7e4735b 100644
--- a/core/tests/coretests/src/android/widget/espresso/TextViewActions.java
+++ b/core/tests/coretests/src/android/widget/espresso/TextViewActions.java
@@ -18,7 +18,6 @@
 
 import static android.support.test.espresso.action.ViewActions.actionWithAssertions;
 
-import android.content.res.Resources;
 import android.support.test.espresso.PerformException;
 import android.support.test.espresso.ViewAction;
 import android.support.test.espresso.action.CoordinatesProvider;
@@ -27,8 +26,6 @@
 import android.support.test.espresso.action.Tap;
 import android.support.test.espresso.util.HumanReadables;
 import android.text.Layout;
-import android.util.DisplayMetrics;
-import android.util.TypedValue;
 import android.view.View;
 import android.widget.TextView;
 
@@ -40,12 +37,14 @@
     private TextViewActions() {}
 
     /**
-     * Returns an action that clicks on text at an index on the text view.<br>
+     * Returns an action that clicks on text at an index on the TextView.<br>
      * <br>
      * View constraints:
      * <ul>
-     * <li>must be a text view displayed on screen
+     * <li>must be a TextView displayed on screen
      * <ul>
+     *
+     * @param index The index of the TextView's text to click on.
      */
     public static ViewAction clickOnTextAtIndex(int index) {
         return actionWithAssertions(
@@ -53,6 +52,48 @@
     }
 
     /**
+     * Returns an action that long presses then drags on text from startIndex to endIndex on the
+     * TextView.<br>
+     * <br>
+     * View constraints:
+     * <ul>
+     * <li>must be a TextView displayed on screen
+     * <ul>
+     *
+     * @param startIndex The index of the TextView's text to start a drag from
+     * @param endIndex The index of the TextView's text to end the drag at
+     */
+    public static ViewAction longPressAndDragOnText(int startIndex, int endIndex) {
+        return actionWithAssertions(
+                new DragOnTextViewActions(
+                        DragOnTextViewActions.Drag.LONG_PRESS,
+                        new TextCoordinates(startIndex),
+                        new TextCoordinates(endIndex),
+                        Press.FINGER));
+    }
+
+    /**
+     * Returns an action that double taps then drags on text from startIndex to endIndex on the
+     * TextView.<br>
+     * <br>
+     * View constraints:
+     * <ul>
+     * <li>must be a TextView displayed on screen
+     * <ul>
+     *
+     * @param startIndex The index of the TextView's text to start a drag from
+     * @param endIndex The index of the TextView's text to end the drag at
+     */
+    public static ViewAction doubleTapAndDragOnText(int startIndex, int endIndex) {
+        return actionWithAssertions(
+                new DragOnTextViewActions(
+                        DragOnTextViewActions.Drag.DOUBLE_TAP,
+                        new TextCoordinates(startIndex),
+                        new TextCoordinates(endIndex),
+                        Press.FINGER));
+    }
+
+    /**
      * A provider of the x, y coordinates of the text at the specified index in a text view.
      */
     private static final class TextCoordinates implements CoordinatesProvider {
diff --git a/core/tests/coretests/src/android/widget/espresso/TextViewAssertions.java b/core/tests/coretests/src/android/widget/espresso/TextViewAssertions.java
new file mode 100644
index 0000000..dce3182
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/espresso/TextViewAssertions.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2015 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 android.widget.espresso;
+
+import static android.support.test.espresso.matcher.ViewMatchers.assertThat;
+import static com.android.internal.util.Preconditions.checkNotNull;
+import static org.hamcrest.Matchers.is;
+
+import android.support.test.espresso.NoMatchingViewException;
+import android.support.test.espresso.ViewAssertion;
+import android.view.View;
+import android.widget.TextView;
+
+import junit.framework.AssertionFailedError;
+import org.hamcrest.Matcher;
+
+/**
+ * A collection of assertions on a {@link android.widget.TextView}.
+ */
+public final class TextViewAssertions {
+
+    private TextViewAssertions() {}
+
+    /**
+     * Returns a {@link ViewAssertion} that asserts that the text view has a specified
+     * selection.<br>
+     * <br>
+     * View constraints:
+     * <ul>
+     * <li>must be a text view displayed on screen
+     * <ul>
+     *
+     * @param selection  The expected selection.
+     */
+    public static ViewAssertion hasSelection(String selection) {
+        return new TextSelectionAssertion(is(selection));
+    }
+
+    /**
+     * Returns a {@link ViewAssertion} that asserts that the text view has a specified
+     * selection.<br>
+     * <br>
+     * View constraints:
+     * <ul>
+     * <li>must be a text view displayed on screen
+     * <ul>
+     *
+     * @param selection  A matcher representing the expected selection.
+     */
+    public static ViewAssertion hasSelection(Matcher<String> selection) {
+        return new TextSelectionAssertion(selection);
+    }
+
+    /**
+     * A {@link ViewAssertion} to check the selected text in a {@link TextView}.
+     */
+    private static final class TextSelectionAssertion implements ViewAssertion {
+
+        private final Matcher<String> mSelection;
+
+        public TextSelectionAssertion(Matcher<String> selection) {
+            mSelection = checkNotNull(selection);
+        }
+
+        @Override
+        public void check(View view, NoMatchingViewException exception) {
+            if (view instanceof TextView) {
+                TextView textView = (TextView) view;
+                int selectionStart = textView.getSelectionStart();
+                int selectionEnd = textView.getSelectionEnd();
+                try {
+                    String selectedText = textView.getText()
+                            .subSequence(selectionStart, selectionEnd)
+                            .toString();
+                    assertThat(selectedText, mSelection);
+                } catch (IndexOutOfBoundsException e) {
+                    throw new AssertionFailedError(e.getMessage());
+                }
+            } else {
+                throw new AssertionFailedError("TextView not found");
+            }
+        }
+    }
+}
diff --git a/packages/DocumentsUI/res/layout/fragment_directory.xml b/packages/DocumentsUI/res/layout/fragment_directory.xml
index b02349a..45cb34d 100644
--- a/packages/DocumentsUI/res/layout/fragment_directory.xml
+++ b/packages/DocumentsUI/res/layout/fragment_directory.xml
@@ -17,61 +17,70 @@
 <com.android.documentsui.DirectoryView xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:background="@color/material_grey_50">
+    android:background="@color/material_grey_50"
+    android:orientation="vertical"
+    android:animateLayoutChanges="true">
 
-    <TextView
-        android:id="@android:id/empty"
+    <ProgressBar
+        android:id="@+id/progressbar"
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:gravity="center"
-        android:text="@string/empty"
-        android:visibility="gone"
-        style="@android:style/TextAppearance.Material.Subhead" />
+        android:layout_height="wrap_content"
+        android:indeterminate="true"
+        style="@style/TrimmedHorizontalProgressBar"
+        android:visibility="gone"/>
+  
+    <FrameLayout
+        android:id="@+id/container_message_bar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:elevation="8dp"
+        android:background="@color/material_grey_50"
+        android:visibility="gone"/>
 
+    <!-- The empty directory view -->
     <LinearLayout
-        android:id="@+id/content"
+        android:id="@android:id/empty"
+        android:gravity="center"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:orientation="vertical"
-        android:animateLayoutChanges="true">
-
-        <ProgressBar
-            android:id="@+id/progressbar"
-            android:layout_width="match_parent"
+        android:visibility="gone">
+        
+        <TextView
+            android:id="@+id/message"
+            android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:indeterminate="true"
-            style="@style/TrimmedHorizontalProgressBar"
-            android:visibility="gone"/>
+            android:text="@string/empty"
+            style="@android:style/TextAppearance.Material.Subhead" />
 
-        <FrameLayout
-            android:id="@+id/container_message_bar"
-            android:layout_width="match_parent"
+         <Button
+            android:id="@+id/button_retry"
+            android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:elevation="8dp"
-            android:background="@color/material_grey_50"
-            android:visibility="gone"/>
-
-        <!-- This FrameLayout works around b/24189541 -->
-        <FrameLayout
-            android:layout_width="match_parent"
-            android:layout_height="match_parent">
-
-            <android.support.v7.widget.RecyclerView
-                android:id="@+id/recyclerView"
-                android:scrollbars="vertical"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:paddingStart="@dimen/grid_padding_horiz"
-                android:paddingEnd="@dimen/grid_padding_horiz"
-                android:paddingTop="@dimen/grid_padding_vert"
-                android:paddingBottom="@dimen/grid_padding_vert"
-                android:clipToPadding="false"
-                android:scrollbarStyle="outsideOverlay"
-                android:drawSelectorOnTop="true"
-                android:background="@color/directory_background" />
-
-        </FrameLayout>
-
+            android:text="@string/button_retry"
+            style="?android:attr/buttonBarPositiveButtonStyle" />
+        
     </LinearLayout>
+    
+    <!-- This FrameLayout works around b/24189541 -->
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <android.support.v7.widget.RecyclerView
+            android:id="@+id/recyclerView"
+            android:scrollbars="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:paddingStart="@dimen/grid_padding_horiz"
+            android:paddingEnd="@dimen/grid_padding_horiz"
+            android:paddingTop="@dimen/grid_padding_vert"
+            android:paddingBottom="@dimen/grid_padding_vert"
+            android:clipToPadding="false"
+            android:scrollbarStyle="outsideOverlay"
+            android:drawSelectorOnTop="true"
+            android:background="@color/directory_background" />
+
+    </FrameLayout>
 
 </com.android.documentsui.DirectoryView>
diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml
index 28018f8e..a12edf3 100644
--- a/packages/DocumentsUI/res/values/strings.xml
+++ b/packages/DocumentsUI/res/values/strings.xml
@@ -81,7 +81,8 @@
     <string name="button_move">Move</string>
     <!-- Button label that hides the error bar [CHAR LIMIT=24] -->
     <string name="button_dismiss">Dismiss</string>
-
+    <string name="button_retry">Try Again</string>
+    
     <!-- Mode that sorts documents by their display name alphabetically [CHAR LIMIT=24] -->
     <string name="sort_name">By name</string>
     <!-- Mode that sorts documents by their last modified time in descending order; most recent first [CHAR LIMIT=24] -->
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
index 1585908..c8ec4dc 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
@@ -33,14 +33,11 @@
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
 import android.provider.DocumentsContract;
 import android.provider.DocumentsContract.Root;
 import android.support.annotation.LayoutRes;
 import android.support.annotation.Nullable;
 import android.util.Log;
-import android.util.SparseArray;
 import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuItem;
@@ -67,7 +64,6 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.HashMap;
 import java.util.List;
 import java.util.concurrent.Executor;
 
@@ -367,129 +363,6 @@
         public static String EXTRA_DIRECTORY_COPY = "com.android.documentsui.DIRECTORY_COPY";
     }
 
-    public static class State implements android.os.Parcelable {
-        public int action;
-        public String[] acceptMimes;
-
-        /** Explicit user choice */
-        public int userMode = MODE_UNKNOWN;
-        /** Derived after loader */
-        public int derivedMode = MODE_LIST;
-
-        /** Explicit user choice */
-        public int userSortOrder = SORT_ORDER_UNKNOWN;
-        /** Derived after loader */
-        public int derivedSortOrder = SORT_ORDER_DISPLAY_NAME;
-
-        public boolean allowMultiple;
-        public boolean forceSize ;
-        public boolean showSize;
-        public boolean localOnly ;
-        public boolean forceAdvanced ;
-        public boolean showAdvanced ;
-        public boolean stackTouched ;
-        public boolean restored ;
-        public boolean directoryCopy ;
-        /** Transfer mode for file copy/move operations. */
-        public int transferMode;
-
-        /** Current user navigation stack; empty implies recents. */
-        public DocumentStack stack = new DocumentStack();
-        /** Currently active search, overriding any stack. */
-        public String currentSearch;
-
-        /** Instance state for every shown directory */
-        public HashMap<String, SparseArray<Parcelable>> dirState = new HashMap<>();
-
-        /** Currently copying file */
-        public List<DocumentInfo> selectedDocumentsForCopy = new ArrayList<DocumentInfo>();
-
-        /** Name of the package that started DocsUI */
-        public List<String> excludedAuthorities = new ArrayList<>();
-
-        public static final int ACTION_OPEN = 1;
-        public static final int ACTION_CREATE = 2;
-        public static final int ACTION_GET_CONTENT = 3;
-        public static final int ACTION_OPEN_TREE = 4;
-        public static final int ACTION_MANAGE = 5;
-        public static final int ACTION_BROWSE = 6;
-        public static final int ACTION_OPEN_COPY_DESTINATION = 8;
-
-        public static final int MODE_UNKNOWN = 0;
-        public static final int MODE_LIST = 1;
-        public static final int MODE_GRID = 2;
-
-        public static final int SORT_ORDER_UNKNOWN = 0;
-        public static final int SORT_ORDER_DISPLAY_NAME = 1;
-        public static final int SORT_ORDER_LAST_MODIFIED = 2;
-        public static final int SORT_ORDER_SIZE = 3;
-
-        public void initAcceptMimes(Intent intent) {
-            if (intent.hasExtra(Intent.EXTRA_MIME_TYPES)) {
-                acceptMimes = intent.getStringArrayExtra(Intent.EXTRA_MIME_TYPES);
-            } else {
-                String glob = intent.getType();
-                acceptMimes = new String[] { glob != null ? glob : "*/*" };
-            }
-        }
-
-        @Override
-        public int describeContents() {
-            return 0;
-        }
-
-        @Override
-        public void writeToParcel(Parcel out, int flags) {
-            out.writeInt(action);
-            out.writeInt(userMode);
-            out.writeStringArray(acceptMimes);
-            out.writeInt(userSortOrder);
-            out.writeInt(allowMultiple ? 1 : 0);
-            out.writeInt(forceSize ? 1 : 0);
-            out.writeInt(showSize ? 1 : 0);
-            out.writeInt(localOnly ? 1 : 0);
-            out.writeInt(forceAdvanced ? 1 : 0);
-            out.writeInt(showAdvanced ? 1 : 0);
-            out.writeInt(stackTouched ? 1 : 0);
-            out.writeInt(restored ? 1 : 0);
-            DurableUtils.writeToParcel(out, stack);
-            out.writeString(currentSearch);
-            out.writeMap(dirState);
-            out.writeList(selectedDocumentsForCopy);
-            out.writeList(excludedAuthorities);
-        }
-
-        public static final Creator<State> CREATOR = new Creator<State>() {
-            @Override
-            public State createFromParcel(Parcel in) {
-                final State state = new State();
-                state.action = in.readInt();
-                state.userMode = in.readInt();
-                state.acceptMimes = in.readStringArray();
-                state.userSortOrder = in.readInt();
-                state.allowMultiple = in.readInt() != 0;
-                state.forceSize = in.readInt() != 0;
-                state.showSize = in.readInt() != 0;
-                state.localOnly = in.readInt() != 0;
-                state.forceAdvanced = in.readInt() != 0;
-                state.showAdvanced = in.readInt() != 0;
-                state.stackTouched = in.readInt() != 0;
-                state.restored = in.readInt() != 0;
-                DurableUtils.readFromParcel(in, state.stack);
-                state.currentSearch = in.readString();
-                in.readMap(state.dirState, null);
-                in.readList(state.selectedDocumentsForCopy, null);
-                in.readList(state.excludedAuthorities, null);
-                return state;
-            }
-
-            @Override
-            public State[] newArray(int size) {
-                return new State[size];
-            }
-        };
-    }
-
     void setDisplayAdvancedDevices(boolean display) {
         State state = getDisplayState();
         LocalPreferences.setDisplayAdvancedDevices(this, display);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
index f8ec8f1..f1492dc7 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
@@ -16,6 +16,7 @@
 
 package com.android.documentsui;
 
+import static com.android.documentsui.Shared.DEBUG;
 import static com.android.documentsui.model.DocumentInfo.getCursorLong;
 import static com.android.documentsui.model.DocumentInfo.getCursorString;
 
@@ -56,7 +57,6 @@
 
 public class CopyService extends IntentService {
     public static final String TAG = "CopyService";
-    public static final boolean DEBUG = false;
 
     private static final String EXTRA_CANCEL = "com.android.documentsui.CANCEL";
     public static final String EXTRA_SRC_LIST = "com.android.documentsui.SRC_LIST";
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index ea8ecf5..5eacf21 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
@@ -16,14 +16,15 @@
 
 package com.android.documentsui;
 
-import static com.android.documentsui.BaseActivity.State.ACTION_BROWSE;
-import static com.android.documentsui.BaseActivity.State.ACTION_CREATE;
-import static com.android.documentsui.BaseActivity.State.ACTION_MANAGE;
-import static com.android.documentsui.BaseActivity.State.MODE_GRID;
-import static com.android.documentsui.BaseActivity.State.MODE_LIST;
-import static com.android.documentsui.BaseActivity.State.MODE_UNKNOWN;
-import static com.android.documentsui.BaseActivity.State.SORT_ORDER_UNKNOWN;
+import static com.android.documentsui.Shared.DEBUG;
 import static com.android.documentsui.Shared.TAG;
+import static com.android.documentsui.State.ACTION_BROWSE;
+import static com.android.documentsui.State.ACTION_CREATE;
+import static com.android.documentsui.State.ACTION_MANAGE;
+import static com.android.documentsui.State.MODE_GRID;
+import static com.android.documentsui.State.MODE_LIST;
+import static com.android.documentsui.State.MODE_UNKNOWN;
+import static com.android.documentsui.State.SORT_ORDER_UNKNOWN;
 import static com.android.documentsui.model.DocumentInfo.getCursorInt;
 import static com.android.documentsui.model.DocumentInfo.getCursorLong;
 import static com.android.documentsui.model.DocumentInfo.getCursorString;
@@ -93,7 +94,6 @@
 import android.widget.Toast;
 
 import com.android.documentsui.BaseActivity.DocumentContext;
-import com.android.documentsui.BaseActivity.State;
 import com.android.documentsui.MultiSelectManager.Selection;
 import com.android.documentsui.ProviderExecutor.Preemptable;
 import com.android.documentsui.RecentsProvider.StateColumns;
@@ -124,7 +124,6 @@
     public static final int REQUEST_COPY_DESTINATION = 1;
 
     private static final int LOADER_ID = 42;
-    private static final boolean DEBUG = false;
     private static final boolean DEBUG_ENABLE_DND = false;
 
     private static final String EXTRA_TYPE = "type";
@@ -367,21 +366,6 @@
 
             @Override
             public void onLoadFinished(Loader<DirectoryResult> loader, DirectoryResult result) {
-                if (result == null || result.exception != null) {
-                    // onBackPressed does a fragment transaction, which can't be done inside
-                    // onLoadFinished
-                    mHandler.post(new Runnable() {
-                        @Override
-                        public void run() {
-                            final Activity activity = getActivity();
-                            if (activity != null) {
-                                activity.onBackPressed();
-                            }
-                        }
-                    });
-                    return;
-                }
-
                 if (!isAdded()) return;
 
                 mModel.update(result);
@@ -629,7 +613,6 @@
 
         @Override
         public void onItemStateChanged(int position, boolean selected) {
-
             final Cursor cursor = mModel.getItem(position);
             checkNotNull(cursor, "Cursor cannot be null.");
 
@@ -715,8 +698,9 @@
                 return true;
 
             } else if (id == R.id.menu_delete) {
-                deleteDocuments(selection);
+                // Exit selection mode first, so we avoid deselecting deleted documents.
                 mode.finish();
+                deleteDocuments(selection);
                 return true;
 
             } else if (id == R.id.menu_copy_to) {
@@ -725,8 +709,9 @@
                 return true;
 
             } else if (id == R.id.menu_move_to) {
-                transferDocuments(selection, CopyService.TRANSFER_MODE_MOVE);
+                // Exit selection mode first, so we avoid deselecting deleted documents.
                 mode.finish();
+                transferDocuments(selection, CopyService.TRANSFER_MODE_MOVE);
                 return true;
 
             } else if (id == R.id.menu_copy_to_clipboard) {
@@ -898,6 +883,29 @@
         }
     }
 
+    void showEmptyView() {
+        mEmptyView.setVisibility(View.VISIBLE);
+        mRecView.setVisibility(View.GONE);
+        TextView msg = (TextView) mEmptyView.findViewById(R.id.message);
+        msg.setText(R.string.empty);
+        // No retry button for the empty view.
+        mEmptyView.findViewById(R.id.button_retry).setVisibility(View.GONE);
+    }
+
+    void showErrorView() {
+        mEmptyView.setVisibility(View.VISIBLE);
+        mRecView.setVisibility(View.GONE);
+        TextView msg = (TextView) mEmptyView.findViewById(R.id.message);
+        msg.setText(R.string.query_error);
+        // TODO: Enable this once the retry button does something.
+        mEmptyView.findViewById(R.id.button_retry).setVisibility(View.GONE);
+    }
+
+    void showRecyclerView() {
+        mEmptyView.setVisibility(View.GONE);
+        mRecView.setVisibility(View.VISIBLE);
+    }
+
     private final class DocumentsAdapter extends RecyclerView.Adapter<DocumentHolder> {
 
         private final Context mContext;
@@ -1950,21 +1958,16 @@
             mProgressBar.setVisibility(model.isLoading() ? View.VISIBLE : View.GONE);
 
             if (model.isEmpty()) {
-                mEmptyView.setVisibility(View.VISIBLE);
-                mRecView.setVisibility(View.GONE);
+                showEmptyView();
             } else {
-                mEmptyView.setVisibility(View.GONE);
-                mRecView.setVisibility(View.VISIBLE);
+                showRecyclerView();
+                mAdapter.notifyDataSetChanged();
             }
-
-            mAdapter.notifyDataSetChanged();
         }
 
         @Override
         public void onModelUpdateFailed(Exception e) {
-            // TODO: deal with catastrophic update failures
-            String error = getString(R.string.query_error);
-            mAdapter.notifyDataSetChanged();
+            showErrorView();
         }
     }
 }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
index 0edb241..bb82b38 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
@@ -16,12 +16,12 @@
 
 package com.android.documentsui;
 
-import static com.android.documentsui.BaseActivity.State.MODE_UNKNOWN;
-import static com.android.documentsui.BaseActivity.State.SORT_ORDER_DISPLAY_NAME;
-import static com.android.documentsui.BaseActivity.State.SORT_ORDER_LAST_MODIFIED;
-import static com.android.documentsui.BaseActivity.State.SORT_ORDER_SIZE;
-import static com.android.documentsui.BaseActivity.State.SORT_ORDER_UNKNOWN;
 import static com.android.documentsui.Shared.TAG;
+import static com.android.documentsui.State.MODE_UNKNOWN;
+import static com.android.documentsui.State.SORT_ORDER_DISPLAY_NAME;
+import static com.android.documentsui.State.SORT_ORDER_LAST_MODIFIED;
+import static com.android.documentsui.State.SORT_ORDER_SIZE;
+import static com.android.documentsui.State.SORT_ORDER_UNKNOWN;
 import static com.android.documentsui.model.DocumentInfo.getCursorInt;
 
 import android.content.AsyncTaskLoader;
@@ -37,7 +37,6 @@
 import android.provider.DocumentsContract.Document;
 import android.util.Log;
 
-import com.android.documentsui.BaseActivity.State;
 import com.android.documentsui.RecentsProvider.StateColumns;
 import com.android.documentsui.model.DocumentInfo;
 import com.android.documentsui.model.RootInfo;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryView.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryView.java
index 4893652..000b92a 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryView.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryView.java
@@ -18,9 +18,9 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
-import android.widget.FrameLayout;
+import android.widget.LinearLayout;
 
-public class DirectoryView extends FrameLayout {
+public class DirectoryView extends LinearLayout {
     private float mPosition = 0f;
 
     private int mWidth;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index dbfcf40..4658fe3 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -16,13 +16,12 @@
 
 package com.android.documentsui;
 
-import static com.android.documentsui.BaseActivity.State.ACTION_CREATE;
-import static com.android.documentsui.BaseActivity.State.ACTION_GET_CONTENT;
-import static com.android.documentsui.BaseActivity.State.ACTION_OPEN;
-import static com.android.documentsui.BaseActivity.State.ACTION_OPEN_COPY_DESTINATION;
-import static com.android.documentsui.BaseActivity.State.ACTION_OPEN_TREE;
-import static com.android.documentsui.DirectoryFragment.ANIM_DOWN;
 import static com.android.documentsui.DirectoryFragment.ANIM_NONE;
+import static com.android.documentsui.State.ACTION_CREATE;
+import static com.android.documentsui.State.ACTION_GET_CONTENT;
+import static com.android.documentsui.State.ACTION_OPEN;
+import static com.android.documentsui.State.ACTION_OPEN_COPY_DESTINATION;
+import static com.android.documentsui.State.ACTION_OPEN_TREE;
 
 import android.app.Activity;
 import android.app.Fragment;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/IconUtils.java b/packages/DocumentsUI/src/com/android/documentsui/IconUtils.java
index ec1cb1d..a1213d2 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/IconUtils.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/IconUtils.java
@@ -222,7 +222,7 @@
                 return context.getDrawable(R.drawable.ic_doc_album);
             }
 
-            if (mode == BaseActivity.State.MODE_GRID) {
+            if (mode == State.MODE_GRID) {
                 return context.getDrawable(R.drawable.ic_grid_folder);
             } else {
                 return context.getDrawable(R.drawable.ic_doc_folder);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/ManageRootActivity.java b/packages/DocumentsUI/src/com/android/documentsui/ManageRootActivity.java
index f5b1d8e..4754899 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/ManageRootActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/ManageRootActivity.java
@@ -16,9 +16,8 @@
 
 package com.android.documentsui;
 
-import static com.android.documentsui.BaseActivity.State.ACTION_MANAGE;
-import static com.android.documentsui.DirectoryFragment.ANIM_DOWN;
 import static com.android.documentsui.DirectoryFragment.ANIM_NONE;
+import static com.android.documentsui.State.ACTION_MANAGE;
 
 import android.app.Activity;
 import android.app.Fragment;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java b/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java
index 5839943..858fb42 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java
@@ -365,8 +365,8 @@
             // To make this more correct, we'd need to update the Ranger class to return
             // information about what has changed.
             notifySelectionChanged();
-        } else if (toggleSelection(input.getItemPosition())) {
-            notifySelectionChanged();
+        } else {
+            toggleSelection(input.getItemPosition());
         }
     }
 
@@ -375,14 +375,13 @@
      * a new Ranger (range selection manager) at that point is created.
      *
      * @param position
-     * @return True if state changed.
      */
-    private boolean toggleSelection(int position) {
+    private void toggleSelection(int position) {
         // Position may be special "no position" during certain
         // transitional phases. If so, skip handling of the event.
         if (position == RecyclerView.NO_POSITION) {
             if (DEBUG) Log.d(TAG, "Ignoring toggle for element with no position.");
-            return false;
+            return;
         }
 
         boolean changed = false;
@@ -391,7 +390,7 @@
         } else {
             boolean canSelect = notifyBeforeItemStateChange(position, true);
             if (!canSelect) {
-                return false;
+                return;
             }
             if (mSingleSelect && !mSelection.isEmpty()) {
                 clearSelectionQuietly();
@@ -407,7 +406,9 @@
             changed = true;
         }
 
-        return changed;
+        if (changed) {
+            notifySelectionChanged();
+        }
     }
 
     /**
diff --git a/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java b/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
index 5f6a5e9..48e28dc 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
@@ -21,7 +21,6 @@
 import android.app.FragmentManager;
 import android.app.FragmentTransaction;
 import android.os.Bundle;
-import android.text.TextUtils;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -29,8 +28,6 @@
 
 import com.android.documentsui.model.DocumentInfo;
 
-import java.util.Locale;
-
 /**
  * Display pick confirmation bar, usually for selecting a directory.
  */
@@ -93,7 +90,7 @@
     };
 
     /**
-     * @param action Which action defined in BaseActivity.State is the picker shown for.
+     * @param action Which action defined in State is the picker shown for.
      */
     public void setPickTarget(int action, int transferMode, DocumentInfo pickTarget) {
         mAction = action;
@@ -109,11 +106,11 @@
      */
     private void updateView() {
         switch (mAction) {
-            case BaseActivity.State.ACTION_OPEN_TREE:
+            case State.ACTION_OPEN_TREE:
                 mPick.setText(R.string.button_select);
                 mCancel.setVisibility(View.GONE);
                 break;
-            case BaseActivity.State.ACTION_OPEN_COPY_DESTINATION:
+            case State.ACTION_OPEN_COPY_DESTINATION:
                 mPick.setText(R.string.button_copy);
                 mCancel.setVisibility(View.VISIBLE);
                 break;
@@ -123,7 +120,7 @@
         }
 
         if (mPickTarget != null && (
-                mAction == BaseActivity.State.ACTION_OPEN_TREE ||
+                mAction == State.ACTION_OPEN_TREE ||
                 mPickTarget.isCreateSupported())) {
             mContainer.setVisibility(View.VISIBLE);
         } else {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java b/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java
index 4685c41..607cb95 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java
@@ -16,6 +16,8 @@
 
 package com.android.documentsui;
 
+import static com.android.documentsui.Shared.DEBUG;
+import static com.android.documentsui.Shared.TAG;
 import static com.android.documentsui.model.DocumentInfo.getCursorString;
 
 import android.content.ClipData;
@@ -38,9 +40,6 @@
  */
 final class QuickViewIntentBuilder {
 
-    private static final String TAG = "QvIntentBuilder";
-    private static final boolean DEBUG = false;
-
     private final DocumentInfo mDocument;
     private final DocumentContext mContext;
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java
index 1a7095a..c2b64fb 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java
@@ -16,8 +16,9 @@
 
 package com.android.documentsui;
 
-import static com.android.documentsui.BaseActivity.State.SORT_ORDER_LAST_MODIFIED;
+import static com.android.documentsui.Shared.DEBUG;
 import static com.android.documentsui.Shared.TAG;
+import static com.android.documentsui.State.SORT_ORDER_LAST_MODIFIED;
 
 import android.app.ActivityManager;
 import android.content.AsyncTaskLoader;
@@ -34,7 +35,6 @@
 import android.text.format.DateUtils;
 import android.util.Log;
 
-import com.android.documentsui.BaseActivity.State;
 import com.android.documentsui.model.RootInfo;
 
 import com.google.common.util.concurrent.AbstractFuture;
@@ -53,8 +53,6 @@
 import java.util.concurrent.TimeUnit;
 
 public class RecentLoader extends AsyncTaskLoader<DirectoryResult> {
-    private static final boolean DEBUG = false;
-
     // TODO: clean up cursor ownership so background thread doesn't traverse
     // previously returned cursors for filtering/sorting; this currently races
     // with the UI thread.
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java
index 6811331..cf682fa 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java
@@ -45,7 +45,6 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
-import com.android.documentsui.BaseActivity.State;
 import com.android.documentsui.RecentsProvider.RecentColumns;
 import com.android.documentsui.model.DocumentStack;
 import com.android.documentsui.model.DurableUtils;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java
index f6e4349..82eb732 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java
@@ -39,6 +39,7 @@
 import com.android.documentsui.model.DocumentStack;
 import com.android.documentsui.model.DurableUtils;
 import com.android.internal.util.Predicate;
+
 import com.google.android.collect.Sets;
 
 import libcore.io.IoUtils;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
index cb46bca..de35cef 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
@@ -16,8 +16,8 @@
 
 package com.android.documentsui;
 
-import static com.android.documentsui.Shared.TAG;
 import static com.android.documentsui.Shared.DEBUG;
+import static com.android.documentsui.Shared.TAG;
 
 import android.content.ContentProviderClient;
 import android.content.ContentResolver;
@@ -35,12 +35,11 @@
 import android.os.SystemClock;
 import android.provider.DocumentsContract;
 import android.provider.DocumentsContract.Root;
+import android.support.annotation.VisibleForTesting;
 import android.util.Log;
 
-import com.android.documentsui.BaseActivity.State;
 import com.android.documentsui.model.RootInfo;
 import com.android.internal.annotations.GuardedBy;
-import android.support.annotation.VisibleForTesting;
 
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.Multimap;
@@ -372,10 +371,8 @@
             if (state.directoryCopy && root.isDownloads()) continue;
 
             // Only show empty roots when creating, or in browse mode.
-            if (empty && (state.action != State.ACTION_BROWSE ||
-                 state.action != State.ACTION_CREATE ||
-                 state.action != State.ACTION_OPEN_TREE ||
-                 state.action != State.ACTION_OPEN_COPY_DESTINATION)) {
+            if (empty && (state.action == State.ACTION_OPEN
+                    || state.action == State.ACTION_GET_CONTENT)) {
                 if (DEBUG) Log.i(TAG, "Skipping empty root: " + root);
                 continue;
             }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
index c02184b..c98da47 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
@@ -41,7 +41,6 @@
 import android.widget.ListView;
 import android.widget.TextView;
 
-import com.android.documentsui.BaseActivity.State;
 import com.android.documentsui.model.DocumentInfo;
 import com.android.documentsui.model.RootInfo;
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsLoader.java b/packages/DocumentsUI/src/com/android/documentsui/RootsLoader.java
index 49651b4..c81377a 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsLoader.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsLoader.java
@@ -19,7 +19,6 @@
 import android.content.AsyncTaskLoader;
 import android.content.Context;
 
-import com.android.documentsui.BaseActivity.State;
 import com.android.documentsui.model.RootInfo;
 
 import java.util.Collection;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/SortingCursorWrapper.java b/packages/DocumentsUI/src/com/android/documentsui/SortingCursorWrapper.java
index 3ec3d1c..6698ff1 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/SortingCursorWrapper.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/SortingCursorWrapper.java
@@ -16,9 +16,9 @@
 
 package com.android.documentsui;
 
-import static com.android.documentsui.BaseActivity.State.SORT_ORDER_DISPLAY_NAME;
-import static com.android.documentsui.BaseActivity.State.SORT_ORDER_LAST_MODIFIED;
-import static com.android.documentsui.BaseActivity.State.SORT_ORDER_SIZE;
+import static com.android.documentsui.State.SORT_ORDER_DISPLAY_NAME;
+import static com.android.documentsui.State.SORT_ORDER_LAST_MODIFIED;
+import static com.android.documentsui.State.SORT_ORDER_SIZE;
 import static com.android.documentsui.model.DocumentInfo.getCursorLong;
 import static com.android.documentsui.model.DocumentInfo.getCursorString;
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/State.java b/packages/DocumentsUI/src/com/android/documentsui/State.java
new file mode 100644
index 0000000..bbffad3
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/State.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2013 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.documentsui;
+
+import android.content.Intent;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.SparseArray;
+
+import com.android.documentsui.model.DocumentInfo;
+import com.android.documentsui.model.DocumentStack;
+import com.android.documentsui.model.DurableUtils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public class State implements android.os.Parcelable {
+    public int action;
+    public String[] acceptMimes;
+
+    /** Explicit user choice */
+    public int userMode = MODE_UNKNOWN;
+    /** Derived after loader */
+    public int derivedMode = MODE_LIST;
+
+    /** Explicit user choice */
+    public int userSortOrder = SORT_ORDER_UNKNOWN;
+    /** Derived after loader */
+    public int derivedSortOrder = SORT_ORDER_DISPLAY_NAME;
+
+    public boolean allowMultiple;
+    public boolean forceSize ;
+    public boolean showSize;
+    public boolean localOnly ;
+    public boolean forceAdvanced ;
+    public boolean showAdvanced ;
+    public boolean stackTouched ;
+    public boolean restored ;
+    public boolean directoryCopy ;
+    /** Transfer mode for file copy/move operations. */
+    public int transferMode;
+
+    /** Current user navigation stack; empty implies recents. */
+    public DocumentStack stack = new DocumentStack();
+    /** Currently active search, overriding any stack. */
+    public String currentSearch;
+
+    /** Instance state for every shown directory */
+    public HashMap<String, SparseArray<Parcelable>> dirState = new HashMap<>();
+
+    /** Currently copying file */
+    public List<DocumentInfo> selectedDocumentsForCopy = new ArrayList<DocumentInfo>();
+
+    /** Name of the package that started DocsUI */
+    public List<String> excludedAuthorities = new ArrayList<>();
+
+    public static final int ACTION_OPEN = 1;
+    public static final int ACTION_CREATE = 2;
+    public static final int ACTION_GET_CONTENT = 3;
+    public static final int ACTION_OPEN_TREE = 4;
+    public static final int ACTION_MANAGE = 5;
+    public static final int ACTION_BROWSE = 6;
+    public static final int ACTION_OPEN_COPY_DESTINATION = 8;
+
+    public static final int MODE_UNKNOWN = 0;
+    public static final int MODE_LIST = 1;
+    public static final int MODE_GRID = 2;
+
+    public static final int SORT_ORDER_UNKNOWN = 0;
+    public static final int SORT_ORDER_DISPLAY_NAME = 1;
+    public static final int SORT_ORDER_LAST_MODIFIED = 2;
+    public static final int SORT_ORDER_SIZE = 3;
+
+    public void initAcceptMimes(Intent intent) {
+        if (intent.hasExtra(Intent.EXTRA_MIME_TYPES)) {
+            acceptMimes = intent.getStringArrayExtra(Intent.EXTRA_MIME_TYPES);
+        } else {
+            String glob = intent.getType();
+            acceptMimes = new String[] { glob != null ? glob : "*/*" };
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(action);
+        out.writeInt(userMode);
+        out.writeStringArray(acceptMimes);
+        out.writeInt(userSortOrder);
+        out.writeInt(allowMultiple ? 1 : 0);
+        out.writeInt(forceSize ? 1 : 0);
+        out.writeInt(showSize ? 1 : 0);
+        out.writeInt(localOnly ? 1 : 0);
+        out.writeInt(forceAdvanced ? 1 : 0);
+        out.writeInt(showAdvanced ? 1 : 0);
+        out.writeInt(stackTouched ? 1 : 0);
+        out.writeInt(restored ? 1 : 0);
+        DurableUtils.writeToParcel(out, stack);
+        out.writeString(currentSearch);
+        out.writeMap(dirState);
+        out.writeList(selectedDocumentsForCopy);
+        out.writeList(excludedAuthorities);
+    }
+
+    public static final Creator<State> CREATOR = new Creator<State>() {
+        @Override
+        public State createFromParcel(Parcel in) {
+            final State state = new State();
+            state.action = in.readInt();
+            state.userMode = in.readInt();
+            state.acceptMimes = in.readStringArray();
+            state.userSortOrder = in.readInt();
+            state.allowMultiple = in.readInt() != 0;
+            state.forceSize = in.readInt() != 0;
+            state.showSize = in.readInt() != 0;
+            state.localOnly = in.readInt() != 0;
+            state.forceAdvanced = in.readInt() != 0;
+            state.showAdvanced = in.readInt() != 0;
+            state.stackTouched = in.readInt() != 0;
+            state.restored = in.readInt() != 0;
+            DurableUtils.readFromParcel(in, state.stack);
+            state.currentSearch = in.readString();
+            in.readMap(state.dirState, null);
+            in.readList(state.selectedDocumentsForCopy, null);
+            in.readList(state.excludedAuthorities, null);
+            return state;
+        }
+
+        @Override
+        public State[] newArray(int size) {
+            return new State[size];
+        }
+    };
+}
\ No newline at end of file
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/RootsCacheTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/RootsCacheTest.java
index 1325706..7d3498e 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/RootsCacheTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/RootsCacheTest.java
@@ -19,7 +19,6 @@
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
-import com.android.documentsui.BaseActivity.State;
 import com.android.documentsui.model.RootInfo;
 
 import com.google.common.collect.Lists;
diff --git a/packages/SystemUI/res/layout/recents.xml b/packages/SystemUI/res/layout/recents.xml
index 8140dd6..064d225 100644
--- a/packages/SystemUI/res/layout/recents.xml
+++ b/packages/SystemUI/res/layout/recents.xml
@@ -39,12 +39,6 @@
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
 
-    <!-- Debug Overlay View -->
-    <ViewStub android:id="@+id/debug_overlay_stub"
-           android:layout="@layout/recents_debug_overlay"
-           android:layout_width="match_parent"
-           android:layout_height="match_parent" />
-
     <!-- Nav Bar Scrim View -->
     <ImageView
         android:id="@+id/nav_bar_scrim"
diff --git a/packages/SystemUI/res/layout/recents_debug_overlay.xml b/packages/SystemUI/res/layout/recents_debug_overlay.xml
deleted file mode 100644
index d23495e..0000000
--- a/packages/SystemUI/res/layout/recents_debug_overlay.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 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.
--->
-<com.android.systemui.recents.views.DebugOverlayView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent" 
-    android:layout_height="match_parent"
-    android:focusable="false">
-    <SeekBar
-        android:id="@+id/debug_seek_bar_1"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_gravity="top"
-        android:layout_marginTop="25dp" />
-    <SeekBar
-        android:id="@+id/debug_seek_bar_2"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_gravity="top"
-        android:layout_marginTop="50dp" />
-</com.android.systemui.recents.views.DebugOverlayView>
-
-
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index e1a8815..9781664 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -29,8 +29,6 @@
     }
 
     public static class DebugFlags {
-        // Enable this with any other debug flag to see more info
-        public static final boolean Verbose = false;
 
         public static class App {
             // Enables debug drawing for the transition thumbnail
@@ -39,10 +37,6 @@
             public static final boolean EnableTaskFiltering = false;
             // Enables dismiss-all
             public static final boolean EnableDismissAll = false;
-            // Enables debug mode
-            public static final boolean EnableDebugMode = false;
-            // Enables the search bar layout
-            public static final boolean EnableSearchLayout = true;
             // Enables the thumbnail alpha on the front-most task
             public static final boolean EnableThumbnailAlphaOnFrontmost = false;
             // This disables the bitmap and icon caches
@@ -63,7 +57,6 @@
     public static class Values {
         public static class App {
             public static int AppWidgetHostId = 1024;
-            public static String DebugModeVersion = "A";
         }
 
         public static class TaskStackView {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 4bb3e4a..c53e573 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -38,14 +38,12 @@
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.recents.misc.Console;
-import com.android.systemui.recents.misc.DebugTrigger;
 import com.android.systemui.recents.misc.ReferenceCountedTrigger;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.model.RecentsTaskLoadPlan;
 import com.android.systemui.recents.model.RecentsTaskLoader;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.recents.views.DebugOverlayView;
 import com.android.systemui.recents.views.RecentsView;
 import com.android.systemui.recents.views.SystemBarScrimViews;
 import com.android.systemui.recents.views.ViewAnimation;
@@ -57,8 +55,7 @@
  * The main Recents activity that is started from AlternateRecentsComponent.
  */
 public class RecentsActivity extends Activity implements RecentsView.RecentsViewCallbacks,
-        RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks,
-        DebugOverlayView.DebugOverlayViewCallbacks {
+        RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks {
 
     RecentsConfiguration mConfig;
     long mLastTabKeyEventTime;
@@ -67,9 +64,7 @@
     RecentsView mRecentsView;
     SystemBarScrimViews mScrimViews;
     ViewStub mEmptyViewStub;
-    ViewStub mDebugOverlayStub;
     View mEmptyView;
-    DebugOverlayView mDebugOverlay;
 
     // Resize task debug
     RecentsResizeTaskDialog mResizeTaskDebugDialog;
@@ -176,16 +171,6 @@
         }
     };
 
-    /**
-     * A custom debug trigger to listen for a debug key chord.
-     */
-    final DebugTrigger mDebugTrigger = new DebugTrigger(new Runnable() {
-        @Override
-        public void run() {
-            onDebugModeTriggered();
-        }
-    });
-
     /** Updates the set of recent tasks */
     void updateRecentsTasks() {
         // If AlternateRecentsComponent has preloaded a load plan, then use that to prevent
@@ -352,9 +337,7 @@
                 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
                 View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
         mEmptyViewStub = (ViewStub) findViewById(R.id.empty_view_stub);
-        mDebugOverlayStub = (ViewStub) findViewById(R.id.debug_overlay_stub);
         mScrimViews = new SystemBarScrimViews(this, mConfig);
-        inflateDebugOverlay();
 
         // Bind the search app widget when we first start up
         mSearchWidgetInfo = ssp.getOrBindSearchAppWidget(this, mAppWidgetHost);
@@ -366,27 +349,10 @@
         registerReceiver(mSystemBroadcastReceiver, filter);
     }
 
-    /** Inflates the debug overlay if debug mode is enabled. */
-    void inflateDebugOverlay() {
-        if (!Constants.DebugFlags.App.EnableDebugMode) return;
-
-        if (mConfig.debugModeEnabled && mDebugOverlay == null) {
-            // Inflate the overlay and seek bars
-            mDebugOverlay = (DebugOverlayView) mDebugOverlayStub.inflate();
-            mDebugOverlay.setCallbacks(this);
-            mRecentsView.setDebugOverlay(mDebugOverlay);
-        }
-    }
-
     @Override
     protected void onNewIntent(Intent intent) {
         super.onNewIntent(intent);
         setIntent(intent);
-
-        // Clear any debug rects
-        if (mDebugOverlay != null) {
-            mDebugOverlay.clear();
-        }
     }
 
     @Override
@@ -538,8 +504,6 @@
             default:
                 break;
         }
-        // Pass through the debug trigger
-        mDebugTrigger.onKeyEvent(keyCode);
         return super.onKeyDown(keyCode, event);
     }
 
@@ -557,33 +521,6 @@
         dismissRecentsToFocusedTaskOrHome(true);
     }
 
-    /** Called when debug mode is triggered */
-    public void onDebugModeTriggered() {
-        if (mConfig.developerOptionsEnabled) {
-            if (Prefs.getBoolean(this, Prefs.Key.DEBUG_MODE_ENABLED, false /* boolean */)) {
-                // Disable the debug mode
-                Prefs.remove(this, Prefs.Key.DEBUG_MODE_ENABLED);
-                mConfig.debugModeEnabled = false;
-                inflateDebugOverlay();
-                if (mDebugOverlay != null) {
-                    mDebugOverlay.disable();
-                }
-            } else {
-                // Enable the debug mode
-                Prefs.putBoolean(this, Prefs.Key.DEBUG_MODE_ENABLED, true);
-                mConfig.debugModeEnabled = true;
-                inflateDebugOverlay();
-                if (mDebugOverlay != null) {
-                    mDebugOverlay.enable();
-                }
-            }
-            Toast.makeText(this, "Debug mode (" + Constants.Values.App.DebugModeVersion + ") " +
-                (mConfig.debugModeEnabled ? "Enabled" : "Disabled") + ", please restart Recents now",
-                Toast.LENGTH_SHORT).show();
-        }
-    }
-
-
     /**** RecentsResizeTaskDialog ****/
 
     private RecentsResizeTaskDialog getResizeTaskDebugDialog() {
@@ -655,16 +592,4 @@
             mRecentsView.setSearchBar(null);
         }
     }
-
-    /**** DebugOverlayView.DebugOverlayViewCallbacks ****/
-
-    @Override
-    public void onPrimarySeekBarChanged(float progress) {
-        // Do nothing
-    }
-
-    @Override
-    public void onSecondarySeekBarChanged(float progress) {
-        // Do nothing
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/DebugTrigger.java b/packages/SystemUI/src/com/android/systemui/recents/misc/DebugTrigger.java
deleted file mode 100644
index fbf8a86..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/DebugTrigger.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2014 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.systemui.recents.misc;
-
-import android.os.Handler;
-import android.os.SystemClock;
-import android.view.KeyEvent;
-import com.android.systemui.recents.Constants;
-
-/**
- * A trigger for catching a debug chord.
- * We currently use volume up then volume down to trigger this mode.
- */
-public class DebugTrigger {
-
-    Handler mHandler;
-    Runnable mTriggeredRunnable;
-
-    int mLastKeyCode;
-    long mLastKeyCodeTime;
-
-    public DebugTrigger(Runnable triggeredRunnable) {
-        mHandler = new Handler();
-        mTriggeredRunnable = triggeredRunnable;
-    }
-
-    /** Resets the debug trigger */
-    void reset() {
-        mLastKeyCode = 0;
-        mLastKeyCodeTime = 0;
-    }
-
-    /**
-     * Processes a key event and tests if it is a part of the trigger. If the chord is complete,
-     * then we just call the callback.
-     */
-    public void onKeyEvent(int keyCode) {
-        if (!Constants.DebugFlags.App.EnableDebugMode) return;
-
-        if (mLastKeyCode == 0) {
-            if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
-                mLastKeyCode = keyCode;
-                mLastKeyCodeTime = SystemClock.uptimeMillis();
-                return;
-            }
-        } else {
-            if (mLastKeyCode == KeyEvent.KEYCODE_VOLUME_UP &&
-                    keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
-                if ((SystemClock.uptimeMillis() - mLastKeyCodeTime) < 750) {
-                    mTriggeredRunnable.run();
-                }
-            }
-        }
-        reset();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index a760a41..515e578 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -17,7 +17,6 @@
 package com.android.systemui.recents.model;
 
 import android.graphics.Color;
-import android.graphics.Rect;
 import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.misc.NamedCounter;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/DebugOverlayView.java b/packages/SystemUI/src/com/android/systemui/recents/views/DebugOverlayView.java
deleted file mode 100644
index 452830d..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/views/DebugOverlayView.java
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright (C) 2014 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.systemui.recents.views;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.util.Pair;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.SeekBar;
-import com.android.systemui.R;
-import com.android.systemui.recents.RecentsConfiguration;
-
-import java.util.ArrayList;
-
-/**
- * A full screen overlay layer that allows us to draw views from throughout the system on the top
- * most layer.
- */
-public class DebugOverlayView extends FrameLayout implements SeekBar.OnSeekBarChangeListener {
-
-    public interface DebugOverlayViewCallbacks {
-        public void onPrimarySeekBarChanged(float progress);
-        public void onSecondarySeekBarChanged(float progress);
-    }
-
-    final static int sCornerRectSize = 50;
-
-    RecentsConfiguration mConfig;
-    DebugOverlayViewCallbacks mCb;
-
-    ArrayList<Pair<Rect, Integer>> mRects = new ArrayList<Pair<Rect, Integer>>();
-    String mText;
-    Paint mDebugOutline = new Paint();
-    Paint mTmpPaint = new Paint();
-    Rect mTmpRect = new Rect();
-    boolean mEnabled = true;
-
-    SeekBar mPrimarySeekBar;
-    SeekBar mSecondarySeekBar;
-
-    public DebugOverlayView(Context context) {
-        this(context, null);
-    }
-
-    public DebugOverlayView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public DebugOverlayView(Context context, AttributeSet attrs, int defStyleAttr) {
-        this(context, attrs, defStyleAttr, 0);
-    }
-
-    public DebugOverlayView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-        mConfig = RecentsConfiguration.getInstance();
-        mDebugOutline.setColor(0xFFff0000);
-        mDebugOutline.setStyle(Paint.Style.STROKE);
-        mDebugOutline.setStrokeWidth(8f);
-        setWillNotDraw(false);
-    }
-
-    public void setCallbacks(DebugOverlayViewCallbacks cb) {
-        mCb = cb;
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        mPrimarySeekBar = (SeekBar) findViewById(R.id.debug_seek_bar_1);
-        mPrimarySeekBar.setOnSeekBarChangeListener(this);
-        mSecondarySeekBar = (SeekBar) findViewById(R.id.debug_seek_bar_2);
-        mSecondarySeekBar.setOnSeekBarChangeListener(this);
-    }
-
-    /** Enables the debug overlay drawing. */
-    public void enable() {
-        mEnabled = true;
-        setVisibility(View.VISIBLE);
-    }
-
-    /** Disables the debug overlay drawing. */
-    public void disable() {
-        mEnabled = false;
-        setVisibility(View.GONE);
-    }
-
-    /** Clears all debug rects. */
-    public void clear() {
-        mRects.clear();
-    }
-
-    /** Adds a rect to be drawn. */
-    void addRect(Rect r, int color) {
-        mRects.add(new Pair<Rect, Integer>(r, color));
-        invalidate();
-    }
-
-    /** Adds a view's global rect to be drawn. */
-    void addViewRect(View v, int color) {
-        Rect vr = new Rect();
-        v.getGlobalVisibleRect(vr);
-        mRects.add(new Pair<Rect, Integer>(vr, color));
-        invalidate();
-    }
-
-    /** Adds a rect, relative to a given view to be drawn. */
-    void addRectRelativeToView(View v, Rect r, int color) {
-        Rect vr = new Rect();
-        v.getGlobalVisibleRect(vr);
-        r.offsetTo(vr.left, vr.top);
-        mRects.add(new Pair<Rect, Integer>(r, color));
-        invalidate();
-    }
-
-    /** Sets the debug text at the bottom of the screen. */
-    void setText(String message) {
-        mText = message;
-        invalidate();
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-        addRect(new Rect(0, 0, sCornerRectSize, sCornerRectSize), 0xFFff0000);
-        addRect(new Rect(getMeasuredWidth() - sCornerRectSize, getMeasuredHeight() - sCornerRectSize,
-                getMeasuredWidth(), getMeasuredHeight()), 0xFFff0000);
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas) {
-        if (mEnabled) {
-            // Draw the outline
-            canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mDebugOutline);
-
-            // Draw the rects
-            int numRects = mRects.size();
-            for (int i = 0; i < numRects; i++) {
-                Pair<Rect, Integer> r = mRects.get(i);
-                mTmpPaint.setColor(r.second);
-                canvas.drawRect(r.first, mTmpPaint);
-            }
-
-            // Draw the text
-            if (mText != null && mText.length() > 0) {
-                mTmpPaint.setColor(0xFFff0000);
-                mTmpPaint.setTextSize(60);
-                mTmpPaint.getTextBounds(mText, 0, 1, mTmpRect);
-                canvas.drawText(mText, 10f, getMeasuredHeight() - mTmpRect.height() - mConfig.systemInsets.bottom, mTmpPaint);
-            }
-        }
-    }
-
-    /**** SeekBar.OnSeekBarChangeListener Implementation ****/
-
-    @Override
-    public void onStopTrackingTouch(SeekBar seekBar) {
-        // Do nothing
-    }
-
-    @Override
-    public void onStartTrackingTouch(SeekBar seekBar) {
-        // Do nothing
-    }
-
-    @Override
-    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
-        if (seekBar == mPrimarySeekBar) {
-            mCb.onPrimarySeekBarChanged((float) progress / mPrimarySeekBar.getMax());
-        } else if (seekBar == mSecondarySeekBar) {
-            mCb.onSecondarySeekBarChanged((float) progress / mSecondarySeekBar.getMax());
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FakeShadowDrawable.java b/packages/SystemUI/src/com/android/systemui/recents/views/FakeShadowDrawable.java
index 509ad1b..41adbed 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/FakeShadowDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/FakeShadowDrawable.java
@@ -28,7 +28,6 @@
 import android.graphics.Shader;
 import android.graphics.drawable.Drawable;
 import android.util.Log;
-
 import com.android.systemui.R;
 import com.android.systemui.recents.RecentsConfiguration;
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index c7d1dd1..fab8b3a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -78,7 +78,6 @@
 
     RecentsConfiguration mConfig;
     LayoutInflater mInflater;
-    DebugOverlayView mDebugOverlay;
 
     ArrayList<TaskStack> mStacks;
     TaskStackView mTaskStackView;
@@ -108,11 +107,6 @@
         mCb = cb;
     }
 
-    /** Sets the debug overlay */
-    public void setDebugOverlay(DebugOverlayView overlay) {
-        mDebugOverlay = overlay;
-    }
-
     /** Set/get the bsp root node */
     public void setTaskStack(TaskStack stack) {
         if (mConfig.launchedReuseTaskStackViews) {
@@ -134,11 +128,6 @@
             addView(mTaskStackView);
         }
 
-        // Enable debug mode drawing on all the stacks if necessary
-        if (mConfig.debugModeEnabled) {
-            mTaskStackView.setDebugOverlay(mDebugOverlay);
-        }
-
         // Trigger a new layout
         requestLayout();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 8058c5e..4db8b37 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -79,7 +79,6 @@
     ViewPool<TaskView, Task> mViewPool;
     ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<TaskViewTransform>();
     DozeTrigger mUIDozeTrigger;
-    DebugOverlayView mDebugOverlay;
     DismissView mDismissAllButton;
     boolean mDismissAllButtonAnimating;
     int mFocusedTaskIndex = -1;
@@ -161,11 +160,6 @@
         return mStack;
     }
 
-    /** Sets the debug overlay */
-    public void setDebugOverlay(DebugOverlayView overlay) {
-        mDebugOverlay = overlay;
-    }
-
     /** Updates the list of task views */
     void updateTaskViewsList() {
         mTaskViews.clear();
@@ -334,9 +328,6 @@
             int[] visibleRange = mTmpVisibleRange;
             boolean isValidVisibleRange = updateStackTransforms(mCurrentTaskTransforms, tasks,
                     stackScroll, visibleRange, false);
-            if (mDebugOverlay != null) {
-                mDebugOverlay.setText("vis[" + visibleRange[1] + "-" + visibleRange[0] + "]");
-            }
 
             // Inflate and add the dismiss button if necessary
             if (Constants.DebugFlags.App.EnableDismissAll && mDismissAllButton == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index b01a2a8..10d4a96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -112,7 +112,7 @@
         public void appTransitionStarting(long startTime, long duration);
         public void showAssistDisclosure();
         public void startAssist(Bundle args);
-        public void onCameraLaunchGestureDetected();
+        public void onCameraLaunchGestureDetected(int source);
     }
 
     public CommandQueue(Callbacks callbacks, StatusBarIconList list) {
@@ -306,10 +306,10 @@
     }
 
     @Override
-    public void onCameraLaunchGestureDetected() {
+    public void onCameraLaunchGestureDetected(int source) {
         synchronized (mList) {
             mHandler.removeMessages(MSG_CAMERA_LAUNCH_GESTURE);
-            mHandler.obtainMessage(MSG_CAMERA_LAUNCH_GESTURE).sendToTarget();
+            mHandler.obtainMessage(MSG_CAMERA_LAUNCH_GESTURE, source, 0).sendToTarget();
         }
     }
 
@@ -415,7 +415,7 @@
                     mCallbacks.startAssist((Bundle) msg.obj);
                     break;
                 case MSG_CAMERA_LAUNCH_GESTURE:
-                    mCallbacks.onCameraLaunchGestureDetected();
+                    mCallbacks.onCameraLaunchGestureDetected(msg.arg1);
                     break;
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 012dc9c..14176a6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -77,6 +77,13 @@
 
     final static String TAG = "PhoneStatusBar/KeyguardBottomAreaView";
 
+    public static final String CAMERA_LAUNCH_SOURCE_AFFORDANCE = "lockscreen_affordance";
+    public static final String CAMERA_LAUNCH_SOURCE_WIGGLE = "wiggle_gesture";
+    public static final String CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP = "power_double_tap";
+
+    public static final String EXTRA_CAMERA_LAUNCH_SOURCE
+            = "com.android.systemui.camera_launch_source";
+
     private static final Intent SECURE_CAMERA_INTENT =
             new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE)
                     .addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
@@ -170,7 +177,7 @@
                             CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
                     return true;
                 } else if (host == mCameraImageView) {
-                    launchCamera();
+                    launchCamera(CAMERA_LAUNCH_SOURCE_AFFORDANCE);
                     return true;
                 } else if (host == mLeftAffordanceView) {
                     launchLeftAffordance();
@@ -349,7 +356,7 @@
     @Override
     public void onClick(View v) {
         if (v == mCameraImageView) {
-            launchCamera();
+            launchCamera(CAMERA_LAUNCH_SOURCE_AFFORDANCE);
         } else if (v == mLeftAffordanceView) {
             launchLeftAffordance();
         } if (v == mLockIcon) {
@@ -417,8 +424,9 @@
         }
     }
 
-    public void launchCamera() {
+    public void launchCamera(String source) {
         final Intent intent = getCameraIntent();
+        intent.putExtra(EXTRA_CAMERA_LAUNCH_SOURCE, source);
         boolean wouldLaunchResolverActivity = PreviewInflater.wouldLaunchResolverActivity(
                 mContext, intent, KeyguardUpdateMonitor.getCurrentUser());
         if (intent == SECURE_CAMERA_INTENT && !wouldLaunchResolverActivity) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 12d373d..08353cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -22,6 +22,7 @@
 import android.animation.PropertyValuesHolder;
 import android.animation.ValueAnimator;
 import android.app.ActivityManager;
+import android.app.StatusBarManager;
 import android.content.Context;
 import android.content.pm.ResolveInfo;
 import android.content.res.Configuration;
@@ -204,6 +205,7 @@
     private boolean mHeadsUpAnimatingAway;
     private boolean mLaunchingAffordance;
     private FalsingManager mFalsingManager;
+    private String mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
 
     private Runnable mHeadsUpExistenceChangedRunnable = new Runnable() {
         @Override
@@ -480,6 +482,7 @@
         mUnlockIconActive = false;
         if (!mLaunchingAffordance) {
             mAfforanceHelper.reset(false);
+            mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
         }
         closeQs();
         mStatusBar.dismissPopups();
@@ -1971,20 +1974,23 @@
                 mKeyguardBottomArea.launchLeftAffordance();
             }
         } else {
-            EventLogTags.writeSysuiLockscreenGesture(
-                    EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_CAMERA, lengthDp, velocityDp);
-
+            if (KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE.equals(
+                    mLastCameraLaunchSource)) {
+                EventLogTags.writeSysuiLockscreenGesture(
+                        EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_CAMERA,
+                        lengthDp, velocityDp);
+            }
             mFalsingManager.onCameraOn();
             if (mFalsingManager.shouldEnforceBouncer()) {
                 mStatusBar.executeRunnableDismissingKeyguard(new Runnable() {
                     @Override
                     public void run() {
-                        mKeyguardBottomArea.launchCamera();
+                        mKeyguardBottomArea.launchCamera(mLastCameraLaunchSource);
                     }
                 }, null, true /* dismissShade */, false /* afterKeyguardGone */);
             }
             else {
-                mKeyguardBottomArea.launchCamera();
+                mKeyguardBottomArea.launchCamera(mLastCameraLaunchSource);
             }
         }
         mStatusBar.startLaunchTransitionTimeout();
@@ -2425,7 +2431,17 @@
         return !mDozing;
     }
 
-    public void launchCamera(boolean animate) {
+    public void launchCamera(boolean animate, int source) {
+        if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP) {
+            mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP;
+        } else if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE) {
+            mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_WIGGLE;
+        } else {
+
+            // Default.
+            mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
+        }
+
         // If we are launching it when we are occluded already we don't want it to animate,
         // nor setting these flags, since the occluded state doesn't change anymore, hence it's
         // never reset.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 151fa3c..98f5444 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -492,6 +492,7 @@
     private ExpandableNotificationRow mDraggedDownRow;
     private boolean mLaunchCameraOnScreenTurningOn;
     private boolean mLaunchCameraOnFinishedGoingToSleep;
+    private int mLastCameraLaunchSource;
     private PowerManager.WakeLock mGestureWakeLock;
     private Vibrator mVibrator;
 
@@ -3992,7 +3993,7 @@
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
-                    onCameraLaunchGestureDetected();
+                    onCameraLaunchGestureDetected(mLastCameraLaunchSource);
                 }
             });
         }
@@ -4010,7 +4011,7 @@
         mFalsingManager.onScreenTurningOn();
         mNotificationPanel.onScreenTurningOn();
         if (mLaunchCameraOnScreenTurningOn) {
-            mNotificationPanel.launchCamera(false);
+            mNotificationPanel.launchCamera(false, mLastCameraLaunchSource);
             mLaunchCameraOnScreenTurningOn = false;
         }
     }
@@ -4175,7 +4176,8 @@
     }
 
     @Override
-    public void onCameraLaunchGestureDetected() {
+    public void onCameraLaunchGestureDetected(int source) {
+        mLastCameraLaunchSource = source;
         if (mStartedGoingToSleep) {
             mLaunchCameraOnFinishedGoingToSleep = true;
             return;
@@ -4201,7 +4203,7 @@
                 mGestureWakeLock.acquire(LAUNCH_TRANSITION_TIMEOUT_MS + 1000L);
             }
             if (mScreenTurningOn || mStatusBarKeyguardViewManager.isScreenTurnedOn()) {
-                mNotificationPanel.launchCamera(mDeviceInteractive /* animate */);
+                mNotificationPanel.launchCamera(mDeviceInteractive /* animate */, source);
             } else {
                 // We need to defer the camera launch until the screen comes on, since otherwise
                 // we will dismiss us too early since we are waiting on an activity to be drawn and
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
index 2587b9f..bbe5dd9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -171,7 +171,7 @@
     }
 
     @Override
-    public void onCameraLaunchGestureDetected() {
+    public void onCameraLaunchGestureDetected(int source) {
     }
 
     @Override
diff --git a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
index 85730cd..c3f5c43 100644
--- a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
@@ -29,6 +29,7 @@
 import android.os.Handler;
 import android.os.SystemClock;
 import android.util.Slog;
+import android.view.GestureDetector;
 import android.view.InputDevice;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -114,9 +115,6 @@
     // Timeout within which we try to detect a tap.
     private final int mTapTimeout;
 
-    // Timeout within which we try to detect a double tap.
-    private final int mDoubleTapTimeout;
-
     // Slop between the down and up tap to be a tap.
     private final int mTouchSlop;
 
@@ -230,7 +228,6 @@
         mInjectedPointerTracker = new InjectedPointerTracker();
         mTapTimeout = ViewConfiguration.getTapTimeout();
         mDetermineUserIntentTimeout = ViewConfiguration.getDoubleTapTimeout();
-        mDoubleTapTimeout = ViewConfiguration.getDoubleTapTimeout();
         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
         mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
         mHandler = new Handler(context.getMainLooper());
@@ -248,7 +245,7 @@
         mSendTouchInteractionEndDelayed = new SendAccessibilityEventDelayed(
                 AccessibilityEvent.TYPE_TOUCH_INTERACTION_END,
                 mDetermineUserIntentTimeout);
-        mDoubleTapDetector = new DoubleTapDetector();
+        mDoubleTapDetector = new DoubleTapDetector(mContext);
         final float density = context.getResources().getDisplayMetrics().density;
         mScaledMinPointerDistanceToUseMiddleLocation =
             (int) (MIN_POINTER_DISTANCE_TO_USE_MIDDLE_LOCATION_DIP * density);
@@ -1109,66 +1106,63 @@
         }
     }
 
-    private class DoubleTapDetector {
-        private MotionEvent mDownEvent;
-        private MotionEvent mFirstTapEvent;
+    private class DoubleTapDetector extends GestureDetector.SimpleOnGestureListener {
+        private final GestureDetector mGestureDetector;
+        private boolean mFirstTapDetected;
+        private boolean mDoubleTapDetected;
 
-        public void onMotionEvent(MotionEvent event, int policyFlags) {
-            final int actionIndex = event.getActionIndex();
-            final int action = event.getActionMasked();
-            switch (action) {
-                case MotionEvent.ACTION_DOWN:
-                case MotionEvent.ACTION_POINTER_DOWN: {
-                    if (mFirstTapEvent != null
-                            && !GestureUtils.isSamePointerContext(mFirstTapEvent, event)) {
-                        clear();
-                    }
-                    mDownEvent = MotionEvent.obtain(event);
-                } break;
-                case MotionEvent.ACTION_UP:
-                case MotionEvent.ACTION_POINTER_UP: {
-                    if (mDownEvent == null) {
-                        return;
-                    }
-                    if (!GestureUtils.isSamePointerContext(mDownEvent, event)) {
-                        clear();
-                        return;
-                    }
-                    if (GestureUtils.isTap(mDownEvent, event, mTapTimeout, mTouchSlop,
-                            actionIndex)) {
-                        if (mFirstTapEvent == null || GestureUtils.isTimedOut(mFirstTapEvent,
-                                event, mDoubleTapTimeout)) {
-                            mFirstTapEvent = MotionEvent.obtain(event);
-                            mDownEvent.recycle();
-                            mDownEvent = null;
-                            return;
-                        }
-                        if (GestureUtils.isMultiTap(mFirstTapEvent, event, mDoubleTapTimeout,
-                                mDoubleTapSlop, actionIndex)) {
-                            onDoubleTap(event, policyFlags);
-                            mFirstTapEvent.recycle();
-                            mFirstTapEvent = null;
-                            mDownEvent.recycle();
-                            mDownEvent = null;
-                            return;
-                        }
-                        mFirstTapEvent.recycle();
-                        mFirstTapEvent = null;
-                    } else {
-                        if (mFirstTapEvent != null) {
-                            mFirstTapEvent.recycle();
-                            mFirstTapEvent = null;
-                        }
-                    }
-                    mDownEvent.recycle();
-                    mDownEvent = null;
-                } break;
-            }
+        DoubleTapDetector(Context context) {
+            mGestureDetector = new GestureDetector(context, this);
+            mGestureDetector.setOnDoubleTapListener(this);
         }
 
-        public void onDoubleTap(MotionEvent secondTapUp, int policyFlags) {
+        public void onMotionEvent(MotionEvent event, int policyFlags) {
+            switch (event.getActionMasked()) {
+                case MotionEvent.ACTION_DOWN:
+                    mDoubleTapDetected = false;
+                    break;
+
+                case MotionEvent.ACTION_UP:
+                    maybeFinishDoubleTap(event, policyFlags);
+                    break;
+            }
+            mGestureDetector.onTouchEvent(event);
+        }
+
+        @Override
+        public boolean onDown(MotionEvent event) {
+            return true;
+        }
+
+        @Override
+        public boolean onSingleTapUp(MotionEvent event) {
+            mFirstTapDetected = true;
+            return false;
+        }
+
+        @Override
+        public boolean onSingleTapConfirmed(MotionEvent event) {
+            clear();
+            return false;
+        }
+
+        @Override
+        public boolean onDoubleTap(MotionEvent event) {
+            // The processing of the double tap is deferred until the finger is
+            // lifted, so that we can detect a long press on the second tap.
+            mDoubleTapDetected = true;
+            return true;
+        }
+
+        private void maybeFinishDoubleTap(MotionEvent event, int policyFlags) {
+            if (!mDoubleTapDetected) {
+                return;
+            }
+
+            clear();
+
             // This should never be called when more than two pointers are down.
-            if (secondTapUp.getPointerCount() > 2) {
+            if (event.getPointerCount() > 2) {
                 return;
             }
 
@@ -1184,8 +1178,8 @@
                 mSendTouchInteractionEndDelayed.forceSendAndRemove();
             }
 
-            final int pointerId = secondTapUp.getPointerId(secondTapUp.getActionIndex());
-            final int pointerIndex = secondTapUp.findPointerIndex(pointerId);
+            final int pointerId = event.getPointerId(event.getActionIndex());
+            final int pointerIndex = event.findPointerIndex(pointerId);
 
             Point clickLocation = mTempPoint;
             final int result = computeClickLocation(clickLocation);
@@ -1196,34 +1190,28 @@
             // Do the click.
             PointerProperties[] properties = new PointerProperties[1];
             properties[0] = new PointerProperties();
-            secondTapUp.getPointerProperties(pointerIndex, properties[0]);
+            event.getPointerProperties(pointerIndex, properties[0]);
             PointerCoords[] coords = new PointerCoords[1];
             coords[0] = new PointerCoords();
             coords[0].x = clickLocation.x;
             coords[0].y = clickLocation.y;
-            MotionEvent event = MotionEvent.obtain(secondTapUp.getDownTime(),
-                    secondTapUp.getEventTime(), MotionEvent.ACTION_DOWN, 1, properties,
-                    coords, 0, 0, 1.0f, 1.0f, secondTapUp.getDeviceId(), 0,
-                    secondTapUp.getSource(), secondTapUp.getFlags());
+            MotionEvent click_event = MotionEvent.obtain(event.getDownTime(),
+                    event.getEventTime(), MotionEvent.ACTION_DOWN, 1, properties,
+                    coords, 0, 0, 1.0f, 1.0f, event.getDeviceId(), 0,
+                    event.getSource(), event.getFlags());
             final boolean targetAccessibilityFocus = (result == CLICK_LOCATION_ACCESSIBILITY_FOCUS);
-            sendActionDownAndUp(event, policyFlags, targetAccessibilityFocus);
-            event.recycle();
+            sendActionDownAndUp(click_event, policyFlags, targetAccessibilityFocus);
+            click_event.recycle();
+            return;
         }
 
         public void clear() {
-            if (mDownEvent != null) {
-                mDownEvent.recycle();
-                mDownEvent = null;
-            }
-            if (mFirstTapEvent != null) {
-                mFirstTapEvent.recycle();
-                mFirstTapEvent = null;
-            }
+            mFirstTapDetected = false;
+            mDoubleTapDetected = false;
         }
 
         public boolean firstTapDetected() {
-            return mFirstTapEvent != null
-                && SystemClock.uptimeMillis() - mFirstTapEvent.getEventTime() < mDoubleTapTimeout;
+            return mFirstTapDetected;
         }
     }
 
diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java
index 7c85001..f245985 100644
--- a/services/core/java/com/android/server/GestureLauncherService.java
+++ b/services/core/java/com/android/server/GestureLauncherService.java
@@ -17,6 +17,7 @@
 package com.android.server;
 
 import android.app.ActivityManager;
+import android.app.StatusBarManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -263,7 +264,8 @@
         }
         if (launched) {
             Slog.i(TAG, "Power button double tap gesture detected, launching camera.");
-            launched = handleCameraLaunchGesture(false /* useWakelock */);
+            launched = handleCameraLaunchGesture(false /* useWakelock */,
+                    StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP);
             if (launched) {
                 MetricsLogger.action(mContext, MetricsLogger.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE,
                         (int) doubleTapInterval);
@@ -276,7 +278,7 @@
     /**
      * @return true if camera was launched, false otherwise.
      */
-    private boolean handleCameraLaunchGesture(boolean useWakelock) {
+    private boolean handleCameraLaunchGesture(boolean useWakelock, int source) {
         boolean userSetupComplete = Settings.Secure.getInt(mContext.getContentResolver(),
                 Settings.Secure.USER_SETUP_COMPLETE, 0) != 0;
         if (!userSetupComplete) {
@@ -295,7 +297,7 @@
         }
         StatusBarManagerInternal service = LocalServices.getService(
                 StatusBarManagerInternal.class);
-        service.onCameraLaunchGestureDetected();
+        service.onCameraLaunchGestureDetected(source);
         return true;
     }
 
@@ -334,7 +336,8 @@
                     Slog.d(TAG, String.format("Received a camera launch event: " +
                             "values=[%.4f, %.4f, %.4f].", values[0], values[1], values[2]));
                 }
-                if (handleCameraLaunchGesture(true /* useWakelock */)) {
+                if (handleCameraLaunchGesture(true /* useWakelock */,
+                        StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE)) {
                     MetricsLogger.action(mContext, MetricsLogger.ACTION_WIGGLE_CAMERA_GESTURE);
                     trackCameraLaunchEvent(event);
                 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 07a7af4..bd10c63 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -994,6 +994,8 @@
      */
     int mConfigurationSeq = 0;
 
+    boolean mSuppressResizeConfigChanges = false;
+
     /**
      * Hardware-reported OpenGLES version.
      */
@@ -17515,6 +17517,15 @@
     }
 
     @Override
+    public void suppressResizeConfigChanges(boolean suppress) throws RemoteException {
+        enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
+                "suppressResizeConfigChanges()");
+        synchronized (this) {
+            mSuppressResizeConfigChanges = suppress;
+        }
+    }
+
+    @Override
     public void updatePersistentConfiguration(Configuration values) {
         enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,
                 "updateConfiguration()");
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 9809c2e..5d106dc 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -4181,8 +4181,12 @@
                 | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE | ActivityInfo.CONFIG_ORIENTATION)) == 0;
     }
 
-    private boolean relaunchActivityLocked(ActivityRecord r, int changes, boolean andResume,
-            boolean preserveWindow) {
+    private void relaunchActivityLocked(
+            ActivityRecord r, int changes, boolean andResume, boolean preserveWindow) {
+        if (mService.mSuppressResizeConfigChanges && preserveWindow) {
+            return;
+        }
+
         List<ResultInfo> results = null;
         List<ReferrerIntent> newIntents = null;
         if (andResume) {
@@ -4222,8 +4226,6 @@
             mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);
             r.state = ActivityState.PAUSED;
         }
-
-        return true;
     }
 
     boolean willActivityBeVisibleLocked(IBinder token) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 99ca050..e49a7e4 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -2524,11 +2524,14 @@
     }
 
     /** @see AudioManager#setBluetoothScoOn(boolean) */
-    public void setBluetoothScoOn(boolean on){
+    public void setBluetoothScoOn(boolean on) {
         if (!checkAudioSettingsPermission("setBluetoothScoOn()")) {
             return;
         }
+        setBluetoothScoOnInt(on);
+    }
 
+    public void setBluetoothScoOnInt(boolean on) {
         if (on) {
             mForcedUseForComm = AudioSystem.FORCE_BT_SCO;
         } else if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
@@ -2889,6 +2892,8 @@
             mScoAudioState = SCO_STATE_INACTIVE;
             broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
         }
+        AudioSystem.setParameters("A2dpSuspended=false");
+        setBluetoothScoOnInt(false);
     }
 
     private void broadcastScoConnectionState(int state) {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 533f425..452378f 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -837,7 +837,6 @@
         if (mPendingScreenOff && target != Display.STATE_OFF) {
             setScreenState(Display.STATE_OFF);
             mPendingScreenOff = false;
-            mPowerState.dismissColorFade();
         }
 
         if (target == Display.STATE_ON) {
@@ -911,7 +910,6 @@
                 // A black surface is already hiding the contents of the screen.
                 setScreenState(Display.STATE_OFF);
                 mPendingScreenOff = false;
-                mPowerState.dismissColorFade();
             } else if (performScreenOffTransition
                     && mPowerState.prepareColorFade(mContext,
                             mColorFadeFadesConfig ?
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index be37f52..088d96e 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -544,7 +544,7 @@
                     physIndex = findDisplayInfoIndexLocked(colorTransformId, modeId);
                 }
             }
-            if (physIndex > 0 && mActivePhysIndex == physIndex) {
+            if (mActivePhysIndex == physIndex) {
                 return;
             }
             SurfaceControl.setActiveConfig(getDisplayTokenLocked(), physIndex);
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 5d01931..25d646d 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -28,5 +28,5 @@
     void showScreenPinningRequest();
     void showAssistDisclosure();
     void startAssist(Bundle args);
-    void onCameraLaunchGestureDetected();
+    void onCameraLaunchGestureDetected(int source);
 }
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 11a1639..19b03d5 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -178,10 +178,10 @@
         }
 
         @Override
-        public void onCameraLaunchGestureDetected() {
+        public void onCameraLaunchGestureDetected(int source) {
             if (mBar != null) {
                 try {
-                    mBar.onCameraLaunchGestureDetected();
+                    mBar.onCameraLaunchGestureDetected(source);
                 } catch (RemoteException e) {
                 }
             }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 4392ab4..ede377d 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -22,6 +22,7 @@
 import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY;
 import static com.android.server.wm.WindowManagerService.TAG;
 import static com.android.server.wm.WindowState.RESIZE_HANDLE_WIDTH_IN_DP;
+import static com.android.server.wm.WindowState.BOUNDS_FOR_TOUCH;
 
 import android.graphics.Rect;
 import android.graphics.Region;
@@ -255,10 +256,10 @@
     }
 
     /**
-     * Find the id of the task whose outside touch area (for resizing) (x, y)
-     * falls within. Returns -1 if the touch doesn't fall into a resizing area.
+     * Find the window whose outside touch area (for resizing) (x, y) falls within.
+     * Returns null if the touch doesn't fall into a resizing area.
      */
-    int taskIdForControlPoint(int x, int y) {
+    WindowState findWindowForControlPoint(int x, int y) {
         final int delta = mService.dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics);
         for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
             TaskStack stack = mStacks.get(stackNdx);
@@ -269,22 +270,31 @@
             for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
                 final Task task = tasks.get(taskNdx);
                 if (task.isFullscreen()) {
-                    return -1;
+                    return null;
                 }
-                task.getBounds(mTmpRect);
-                mTmpRect.inset(-delta, -delta);
-                if (mTmpRect.contains(x, y)) {
-                    mTmpRect.inset(delta, delta);
-                    if (!mTmpRect.contains(x, y)) {
-                        return task.mTaskId;
+
+                // We need to use the visible frame on the window for any touch-related
+                // tests. Can't use the task's bounds because the original task bounds
+                // might be adjusted to fit the content frame. (One example is when the
+                // task is put to top-left quadrant, the actual visible frame would not
+                // start at (0,0) after it's adjusted for the status bar.)
+                WindowState win = task.getTopAppMainWindow();
+                if (win != null) {
+                    win.getVisibleBounds(mTmpRect, !BOUNDS_FOR_TOUCH);
+                    mTmpRect.inset(-delta, -delta);
+                    if (mTmpRect.contains(x, y)) {
+                        mTmpRect.inset(delta, delta);
+                        if (!mTmpRect.contains(x, y)) {
+                            return win;
+                        }
+                        // User touched inside the task. No need to look further,
+                        // focus transfer will be handled in ACTION_UP.
+                        return null;
                     }
-                    // User touched inside the task. No need to look further,
-                    // focus transfer will be handled in ACTION_UP.
-                    return -1;
                 }
             }
         }
-        return -1;
+        return null;
     }
 
     void setTouchExcludeRegion(Task focusedTask) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 6ebff42..d1111f7 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -437,6 +437,11 @@
         return mStack != null && mStack.mStackId == DOCKED_STACK_ID;
     }
 
+    WindowState getTopAppMainWindow() {
+        final int tokensCount = mAppTokens.size();
+        return tokensCount > 0 ? mAppTokens.get(tokensCount - 1).findMainWindow() : null;
+    }
+
     @Override
     public boolean isFullscreen() {
         return mFullscreen;
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 9557d121..d1904d8 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -35,12 +35,13 @@
 import android.os.RemoteException;
 import android.util.DisplayMetrics;
 import android.util.Slog;
+import android.view.Choreographer;
 import android.view.Display;
 import android.view.DisplayInfo;
 import android.view.InputChannel;
 import android.view.InputDevice;
 import android.view.InputEvent;
-import android.view.InputEventReceiver;
+import android.view.BatchedInputEventReceiver;
 import android.view.MotionEvent;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
@@ -103,9 +104,10 @@
     InputApplicationHandle mDragApplicationHandle;
     InputWindowHandle mDragWindowHandle;
 
-    private final class WindowPositionerEventReceiver extends InputEventReceiver {
-        public WindowPositionerEventReceiver(InputChannel inputChannel, Looper looper) {
-            super(inputChannel, looper);
+    private final class WindowPositionerEventReceiver extends BatchedInputEventReceiver {
+        public WindowPositionerEventReceiver(
+                InputChannel inputChannel, Looper looper, Choreographer choreographer) {
+            super(inputChannel, looper, choreographer);
         }
 
         @Override
@@ -222,8 +224,8 @@
         mClientChannel = channels[1];
         mService.mInputManager.registerInputChannel(mServerChannel, null);
 
-        mInputEventReceiver = new WindowPositionerEventReceiver(mClientChannel,
-                mService.mH.getLooper());
+        mInputEventReceiver = new WindowPositionerEventReceiver(
+                mClientChannel, mService.mH.getLooper(), mService.mChoreographer);
 
         mDragApplicationHandle = new InputApplicationHandle(null);
         mDragApplicationHandle.name = TAG;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index d510c4a..22f9f50 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -6885,37 +6885,29 @@
     }
 
     boolean startMovingTask(IWindow window, float startX, float startY) {
-        WindowState callingWin = null;
+        WindowState win = null;
         synchronized (mWindowMap) {
-            callingWin = windowForClientLocked(null, window, false);
-            if (!startPositioningLocked(callingWin, false /*resize*/, startX, startY)) {
+            win = windowForClientLocked(null, window, false);
+            if (!startPositioningLocked(win, false /*resize*/, startX, startY)) {
                 return false;
             }
         }
         try {
-            mActivityManager.setFocusedTask(callingWin.getTask().mTaskId);
+            mActivityManager.setFocusedTask(win.getTask().mTaskId);
         } catch(RemoteException e) {}
         return true;
     }
 
     private void startResizingTask(DisplayContent displayContent, int startX, int startY) {
-        int taskId = -1;
-        AppWindowToken atoken = null;
+        WindowState win = null;
         synchronized (mWindowMap) {
-            taskId = displayContent.taskIdForControlPoint(startX, startY);
-            Task task = mTaskIdToTask.get(taskId);
-            if (task == null || task.mAppTokens == null) {
-                return;
-            }
-            AppTokenList tokens = task.mAppTokens;
-            atoken = tokens.get(tokens.size() - 1);
-            WindowState win = atoken.findMainWindow();
+            win = displayContent.findWindowForControlPoint(startX, startY);
             if (!startPositioningLocked(win, true /*resize*/, startX, startY)) {
                 return;
             }
         }
         try {
-            mActivityManager.setFocusedTask(taskId);
+            mActivityManager.setFocusedTask(win.getTask().mTaskId);
         } catch(RemoteException e) {}
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 64440d3..c73dbaf 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -943,7 +943,6 @@
      */
     void getVisibleBounds(Rect bounds, boolean forTouch) {
         boolean intersectWithStackBounds = mAppToken != null && mAppToken.mCropWindowsToStack;
-        boolean isFreeform = false;
         bounds.setEmpty();
         mTmpRect.setEmpty();
         if (intersectWithStackBounds) {
@@ -955,13 +954,9 @@
             }
         }
 
-        final Task task = getTask();
-        if (task != null) {
-            task.getBounds(bounds);
-            isFreeform = task.inFreeformWorkspace();
-            if (intersectWithStackBounds) {
-                bounds.intersect(mTmpRect);
-            }
+        bounds.set(mVisibleFrame);
+        if (intersectWithStackBounds) {
+            bounds.intersect(mTmpRect);
         }
 
         if (bounds.isEmpty()) {
@@ -971,7 +966,7 @@
             }
             return;
         }
-        if (forTouch && isFreeform) {
+        if (forTouch && inFreeformWorkspace()) {
             final DisplayMetrics displayMetrics = getDisplayContent().getDisplayMetrics();
             final int delta = mService.dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, displayMetrics);
             bounds.inset(-delta, -delta);
diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java
index 9ee9cf4..e0d2ac1 100644
--- a/services/net/java/android/net/dhcp/DhcpClient.java
+++ b/services/net/java/android/net/dhcp/DhcpClient.java
@@ -244,8 +244,9 @@
     private PendingIntent createStateMachineCommandIntent(final String cmdName, final int cmd) {
         String action = DhcpClient.class.getName() + "." + mIfaceName + "." + cmdName;
 
-        Intent intent = new Intent(action, null)
-                .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        Intent intent = new Intent(action, null).addFlags(
+                Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT |
+                Intent.FLAG_RECEIVER_FOREGROUND);
         // TODO: The intent's package covers the whole of the system server, so it's pretty generic.
         // Consider adding some sort of token as well.
         intent.setPackage(mContext.getPackageName());
diff --git a/tests/CameraPrewarmTest/src/com/google/android/test/cameraprewarm/CameraActivity.java b/tests/CameraPrewarmTest/src/com/google/android/test/cameraprewarm/CameraActivity.java
index 0b43666..8085db7 100644
--- a/tests/CameraPrewarmTest/src/com/google/android/test/cameraprewarm/CameraActivity.java
+++ b/tests/CameraPrewarmTest/src/com/google/android/test/cameraprewarm/CameraActivity.java
@@ -31,5 +31,7 @@
         super.onCreate(savedInstanceState);
         setContentView(R.layout.camera_activity);
         Log.i(TAG, "Activity created");
+        Log.i(TAG, "Source: "
+                + getIntent().getStringExtra("com.android.systemui.camera_launch_source"));
     }
 }
diff --git a/tests/CameraPrewarmTest/src/com/google/android/test/cameraprewarm/SecureCameraActivity.java b/tests/CameraPrewarmTest/src/com/google/android/test/cameraprewarm/SecureCameraActivity.java
index 530fe00..242d3b2 100644
--- a/tests/CameraPrewarmTest/src/com/google/android/test/cameraprewarm/SecureCameraActivity.java
+++ b/tests/CameraPrewarmTest/src/com/google/android/test/cameraprewarm/SecureCameraActivity.java
@@ -17,6 +17,7 @@
 package com.google.android.test.cameraprewarm;
 
 import android.app.Activity;
+import android.graphics.Camera;
 import android.os.Bundle;
 import android.util.Log;
 import android.view.WindowManager;
@@ -31,5 +32,7 @@
         setContentView(R.layout.camera_activity);
         getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
         Log.i(CameraActivity.TAG, "Activity created");
+        Log.i(CameraActivity.TAG, "Source: "
+                + getIntent().getStringExtra("com.android.systemui.camera_launch_source"));
     }
 }