Merge "Fix broken Ryu default dir test."
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index e9a994e..0c0af87 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -306,7 +306,7 @@
  *     </thead>
  *
  *     <tbody>
- *     <tr><th colspan="3" align="left" border="0">{@link android.app.Activity#onCreate onCreate()}</th>
+ *     <tr><td colspan="3" align="left" border="0">{@link android.app.Activity#onCreate onCreate()}</td>
  *         <td>Called when the activity is first created.
  *             This is where you should do all of your normal static set up:
  *             create views, bind data to lists, etc.  This method also
@@ -318,7 +318,7 @@
  *     </tr>
  *
  *     <tr><td rowspan="5" style="border-left: none; border-right: none;">&nbsp;&nbsp;&nbsp;&nbsp;</td>
- *         <th colspan="2" align="left" border="0">{@link android.app.Activity#onRestart onRestart()}</th>
+ *         <td colspan="2" align="left" border="0">{@link android.app.Activity#onRestart onRestart()}</td>
  *         <td>Called after your activity has been stopped, prior to it being
  *             started again.
  *             <p>Always followed by <code>onStart()</code></td>
@@ -326,7 +326,7 @@
  *         <td align="center"><code>onStart()</code></td>
  *     </tr>
  *
- *     <tr><th colspan="2" align="left" border="0">{@link android.app.Activity#onStart onStart()}</th>
+ *     <tr><td colspan="2" align="left" border="0">{@link android.app.Activity#onStart onStart()}</td>
  *         <td>Called when the activity is becoming visible to the user.
  *             <p>Followed by <code>onResume()</code> if the activity comes
  *             to the foreground, or <code>onStop()</code> if it becomes hidden.</td>
@@ -335,7 +335,7 @@
  *     </tr>
  *
  *     <tr><td rowspan="2" style="border-left: none;">&nbsp;&nbsp;&nbsp;&nbsp;</td>
- *         <th align="left" border="0">{@link android.app.Activity#onResume onResume()}</th>
+ *         <td align="left" border="0">{@link android.app.Activity#onResume onResume()}</td>
  *         <td>Called when the activity will start
  *             interacting with the user.  At this point your activity is at
  *             the top of the activity stack, with user input going to it.
@@ -344,7 +344,7 @@
  *         <td align="center"><code>onPause()</code></td>
  *     </tr>
  *
- *     <tr><th align="left" border="0">{@link android.app.Activity#onPause onPause()}</th>
+ *     <tr><td align="left" border="0">{@link android.app.Activity#onPause onPause()}</td>
  *         <td>Called when the system is about to start resuming a previous
  *             activity.  This is typically used to commit unsaved changes to
  *             persistent data, stop animations and other things that may be consuming
@@ -358,7 +358,7 @@
  *                 <code>onStop()</code></td>
  *     </tr>
  *
- *     <tr><th colspan="2" align="left" border="0">{@link android.app.Activity#onStop onStop()}</th>
+ *     <tr><td colspan="2" align="left" border="0">{@link android.app.Activity#onStop onStop()}</td>
  *         <td>Called when the activity is no longer visible to the user, because
  *             another activity has been resumed and is covering this one.  This
  *             may happen either because a new activity is being started, an existing
@@ -372,7 +372,7 @@
  *                 <code>onDestroy()</code></td>
  *     </tr>
  *
- *     <tr><th colspan="3" align="left" border="0">{@link android.app.Activity#onDestroy onDestroy()}</th>
+ *     <tr><td colspan="3" align="left" border="0">{@link android.app.Activity#onDestroy onDestroy()}</td>
  *         <td>The final call you receive before your
  *             activity is destroyed.  This can happen either because the
  *             activity is finishing (someone called {@link Activity#finish} on
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index cf5240b..cee5646 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -2371,7 +2371,8 @@
             data.enforceInterface(IActivityManager.descriptor);
             IUserSwitchObserver observer = IUserSwitchObserver.Stub.asInterface(
                     data.readStrongBinder());
-            registerUserSwitchObserver(observer);
+            String name = data.readString();
+            registerUserSwitchObserver(observer, name);
             reply.writeNoException();
             return true;
         }
@@ -6060,11 +6061,13 @@
         return result;
     }
 
-    public void registerUserSwitchObserver(IUserSwitchObserver observer) throws RemoteException {
+    public void registerUserSwitchObserver(IUserSwitchObserver observer,
+            String name) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         data.writeInterfaceToken(IActivityManager.descriptor);
         data.writeStrongBinder(observer != null ? observer.asBinder() : null);
+        data.writeString(name);
         mRemote.transact(REGISTER_USER_SWITCH_OBSERVER_TRANSACTION, data, reply, 0);
         reply.readException();
         data.recycle();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 5021a5f..cd94740 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -235,6 +235,7 @@
     boolean mSystemThread = false;
     boolean mJitEnabled = false;
     boolean mSomeActivitiesChanged = false;
+    boolean mUpdatingSystemConfig = false;
 
     // These can be accessed by multiple threads; mPackages is the lock.
     // XXX For now we keep around information about all packages we have
@@ -1573,7 +1574,9 @@
                 case CONFIGURATION_CHANGED:
                     Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged");
                     mCurDefaultDisplayDpi = ((Configuration)msg.obj).densityDpi;
+                    mUpdatingSystemConfig = true;
                     handleConfigurationChanged((Configuration)msg.obj, null);
+                    mUpdatingSystemConfig = false;
                     Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                     break;
                 case CLEAN_UP_CONTEXT:
@@ -4628,7 +4631,14 @@
             // onConfigurationChanged
             int diff = activity.mCurrentConfig.diff(newConfig);
             if (diff != 0) {
-                shouldChangeConfig = true;
+                // Always send the task-level config changes. For system-level configuration, if
+                // this activity doesn't handle any of the config changes, then don't bother
+                // calling onConfigurationChanged as we're going to destroy it.
+                if (!mUpdatingSystemConfig
+                        || (~activity.mActivityInfo.getRealConfigChanged() & diff) == 0
+                        || !reportToActivity) {
+                    shouldChangeConfig = true;
+                }
             }
         }
 
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 5037e3e..3f7ff0b 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -512,7 +512,8 @@
     public int getLaunchedFromUid(IBinder activityToken) throws RemoteException;
     public String getLaunchedFromPackage(IBinder activityToken) throws RemoteException;
 
-    public void registerUserSwitchObserver(IUserSwitchObserver observer) throws RemoteException;
+    public void registerUserSwitchObserver(IUserSwitchObserver observer,
+            String name) throws RemoteException;
     public void unregisterUserSwitchObserver(IUserSwitchObserver observer) throws RemoteException;
 
     public void requestBugReport(int bugreportType) throws RemoteException;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Events.java b/packages/DocumentsUI/src/com/android/documentsui/Events.java
index 27293e1..1162923 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/Events.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/Events.java
@@ -144,6 +144,10 @@
         private static final Pools.SimplePool<MotionInputEvent> sPool = new Pools.SimplePool<>(1);
 
         private MotionEvent mEvent;
+        interface PositionProvider {
+            int get(MotionEvent e);
+        }
+
         private int mPosition;
 
         private MotionInputEvent() {
@@ -167,6 +171,19 @@
             return instance;
         }
 
+        public static MotionInputEvent obtain(
+                MotionEvent event, PositionProvider positionProvider) {
+            Shared.checkMainLoop();
+
+            MotionInputEvent instance = sPool.acquire();
+            instance = (instance != null ? instance : new MotionInputEvent());
+
+            instance.mEvent = event;
+            instance.mPosition = positionProvider.get(event);
+
+            return instance;
+        }
+
         public void recycle() {
             Shared.checkMainLoop();
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 00f2885..6fcf24c 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -49,7 +49,6 @@
 import android.support.v7.widget.GridLayoutManager;
 import android.support.v7.widget.GridLayoutManager.SpanSizeLookup;
 import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.RecyclerView.OnItemTouchListener;
 import android.support.v7.widget.RecyclerView.RecyclerListener;
 import android.support.v7.widget.RecyclerView.ViewHolder;
 import android.text.BidiFormatter;
@@ -61,14 +60,12 @@
 import android.view.DragEvent;
 import android.view.GestureDetector;
 import android.view.HapticFeedbackConstants;
-import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.View.OnTouchListener;
 import android.view.ViewGroup;
 import android.widget.ImageView;
 import android.widget.TextView;
@@ -143,7 +140,7 @@
     private Model mModel;
     private MultiSelectManager mSelectionManager;
     private Model.UpdateListener mModelUpdateListener = new ModelUpdateListener();
-    private ItemEventListener mItemEventListener = new ItemEventListener();
+    private ItemEventListener mItemEventListener;
     private SelectionModeListener mSelectionModeListener;
     private FocusManager mFocusManager;
 
@@ -322,6 +319,13 @@
         // Make sure this is done after the RecyclerView is set up.
         mFocusManager = new FocusManager(context, mRecView, mModel);
 
+        mItemEventListener = new ItemEventListener(
+                mSelectionManager,
+                mFocusManager,
+                this::handleViewItem,
+                this::deleteDocuments,
+                this::canSelect);
+
         final BaseActivity activity = getBaseActivity();
         mTuner = activity.createFragmentTuner();
         mMenuManager = activity.getMenuManager();
@@ -1395,99 +1399,6 @@
         return mSelectionManager.getSelection().contains(modelId);
     }
 
-    private class ItemEventListener implements DocumentHolder.EventListener {
-        @Override
-        public boolean onActivate(DocumentHolder doc) {
-            // Toggle selection if we're in selection mode, othewise, view item.
-            if (mSelectionManager.hasSelection()) {
-                mSelectionManager.toggleSelection(doc.modelId);
-            } else {
-                handleViewItem(doc.modelId);
-            }
-            return true;
-        }
-
-        @Override
-        public boolean onSelect(DocumentHolder doc) {
-            mSelectionManager.toggleSelection(doc.modelId);
-            mSelectionManager.setSelectionRangeBegin(doc.getAdapterPosition());
-            return true;
-        }
-
-        @Override
-        public boolean onKey(DocumentHolder doc, int keyCode, KeyEvent event) {
-            // Only handle key-down events. This is simpler, consistent with most other UIs, and
-            // enables the handling of repeated key events from holding down a key.
-            if (event.getAction() != KeyEvent.ACTION_DOWN) {
-                return false;
-            }
-
-            // Ignore tab key events.  Those should be handled by the top-level key handler.
-            if (keyCode == KeyEvent.KEYCODE_TAB) {
-                return false;
-            }
-
-            if (mFocusManager.handleKey(doc, keyCode, event)) {
-                // Handle range selection adjustments. Extending the selection will adjust the
-                // bounds of the in-progress range selection. Each time an unshifted navigation
-                // event is received, the range selection is restarted.
-                if (shouldExtendSelection(doc, event)) {
-                    if (!mSelectionManager.isRangeSelectionActive()) {
-                        // Start a range selection if one isn't active
-                        mSelectionManager.startRangeSelection(doc.getAdapterPosition());
-                    }
-                    mSelectionManager.snapRangeSelection(mFocusManager.getFocusPosition());
-                } else {
-                    mSelectionManager.endRangeSelection();
-                }
-                return true;
-            }
-
-            // Handle enter key events
-            switch (keyCode) {
-                case KeyEvent.KEYCODE_ENTER:
-                    if (event.isShiftPressed()) {
-                        return onSelect(doc);
-                    }
-                    // For non-shifted enter keypresses, fall through.
-                case KeyEvent.KEYCODE_DPAD_CENTER:
-                case KeyEvent.KEYCODE_BUTTON_A:
-                    return onActivate(doc);
-                case KeyEvent.KEYCODE_FORWARD_DEL:
-                    // This has to be handled here instead of in a keyboard shortcut, because
-                    // keyboard shortcuts all have to be modified with the 'Ctrl' key.
-                    if (mSelectionManager.hasSelection()) {
-                        Selection selection = mSelectionManager.getSelection(new Selection());
-                        deleteDocuments(selection);
-                    }
-                    // Always handle the key, even if there was nothing to delete. This is a
-                    // precaution to prevent other handlers from potentially picking up the event
-                    // and triggering extra behaviours.
-                    return true;
-            }
-
-            return false;
-        }
-
-        private boolean shouldExtendSelection(DocumentHolder doc, KeyEvent event) {
-            if (!Events.isNavigationKeyCode(event.getKeyCode()) || !event.isShiftPressed()) {
-                return false;
-            }
-
-            // TODO: Combine this method with onBeforeItemStateChange, as both of them are almost
-            // the same, and responsible for the same thing (whether to select or not).
-            final Cursor cursor = mModel.getItem(doc.modelId);
-            if (cursor == null) {
-                Log.w(TAG, "Couldn't obtain cursor for modelId: " + doc.modelId);
-                return false;
-            }
-
-            final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
-            final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
-            return mTuner.canSelectType(docMimeType, docFlags);
-        }
-    }
-
     private final class ModelUpdateListener implements Model.UpdateListener {
         @Override
         public void onModelUpdate(Model model) {
@@ -1594,68 +1505,26 @@
         }
     };
 
-    // Previously we listened to events with one class, only to bounce them forward
-    // to GestureDetector. We're still doing that here, but with a single class
-    // that reduces overall complexity in our glue code.
-    private static final class ListeningGestureDetector extends GestureDetector
-            implements OnItemTouchListener, OnTouchListener {
+    private boolean canSelect(String modelId) {
 
-        private DragStartHelper mDragHelper;
-        private GestureListener mGestureListener;
-
-        public ListeningGestureDetector(
-                Context context, DragStartHelper dragHelper, GestureListener listener) {
-            super(context, listener);
-            mDragHelper = dragHelper;
-            mGestureListener = listener;
-            setOnDoubleTapListener(listener);
-        }
-
-        @Override
-        public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
-            if (e.getAction() == MotionEvent.ACTION_DOWN && Events.isMouseEvent(e)) {
-                mGestureListener.setLastButtonState(e.getButtonState());
-            }
-
-            // Detect drag events. When a drag is detected, intercept the rest of the gesture.
-            View itemView = rv.findChildViewUnder(e.getX(), e.getY());
-            if (itemView != null && mDragHelper.onTouch(itemView,  e)) {
-                return true;
-            }
-            // Forward unhandled events to the GestureDetector.
-            onTouchEvent(e);
-
+        // TODO: Combine this method with onBeforeItemStateChange, as both of them are almost
+        // the same, and responsible for the same thing (whether to select or not).
+        final Cursor cursor = mModel.getItem(modelId);
+        if (cursor == null) {
+            Log.w(TAG, "Couldn't obtain cursor for modelId: " + modelId);
             return false;
         }
 
-        @Override
-        public void onTouchEvent(RecyclerView rv, MotionEvent e) {
-            View itemView = rv.findChildViewUnder(e.getX(), e.getY());
-            mDragHelper.onTouch(itemView,  e);
-            // Note: even though this event is being handled as part of a drag gesture, continue
-            // forwarding to the GestureDetector. The detector needs to see the entire cluster of
-            // events in order to properly interpret gestures.
-            onTouchEvent(e);
-        }
-
-        @Override
-        public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {}
-
-        // For mEmptyView right-click context menu
-        @Override
-        public boolean onTouch(View v, MotionEvent event) {
-            if (event.getButtonState() == MotionEvent.BUTTON_SECONDARY) {
-                return mGestureListener.onRightClick(event);
-            }
-            return false;
-        }
+        final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
+        final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
+        return mTuner.canSelectType(docMimeType, docFlags);
     }
 
     /**
      * The gesture listener for items in the list/grid view. Interprets gestures and sends the
      * events to the target DocumentHolder, whence they are routed to the appropriate listener.
      */
-    private class GestureListener extends GestureDetector.SimpleOnGestureListener {
+    class GestureListener extends GestureDetector.SimpleOnGestureListener {
         // From the RecyclerView, we get two events sent to
         // ListeningGestureDetector#onInterceptTouchEvent on a mouse click; we first get an
         // ACTION_DOWN Event for clicking on the mouse, and then an ACTION_UP event from releasing
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/ItemEventListener.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/ItemEventListener.java
new file mode 100644
index 0000000..cffba8d
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/ItemEventListener.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui.dirlist;
+
+import android.view.KeyEvent;
+
+import com.android.documentsui.Events;
+import com.android.documentsui.dirlist.MultiSelectManager.Selection;
+
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+/**
+ * Handles click/tap/key events on individual DocumentHolders.
+ */
+class ItemEventListener implements DocumentHolder.EventListener {
+    private MultiSelectManager mSelectionManager;
+    private FocusManager mFocusManager;
+
+    private Consumer<String> mViewItemCallback;
+    private Consumer<Selection> mDeleteDocumentsCallback;
+    private Predicate<String> mCanSelectPredicate;
+
+    public ItemEventListener(
+            MultiSelectManager selectionManager,
+            FocusManager focusManager,
+            Consumer<String> viewItemCallback,
+            Consumer<Selection> deleteDocumentsCallback,
+            Predicate<String> canSelectPredicate) {
+
+        mSelectionManager = selectionManager;
+        mFocusManager = focusManager;
+        mViewItemCallback = viewItemCallback;
+        mDeleteDocumentsCallback = deleteDocumentsCallback;
+        mCanSelectPredicate = canSelectPredicate;
+    }
+
+    @Override
+    public boolean onActivate(DocumentHolder doc) {
+        // Toggle selection if we're in selection mode, othewise, view item.
+        if (mSelectionManager.hasSelection()) {
+            mSelectionManager.toggleSelection(doc.modelId);
+        } else {
+            mViewItemCallback.accept(doc.modelId);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean onSelect(DocumentHolder doc) {
+        mSelectionManager.toggleSelection(doc.modelId);
+        mSelectionManager.setSelectionRangeBegin(doc.getAdapterPosition());
+        return true;
+    }
+
+    @Override
+    public boolean onKey(DocumentHolder doc, int keyCode, KeyEvent event) {
+        // Only handle key-down events. This is simpler, consistent with most other UIs, and
+        // enables the handling of repeated key events from holding down a key.
+        if (event.getAction() != KeyEvent.ACTION_DOWN) {
+            return false;
+        }
+
+        // Ignore tab key events.  Those should be handled by the top-level key handler.
+        if (keyCode == KeyEvent.KEYCODE_TAB) {
+            return false;
+        }
+
+        if (mFocusManager.handleKey(doc, keyCode, event)) {
+            // Handle range selection adjustments. Extending the selection will adjust the
+            // bounds of the in-progress range selection. Each time an unshifted navigation
+            // event is received, the range selection is restarted.
+            if (shouldExtendSelection(doc, event)) {
+                if (!mSelectionManager.isRangeSelectionActive()) {
+                    // Start a range selection if one isn't active
+                    mSelectionManager.startRangeSelection(doc.getAdapterPosition());
+                }
+                mSelectionManager.snapRangeSelection(mFocusManager.getFocusPosition());
+            } else {
+                mSelectionManager.endRangeSelection();
+            }
+            return true;
+        }
+
+        // Handle enter key events
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_ENTER:
+                if (event.isShiftPressed()) {
+                    return onSelect(doc);
+                }
+                // For non-shifted enter keypresses, fall through.
+            case KeyEvent.KEYCODE_DPAD_CENTER:
+            case KeyEvent.KEYCODE_BUTTON_A:
+                return onActivate(doc);
+            case KeyEvent.KEYCODE_FORWARD_DEL:
+                // This has to be handled here instead of in a keyboard shortcut, because
+                // keyboard shortcuts all have to be modified with the 'Ctrl' key.
+                if (mSelectionManager.hasSelection()) {
+                    Selection selection = mSelectionManager.getSelection(new Selection());
+                    mDeleteDocumentsCallback.accept(selection);
+                }
+                // Always handle the key, even if there was nothing to delete. This is a
+                // precaution to prevent other handlers from potentially picking up the event
+                // and triggering extra behaviours.
+                return true;
+        }
+
+        return false;
+    }
+
+    private boolean shouldExtendSelection(DocumentHolder doc, KeyEvent event) {
+        if (!Events.isNavigationKeyCode(event.getKeyCode()) || !event.isShiftPressed()) {
+            return false;
+        }
+
+        return mCanSelectPredicate.test(doc.modelId);
+    }
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/ListeningGestureDetector.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/ListeningGestureDetector.java
new file mode 100644
index 0000000..96e15d9
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/ListeningGestureDetector.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui.dirlist;
+
+import android.content.Context;
+import android.support.v13.view.DragStartHelper;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.OnItemTouchListener;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnTouchListener;
+
+import com.android.documentsui.Events;
+import com.android.documentsui.dirlist.DirectoryFragment.GestureListener;
+
+// Previously we listened to events with one class, only to bounce them forward
+// to GestureDetector. We're still doing that here, but with a single class
+// that reduces overall complexity in our glue code.
+final class ListeningGestureDetector extends GestureDetector
+        implements OnItemTouchListener, OnTouchListener {
+
+    private DragStartHelper mDragHelper;
+    private GestureListener mGestureListener;
+
+    public ListeningGestureDetector(
+            Context context, DragStartHelper dragHelper, GestureListener listener) {
+        super(context, listener);
+        mDragHelper = dragHelper;
+        mGestureListener = listener;
+        setOnDoubleTapListener(listener);
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
+        if (e.getAction() == MotionEvent.ACTION_DOWN && Events.isMouseEvent(e)) {
+            mGestureListener.setLastButtonState(e.getButtonState());
+        }
+
+        // Detect drag events. When a drag is detected, intercept the rest of the gesture.
+        View itemView = rv.findChildViewUnder(e.getX(), e.getY());
+        if (itemView != null && mDragHelper.onTouch(itemView,  e)) {
+            return true;
+        }
+        // Forward unhandled events to the GestureDetector.
+        onTouchEvent(e);
+
+        return false;
+    }
+
+    @Override
+    public void onTouchEvent(RecyclerView rv, MotionEvent e) {
+        View itemView = rv.findChildViewUnder(e.getX(), e.getY());
+        mDragHelper.onTouch(itemView,  e);
+        // Note: even though this event is being handled as part of a drag gesture, continue
+        // forwarding to the GestureDetector. The detector needs to see the entire cluster of
+        // events in order to properly interpret gestures.
+        onTouchEvent(e);
+    }
+
+    @Override
+    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {}
+
+    // For mEmptyView right-click context menu
+    @Override
+    public boolean onTouch(View v, MotionEvent event) {
+        if (event.getButtonState() == MotionEvent.BUTTON_SECONDARY) {
+            return mGestureListener.onRightClick(event);
+        }
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
index a8419bf..94d9550 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1040,10 +1040,9 @@
                         public void onForegroundProfileSwitch(int newProfileId) {
                             // Ignore.
                         }
-                    });
+                    }, TAG);
         } catch (RemoteException e) {
-            // TODO Auto-generated catch block
-            e.printStackTrace();
+            e.rethrowAsRuntimeException();
         }
 
         IntentFilter strongAuthTimeoutFilter = new IntentFilter();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 20c0e1d..3279800 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -1087,8 +1087,8 @@
             pkgicon = pmUser.getDefaultActivityIcon();
         }
 
-        ((ImageView) row.findViewById(R.id.app_icon)).setImageDrawable(pkgicon);
-        ((TextView) row.findViewById(R.id.pkgname)).setText(appname);
+        ((ImageView) guts.findViewById(R.id.app_icon)).setImageDrawable(pkgicon);
+        ((TextView) guts.findViewById(R.id.pkgname)).setText(appname);
 
         final View settingsButton = guts.findViewById(R.id.more_settings);
         if (appUid >= 0) {
@@ -1104,8 +1104,9 @@
             settingsButton.setVisibility(View.GONE);
         }
 
-        guts.bindImportance(pmUser, sbn, row, mNotificationData.getImportance(sbn.getKey()));
-        row.findViewById(R.id.done).setOnClickListener(new View.OnClickListener() {
+        guts.bindImportance(pmUser, sbn, mNotificationData.getImportance(sbn.getKey()));
+
+        guts.findViewById(R.id.done).setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
                 // If the user has security enabled, show challenge if the setting is changed.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
index f0195d4..37c7ef2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
@@ -173,7 +173,7 @@
     }
 
     void bindImportance(final PackageManager pm, final StatusBarNotification sbn,
-            final ExpandableNotificationRow row, final int importance) {
+            final int importance) {
         mINotificationManager = INotificationManager.Stub.asInterface(
                 ServiceManager.getService(Context.NOTIFICATION_SERVICE));
         mStartingUserImportance = NotificationListenerService.Ranking.IMPORTANCE_UNSPECIFIED;
@@ -191,8 +191,8 @@
             // unlikely.
         }
 
-        final View importanceSlider = row.findViewById(R.id.importance_slider);
-        final View importanceButtons = row.findViewById(R.id.importance_buttons);
+        final View importanceSlider = findViewById(R.id.importance_slider);
+        final View importanceButtons = findViewById(R.id.importance_buttons);
         if (mShowSlider) {
             bindSlider(importanceSlider, systemApp);
             importanceSlider.setVisibility(View.VISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 0563f4a5..7187ec2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -147,7 +147,7 @@
 
         // listen for user / profile change.
         try {
-            ActivityManagerNative.getDefault().registerUserSwitchObserver(mUserSwitchListener);
+            ActivityManagerNative.getDefault().registerUserSwitchObserver(mUserSwitchListener, TAG);
         } catch (RemoteException e) {
             // Ignore
         }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 3d9502e..9243b3f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -21378,8 +21378,8 @@
     }
 
     @Override
-    public void registerUserSwitchObserver(IUserSwitchObserver observer) {
-        mUserController.registerUserSwitchObserver(observer);
+    public void registerUserSwitchObserver(IUserSwitchObserver observer, String name) {
+        mUserController.registerUserSwitchObserver(observer, name);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 597b499..152d34d 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -76,6 +76,7 @@
 import android.os.UserManagerInternal;
 import android.os.storage.IMountService;
 import android.os.storage.StorageManager;
+import android.util.ArraySet;
 import android.util.IntArray;
 import android.util.Pair;
 import android.util.Slog;
@@ -86,6 +87,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.LocalServices;
 import com.android.server.pm.UserManagerService;
@@ -151,9 +153,9 @@
             = new RemoteCallbackList<>();
 
     /**
-     * Currently active user switch.
+     * Currently active user switch callbacks.
      */
-    Object mCurUserSwitchCallback;
+    private volatile ArraySet<String> mCurWaitingUserSwitchCallbacks;
 
     private volatile UserManagerService mUserManager;
 
@@ -1040,7 +1042,8 @@
 
     void timeoutUserSwitch(UserState uss, int oldUserId, int newUserId) {
         synchronized (mService) {
-            Slog.wtf(TAG, "User switch timeout: from " + oldUserId + " to " + newUserId);
+            Slog.wtf(TAG, "User switch timeout: from " + oldUserId + " to " + newUserId
+                    + ". Observers that didn't send results: " + mCurWaitingUserSwitchCallbacks);
             sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
         }
     }
@@ -1049,28 +1052,34 @@
         Slog.d(TAG, "Dispatch onUserSwitching oldUser #" + oldUserId + " newUser #" + newUserId);
         final int observerCount = mUserSwitchObservers.beginBroadcast();
         if (observerCount > 0) {
-            final IRemoteCallback callback = new IRemoteCallback.Stub() {
-                int mCount = 0;
-                @Override
-                public void sendResult(Bundle data) throws RemoteException {
-                    synchronized (mService) {
-                        if (mCurUserSwitchCallback == this) {
-                            mCount++;
-                            if (mCount == observerCount) {
-                                sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
-                            }
-                        }
-                    }
-                }
-            };
+            final ArraySet<String> curWaitingUserSwitchCallbacks = new ArraySet<>();
             synchronized (mService) {
                 uss.switching = true;
-                mCurUserSwitchCallback = callback;
+                mCurWaitingUserSwitchCallbacks = curWaitingUserSwitchCallbacks;
             }
             for (int i = 0; i < observerCount; i++) {
                 try {
-                    mUserSwitchObservers.getBroadcastItem(i).onUserSwitching(
-                            newUserId, callback);
+                    // Prepend with unique prefix to guarantee that keys are unique
+                    final String name = "#" + i + " " + mUserSwitchObservers.getBroadcastCookie(i);
+                    mCurWaitingUserSwitchCallbacks.add(name);
+                    final IRemoteCallback callback = new IRemoteCallback.Stub() {
+                        @Override
+                        public void sendResult(Bundle data) throws RemoteException {
+                            synchronized (mService) {
+                                // Early return if this session is no longer valid
+                                if (curWaitingUserSwitchCallbacks
+                                        != mCurWaitingUserSwitchCallbacks) {
+                                    return;
+                                }
+                                curWaitingUserSwitchCallbacks.remove(name);
+                                // Continue switching if all callbacks have been notified
+                                if (curWaitingUserSwitchCallbacks.isEmpty()) {
+                                    sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
+                                }
+                            }
+                        }
+                    };
+                    mUserSwitchObservers.getBroadcastItem(i).onUserSwitching(newUserId, callback);
                 } catch (RemoteException e) {
                 }
             }
@@ -1083,7 +1092,7 @@
     }
 
     void sendContinueUserSwitchLocked(UserState uss, int oldUserId, int newUserId) {
-        mCurUserSwitchCallback = null;
+        mCurWaitingUserSwitchCallbacks = null;
         mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
         mHandler.sendMessage(mHandler.obtainMessage(ActivityManagerService.CONTINUE_USER_SWITCH_MSG,
                 oldUserId, newUserId, uss));
@@ -1248,7 +1257,8 @@
                 ? getCurrentUserIdLocked(): userId;
     }
 
-    void registerUserSwitchObserver(IUserSwitchObserver observer) {
+    void registerUserSwitchObserver(IUserSwitchObserver observer, String name) {
+        Preconditions.checkNotNull(name, "Observer name cannot be null");
         if (mService.checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
                 != PackageManager.PERMISSION_GRANTED) {
             final String msg = "Permission Denial: registerUserSwitchObserver() from pid="
@@ -1258,8 +1268,7 @@
             Slog.w(TAG, msg);
             throw new SecurityException(msg);
         }
-
-        mUserSwitchObservers.register(observer);
+        mUserSwitchObservers.register(observer, name);
     }
 
     void unregisterUserSwitchObserver(IUserSwitchObserver observer) {
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java
index cc556c7..14e4bc6 100644
--- a/services/core/java/com/android/server/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java
@@ -1013,7 +1013,7 @@
                     public void onForegroundProfileSwitch(int newProfileId) {
                         // Ignore.
                     }
-                });
+                }, TAG);
         } catch (RemoteException e) {
             Slog.w(TAG, "Failed to listen for user switching event" ,e);
         }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 8bb3f98..62cdefa 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -6763,11 +6763,26 @@
         }
     }
 
+    private long getLastModifiedTime(PackageParser.Package pkg, File srcFile) {
+        if (srcFile.isDirectory()) {
+            final File baseFile = new File(pkg.baseCodePath);
+            long maxModifiedTime = baseFile.lastModified();
+            if (pkg.splitCodePaths != null) {
+                for (int i = pkg.splitCodePaths.length - 1; i >=0; --i) {
+                    final File splitFile = new File(pkg.splitCodePaths[i]);
+                    maxModifiedTime = Math.max(maxModifiedTime, splitFile.lastModified());
+                }
+            }
+            return maxModifiedTime;
+        }
+        return srcFile.lastModified();
+    }
+
     private void collectCertificatesLI(PackageSetting ps, PackageParser.Package pkg, File srcFile,
             final int policyFlags) throws PackageManagerException {
         if (ps != null
                 && ps.codePath.equals(srcFile)
-                && ps.timeStamp == srcFile.lastModified()
+                && ps.timeStamp == getLastModifiedTime(pkg, srcFile)
                 && !isCompatSignatureUpdateNeeded(pkg)
                 && !isRecoverSignatureUpdateNeeded(pkg)) {
             long mSigningKeySetId = ps.keySetData.getProperSigningKeySet();
@@ -8403,7 +8418,7 @@
 
         final String pkgName = pkg.packageName;
 
-        final long scanFileTime = scanFile.lastModified();
+        final long scanFileTime = getLastModifiedTime(pkg, scanFile);
         final boolean forceDex = (scanFlags & SCAN_FORCE_DEX) != 0;
         pkg.applicationInfo.processName = fixProcessName(
                 pkg.applicationInfo.packageName,
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 26d7194..a825efd 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -906,10 +906,9 @@
                         public void onForegroundProfileSwitch(int newProfileId) {
                             // Ignore.
                         }
-                    });
+                    }, TAG);
         } catch (RemoteException e) {
-            // TODO Auto-generated catch block
-            e.printStackTrace();
+            e.rethrowAsRuntimeException();
         }
     }
 
diff --git a/tools/aapt2/compile/Pseudolocalizer.cpp b/tools/aapt2/compile/Pseudolocalizer.cpp
index eae52d7..767d746 100644
--- a/tools/aapt2/compile/Pseudolocalizer.cpp
+++ b/tools/aapt2/compile/Pseudolocalizer.cpp
@@ -280,13 +280,13 @@
             std::u16string chunk;
             bool end = false;
             chunk.append(&c, 1);
-            while (!end && i < I) {
+            while (!end && i + 1 < I) {
                 ++i;
                 c = s[i];
                 chunk.append(&c, 1);
                 if (isPossibleNormalPlaceholderEnd(c)) {
                     end = true;
-                } else if (c == 't') {
+                } else if (i + 1 < I && c == 't') {
                     ++i;
                     c = s[i];
                     chunk.append(&c, 1);
diff --git a/tools/aapt2/compile/Pseudolocalizer_test.cpp b/tools/aapt2/compile/Pseudolocalizer_test.cpp
index b0bc2c1..36c8896 100644
--- a/tools/aapt2/compile/Pseudolocalizer_test.cpp
+++ b/tools/aapt2/compile/Pseudolocalizer_test.cpp
@@ -69,7 +69,7 @@
 
     EXPECT_TRUE(simpleHelper("Battery %1d%%",
                              "[βåţţéŕý »%1d«%% one two]", Pseudolocalizer::Method::kAccent));
-
+    EXPECT_TRUE(simpleHelper("^1 %", "[^1 % one]", Pseudolocalizer::Method::kAccent));
     EXPECT_TRUE(compoundHelper("", "", "", "[]", Pseudolocalizer::Method::kAccent));
     EXPECT_TRUE(compoundHelper("Hello,", " world", "",
                                "[Ĥéļļö, ŵöŕļð one two]", Pseudolocalizer::Method::kAccent));
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index 5b2093d..45e07a7 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -191,6 +191,9 @@
     // Uses library actions.
     applicationAction[u"uses-library"];
 
+    // Meta-data.
+    applicationAction[u"meta-data"] = metaDataAction;
+
     // Activity actions.
     applicationAction[u"activity"].action(requiredNameIsJavaClassName);
     applicationAction[u"activity"][u"intent-filter"] = intentFilterAction;