Merge "Discard input events sent to dead window"
diff --git a/api/current.txt b/api/current.txt
index 62f0113..3e63cd7 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -26218,6 +26218,7 @@
     field public static final int FLAG_SUPPORTS_MOVE = 256; // 0x100
     field public static final int FLAG_SUPPORTS_RENAME = 64; // 0x40
     field public static final int FLAG_SUPPORTS_THUMBNAIL = 1; // 0x1
+    field public static final int FLAG_SUPPORTS_TYPED_DOCUMENT = 512; // 0x200
     field public static final int FLAG_SUPPORTS_WRITE = 2; // 0x2
     field public static final java.lang.String MIME_TYPE_DIR = "vnd.android.document/directory";
   }
@@ -26255,6 +26256,7 @@
     method public final android.os.ParcelFileDescriptor openFile(android.net.Uri, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException;
     method public final android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle) throws java.io.FileNotFoundException;
     method public final android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException;
+    method public android.content.res.AssetFileDescriptor openTypedDocument(java.lang.String, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException;
     method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
     method public abstract android.database.Cursor queryChildDocuments(java.lang.String, java.lang.String[], java.lang.String) throws java.io.FileNotFoundException;
     method public abstract android.database.Cursor queryDocument(java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException;
@@ -26781,6 +26783,7 @@
     field public static final deprecated java.lang.String DEVICE_PROVISIONED = "device_provisioned";
     field public static final java.lang.String ENABLED_ACCESSIBILITY_SERVICES = "enabled_accessibility_services";
     field public static final java.lang.String ENABLED_INPUT_METHODS = "enabled_input_methods";
+    field public static final java.lang.String ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES = "enabled_notification_policy_access_packages";
     field public static final deprecated java.lang.String HTTP_PROXY = "http_proxy";
     field public static final java.lang.String INPUT_METHOD_SELECTOR_VISIBILITY = "input_method_selector_visibility";
     field public static final java.lang.String INSTALL_NON_MARKET_APPS = "install_non_market_apps";
diff --git a/api/system-current.txt b/api/system-current.txt
index 4118f33..42753ee 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -28207,6 +28207,7 @@
     field public static final int FLAG_SUPPORTS_MOVE = 256; // 0x100
     field public static final int FLAG_SUPPORTS_RENAME = 64; // 0x40
     field public static final int FLAG_SUPPORTS_THUMBNAIL = 1; // 0x1
+    field public static final int FLAG_SUPPORTS_TYPED_DOCUMENT = 512; // 0x200
     field public static final int FLAG_SUPPORTS_WRITE = 2; // 0x2
     field public static final java.lang.String MIME_TYPE_DIR = "vnd.android.document/directory";
   }
@@ -28244,6 +28245,7 @@
     method public final android.os.ParcelFileDescriptor openFile(android.net.Uri, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException;
     method public final android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle) throws java.io.FileNotFoundException;
     method public final android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException;
+    method public android.content.res.AssetFileDescriptor openTypedDocument(java.lang.String, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException;
     method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
     method public abstract android.database.Cursor queryChildDocuments(java.lang.String, java.lang.String[], java.lang.String) throws java.io.FileNotFoundException;
     method public abstract android.database.Cursor queryDocument(java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException;
@@ -28873,6 +28875,7 @@
     field public static final deprecated java.lang.String DEVICE_PROVISIONED = "device_provisioned";
     field public static final java.lang.String ENABLED_ACCESSIBILITY_SERVICES = "enabled_accessibility_services";
     field public static final java.lang.String ENABLED_INPUT_METHODS = "enabled_input_methods";
+    field public static final java.lang.String ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES = "enabled_notification_policy_access_packages";
     field public static final deprecated java.lang.String HTTP_PROXY = "http_proxy";
     field public static final java.lang.String INPUT_METHOD_SELECTOR_VISIBILITY = "input_method_selector_visibility";
     field public static final java.lang.String INSTALL_NON_MARKET_APPS = "install_non_market_apps";
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index ffeb6ed..5a75640 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -3823,6 +3823,9 @@
             return this;
         }
 
+        /** @hide */
+        public static final int MIN_ASHMEM_BITMAP_SIZE = 128 * (1 << 10);
+
         /**
          * @hide
          */
@@ -3831,7 +3834,7 @@
             super.purgeResources();
             if (mPicture != null &&
                 mPicture.isMutable() &&
-                mPicture.getAllocationByteCount() >= (128 * (1 << 10))) {
+                mPicture.getAllocationByteCount() >= MIN_ASHMEM_BITMAP_SIZE) {
                 mPicture = mPicture.createAshmemBitmap();
             }
             if (mBigLargeIcon != null) {
diff --git a/core/java/android/app/backup/BlobBackupHelper.java b/core/java/android/app/backup/BlobBackupHelper.java
index cdc62dc..82d0a94 100644
--- a/core/java/android/app/backup/BlobBackupHelper.java
+++ b/core/java/android/app/backup/BlobBackupHelper.java
@@ -20,7 +20,6 @@
 import android.util.ArrayMap;
 import android.util.Log;
 
-import java.io.BufferedInputStream;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.DataInputStream;
@@ -42,7 +41,7 @@
  */
 public abstract class BlobBackupHelper implements BackupHelper {
     private static final String TAG = "BlobBackupHelper";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    private static final boolean DEBUG = false;
 
     private final int mCurrentBlobVersion;
     private final String[] mKeys;
@@ -92,16 +91,21 @@
         final ArrayMap<String, Long> state = new ArrayMap<String, Long>();
 
         FileInputStream fis = new FileInputStream(oldStateFd.getFileDescriptor());
-        BufferedInputStream bis = new BufferedInputStream(fis);
-        DataInputStream in = new DataInputStream(bis);
+        DataInputStream in = new DataInputStream(fis);
 
         try {
             int version = in.readInt();
             if (version <= mCurrentBlobVersion) {
                 final int numKeys = in.readInt();
+                if (DEBUG) {
+                    Log.i(TAG, "  " + numKeys + " keys in state record");
+                }
                 for (int i = 0; i < numKeys; i++) {
                     String key = in.readUTF();
                     long checksum = in.readLong();
+                    if (DEBUG) {
+                        Log.i(TAG, "  key '" + key + "' checksum is " + checksum);
+                    }
                     state.put(key, checksum);
                 }
             } else {
@@ -110,6 +114,9 @@
         } catch (EOFException e) {
             // Empty file is expected on first backup,  so carry on. If the state
             // is truncated we just treat it the same way.
+            if (DEBUG) {
+                Log.i(TAG, "Hit EOF reading prior state");
+            }
             state.clear();
         } catch (Exception e) {
             Log.e(TAG, "Error examining prior backup state " + e.getMessage());
@@ -136,8 +143,13 @@
             final int N = (state != null) ? state.size() : 0;
             out.writeInt(N);
             for (int i = 0; i < N; i++) {
-                out.writeUTF(state.keyAt(i));
-                out.writeLong(state.valueAt(i).longValue());
+                final String key = state.keyAt(i);
+                final long checksum = state.valueAt(i).longValue();
+                if (DEBUG) {
+                    Log.i(TAG, "  writing key " + key + " checksum = " + checksum);
+                }
+                out.writeUTF(key);
+                out.writeLong(checksum);
             }
         } catch (IOException e) {
             Log.e(TAG, "Unable to write updated state", e);
@@ -226,6 +238,9 @@
     @Override
     public void performBackup(ParcelFileDescriptor oldStateFd, BackupDataOutput data,
             ParcelFileDescriptor newStateFd) {
+        if (DEBUG) {
+            Log.i(TAG, "Performing backup for " + this.getClass().getName());
+        }
 
         final ArrayMap<String, Long> oldState = readOldState(oldStateFd);
         final ArrayMap<String, Long> newState = new ArrayMap<String, Long>();
@@ -234,12 +249,16 @@
             for (String key : mKeys) {
                 final byte[] payload = deflate(getBackupPayload(key));
                 final long checksum = checksum(payload);
+                if (DEBUG) {
+                    Log.i(TAG, "Key " + key + " backup checksum is " + checksum);
+                }
                 newState.put(key, checksum);
 
                 Long oldChecksum = oldState.get(key);
-                if (oldChecksum == null || checksum != oldChecksum) {
+                if (oldChecksum == null || checksum != oldChecksum.longValue()) {
                     if (DEBUG) {
-                        Log.i(TAG, "State has changed for key " + key + ", writing");
+                        Log.i(TAG, "Checksum has changed from " + oldChecksum + " to " + checksum
+                                + " for key " + key + ", writing");
                     }
                     if (payload != null) {
                         data.writeEntityHeader(key, payload.length);
@@ -258,7 +277,7 @@
             Log.w(TAG,  "Unable to record notification state: " + e.getMessage());
             newState.clear();
         } finally {
-            // Always recommit the state even if nothing changed
+            // Always rewrite the state even if nothing changed
             writeBackupState(newState, newStateFd);
         }
     }
@@ -291,6 +310,9 @@
     @Override
     public void writeNewStateDescription(ParcelFileDescriptor newState) {
         // Just ensure that we do a full backup the first time after a restore
+        if (DEBUG) {
+            Log.i(TAG, "Writing state description after restore");
+        }
         writeBackupState(null, newState);
     }
 }
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 241e6db..159ca01 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -231,6 +231,7 @@
          * @see #FLAG_SUPPORTS_WRITE
          * @see #FLAG_SUPPORTS_DELETE
          * @see #FLAG_SUPPORTS_THUMBNAIL
+         * @see #FLAG_SUPPORTS_TYPED_DOCUMENT
          * @see #FLAG_DIR_PREFERS_GRID
          * @see #FLAG_DIR_PREFERS_LAST_MODIFIED
          */
@@ -347,6 +348,15 @@
         public static final int FLAG_SUPPORTS_MOVE = 1 << 8;
 
         /**
+         * Flag indicating that a document can be converted to alternative types.
+         *
+         * @see #COLUMN_FLAGS
+         * @see DocumentsProvider#openTypedDocument(String, String, Bundle,
+         *      android.os.CancellationSignal)
+         */
+        public static final int FLAG_SUPPORTS_TYPED_DOCUMENT = 1 << 9;
+
+        /**
          * Flag indicating that document titles should be hidden when viewing
          * this directory in a larger format grid. For example, a directory
          * containing only images may want the image thumbnails to speak for
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 28ac165..f01073b 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -31,6 +31,7 @@
 import static android.provider.DocumentsContract.isTreeUri;
 
 import android.annotation.CallSuper;
+import android.content.ClipDescription;
 import android.content.ContentProvider;
 import android.content.ContentResolver;
 import android.content.ContentValues;
@@ -502,6 +503,29 @@
     }
 
     /**
+     * Open and return the document in a format matching the specified MIME
+     * type filter.
+     * <p>
+     * A provider may perform a conversion if the documents's MIME type is not
+     * matching the specified MIME type filter.
+     *
+     * @param documentId the document to return.
+     * @param mimeTypeFilter the MIME type filter for the requested format. May
+     *            be *\/*, which matches any MIME type.
+     * @param opts extra options from the client. Specific to the content
+     *            provider.
+     * @param signal used by the caller to signal if the request should be
+     *            cancelled. May be null.
+     * @see Document#FLAG_SUPPORTS_TYPED_DOCUMENT
+     */
+    @SuppressWarnings("unused")
+    public AssetFileDescriptor openTypedDocument(
+            String documentId, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
+            throws FileNotFoundException {
+        throw new UnsupportedOperationException("Typed documents not supported");
+    }
+
+    /**
      * Implementation is provided by the parent class. Cannot be overriden.
      *
      * @see #queryRoots(String[])
@@ -846,34 +870,50 @@
      * Implementation is provided by the parent class. Cannot be overriden.
      *
      * @see #openDocumentThumbnail(String, Point, CancellationSignal)
+     * @see #openTypedDocument(String, String, Bundle, CancellationSignal)
      */
     @Override
     public final AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)
             throws FileNotFoundException {
-        enforceTree(uri);
-        if (opts != null && opts.containsKey(ContentResolver.EXTRA_SIZE)) {
-            final Point sizeHint = opts.getParcelable(ContentResolver.EXTRA_SIZE);
-            return openDocumentThumbnail(getDocumentId(uri), sizeHint, null);
-        } else {
-            return super.openTypedAssetFile(uri, mimeTypeFilter, opts);
-        }
+        return openTypedAssetFileImpl(uri, mimeTypeFilter, opts, null);
     }
 
     /**
      * Implementation is provided by the parent class. Cannot be overriden.
      *
      * @see #openDocumentThumbnail(String, Point, CancellationSignal)
+     * @see #openTypedDocument(String, String, Bundle, CancellationSignal)
      */
     @Override
     public final AssetFileDescriptor openTypedAssetFile(
             Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
             throws FileNotFoundException {
+        return openTypedAssetFileImpl(uri, mimeTypeFilter, opts, signal);
+    }
+
+    /**
+     * @hide
+     */
+    private final AssetFileDescriptor openTypedAssetFileImpl(
+            Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
+            throws FileNotFoundException {
         enforceTree(uri);
+        final String documentId = getDocumentId(uri);
         if (opts != null && opts.containsKey(ContentResolver.EXTRA_SIZE)) {
             final Point sizeHint = opts.getParcelable(ContentResolver.EXTRA_SIZE);
-            return openDocumentThumbnail(getDocumentId(uri), sizeHint, signal);
-        } else {
-            return super.openTypedAssetFile(uri, mimeTypeFilter, opts, signal);
+            return openDocumentThumbnail(documentId, sizeHint, signal);
         }
+        if ("*/*".equals(mimeTypeFilter)) {
+             // If they can take anything, the untyped open call is good enough.
+             return openAssetFile(uri, "r");
+        }
+        final String baseType = getType(uri);
+        if (baseType != null && ClipDescription.compareMimeTypes(baseType, mimeTypeFilter)) {
+            // Use old untyped open call if this provider has a type for this
+            // URI and it matches the request.
+            return openAssetFile(uri, "r");
+        }
+        // For any other yet unhandled case, let the provider subclass handle it.
+        return openTypedDocument(documentId, mimeTypeFilter, opts, signal);
     }
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ad46c3d..37f2c04 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5598,8 +5598,6 @@
         /**
          * Names of the packages that the current user has explicitly allowed to
          * manage notification policy configuration, separated by ':'.
-         *
-         * @hide
          */
         public static final String ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES =
                 "enabled_notification_policy_access_packages";
diff --git a/core/java/android/service/notification/ConditionProviderService.java b/core/java/android/service/notification/ConditionProviderService.java
index bbac023..88bd283 100644
--- a/core/java/android/service/notification/ConditionProviderService.java
+++ b/core/java/android/service/notification/ConditionProviderService.java
@@ -168,11 +168,6 @@
         }
 
         @Override
-        public void onRequestConditions(int relevance) {
-            mHandler.obtainMessage(H.ON_REQUEST_CONDITIONS, relevance, 0).sendToTarget();
-        }
-
-        @Override
         public void onSubscribe(Uri conditionId) {
             mHandler.obtainMessage(H.ON_SUBSCRIBE, conditionId).sendToTarget();
         }
@@ -185,7 +180,6 @@
 
     private final class H extends Handler {
         private static final int ON_CONNECTED = 1;
-        private static final int ON_REQUEST_CONDITIONS = 2;
         private static final int ON_SUBSCRIBE = 3;
         private static final int ON_UNSUBSCRIBE = 4;
 
@@ -198,10 +192,6 @@
                         name = "onConnected";
                         onConnected();
                         break;
-                    case ON_REQUEST_CONDITIONS:
-                        name = "onRequestConditions";
-                        onRequestConditions(msg.arg1);
-                        break;
                     case ON_SUBSCRIBE:
                         name = "onSubscribe";
                         onSubscribe((Uri)msg.obj);
diff --git a/core/java/android/service/notification/IConditionProvider.aidl b/core/java/android/service/notification/IConditionProvider.aidl
index ada8939..3f3c6b8 100644
--- a/core/java/android/service/notification/IConditionProvider.aidl
+++ b/core/java/android/service/notification/IConditionProvider.aidl
@@ -22,7 +22,6 @@
 /** @hide */
 oneway interface IConditionProvider {
     void onConnected();
-    void onRequestConditions(int relevance);
     void onSubscribe(in Uri conditionId);
     void onUnsubscribe(in Uri conditionId);
 }
\ No newline at end of file
diff --git a/graphics/java/android/graphics/drawable/Icon.java b/graphics/java/android/graphics/drawable/Icon.java
index 44d7530..d800acb 100644
--- a/graphics/java/android/graphics/drawable/Icon.java
+++ b/graphics/java/android/graphics/drawable/Icon.java
@@ -377,6 +377,9 @@
         return loadDrawable(context);
     }
 
+    /** @hide */
+    public static final int MIN_ASHMEM_ICON_SIZE = 128 * (1 << 10);
+
     /**
      * Puts the memory used by this instance into Ashmem memory, if possible.
      * @hide
@@ -384,7 +387,7 @@
     public void convertToAshmem() {
         if (mType == TYPE_BITMAP &&
             getBitmap().isMutable() &&
-            getBitmap().getAllocationByteCount() >= (128 * (1 << 10))) {
+            getBitmap().getAllocationByteCount() >= MIN_ASHMEM_ICON_SIZE) {
             setBitmap(getBitmap().createAshmemBitmap());
         }
     }
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 603caf1..9d3d4ae 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -39,6 +39,7 @@
     DamageAccumulator.cpp \
     DeferredDisplayList.cpp \
     DeferredLayerUpdater.cpp \
+    DeviceInfo.cpp \
     DisplayList.cpp \
     DisplayListCanvas.cpp \
     Dither.cpp \
@@ -208,6 +209,7 @@
     unit_tests/CanvasStateTests.cpp \
     unit_tests/ClipAreaTests.cpp \
     unit_tests/DamageAccumulatorTests.cpp \
+    unit_tests/DeviceInfoTests.cpp \
     unit_tests/FatVectorTests.cpp \
     unit_tests/LayerUpdateQueueTests.cpp \
     unit_tests/LinearAllocatorTests.cpp \
diff --git a/libs/hwui/DeviceInfo.cpp b/libs/hwui/DeviceInfo.cpp
new file mode 100644
index 0000000..03b1706
--- /dev/null
+++ b/libs/hwui/DeviceInfo.cpp
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+#include <DeviceInfo.h>
+
+#include "Extensions.h"
+
+#include <GLES2/gl2.h>
+
+#include <thread>
+#include <mutex>
+
+namespace android {
+namespace uirenderer {
+
+static DeviceInfo* sDeviceInfo = nullptr;
+static std::once_flag sInitializedFlag;
+
+const DeviceInfo* DeviceInfo::get() {
+    return sDeviceInfo;
+}
+
+void DeviceInfo::initialize() {
+    std::call_once(sInitializedFlag, []() {
+        sDeviceInfo = new DeviceInfo();
+        sDeviceInfo->load();
+    });
+}
+
+void DeviceInfo::load() {
+    mExtensions.load();
+    glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize);
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/DeviceInfo.h b/libs/hwui/DeviceInfo.h
new file mode 100644
index 0000000..f576a4f
--- /dev/null
+++ b/libs/hwui/DeviceInfo.h
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+#ifndef DEVICEINFO_H
+#define DEVICEINFO_H
+
+#include "Extensions.h"
+#include "utils/Macros.h"
+
+namespace android {
+namespace uirenderer {
+
+class DeviceInfo {
+    PREVENT_COPY_AND_ASSIGN(DeviceInfo);
+public:
+    // returns nullptr if DeviceInfo is not initialized yet
+    // Note this does not have a memory fence so it's up to the caller
+    // to use one if required. Normally this should not be necessary
+    static const DeviceInfo* get();
+
+    // only call this after GL has been initialized, or at any point if compiled
+    // with HWUI_NULL_GPU
+    static void initialize();
+
+    const Extensions& extensions() const { return mExtensions; }
+
+    int maxTextureSize() const { return mMaxTextureSize; }
+
+private:
+    DeviceInfo() {}
+    ~DeviceInfo() {}
+
+    void load();
+
+    Extensions mExtensions;
+    int mMaxTextureSize;
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* DEVICEINFO_H */
diff --git a/libs/hwui/Extensions.cpp b/libs/hwui/Extensions.cpp
index 6dd29ad..e257715 100644
--- a/libs/hwui/Extensions.cpp
+++ b/libs/hwui/Extensions.cpp
@@ -35,8 +35,8 @@
 #endif
 
 
-Extensions::Extensions() {
-    StringCollection extensions((const char*) glGetString(GL_EXTENSIONS));
+void Extensions::load() {
+    auto extensions = StringUtils::split((const char*) glGetString(GL_EXTENSIONS));
     mHasNPot = extensions.has("GL_OES_texture_npot");
     mHasFramebufferFetch = extensions.has("GL_NV_shader_framebuffer_fetch");
     mHasDiscardFramebuffer = extensions.has("GL_EXT_discard_framebuffer");
diff --git a/libs/hwui/Extensions.h b/libs/hwui/Extensions.h
index 6689b88..8ccfabd 100644
--- a/libs/hwui/Extensions.h
+++ b/libs/hwui/Extensions.h
@@ -19,6 +19,9 @@
 
 #include <cutils/compiler.h>
 
+#include <string>
+#include <unordered_set>
+
 namespace android {
 namespace uirenderer {
 
@@ -26,9 +29,9 @@
 // Classes
 ///////////////////////////////////////////////////////////////////////////////
 
-class ANDROID_API Extensions {
+class Extensions {
 public:
-    Extensions();
+    void load();
 
     inline bool hasNPot() const { return mHasNPot; }
     inline bool hasFramebufferFetch() const { return mHasFramebufferFetch; }
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index 485759b..78df297 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -17,15 +17,14 @@
 #include "EglManager.h"
 
 #include "Caches.h"
+#include "DeviceInfo.h"
 #include "Properties.h"
 #include "RenderThread.h"
 #include "renderstate/RenderState.h"
 #include "utils/StringUtils.h"
-
 #include <cutils/log.h>
 #include <cutils/properties.h>
 #include <EGL/eglext.h>
-
 #include <string>
 
 #define GLES_VERSION 2
@@ -129,12 +128,14 @@
     createContext();
     createPBufferSurface();
     makeCurrent(mPBufferSurface);
+    DeviceInfo::initialize();
     mRenderThread.renderState().onGLContextCreated();
     initAtlas();
 }
 
 void EglManager::initExtensions() {
-    StringCollection extensions(eglQueryString(mEglDisplay, EGL_EXTENSIONS));
+    auto extensions = StringUtils::split(
+            eglQueryString(mEglDisplay, EGL_EXTENSIONS));
     EglExtensions.bufferAge = extensions.has("EGL_EXT_buffer_age");
     EglExtensions.setDamage = extensions.has("EGL_KHR_partial_update");
     LOG_ALWAYS_FATAL_IF(!extensions.has("EGL_KHR_swap_buffers_with_damage"),
diff --git a/libs/hwui/unit_tests/DeviceInfoTests.cpp b/libs/hwui/unit_tests/DeviceInfoTests.cpp
new file mode 100644
index 0000000..c3c68ae
--- /dev/null
+++ b/libs/hwui/unit_tests/DeviceInfoTests.cpp
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+
+#include "DeviceInfo.h"
+
+#include <gtest/gtest.h>
+
+using namespace android;
+using namespace android::uirenderer;
+
+TEST(DeviceInfo, basic) {
+    const DeviceInfo* di = DeviceInfo::get();
+    EXPECT_EQ(nullptr, di) << "DeviceInfo was already initialized?";
+    DeviceInfo::initialize();
+    di = DeviceInfo::get();
+    ASSERT_NE(nullptr, di) << "DeviceInfo initialization failed";
+    EXPECT_EQ(2048, di->maxTextureSize()) << "Max texture size didn't match";
+}
diff --git a/libs/hwui/unit_tests/StringUtilsTests.cpp b/libs/hwui/unit_tests/StringUtilsTests.cpp
index 5174ae9..6b2e265 100644
--- a/libs/hwui/unit_tests/StringUtilsTests.cpp
+++ b/libs/hwui/unit_tests/StringUtilsTests.cpp
@@ -16,13 +16,13 @@
 
 #include <gtest/gtest.h>
 
-#include "utils/StringUtils.h"
+#include <utils/StringUtils.h>
 
-namespace android {
-namespace uirenderer {
+using namespace android;
+using namespace android::uirenderer;
 
 TEST(StringUtils, simpleBuildSet) {
-    StringCollection collection("a b c");
+    auto collection = StringUtils::split("a b c");
 
     EXPECT_TRUE(collection.has("a"));
     EXPECT_TRUE(collection.has("b"));
@@ -31,11 +31,8 @@
 }
 
 TEST(StringUtils, advancedBuildSet) {
-    StringCollection collection("GL_ext1 GL_ext2 GL_ext3");
+    auto collection = StringUtils::split("GL_ext1 GL_ext2 GL_ext3");
 
     EXPECT_TRUE(collection.has("GL_ext1"));
     EXPECT_FALSE(collection.has("GL_ext")); // string present, but not in list
 }
-
-};
-};
diff --git a/libs/hwui/utils/StringUtils.cpp b/libs/hwui/utils/StringUtils.cpp
index a1df0e7..ccddd3c 100644
--- a/libs/hwui/utils/StringUtils.cpp
+++ b/libs/hwui/utils/StringUtils.cpp
@@ -14,26 +14,24 @@
  * limitations under the License.
  */
 
-#include "StringUtils.h"
+#include <utils/StringUtils.h>
 
 namespace android {
 namespace uirenderer {
 
-StringCollection::StringCollection(const char* spacedList) {
+unordered_string_set&& StringUtils::split(const char* spacedList) {
+    unordered_string_set set;
     const char* current = spacedList;
     const char* head = current;
     do {
         head = strchr(current, ' ');
         std::string s(current, head ? head - current : strlen(current));
         if (s.length()) {
-            mSet.insert(s);
+            set.insert(std::move(s));
         }
         current = head + 1;
     } while (head);
-}
-
-bool StringCollection::has(const char* s) {
-    return mSet.find(std::string(s)) != mSet.end();
+    return std::move(set);
 }
 
 }; // namespace uirenderer
diff --git a/libs/hwui/utils/StringUtils.h b/libs/hwui/utils/StringUtils.h
index ef2a6d5..28b3d63 100644
--- a/libs/hwui/utils/StringUtils.h
+++ b/libs/hwui/utils/StringUtils.h
@@ -22,12 +22,16 @@
 namespace android {
 namespace uirenderer {
 
-class StringCollection {
+class unordered_string_set : public std::unordered_set<std::string> {
 public:
-    StringCollection(const char* spacedList);
-    bool has(const char* string);
-private:
-    std::unordered_set<std::string> mSet;
+    bool has(const char* str) {
+        return find(std::string(str)) != end();
+    }
+};
+
+class StringUtils {
+public:
+    static unordered_string_set&& split(const char* spacedList);
 };
 
 } /* namespace uirenderer */
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
index e3b1324..0ee970d 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
@@ -16,11 +16,11 @@
 
 package com.android.documentsui;
 
-import static com.android.documentsui.DirectoryFragment.ANIM_DOWN;
-import static com.android.documentsui.DirectoryFragment.ANIM_NONE;
-import static com.android.documentsui.DirectoryFragment.ANIM_SIDE;
-import static com.android.documentsui.DirectoryFragment.ANIM_UP;
 import static com.android.documentsui.Shared.DEBUG;
+import static com.android.documentsui.dirlist.DirectoryFragment.ANIM_DOWN;
+import static com.android.documentsui.dirlist.DirectoryFragment.ANIM_NONE;
+import static com.android.documentsui.dirlist.DirectoryFragment.ANIM_SIDE;
+import static com.android.documentsui.dirlist.DirectoryFragment.ANIM_UP;
 import static com.android.internal.util.Preconditions.checkArgument;
 
 import android.app.Activity;
@@ -55,6 +55,7 @@
 import android.widget.TextView;
 
 import com.android.documentsui.RecentsProvider.ResumeColumns;
+import com.android.documentsui.dirlist.DirectoryFragment;
 import com.android.documentsui.model.DocumentInfo;
 import com.android.documentsui.model.DocumentStack;
 import com.android.documentsui.model.DurableUtils;
@@ -69,7 +70,7 @@
 import java.util.List;
 import java.util.concurrent.Executor;
 
-abstract class BaseActivity extends Activity {
+public abstract class BaseActivity extends Activity {
 
     static final String EXTRA_STATE = "state";
 
@@ -383,7 +384,7 @@
         invalidateOptionsMenu();
     }
 
-    void onStateChanged() {
+    public void onStateChanged() {
         invalidateOptionsMenu();
     }
 
@@ -421,7 +422,7 @@
         super.onRestoreInstanceState(state);
     }
 
-    RootInfo getCurrentRoot() {
+    public RootInfo getCurrentRoot() {
         if (mState.stack.root != null) {
             return mState.stack.root;
         } else {
@@ -825,7 +826,7 @@
      * Interface providing access to current view of documents
      * even when all documents are not homed to the same parent.
      */
-    interface DocumentContext {
+    public interface DocumentContext {
         /**
          * Returns the cursor for the selected document. The cursor can be used to retrieve
          * details about a document and its siblings.
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
index bb82b38..b0bbec3 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
@@ -17,11 +17,9 @@
 package com.android.documentsui;
 
 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;
@@ -38,6 +36,7 @@
 import android.util.Log;
 
 import com.android.documentsui.RecentsProvider.StateColumns;
+import com.android.documentsui.dirlist.DirectoryFragment;
 import com.android.documentsui.model.DocumentInfo;
 import com.android.documentsui.model.RootInfo;
 
@@ -45,23 +44,6 @@
 
 import java.io.FileNotFoundException;
 
-class DirectoryResult implements AutoCloseable {
-    ContentProviderClient client;
-    Cursor cursor;
-    Exception exception;
-
-    int mode = MODE_UNKNOWN;
-    int sortOrder = SORT_ORDER_UNKNOWN;
-
-    @Override
-    public void close() {
-        IoUtils.closeQuietly(cursor);
-        ContentProviderClient.releaseQuietly(client);
-        cursor = null;
-        client = null;
-    }
-}
-
 public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
 
     private static final String[] SEARCH_REJECT_MIMES = new String[] { Document.MIME_TYPE_DIR };
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryResult.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryResult.java
new file mode 100644
index 0000000..e7e4f73
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryResult.java
@@ -0,0 +1,42 @@
+/*
+ * 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 static com.android.documentsui.State.MODE_UNKNOWN;
+import static com.android.documentsui.State.SORT_ORDER_UNKNOWN;
+
+import android.content.ContentProviderClient;
+import android.database.Cursor;
+
+import libcore.io.IoUtils;
+
+public class DirectoryResult implements AutoCloseable {
+    ContentProviderClient client;
+    public Cursor cursor;
+    public Exception exception;
+
+    public int mode = MODE_UNKNOWN;
+    public int sortOrder = SORT_ORDER_UNKNOWN;
+
+    @Override
+    public void close() {
+        IoUtils.closeQuietly(cursor);
+        ContentProviderClient.releaseQuietly(client);
+        cursor = null;
+        client = null;
+    }
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentClipper.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentClipper.java
index 6ba07fbb..b3c2846 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentClipper.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentClipper.java
@@ -39,7 +39,7 @@
  * ClipboardManager wrapper class providing higher level logical
  * support for dealing with Documents.
  */
-final class DocumentClipper {
+public final class DocumentClipper {
 
     private static final String TAG = "DocumentClipper";
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index 18957ee..403a464 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -16,12 +16,12 @@
 
 package com.android.documentsui;
 
-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 static com.android.documentsui.dirlist.DirectoryFragment.ANIM_NONE;
 
 import android.app.Activity;
 import android.app.Fragment;
@@ -53,6 +53,7 @@
 
 import com.android.documentsui.RecentsProvider.RecentColumns;
 import com.android.documentsui.RecentsProvider.ResumeColumns;
+import com.android.documentsui.dirlist.DirectoryFragment;
 import com.android.documentsui.model.DocumentInfo;
 import com.android.documentsui.model.DurableUtils;
 import com.android.documentsui.model.RootInfo;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Events.java b/packages/DocumentsUI/src/com/android/documentsui/Events.java
index d4c3ba3..49dae3d 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/Events.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/Events.java
@@ -25,33 +25,33 @@
 /**
  * Utility code for dealing with MotionEvents.
  */
-final class Events {
+public final class Events {
 
     /**
      * Returns true if event was triggered by a mouse.
      */
-    static boolean isMouseEvent(MotionEvent e) {
+    public static boolean isMouseEvent(MotionEvent e) {
         return isMouseType(e.getToolType(0));
     }
 
     /**
      * Returns true if event was triggered by a finger or stylus touch.
      */
-    static boolean isTouchEvent(MotionEvent e) {
+    public static boolean isTouchEvent(MotionEvent e) {
         return isTouchType(e.getToolType(0));
     }
 
     /**
      * Returns true if event was triggered by a mouse.
      */
-    static boolean isMouseType(int toolType) {
+    public static boolean isMouseType(int toolType) {
         return toolType == MotionEvent.TOOL_TYPE_MOUSE;
     }
 
     /**
      * Returns true if event was triggered by a finger or stylus touch.
      */
-    static boolean isTouchType(int toolType) {
+    public static boolean isTouchType(int toolType) {
         return toolType == MotionEvent.TOOL_TYPE_FINGER
                 || toolType == MotionEvent.TOOL_TYPE_STYLUS;
     }
@@ -59,28 +59,28 @@
     /**
      * Returns true if event was triggered by a finger or stylus touch.
      */
-    static boolean isActionDown(MotionEvent e) {
+    public static boolean isActionDown(MotionEvent e) {
         return e.getActionMasked() == MotionEvent.ACTION_DOWN;
     }
 
     /**
      * Returns true if event was triggered by a finger or stylus touch.
      */
-    static boolean isActionUp(MotionEvent e) {
+    public static boolean isActionUp(MotionEvent e) {
         return e.getActionMasked() == MotionEvent.ACTION_UP;
     }
 
     /**
      * Returns true if the shift is pressed.
      */
-    boolean isShiftPressed(MotionEvent e) {
+    public boolean isShiftPressed(MotionEvent e) {
         return hasShiftBit(e.getMetaState());
     }
 
     /**
      * Returns true if the "SHIFT" bit is set.
      */
-    static boolean hasShiftBit(int metaState) {
+    public static boolean hasShiftBit(int metaState) {
         return (metaState & KeyEvent.META_SHIFT_ON) != 0;
     }
 
@@ -88,7 +88,7 @@
      * A facade over MotionEvent primarily designed to permit for unit testing
      * of related code.
      */
-    interface InputEvent {
+    public interface InputEvent {
         boolean isMouseEvent();
         boolean isPrimaryButtonPressed();
         boolean isSecondaryButtonPressed();
@@ -109,7 +109,7 @@
         int getItemPosition();
     }
 
-    static final class MotionInputEvent implements InputEvent {
+    public static final class MotionInputEvent implements InputEvent {
         private final MotionEvent mEvent;
         private final RecyclerView mView;
         private final int mPosition;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
index 627ba75..c7cffed 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
@@ -16,8 +16,8 @@
 
 package com.android.documentsui;
 
-import static com.android.documentsui.DirectoryFragment.ANIM_NONE;
 import static com.android.documentsui.Shared.DEBUG;
+import static com.android.documentsui.dirlist.DirectoryFragment.ANIM_NONE;
 import static com.android.internal.util.Preconditions.checkArgument;
 import static com.android.internal.util.Preconditions.checkState;
 
@@ -44,6 +44,7 @@
 import android.widget.Toolbar;
 
 import com.android.documentsui.RecentsProvider.ResumeColumns;
+import com.android.documentsui.dirlist.DirectoryFragment;
 import com.android.documentsui.model.DocumentInfo;
 import com.android.documentsui.model.DocumentStack;
 import com.android.documentsui.model.DurableUtils;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/ManageRootActivity.java b/packages/DocumentsUI/src/com/android/documentsui/ManageRootActivity.java
index 26a3734..3045fa8 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/ManageRootActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/ManageRootActivity.java
@@ -16,8 +16,8 @@
 
 package com.android.documentsui;
 
-import static com.android.documentsui.DirectoryFragment.ANIM_NONE;
 import static com.android.documentsui.State.ACTION_MANAGE;
+import static com.android.documentsui.dirlist.DirectoryFragment.ANIM_NONE;
 
 import android.app.Activity;
 import android.app.Fragment;
@@ -41,6 +41,7 @@
 import android.widget.Toolbar;
 
 import com.android.documentsui.RecentsProvider.ResumeColumns;
+import com.android.documentsui.dirlist.DirectoryFragment;
 import com.android.documentsui.model.DocumentInfo;
 import com.android.documentsui.model.DurableUtils;
 import com.android.documentsui.model.RootInfo;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/MessageBar.java b/packages/DocumentsUI/src/com/android/documentsui/MessageBar.java
index 312d53b..5c6213f 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/MessageBar.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/MessageBar.java
@@ -105,7 +105,7 @@
         return mView;
     }
 
-    void hide() {
+    public void hide() {
         // The container view is used to show/hide the error bar. If a container is not provided,
         // fall back to showing/hiding the error bar View, which also works, but does not provide
         // the same animated transition.
@@ -116,7 +116,7 @@
         }
     }
 
-    void show() {
+    public void show() {
         // The container view is used to show/hide the error bar. If a container is not provided,
         // fall back to showing/hiding the error bar View, which also works, but does not provide
         // the same animated transition.
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Snackbars.java b/packages/DocumentsUI/src/com/android/documentsui/Snackbars.java
index f48b298..48c1a73 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/Snackbars.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/Snackbars.java
@@ -22,7 +22,7 @@
 import android.support.design.widget.Snackbar;
 import android.view.View;
 
-final class Snackbars {
+public final class Snackbars {
     private Snackbars() {}
 
     public static final Snackbar makeSnackbar(Activity activity, int messageId, int duration) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
similarity index 97%
rename from packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
rename to packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 3b7da78..e1f5ee1 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.documentsui;
+package com.android.documentsui.dirlist;
 
 import static com.android.documentsui.Shared.DEBUG;
 import static com.android.documentsui.State.ACTION_CREATE;
@@ -88,11 +88,44 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import com.android.documentsui.BaseActivity;
+import com.android.documentsui.CopyService;
+import com.android.documentsui.DirectoryLoader;
+import com.android.documentsui.DirectoryResult;
+import com.android.documentsui.DocumentClipper;
+import com.android.documentsui.DocumentsActivity;
+import com.android.documentsui.DocumentsApplication;
+import com.android.documentsui.Events;
+import com.android.documentsui.IconUtils;
+import com.android.documentsui.Menus;
+import com.android.documentsui.MessageBar;
+import com.android.documentsui.MimePredicate;
+import com.android.documentsui.ProviderExecutor;
+import com.android.documentsui.R;
+import com.android.documentsui.RecentLoader;
+import com.android.documentsui.RecentsProvider;
+import com.android.documentsui.RootCursorWrapper;
+import com.android.documentsui.RootsCache;
+import com.android.documentsui.Shared;
+import com.android.documentsui.Snackbars;
+import com.android.documentsui.State;
+import com.android.documentsui.ThumbnailCache;
 import com.android.documentsui.BaseActivity.DocumentContext;
-import com.android.documentsui.MultiSelectManager.Selection;
+import com.android.documentsui.BaseActivity.DocumentsIntent;
 import com.android.documentsui.ProviderExecutor.Preemptable;
+import com.android.documentsui.R.animator;
+import com.android.documentsui.R.attr;
+import com.android.documentsui.R.bool;
+import com.android.documentsui.R.dimen;
+import com.android.documentsui.R.drawable;
+import com.android.documentsui.R.id;
+import com.android.documentsui.R.layout;
+import com.android.documentsui.R.menu;
+import com.android.documentsui.R.plurals;
+import com.android.documentsui.R.string;
 import com.android.documentsui.RecentsProvider.StateColumns;
-import com.android.documentsui.dirlist.FragmentTuner;
+import com.android.documentsui.dirlist.MultiSelectManager.Callback;
+import com.android.documentsui.dirlist.MultiSelectManager.Selection;
 import com.android.documentsui.model.DocumentInfo;
 import com.android.documentsui.model.DocumentStack;
 import com.android.documentsui.model.RootInfo;
@@ -1391,7 +1424,7 @@
         return clipData;
     }
 
-    void copySelectedToClipboard() {
+    public void copySelectedToClipboard() {
         Selection sel = mSelectionManager.getSelection(new Selection());
         copySelectionToClipboard(sel);
     }
@@ -1410,7 +1443,7 @@
         }.execute(items);
     }
 
-    void pasteFromClipboard() {
+    public void pasteFromClipboard() {
         copyFromClipboard();
         getActivity().invalidateOptionsMenu();
     }
@@ -1440,7 +1473,7 @@
         return dest != null && dest.isDirectory() && dest.isCreateSupported();
     }
 
-    void selectAllFiles() {
+    public void selectAllFiles() {
         boolean changed = mSelectionManager.setItemsSelected(0, mModel.getItemCount(), true);
         if (changed) {
             updateDisplayState();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryItemAnimator.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryItemAnimator.java
similarity index 99%
rename from packages/DocumentsUI/src/com/android/documentsui/DirectoryItemAnimator.java
rename to packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryItemAnimator.java
index 0eb1ea5..0963845 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryItemAnimator.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryItemAnimator.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.documentsui;
+package com.android.documentsui.dirlist;
 
 import android.animation.Animator;
 import android.animation.ArgbEvaluator;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
index ca85cff..7e9bbe2 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
@@ -24,7 +24,6 @@
 import android.view.Menu;
 import android.view.MenuItem;
 
-import com.android.documentsui.DirectoryFragment;
 import com.android.documentsui.Menus;
 import com.android.documentsui.R;
 import com.android.documentsui.State;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
similarity index 99%
rename from packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java
rename to packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
index 4fde6ff..b5a3b93 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/MultiSelectManager.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.documentsui;
+package com.android.documentsui.dirlist;
 
 import static com.android.documentsui.Shared.DEBUG;
 import static com.android.internal.util.Preconditions.checkArgument;
@@ -41,8 +41,11 @@
 import android.view.MotionEvent;
 import android.view.View;
 
+import com.android.documentsui.Events;
+import com.android.documentsui.R;
 import com.android.documentsui.Events.InputEvent;
 import com.android.documentsui.Events.MotionInputEvent;
+import com.android.documentsui.R.drawable;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -342,7 +345,7 @@
      *
      * @param position
      */
-    void toggleSelection(int position) {
+    public 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) {
@@ -801,14 +804,11 @@
             cancelProvisionalSelection();
         }
 
-        /** @hide */
-        @VisibleForTesting
-        void clear() {
+        public void clear() {
             mSavedSelection.clear();
             mTotalSelection.clear();
         }
 
-        /** @hide */
         @VisibleForTesting
         void copyFrom(Selection source) {
             mSavedSelection = source.mSavedSelection.clone();
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/TestInputEvent.java b/packages/DocumentsUI/tests/src/com/android/documentsui/TestInputEvent.java
index e83f9e0..ec5321a 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/TestInputEvent.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/TestInputEvent.java
@@ -3,7 +3,7 @@
 import android.graphics.Point;
 import android.support.v7.widget.RecyclerView;
 
-class TestInputEvent implements Events.InputEvent {
+public class TestInputEvent implements Events.InputEvent {
 
     public boolean mouseEvent;
     public boolean primaryButtonPressed;
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/DirectoryFragmentModelTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/DirectoryFragmentModelTest.java
similarity index 95%
rename from packages/DocumentsUI/tests/src/com/android/documentsui/DirectoryFragmentModelTest.java
rename to packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/DirectoryFragmentModelTest.java
index 36d880a..746e2117 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/DirectoryFragmentModelTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/DirectoryFragmentModelTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.documentsui;
+package com.android.documentsui.dirlist;
 
 import android.content.ContentResolver;
 import android.content.Context;
@@ -27,8 +27,9 @@
 import android.test.mock.MockContentResolver;
 import android.view.ViewGroup;
 
-import com.android.documentsui.DirectoryFragment.Model;
-import com.android.documentsui.MultiSelectManager.Selection;
+import com.android.documentsui.DirectoryResult;
+import com.android.documentsui.dirlist.DirectoryFragment.Model;
+import com.android.documentsui.dirlist.MultiSelectManager.Selection;
 import com.android.documentsui.model.DocumentInfo;
 
 import java.util.List;
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/MultiSelectManagerTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
similarity index 97%
rename from packages/DocumentsUI/tests/src/com/android/documentsui/MultiSelectManagerTest.java
rename to packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
index ceb8cdc..24f5c9e 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/MultiSelectManagerTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.documentsui;
+package com.android.documentsui.dirlist;
 
 import android.support.v7.widget.RecyclerView;
 import android.test.AndroidTestCase;
@@ -22,7 +22,9 @@
 import android.view.View;
 import android.view.ViewGroup;
 
-import com.android.documentsui.MultiSelectManager.Selection;
+import com.android.documentsui.TestInputEvent;
+import com.android.documentsui.dirlist.MultiSelectManager;
+import com.android.documentsui.dirlist.MultiSelectManager.Selection;
 
 import org.mockito.Mockito;
 
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/MultiSelectManager_GridModelTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java
similarity index 98%
rename from packages/DocumentsUI/tests/src/com/android/documentsui/MultiSelectManager_GridModelTest.java
rename to packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java
index f6683fa..c4b6ce5 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/MultiSelectManager_GridModelTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java
@@ -14,10 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.documentsui;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+package com.android.documentsui.dirlist;
 
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -26,7 +23,7 @@
 import android.util.SparseBooleanArray;
 import android.view.View;
 
-import com.android.documentsui.MultiSelectManager.GridModel;
+import com.android.documentsui.dirlist.MultiSelectManager.GridModel;
 
 public class MultiSelectManager_GridModelTest extends AndroidTestCase {
 
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/MultiSelectManager_SelectionTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_SelectionTest.java
similarity index 96%
rename from packages/DocumentsUI/tests/src/com/android/documentsui/MultiSelectManager_SelectionTest.java
rename to packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_SelectionTest.java
index eddf4ef..64da750 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/MultiSelectManager_SelectionTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_SelectionTest.java
@@ -14,13 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.documentsui;
-
-import static org.junit.Assert.*;
+package com.android.documentsui.dirlist;
 
 import android.test.AndroidTestCase;
 
-import com.android.documentsui.MultiSelectManager.Selection;
+import com.android.documentsui.dirlist.MultiSelectManager.Selection;
 
 
 public class MultiSelectManager_SelectionTest extends AndroidTestCase{
diff --git a/packages/SettingsLib/Android.mk b/packages/SettingsLib/Android.mk
index 0245ed3..c860668 100644
--- a/packages/SettingsLib/Android.mk
+++ b/packages/SettingsLib/Android.mk
@@ -3,6 +3,8 @@
 
 LOCAL_MODULE := SettingsLib
 
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4
+
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/packages/SettingsLib/res/drawable/ic_menu.xml b/packages/SettingsLib/res/drawable/ic_menu.xml
new file mode 100644
index 0000000..910a3d0
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_menu.xml
@@ -0,0 +1,24 @@
+<!--
+    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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24.0dp"
+        android:height="24.0dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M3.0,18.0l18.0,0.0l0.0,-2.0L3.0,16.0l0.0,2.0zm0.0,-5.0l18.0,0.0l0.0,-2.0L3.0,11.0l0.0,2.0zm0.0,-7.0l0.0,2.0l18.0,0.0L21.0,6.0L3.0,6.0z"/>
+</vector>
diff --git a/packages/SettingsLib/res/layout/drawer_category.xml b/packages/SettingsLib/res/layout/drawer_category.xml
new file mode 100644
index 0000000..20afcd4
--- /dev/null
+++ b/packages/SettingsLib/res/layout/drawer_category.xml
@@ -0,0 +1,36 @@
+<!--
+    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.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="1dp"
+        android:background="?android:attr/listDivider" />
+
+    <TextView
+        android:id="@android:id/title"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingTop="12dp"
+        android:paddingBottom="12dp"
+        android:paddingStart="16dp"
+        android:textAppearance="?android:attr/textAppearanceSmall" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/res/layout/drawer_item.xml b/packages/SettingsLib/res/layout/drawer_item.xml
new file mode 100644
index 0000000..4b53049
--- /dev/null
+++ b/packages/SettingsLib/res/layout/drawer_item.xml
@@ -0,0 +1,40 @@
+<!--
+    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.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/tile_item"
+    android:layout_width="match_parent"
+    android:layout_height="48dp"
+    android:orientation="horizontal" >
+
+    <ImageView
+        android:id="@android:id/icon"
+        android:layout_width="72dp"
+        android:layout_height="24dp"
+        android:layout_gravity="center_vertical"
+        android:tint="?android:attr/colorAccent"
+        android:paddingStart="16dp"
+        android:paddingEnd="32dp" />
+
+    <TextView
+        android:id="@android:id/title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:textColor="?android:attr/colorControlNormal"
+        android:textAppearance="?android:attr/textAppearanceMedium" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/res/layout/settings_with_drawer.xml b/packages/SettingsLib/res/layout/settings_with_drawer.xml
new file mode 100644
index 0000000..a9a78e8
--- /dev/null
+++ b/packages/SettingsLib/res/layout/settings_with_drawer.xml
@@ -0,0 +1,55 @@
+<!--
+    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.
+-->
+<android.support.v4.widget.DrawerLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/drawer_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <!-- The main content view -->
+    <LinearLayout
+        android:id="@+id/content_parent"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical" >
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            style="?android:attr/actionBarStyle">
+            <Toolbar
+                xmlns:android="http://schemas.android.com/apk/res/android"
+                android:id="@+id/action_bar"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:navigationContentDescription="@*android:string/action_bar_up_description"
+                android:theme="?android:attr/actionBarTheme"
+                style="?android:attr/toolbarStyle"
+                android:background="?android:attr/colorPrimary" />
+        </FrameLayout>
+        <FrameLayout
+            android:id="@+id/content_frame"
+            android:layout_width="match_parent"
+            android:layout_height="fill_parent" />
+    </LinearLayout>
+    <!-- The navigation drawer -->
+    <ListView android:id="@+id/left_drawer"
+        android:layout_width="300dp"
+        android:layout_height="match_parent"
+        android:layout_gravity="start"
+        android:choiceMode="singleChoice"
+        android:divider="@android:color/transparent"
+        android:dividerHeight="0dp"
+        android:background="?android:attr/colorBackground" />
+</android.support.v4.widget.DrawerLayout>
diff --git a/packages/SettingsLib/res/layout/user_preference.xml b/packages/SettingsLib/res/layout/user_preference.xml
new file mode 100644
index 0000000..75031fd
--- /dev/null
+++ b/packages/SettingsLib/res/layout/user_preference.xml
@@ -0,0 +1,47 @@
+<?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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@android:id/widget_frame"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/user_spinner_item_height"
+        android:paddingStart="@dimen/user_spinner_padding_sides"
+        android:paddingEnd="@dimen/user_spinner_padding_sides"
+        android:orientation="horizontal" >
+
+    <ImageView
+            android:id="@+android:id/icon"
+            android:layout_width="@dimen/user_icon_view_height"
+            android:layout_height="@dimen/user_icon_view_height"
+            android:layout_gravity="center"
+            android:scaleType="fitCenter"
+            android:paddingBottom="@dimen/user_spinner_padding"
+            android:paddingTop="@dimen/user_spinner_padding" />
+
+    <TextView
+            android:id="@+android:id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:singleLine="true"
+            android:layout_gravity="center"
+            android:labelFor="@+android:id/icon"
+            android:ellipsize="marquee"
+            android:fadingEdge="horizontal"
+            android:paddingStart="@dimen/user_spinner_padding"
+            android:paddingEnd="@dimen/user_spinner_padding"
+            style="?android:attr/textAppearanceMedium" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml
index 3ad8f21..d7c78f6 100644
--- a/packages/SettingsLib/res/values/dimens.xml
+++ b/packages/SettingsLib/res/values/dimens.xml
@@ -23,4 +23,12 @@
     <dimen name="disappear_y_translation">-32dp</dimen>
 
     <dimen name="circle_avatar_size">40dp</dimen>
+
+    <!-- Height of a user icon view -->
+    <dimen name="user_icon_view_height">56dp</dimen>
+    <!-- User spinner -->
+    <dimen name="user_spinner_height">72dp</dimen>
+    <dimen name="user_spinner_padding">4dp</dimen>
+    <dimen name="user_spinner_padding_sides">20dp</dimen>
+    <dimen name="user_spinner_item_height">56dp</dimen>
 </resources>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 14eb084..9e48849 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -340,4 +340,12 @@
         <item>kor</item>
     </string-array>
 
+   <!-- Title for profile selection dialog [CHAR LIMIT=30] -->
+   <string name="choose_profile">Choose Profile</string>
+
+    <!-- Header for items under the personal user [CHAR LIMIT=30] -->
+    <string name="category_personal">Personal</string>
+    <!-- Header for items under the work user [CHAR LIMIT=30] -->
+    <string name="category_work">Work</string>
+
 </resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java b/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java
new file mode 100644
index 0000000..0f322cf
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java
@@ -0,0 +1,123 @@
+/**
+ * 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 com.android.settingslib.drawer;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DashboardCategory implements Parcelable {
+
+    /**
+     * Title of the category that is shown to the user.
+     */
+    public CharSequence title;
+
+    /**
+     * Key used for placing external tiles.
+     */
+    public String key;
+
+    /**
+     * Used to control display order.
+     */
+    public int priority;
+
+    /**
+     * List of the category's children
+     */
+    public List<DashboardTile> tiles = new ArrayList<DashboardTile>();
+
+
+    public DashboardCategory() {
+        // Empty
+    }
+
+    public void addTile(DashboardTile tile) {
+        tiles.add(tile);
+    }
+
+    public void addTile(int n, DashboardTile tile) {
+        tiles.add(n, tile);
+    }
+
+    public void removeTile(DashboardTile tile) {
+        tiles.remove(tile);
+    }
+
+    public void removeTile(int n) {
+        tiles.remove(n);
+    }
+
+    public int getTilesCount() {
+        return tiles.size();
+    }
+
+    public DashboardTile getTile(int n) {
+        return tiles.get(n);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        TextUtils.writeToParcel(title, dest, flags);
+        dest.writeString(key);
+        dest.writeInt(priority);
+
+        final int count = tiles.size();
+        dest.writeInt(count);
+
+        for (int n = 0; n < count; n++) {
+            DashboardTile tile = tiles.get(n);
+            tile.writeToParcel(dest, flags);
+        }
+    }
+
+    public void readFromParcel(Parcel in) {
+        title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        key = in.readString();
+        priority = in.readInt();
+
+        final int count = in.readInt();
+
+        for (int n = 0; n < count; n++) {
+            DashboardTile tile = DashboardTile.CREATOR.createFromParcel(in);
+            tiles.add(tile);
+        }
+    }
+
+    DashboardCategory(Parcel in) {
+        readFromParcel(in);
+    }
+
+    public static final Creator<DashboardCategory> CREATOR = new Creator<DashboardCategory>() {
+        public DashboardCategory createFromParcel(Parcel source) {
+            return new DashboardCategory(source);
+        }
+
+        public DashboardCategory[] newArray(int size) {
+            return new DashboardCategory[size];
+        }
+    };
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardTile.java b/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardTile.java
new file mode 100644
index 0000000..6fef134
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardTile.java
@@ -0,0 +1,142 @@
+/**
+ * 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 com.android.settingslib.drawer;
+
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+
+/**
+ * Description of a single dashboard tile that the user can select.
+ */
+public class DashboardTile implements Parcelable {
+
+    /**
+     * Title of the tile that is shown to the user.
+     * @attr ref android.R.styleable#PreferenceHeader_title
+     */
+    public CharSequence title;
+
+    /**
+     * Optional summary describing what this tile controls.
+     * @attr ref android.R.styleable#PreferenceHeader_summary
+     */
+    public CharSequence summary;
+
+    /**
+     * Optional icon to show for this tile.
+     * @attr ref android.R.styleable#PreferenceHeader_icon
+     */
+    public Icon icon;
+
+    /**
+     * Intent to launch when the preference is selected.
+     */
+    public Intent intent;
+
+    /**
+     * Optional list of user handles which the intent should be launched on.
+     */
+    public ArrayList<UserHandle> userHandle = new ArrayList<>();
+
+    /**
+     * Optional additional data for use by subclasses of the activity
+     */
+    public Bundle extras;
+
+    /**
+     * Category in which the tile should be placed.
+     */
+    public String category;
+
+    /**
+     * Priority of the intent filter that created this tile, used for display ordering.
+     */
+    public int priority;
+
+    public DashboardTile() {
+        // Empty
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        TextUtils.writeToParcel(title, dest, flags);
+        TextUtils.writeToParcel(summary, dest, flags);
+        if (icon != null) {
+            dest.writeByte((byte) 1);
+            icon.writeToParcel(dest, flags);
+        } else {
+            dest.writeByte((byte) 0);
+        }
+        if (intent != null) {
+            dest.writeByte((byte) 1);
+            intent.writeToParcel(dest, flags);
+        } else {
+            dest.writeByte((byte) 0);
+        }
+        final int N = userHandle.size();
+        dest.writeInt(N);
+        for (int i = 0; i < N; i++) {
+            userHandle.get(i).writeToParcel(dest, flags);
+        }
+        dest.writeBundle(extras);
+        dest.writeString(category);
+        dest.writeInt(priority);
+    }
+
+    public void readFromParcel(Parcel in) {
+        title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        summary = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        if (in.readByte() != 0) {
+            icon = Icon.CREATOR.createFromParcel(in);
+        }
+        if (in.readByte() != 0) {
+            intent = Intent.CREATOR.createFromParcel(in);
+        }
+        final int N = in.readInt();
+        for (int i = 0; i < N; i++) {
+            userHandle.add(UserHandle.CREATOR.createFromParcel(in));
+        }
+        extras = in.readBundle();
+        category = in.readString();
+        priority = in.readInt();
+    }
+
+    DashboardTile(Parcel in) {
+        readFromParcel(in);
+    }
+
+    public static final Creator<DashboardTile> CREATOR = new Creator<DashboardTile>() {
+        public DashboardTile createFromParcel(Parcel source) {
+            return new DashboardTile(source);
+        }
+        public DashboardTile[] newArray(int size) {
+            return new DashboardTile[size];
+        }
+    };
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/ProfileSelectDialog.java b/packages/SettingsLib/src/com/android/settingslib/drawer/ProfileSelectDialog.java
new file mode 100644
index 0000000..793e877d
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/ProfileSelectDialog.java
@@ -0,0 +1,67 @@
+/*
+ * 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 com.android.settingslib.drawer;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.FragmentManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+public class ProfileSelectDialog extends DialogFragment implements OnClickListener {
+
+    private static final String ARG_SELECTED_TILE = "selectedTile";
+
+    private DashboardTile mSelectedTile;
+
+    public static void show(FragmentManager manager, DashboardTile tile) {
+        ProfileSelectDialog dialog = new ProfileSelectDialog();
+        Bundle args = new Bundle();
+        args.putParcelable(ARG_SELECTED_TILE, tile);
+        dialog.setArguments(args);
+        dialog.show(manager, "select_profile");
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mSelectedTile = getArguments().getParcelable(ARG_SELECTED_TILE);
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        Context context = getActivity();
+        AlertDialog.Builder builder = new AlertDialog.Builder(context);
+        UserAdapter adapter = UserAdapter.createUserAdapter(UserManager.get(context), context,
+                mSelectedTile.userHandle);
+        builder.setTitle(com.android.settingslib.R.string.choose_profile)
+                .setAdapter(adapter, this);
+
+        return builder.create();
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        UserHandle user = mSelectedTile.userHandle.get(which);
+        getActivity().startActivityAsUser(mSelectedTile.intent, user);
+        ((SettingsDrawerActivity) getActivity()).onProfileTileOpen();
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
new file mode 100644
index 0000000..910e615
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
@@ -0,0 +1,144 @@
+/**
+ * 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 com.android.settingslib.drawer;
+
+import android.annotation.LayoutRes;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.v4.widget.DrawerLayout;
+import android.util.Pair;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.AdapterView;
+import android.widget.ListView;
+import android.widget.Toolbar;
+import com.android.settingslib.R;
+
+import java.util.HashMap;
+import java.util.List;
+
+public class SettingsDrawerActivity extends Activity {
+
+    private SettingsDrawerAdapter mDrawerAdapter;
+    // Hold on to a cache of tiles to avoid loading the info multiple times.
+    private final HashMap<Pair<String, String>, DashboardTile> mTileCache = new HashMap<>();
+    private List<DashboardCategory> mDashboardCategories;
+    private DrawerLayout mDrawerLayout;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        requestWindowFeature(Window.FEATURE_NO_TITLE);
+        super.setContentView(R.layout.settings_with_drawer);
+        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
+        // Nope.
+        Toolbar toolbar = (Toolbar) findViewById(R.id.action_bar);
+        setActionBar(toolbar);
+        mDrawerAdapter = new SettingsDrawerAdapter(this);
+        ListView listView = (ListView) findViewById(R.id.left_drawer);
+        listView.setAdapter(mDrawerAdapter);
+        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+            public void onItemClick(android.widget.AdapterView<?> parent, View view, int position,
+                    long id) {
+                onTileClicked(mDrawerAdapter.getTile(position));
+            };
+        });
+        getActionBar().setHomeAsUpIndicator(R.drawable.ic_menu);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        if (item.getItemId() == android.R.id.home) {
+            openDrawer();
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        mDrawerAdapter.updateCategories();
+    }
+
+    public void openDrawer() {
+        mDrawerLayout.openDrawer(Gravity.START);
+    }
+
+    public void closeDrawer() {
+        mDrawerLayout.closeDrawers();
+    }
+
+    @Override
+    public void setContentView(@LayoutRes int layoutResID) {
+        LayoutInflater.from(this).inflate(layoutResID,
+                (ViewGroup) findViewById(R.id.content_frame));
+    }
+
+    @Override
+    public void setContentView(View view) {
+        ((ViewGroup) findViewById(R.id.content_frame)).addView(view);
+    }
+
+    @Override
+    public void setContentView(View view, ViewGroup.LayoutParams params) {
+        ((ViewGroup) findViewById(R.id.content_frame)).addView(view, params);
+    }
+
+    public void updateDrawer() {
+        // TODO: Do this in the background with some loading.
+        mDrawerAdapter.updateCategories();
+        getActionBar().setDisplayHomeAsUpEnabled(mDrawerAdapter.getCount() != 0);
+    }
+
+    public List<DashboardCategory> getDashboardCategories(boolean force) {
+        if (force) {
+            mDashboardCategories = TileUtils.getCategories(this, mTileCache);
+        }
+        return mDashboardCategories;
+    }
+
+    public boolean openTile(DashboardTile tile) {
+        closeDrawer();
+        int numUserHandles = tile.userHandle.size();
+        if (numUserHandles > 1) {
+            ProfileSelectDialog.show(getFragmentManager(), tile);
+            return false;
+        } else if (numUserHandles == 1) {
+            startActivityAsUser(tile.intent, tile.userHandle.get(0));
+        } else {
+            startActivity(tile.intent);
+        }
+        return true;
+    }
+
+    protected void onTileClicked(DashboardTile tile) {
+        if (openTile(tile)) {
+            finish();
+        }
+    }
+
+    public void onProfileTileOpen() {
+        finish();
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerAdapter.java b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerAdapter.java
new file mode 100644
index 0000000..fef716c
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerAdapter.java
@@ -0,0 +1,106 @@
+/*
+ * 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 com.android.settingslib.drawer;
+
+import android.graphics.drawable.Icon;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.settingslib.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SettingsDrawerAdapter extends BaseAdapter {
+
+    private final ArrayList<Item> mItems = new ArrayList<>();
+    private final SettingsDrawerActivity mActivity;
+
+    public SettingsDrawerAdapter(SettingsDrawerActivity activity) {
+        mActivity = activity;
+    }
+
+    void updateCategories() {
+        List<DashboardCategory> categories = mActivity.getDashboardCategories(true);
+        mItems.clear();
+        for (int i = 0; i < categories.size(); i++) {
+            Item category = new Item();
+            category.icon = null;
+            DashboardCategory dashboardCategory = categories.get(i);
+            category.label = dashboardCategory.title;
+            mItems.add(category);
+            for (int j = 0; j < dashboardCategory.tiles.size(); j++) {
+                Item tile = new Item();
+                DashboardTile dashboardTile = dashboardCategory.tiles.get(j);
+                tile.label = dashboardTile.title;
+                tile.icon = dashboardTile.icon;
+                tile.tile = dashboardTile;
+                mItems.add(tile);
+            }
+        }
+        notifyDataSetChanged();
+    }
+
+    public DashboardTile getTile(int position) {
+        return mItems.get(position).tile;
+    }
+
+    @Override
+    public int getCount() {
+        return mItems.size();
+    }
+
+    @Override
+    public Object getItem(int position) {
+        return mItems.get(position);
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return position;
+    }
+
+    @Override
+    public boolean isEnabled(int position) {
+        return mItems.get(position).icon != null;
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        Item item = mItems.get(position);
+        boolean isTile = item.icon != null;
+        if (convertView == null || (isTile != (convertView.getId() == R.id.tile_item))) {
+            convertView = LayoutInflater.from(mActivity).inflate(isTile ? R.layout.drawer_item
+                            : R.layout.drawer_category,
+                    parent, false);
+        }
+        if (isTile) {
+            ((ImageView) convertView.findViewById(android.R.id.icon)).setImageIcon(item.icon);
+        }
+        ((TextView) convertView.findViewById(android.R.id.title)).setText(item.label);
+        return convertView;
+    }
+
+    private static class Item {
+        public Icon icon;
+        public CharSequence label;
+        public DashboardTile tile;
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
new file mode 100644
index 0000000..18e8d31
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
@@ -0,0 +1,312 @@
+/*
+ * 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 com.android.settingslib.drawer;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class TileUtils {
+
+    private static final boolean DEBUG = false;
+
+    private static final String LOG_TAG = "TileUtils";
+
+    /**
+     * Settings will search for system activities of this action and add them as a top level
+     * settings tile using the following parameters.
+     *
+     * <p>A category must be specified in the meta-data for the activity named
+     * {@link #EXTRA_CATEGORY_KEY}
+     *
+     * <p>The title may be defined by meta-data named {@link #META_DATA_PREFERENCE_TITLE}
+     * otherwise the label for the activity will be used.
+     *
+     * <p>The icon may be defined by meta-data named {@link #META_DATA_PREFERENCE_ICON}
+     * otherwise the icon for the activity will be used.
+     *
+     * <p>A summary my be defined by meta-data named {@link #META_DATA_PREFERENCE_SUMMARY}
+     */
+    private static final String EXTRA_SETTINGS_ACTION =
+            "com.android.settings.action.EXTRA_SETTINGS";
+
+    /**
+     * Same as #EXTRA_SETTINGS_ACTION but used for the platform Settings activities.
+     */
+    private static final String SETTINGS_ACTION =
+            "com.android.settings.action.SETTINGS";
+
+    private static final String OPERATOR_SETTINGS =
+            "com.android.settings.OPERATOR_APPLICATION_SETTING";
+
+    private static final String OPERATOR_DEFAULT_CATEGORY =
+            "com.android.settings.category.wireless";
+
+    private static final String MANUFACTURER_SETTINGS =
+            "com.android.settings.MANUFACTURER_APPLICATION_SETTING";
+
+    private static final String MANUFACTURER_DEFAULT_CATEGORY =
+            "com.android.settings.category.device";
+
+    /**
+     * The key used to get the category from metadata of activities of action
+     * {@link #EXTRA_SETTINGS_ACTION}
+     * The value must be one of:
+     * <li>com.android.settings.category.wireless</li>
+     * <li>com.android.settings.category.device</li>
+     * <li>com.android.settings.category.personal</li>
+     * <li>com.android.settings.category.system</li>
+     */
+    private static final String EXTRA_CATEGORY_KEY = "com.android.settings.category";
+
+    /**
+     * Name of the meta-data item that should be set in the AndroidManifest.xml
+     * to specify the icon that should be displayed for the preference.
+     */
+    public static final String META_DATA_PREFERENCE_ICON = "com.android.settings.icon";
+
+    /**
+     * Name of the meta-data item that should be set in the AndroidManifest.xml
+     * to specify the title that should be displayed for the preference.
+     */
+    public static final String META_DATA_PREFERENCE_TITLE = "com.android.settings.title";
+
+    /**
+     * Name of the meta-data item that should be set in the AndroidManifest.xml
+     * to specify the summary text that should be displayed for the preference.
+     */
+    public static final String META_DATA_PREFERENCE_SUMMARY = "com.android.settings.summary";
+
+    private static final String SETTING_PKG = "com.android.settings";
+
+    public static List<DashboardCategory> getCategories(Context context) {
+        return getCategories(context, new HashMap<Pair<String, String>, DashboardTile>());
+    }
+
+    public static List<DashboardCategory> getCategories(Context context,
+            HashMap<Pair<String, String>, DashboardTile> cache) {
+        ArrayList<DashboardTile> tiles = new ArrayList<>();
+        UserManager userManager = UserManager.get(context);
+        for (UserHandle user : userManager.getUserProfiles()) {
+            // TODO: Needs much optimization, too many PM queries going on here.
+            if (user.getIdentifier() == ActivityManager.getCurrentUser()) {
+                // Only add Settings for this user.
+                getTilesForAction(context, user, SETTINGS_ACTION, cache, null, tiles, true);
+                getTilesForAction(context, user, OPERATOR_SETTINGS, cache,
+                        OPERATOR_DEFAULT_CATEGORY, tiles, false);
+                getTilesForAction(context, user, MANUFACTURER_SETTINGS, cache,
+                        MANUFACTURER_DEFAULT_CATEGORY, tiles, false);
+            }
+            getTilesForAction(context, user, EXTRA_SETTINGS_ACTION, cache, null, tiles, false);
+        }
+        HashMap<String, DashboardCategory> categoryMap = new HashMap<>();
+        for (DashboardTile tile : tiles) {
+            DashboardCategory category = categoryMap.get(tile.category);
+            if (category == null) {
+                category = createCategory(context, tile.category);
+                if (category == null) {
+                    Log.w(LOG_TAG, "Couldn't find category " + tile.category);
+                    continue;
+                }
+                categoryMap.put(category.key, category);
+            }
+            category.addTile(tile);
+        }
+        ArrayList<DashboardCategory> categories = new ArrayList<>(categoryMap.values());
+        for (DashboardCategory category : categories) {
+            Collections.sort(category.tiles, TILE_COMPARATOR);
+        }
+        Collections.sort(categories, CATEGORY_COMPARATOR);
+        return categories;
+    }
+
+    private static DashboardCategory createCategory(Context context, String categoryKey) {
+        DashboardCategory category = new DashboardCategory();
+        category.key = categoryKey;
+        PackageManager pm = context.getPackageManager();
+        List<ResolveInfo> results = pm.queryIntentActivities(new Intent(categoryKey), 0);
+        if (results.size() == 0) {
+            return null;
+        }
+        for (ResolveInfo resolved : results) {
+            if (!resolved.system) {
+                // Do not allow any app to add to settings, only system ones.
+                continue;
+            }
+            category.title = resolved.activityInfo.loadLabel(pm);
+            category.priority = SETTING_PKG.equals(
+                    resolved.activityInfo.applicationInfo.packageName) ? resolved.priority : 0;
+            if (DEBUG) Log.d(LOG_TAG, "Adding category " + category.title);
+        }
+
+        return category;
+    }
+
+    private static void getTilesForAction(Context context,
+            UserHandle user, String action, Map<Pair<String, String>, DashboardTile> addedCache,
+            String defaultCategory, ArrayList<DashboardTile> outTiles, boolean requireSettings) {
+        PackageManager pm = context.getPackageManager();
+        Intent intent = new Intent(action);
+        List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent,
+                PackageManager.GET_META_DATA, user.getIdentifier());
+        for (ResolveInfo resolved : results) {
+            if (requireSettings) {
+                if (!SETTING_PKG.equals(resolved.activityInfo.applicationInfo.packageName)) {
+                    continue;
+                }
+            } else {
+                if (!resolved.system) {
+                    // Do not allow any app to add to settings, only system ones.
+                    continue;
+                }
+            }
+            ActivityInfo activityInfo = resolved.activityInfo;
+            Bundle metaData = activityInfo.metaData;
+            String categoryKey = defaultCategory;
+            if (((metaData == null) || !metaData.containsKey(EXTRA_CATEGORY_KEY))
+                    && categoryKey == null) {
+                Log.w(LOG_TAG, "Found " + resolved.activityInfo.name + " for action "
+                        + action + " missing metadata "
+                        + (metaData == null ? "" : EXTRA_CATEGORY_KEY));
+                continue;
+            } else {
+                categoryKey = metaData.getString(EXTRA_CATEGORY_KEY);
+            }
+            Pair<String, String> key = new Pair<String, String>(activityInfo.packageName,
+                    activityInfo.name);
+            DashboardTile tile = addedCache.get(key);
+            if (tile == null) {
+                tile = new DashboardTile();
+                tile.intent = new Intent().setClassName(
+                        activityInfo.packageName, activityInfo.name);
+                tile.category = categoryKey;
+                tile.priority = requireSettings ? resolved.priority : 0;
+                updateTileData(context, tile);
+                if (DEBUG) Log.d(LOG_TAG, "Adding tile " + tile.title);
+
+                addedCache.put(key, tile);
+            }
+            if (!tile.userHandle.contains(user)) {
+                tile.userHandle.add(user);
+            }
+            if (!outTiles.contains(tile)) {
+                outTiles.add(tile);
+            }
+        }
+    }
+
+    private static DashboardCategory getCategory(List<DashboardCategory> target,
+            String categoryKey) {
+        for (DashboardCategory category : target) {
+            if (categoryKey.equals(category.key)) {
+                return category;
+            }
+        }
+        return null;
+    }
+
+    private static boolean updateTileData(Context context, DashboardTile tile) {
+        Intent intent = tile.intent;
+        if (intent != null) {
+            // Find the activity that is in the system image
+            PackageManager pm = context.getPackageManager();
+            List<ResolveInfo> list = tile.userHandle.size() != 0
+                    ? pm.queryIntentActivitiesAsUser(intent, PackageManager.GET_META_DATA,
+                            tile.userHandle.get(0).getIdentifier())
+                    : pm.queryIntentActivities(intent, PackageManager.GET_META_DATA);
+            int listSize = list.size();
+            for (int i = 0; i < listSize; i++) {
+                ResolveInfo resolveInfo = list.get(i);
+                if (resolveInfo.activityInfo.applicationInfo.isSystemApp()) {
+                    int icon = 0;
+                    CharSequence title = null;
+                    String summary = null;
+
+                    // Get the activity's meta-data
+                    try {
+                        Resources res = pm.getResourcesForApplication(
+                                resolveInfo.activityInfo.packageName);
+                        Bundle metaData = resolveInfo.activityInfo.metaData;
+
+                        if (res != null && metaData != null) {
+                            if (metaData.containsKey(META_DATA_PREFERENCE_ICON)) {
+                                icon = metaData.getInt(META_DATA_PREFERENCE_ICON);
+                            }
+                            if (metaData.containsKey(META_DATA_PREFERENCE_TITLE)) {
+                                title = metaData.getString(META_DATA_PREFERENCE_TITLE);
+                            }
+                            if (metaData.containsKey(META_DATA_PREFERENCE_SUMMARY)) {
+                                summary = metaData.getString(META_DATA_PREFERENCE_SUMMARY);
+                            }
+                        }
+                    } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
+                        if (DEBUG) Log.d(LOG_TAG, "Couldn't find info", e);
+                    }
+
+                    // Set the preference title to the activity's label if no
+                    // meta-data is found
+                    if (TextUtils.isEmpty(title)) {
+                        title = resolveInfo.loadLabel(pm).toString();
+                    }
+                    if (icon == 0) {
+                        icon = resolveInfo.activityInfo.icon;
+                    }
+
+                    // Set icon, title and summary for the preference
+                    tile.icon = Icon.createWithResource(resolveInfo.activityInfo.packageName, icon);
+                    tile.title = title;
+                    tile.summary = summary;
+                    // Replace the intent with this specific activity
+                    tile.intent = new Intent().setClassName(resolveInfo.activityInfo.packageName,
+                            resolveInfo.activityInfo.name);
+
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    private static final Comparator<DashboardTile> TILE_COMPARATOR =
+            new Comparator<DashboardTile>() {
+        @Override
+        public int compare(DashboardTile lhs, DashboardTile rhs) {
+            return rhs.priority - lhs.priority;
+        }
+    };
+
+    private static final Comparator<DashboardCategory> CATEGORY_COMPARATOR =
+            new Comparator<DashboardCategory>() {
+        @Override
+        public int compare(DashboardCategory lhs, DashboardCategory rhs) {
+            return rhs.priority - lhs.priority;
+        }
+    };
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/UserAdapter.java b/packages/SettingsLib/src/com/android/settingslib/drawer/UserAdapter.java
new file mode 100644
index 0000000..f9fa805f
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/UserAdapter.java
@@ -0,0 +1,211 @@
+/*
+ * 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.settingslib.drawer;
+
+import android.app.ActivityManager;
+
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.database.DataSetObserver;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.ListAdapter;
+import android.widget.SpinnerAdapter;
+import android.widget.TextView;
+import com.android.internal.util.UserIcons;
+import com.android.settingslib.drawable.CircleFramedDrawable;
+
+import com.android.settingslib.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Adapter for a spinner that shows a list of users.
+ */
+public class UserAdapter implements SpinnerAdapter, ListAdapter {
+    /** Holder for user details */
+    public static class UserDetails {
+        private final UserHandle mUserHandle;
+        private final String mName;
+        private final Drawable mIcon;
+
+        public UserDetails(UserHandle userHandle, UserManager um, Context context) {
+            mUserHandle = userHandle;
+            UserInfo userInfo = um.getUserInfo(mUserHandle.getIdentifier());
+            Drawable icon;
+            if (userInfo.isManagedProfile()) {
+                mName = context.getString(R.string.managed_user_title);
+                icon = context.getDrawable(
+                    com.android.internal.R.drawable.ic_corp_icon);
+            } else {
+                mName = userInfo.name;
+                final int userId = userInfo.id;
+                if (um.getUserIcon(userId) != null) {
+                    icon = new BitmapDrawable(context.getResources(), um.getUserIcon(userId));
+                } else {
+                    icon = UserIcons.getDefaultUserIcon(userId, /* light= */ false);
+                }
+            }
+            this.mIcon = encircle(context, icon);
+        }
+
+        private static Drawable encircle(Context context, Drawable icon) {
+            return CircleFramedDrawable.getInstance(context, UserIcons.convertToBitmap(icon));
+        }
+    }
+    private ArrayList<UserDetails> data;
+    private final LayoutInflater mInflater;
+
+    public UserAdapter(Context context, ArrayList<UserDetails> users) {
+        if (users == null) {
+            throw new IllegalArgumentException("A list of user details must be provided");
+        }
+        this.data = users;
+        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+    }
+
+    public UserHandle getUserHandle(int position) {
+        if (position < 0 || position >= data.size()) {
+            return null;
+        }
+        return data.get(position).mUserHandle;
+    }
+
+    @Override
+    public View getDropDownView(int position, View convertView, ViewGroup parent) {
+        final View row = convertView != null ? convertView : createUser(parent);
+
+        UserDetails user = data.get(position);
+        ((ImageView) row.findViewById(android.R.id.icon)).setImageDrawable(user.mIcon);
+        ((TextView) row.findViewById(android.R.id.title)).setText(getTitle(user));
+        return row;
+    }
+
+    private int getTitle(UserDetails user) {
+        int userHandle = user.mUserHandle.getIdentifier();
+        if (userHandle == UserHandle.USER_CURRENT
+                || userHandle == ActivityManager.getCurrentUser()) {
+            return R.string.category_personal;
+        } else {
+            return R.string.category_work;
+        }
+    }
+
+    private View createUser(ViewGroup parent) {
+        return mInflater.inflate(R.layout.user_preference, parent, false);
+    }
+
+    @Override
+    public void registerDataSetObserver(DataSetObserver observer) {
+        // We don't support observers
+    }
+
+    @Override
+    public void unregisterDataSetObserver(DataSetObserver observer) {
+        // We don't support observers
+    }
+
+    @Override
+    public int getCount() {
+        return data.size();
+    }
+
+    @Override
+    public UserDetails getItem(int position) {
+        return data.get(position);
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return data.get(position).mUserHandle.getIdentifier();
+    }
+
+    @Override
+    public boolean hasStableIds() {
+        return false;
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        return getDropDownView(position, convertView, parent);
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        return 0;
+    }
+
+    @Override
+    public int getViewTypeCount() {
+        return 1;
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return data.isEmpty();
+    }
+
+    @Override
+    public boolean areAllItemsEnabled() {
+        return true;
+    }
+
+    @Override
+    public boolean isEnabled(int position) {
+        return true;
+    }
+
+    /**
+     * Creates a {@link UserAdapter} if there is more than one profile on the device.
+     *
+     * <p> The adapter can be used to populate a spinner that switches between the Settings
+     * app on the different profiles.
+     *
+     * @return a {@link UserAdapter} or null if there is only one profile.
+     */
+    public static UserAdapter createUserSpinnerAdapter(UserManager userManager,
+            Context context) {
+        List<UserHandle> userProfiles = userManager.getUserProfiles();
+        if (userProfiles.size() < 2) {
+            return null;
+        }
+
+        UserHandle myUserHandle = new UserHandle(UserHandle.myUserId());
+        // The first option should be the current profile
+        userProfiles.remove(myUserHandle);
+        userProfiles.add(0, myUserHandle);
+
+        return createUserAdapter(userManager, context, userProfiles);
+    }
+
+    public static UserAdapter createUserAdapter(UserManager userManager,
+            Context context, List<UserHandle> userProfiles) {
+        ArrayList<UserDetails> userDetails = new ArrayList<UserDetails>(userProfiles.size());
+        final int count = userProfiles.size();
+        for (int i = 0; i < count; i++) {
+            userDetails.add(new UserDetails(userProfiles.get(i), userManager, context));
+        }
+        return new UserAdapter(context, userDetails);
+    }
+}
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index 9441d88..1987214 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -168,24 +168,6 @@
         }
     }
 
-    public void requestConditions(IConditionListener callback, int relevance) {
-        synchronized(mMutex) {
-            if (DEBUG) Slog.d(TAG, "requestConditions callback=" + callback
-                    + " relevance=" + Condition.relevanceToString(relevance));
-            if (callback == null) return;
-            relevance = relevance & (Condition.FLAG_RELEVANT_NOW | Condition.FLAG_RELEVANT_ALWAYS);
-            if (relevance != 0) {
-                mListeners.put(callback.asBinder(), callback);
-                requestConditionsLocked(relevance);
-            } else {
-                mListeners.remove(callback.asBinder());
-                if (mListeners.isEmpty()) {
-                    requestConditionsLocked(0);
-                }
-            }
-        }
-    }
-
     private Condition[] validateConditions(String pkg, Condition[] conditions) {
         if (conditions == null || conditions.length == 0) return null;
         final int N = conditions.length;
@@ -382,25 +364,6 @@
         return info == null ? null : (IConditionProvider) info.service;
     }
 
-    private void requestConditionsLocked(int flags) {
-        for (ManagedServiceInfo info : mServices) {
-            final IConditionProvider provider = provider(info);
-            if (provider == null) continue;
-            // clear all stored conditions from this provider that we no longer care about
-            for (int i = mRecords.size() - 1; i >= 0; i--) {
-                final ConditionRecord r = mRecords.get(i);
-                if (r.info != info) continue;
-                if (r.subscribed) continue;
-                mRecords.remove(i);
-            }
-            try {
-                provider.onRequestConditions(flags);
-            } catch (RemoteException e) {
-                Slog.w(TAG, "Error requesting conditions from " + info.component, e);
-            }
-        }
-    }
-
     private static class ConditionRecord {
         public final Uri id;
         public final ComponentName component;
diff --git a/services/core/java/com/android/server/notification/ZenModeConditions.java b/services/core/java/com/android/server/notification/ZenModeConditions.java
index c2e4349..cee9ec8 100644
--- a/services/core/java/com/android/server/notification/ZenModeConditions.java
+++ b/services/core/java/com/android/server/notification/ZenModeConditions.java
@@ -59,10 +59,6 @@
         pw.print(prefix); pw.print("mSubscriptions="); pw.println(mSubscriptions);
     }
 
-    public void requestConditions(IConditionListener callback, int relevance) {
-        mConditionProviders.requestConditions(callback, relevance);
-    }
-
     public void evaluateConfig(ZenModeConfig config, boolean processSubscriptions) {
         if (config == null) return;
         if (config.manualRule != null && config.manualRule.condition != null