Merge "Bug fixes in the printer dialog activity and fused printer loader." into klp-dev
diff --git a/api/current.txt b/api/current.txt
index 7aea255..063f6da 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -13187,6 +13187,7 @@
     ctor public RemoteControlClient(android.app.PendingIntent);
     ctor public RemoteControlClient(android.app.PendingIntent, android.os.Looper);
     method public android.media.RemoteControlClient.MetadataEditor editMetadata(boolean);
+    method public void setMetadataUpdateListener(android.media.RemoteControlClient.OnMetadataUpdateListener);
     method public void setOnGetPlaybackPositionListener(android.media.RemoteControlClient.OnGetPlaybackPositionListener);
     method public void setPlaybackPositionUpdateListener(android.media.RemoteControlClient.OnPlaybackPositionUpdateListener);
     method public void setPlaybackState(int);
@@ -13199,6 +13200,7 @@
     field public static final int FLAG_KEY_MEDIA_PLAY_PAUSE = 8; // 0x8
     field public static final int FLAG_KEY_MEDIA_POSITION_UPDATE = 256; // 0x100
     field public static final int FLAG_KEY_MEDIA_PREVIOUS = 1; // 0x1
+    field public static final int FLAG_KEY_MEDIA_RATING = 512; // 0x200
     field public static final int FLAG_KEY_MEDIA_REWIND = 2; // 0x2
     field public static final int FLAG_KEY_MEDIA_STOP = 32; // 0x20
     field public static final int PLAYSTATE_BUFFERING = 8; // 0x8
@@ -13213,18 +13215,32 @@
   }
 
   public class RemoteControlClient.MetadataEditor {
+    method public synchronized void addEditableKey(int);
     method public synchronized void apply();
     method public synchronized void clear();
+    method public synchronized void clearEditableKeys();
     method public synchronized android.media.RemoteControlClient.MetadataEditor putBitmap(int, android.graphics.Bitmap) throws java.lang.IllegalArgumentException;
     method public synchronized android.media.RemoteControlClient.MetadataEditor putLong(int, long) throws java.lang.IllegalArgumentException;
     method public synchronized android.media.RemoteControlClient.MetadataEditor putString(int, java.lang.String) throws java.lang.IllegalArgumentException;
     field public static final int BITMAP_KEY_ARTWORK = 100; // 0x64
+    field public static final int LONG_KEY_RATING_BY_OTHERS = 102; // 0x66
+    field public static final int LONG_KEY_RATING_BY_USER = 268435457; // 0x10000001
+    field public static final int LONG_KEY_RATING_TYPE = 101; // 0x65
+    field public static final long RATING_HEART = -1L; // 0xffffffffffffffffL
+    field public static final long RATING_NOT_RATED = -101L; // 0xffffffffffffff9bL
+    field public static final long RATING_THUMB_UP_DOWN = -2L; // 0xfffffffffffffffeL
   }
 
   public static abstract interface RemoteControlClient.OnGetPlaybackPositionListener {
     method public abstract long onGetPlaybackPosition();
   }
 
+  public static abstract interface RemoteControlClient.OnMetadataUpdateListener {
+    method public abstract void onMetadataUpdateBitmap(int, android.graphics.Bitmap);
+    method public abstract void onMetadataUpdateLong(int, long);
+    method public abstract void onMetadataUpdateString(int, java.lang.String);
+  }
+
   public static abstract interface RemoteControlClient.OnPlaybackPositionUpdateListener {
     method public abstract void onPlaybackPositionUpdate(long);
   }
@@ -15248,12 +15264,12 @@
 
 package android.nfc.cardemulation {
 
-  public final class CardEmulationManager {
-    method public static synchronized android.nfc.cardemulation.CardEmulationManager getInstance(android.nfc.NfcAdapter);
+  public final class CardEmulation {
+    method public static synchronized android.nfc.cardemulation.CardEmulation getInstance(android.nfc.NfcAdapter);
     method public int getSelectionModeForCategory(java.lang.String);
     method public boolean isDefaultServiceForAid(android.content.ComponentName, java.lang.String);
     method public boolean isDefaultServiceForCategory(android.content.ComponentName, java.lang.String);
-    field public static final java.lang.String ACTION_CHANGE_DEFAULT = "android.nfc.cardemulation.ACTION_CHANGE_DEFAULT";
+    field public static final java.lang.String ACTION_CHANGE_DEFAULT = "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT";
     field public static final java.lang.String CATEGORY_OTHER = "other";
     field public static final java.lang.String CATEGORY_PAYMENT = "payment";
     field public static final java.lang.String EXTRA_CATEGORY = "category";
@@ -15273,15 +15289,15 @@
     method public final void sendResponseApdu(byte[]);
     field public static final int DEACTIVATION_DESELECTED = 1; // 0x1
     field public static final int DEACTIVATION_LINK_LOSS = 0; // 0x0
-    field public static final java.lang.String SERVICE_INTERFACE = "android.nfc.HostApduService";
-    field public static final java.lang.String SERVICE_META_DATA = "android.nfc.HostApduService";
+    field public static final java.lang.String SERVICE_INTERFACE = "android.nfc.cardemulation.action.HOST_APDU_SERVICE";
+    field public static final java.lang.String SERVICE_META_DATA = "android.nfc.cardemulation.host_apdu_service";
   }
 
   public abstract class OffHostApduService extends android.app.Service {
     ctor public OffHostApduService();
     method public abstract android.os.IBinder onBind(android.content.Intent);
-    field public static final java.lang.String SERVICE_INTERFACE = "android.nfc.OffHostApduService";
-    field public static final java.lang.String SERVICE_META_DATA = "android.nfc.OffHostApduService";
+    field public static final java.lang.String SERVICE_INTERFACE = "android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE";
+    field public static final java.lang.String SERVICE_META_DATA = "android.nfc.cardemulation.off_host_apdu_service";
   }
 
 }
@@ -20815,67 +20831,70 @@
   }
 
   public final class DocumentsContract {
+    method public static android.net.Uri buildChildDocumentsUri(java.lang.String, java.lang.String);
     method public static android.net.Uri buildDocumentUri(java.lang.String, java.lang.String);
-    method public static java.lang.String getDocId(android.net.Uri);
+    method public static android.net.Uri buildRecentDocumentsUri(java.lang.String, java.lang.String);
+    method public static android.net.Uri buildRootsUri(java.lang.String);
+    method public static android.net.Uri buildSearchDocumentsUri(java.lang.String, java.lang.String, java.lang.String);
+    method public static android.net.Uri createDocument(android.content.ContentResolver, android.net.Uri, java.lang.String, java.lang.String);
+    method public static boolean deleteDocument(android.content.ContentResolver, android.net.Uri);
+    method public static java.lang.String getDocumentId(android.net.Uri);
+    method public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentResolver, android.net.Uri, android.graphics.Point, android.os.CancellationSignal);
     method public static android.net.Uri[] getOpenDocuments(android.content.Context);
+    method public static java.lang.String getRootId(android.net.Uri);
+    method public static java.lang.String getSearchDocumentsQuery(android.net.Uri);
     field public static final java.lang.String EXTRA_ERROR = "error";
     field public static final java.lang.String EXTRA_INFO = "info";
     field public static final java.lang.String EXTRA_LOADING = "loading";
   }
 
-  public static abstract interface DocumentsContract.DocumentColumns implements android.provider.OpenableColumns {
-    field public static final java.lang.String DOC_ID = "doc_id";
-    field public static final java.lang.String FLAGS = "flags";
-    field public static final java.lang.String ICON = "icon";
-    field public static final java.lang.String LAST_MODIFIED = "last_modified";
-    field public static final java.lang.String MIME_TYPE = "mime_type";
-    field public static final java.lang.String SUMMARY = "summary";
+  public static final class DocumentsContract.Document {
+    field public static final java.lang.String COLUMN_DISPLAY_NAME = "_display_name";
+    field public static final java.lang.String COLUMN_DOCUMENT_ID = "document_id";
+    field public static final java.lang.String COLUMN_FLAGS = "flags";
+    field public static final java.lang.String COLUMN_ICON = "icon";
+    field public static final java.lang.String COLUMN_LAST_MODIFIED = "last_modified";
+    field public static final java.lang.String COLUMN_MIME_TYPE = "mime_type";
+    field public static final java.lang.String COLUMN_SIZE = "_size";
+    field public static final java.lang.String COLUMN_SUMMARY = "summary";
+    field public static final int FLAG_DIR_PREFERS_GRID = 32; // 0x20
+    field public static final int FLAG_DIR_SUPPORTS_CREATE = 8; // 0x8
+    field public static final int FLAG_DIR_SUPPORTS_SEARCH = 16; // 0x10
+    field public static final int FLAG_SUPPORTS_DELETE = 4; // 0x4
+    field public static final int FLAG_SUPPORTS_THUMBNAIL = 1; // 0x1
+    field public static final int FLAG_SUPPORTS_WRITE = 2; // 0x2
+    field public static final java.lang.String MIME_TYPE_DIR = "vnd.android.document/directory";
   }
 
-  public static final class DocumentsContract.DocumentRoot implements android.os.Parcelable {
-    ctor public DocumentsContract.DocumentRoot();
-    method public int describeContents();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator CREATOR;
+  public static final class DocumentsContract.Root {
+    field public static final java.lang.String COLUMN_AVAILABLE_BYTES = "available_bytes";
+    field public static final java.lang.String COLUMN_DOCUMENT_ID = "document_id";
+    field public static final java.lang.String COLUMN_FLAGS = "flags";
+    field public static final java.lang.String COLUMN_ICON = "icon";
+    field public static final java.lang.String COLUMN_ROOT_ID = "root_id";
+    field public static final java.lang.String COLUMN_ROOT_TYPE = "root_type";
+    field public static final java.lang.String COLUMN_SUMMARY = "summary";
+    field public static final java.lang.String COLUMN_TITLE = "title";
+    field public static final int FLAG_ADVANCED = 4; // 0x4
     field public static final int FLAG_LOCAL_ONLY = 2; // 0x2
+    field public static final int FLAG_PROVIDES_AUDIO = 8; // 0x8
+    field public static final int FLAG_PROVIDES_IMAGES = 32; // 0x20
+    field public static final int FLAG_PROVIDES_VIDEO = 16; // 0x10
     field public static final int FLAG_SUPPORTS_CREATE = 1; // 0x1
+    field public static final int FLAG_SUPPORTS_RECENTS = 64; // 0x40
     field public static final int ROOT_TYPE_DEVICE = 3; // 0x3
-    field public static final int ROOT_TYPE_DEVICE_ADVANCED = 4; // 0x4
     field public static final int ROOT_TYPE_SERVICE = 1; // 0x1
     field public static final int ROOT_TYPE_SHORTCUT = 2; // 0x2
-    field public long availableBytes;
-    field public java.lang.String docId;
-    field public int flags;
-    field public int icon;
-    field public java.lang.String[] mimeTypes;
-    field public java.lang.String recentDocId;
-    field public int rootType;
-    field public java.lang.String summary;
-    field public java.lang.String title;
-  }
-
-  public static final class DocumentsContract.Documents {
-    field public static final int FLAG_PREFERS_GRID = 64; // 0x40
-    field public static final int FLAG_SUPPORTS_CREATE = 1; // 0x1
-    field public static final int FLAG_SUPPORTS_DELETE = 4; // 0x4
-    field public static final int FLAG_SUPPORTS_RENAME = 2; // 0x2
-    field public static final int FLAG_SUPPORTS_SEARCH = 16; // 0x10
-    field public static final int FLAG_SUPPORTS_THUMBNAIL = 8; // 0x8
-    field public static final int FLAG_SUPPORTS_WRITE = 32; // 0x20
-    field public static final java.lang.String MIME_TYPE_DIR = "vnd.android.doc/dir";
   }
 
   public abstract class DocumentsProvider extends android.content.ContentProvider {
     ctor public DocumentsProvider();
-    method public final android.os.Bundle callFromPackage(java.lang.String, java.lang.String, java.lang.String, android.os.Bundle);
     method public java.lang.String createDocument(java.lang.String, java.lang.String, java.lang.String) throws java.io.FileNotFoundException;
     method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]);
     method public void deleteDocument(java.lang.String) throws java.io.FileNotFoundException;
-    method public abstract java.util.List<android.provider.DocumentsContract.DocumentRoot> getDocumentRoots();
-    method public java.lang.String getType(java.lang.String) throws java.io.FileNotFoundException;
+    method public java.lang.String getDocumentType(java.lang.String) throws java.io.FileNotFoundException;
     method public final java.lang.String getType(android.net.Uri);
     method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues);
-    method public void notifyDocumentRootsChanged();
     method public abstract android.os.ParcelFileDescriptor openDocument(java.lang.String, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException;
     method public android.content.res.AssetFileDescriptor openDocumentThumbnail(java.lang.String, android.graphics.Point, android.os.CancellationSignal) throws java.io.FileNotFoundException;
     method public final android.os.ParcelFileDescriptor openFile(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException;
@@ -20883,10 +20902,11 @@
     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 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 queryDocument(java.lang.String) throws java.io.FileNotFoundException;
-    method public abstract android.database.Cursor queryDocumentChildren(java.lang.String) throws java.io.FileNotFoundException;
-    method public android.database.Cursor querySearch(java.lang.String, java.lang.String) throws java.io.FileNotFoundException;
-    method public void renameDocument(java.lang.String, java.lang.String) throws java.io.FileNotFoundException;
+    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;
+    method public android.database.Cursor queryRecentDocuments(java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException;
+    method public abstract android.database.Cursor queryRoots(java.lang.String[]) throws java.io.FileNotFoundException;
+    method public android.database.Cursor querySearchDocuments(java.lang.String, java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException;
     method public final int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]);
   }
 
@@ -22573,8 +22593,11 @@
   }
 
   public final class KeyPairGeneratorSpec implements java.security.spec.AlgorithmParameterSpec {
+    method public java.security.spec.AlgorithmParameterSpec getAlgorithmParameterSpec();
     method public android.content.Context getContext();
     method public java.util.Date getEndDate();
+    method public int getKeySize();
+    method public java.lang.String getKeyType();
     method public java.lang.String getKeystoreAlias();
     method public java.math.BigInteger getSerialNumber();
     method public java.util.Date getStartDate();
@@ -22585,9 +22608,12 @@
   public static final class KeyPairGeneratorSpec.Builder {
     ctor public KeyPairGeneratorSpec.Builder(android.content.Context);
     method public android.security.KeyPairGeneratorSpec build();
+    method public android.security.KeyPairGeneratorSpec.Builder setAlgorithmParameterSpec(java.security.spec.AlgorithmParameterSpec);
     method public android.security.KeyPairGeneratorSpec.Builder setAlias(java.lang.String);
     method public android.security.KeyPairGeneratorSpec.Builder setEncryptionRequired();
     method public android.security.KeyPairGeneratorSpec.Builder setEndDate(java.util.Date);
+    method public android.security.KeyPairGeneratorSpec.Builder setKeySize(int);
+    method public android.security.KeyPairGeneratorSpec.Builder setKeyType(java.lang.String) throws java.security.NoSuchAlgorithmException;
     method public android.security.KeyPairGeneratorSpec.Builder setSerialNumber(java.math.BigInteger);
     method public android.security.KeyPairGeneratorSpec.Builder setStartDate(java.util.Date);
     method public android.security.KeyPairGeneratorSpec.Builder setSubject(javax.security.auth.x500.X500Principal);
@@ -27149,6 +27175,7 @@
     method public long getTimeDelta();
     method public boolean isInProgress();
     method public boolean onTouchEvent(android.view.MotionEvent);
+    method public void setQuickScaleEnabled(boolean);
   }
 
   public static abstract interface ScaleGestureDetector.OnScaleGestureListener {
diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
index b83911a..41c6603 100644
--- a/core/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -105,8 +105,12 @@
             if (onHost) {
                 parser = si.loadXmlMetaData(pm, HostApduService.SERVICE_META_DATA);
                 if (parser == null) {
-                    throw new XmlPullParserException("No " + HostApduService.SERVICE_META_DATA +
-                            " meta-data");
+                    Log.d(TAG, "Didn't find service meta-data, trying legacy.");
+                    parser = si.loadXmlMetaData(pm, HostApduService.OLD_SERVICE_META_DATA);
+                    if (parser == null) {
+                        throw new XmlPullParserException("No " + HostApduService.SERVICE_META_DATA +
+                                " meta-data");
+                    }
                 }
             } else {
                 parser = si.loadXmlMetaData(pm, OffHostApduService.SERVICE_META_DATA);
@@ -170,12 +174,12 @@
                             com.android.internal.R.styleable.AidGroup_description);
                     String groupCategory = groupAttrs.getString(
                             com.android.internal.R.styleable.AidGroup_category);
-                    if (!CardEmulationManager.CATEGORY_PAYMENT.equals(groupCategory)) {
-                        groupCategory = CardEmulationManager.CATEGORY_OTHER;
+                    if (!CardEmulation.CATEGORY_PAYMENT.equals(groupCategory)) {
+                        groupCategory = CardEmulation.CATEGORY_OTHER;
                     }
                     currentGroup = mCategoryToGroup.get(groupCategory);
                     if (currentGroup != null) {
-                        if (!CardEmulationManager.CATEGORY_OTHER.equals(groupCategory)) {
+                        if (!CardEmulation.CATEGORY_OTHER.equals(groupCategory)) {
                             Log.e(TAG, "Not allowing multiple aid-groups in the " +
                                     groupCategory + " category");
                             currentGroup = null;
diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java
new file mode 100644
index 0000000..3cd7863
--- /dev/null
+++ b/core/java/android/nfc/cardemulation/CardEmulation.java
@@ -0,0 +1,343 @@
+/*
+ * 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 android.nfc.cardemulation;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.app.ActivityThread;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.nfc.INfcCardEmulation;
+import android.nfc.NfcAdapter;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.List;
+
+public final class CardEmulation {
+    static final String TAG = "CardEmulation";
+
+    /**
+     * Activity action: ask the user to change the default
+     * card emulation service for a certain category. This will
+     * show a dialog that asks the user whether he wants to
+     * replace the current default service with the service
+     * identified with the ComponentName specified in
+     * {@link #EXTRA_SERVICE_COMPONENT}, for the category
+     * specified in {@link #EXTRA_CATEGORY}
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_CHANGE_DEFAULT =
+            "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT";
+
+    /**
+     * The category extra for {@link #ACTION_CHANGE_DEFAULT}
+     *
+     * @see #ACTION_CHANGE_DEFAULT
+     */
+    public static final String EXTRA_CATEGORY = "category";
+
+    /**
+     * The ComponentName object passed in as a parcelable
+     * extra for {@link #ACTION_CHANGE_DEFAULT}
+     *
+     * @see #ACTION_CHANGE_DEFAULT
+     */
+    public static final String EXTRA_SERVICE_COMPONENT = "component";
+
+    /**
+     * The payment category can be used to indicate that an AID
+     * represents a payment application.
+     */
+    public static final String CATEGORY_PAYMENT = "payment";
+
+    /**
+     * If an AID group does not contain a category, or the
+     * specified category is not defined by the platform version
+     * that is parsing the AID group, all AIDs in the group will
+     * automatically be categorized under the {@link #CATEGORY_OTHER}
+     * category.
+     */
+    public static final String CATEGORY_OTHER = "other";
+
+    /**
+     * Return value for {@link #getSelectionModeForCategory(String)}.
+     *
+     * <p>In this mode, the user has set a default service for this
+     *    AID category. If a remote reader selects any of the AIDs
+     *    that the default service has registered in this category,
+     *    that service will automatically be bound to to handle
+     *    the transaction.
+     *
+     * <p>There are still cases where a service that is
+     *    not the default for a category can selected:
+     *    <p>
+     *    If a remote reader selects an AID in this category
+     *    that is not handled by the default service, and there is a set
+     *    of other services {S} that do handle this AID, the
+     *    user is asked if he wants to use any of the services in
+     *    {S} instead.
+     *    <p>
+     *    As a special case, if the size of {S} is one, containing a single service X,
+     *    and all AIDs X has registered in this category are not
+     *    registered by any other service, then X will be
+     *    selected automatically without asking the user.
+     *    <p>Example:
+     *    <ul>
+     *    <li>Service A registers AIDs "1", "2" and "3" in the category
+     *    <li>Service B registers AIDs "3" and "4" in the category
+     *    <li>Service C registers AIDs "5" and "6" in the category
+     *    </ul>
+     *    In this case, the following will happen when service A
+     *    is the default:
+     *    <ul>
+     *    <li>Reader selects AID "1", "2" or "3": service A is invoked automatically
+     *    <li>Reader selects AID "4": the user is asked to confirm he
+     *        wants to use service B, because its AIDs overlap with service A.
+     *    <li>Reader selects AID "5" or "6": service C is invoked automatically,
+     *        because all AIDs it has asked for are only registered by C,
+     *        and there is no overlap.
+     *    </ul>
+     *
+     */
+    public static final int SELECTION_MODE_PREFER_DEFAULT = 0;
+
+    /**
+     * Return value for {@link #getSelectionModeForCategory(String)}.
+     *
+     * <p>In this mode, whenever an AID of this category is selected,
+     *    the user is asked which service he wants to use to handle
+     *    the transaction, even if there is only one matching service.
+     */
+    public static final int SELECTION_MODE_ALWAYS_ASK = 1;
+
+    /**
+     * Return value for {@link #getSelectionModeForCategory(String)}.
+     *
+     * <p>In this mode, the user will only be asked to select a service
+     *    if the selected AID has been registered by multiple applications.
+     */
+    public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2;
+
+    static boolean sIsInitialized = false;
+    static HashMap<Context, CardEmulation> sCardEmus = new HashMap();
+    static INfcCardEmulation sService;
+
+    final Context mContext;
+
+    private CardEmulation(Context context, INfcCardEmulation service) {
+        mContext = context.getApplicationContext();
+        sService = service;
+    }
+
+    public static synchronized CardEmulation getInstance(NfcAdapter adapter) {
+        if (adapter == null) throw new NullPointerException("NfcAdapter is null");
+        Context context = adapter.getContext();
+        if (context == null) {
+            Log.e(TAG, "NfcAdapter context is null.");
+            throw new UnsupportedOperationException();
+        }
+        if (!sIsInitialized) {
+            IPackageManager pm = ActivityThread.getPackageManager();
+            if (pm == null) {
+                Log.e(TAG, "Cannot get PackageManager");
+                throw new UnsupportedOperationException();
+            }
+            try {
+                if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
+                    Log.e(TAG, "This device does not support card emulation");
+                    throw new UnsupportedOperationException();
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "PackageManager query failed.");
+                throw new UnsupportedOperationException();
+            }
+            sIsInitialized = true;
+        }
+        CardEmulation manager = sCardEmus.get(context);
+        if (manager == null) {
+            // Get card emu service
+            INfcCardEmulation service = adapter.getCardEmulationService();
+            manager = new CardEmulation(context, service);
+            sCardEmus.put(context, manager);
+        }
+        return manager;
+    }
+
+    /**
+     * Allows an application to query whether a service is currently
+     * the default service to handle a card emulation category.
+     *
+     * <p>Note that if {@link #getSelectionModeForCategory(String)}
+     * returns {@link #SELECTION_MODE_ALWAYS_ASK}, this method will always
+     * return false.
+     *
+     * @param service The ComponentName of the service
+     * @param category The category
+     * @return whether service is currently the default service for the category.
+     */
+    public boolean isDefaultServiceForCategory(ComponentName service, String category) {
+        try {
+            return sService.isDefaultServiceForCategory(UserHandle.myUserId(), service, category);
+        } catch (RemoteException e) {
+            // Try one more time
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return false;
+            }
+            try {
+                return sService.isDefaultServiceForCategory(UserHandle.myUserId(), service,
+                        category);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return false;
+            }
+        }
+    }
+
+    /**
+     *
+     * Allows an application to query whether a service is currently
+     * the default handler for a specified ISO7816-4 Application ID.
+     *
+     * @param service The ComponentName of the service
+     * @param aid The ISO7816-4 Application ID
+     * @return
+     */
+    public boolean isDefaultServiceForAid(ComponentName service, String aid) {
+        try {
+            return sService.isDefaultServiceForAid(UserHandle.myUserId(), service, aid);
+        } catch (RemoteException e) {
+            // Try one more time
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return false;
+            }
+            try {
+                return sService.isDefaultServiceForAid(UserHandle.myUserId(), service, aid);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to reach CardEmulationService.");
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Returns the application selection mode for the passed in category.
+     * Valid return values are:
+     * <p>{@link #SELECTION_MODE_PREFER_DEFAULT} the user has requested a default
+     *    application for this category, which will be preferred.
+     * <p>{@link #SELECTION_MODE_ALWAYS_ASK} the user has requested to be asked
+     *    every time what app he would like to use in this category.
+     * <p>{@link #SELECTION_MODE_ASK_IF_CONFLICT} the user will only be asked
+     *    to pick a service if there is a conflict.
+     * @param category The category, for example {@link #CATEGORY_PAYMENT}
+     * @return
+     */
+    public int getSelectionModeForCategory(String category) {
+        if (CATEGORY_PAYMENT.equals(category)) {
+            String defaultComponent = Settings.Secure.getString(mContext.getContentResolver(),
+                    Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT);
+            if (defaultComponent != null) {
+                return SELECTION_MODE_PREFER_DEFAULT;
+            } else {
+                return SELECTION_MODE_ALWAYS_ASK;
+            }
+        } else {
+            // All other categories are in "only ask if conflict" mode
+            return SELECTION_MODE_ASK_IF_CONFLICT;
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public boolean setDefaultServiceForCategory(ComponentName service, String category) {
+        try {
+            return sService.setDefaultServiceForCategory(UserHandle.myUserId(), service, category);
+        } catch (RemoteException e) {
+            // Try one more time
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return false;
+            }
+            try {
+                return sService.setDefaultServiceForCategory(UserHandle.myUserId(), service,
+                        category);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to reach CardEmulationService.");
+                return false;
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public boolean setDefaultForNextTap(ComponentName service) {
+        try {
+            return sService.setDefaultForNextTap(UserHandle.myUserId(), service);
+        } catch (RemoteException e) {
+            // Try one more time
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return false;
+            }
+            try {
+                return sService.setDefaultForNextTap(UserHandle.myUserId(), service);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to reach CardEmulationService.");
+                return false;
+            }
+        }
+    }
+    /**
+     * @hide
+     */
+    public List<ApduServiceInfo> getServices(String category) {
+        try {
+            return sService.getServices(UserHandle.myUserId(), category);
+        } catch (RemoteException e) {
+            // Try one more time
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return null;
+            }
+            try {
+                return sService.getServices(UserHandle.myUserId(), category);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to reach CardEmulationService.");
+                return null;
+            }
+        }
+    }
+
+    void recoverService() {
+        NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
+        sService = adapter.getCardEmulationService();
+    }
+}
diff --git a/core/java/android/nfc/cardemulation/CardEmulationManager.java b/core/java/android/nfc/cardemulation/CardEmulationManager.java
index 9d60c73..124ea1c 100644
--- a/core/java/android/nfc/cardemulation/CardEmulationManager.java
+++ b/core/java/android/nfc/cardemulation/CardEmulationManager.java
@@ -33,6 +33,10 @@
 import java.util.HashMap;
 import java.util.List;
 
+/**
+ * TODO Remove when calling .apks are upgraded
+ * @hide
+ */
 public final class CardEmulationManager {
     static final String TAG = "CardEmulationManager";
 
diff --git a/core/java/android/nfc/cardemulation/HostApduService.java b/core/java/android/nfc/cardemulation/HostApduService.java
index ae94b2f..1bb2ea4 100644
--- a/core/java/android/nfc/cardemulation/HostApduService.java
+++ b/core/java/android/nfc/cardemulation/HostApduService.java
@@ -40,13 +40,31 @@
      */
     @SdkConstant(SdkConstantType.SERVICE_ACTION)
     public static final String SERVICE_INTERFACE =
-            "android.nfc.HostApduService";
+            "android.nfc.cardemulation.action.HOST_APDU_SERVICE";
 
     /**
      * The name of the meta-data element that contains
      * more information about this service.
      */
-    public static final String SERVICE_META_DATA = "android.nfc.HostApduService";
+    public static final String SERVICE_META_DATA =
+            "android.nfc.cardemulation.host_apdu_service";
+
+    /**
+     * The {@link Intent} that must be declared as handled by the service.
+     * TODO Remove
+     * @hide
+     */
+    public static final String OLD_SERVICE_INTERFACE =
+            "android.nfc.HostApduService";
+
+    /**
+     * The name of the meta-data element that contains
+     * more information about this service.
+     *
+     * TODO Remove
+     * @hide
+     */
+    public static final String OLD_SERVICE_META_DATA = "android.nfc.HostApduService";
 
     /**
      * Reason for {@link #onDeactivated(int)}.
diff --git a/core/java/android/nfc/cardemulation/OffHostApduService.java b/core/java/android/nfc/cardemulation/OffHostApduService.java
index 79599db..15f63f9 100644
--- a/core/java/android/nfc/cardemulation/OffHostApduService.java
+++ b/core/java/android/nfc/cardemulation/OffHostApduService.java
@@ -42,13 +42,14 @@
      */
     @SdkConstant(SdkConstantType.SERVICE_ACTION)
     public static final String SERVICE_INTERFACE =
-            "android.nfc.OffHostApduService";
+            "android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE";
 
     /**
      * The name of the meta-data element that contains
      * more information about this service.
      */
-    public static final String SERVICE_META_DATA = "android.nfc.OffHostApduService";
+    public static final String SERVICE_META_DATA =
+            "android.nfc.cardemulation.off_host_apdu_service";
 
     /**
      * The Android platform itself will not bind to this service,
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index ebb7eb8..f445fd5 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -19,7 +19,6 @@
 import static android.net.TrafficStats.KB_IN_BYTES;
 import static libcore.io.OsConstants.SEEK_SET;
 
-import android.content.ContentProviderClient;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
@@ -30,16 +29,13 @@
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Point;
-import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Bundle;
-import android.os.Parcel;
+import android.os.CancellationSignal;
 import android.os.ParcelFileDescriptor;
 import android.os.ParcelFileDescriptor.OnCloseListener;
-import android.os.Parcelable;
 import android.util.Log;
 
-import com.android.internal.util.Preconditions;
 import com.google.android.collect.Lists;
 
 import libcore.io.ErrnoException;
@@ -62,9 +58,12 @@
 public final class DocumentsContract {
     private static final String TAG = "Documents";
 
-    // content://com.example/docs/12/
-    // content://com.example/docs/12/children/
-    // content://com.example/docs/12/search/?query=pony
+    // content://com.example/root/
+    // content://com.example/root/sdcard/
+    // content://com.example/root/sdcard/recent/
+    // content://com.example/document/12/
+    // content://com.example/document/12/children/
+    // content://com.example/document/12/search/?query=pony
 
     private DocumentsContract() {
     }
@@ -75,207 +74,69 @@
     /** {@hide} */
     public static final String ACTION_MANAGE_DOCUMENTS = "android.provider.action.MANAGE_DOCUMENTS";
 
-    /** {@hide} */
-    public static final String
-            ACTION_DOCUMENT_ROOT_CHANGED = "android.provider.action.DOCUMENT_ROOT_CHANGED";
-
     /**
-     * Constants for individual documents.
+     * Constants related to a document, including {@link Cursor} columns names
+     * and flags.
+     * <p>
+     * A document can be either an openable file (with a specific MIME type), or
+     * a directory containing additional documents (with the
+     * {@link #MIME_TYPE_DIR} MIME type).
+     * <p>
+     * All columns are <em>read-only</em> to client applications.
      */
-    public final static class Documents {
-        private Documents() {
+    public final static class Document {
+        private Document() {
         }
 
         /**
-         * MIME type of a document which is a directory that may contain additional
-         * documents.
-         */
-        public static final String MIME_TYPE_DIR = "vnd.android.doc/dir";
-
-        /**
-         * Flag indicating that a document is a directory that supports creation of
-         * new files within it.
-         *
-         * @see DocumentColumns#FLAGS
-         */
-        public static final int FLAG_SUPPORTS_CREATE = 1;
-
-        /**
-         * Flag indicating that a document is renamable.
-         *
-         * @see DocumentColumns#FLAGS
-         */
-        public static final int FLAG_SUPPORTS_RENAME = 1 << 1;
-
-        /**
-         * Flag indicating that a document is deletable.
-         *
-         * @see DocumentColumns#FLAGS
-         */
-        public static final int FLAG_SUPPORTS_DELETE = 1 << 2;
-
-        /**
-         * Flag indicating that a document can be represented as a thumbnail.
-         *
-         * @see DocumentColumns#FLAGS
-         */
-        public static final int FLAG_SUPPORTS_THUMBNAIL = 1 << 3;
-
-        /**
-         * Flag indicating that a document is a directory that supports search.
-         *
-         * @see DocumentColumns#FLAGS
-         */
-        public static final int FLAG_SUPPORTS_SEARCH = 1 << 4;
-
-        /**
-         * Flag indicating that a document supports writing.
-         *
-         * @see DocumentColumns#FLAGS
-         */
-        public static final int FLAG_SUPPORTS_WRITE = 1 << 5;
-
-        /**
-         * Flag indicating that a document is a directory that prefers its contents
-         * be shown in a larger format grid. Usually suitable when a directory
-         * contains mostly pictures.
-         *
-         * @see DocumentColumns#FLAGS
-         */
-        public static final int FLAG_PREFERS_GRID = 1 << 6;
-    }
-
-    /**
-     * Extra boolean flag included in a directory {@link Cursor#getExtras()}
-     * indicating that a document provider is still loading data. For example, a
-     * provider has returned some results, but is still waiting on an
-     * outstanding network request.
-     *
-     * @see ContentResolver#notifyChange(Uri, android.database.ContentObserver,
-     *      boolean)
-     */
-    public static final String EXTRA_LOADING = "loading";
-
-    /**
-     * Extra string included in a directory {@link Cursor#getExtras()}
-     * providing an informational message that should be shown to a user. For
-     * example, a provider may wish to indicate that not all documents are
-     * available.
-     */
-    public static final String EXTRA_INFO = "info";
-
-    /**
-     * Extra string included in a directory {@link Cursor#getExtras()} providing
-     * an error message that should be shown to a user. For example, a provider
-     * may wish to indicate that a network error occurred. The user may choose
-     * to retry, resulting in a new query.
-     */
-    public static final String EXTRA_ERROR = "error";
-
-    /** {@hide} */
-    public static final String METHOD_GET_ROOTS = "android:getRoots";
-    /** {@hide} */
-    public static final String METHOD_CREATE_DOCUMENT = "android:createDocument";
-    /** {@hide} */
-    public static final String METHOD_RENAME_DOCUMENT = "android:renameDocument";
-    /** {@hide} */
-    public static final String METHOD_DELETE_DOCUMENT = "android:deleteDocument";
-
-    /** {@hide} */
-    public static final String EXTRA_AUTHORITY = "authority";
-    /** {@hide} */
-    public static final String EXTRA_PACKAGE_NAME = "packageName";
-    /** {@hide} */
-    public static final String EXTRA_URI = "uri";
-    /** {@hide} */
-    public static final String EXTRA_ROOTS = "roots";
-    /** {@hide} */
-    public static final String EXTRA_THUMBNAIL_SIZE = "thumbnail_size";
-
-    private static final String PATH_DOCS = "docs";
-    private static final String PATH_CHILDREN = "children";
-    private static final String PATH_SEARCH = "search";
-
-    private static final String PARAM_QUERY = "query";
-
-    /**
-     * Build Uri representing the given {@link DocumentColumns#DOC_ID} in a
-     * document provider.
-     */
-    public static Uri buildDocumentUri(String authority, String docId) {
-        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
-                .authority(authority).appendPath(PATH_DOCS).appendPath(docId).build();
-    }
-
-    /**
-     * Build Uri representing the contents of the given directory in a document
-     * provider. The given document must be {@link Documents#MIME_TYPE_DIR}.
-     *
-     * @hide
-     */
-    public static Uri buildChildrenUri(String authority, String docId) {
-        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
-                .appendPath(PATH_DOCS).appendPath(docId).appendPath(PATH_CHILDREN).build();
-    }
-
-    /**
-     * Build Uri representing a search for matching documents under a specific
-     * directory in a document provider. The given document must have
-     * {@link Documents#FLAG_SUPPORTS_SEARCH}.
-     *
-     * @hide
-     */
-    public static Uri buildSearchUri(String authority, String docId, String query) {
-        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
-                .appendPath(PATH_DOCS).appendPath(docId).appendPath(PATH_SEARCH)
-                .appendQueryParameter(PARAM_QUERY, query).build();
-    }
-
-    /**
-     * Extract the {@link DocumentColumns#DOC_ID} from the given Uri.
-     */
-    public static String getDocId(Uri documentUri) {
-        final List<String> paths = documentUri.getPathSegments();
-        if (paths.size() < 2) {
-            throw new IllegalArgumentException("Not a document: " + documentUri);
-        }
-        if (!PATH_DOCS.equals(paths.get(0))) {
-            throw new IllegalArgumentException("Not a document: " + documentUri);
-        }
-        return paths.get(1);
-    }
-
-    /** {@hide} */
-    public static String getSearchQuery(Uri documentUri) {
-        return documentUri.getQueryParameter(PARAM_QUERY);
-    }
-
-    /**
-     * Standard columns for document queries. Document providers <em>must</em>
-     * support at least these columns when queried.
-     */
-    public interface DocumentColumns extends OpenableColumns {
-        /**
-         * Unique ID for a document. Values <em>must</em> never change once
-         * returned, since they may used for long-term Uri permission grants.
+         * Unique ID of a document. This ID is both provided by and interpreted
+         * by a {@link DocumentsProvider}, and should be treated as an opaque
+         * value by client applications.
+         * <p>
+         * Each document must have a unique ID within a provider, but that
+         * single document may be included as a child of multiple directories.
+         * <p>
+         * A provider must always return durable IDs, since they will be used to
+         * issue long-term Uri permission grants when an application interacts
+         * with {@link Intent#ACTION_OPEN_DOCUMENT} and
+         * {@link Intent#ACTION_CREATE_DOCUMENT}.
          * <p>
          * Type: STRING
          */
-        public static final String DOC_ID = "doc_id";
+        public static final String COLUMN_DOCUMENT_ID = "document_id";
 
         /**
-         * MIME type of a document.
+         * Concrete MIME type of a document. For example, "image/png" or
+         * "application/pdf" for openable files. A document can also be a
+         * directory containing additional documents, which is represented with
+         * the {@link #MIME_TYPE_DIR} MIME type.
          * <p>
          * Type: STRING
          *
-         * @see Documents#MIME_TYPE_DIR
+         * @see #MIME_TYPE_DIR
          */
-        public static final String MIME_TYPE = "mime_type";
+        public static final String COLUMN_MIME_TYPE = "mime_type";
+
+        /**
+         * Display name of a document, used as the primary title displayed to a
+         * user.
+         * <p>
+         * Type: STRING
+         */
+        public static final String COLUMN_DISPLAY_NAME = OpenableColumns.DISPLAY_NAME;
+
+        /**
+         * Summary of a document, which may be shown to a user. The summary may
+         * be {@code null}.
+         * <p>
+         * Type: STRING
+         */
+        public static final String COLUMN_SUMMARY = "summary";
 
         /**
          * Timestamp when a document was last modified, in milliseconds since
-         * January 1, 1970 00:00:00.0 UTC, or {@code null} if unknown. Document
-         * providers can update this field using events from
+         * January 1, 1970 00:00:00.0 UTC, or {@code null} if unknown. A
+         * {@link DocumentsProvider} can update this field using events from
          * {@link OnCloseListener} or other reliable
          * {@link ParcelFileDescriptor} transports.
          * <p>
@@ -283,71 +144,227 @@
          *
          * @see System#currentTimeMillis()
          */
-        public static final String LAST_MODIFIED = "last_modified";
+        public static final String COLUMN_LAST_MODIFIED = "last_modified";
 
         /**
-         * Specific icon resource for a document, or {@code null} to resolve
-         * default using {@link #MIME_TYPE}.
+         * Specific icon resource ID for a document, or {@code null} to use
+         * platform default icon based on {@link #COLUMN_MIME_TYPE}.
          * <p>
          * Type: INTEGER (int)
          */
-        public static final String ICON = "icon";
+        public static final String COLUMN_ICON = "icon";
 
         /**
-         * Summary for a document, or {@code null} to omit.
-         * <p>
-         * Type: STRING
-         */
-        public static final String SUMMARY = "summary";
-
-        /**
-         * Flags that apply to a specific document.
+         * Flags that apply to a document.
          * <p>
          * Type: INTEGER (int)
+         *
+         * @see #FLAG_SUPPORTS_WRITE
+         * @see #FLAG_SUPPORTS_DELETE
+         * @see #FLAG_SUPPORTS_THUMBNAIL
+         * @see #FLAG_DIR_PREFERS_GRID
+         * @see #FLAG_DIR_SUPPORTS_CREATE
+         * @see #FLAG_DIR_SUPPORTS_SEARCH
          */
-        public static final String FLAGS = "flags";
+        public static final String COLUMN_FLAGS = "flags";
+
+        /**
+         * Size of a document, in bytes, or {@code null} if unknown.
+         * <p>
+         * Type: INTEGER (long)
+         */
+        public static final String COLUMN_SIZE = OpenableColumns.SIZE;
+
+        /**
+         * MIME type of a document which is a directory that may contain
+         * additional documents.
+         *
+         * @see #COLUMN_MIME_TYPE
+         */
+        public static final String MIME_TYPE_DIR = "vnd.android.document/directory";
+
+        /**
+         * Flag indicating that a document can be represented as a thumbnail.
+         *
+         * @see #COLUMN_FLAGS
+         * @see DocumentsContract#getDocumentThumbnail(ContentResolver, Uri,
+         *      Point, CancellationSignal)
+         * @see DocumentsProvider#openDocumentThumbnail(String, Point,
+         *      android.os.CancellationSignal)
+         */
+        public static final int FLAG_SUPPORTS_THUMBNAIL = 1;
+
+        /**
+         * Flag indicating that a document supports writing.
+         * <p>
+         * When a document is opened with {@link Intent#ACTION_OPEN_DOCUMENT},
+         * the calling application is granted both
+         * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} and
+         * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}. However, the actual
+         * writability of a document may change over time, for example due to
+         * remote access changes. This flag indicates that a document client can
+         * expect {@link ContentResolver#openOutputStream(Uri)} to succeed.
+         *
+         * @see #COLUMN_FLAGS
+         */
+        public static final int FLAG_SUPPORTS_WRITE = 1 << 1;
+
+        /**
+         * Flag indicating that a document is deletable.
+         *
+         * @see #COLUMN_FLAGS
+         * @see DocumentsContract#deleteDocument(ContentResolver, Uri)
+         * @see DocumentsProvider#deleteDocument(String)
+         */
+        public static final int FLAG_SUPPORTS_DELETE = 1 << 2;
+
+        /**
+         * Flag indicating that a document is a directory that supports creation
+         * of new files within it. Only valid when {@link #COLUMN_MIME_TYPE} is
+         * {@link #MIME_TYPE_DIR}.
+         *
+         * @see #COLUMN_FLAGS
+         * @see DocumentsContract#createDocument(ContentResolver, Uri, String,
+         *      String)
+         * @see DocumentsProvider#createDocument(String, String, String)
+         */
+        public static final int FLAG_DIR_SUPPORTS_CREATE = 1 << 3;
+
+        /**
+         * Flag indicating that a directory supports search. Only valid when
+         * {@link #COLUMN_MIME_TYPE} is {@link #MIME_TYPE_DIR}.
+         *
+         * @see #COLUMN_FLAGS
+         * @see DocumentsProvider#querySearchDocuments(String, String,
+         *      String[])
+         */
+        public static final int FLAG_DIR_SUPPORTS_SEARCH = 1 << 4;
+
+        /**
+         * Flag indicating that a directory prefers its contents be shown in a
+         * larger format grid. Usually suitable when a directory contains mostly
+         * pictures. Only valid when {@link #COLUMN_MIME_TYPE} is
+         * {@link #MIME_TYPE_DIR}.
+         *
+         * @see #COLUMN_FLAGS
+         */
+        public static final int FLAG_DIR_PREFERS_GRID = 1 << 5;
     }
 
     /**
-     * Metadata about a specific root of documents.
+     * Constants related to a root of documents, including {@link Cursor}
+     * columns names and flags.
+     * <p>
+     * All columns are <em>read-only</em> to client applications.
      */
-    public final static class DocumentRoot implements Parcelable {
+    public final static class Root {
+        private Root() {
+        }
+
         /**
-         * Root that represents a storage service, such as a cloud-based
+         * Unique ID of a root. This ID is both provided by and interpreted by a
+         * {@link DocumentsProvider}, and should be treated as an opaque value
+         * by client applications.
+         * <p>
+         * Type: STRING
+         */
+        public static final String COLUMN_ROOT_ID = "root_id";
+
+        /**
+         * Type of a root, used for clustering when presenting multiple roots to
+         * a user.
+         * <p>
+         * Type: INTEGER (int)
+         *
+         * @see #ROOT_TYPE_SERVICE
+         * @see #ROOT_TYPE_SHORTCUT
+         * @see #ROOT_TYPE_DEVICE
+         */
+        public static final String COLUMN_ROOT_TYPE = "root_type";
+
+        /**
+         * Flags that apply to a root.
+         * <p>
+         * Type: INTEGER (int)
+         *
+         * @see #FLAG_LOCAL_ONLY
+         * @see #FLAG_SUPPORTS_CREATE
+         * @see #FLAG_ADVANCED
+         * @see #FLAG_PROVIDES_AUDIO
+         * @see #FLAG_PROVIDES_IMAGES
+         * @see #FLAG_PROVIDES_VIDEO
+         */
+        public static final String COLUMN_FLAGS = "flags";
+
+        /**
+         * Icon resource ID for a root.
+         * <p>
+         * Type: INTEGER (int)
+         */
+        public static final String COLUMN_ICON = "icon";
+
+        /**
+         * Title for a root, which will be shown to a user.
+         * <p>
+         * Type: STRING
+         */
+        public static final String COLUMN_TITLE = "title";
+
+        /**
+         * Summary for this root, which may be shown to a user. The summary may
+         * be {@code null}.
+         * <p>
+         * Type: STRING
+         */
+        public static final String COLUMN_SUMMARY = "summary";
+
+        /**
+         * Document which is a directory that represents the top directory of
+         * this root.
+         * <p>
+         * Type: STRING
+         *
+         * @see Document#COLUMN_DOCUMENT_ID
+         */
+        public static final String COLUMN_DOCUMENT_ID = "document_id";
+
+        /**
+         * Number of bytes available in this root, or {@code null} if unknown or
+         * unbounded.
+         * <p>
+         * Type: INTEGER (long)
+         */
+        public static final String COLUMN_AVAILABLE_BYTES = "available_bytes";
+
+        /**
+         * Type of root that represents a storage service, such as a cloud-based
          * service.
          *
-         * @see #rootType
+         * @see #COLUMN_ROOT_TYPE
          */
         public static final int ROOT_TYPE_SERVICE = 1;
 
         /**
-         * Root that represents a shortcut to content that may be available
-         * elsewhere through another storage root.
+         * Type of root that represents a shortcut to content that may be
+         * available elsewhere through another storage root.
          *
-         * @see #rootType
+         * @see #COLUMN_ROOT_TYPE
          */
         public static final int ROOT_TYPE_SHORTCUT = 2;
 
         /**
-         * Root that represents a physical storage device.
+         * Type of root that represents a physical storage device.
          *
-         * @see #rootType
+         * @see #COLUMN_ROOT_TYPE
          */
         public static final int ROOT_TYPE_DEVICE = 3;
 
         /**
-         * Root that represents a physical storage device that should only be
-         * displayed to advanced users.
-         *
-         * @see #rootType
-         */
-        public static final int ROOT_TYPE_DEVICE_ADVANCED = 4;
-
-        /**
          * Flag indicating that at least one directory under this root supports
-         * creating content.
+         * creating content. Roots with this flag will be shown when an
+         * application interacts with {@link Intent#ACTION_CREATE_DOCUMENT}.
          *
-         * @see #flags
+         * @see #COLUMN_FLAGS
          */
         public static final int FLAG_SUPPORTS_CREATE = 1;
 
@@ -355,138 +372,210 @@
          * Flag indicating that this root offers content that is strictly local
          * on the device. That is, no network requests are made for the content.
          *
-         * @see #flags
+         * @see #COLUMN_FLAGS
+         * @see Intent#EXTRA_LOCAL_ONLY
          */
         public static final int FLAG_LOCAL_ONLY = 1 << 1;
 
-        /** {@hide} */
-        public String authority;
-
         /**
-         * Root type, use for clustering.
+         * Flag indicating that this root should only be visible to advanced
+         * users.
          *
-         * @see #ROOT_TYPE_SERVICE
-         * @see #ROOT_TYPE_DEVICE
+         * @see #COLUMN_FLAGS
          */
-        public int rootType;
+        public static final int FLAG_ADVANCED = 1 << 2;
 
         /**
-         * Flags for this root.
+         * Flag indicating that a root offers audio documents. When a user is
+         * selecting audio, roots not providing audio may be excluded.
          *
-         * @see #FLAG_LOCAL_ONLY
+         * @see #COLUMN_FLAGS
+         * @see Intent#EXTRA_MIME_TYPES
          */
-        public int flags;
+        public static final int FLAG_PROVIDES_AUDIO = 1 << 3;
 
         /**
-         * Icon resource ID for this root.
-         */
-        public int icon;
-
-        /**
-         * Title for this root.
-         */
-        public String title;
-
-        /**
-         * Summary for this root. May be {@code null}.
-         */
-        public String summary;
-
-        /**
-         * Document which is a directory that represents the top of this root.
-         * Must not be {@code null}.
+         * Flag indicating that a root offers video documents. When a user is
+         * selecting video, roots not providing video may be excluded.
          *
-         * @see DocumentColumns#DOC_ID
+         * @see #COLUMN_FLAGS
+         * @see Intent#EXTRA_MIME_TYPES
          */
-        public String docId;
+        public static final int FLAG_PROVIDES_VIDEO = 1 << 4;
 
         /**
-         * Document which is a directory representing recently modified
-         * documents under this root. This directory should return at most two
-         * dozen documents modified within the last 90 days. May be {@code null}
-         * if this root doesn't support recents.
+         * Flag indicating that a root offers image documents. When a user is
+         * selecting images, roots not providing images may be excluded.
          *
-         * @see DocumentColumns#DOC_ID
+         * @see #COLUMN_FLAGS
+         * @see Intent#EXTRA_MIME_TYPES
          */
-        public String recentDocId;
+        public static final int FLAG_PROVIDES_IMAGES = 1 << 5;
 
         /**
-         * Number of free bytes of available in this root, or -1 if unknown or
-         * unbounded.
+         * Flag indicating that this root can report recently modified
+         * documents.
+         *
+         * @see #COLUMN_FLAGS
+         * @see DocumentsContract#buildRecentDocumentsUri(String, String)
          */
-        public long availableBytes;
+        public static final int FLAG_SUPPORTS_RECENTS = 1 << 6;
+    }
 
-        /**
-         * Set of MIME type filters describing the content offered by this root,
-         * or {@code null} to indicate that all MIME types are supported. For
-         * example, a provider only supporting audio and video might set this to
-         * {@code ["audio/*", "video/*"]}.
-         */
-        public String[] mimeTypes;
+    /**
+     * Optional boolean flag included in a directory {@link Cursor#getExtras()}
+     * indicating that a document provider is still loading data. For example, a
+     * provider has returned some results, but is still waiting on an
+     * outstanding network request. The provider must send a content changed
+     * notification when loading is finished.
+     *
+     * @see ContentResolver#notifyChange(Uri, android.database.ContentObserver,
+     *      boolean)
+     */
+    public static final String EXTRA_LOADING = "loading";
 
-        public DocumentRoot() {
+    /**
+     * Optional string included in a directory {@link Cursor#getExtras()}
+     * providing an informational message that should be shown to a user. For
+     * example, a provider may wish to indicate that not all documents are
+     * available.
+     */
+    public static final String EXTRA_INFO = "info";
+
+    /**
+     * Optional string included in a directory {@link Cursor#getExtras()}
+     * providing an error message that should be shown to a user. For example, a
+     * provider may wish to indicate that a network error occurred. The user may
+     * choose to retry, resulting in a new query.
+     */
+    public static final String EXTRA_ERROR = "error";
+
+    /** {@hide} */
+    public static final String METHOD_CREATE_DOCUMENT = "android:createDocument";
+    /** {@hide} */
+    public static final String METHOD_DELETE_DOCUMENT = "android:deleteDocument";
+
+    /** {@hide} */
+    public static final String EXTRA_THUMBNAIL_SIZE = "thumbnail_size";
+
+    private static final String PATH_ROOT = "root";
+    private static final String PATH_RECENT = "recent";
+    private static final String PATH_DOCUMENT = "document";
+    private static final String PATH_CHILDREN = "children";
+    private static final String PATH_SEARCH = "search";
+
+    private static final String PARAM_QUERY = "query";
+
+    /**
+     * Build Uri representing the roots of a document provider. When queried, a
+     * provider will return one or more rows with columns defined by
+     * {@link Root}.
+     *
+     * @see DocumentsProvider#queryRoots(String[])
+     */
+    public static Uri buildRootsUri(String authority) {
+        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+                .authority(authority).appendPath(PATH_ROOT).build();
+    }
+
+    /**
+     * Build Uri representing the recently modified documents of a specific
+     * root. When queried, a provider will return zero or more rows with columns
+     * defined by {@link Document}.
+     *
+     * @see DocumentsProvider#queryRecentDocuments(String, String[])
+     * @see #getRootId(Uri)
+     */
+    public static Uri buildRecentDocumentsUri(String authority, String rootId) {
+        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+                .authority(authority).appendPath(PATH_ROOT).appendPath(rootId)
+                .appendPath(PATH_RECENT).build();
+    }
+
+    /**
+     * Build Uri representing the given {@link Document#COLUMN_DOCUMENT_ID} in a
+     * document provider. When queried, a provider will return a single row with
+     * columns defined by {@link Document}.
+     *
+     * @see DocumentsProvider#queryDocument(String, String[])
+     * @see #getDocumentId(Uri)
+     */
+    public static Uri buildDocumentUri(String authority, String documentId) {
+        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+                .authority(authority).appendPath(PATH_DOCUMENT).appendPath(documentId).build();
+    }
+
+    /**
+     * Build Uri representing the children of the given directory in a document
+     * provider. When queried, a provider will return zero or more rows with
+     * columns defined by {@link Document}.
+     *
+     * @param parentDocumentId the document to return children for, which must
+     *            be a directory with MIME type of
+     *            {@link Document#MIME_TYPE_DIR}.
+     * @see DocumentsProvider#queryChildDocuments(String, String[], String)
+     * @see #getDocumentId(Uri)
+     */
+    public static Uri buildChildDocumentsUri(String authority, String parentDocumentId) {
+        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
+                .appendPath(PATH_DOCUMENT).appendPath(parentDocumentId).appendPath(PATH_CHILDREN)
+                .build();
+    }
+
+    /**
+     * Build Uri representing a search for matching documents under a specific
+     * directory in a document provider. When queried, a provider will return
+     * zero or more rows with columns defined by {@link Document}.
+     *
+     * @param parentDocumentId the document to return children for, which must
+     *            be both a directory with MIME type of
+     *            {@link Document#MIME_TYPE_DIR} and have
+     *            {@link Document#FLAG_DIR_SUPPORTS_SEARCH} set.
+     * @see DocumentsProvider#querySearchDocuments(String, String, String[])
+     * @see #getDocumentId(Uri)
+     * @see #getSearchDocumentsQuery(Uri)
+     */
+    public static Uri buildSearchDocumentsUri(
+            String authority, String parentDocumentId, String query) {
+        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
+                .appendPath(PATH_DOCUMENT).appendPath(parentDocumentId).appendPath(PATH_SEARCH)
+                .appendQueryParameter(PARAM_QUERY, query).build();
+    }
+
+    /**
+     * Extract the {@link Root#COLUMN_ROOT_ID} from the given Uri.
+     */
+    public static String getRootId(Uri rootUri) {
+        final List<String> paths = rootUri.getPathSegments();
+        if (paths.size() < 2) {
+            throw new IllegalArgumentException("Not a root: " + rootUri);
         }
-
-        /** {@hide} */
-        public DocumentRoot(Parcel in) {
-            rootType = in.readInt();
-            flags = in.readInt();
-            icon = in.readInt();
-            title = in.readString();
-            summary = in.readString();
-            docId = in.readString();
-            recentDocId = in.readString();
-            availableBytes = in.readLong();
-            mimeTypes = in.readStringArray();
+        if (!PATH_ROOT.equals(paths.get(0))) {
+            throw new IllegalArgumentException("Not a root: " + rootUri);
         }
+        return paths.get(1);
+    }
 
-        /** {@hide} */
-        public Drawable loadIcon(Context context) {
-            if (icon != 0) {
-                if (authority != null) {
-                    final PackageManager pm = context.getPackageManager();
-                    final ProviderInfo info = pm.resolveContentProvider(authority, 0);
-                    if (info != null) {
-                        return pm.getDrawable(info.packageName, icon, info.applicationInfo);
-                    }
-                } else {
-                    return context.getResources().getDrawable(icon);
-                }
-            }
-            return null;
+    /**
+     * Extract the {@link Document#COLUMN_DOCUMENT_ID} from the given Uri.
+     */
+    public static String getDocumentId(Uri documentUri) {
+        final List<String> paths = documentUri.getPathSegments();
+        if (paths.size() < 2) {
+            throw new IllegalArgumentException("Not a document: " + documentUri);
         }
-
-        @Override
-        public int describeContents() {
-            return 0;
+        if (!PATH_DOCUMENT.equals(paths.get(0))) {
+            throw new IllegalArgumentException("Not a document: " + documentUri);
         }
+        return paths.get(1);
+    }
 
-        @Override
-        public void writeToParcel(Parcel dest, int flags) {
-            Preconditions.checkNotNull(docId);
-
-            dest.writeInt(rootType);
-            dest.writeInt(flags);
-            dest.writeInt(icon);
-            dest.writeString(title);
-            dest.writeString(summary);
-            dest.writeString(docId);
-            dest.writeString(recentDocId);
-            dest.writeLong(availableBytes);
-            dest.writeStringArray(mimeTypes);
-        }
-
-        public static final Creator<DocumentRoot> CREATOR = new Creator<DocumentRoot>() {
-            @Override
-            public DocumentRoot createFromParcel(Parcel in) {
-                return new DocumentRoot(in);
-            }
-
-            @Override
-            public DocumentRoot[] newArray(int size) {
-                return new DocumentRoot[size];
-            }
-        };
+    /**
+     * Extract the search query from a Uri built by
+     * {@link #buildSearchDocumentsUri(String, String, String)}.
+     */
+    public static String getSearchDocumentsQuery(Uri searchDocumentsUri) {
+        return searchDocumentsUri.getQueryParameter(PARAM_QUERY);
     }
 
     /**
@@ -497,6 +586,7 @@
      * {@link Intent#ACTION_CREATE_DOCUMENT}.
      *
      * @see Context#grantUriPermission(String, Uri, int)
+     * @see Context#revokeUriPermission(Uri, int)
      * @see ContentResolver#getIncomingUriPermissionGrants(int, int)
      */
     public static Uri[] getOpenDocuments(Context context) {
@@ -520,20 +610,28 @@
     }
 
     /**
-     * Return thumbnail representing the document at the given URI. Callers are
-     * responsible for their own in-memory caching. Given document must have
-     * {@link Documents#FLAG_SUPPORTS_THUMBNAIL} set.
+     * Return thumbnail representing the document at the given Uri. Callers are
+     * responsible for their own in-memory caching.
      *
+     * @param documentUri document to return thumbnail for, which must have
+     *            {@link Document#FLAG_SUPPORTS_THUMBNAIL} set.
+     * @param size optimal thumbnail size desired. A provider may return a
+     *            thumbnail of a different size, but never more than double the
+     *            requested size.
+     * @param signal signal used to indicate that caller is no longer interested
+     *            in the thumbnail.
      * @return decoded thumbnail, or {@code null} if problem was encountered.
-     * @hide
+     * @see DocumentsProvider#openDocumentThumbnail(String, Point,
+     *      android.os.CancellationSignal)
      */
-    public static Bitmap getThumbnail(ContentResolver resolver, Uri documentUri, Point size) {
+    public static Bitmap getDocumentThumbnail(
+            ContentResolver resolver, Uri documentUri, Point size, CancellationSignal signal) {
         final Bundle openOpts = new Bundle();
         openOpts.putParcelable(DocumentsContract.EXTRA_THUMBNAIL_SIZE, size);
 
         AssetFileDescriptor afd = null;
         try {
-            afd = resolver.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts);
+            afd = resolver.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts, signal);
 
             final FileDescriptor fd = afd.getFileDescriptor();
             final long offset = afd.getStartOffset();
@@ -583,38 +681,26 @@
         }
     }
 
-    /** {@hide} */
-    public static List<DocumentRoot> getDocumentRoots(ContentProviderClient client) {
-        try {
-            final Bundle out = client.call(METHOD_GET_ROOTS, null, null);
-            final List<DocumentRoot> roots = out.getParcelableArrayList(EXTRA_ROOTS);
-            return roots;
-        } catch (Exception e) {
-            Log.w(TAG, "Failed to get roots", e);
-            return null;
-        }
-    }
-
     /**
-     * Create a new document under the given parent document with MIME type and
-     * display name.
+     * Create a new document with given MIME type and display name.
      *
-     * @param docId document with {@link Documents#FLAG_SUPPORTS_CREATE}
+     * @param parentDocumentUri directory with
+     *            {@link Document#FLAG_DIR_SUPPORTS_CREATE}
      * @param mimeType MIME type of new document
      * @param displayName name of new document
      * @return newly created document, or {@code null} if failed
-     * @hide
      */
-    public static String createDocument(
-            ContentProviderClient client, String docId, String mimeType, String displayName) {
+    public static Uri createDocument(ContentResolver resolver, Uri parentDocumentUri,
+            String mimeType, String displayName) {
         final Bundle in = new Bundle();
-        in.putString(DocumentColumns.DOC_ID, docId);
-        in.putString(DocumentColumns.MIME_TYPE, mimeType);
-        in.putString(DocumentColumns.DISPLAY_NAME, displayName);
+        in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(parentDocumentUri));
+        in.putString(Document.COLUMN_MIME_TYPE, mimeType);
+        in.putString(Document.COLUMN_DISPLAY_NAME, displayName);
 
         try {
-            final Bundle out = client.call(METHOD_CREATE_DOCUMENT, null, in);
-            return out.getString(DocumentColumns.DOC_ID);
+            final Bundle out = resolver.call(parentDocumentUri, METHOD_CREATE_DOCUMENT, null, in);
+            return buildDocumentUri(
+                    parentDocumentUri.getAuthority(), out.getString(Document.COLUMN_DOCUMENT_ID));
         } catch (Exception e) {
             Log.w(TAG, "Failed to create document", e);
             return null;
@@ -622,40 +708,16 @@
     }
 
     /**
-     * Rename the given document.
-     *
-     * @param docId document with {@link Documents#FLAG_SUPPORTS_RENAME}
-     * @return document which may have changed due to rename, or {@code null} if
-     *         rename failed.
-     * @hide
-     */
-    public static String renameDocument(
-            ContentProviderClient client, String docId, String displayName) {
-        final Bundle in = new Bundle();
-        in.putString(DocumentColumns.DOC_ID, docId);
-        in.putString(DocumentColumns.DISPLAY_NAME, displayName);
-
-        try {
-            final Bundle out = client.call(METHOD_RENAME_DOCUMENT, null, in);
-            return out.getString(DocumentColumns.DOC_ID);
-        } catch (Exception e) {
-            Log.w(TAG, "Failed to rename document", e);
-            return null;
-        }
-    }
-
-    /**
      * Delete the given document.
      *
-     * @param docId document with {@link Documents#FLAG_SUPPORTS_DELETE}
-     * @hide
+     * @param documentUri document with {@link Document#FLAG_SUPPORTS_DELETE}
      */
-    public static boolean deleteDocument(ContentProviderClient client, String docId) {
+    public static boolean deleteDocument(ContentResolver resolver, Uri documentUri) {
         final Bundle in = new Bundle();
-        in.putString(DocumentColumns.DOC_ID, docId);
+        in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(documentUri));
 
         try {
-            client.call(METHOD_DELETE_DOCUMENT, null, in);
+            final Bundle out = resolver.call(documentUri, METHOD_DELETE_DOCUMENT, null, in);
             return true;
         } catch (Exception e) {
             Log.w(TAG, "Failed to delete document", e);
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index eeb8c41..09f4866 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -16,16 +16,12 @@
 
 package android.provider;
 
-import static android.provider.DocumentsContract.ACTION_DOCUMENT_ROOT_CHANGED;
-import static android.provider.DocumentsContract.EXTRA_AUTHORITY;
-import static android.provider.DocumentsContract.EXTRA_ROOTS;
 import static android.provider.DocumentsContract.EXTRA_THUMBNAIL_SIZE;
 import static android.provider.DocumentsContract.METHOD_CREATE_DOCUMENT;
 import static android.provider.DocumentsContract.METHOD_DELETE_DOCUMENT;
-import static android.provider.DocumentsContract.METHOD_GET_ROOTS;
-import static android.provider.DocumentsContract.METHOD_RENAME_DOCUMENT;
-import static android.provider.DocumentsContract.getDocId;
-import static android.provider.DocumentsContract.getSearchQuery;
+import static android.provider.DocumentsContract.getDocumentId;
+import static android.provider.DocumentsContract.getRootId;
+import static android.provider.DocumentsContract.getSearchDocumentsQuery;
 
 import android.content.ContentProvider;
 import android.content.ContentValues;
@@ -41,15 +37,12 @@
 import android.os.CancellationSignal;
 import android.os.ParcelFileDescriptor;
 import android.os.ParcelFileDescriptor.OnCloseListener;
-import android.provider.DocumentsContract.DocumentColumns;
-import android.provider.DocumentsContract.DocumentRoot;
-import android.provider.DocumentsContract.Documents;
+import android.provider.DocumentsContract.Document;
 import android.util.Log;
 
 import libcore.io.IoUtils;
 
 import java.io.FileNotFoundException;
-import java.util.List;
 
 /**
  * Base class for a document provider. A document provider should extend this
@@ -58,13 +51,13 @@
  * Each document provider expresses one or more "roots" which each serve as the
  * top-level of a tree. For example, a root could represent an account, or a
  * physical storage device. Under each root, documents are referenced by
- * {@link DocumentColumns#DOC_ID}, which must not change once returned.
+ * {@link Document#COLUMN_DOCUMENT_ID}, which must not change once returned.
  * <p>
  * Documents can be either an openable file (with a specific MIME type), or a
  * directory containing additional documents (with the
- * {@link Documents#MIME_TYPE_DIR} MIME type). Each document can have different
- * capabilities, as described by {@link DocumentColumns#FLAGS}. The same
- * {@link DocumentColumns#DOC_ID} can be included in multiple directories.
+ * {@link Document#MIME_TYPE_DIR} MIME type). Each document can have different
+ * capabilities, as described by {@link Document#COLUMN_FLAGS}. The same
+ * {@link Document#COLUMN_DOCUMENT_ID} can be included in multiple directories.
  * <p>
  * Document providers must be protected with the
  * {@link android.Manifest.permission#MANAGE_DOCUMENTS} permission, which can
@@ -78,22 +71,29 @@
 public abstract class DocumentsProvider extends ContentProvider {
     private static final String TAG = "DocumentsProvider";
 
-    private static final int MATCH_DOCUMENT = 1;
-    private static final int MATCH_CHILDREN = 2;
-    private static final int MATCH_SEARCH = 3;
+    private static final int MATCH_ROOT = 1;
+    private static final int MATCH_RECENT = 2;
+    private static final int MATCH_DOCUMENT = 3;
+    private static final int MATCH_CHILDREN = 4;
+    private static final int MATCH_SEARCH = 5;
 
     private String mAuthority;
 
     private UriMatcher mMatcher;
 
+    /**
+     * Implementation is provided by the parent class.
+     */
     @Override
     public void attachInfo(Context context, ProviderInfo info) {
         mAuthority = info.authority;
 
         mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
-        mMatcher.addURI(mAuthority, "docs/*", MATCH_DOCUMENT);
-        mMatcher.addURI(mAuthority, "docs/*/children", MATCH_CHILDREN);
-        mMatcher.addURI(mAuthority, "docs/*/search", MATCH_SEARCH);
+        mMatcher.addURI(mAuthority, "root", MATCH_ROOT);
+        mMatcher.addURI(mAuthority, "root/*/recent", MATCH_RECENT);
+        mMatcher.addURI(mAuthority, "document/*", MATCH_DOCUMENT);
+        mMatcher.addURI(mAuthority, "document/*/children", MATCH_CHILDREN);
+        mMatcher.addURI(mAuthority, "document/*/search", MATCH_SEARCH);
 
         // Sanity check our setup
         if (!info.exported) {
@@ -111,83 +111,80 @@
     }
 
     /**
-     * Return list of all document roots provided by this document provider.
-     * When this list changes, a provider must call
-     * {@link #notifyDocumentRootsChanged()}.
-     */
-    public abstract List<DocumentRoot> getDocumentRoots();
-
-    /**
-     * Create and return a new document. A provider must allocate a new
-     * {@link DocumentColumns#DOC_ID} to represent the document, which must not
-     * change once returned.
+     * Create a new document and return its {@link Document#COLUMN_DOCUMENT_ID}.
+     * A provider must allocate a new {@link Document#COLUMN_DOCUMENT_ID} to
+     * represent the document, which must not change once returned.
      *
-     * @param docId the parent directory to create the new document under.
+     * @param documentId the parent directory to create the new document under.
      * @param mimeType the MIME type associated with the new document.
      * @param displayName the display name of the new document.
      */
     @SuppressWarnings("unused")
-    public String createDocument(String docId, String mimeType, String displayName)
+    public String createDocument(String documentId, String mimeType, String displayName)
             throws FileNotFoundException {
         throw new UnsupportedOperationException("Create not supported");
     }
 
     /**
-     * Rename the given document.
+     * Delete the given document. Upon returning, any Uri permission grants for
+     * the given document will be revoked. If additional documents were deleted
+     * as a side effect of this call, such as documents inside a directory, the
+     * implementor is responsible for revoking those permissions.
      *
-     * @param docId the document to rename.
-     * @param displayName the new display name.
+     * @param documentId the document to delete.
      */
     @SuppressWarnings("unused")
-    public void renameDocument(String docId, String displayName) throws FileNotFoundException {
-        throw new UnsupportedOperationException("Rename not supported");
+    public void deleteDocument(String documentId) throws FileNotFoundException {
+        throw new UnsupportedOperationException("Delete not supported");
     }
 
-    /**
-     * Delete the given document.
-     *
-     * @param docId the document to delete.
-     */
+    public abstract Cursor queryRoots(String[] projection) throws FileNotFoundException;
+
     @SuppressWarnings("unused")
-    public void deleteDocument(String docId) throws FileNotFoundException {
-        throw new UnsupportedOperationException("Delete not supported");
+    public Cursor queryRecentDocuments(String rootId, String[] projection)
+            throws FileNotFoundException {
+        throw new UnsupportedOperationException("Recent not supported");
     }
 
     /**
      * Return metadata for the given document. A provider should avoid making
      * network requests to keep this request fast.
      *
-     * @param docId the document to return.
+     * @param documentId the document to return.
      */
-    public abstract Cursor queryDocument(String docId) throws FileNotFoundException;
+    public abstract Cursor queryDocument(String documentId, String[] projection)
+            throws FileNotFoundException;
 
     /**
      * Return the children of the given document which is a directory.
      *
-     * @param docId the directory to return children for.
+     * @param parentDocumentId the directory to return children for.
      */
-    public abstract Cursor queryDocumentChildren(String docId) throws FileNotFoundException;
+    public abstract Cursor queryChildDocuments(
+            String parentDocumentId, String[] projection, String sortOrder)
+            throws FileNotFoundException;
 
     /**
      * Return documents that that match the given query, starting the search at
      * the given directory.
      *
-     * @param docId the directory to start search at.
+     * @param parentDocumentId the directory to start search at.
      */
     @SuppressWarnings("unused")
-    public Cursor querySearch(String docId, String query) throws FileNotFoundException {
+    public Cursor querySearchDocuments(String parentDocumentId, String query, String[] projection)
+            throws FileNotFoundException {
         throw new UnsupportedOperationException("Search not supported");
     }
 
     /**
      * Return MIME type for the given document. Must match the value of
-     * {@link DocumentColumns#MIME_TYPE} for this document.
+     * {@link Document#COLUMN_MIME_TYPE} for this document.
      */
-    public String getType(String docId) throws FileNotFoundException {
-        final Cursor cursor = queryDocument(docId);
+    public String getDocumentType(String documentId) throws FileNotFoundException {
+        final Cursor cursor = queryDocument(documentId, null);
         try {
             if (cursor.moveToFirst()) {
-                return cursor.getString(cursor.getColumnIndexOrThrow(DocumentColumns.MIME_TYPE));
+                return cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE));
             } else {
                 return null;
             }
@@ -233,7 +230,7 @@
      * @param sizeHint hint of the optimal thumbnail dimensions.
      * @param signal used by the caller to signal if the request should be
      *            cancelled.
-     * @see Documents#FLAG_SUPPORTS_THUMBNAIL
+     * @see Document#FLAG_SUPPORTS_THUMBNAIL
      */
     @SuppressWarnings("unused")
     public AssetFileDescriptor openDocumentThumbnail(
@@ -241,17 +238,31 @@
         throw new UnsupportedOperationException("Thumbnails not supported");
     }
 
+    /**
+     * Implementation is provided by the parent class. Cannot be overriden.
+     *
+     * @see #queryRoots(String[])
+     * @see #queryRecentDocuments(String, String[])
+     * @see #queryDocument(String, String[])
+     * @see #queryChildDocuments(String, String[], String)
+     * @see #querySearchDocuments(String, String, String[])
+     */
     @Override
-    public final Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
-            String sortOrder) {
+    public final Cursor query(Uri uri, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder) {
         try {
             switch (mMatcher.match(uri)) {
+                case MATCH_ROOT:
+                    return queryRoots(projection);
+                case MATCH_RECENT:
+                    return queryRecentDocuments(getRootId(uri), projection);
                 case MATCH_DOCUMENT:
-                    return queryDocument(getDocId(uri));
+                    return queryDocument(getDocumentId(uri), projection);
                 case MATCH_CHILDREN:
-                    return queryDocumentChildren(getDocId(uri));
+                    return queryChildDocuments(getDocumentId(uri), projection, sortOrder);
                 case MATCH_SEARCH:
-                    return querySearch(getDocId(uri), getSearchQuery(uri));
+                    return querySearchDocuments(
+                            getDocumentId(uri), getSearchDocumentsQuery(uri), projection);
                 default:
                     throw new UnsupportedOperationException("Unsupported Uri " + uri);
             }
@@ -261,12 +272,17 @@
         }
     }
 
+    /**
+     * Implementation is provided by the parent class. Cannot be overriden.
+     *
+     * @see #getDocumentType(String)
+     */
     @Override
     public final String getType(Uri uri) {
         try {
             switch (mMatcher.match(uri)) {
                 case MATCH_DOCUMENT:
-                    return getType(getDocId(uri));
+                    return getDocumentType(getDocumentId(uri));
                 default:
                     return null;
             }
@@ -276,22 +292,39 @@
         }
     }
 
+    /**
+     * Implementation is provided by the parent class. Throws by default, and
+     * cannot be overriden.
+     *
+     * @see #createDocument(String, String, String)
+     */
     @Override
     public final Uri insert(Uri uri, ContentValues values) {
         throw new UnsupportedOperationException("Insert not supported");
     }
 
+    /**
+     * Implementation is provided by the parent class. Throws by default, and
+     * cannot be overriden.
+     *
+     * @see #deleteDocument(String)
+     */
     @Override
     public final int delete(Uri uri, String selection, String[] selectionArgs) {
         throw new UnsupportedOperationException("Delete not supported");
     }
 
+    /**
+     * Implementation is provided by the parent class. Throws by default, and
+     * cannot be overriden.
+     */
     @Override
     public final int update(
             Uri uri, ContentValues values, String selection, String[] selectionArgs) {
         throw new UnsupportedOperationException("Update not supported");
     }
 
+    /** {@hide} */
     @Override
     public final Bundle callFromPackage(
             String callingPackage, String method, String arg, Bundle extras) {
@@ -300,33 +333,25 @@
             return super.callFromPackage(callingPackage, method, arg, extras);
         }
 
-        // Platform operations require the caller explicitly hold manage
-        // permission; Uri permissions don't extend management operations.
-        getContext().enforceCallingOrSelfPermission(
-                android.Manifest.permission.MANAGE_DOCUMENTS, "Document management");
+        // Require that caller can manage given document
+        final String documentId = extras.getString(Document.COLUMN_DOCUMENT_ID);
+        final Uri documentUri = DocumentsContract.buildDocumentUri(mAuthority, documentId);
+        getContext().enforceCallingOrSelfUriPermission(
+                documentUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, method);
 
         final Bundle out = new Bundle();
         try {
-            if (METHOD_GET_ROOTS.equals(method)) {
-                final List<DocumentRoot> roots = getDocumentRoots();
-                out.putParcelableList(EXTRA_ROOTS, roots);
+            if (METHOD_CREATE_DOCUMENT.equals(method)) {
+                final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE);
+                final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
 
-            } else if (METHOD_CREATE_DOCUMENT.equals(method)) {
-                final String docId = extras.getString(DocumentColumns.DOC_ID);
-                final String mimeType = extras.getString(DocumentColumns.MIME_TYPE);
-                final String displayName = extras.getString(DocumentColumns.DISPLAY_NAME);
-
-                // TODO: issue Uri grant towards caller
-                final String newDocId = createDocument(docId, mimeType, displayName);
-                out.putString(DocumentColumns.DOC_ID, newDocId);
-
-            } else if (METHOD_RENAME_DOCUMENT.equals(method)) {
-                final String docId = extras.getString(DocumentColumns.DOC_ID);
-                final String displayName = extras.getString(DocumentColumns.DISPLAY_NAME);
-                renameDocument(docId, displayName);
+                // TODO: issue Uri grant towards calling package
+                // TODO: enforce that package belongs to caller
+                final String newDocumentId = createDocument(documentId, mimeType, displayName);
+                out.putString(Document.COLUMN_DOCUMENT_ID, newDocumentId);
 
             } else if (METHOD_DELETE_DOCUMENT.equals(method)) {
-                final String docId = extras.getString(DocumentColumns.DOC_ID);
+                final String docId = extras.getString(Document.COLUMN_DOCUMENT_ID);
                 deleteDocument(docId);
 
             } else {
@@ -338,47 +363,57 @@
         return out;
     }
 
+    /**
+     * Implementation is provided by the parent class.
+     *
+     * @see #openDocument(String, String, CancellationSignal)
+     */
     @Override
     public final ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
-        return openDocument(getDocId(uri), mode, null);
+        return openDocument(getDocumentId(uri), mode, null);
     }
 
+    /**
+     * Implementation is provided by the parent class.
+     *
+     * @see #openDocument(String, String, CancellationSignal)
+     */
     @Override
     public final ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal)
             throws FileNotFoundException {
-        return openDocument(getDocId(uri), mode, signal);
+        return openDocument(getDocumentId(uri), mode, signal);
     }
 
+    /**
+     * Implementation is provided by the parent class.
+     *
+     * @see #openDocumentThumbnail(String, Point, CancellationSignal)
+     */
     @Override
     public final AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)
             throws FileNotFoundException {
         if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) {
             final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE);
-            return openDocumentThumbnail(getDocId(uri), sizeHint, null);
+            return openDocumentThumbnail(getDocumentId(uri), sizeHint, null);
         } else {
             return super.openTypedAssetFile(uri, mimeTypeFilter, opts);
         }
     }
 
+    /**
+     * Implementation is provided by the parent class.
+     *
+     * @see #openDocumentThumbnail(String, Point, CancellationSignal)
+     */
     @Override
     public final AssetFileDescriptor openTypedAssetFile(
             Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
             throws FileNotFoundException {
         if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) {
             final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE);
-            return openDocumentThumbnail(getDocId(uri), sizeHint, signal);
+            return openDocumentThumbnail(getDocumentId(uri), sizeHint, signal);
         } else {
             return super.openTypedAssetFile(uri, mimeTypeFilter, opts, signal);
         }
     }
-
-    /**
-     * Notify system that {@link #getDocumentRoots()} has changed, usually due to an
-     * account or device change.
-     */
-    public void notifyDocumentRootsChanged() {
-        final Intent intent = new Intent(ACTION_DOCUMENT_ROOT_CHANGED);
-        intent.putExtra(EXTRA_AUTHORITY, mAuthority);
-        getContext().sendBroadcast(intent);
-    }
 }
diff --git a/core/java/android/security/IKeystoreService.java b/core/java/android/security/IKeystoreService.java
index 3d75dc8..bf8d4e5 100644
--- a/core/java/android/security/IKeystoreService.java
+++ b/core/java/android/security/IKeystoreService.java
@@ -244,7 +244,8 @@
                 return _result;
             }
 
-            public int generate(String name, int uid, int flags) throws RemoteException {
+            public int generate(String name, int uid, int keyType, int keySize, int flags,
+                    byte[][] args) throws RemoteException {
                 Parcel _data = Parcel.obtain();
                 Parcel _reply = Parcel.obtain();
                 int _result;
@@ -252,7 +253,17 @@
                     _data.writeInterfaceToken(DESCRIPTOR);
                     _data.writeString(name);
                     _data.writeInt(uid);
+                    _data.writeInt(keyType);
+                    _data.writeInt(keySize);
                     _data.writeInt(flags);
+                    if (args == null) {
+                        _data.writeInt(0);
+                    } else {
+                        _data.writeInt(args.length);
+                        for (int i = 0; i < args.length; i++) {
+                            _data.writeByteArray(args[i]);
+                        }
+                    }
                     mRemote.transact(Stub.TRANSACTION_generate, _data, _reply, 0);
                     _reply.readException();
                     _result = _reply.readInt();
@@ -560,7 +571,8 @@
 
     public int zero() throws RemoteException;
 
-    public int generate(String name, int uid, int flags) throws RemoteException;
+    public int generate(String name, int uid, int keyType, int keySize, int flags, byte[][] args)
+            throws RemoteException;
 
     public int import_key(String name, byte[] data, int uid, int flags) throws RemoteException;
 
diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java
index 51c5c7b..0bebc04 100644
--- a/core/java/android/view/ScaleGestureDetector.java
+++ b/core/java/android/view/ScaleGestureDetector.java
@@ -18,6 +18,8 @@
 
 import android.content.Context;
 import android.content.res.Resources;
+import android.os.Build;
+import android.os.Handler;
 import android.os.SystemClock;
 import android.util.FloatMath;
 
@@ -128,6 +130,8 @@
     private float mFocusX;
     private float mFocusY;
 
+    private boolean mDoubleTapScales;
+
     private float mCurrSpan;
     private float mPrevSpan;
     private float mInitialSpan;
@@ -148,9 +152,14 @@
     private int mTouchHistoryDirection;
     private long mTouchHistoryLastAcceptedTime;
     private int mTouchMinMajor;
+    private MotionEvent mDoubleTapEvent;
+    private int mDoubleTapMode = DOUBLE_TAP_MODE_NONE;
+    private final Handler mHandler;
 
     private static final long TOUCH_STABILIZE_TIME = 128; // ms
-    private static final int TOUCH_MIN_MAJOR = 48; // dp
+    private static final int DOUBLE_TAP_MODE_NONE = 0;
+    private static final int DOUBLE_TAP_MODE_IN_PROGRESS = 1;
+
 
     /**
      * Consistency verifier for debugging purposes.
@@ -158,8 +167,37 @@
     private final InputEventConsistencyVerifier mInputEventConsistencyVerifier =
             InputEventConsistencyVerifier.isInstrumentationEnabled() ?
                     new InputEventConsistencyVerifier(this, 0) : null;
+    private GestureDetector mGestureDetector;
 
+    private boolean mEventBeforeOrAboveStartingGestureEvent;
+
+    /**
+     * Creates a ScaleGestureDetector with the supplied listener.
+     * You may only use this constructor from a {@link android.os.Looper Looper} thread.
+     *
+     * @param context the application's context
+     * @param listener the listener invoked for all the callbacks, this must
+     * not be null.
+     *
+     * @throws NullPointerException if {@code listener} is null.
+     */
     public ScaleGestureDetector(Context context, OnScaleGestureListener listener) {
+        this(context, listener, null);
+    }
+
+    /**
+     * Creates a ScaleGestureDetector with the supplied listener.
+     * @see android.os.Handler#Handler()
+     *
+     * @param context the application's context
+     * @param listener the listener invoked for all the callbacks, this must
+     * not be null.
+     * @param handler the handler to use for running deferred listener events.
+     *
+     * @throws NullPointerException if {@code listener} is null.
+     */
+    public ScaleGestureDetector(Context context, OnScaleGestureListener listener,
+            Handler handler) {
         mContext = context;
         mListener = listener;
         mSpanSlop = ViewConfiguration.get(context).getScaledTouchSlop() * 2;
@@ -167,8 +205,12 @@
         final Resources res = context.getResources();
         mTouchMinMajor = res.getDimensionPixelSize(
                 com.android.internal.R.dimen.config_minScalingTouchMajor);
-        mMinSpan = res.getDimensionPixelSize(
-                com.android.internal.R.dimen.config_minScalingSpan);
+        mMinSpan = res.getDimensionPixelSize(com.android.internal.R.dimen.config_minScalingSpan);
+        mHandler = handler;
+        // Quick scale is enabled by default after JB_MR2
+        if (context.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.JELLY_BEAN_MR2) {
+            setQuickScaleEnabled(true);
+        }
     }
 
     /**
@@ -263,8 +305,14 @@
 
         final int action = event.getActionMasked();
 
+        // Forward the event to check for double tap gesture
+        if (mDoubleTapScales) {
+            mGestureDetector.onTouchEvent(event);
+        }
+
         final boolean streamComplete = action == MotionEvent.ACTION_UP ||
                 action == MotionEvent.ACTION_CANCEL;
+
         if (action == MotionEvent.ACTION_DOWN || streamComplete) {
             // Reset any scale in progress with the listener.
             // If it's an ACTION_DOWN we're beginning a new event stream.
@@ -273,6 +321,7 @@
                 mListener.onScaleEnd(this);
                 mInProgress = false;
                 mInitialSpan = 0;
+                mDoubleTapMode = DOUBLE_TAP_MODE_NONE;
             }
 
             if (streamComplete) {
@@ -284,21 +333,37 @@
         final boolean configChanged = action == MotionEvent.ACTION_DOWN ||
                 action == MotionEvent.ACTION_POINTER_UP ||
                 action == MotionEvent.ACTION_POINTER_DOWN;
+
+
         final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;
         final int skipIndex = pointerUp ? event.getActionIndex() : -1;
 
         // Determine focal point
         float sumX = 0, sumY = 0;
         final int count = event.getPointerCount();
-        for (int i = 0; i < count; i++) {
-            if (skipIndex == i) continue;
-            sumX += event.getX(i);
-            sumY += event.getY(i);
-        }
         final int div = pointerUp ? count - 1 : count;
-        final float focusX = sumX / div;
-        final float focusY = sumY / div;
+        final float focusX;
+        final float focusY;
+        if (mDoubleTapMode == DOUBLE_TAP_MODE_IN_PROGRESS) {
+            // In double tap mode, the focal pt is always where the double tap
+            // gesture started
+            focusX = mDoubleTapEvent.getX();
+            focusY = mDoubleTapEvent.getY();
+            if (event.getY() < focusY) {
+                mEventBeforeOrAboveStartingGestureEvent = true;
+            } else {
+                mEventBeforeOrAboveStartingGestureEvent = false;
+            }
+        } else {
+            for (int i = 0; i < count; i++) {
+                if (skipIndex == i) continue;
+                sumX += event.getX(i);
+                sumY += event.getY(i);
+            }
 
+            focusX = sumX / div;
+            focusY = sumY / div;
+        }
 
         addTouchHistory(event);
 
@@ -320,7 +385,12 @@
         // the focal point.
         final float spanX = devX * 2;
         final float spanY = devY * 2;
-        final float span = FloatMath.sqrt(spanX * spanX + spanY * spanY);
+        final float span;
+        if (inDoubleTapMode()) {
+            span = spanY;
+        } else {
+            span = FloatMath.sqrt(spanX * spanX + spanY * spanY);
+        }
 
         // Dispatch begin/end events as needed.
         // If the configuration changes, notify the app to reset its current state by beginning
@@ -328,10 +398,11 @@
         final boolean wasInProgress = mInProgress;
         mFocusX = focusX;
         mFocusY = focusY;
-        if (mInProgress && (span < mMinSpan || configChanged)) {
+        if (!inDoubleTapMode() && mInProgress && (span < mMinSpan || configChanged)) {
             mListener.onScaleEnd(this);
             mInProgress = false;
             mInitialSpan = span;
+            mDoubleTapMode = DOUBLE_TAP_MODE_NONE;
         }
         if (configChanged) {
             mPrevSpanX = mCurrSpanX = spanX;
@@ -354,6 +425,7 @@
             mCurrSpan = span;
 
             boolean updatePrev = true;
+
             if (mInProgress) {
                 updatePrev = mListener.onScale(this);
             }
@@ -369,6 +441,34 @@
         return true;
     }
 
+
+    private boolean inDoubleTapMode() {
+        return mDoubleTapMode == DOUBLE_TAP_MODE_IN_PROGRESS;
+    }
+
+    /**
+     * Set whether the associated {@link OnScaleGestureListener} should receive onScale callbacks
+     * when the user performs a doubleTap followed by a swipe. Note that this is enabled by default
+     * if the app targets API 19 and newer.
+     * @param scales true to enable quick scaling, false to disable
+     */
+    public void setQuickScaleEnabled(boolean scales) {
+        mDoubleTapScales = scales;
+        if (mDoubleTapScales && mGestureDetector == null) {
+            GestureDetector.SimpleOnGestureListener gestureListener =
+                    new GestureDetector.SimpleOnGestureListener() {
+                        @Override
+                        public boolean onDoubleTap(MotionEvent e) {
+                            // Double tap: start watching for a swipe
+                            mDoubleTapEvent = e;
+                            mDoubleTapMode = DOUBLE_TAP_MODE_IN_PROGRESS;
+                            return true;
+                        }
+            };
+            mGestureDetector = new GestureDetector(mContext, gestureListener, mHandler);
+        }
+    }
+
     /**
      * Returns {@code true} if a scale gesture is in progress.
      */
@@ -472,6 +572,12 @@
      * @return The current scaling factor.
      */
     public float getScaleFactor() {
+        if (inDoubleTapMode() && mEventBeforeOrAboveStartingGestureEvent) {
+            // Drag is moving up; the further away from the gesture
+            // start, the smaller the span should be, the closer,
+            // the larger the span, and therefore the larger the scale
+            return (1 / mCurrSpan) / (1 / mPrevSpan);
+        }
         return mPrevSpan > 0 ? mCurrSpan / mPrevSpan : 1;
     }
 
@@ -493,4 +599,4 @@
     public long getEventTime() {
         return mCurrTime;
     }
-}
+}
\ No newline at end of file
diff --git a/core/jni/android/graphics/BitmapRegionDecoder.cpp b/core/jni/android/graphics/BitmapRegionDecoder.cpp
index 6646579..ee47ac4 100644
--- a/core/jni/android/graphics/BitmapRegionDecoder.cpp
+++ b/core/jni/android/graphics/BitmapRegionDecoder.cpp
@@ -141,7 +141,7 @@
                                   jboolean isShareable) {
     jobject brd = NULL;
     // for now we don't allow shareable with java inputstreams
-    SkStream* stream = CopyJavaInputStream(env, is, storage);
+    SkStreamRewindable* stream = CopyJavaInputStream(env, is, storage);
 
     if (stream) {
         brd = createBitmapRegionDecoder(env, stream);
diff --git a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp
index 797d155..d264392 100644
--- a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp
+++ b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp
@@ -159,8 +159,8 @@
     friend class RewindableJavaStream;
 };
 
-SkStream* WrapJavaInputStream(JNIEnv* env, jobject stream,
-                              jbyteArray storage) {
+SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream,
+                                       jbyteArray storage) {
     static bool gInited;
 
     if (!gInited) {
@@ -190,6 +190,7 @@
     return new JavaInputStreamAdaptor(env, stream, storage);
 }
 
+
 static SkMemoryStream* adaptor_to_mem_stream(SkStream* adaptor) {
     SkASSERT(adaptor != NULL);
     SkDynamicMemoryWStream wStream;
@@ -203,9 +204,9 @@
     return new SkMemoryStream(data.get());
 }
 
-SkMemoryStream* CopyJavaInputStream(JNIEnv* env, jobject stream,
-                                    jbyteArray storage) {
-    SkAutoTUnref<SkStream> adaptor(WrapJavaInputStream(env, stream, storage));
+SkStreamRewindable* CopyJavaInputStream(JNIEnv* env, jobject stream,
+                                        jbyteArray storage) {
+    SkAutoTUnref<SkStream> adaptor(CreateJavaInputStreamAdaptor(env, stream, storage));
     if (NULL == adaptor.get()) {
         return NULL;
     }
@@ -302,7 +303,7 @@
 
 SkStreamRewindable* GetRewindableStream(JNIEnv* env, jobject stream,
                                         jbyteArray storage) {
-    SkAutoTUnref<SkStream> adaptor(WrapJavaInputStream(env, stream, storage));
+    SkAutoTUnref<SkStream> adaptor(CreateJavaInputStreamAdaptor(env, stream, storage));
     if (NULL == adaptor.get()) {
         return NULL;
     }
diff --git a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.h b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.h
index 5218dc5..fcc0c9a 100644
--- a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.h
+++ b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.h
@@ -24,8 +24,8 @@
  *      function returns, since the Java InputStream is not managed
  *      by the SkStream.
  */
-SkStream* WrapJavaInputStream(JNIEnv* env, jobject stream,
-                              jbyteArray storage);
+SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream,
+                                       jbyteArray storage);
 
 /**
  *  Copy a Java InputStream.
@@ -33,13 +33,11 @@
  *  @param stream Pointer to Java InputStream.
  *  @param storage Java byte array for retrieving data from the
  *      Java InputStream.
- *  @return SkMemoryStream The data in stream will be copied to a new
- *      SkMemoryStream.
- *  FIXME: Could return a more generic return type if ViewStateSerializer
- *  did not require an SkMemoryStream.
+ *  @return SkStreamRewindable The data in stream will be copied
+ *      to a new SkStreamRewindable.
  */
-SkMemoryStream* CopyJavaInputStream(JNIEnv* env, jobject stream,
-                                    jbyteArray storage);
+SkStreamRewindable* CopyJavaInputStream(JNIEnv* env, jobject stream,
+                                        jbyteArray storage);
 
 /**
  *  Get a rewindable stream from a Java InputStream.
@@ -50,7 +48,7 @@
  *  @return SkStreamRewindable Either a wrapper around the Java
  *      InputStream, if possible, or a copy which is rewindable.
  *      Since it may be a wrapper, must not be used after the
- *      caller returns, like the result of WrapJavaInputStream.
+ *      caller returns, like the result of CreateJavaInputStreamAdaptor.
  */
 SkStreamRewindable* GetRewindableStream(JNIEnv* env, jobject stream,
                                         jbyteArray storage);
@@ -69,5 +67,4 @@
 
 SkWStream* CreateJavaOutputStreamAdaptor(JNIEnv* env, jobject stream,
                                          jbyteArray storage);
-
 #endif
diff --git a/core/jni/android/graphics/Picture.cpp b/core/jni/android/graphics/Picture.cpp
index dff2b18..fcf22b8 100644
--- a/core/jni/android/graphics/Picture.cpp
+++ b/core/jni/android/graphics/Picture.cpp
@@ -39,9 +39,10 @@
     static SkPicture* deserialize(JNIEnv* env, jobject, jobject jstream,
                                   jbyteArray jstorage) {
         SkPicture* picture = NULL;
-        SkAutoTUnref<SkStream> strm(WrapJavaInputStream(env, jstream, jstorage));
-        if (strm.get()) {
-            picture = SkPicture::CreateFromStream(strm.get());
+        SkStream* strm = CreateJavaInputStreamAdaptor(env, jstream, jstorage);
+        if (strm) {
+            picture = SkPicture::CreateFromStream(strm);
+            delete strm;
         }
         return picture;
     }
diff --git a/drm/java/android/drm/DrmManagerClient.java b/drm/java/android/drm/DrmManagerClient.java
index e2606d6..10cdab0 100644
--- a/drm/java/android/drm/DrmManagerClient.java
+++ b/drm/java/android/drm/DrmManagerClient.java
@@ -63,8 +63,6 @@
 
     private final CloseGuard mCloseGuard = CloseGuard.get();
 
-    private static final String EXTENDED_INFO_DATA = "extended_info_data";
-
     static {
         // Load the respective library
         System.loadLibrary("drmframework_jni");
@@ -186,22 +184,8 @@
         DrmManagerClient instance = (DrmManagerClient)((WeakReference)thisReference).get();
 
         if (null != instance && null != instance.mInfoHandler) {
-            DrmInfoEvent event = new DrmInfoEvent(uniqueId, infoType, message);
             Message m = instance.mInfoHandler.obtainMessage(
-                    InfoHandler.INFO_EVENT_TYPE, event);
-            instance.mInfoHandler.sendMessage(m);
-        }
-    }
-
-    private static void notify(
-            Object thisReference, int uniqueId, int infoType, String message,
-            HashMap<String, Object> attributes) {
-        DrmManagerClient instance = (DrmManagerClient)((WeakReference)thisReference).get();
-
-        if (null != instance && null != instance.mInfoHandler) {
-            DrmInfoEvent event = new DrmInfoEvent(uniqueId, infoType, message, attributes);
-            Message m = instance.mInfoHandler.obtainMessage(
-                    InfoHandler.INFO_EVENT_TYPE, event);
+                InfoHandler.INFO_EVENT_TYPE, uniqueId, infoType, message);
             instance.mInfoHandler.sendMessage(m);
         }
     }
@@ -214,25 +198,23 @@
         }
 
         public void handleMessage(Message msg) {
-            DrmInfoEvent info = (DrmInfoEvent) msg.obj;
+            DrmInfoEvent info = null;
             DrmErrorEvent error = null;
-            int uniqueId;
-            int eventType;
-            String message;
 
             switch (msg.what) {
             case InfoHandler.INFO_EVENT_TYPE:
-                uniqueId = info.getUniqueId();
-                eventType = info.getType();
-                message = info.getMessage();
+                int uniqueId = msg.arg1;
+                int infoType = msg.arg2;
+                String message = msg.obj.toString();
 
-                switch (eventType) {
+                switch (infoType) {
                 case DrmInfoEvent.TYPE_REMOVE_RIGHTS: {
                     try {
                         DrmUtils.removeFile(message);
                     } catch (IOException e) {
                         e.printStackTrace();
                     }
+                    info = new DrmInfoEvent(uniqueId, infoType, message);
                     break;
                 }
                 case DrmInfoEvent.TYPE_ALREADY_REGISTERED_BY_ANOTHER_ACCOUNT:
@@ -240,11 +222,11 @@
                 case DrmInfoEvent.TYPE_WAIT_FOR_RIGHTS:
                 case DrmInfoEvent.TYPE_ACCOUNT_ALREADY_REGISTERED:
                 case DrmInfoEvent.TYPE_RIGHTS_REMOVED: {
+                    info = new DrmInfoEvent(uniqueId, infoType, message);
                     break;
                 }
                 default:
-                    info = null;
-                    error = new DrmErrorEvent(uniqueId, eventType, message);
+                    error = new DrmErrorEvent(uniqueId, infoType, message);
                     break;
                 }
 
diff --git a/drm/jni/android_drm_DrmManagerClient.cpp b/drm/jni/android_drm_DrmManagerClient.cpp
index 7fce3d0..baddf62 100644
--- a/drm/jni/android_drm_DrmManagerClient.cpp
+++ b/drm/jni/android_drm_DrmManagerClient.cpp
@@ -169,49 +169,11 @@
     JNIEnv *env = AndroidRuntime::getJNIEnv();
     jstring message = env->NewStringUTF(event.getMessage().string());
     ALOGV("JNIOnInfoListener::onInfo => %d | %d | %s", uniqueId, type, event.getMessage().string());
-    const DrmBuffer& drmBuffer = event.getData();
-    if (event.getCount() > 0 || drmBuffer.length > 0) {
-        jclass hashMapClazz = env->FindClass("java/util/HashMap");
-        jmethodID hashMapInitId = env->GetMethodID(hashMapClazz, "<init>", "()V");
-        jmethodID hashMapPutId = env->GetMethodID(hashMapClazz, "put",
-                "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
-        jobject hashMapObject = env->NewObject(hashMapClazz, hashMapInitId);
-        env->DeleteLocalRef(hashMapClazz);
 
-        if (0 < drmBuffer.length) {
-            jfieldID fid = env->GetStaticFieldID(
-                mClass, "EXTENDED_INFO_DATA", "Ljava/lang/String;");
-            jstring key = (jstring) env->GetStaticObjectField(mClass, fid);
-
-            jbyteArray valueByte = env->NewByteArray(drmBuffer.length);
-            env->SetByteArrayRegion(valueByte, 0, drmBuffer.length, (jbyte*) drmBuffer.data);
-            env->CallObjectMethod(hashMapObject, hashMapPutId, key, valueByte);
-            env->DeleteLocalRef(valueByte);
-            env->DeleteLocalRef(key);
-        }
-        DrmInfoEvent::KeyIterator keyIt = event.keyIterator();
-        while (keyIt.hasNext()) {
-            String8 mapKey = keyIt.next();
-            jstring key = env->NewStringUTF(mapKey.string());
-            jstring value = env->NewStringUTF(event.get(mapKey).string());
-            env->CallObjectMethod(hashMapObject, hashMapPutId, key, value);
-            env->DeleteLocalRef(value);
-            env->DeleteLocalRef(key);
-        }
-        env->CallStaticVoidMethod(
-                mClass,
-                env->GetStaticMethodID(mClass, "notify",
-                        "(Ljava/lang/Object;IILjava/lang/String;Ljava/util/HashMap;)V"),
-                mObject, uniqueId, type, message, hashMapObject);
-        env->DeleteLocalRef(hashMapObject);
-    } else {
-        env->CallStaticVoidMethod(
-                mClass,
-                env->GetStaticMethodID(mClass, "notify",
-                        "(Ljava/lang/Object;IILjava/lang/String;)V"),
-                mObject, uniqueId, type, message);
-    }
-    env->DeleteLocalRef(message);
+    env->CallStaticVoidMethod(
+            mClass,
+            env->GetStaticMethodID(mClass, "notify", "(Ljava/lang/Object;IILjava/lang/String;)V"),
+            mObject, uniqueId, type, message);
 }
 
 static Mutex sLock;
diff --git a/keystore/java/android/security/AndroidKeyPairGenerator.java b/keystore/java/android/security/AndroidKeyPairGenerator.java
index 390e732..1ab0aeb 100644
--- a/keystore/java/android/security/AndroidKeyPairGenerator.java
+++ b/keystore/java/android/security/AndroidKeyPairGenerator.java
@@ -18,6 +18,7 @@
 
 import com.android.org.bouncycastle.x509.X509V3CertificateGenerator;
 
+import com.android.org.conscrypt.NativeCrypto;
 import com.android.org.conscrypt.OpenSSLEngine;
 
 import java.security.InvalidAlgorithmParameterException;
@@ -33,7 +34,10 @@
 import java.security.cert.CertificateEncodingException;
 import java.security.cert.X509Certificate;
 import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.DSAParameterSpec;
+import java.security.spec.ECParameterSpec;
 import java.security.spec.InvalidKeySpecException;
+import java.security.spec.RSAKeyGenParameterSpec;
 import java.security.spec.X509EncodedKeySpec;
 
 /**
@@ -87,8 +91,12 @@
 
         Credentials.deleteAllTypesForAlias(mKeyStore, alias);
 
+        final int keyType = KeyStore.getKeyTypeForAlgorithm(mSpec.getKeyType());
+        byte[][] args = getArgsForKeyType(keyType, mSpec.getAlgorithmParameterSpec());
+
         final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + alias;
-        if (!mKeyStore.generate(privateKeyAlias, KeyStore.UID_SELF, mSpec.getFlags())) {
+        if (!mKeyStore.generate(privateKeyAlias, KeyStore.UID_SELF, keyType,
+                mSpec.getKeySize(), mSpec.getFlags(), args)) {
             throw new IllegalStateException("could not generate key in keystore");
         }
 
@@ -104,10 +112,10 @@
 
         final PublicKey pubKey;
         try {
-            final KeyFactory keyFact = KeyFactory.getInstance("RSA");
+            final KeyFactory keyFact = KeyFactory.getInstance(mSpec.getKeyType());
             pubKey = keyFact.generatePublic(new X509EncodedKeySpec(pubKeyBytes));
         } catch (NoSuchAlgorithmException e) {
-            throw new IllegalStateException("Can't instantiate RSA key generator", e);
+            throw new IllegalStateException("Can't instantiate key generator", e);
         } catch (InvalidKeySpecException e) {
             throw new IllegalStateException("keystore returned invalid key encoding", e);
         }
@@ -119,7 +127,7 @@
         certGen.setIssuerDN(mSpec.getSubjectDN());
         certGen.setNotBefore(mSpec.getStartDate());
         certGen.setNotAfter(mSpec.getEndDate());
-        certGen.setSignatureAlgorithm("sha1WithRSA");
+        certGen.setSignatureAlgorithm(getDefaultSignatureAlgorithmForKeyType(mSpec.getKeyType()));
 
         final X509Certificate cert;
         try {
@@ -146,6 +154,37 @@
         return new KeyPair(pubKey, privKey);
     }
 
+    private static String getDefaultSignatureAlgorithmForKeyType(String keyType) {
+        if ("RSA".equalsIgnoreCase(keyType)) {
+            return "sha256WithRSA";
+        } else if ("DSA".equalsIgnoreCase(keyType)) {
+            return "sha1WithDSA";
+        } else if ("EC".equalsIgnoreCase(keyType)) {
+            return "sha256WithECDSA";
+        } else {
+            throw new IllegalArgumentException("Unsupported key type " + keyType);
+        }
+    }
+
+    private static byte[][] getArgsForKeyType(int keyType, AlgorithmParameterSpec spec) {
+        switch (keyType) {
+            case NativeCrypto.EVP_PKEY_RSA:
+                if (spec instanceof RSAKeyGenParameterSpec) {
+                    RSAKeyGenParameterSpec rsaSpec = (RSAKeyGenParameterSpec) spec;
+                    return new byte[][] { rsaSpec.getPublicExponent().toByteArray() };
+                }
+                break;
+            case NativeCrypto.EVP_PKEY_DSA:
+                if (spec instanceof DSAParameterSpec) {
+                    DSAParameterSpec dsaSpec = (DSAParameterSpec) spec;
+                    return new byte[][] { dsaSpec.getG().toByteArray(),
+                            dsaSpec.getP().toByteArray(), dsaSpec.getQ().toByteArray() };
+                }
+                break;
+        }
+        return null;
+    }
+
     @Override
     public void initialize(int keysize, SecureRandom random) {
         throw new IllegalArgumentException("cannot specify keysize with AndroidKeyPairGenerator");
diff --git a/keystore/java/android/security/KeyPairGeneratorSpec.java b/keystore/java/android/security/KeyPairGeneratorSpec.java
index 59f89bc..21d6caa 100644
--- a/keystore/java/android/security/KeyPairGeneratorSpec.java
+++ b/keystore/java/android/security/KeyPairGeneratorSpec.java
@@ -16,13 +16,18 @@
 
 package android.security;
 
+import com.android.org.conscrypt.NativeCrypto;
+
 import android.content.Context;
 import android.text.TextUtils;
 
 import java.math.BigInteger;
+import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
 import java.security.cert.Certificate;
 import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.DSAParameterSpec;
+import java.security.spec.RSAKeyGenParameterSpec;
 import java.util.Date;
 
 import javax.security.auth.x500.X500Principal;
@@ -50,10 +55,35 @@
  * certificate signed by a real Certificate Authority.
  */
 public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
-    private final String mKeystoreAlias;
+    /*
+     * These must be kept in sync with system/security/keystore/defaults.h
+     */
+
+    /* DSA */
+    private static final int DSA_DEFAULT_KEY_SIZE = 1024;
+    private static final int DSA_MIN_KEY_SIZE = 512;
+    private static final int DSA_MAX_KEY_SIZE = 8192;
+
+    /* EC */
+    private static final int EC_DEFAULT_KEY_SIZE = 256;
+    private static final int EC_MIN_KEY_SIZE = 192;
+    private static final int EC_MAX_KEY_SIZE = 521;
+
+    /* RSA */
+    private static final int RSA_DEFAULT_KEY_SIZE = 2048;
+    private static final int RSA_MIN_KEY_SIZE = 512;
+    private static final int RSA_MAX_KEY_SIZE = 8192;
 
     private final Context mContext;
 
+    private final String mKeystoreAlias;
+
+    private final String mKeyType;
+
+    private final int mKeySize;
+
+    private final AlgorithmParameterSpec mSpec;
+
     private final X500Principal mSubjectDN;
 
     private final BigInteger mSerialNumber;
@@ -84,6 +114,9 @@
      * @param context Android context for the activity
      * @param keyStoreAlias name to use for the generated key in the Android
      *            keystore
+     * @param keyType key algorithm to use (RSA, DSA, EC)
+     * @param keySize size of key to generate
+     * @param spec the underlying key type parameters
      * @param subjectDN X.509 v3 Subject Distinguished Name
      * @param serialNumber X509 v3 certificate serial number
      * @param startDate the start of the self-signed certificate validity period
@@ -93,9 +126,9 @@
      *             {@code endDate} is before {@code startDate}.
      * @hide should be built with KeyPairGeneratorSpecBuilder
      */
-    public KeyPairGeneratorSpec(Context context, String keyStoreAlias,
-            X500Principal subjectDN, BigInteger serialNumber, Date startDate, Date endDate,
-            int flags) {
+    public KeyPairGeneratorSpec(Context context, String keyStoreAlias, String keyType, int keySize,
+            AlgorithmParameterSpec spec, X500Principal subjectDN, BigInteger serialNumber,
+            Date startDate, Date endDate, int flags) {
         if (context == null) {
             throw new IllegalArgumentException("context == null");
         } else if (TextUtils.isEmpty(keyStoreAlias)) {
@@ -112,8 +145,18 @@
             throw new IllegalArgumentException("endDate < startDate");
         }
 
+        final int keyTypeInt = KeyStore.getKeyTypeForAlgorithm(keyType);
+        if (keySize == -1) {
+            keySize = getDefaultKeySizeForType(keyTypeInt);
+        }
+        checkCorrectParametersSpec(keyTypeInt, keySize, spec);
+        checkValidKeySize(keyTypeInt, keySize);
+
         mContext = context;
         mKeystoreAlias = keyStoreAlias;
+        mKeyType = keyType;
+        mKeySize = keySize;
+        mSpec = spec;
         mSubjectDN = subjectDN;
         mSerialNumber = serialNumber;
         mStartDate = startDate;
@@ -121,6 +164,64 @@
         mFlags = flags;
     }
 
+    private static int getDefaultKeySizeForType(int keyType) {
+        if (keyType == NativeCrypto.EVP_PKEY_DSA) {
+            return DSA_DEFAULT_KEY_SIZE;
+        } else if (keyType == NativeCrypto.EVP_PKEY_EC) {
+            return EC_DEFAULT_KEY_SIZE;
+        } else if (keyType == NativeCrypto.EVP_PKEY_RSA) {
+            return RSA_DEFAULT_KEY_SIZE;
+        }
+        throw new IllegalArgumentException("Invalid key type " + keyType);
+    }
+
+    private static void checkValidKeySize(int keyType, int keySize) {
+        if (keyType == NativeCrypto.EVP_PKEY_DSA) {
+            if (keySize < DSA_MIN_KEY_SIZE || keySize > DSA_MAX_KEY_SIZE) {
+                throw new IllegalArgumentException("DSA keys must be >= " + DSA_MIN_KEY_SIZE
+                        + " and <= " + DSA_MAX_KEY_SIZE);
+            }
+        } else if (keyType == NativeCrypto.EVP_PKEY_EC) {
+            if (keySize < EC_MIN_KEY_SIZE || keySize > EC_MAX_KEY_SIZE) {
+                throw new IllegalArgumentException("EC keys must be >= " + EC_MIN_KEY_SIZE
+                        + " and <= " + EC_MAX_KEY_SIZE);
+            }
+        } else if (keyType == NativeCrypto.EVP_PKEY_RSA) {
+            if (keySize < RSA_MIN_KEY_SIZE || keySize > RSA_MAX_KEY_SIZE) {
+                throw new IllegalArgumentException("RSA keys must be >= " + RSA_MIN_KEY_SIZE
+                        + " and <= " + RSA_MAX_KEY_SIZE);
+            }
+        } else {
+            throw new IllegalArgumentException("Invalid key type " + keyType);
+        }
+    }
+
+    private static void checkCorrectParametersSpec(int keyType, int keySize,
+            AlgorithmParameterSpec spec) {
+        if (keyType == NativeCrypto.EVP_PKEY_DSA && spec != null) {
+            if (!(spec instanceof DSAParameterSpec)) {
+                throw new IllegalArgumentException("DSA keys must have DSAParameterSpec specified");
+            }
+        } else if (keyType == NativeCrypto.EVP_PKEY_RSA && spec != null) {
+            if (spec instanceof RSAKeyGenParameterSpec) {
+                RSAKeyGenParameterSpec rsaSpec = (RSAKeyGenParameterSpec) spec;
+                if (keySize != -1 && keySize != rsaSpec.getKeysize()) {
+                    throw new IllegalArgumentException("RSA key size must match: " + keySize
+                            + " vs " + rsaSpec.getKeysize());
+                }
+            } else {
+                throw new IllegalArgumentException("RSA may only use RSAKeyGenParameterSpec");
+            }
+        }
+    }
+
+    /**
+     * Gets the Android context used for operations with this instance.
+     */
+    public Context getContext() {
+        return mContext;
+    }
+
     /**
      * Returns the alias that will be used in the {@code java.security.KeyStore}
      * in conjunction with the {@code AndroidKeyStore}.
@@ -130,10 +231,28 @@
     }
 
     /**
-     * Gets the Android context used for operations with this instance.
+     * Returns the key type (e.g., "RSA", "DSA", "EC") specified by this
+     * parameter.
      */
-    public Context getContext() {
-        return mContext;
+    public String getKeyType() {
+        return mKeyType;
+    }
+
+    /**
+     * Returns the key size specified by this parameter. For instance, for RSA
+     * this will return the modulus size and for EC it will return the field
+     * size.
+     */
+    public int getKeySize() {
+        return mKeySize;
+    }
+
+    /**
+     * Returns the {@link AlgorithmParameterSpec} that will be used for creation
+     * of the key pair.
+     */
+    public AlgorithmParameterSpec getAlgorithmParameterSpec() {
+        return mSpec;
     }
 
     /**
@@ -209,6 +328,12 @@
 
         private String mKeystoreAlias;
 
+        private String mKeyType = "RSA";
+
+        private int mKeySize = -1;
+
+        private AlgorithmParameterSpec mSpec;
+
         private X500Principal mSubjectDN;
 
         private BigInteger mSerialNumber;
@@ -246,6 +371,49 @@
         }
 
         /**
+         * Sets the key type (e.g., RSA, DSA, EC) of the keypair to be created.
+         */
+        public Builder setKeyType(String keyType) throws NoSuchAlgorithmException {
+            if (keyType == null) {
+                throw new NullPointerException("keyType == null");
+            } else {
+                try {
+                    KeyStore.getKeyTypeForAlgorithm(keyType);
+                } catch (IllegalArgumentException e) {
+                    throw new NoSuchAlgorithmException("Unsupported key type: " + keyType);
+                }
+            }
+            mKeyType = keyType;
+            return this;
+        }
+
+        /**
+         * Sets the key size for the keypair to be created. For instance, for a
+         * key type of RSA this will set the modulus size and for a key type of
+         * EC it will select a curve with a matching field size.
+         */
+        public Builder setKeySize(int keySize) {
+            if (keySize < 0) {
+                throw new IllegalArgumentException("keySize < 0");
+            }
+            mKeySize = keySize;
+            return this;
+        }
+
+        /**
+         * Sets the underlying key type's parameters. This is required for DSA
+         * where you must set this to an instance of
+         * {@link java.security.spec.DSAParameterSpec}.
+         */
+        public Builder setAlgorithmParameterSpec(AlgorithmParameterSpec spec) {
+            if (spec == null) {
+                throw new NullPointerException("spec == null");
+            }
+            mSpec = spec;
+            return this;
+        }
+
+        /**
          * Sets the subject used for the self-signed certificate of the
          * generated key pair.
          */
@@ -311,8 +479,8 @@
          * @return built instance of {@code KeyPairGeneratorSpec}
          */
         public KeyPairGeneratorSpec build() {
-            return new KeyPairGeneratorSpec(mContext, mKeystoreAlias, mSubjectDN,
-                    mSerialNumber, mStartDate, mEndDate, mFlags);
+            return new KeyPairGeneratorSpec(mContext, mKeystoreAlias, mKeyType, mKeySize, mSpec,
+                    mSubjectDN, mSerialNumber, mStartDate, mEndDate, mFlags);
         }
     }
 }
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index fb5e039..9babb94 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -16,6 +16,8 @@
 
 package android.security;
 
+import com.android.org.conscrypt.NativeCrypto;
+
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.util.Log;
@@ -64,6 +66,18 @@
         return new KeyStore(keystore);
     }
 
+    static int getKeyTypeForAlgorithm(String keyType) throws IllegalArgumentException {
+        if ("RSA".equalsIgnoreCase(keyType)) {
+            return NativeCrypto.EVP_PKEY_RSA;
+        } else if ("DSA".equalsIgnoreCase(keyType)) {
+            return NativeCrypto.EVP_PKEY_DSA;
+        } else if ("EC".equalsIgnoreCase(keyType)) {
+            return NativeCrypto.EVP_PKEY_EC;
+        } else {
+            throw new IllegalArgumentException("Unsupported key type: " + keyType);
+        }
+    }
+
     public State state() {
         final int ret;
         try {
@@ -188,9 +202,10 @@
         }
     }
 
-    public boolean generate(String key, int uid, int flags) {
+    public boolean generate(String key, int uid, int keyType, int keySize, int flags,
+            byte[][] args) {
         try {
-            return mBinder.generate(key, uid, flags) == NO_ERROR;
+            return mBinder.generate(key, uid, keyType, keySize, flags, args) == NO_ERROR;
         } catch (RemoteException e) {
             Log.w(TAG, "Cannot connect to keystore", e);
             return false;
diff --git a/keystore/tests/src/android/security/AndroidKeyPairGeneratorTest.java b/keystore/tests/src/android/security/AndroidKeyPairGeneratorTest.java
index 1582f74..ea6c43d 100644
--- a/keystore/tests/src/android/security/AndroidKeyPairGeneratorTest.java
+++ b/keystore/tests/src/android/security/AndroidKeyPairGeneratorTest.java
@@ -27,6 +27,13 @@
 import java.security.cert.Certificate;
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
+import java.security.interfaces.DSAParams;
+import java.security.interfaces.DSAPublicKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.DSAParameterSpec;
+import java.security.spec.RSAKeyGenParameterSpec;
 import java.text.SimpleDateFormat;
 import java.util.Date;
 
@@ -118,6 +125,8 @@
         mGenerator.initialize(
                 new KeyPairGeneratorSpec.Builder(getContext())
                         .setAlias(TEST_ALIAS_1)
+                        .setKeyType("RSA")
+                        .setKeySize(1024)
                         .setSubject(TEST_DN_1)
                         .setSerialNumber(TEST_SERIAL_1)
                         .setStartDate(NOW)
@@ -142,10 +151,207 @@
         final KeyPair pair = mGenerator.generateKeyPair();
         assertNotNull("The KeyPair returned should not be null", pair);
 
-        assertKeyPairCorrect(pair, TEST_ALIAS_1, TEST_DN_1, TEST_SERIAL_1, NOW, NOW_PLUS_10_YEARS);
+        assertKeyPairCorrect(pair, TEST_ALIAS_1, "RSA", 2048, null, TEST_DN_1, TEST_SERIAL_1, NOW,
+                NOW_PLUS_10_YEARS);
     }
 
-    public void testKeyPairGenerator_GenerateKeyPair_Unencrypted_Success() throws Exception {
+    public void testKeyPairGenerator_GenerateKeyPair_DSA_Unencrypted_Success() throws Exception {
+        mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
+                .setAlias(TEST_ALIAS_1)
+                .setKeyType("DSA")
+                .setSubject(TEST_DN_1)
+                .setSerialNumber(TEST_SERIAL_1)
+                .setStartDate(NOW)
+                .setEndDate(NOW_PLUS_10_YEARS)
+                .build());
+
+        final KeyPair pair = mGenerator.generateKeyPair();
+        assertNotNull("The KeyPair returned should not be null", pair);
+
+        assertKeyPairCorrect(pair, TEST_ALIAS_1, "DSA", 1024, null, TEST_DN_1, TEST_SERIAL_1, NOW,
+                NOW_PLUS_10_YEARS);
+    }
+
+    public void testKeyPairGenerator_GenerateKeyPair_DSA_2048_Unencrypted_Success()
+            throws Exception {
+        mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
+                .setAlias(TEST_ALIAS_1)
+                .setKeyType("DSA")
+                .setKeySize(2048)
+                .setSubject(TEST_DN_1)
+                .setSerialNumber(TEST_SERIAL_1)
+                .setStartDate(NOW)
+                .setEndDate(NOW_PLUS_10_YEARS)
+                .build());
+
+        final KeyPair pair = mGenerator.generateKeyPair();
+        assertNotNull("The KeyPair returned should not be null", pair);
+
+        assertKeyPairCorrect(pair, TEST_ALIAS_1, "DSA", 2048, null, TEST_DN_1, TEST_SERIAL_1, NOW,
+                NOW_PLUS_10_YEARS);
+    }
+
+    public void testKeyPairGenerator_GenerateKeyPair_DSA_SpecifiedParams_Unencrypted_Success()
+            throws Exception {
+        /*
+         * generated using: openssl dsaparam -C 2048
+         */
+        BigInteger p = new BigInteger(1, new byte[] {
+                (byte) 0xC0, (byte) 0x3D, (byte) 0x86, (byte) 0x09, (byte) 0xCA, (byte) 0x8C,
+                (byte) 0x37, (byte) 0xCA, (byte) 0xCC, (byte) 0x4A, (byte) 0x81, (byte) 0xBD,
+                (byte) 0xD8, (byte) 0x50, (byte) 0x77, (byte) 0xCD, (byte) 0xDD, (byte) 0x32,
+                (byte) 0x0B, (byte) 0x43, (byte) 0xBF, (byte) 0x42, (byte) 0x06, (byte) 0x5A,
+                (byte) 0x3D, (byte) 0x18, (byte) 0x50, (byte) 0x47, (byte) 0x79, (byte) 0xE1,
+                (byte) 0x5B, (byte) 0x86, (byte) 0x03, (byte) 0xB9, (byte) 0x28, (byte) 0x9C,
+                (byte) 0x18, (byte) 0xA9, (byte) 0xF5, (byte) 0xD6, (byte) 0xF4, (byte) 0x94,
+                (byte) 0x5B, (byte) 0x87, (byte) 0x58, (byte) 0xCA, (byte) 0xB2, (byte) 0x1E,
+                (byte) 0xFC, (byte) 0xED, (byte) 0x37, (byte) 0xC3, (byte) 0x49, (byte) 0xAC,
+                (byte) 0xFA, (byte) 0x46, (byte) 0xDB, (byte) 0x7A, (byte) 0x50, (byte) 0x96,
+                (byte) 0xCF, (byte) 0x52, (byte) 0xD7, (byte) 0x4E, (byte) 0xEB, (byte) 0x26,
+                (byte) 0x41, (byte) 0xA2, (byte) 0x6F, (byte) 0x99, (byte) 0x80, (byte) 0x9F,
+                (byte) 0x0F, (byte) 0x0A, (byte) 0xA8, (byte) 0x0D, (byte) 0xAC, (byte) 0xAB,
+                (byte) 0xEF, (byte) 0x7D, (byte) 0xE7, (byte) 0x4C, (byte) 0xF1, (byte) 0x88,
+                (byte) 0x44, (byte) 0xC9, (byte) 0x17, (byte) 0xD0, (byte) 0xBB, (byte) 0xE2,
+                (byte) 0x01, (byte) 0x8C, (byte) 0xC1, (byte) 0x02, (byte) 0x1D, (byte) 0x3C,
+                (byte) 0x15, (byte) 0xB7, (byte) 0x41, (byte) 0x30, (byte) 0xD8, (byte) 0x11,
+                (byte) 0xBD, (byte) 0x6A, (byte) 0x2A, (byte) 0x0D, (byte) 0x36, (byte) 0x44,
+                (byte) 0x9C, (byte) 0x3F, (byte) 0x32, (byte) 0xE2, (byte) 0x1C, (byte) 0xFB,
+                (byte) 0xE3, (byte) 0xFF, (byte) 0xCC, (byte) 0x1A, (byte) 0x72, (byte) 0x38,
+                (byte) 0x37, (byte) 0x69, (byte) 0x5E, (byte) 0x35, (byte) 0x73, (byte) 0xE1,
+                (byte) 0x1E, (byte) 0x74, (byte) 0x35, (byte) 0x44, (byte) 0x07, (byte) 0xB5,
+                (byte) 0x2F, (byte) 0x0B, (byte) 0x60, (byte) 0xF4, (byte) 0xA9, (byte) 0xE0,
+                (byte) 0x81, (byte) 0xB2, (byte) 0xCD, (byte) 0x8B, (byte) 0x82, (byte) 0x76,
+                (byte) 0x7F, (byte) 0xD4, (byte) 0x17, (byte) 0x32, (byte) 0x86, (byte) 0x98,
+                (byte) 0x7C, (byte) 0x85, (byte) 0x66, (byte) 0xF6, (byte) 0x77, (byte) 0xED,
+                (byte) 0x8B, (byte) 0x1A, (byte) 0x52, (byte) 0x16, (byte) 0xDA, (byte) 0x1C,
+                (byte) 0xA7, (byte) 0x16, (byte) 0x79, (byte) 0x20, (byte) 0x1C, (byte) 0x99,
+                (byte) 0x5F, (byte) 0x12, (byte) 0x66, (byte) 0x15, (byte) 0x9F, (byte) 0xE5,
+                (byte) 0x73, (byte) 0xA9, (byte) 0x61, (byte) 0xBA, (byte) 0xA7, (byte) 0x23,
+                (byte) 0x93, (byte) 0x77, (byte) 0xB5, (byte) 0xF6, (byte) 0xEC, (byte) 0x13,
+                (byte) 0xBF, (byte) 0x95, (byte) 0x60, (byte) 0x78, (byte) 0x84, (byte) 0xE3,
+                (byte) 0x44, (byte) 0xEC, (byte) 0x74, (byte) 0xC2, (byte) 0xCB, (byte) 0xD4,
+                (byte) 0x70, (byte) 0xC5, (byte) 0x7B, (byte) 0xF8, (byte) 0x07, (byte) 0x3B,
+                (byte) 0xEB, (byte) 0x9F, (byte) 0xC9, (byte) 0x7D, (byte) 0xE0, (byte) 0xA5,
+                (byte) 0xBA, (byte) 0x68, (byte) 0x7B, (byte) 0xF4, (byte) 0x70, (byte) 0x40,
+                (byte) 0xAE, (byte) 0xE9, (byte) 0x65, (byte) 0xEE, (byte) 0x5B, (byte) 0x71,
+                (byte) 0x36, (byte) 0x0B, (byte) 0xB0, (byte) 0xA2, (byte) 0x98, (byte) 0x7D,
+                (byte) 0xE3, (byte) 0x24, (byte) 0x95, (byte) 0x2B, (byte) 0xC2, (byte) 0x0A,
+                (byte) 0x78, (byte) 0x3D, (byte) 0xCC, (byte) 0x3A, (byte) 0xEE, (byte) 0xED,
+                (byte) 0x48, (byte) 0xEB, (byte) 0xA3, (byte) 0x78, (byte) 0xA8, (byte) 0x9D,
+                (byte) 0x0A, (byte) 0x8F, (byte) 0x9E, (byte) 0x59, (byte) 0x2C, (byte) 0x44,
+                (byte) 0xB5, (byte) 0xF9, (byte) 0x53, (byte) 0x43,
+        });
+
+        BigInteger q = new BigInteger(1, new byte[] {
+                (byte) 0xA1, (byte) 0x9B, (byte) 0x1D, (byte) 0xC0, (byte) 0xE3, (byte) 0xF6,
+                (byte) 0x4A, (byte) 0x35, (byte) 0xE1, (byte) 0x8A, (byte) 0x43, (byte) 0xC2,
+                (byte) 0x9C, (byte) 0xF9, (byte) 0x52, (byte) 0x8F, (byte) 0x94, (byte) 0xA1,
+                (byte) 0x12, (byte) 0x11, (byte) 0xDB, (byte) 0x9A, (byte) 0xB6, (byte) 0x35,
+                (byte) 0x56, (byte) 0x26, (byte) 0x60, (byte) 0x89, (byte) 0x11, (byte) 0xAC,
+                (byte) 0xA8, (byte) 0xE5,
+        });
+
+        BigInteger g = new BigInteger(1, new byte[] {
+                (byte) 0xA1, (byte) 0x5C, (byte) 0x57, (byte) 0x15, (byte) 0xC3, (byte) 0xD9,
+                (byte) 0xD7, (byte) 0x41, (byte) 0x89, (byte) 0xD6, (byte) 0xB8, (byte) 0x7B,
+                (byte) 0xF3, (byte) 0xE0, (byte) 0xB3, (byte) 0xC5, (byte) 0xD1, (byte) 0xAA,
+                (byte) 0xF9, (byte) 0x55, (byte) 0x48, (byte) 0xF1, (byte) 0xDA, (byte) 0xE8,
+                (byte) 0x6F, (byte) 0x51, (byte) 0x05, (byte) 0xB2, (byte) 0xC9, (byte) 0x64,
+                (byte) 0xDA, (byte) 0x5F, (byte) 0xD4, (byte) 0xAA, (byte) 0xFD, (byte) 0x67,
+                (byte) 0xE0, (byte) 0x10, (byte) 0x2C, (byte) 0x1F, (byte) 0x03, (byte) 0x10,
+                (byte) 0xD4, (byte) 0x4B, (byte) 0x20, (byte) 0x82, (byte) 0x2B, (byte) 0x04,
+                (byte) 0xF9, (byte) 0x09, (byte) 0xAE, (byte) 0x28, (byte) 0x3D, (byte) 0x9B,
+                (byte) 0xFF, (byte) 0x87, (byte) 0x76, (byte) 0xCD, (byte) 0xF0, (byte) 0x11,
+                (byte) 0xB7, (byte) 0xEA, (byte) 0xE6, (byte) 0xCD, (byte) 0x60, (byte) 0xD3,
+                (byte) 0x8C, (byte) 0x74, (byte) 0xD3, (byte) 0x45, (byte) 0x63, (byte) 0x69,
+                (byte) 0x3F, (byte) 0x1D, (byte) 0x31, (byte) 0x25, (byte) 0x49, (byte) 0x97,
+                (byte) 0x4B, (byte) 0x73, (byte) 0x34, (byte) 0x12, (byte) 0x73, (byte) 0x27,
+                (byte) 0x4C, (byte) 0xDA, (byte) 0xF3, (byte) 0x08, (byte) 0xA8, (byte) 0xA9,
+                (byte) 0x27, (byte) 0xE4, (byte) 0xB8, (byte) 0xD6, (byte) 0xB5, (byte) 0xC4,
+                (byte) 0x18, (byte) 0xED, (byte) 0xBD, (byte) 0x6F, (byte) 0xA2, (byte) 0x36,
+                (byte) 0xA2, (byte) 0x9C, (byte) 0x27, (byte) 0x62, (byte) 0x7F, (byte) 0x93,
+                (byte) 0xD7, (byte) 0x52, (byte) 0xA9, (byte) 0x76, (byte) 0x55, (byte) 0x99,
+                (byte) 0x00, (byte) 0x5B, (byte) 0xC2, (byte) 0xB9, (byte) 0x18, (byte) 0xAC,
+                (byte) 0x6B, (byte) 0x83, (byte) 0x0D, (byte) 0xA1, (byte) 0xC5, (byte) 0x01,
+                (byte) 0x1A, (byte) 0xE5, (byte) 0x4D, (byte) 0x2F, (byte) 0xCF, (byte) 0x5D,
+                (byte) 0xB2, (byte) 0xE7, (byte) 0xC7, (byte) 0xCB, (byte) 0x2C, (byte) 0xFF,
+                (byte) 0x51, (byte) 0x1B, (byte) 0x9D, (byte) 0xA4, (byte) 0x05, (byte) 0xEB,
+                (byte) 0x17, (byte) 0xD8, (byte) 0x97, (byte) 0x9D, (byte) 0x0C, (byte) 0x59,
+                (byte) 0x92, (byte) 0x8A, (byte) 0x03, (byte) 0x34, (byte) 0xFD, (byte) 0x16,
+                (byte) 0x0F, (byte) 0x2A, (byte) 0xF9, (byte) 0x7D, (byte) 0xC3, (byte) 0x41,
+                (byte) 0x0D, (byte) 0x06, (byte) 0x5A, (byte) 0x4B, (byte) 0x34, (byte) 0xD5,
+                (byte) 0xF5, (byte) 0x09, (byte) 0x1C, (byte) 0xCE, (byte) 0xA7, (byte) 0x19,
+                (byte) 0x6D, (byte) 0x04, (byte) 0x53, (byte) 0x71, (byte) 0xCC, (byte) 0x84,
+                (byte) 0xA0, (byte) 0xB2, (byte) 0xA0, (byte) 0x68, (byte) 0xA3, (byte) 0x40,
+                (byte) 0xC0, (byte) 0x67, (byte) 0x38, (byte) 0x96, (byte) 0x73, (byte) 0x2E,
+                (byte) 0x8E, (byte) 0x2A, (byte) 0x9D, (byte) 0x56, (byte) 0xE9, (byte) 0xAC,
+                (byte) 0xC7, (byte) 0xEC, (byte) 0x84, (byte) 0x7F, (byte) 0xFC, (byte) 0xE0,
+                (byte) 0x69, (byte) 0x03, (byte) 0x8B, (byte) 0x48, (byte) 0x64, (byte) 0x76,
+                (byte) 0x85, (byte) 0xA5, (byte) 0x10, (byte) 0xD9, (byte) 0x31, (byte) 0xC3,
+                (byte) 0x8B, (byte) 0x07, (byte) 0x48, (byte) 0x62, (byte) 0xF6, (byte) 0x68,
+                (byte) 0xF2, (byte) 0x96, (byte) 0xB2, (byte) 0x18, (byte) 0x5B, (byte) 0xFF,
+                (byte) 0x6D, (byte) 0xD1, (byte) 0x6B, (byte) 0xF5, (byte) 0xFD, (byte) 0x81,
+                (byte) 0xF1, (byte) 0xFD, (byte) 0x04, (byte) 0xF0, (byte) 0x9F, (byte) 0xB7,
+                (byte) 0x08, (byte) 0x95, (byte) 0x57, (byte) 0x48, (byte) 0x07, (byte) 0x00,
+                (byte) 0x52, (byte) 0xEC, (byte) 0x75, (byte) 0x91, (byte) 0x02, (byte) 0x11,
+                (byte) 0xA3, (byte) 0x64, (byte) 0x26, (byte) 0xCA,
+        });
+
+        AlgorithmParameterSpec spec = new DSAParameterSpec(p, q, g);
+        mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
+                .setAlias(TEST_ALIAS_1)
+                .setKeyType("DSA")
+                .setKeySize(2048)
+                .setAlgorithmParameterSpec(spec)
+                .setSubject(TEST_DN_1)
+                .setSerialNumber(TEST_SERIAL_1)
+                .setStartDate(NOW)
+                .setEndDate(NOW_PLUS_10_YEARS)
+                .build());
+
+        final KeyPair pair = mGenerator.generateKeyPair();
+        assertNotNull("The KeyPair returned should not be null", pair);
+
+        assertKeyPairCorrect(pair, TEST_ALIAS_1, "DSA", 2048, spec, TEST_DN_1, TEST_SERIAL_1, NOW,
+                NOW_PLUS_10_YEARS);
+    }
+
+    public void testKeyPairGenerator_GenerateKeyPair_EC_Unencrypted_Success() throws Exception {
+        mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
+                .setAlias(TEST_ALIAS_1)
+                .setKeyType("EC")
+                .setSubject(TEST_DN_1)
+                .setSerialNumber(TEST_SERIAL_1)
+                .setStartDate(NOW)
+                .setEndDate(NOW_PLUS_10_YEARS)
+                .build());
+
+        final KeyPair pair = mGenerator.generateKeyPair();
+        assertNotNull("The KeyPair returned should not be null", pair);
+
+        assertKeyPairCorrect(pair, TEST_ALIAS_1, "EC", 256, null, TEST_DN_1, TEST_SERIAL_1, NOW,
+                NOW_PLUS_10_YEARS);
+    }
+
+    public void testKeyPairGenerator_GenerateKeyPair_EC_P521_Unencrypted_Success() throws Exception {
+        mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
+                .setAlias(TEST_ALIAS_1)
+                .setKeyType("EC")
+                .setKeySize(521)
+                .setSubject(TEST_DN_1)
+                .setSerialNumber(TEST_SERIAL_1)
+                .setStartDate(NOW)
+                .setEndDate(NOW_PLUS_10_YEARS)
+                .build());
+
+        final KeyPair pair = mGenerator.generateKeyPair();
+        assertNotNull("The KeyPair returned should not be null", pair);
+
+        assertKeyPairCorrect(pair, TEST_ALIAS_1, "EC", 521, null, TEST_DN_1, TEST_SERIAL_1, NOW,
+                NOW_PLUS_10_YEARS);
+    }
+
+    public void testKeyPairGenerator_GenerateKeyPair_RSA_Unencrypted_Success() throws Exception {
         mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
                 .setAlias(TEST_ALIAS_1)
                 .setSubject(TEST_DN_1)
@@ -157,7 +363,28 @@
         final KeyPair pair = mGenerator.generateKeyPair();
         assertNotNull("The KeyPair returned should not be null", pair);
 
-        assertKeyPairCorrect(pair, TEST_ALIAS_1, TEST_DN_1, TEST_SERIAL_1, NOW, NOW_PLUS_10_YEARS);
+        assertKeyPairCorrect(pair, TEST_ALIAS_1, "RSA", 2048, null, TEST_DN_1, TEST_SERIAL_1, NOW,
+                NOW_PLUS_10_YEARS);
+    }
+
+    public void testKeyPairGenerator_GenerateKeyPair_RSA_WithParams_Unencrypted_Success()
+            throws Exception {
+        AlgorithmParameterSpec spec = new RSAKeyGenParameterSpec(1024, BigInteger.valueOf(3L));
+        mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
+                .setAlias(TEST_ALIAS_1)
+                .setKeySize(1024)
+                .setAlgorithmParameterSpec(spec)
+                .setSubject(TEST_DN_1)
+                .setSerialNumber(TEST_SERIAL_1)
+                .setStartDate(NOW)
+                .setEndDate(NOW_PLUS_10_YEARS)
+                .build());
+
+        final KeyPair pair = mGenerator.generateKeyPair();
+        assertNotNull("The KeyPair returned should not be null", pair);
+
+        assertKeyPairCorrect(pair, TEST_ALIAS_1, "RSA", 1024, spec, TEST_DN_1, TEST_SERIAL_1, NOW,
+                NOW_PLUS_10_YEARS);
     }
 
     public void testKeyPairGenerator_GenerateKeyPair_Replaced_Success() throws Exception {
@@ -172,8 +399,8 @@
                     .build());
             final KeyPair pair1 = mGenerator.generateKeyPair();
             assertNotNull("The KeyPair returned should not be null", pair1);
-            assertKeyPairCorrect(pair1, TEST_ALIAS_1, TEST_DN_1, TEST_SERIAL_1, NOW,
-                    NOW_PLUS_10_YEARS);
+            assertKeyPairCorrect(pair1, TEST_ALIAS_1, "RSA", 2048, null, TEST_DN_1, TEST_SERIAL_1,
+                    NOW, NOW_PLUS_10_YEARS);
         }
 
         // Replace the original key
@@ -187,8 +414,8 @@
                     .build());
             final KeyPair pair2 = mGenerator.generateKeyPair();
             assertNotNull("The KeyPair returned should not be null", pair2);
-            assertKeyPairCorrect(pair2, TEST_ALIAS_2, TEST_DN_2, TEST_SERIAL_2, NOW,
-                    NOW_PLUS_10_YEARS);
+            assertKeyPairCorrect(pair2, TEST_ALIAS_2, "RSA", 2048, null, TEST_DN_2, TEST_SERIAL_2,
+                    NOW, NOW_PLUS_10_YEARS);
         }
     }
 
@@ -205,8 +432,8 @@
                     .build());
             final KeyPair pair1 = mGenerator.generateKeyPair();
             assertNotNull("The KeyPair returned should not be null", pair1);
-            assertKeyPairCorrect(pair1, TEST_ALIAS_1, TEST_DN_1, TEST_SERIAL_1, NOW,
-                    NOW_PLUS_10_YEARS);
+            assertKeyPairCorrect(pair1, TEST_ALIAS_1, "RSA", 2048, null, TEST_DN_1, TEST_SERIAL_1,
+                    NOW, NOW_PLUS_10_YEARS);
         }
 
         // Attempt to replace previous key
@@ -230,18 +457,45 @@
 
             final KeyPair pair2 = mGenerator.generateKeyPair();
             assertNotNull("The KeyPair returned should not be null", pair2);
-            assertKeyPairCorrect(pair2, TEST_ALIAS_1, TEST_DN_2, TEST_SERIAL_2, NOW,
-                    NOW_PLUS_10_YEARS);
+            assertKeyPairCorrect(pair2, TEST_ALIAS_1, "RSA", 2048, null, TEST_DN_2, TEST_SERIAL_2,
+                    NOW, NOW_PLUS_10_YEARS);
         }
     }
 
-    private void assertKeyPairCorrect(KeyPair pair, String alias, X500Principal dn,
-            BigInteger serial, Date start, Date end) throws Exception {
+    private void assertKeyPairCorrect(KeyPair pair, String alias, String keyType, int keySize,
+            AlgorithmParameterSpec spec, X500Principal dn, BigInteger serial, Date start, Date end)
+            throws Exception {
         final PublicKey pubKey = pair.getPublic();
         assertNotNull("The PublicKey for the KeyPair should be not null", pubKey);
+        assertEquals(keyType, pubKey.getAlgorithm());
+
+        if ("DSA".equalsIgnoreCase(keyType)) {
+            DSAPublicKey dsaPubKey = (DSAPublicKey) pubKey;
+            DSAParams actualParams = dsaPubKey.getParams();
+            assertEquals(keySize, (actualParams.getP().bitLength() + 7) & ~7);
+            if (spec != null) {
+                DSAParameterSpec expectedParams = (DSAParameterSpec) spec;
+                assertEquals(expectedParams.getP(), actualParams.getP());
+                assertEquals(expectedParams.getQ(), actualParams.getQ());
+                assertEquals(expectedParams.getG(), actualParams.getG());
+            }
+        } else if ("EC".equalsIgnoreCase(keyType)) {
+            assertEquals("Curve should be what was specified during initialization", keySize,
+                    ((ECPublicKey) pubKey).getParams().getCurve().getField().getFieldSize());
+        } else if ("RSA".equalsIgnoreCase(keyType)) {
+            RSAPublicKey rsaPubKey = (RSAPublicKey) pubKey;
+            assertEquals("Modulus size should be what is specified during initialization",
+                    (keySize + 7) & ~7, (rsaPubKey.getModulus().bitLength() + 7) & ~7);
+            if (spec != null) {
+                RSAKeyGenParameterSpec params = (RSAKeyGenParameterSpec) spec;
+                assertEquals((keySize + 7) & ~7, (params.getKeysize() + 7) & ~7);
+                assertEquals(params.getPublicExponent(), rsaPubKey.getPublicExponent());
+            }
+        }
 
         final PrivateKey privKey = pair.getPrivate();
         assertNotNull("The PrivateKey for the KeyPair should be not null", privKey);
+        assertEquals(keyType, privKey.getAlgorithm());
 
         final byte[] userCertBytes = mAndroidKeyStore.get(Credentials.USER_CERTIFICATE + alias);
         assertNotNull("The user certificate should exist for the generated entry", userCertBytes);
diff --git a/keystore/tests/src/android/security/AndroidKeyStoreTest.java b/keystore/tests/src/android/security/AndroidKeyStoreTest.java
index b7129db..6597d3f 100644
--- a/keystore/tests/src/android/security/AndroidKeyStoreTest.java
+++ b/keystore/tests/src/android/security/AndroidKeyStoreTest.java
@@ -18,7 +18,9 @@
 
 import com.android.org.bouncycastle.x509.X509V3CertificateGenerator;
 
+import com.android.org.conscrypt.NativeCrypto;
 import com.android.org.conscrypt.OpenSSLEngine;
+import com.android.org.conscrypt.OpenSSLKeyHolder;
 
 import android.test.AndroidTestCase;
 
@@ -39,6 +41,10 @@
 import java.security.cert.Certificate;
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
+import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.DSAPublicKey;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
 import java.security.interfaces.RSAPrivateKey;
 import java.security.spec.InvalidKeySpecException;
 import java.security.spec.PKCS8EncodedKeySpec;
@@ -99,7 +105,7 @@
      *
      * openssl x509 -outform d -in cacert.pem | xxd -i | sed 's/0x/(byte) 0x/g'
      */
-    private static final byte[] FAKE_CA_1 = {
+    private static final byte[] FAKE_RSA_CA_1 = {
             (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0xce, (byte) 0x30, (byte) 0x82,
             (byte) 0x02, (byte) 0x37, (byte) 0xa0, (byte) 0x03, (byte) 0x02, (byte) 0x01,
             (byte) 0x02, (byte) 0x02, (byte) 0x09, (byte) 0x00, (byte) 0xe1, (byte) 0x6a,
@@ -228,7 +234,7 @@
      *
      * openssl pkcs8 -topk8 -outform d -in userkey.pem -nocrypt | xxd -i | sed 's/0x/(byte) 0x/g'
      */
-    private static final byte[] FAKE_KEY_1 = new byte[] {
+    private static final byte[] FAKE_RSA_KEY_1 = new byte[] {
             (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x78, (byte) 0x02, (byte) 0x01,
             (byte) 0x00, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a,
             (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01,
@@ -342,7 +348,7 @@
      *
      * openssl x509 -outform d -in usercert.pem | xxd -i | sed 's/0x/(byte) 0x/g'
      */
-    private static final byte[] FAKE_USER_1 = new byte[] {
+    private static final byte[] FAKE_RSA_USER_1 = new byte[] {
             (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x95, (byte) 0x30, (byte) 0x82,
             (byte) 0x01, (byte) 0xfe, (byte) 0xa0, (byte) 0x03, (byte) 0x02, (byte) 0x01,
             (byte) 0x02, (byte) 0x02, (byte) 0x01, (byte) 0x01, (byte) 0x30, (byte) 0x0d,
@@ -456,6 +462,628 @@
             (byte) 0x08, (byte) 0x41, (byte) 0x0a, (byte) 0xf3, (byte) 0x72
     };
 
+    /*
+     * The keys and certificates below are generated with:
+     *
+     * openssl req -new -x509 -days 3650 -extensions v3_ca -keyout cakey.pem -out cacert.pem
+     * openssl ecparam -name prime256v1 -out ecparam.pem
+     * openssl req -newkey ec:ecparam.pem -keyout userkey.pem -nodes -days 3650 -out userkey.req
+     * mkdir -p demoCA/newcerts
+     * touch demoCA/index.txt
+     * echo "01" > demoCA/serial
+     * openssl ca -out usercert.pem -in userkey.req -cert cacert.pem -keyfile cakey.pem -days 3650
+     */
+
+    /**
+     * Generated from above and converted with:
+     *
+     * openssl x509 -outform d -in cacert.pem | xxd -i | sed 's/0x/(byte) 0x/g'
+     */
+    private static final byte[] FAKE_EC_CA_1 = {
+            (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x58, (byte) 0x30, (byte) 0x82,
+            (byte) 0x01, (byte) 0xc1, (byte) 0xa0, (byte) 0x03, (byte) 0x02, (byte) 0x01,
+            (byte) 0x02, (byte) 0x02, (byte) 0x09, (byte) 0x00, (byte) 0xe1, (byte) 0xb2,
+            (byte) 0x8c, (byte) 0x04, (byte) 0x95, (byte) 0xeb, (byte) 0x10, (byte) 0xcb,
+            (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86,
+            (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01,
+            (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x30, (byte) 0x45, (byte) 0x31,
+            (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55,
+            (byte) 0x04, (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x41, (byte) 0x55,
+            (byte) 0x31, (byte) 0x13, (byte) 0x30, (byte) 0x11, (byte) 0x06, (byte) 0x03,
+            (byte) 0x55, (byte) 0x04, (byte) 0x08, (byte) 0x0c, (byte) 0x0a, (byte) 0x53,
+            (byte) 0x6f, (byte) 0x6d, (byte) 0x65, (byte) 0x2d, (byte) 0x53, (byte) 0x74,
+            (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x31, (byte) 0x21, (byte) 0x30,
+            (byte) 0x1f, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x0a,
+            (byte) 0x0c, (byte) 0x18, (byte) 0x49, (byte) 0x6e, (byte) 0x74, (byte) 0x65,
+            (byte) 0x72, (byte) 0x6e, (byte) 0x65, (byte) 0x74, (byte) 0x20, (byte) 0x57,
+            (byte) 0x69, (byte) 0x64, (byte) 0x67, (byte) 0x69, (byte) 0x74, (byte) 0x73,
+            (byte) 0x20, (byte) 0x50, (byte) 0x74, (byte) 0x79, (byte) 0x20, (byte) 0x4c,
+            (byte) 0x74, (byte) 0x64, (byte) 0x30, (byte) 0x1e, (byte) 0x17, (byte) 0x0d,
+            (byte) 0x31, (byte) 0x33, (byte) 0x30, (byte) 0x38, (byte) 0x32, (byte) 0x37,
+            (byte) 0x31, (byte) 0x36, (byte) 0x32, (byte) 0x38, (byte) 0x32, (byte) 0x38,
+            (byte) 0x5a, (byte) 0x17, (byte) 0x0d, (byte) 0x32, (byte) 0x33, (byte) 0x30,
+            (byte) 0x38, (byte) 0x32, (byte) 0x35, (byte) 0x31, (byte) 0x36, (byte) 0x32,
+            (byte) 0x38, (byte) 0x32, (byte) 0x38, (byte) 0x5a, (byte) 0x30, (byte) 0x45,
+            (byte) 0x31, (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03,
+            (byte) 0x55, (byte) 0x04, (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x41,
+            (byte) 0x55, (byte) 0x31, (byte) 0x13, (byte) 0x30, (byte) 0x11, (byte) 0x06,
+            (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x08, (byte) 0x0c, (byte) 0x0a,
+            (byte) 0x53, (byte) 0x6f, (byte) 0x6d, (byte) 0x65, (byte) 0x2d, (byte) 0x53,
+            (byte) 0x74, (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x31, (byte) 0x21,
+            (byte) 0x30, (byte) 0x1f, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
+            (byte) 0x0a, (byte) 0x0c, (byte) 0x18, (byte) 0x49, (byte) 0x6e, (byte) 0x74,
+            (byte) 0x65, (byte) 0x72, (byte) 0x6e, (byte) 0x65, (byte) 0x74, (byte) 0x20,
+            (byte) 0x57, (byte) 0x69, (byte) 0x64, (byte) 0x67, (byte) 0x69, (byte) 0x74,
+            (byte) 0x73, (byte) 0x20, (byte) 0x50, (byte) 0x74, (byte) 0x79, (byte) 0x20,
+            (byte) 0x4c, (byte) 0x74, (byte) 0x64, (byte) 0x30, (byte) 0x81, (byte) 0x9f,
+            (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86,
+            (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01,
+            (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81, (byte) 0x8d,
+            (byte) 0x00, (byte) 0x30, (byte) 0x81, (byte) 0x89, (byte) 0x02, (byte) 0x81,
+            (byte) 0x81, (byte) 0x00, (byte) 0xb5, (byte) 0xf6, (byte) 0x08, (byte) 0x0f,
+            (byte) 0xc4, (byte) 0x4d, (byte) 0xe4, (byte) 0x0d, (byte) 0x34, (byte) 0x1d,
+            (byte) 0xe2, (byte) 0x23, (byte) 0x18, (byte) 0x63, (byte) 0x03, (byte) 0xf7,
+            (byte) 0x14, (byte) 0x0e, (byte) 0x98, (byte) 0xcd, (byte) 0x45, (byte) 0x1f,
+            (byte) 0xfe, (byte) 0xfb, (byte) 0x09, (byte) 0x3f, (byte) 0x5d, (byte) 0x36,
+            (byte) 0x3b, (byte) 0x0f, (byte) 0xf9, (byte) 0x5e, (byte) 0x86, (byte) 0x56,
+            (byte) 0x64, (byte) 0xd7, (byte) 0x3f, (byte) 0xae, (byte) 0x33, (byte) 0x09,
+            (byte) 0xd3, (byte) 0xdd, (byte) 0x06, (byte) 0x17, (byte) 0x26, (byte) 0xdc,
+            (byte) 0xa2, (byte) 0x8c, (byte) 0x3c, (byte) 0x65, (byte) 0xed, (byte) 0x03,
+            (byte) 0x82, (byte) 0x78, (byte) 0x9b, (byte) 0xee, (byte) 0xe3, (byte) 0x98,
+            (byte) 0x58, (byte) 0xe1, (byte) 0xf1, (byte) 0xa0, (byte) 0x85, (byte) 0xae,
+            (byte) 0x63, (byte) 0x84, (byte) 0x41, (byte) 0x46, (byte) 0xa7, (byte) 0x4f,
+            (byte) 0xdc, (byte) 0xbb, (byte) 0x1c, (byte) 0x6e, (byte) 0xec, (byte) 0x7b,
+            (byte) 0xd5, (byte) 0xab, (byte) 0x3d, (byte) 0x6a, (byte) 0x05, (byte) 0x58,
+            (byte) 0x0f, (byte) 0x9b, (byte) 0x6a, (byte) 0x67, (byte) 0x4b, (byte) 0xe9,
+            (byte) 0x2a, (byte) 0x6d, (byte) 0x96, (byte) 0x11, (byte) 0x53, (byte) 0x95,
+            (byte) 0x78, (byte) 0xaa, (byte) 0xd1, (byte) 0x91, (byte) 0x4a, (byte) 0xf8,
+            (byte) 0x54, (byte) 0x52, (byte) 0x6d, (byte) 0xb9, (byte) 0xca, (byte) 0x74,
+            (byte) 0x81, (byte) 0xf8, (byte) 0x99, (byte) 0x64, (byte) 0xd1, (byte) 0x4f,
+            (byte) 0x01, (byte) 0x38, (byte) 0x4f, (byte) 0x08, (byte) 0x5c, (byte) 0x31,
+            (byte) 0xcb, (byte) 0x7c, (byte) 0x5c, (byte) 0x78, (byte) 0x5d, (byte) 0x47,
+            (byte) 0xd9, (byte) 0xf0, (byte) 0x1a, (byte) 0xeb, (byte) 0x02, (byte) 0x03,
+            (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0xa3, (byte) 0x50, (byte) 0x30,
+            (byte) 0x4e, (byte) 0x30, (byte) 0x1d, (byte) 0x06, (byte) 0x03, (byte) 0x55,
+            (byte) 0x1d, (byte) 0x0e, (byte) 0x04, (byte) 0x16, (byte) 0x04, (byte) 0x14,
+            (byte) 0x5f, (byte) 0x5b, (byte) 0x5e, (byte) 0xac, (byte) 0x29, (byte) 0xfa,
+            (byte) 0xa1, (byte) 0x9f, (byte) 0x9e, (byte) 0xad, (byte) 0x46, (byte) 0xe1,
+            (byte) 0xbc, (byte) 0x20, (byte) 0x72, (byte) 0xcf, (byte) 0x4a, (byte) 0xd4,
+            (byte) 0xfa, (byte) 0xe3, (byte) 0x30, (byte) 0x1f, (byte) 0x06, (byte) 0x03,
+            (byte) 0x55, (byte) 0x1d, (byte) 0x23, (byte) 0x04, (byte) 0x18, (byte) 0x30,
+            (byte) 0x16, (byte) 0x80, (byte) 0x14, (byte) 0x5f, (byte) 0x5b, (byte) 0x5e,
+            (byte) 0xac, (byte) 0x29, (byte) 0xfa, (byte) 0xa1, (byte) 0x9f, (byte) 0x9e,
+            (byte) 0xad, (byte) 0x46, (byte) 0xe1, (byte) 0xbc, (byte) 0x20, (byte) 0x72,
+            (byte) 0xcf, (byte) 0x4a, (byte) 0xd4, (byte) 0xfa, (byte) 0xe3, (byte) 0x30,
+            (byte) 0x0c, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x13,
+            (byte) 0x04, (byte) 0x05, (byte) 0x30, (byte) 0x03, (byte) 0x01, (byte) 0x01,
+            (byte) 0xff, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a,
+            (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01,
+            (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81,
+            (byte) 0x81, (byte) 0x00, (byte) 0xa1, (byte) 0x4a, (byte) 0xe6, (byte) 0xfc,
+            (byte) 0x7f, (byte) 0x17, (byte) 0xaa, (byte) 0x65, (byte) 0x4a, (byte) 0x34,
+            (byte) 0xde, (byte) 0x69, (byte) 0x67, (byte) 0x54, (byte) 0x4d, (byte) 0xa2,
+            (byte) 0xc2, (byte) 0x98, (byte) 0x02, (byte) 0x43, (byte) 0x6a, (byte) 0x0e,
+            (byte) 0x0b, (byte) 0x7f, (byte) 0xa4, (byte) 0x46, (byte) 0xaf, (byte) 0xa4,
+            (byte) 0x65, (byte) 0xa0, (byte) 0xdb, (byte) 0xf1, (byte) 0x5b, (byte) 0xd5,
+            (byte) 0x09, (byte) 0xbc, (byte) 0xee, (byte) 0x37, (byte) 0x51, (byte) 0x19,
+            (byte) 0x36, (byte) 0xc0, (byte) 0x90, (byte) 0xd3, (byte) 0x5f, (byte) 0xf3,
+            (byte) 0x4f, (byte) 0xb9, (byte) 0x08, (byte) 0x45, (byte) 0x0e, (byte) 0x01,
+            (byte) 0x8a, (byte) 0x95, (byte) 0xef, (byte) 0x92, (byte) 0x95, (byte) 0x33,
+            (byte) 0x78, (byte) 0xdd, (byte) 0x90, (byte) 0xbb, (byte) 0xf3, (byte) 0x06,
+            (byte) 0x75, (byte) 0xd0, (byte) 0x66, (byte) 0xe6, (byte) 0xd0, (byte) 0x18,
+            (byte) 0x6e, (byte) 0xeb, (byte) 0x1c, (byte) 0x52, (byte) 0xc3, (byte) 0x2e,
+            (byte) 0x57, (byte) 0x7d, (byte) 0xa9, (byte) 0x03, (byte) 0xdb, (byte) 0xf4,
+            (byte) 0x57, (byte) 0x5f, (byte) 0x6c, (byte) 0x7e, (byte) 0x00, (byte) 0x0d,
+            (byte) 0x8f, (byte) 0xe8, (byte) 0x91, (byte) 0xf7, (byte) 0xae, (byte) 0x24,
+            (byte) 0x35, (byte) 0x07, (byte) 0xb5, (byte) 0x48, (byte) 0x2d, (byte) 0x36,
+            (byte) 0x30, (byte) 0x5d, (byte) 0xe9, (byte) 0x49, (byte) 0x2d, (byte) 0xd1,
+            (byte) 0x5d, (byte) 0xc5, (byte) 0xf4, (byte) 0x33, (byte) 0x77, (byte) 0x3c,
+            (byte) 0x71, (byte) 0xad, (byte) 0x90, (byte) 0x65, (byte) 0xa9, (byte) 0xc1,
+            (byte) 0x0b, (byte) 0x5c, (byte) 0x62, (byte) 0x55, (byte) 0x50, (byte) 0x6f,
+            (byte) 0x9b, (byte) 0xc9, (byte) 0x0d, (byte) 0xee
+    };
+
+    /**
+     * Generated from above and converted with:
+     *
+     * openssl pkcs8 -topk8 -outform d -in userkey.pem -nocrypt | xxd -i | sed 's/0x/(byte) 0x/g'
+     */
+    private static final byte[] FAKE_EC_KEY_1 = new byte[] {
+            (byte) 0x30, (byte) 0x81, (byte) 0x87, (byte) 0x02, (byte) 0x01, (byte) 0x00,
+            (byte) 0x30, (byte) 0x13, (byte) 0x06, (byte) 0x07, (byte) 0x2a, (byte) 0x86,
+            (byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x02, (byte) 0x01, (byte) 0x06,
+            (byte) 0x08, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x3d,
+            (byte) 0x03, (byte) 0x01, (byte) 0x07, (byte) 0x04, (byte) 0x6d, (byte) 0x30,
+            (byte) 0x6b, (byte) 0x02, (byte) 0x01, (byte) 0x01, (byte) 0x04, (byte) 0x20,
+            (byte) 0x3a, (byte) 0x8a, (byte) 0x02, (byte) 0xdc, (byte) 0xde, (byte) 0x70,
+            (byte) 0x84, (byte) 0x45, (byte) 0x34, (byte) 0xaf, (byte) 0xbd, (byte) 0xd5,
+            (byte) 0x02, (byte) 0x17, (byte) 0x69, (byte) 0x90, (byte) 0x65, (byte) 0x1e,
+            (byte) 0x87, (byte) 0xf1, (byte) 0x3d, (byte) 0x17, (byte) 0xb6, (byte) 0xf4,
+            (byte) 0x31, (byte) 0x94, (byte) 0x86, (byte) 0x76, (byte) 0x55, (byte) 0xf7,
+            (byte) 0xcc, (byte) 0xba, (byte) 0xa1, (byte) 0x44, (byte) 0x03, (byte) 0x42,
+            (byte) 0x00, (byte) 0x04, (byte) 0xd9, (byte) 0xcf, (byte) 0xe7, (byte) 0x9b,
+            (byte) 0x23, (byte) 0xc8, (byte) 0xa3, (byte) 0xb8, (byte) 0x33, (byte) 0x14,
+            (byte) 0xa4, (byte) 0x4d, (byte) 0x75, (byte) 0x90, (byte) 0xf3, (byte) 0xcd,
+            (byte) 0x43, (byte) 0xe5, (byte) 0x1b, (byte) 0x05, (byte) 0x1d, (byte) 0xf3,
+            (byte) 0xd0, (byte) 0xa3, (byte) 0xb7, (byte) 0x32, (byte) 0x5f, (byte) 0x79,
+            (byte) 0xdc, (byte) 0x88, (byte) 0xb8, (byte) 0x4d, (byte) 0xb3, (byte) 0xd1,
+            (byte) 0x6d, (byte) 0xf7, (byte) 0x75, (byte) 0xf3, (byte) 0xbf, (byte) 0x50,
+            (byte) 0xa1, (byte) 0xbc, (byte) 0x03, (byte) 0x64, (byte) 0x22, (byte) 0xe6,
+            (byte) 0x1a, (byte) 0xa1, (byte) 0xe1, (byte) 0x06, (byte) 0x68, (byte) 0x3b,
+            (byte) 0xbc, (byte) 0x9f, (byte) 0xd3, (byte) 0xae, (byte) 0x77, (byte) 0x5e,
+            (byte) 0x88, (byte) 0x0c, (byte) 0x5e, (byte) 0x0c, (byte) 0xb2, (byte) 0x38
+    };
+
+    /**
+     * Generated from above and converted with:
+     *
+     * openssl x509 -outform d -in usercert.pem | xxd -i | sed 's/0x/(byte) 0x/g'
+     */
+    private static final byte[] FAKE_EC_USER_1 = new byte[] {
+            (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x51, (byte) 0x30, (byte) 0x82,
+            (byte) 0x01, (byte) 0xba, (byte) 0xa0, (byte) 0x03, (byte) 0x02, (byte) 0x01,
+            (byte) 0x02, (byte) 0x02, (byte) 0x01, (byte) 0x01, (byte) 0x30, (byte) 0x0d,
+            (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86,
+            (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x05,
+            (byte) 0x00, (byte) 0x30, (byte) 0x45, (byte) 0x31, (byte) 0x0b, (byte) 0x30,
+            (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x06,
+            (byte) 0x13, (byte) 0x02, (byte) 0x41, (byte) 0x55, (byte) 0x31, (byte) 0x13,
+            (byte) 0x30, (byte) 0x11, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
+            (byte) 0x08, (byte) 0x0c, (byte) 0x0a, (byte) 0x53, (byte) 0x6f, (byte) 0x6d,
+            (byte) 0x65, (byte) 0x2d, (byte) 0x53, (byte) 0x74, (byte) 0x61, (byte) 0x74,
+            (byte) 0x65, (byte) 0x31, (byte) 0x21, (byte) 0x30, (byte) 0x1f, (byte) 0x06,
+            (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x0a, (byte) 0x0c, (byte) 0x18,
+            (byte) 0x49, (byte) 0x6e, (byte) 0x74, (byte) 0x65, (byte) 0x72, (byte) 0x6e,
+            (byte) 0x65, (byte) 0x74, (byte) 0x20, (byte) 0x57, (byte) 0x69, (byte) 0x64,
+            (byte) 0x67, (byte) 0x69, (byte) 0x74, (byte) 0x73, (byte) 0x20, (byte) 0x50,
+            (byte) 0x74, (byte) 0x79, (byte) 0x20, (byte) 0x4c, (byte) 0x74, (byte) 0x64,
+            (byte) 0x30, (byte) 0x1e, (byte) 0x17, (byte) 0x0d, (byte) 0x31, (byte) 0x33,
+            (byte) 0x30, (byte) 0x38, (byte) 0x32, (byte) 0x37, (byte) 0x31, (byte) 0x36,
+            (byte) 0x33, (byte) 0x30, (byte) 0x30, (byte) 0x38, (byte) 0x5a, (byte) 0x17,
+            (byte) 0x0d, (byte) 0x32, (byte) 0x33, (byte) 0x30, (byte) 0x38, (byte) 0x32,
+            (byte) 0x35, (byte) 0x31, (byte) 0x36, (byte) 0x33, (byte) 0x30, (byte) 0x30,
+            (byte) 0x38, (byte) 0x5a, (byte) 0x30, (byte) 0x62, (byte) 0x31, (byte) 0x0b,
+            (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
+            (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x41, (byte) 0x55, (byte) 0x31,
+            (byte) 0x13, (byte) 0x30, (byte) 0x11, (byte) 0x06, (byte) 0x03, (byte) 0x55,
+            (byte) 0x04, (byte) 0x08, (byte) 0x0c, (byte) 0x0a, (byte) 0x53, (byte) 0x6f,
+            (byte) 0x6d, (byte) 0x65, (byte) 0x2d, (byte) 0x53, (byte) 0x74, (byte) 0x61,
+            (byte) 0x74, (byte) 0x65, (byte) 0x31, (byte) 0x21, (byte) 0x30, (byte) 0x1f,
+            (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x0a, (byte) 0x0c,
+            (byte) 0x18, (byte) 0x49, (byte) 0x6e, (byte) 0x74, (byte) 0x65, (byte) 0x72,
+            (byte) 0x6e, (byte) 0x65, (byte) 0x74, (byte) 0x20, (byte) 0x57, (byte) 0x69,
+            (byte) 0x64, (byte) 0x67, (byte) 0x69, (byte) 0x74, (byte) 0x73, (byte) 0x20,
+            (byte) 0x50, (byte) 0x74, (byte) 0x79, (byte) 0x20, (byte) 0x4c, (byte) 0x74,
+            (byte) 0x64, (byte) 0x31, (byte) 0x1b, (byte) 0x30, (byte) 0x19, (byte) 0x06,
+            (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x03, (byte) 0x0c, (byte) 0x12,
+            (byte) 0x73, (byte) 0x65, (byte) 0x72, (byte) 0x76, (byte) 0x65, (byte) 0x72,
+            (byte) 0x2e, (byte) 0x65, (byte) 0x78, (byte) 0x61, (byte) 0x6d, (byte) 0x70,
+            (byte) 0x6c, (byte) 0x65, (byte) 0x2e, (byte) 0x63, (byte) 0x6f, (byte) 0x6d,
+            (byte) 0x30, (byte) 0x59, (byte) 0x30, (byte) 0x13, (byte) 0x06, (byte) 0x07,
+            (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x02,
+            (byte) 0x01, (byte) 0x06, (byte) 0x08, (byte) 0x2a, (byte) 0x86, (byte) 0x48,
+            (byte) 0xce, (byte) 0x3d, (byte) 0x03, (byte) 0x01, (byte) 0x07, (byte) 0x03,
+            (byte) 0x42, (byte) 0x00, (byte) 0x04, (byte) 0xd9, (byte) 0xcf, (byte) 0xe7,
+            (byte) 0x9b, (byte) 0x23, (byte) 0xc8, (byte) 0xa3, (byte) 0xb8, (byte) 0x33,
+            (byte) 0x14, (byte) 0xa4, (byte) 0x4d, (byte) 0x75, (byte) 0x90, (byte) 0xf3,
+            (byte) 0xcd, (byte) 0x43, (byte) 0xe5, (byte) 0x1b, (byte) 0x05, (byte) 0x1d,
+            (byte) 0xf3, (byte) 0xd0, (byte) 0xa3, (byte) 0xb7, (byte) 0x32, (byte) 0x5f,
+            (byte) 0x79, (byte) 0xdc, (byte) 0x88, (byte) 0xb8, (byte) 0x4d, (byte) 0xb3,
+            (byte) 0xd1, (byte) 0x6d, (byte) 0xf7, (byte) 0x75, (byte) 0xf3, (byte) 0xbf,
+            (byte) 0x50, (byte) 0xa1, (byte) 0xbc, (byte) 0x03, (byte) 0x64, (byte) 0x22,
+            (byte) 0xe6, (byte) 0x1a, (byte) 0xa1, (byte) 0xe1, (byte) 0x06, (byte) 0x68,
+            (byte) 0x3b, (byte) 0xbc, (byte) 0x9f, (byte) 0xd3, (byte) 0xae, (byte) 0x77,
+            (byte) 0x5e, (byte) 0x88, (byte) 0x0c, (byte) 0x5e, (byte) 0x0c, (byte) 0xb2,
+            (byte) 0x38, (byte) 0xa3, (byte) 0x7b, (byte) 0x30, (byte) 0x79, (byte) 0x30,
+            (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x13,
+            (byte) 0x04, (byte) 0x02, (byte) 0x30, (byte) 0x00, (byte) 0x30, (byte) 0x2c,
+            (byte) 0x06, (byte) 0x09, (byte) 0x60, (byte) 0x86, (byte) 0x48, (byte) 0x01,
+            (byte) 0x86, (byte) 0xf8, (byte) 0x42, (byte) 0x01, (byte) 0x0d, (byte) 0x04,
+            (byte) 0x1f, (byte) 0x16, (byte) 0x1d, (byte) 0x4f, (byte) 0x70, (byte) 0x65,
+            (byte) 0x6e, (byte) 0x53, (byte) 0x53, (byte) 0x4c, (byte) 0x20, (byte) 0x47,
+            (byte) 0x65, (byte) 0x6e, (byte) 0x65, (byte) 0x72, (byte) 0x61, (byte) 0x74,
+            (byte) 0x65, (byte) 0x64, (byte) 0x20, (byte) 0x43, (byte) 0x65, (byte) 0x72,
+            (byte) 0x74, (byte) 0x69, (byte) 0x66, (byte) 0x69, (byte) 0x63, (byte) 0x61,
+            (byte) 0x74, (byte) 0x65, (byte) 0x30, (byte) 0x1d, (byte) 0x06, (byte) 0x03,
+            (byte) 0x55, (byte) 0x1d, (byte) 0x0e, (byte) 0x04, (byte) 0x16, (byte) 0x04,
+            (byte) 0x14, (byte) 0xd5, (byte) 0xc4, (byte) 0x72, (byte) 0xbd, (byte) 0xd2,
+            (byte) 0x4e, (byte) 0x90, (byte) 0x1b, (byte) 0x14, (byte) 0x32, (byte) 0xdb,
+            (byte) 0x03, (byte) 0xae, (byte) 0xfa, (byte) 0x27, (byte) 0x7d, (byte) 0x8d,
+            (byte) 0xe4, (byte) 0x80, (byte) 0x58, (byte) 0x30, (byte) 0x1f, (byte) 0x06,
+            (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x23, (byte) 0x04, (byte) 0x18,
+            (byte) 0x30, (byte) 0x16, (byte) 0x80, (byte) 0x14, (byte) 0x5f, (byte) 0x5b,
+            (byte) 0x5e, (byte) 0xac, (byte) 0x29, (byte) 0xfa, (byte) 0xa1, (byte) 0x9f,
+            (byte) 0x9e, (byte) 0xad, (byte) 0x46, (byte) 0xe1, (byte) 0xbc, (byte) 0x20,
+            (byte) 0x72, (byte) 0xcf, (byte) 0x4a, (byte) 0xd4, (byte) 0xfa, (byte) 0xe3,
+            (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86,
+            (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01,
+            (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81, (byte) 0x81,
+            (byte) 0x00, (byte) 0x43, (byte) 0x99, (byte) 0x9f, (byte) 0x67, (byte) 0x08,
+            (byte) 0x43, (byte) 0xd5, (byte) 0x6b, (byte) 0x6f, (byte) 0xd7, (byte) 0x05,
+            (byte) 0xd6, (byte) 0x75, (byte) 0x34, (byte) 0x30, (byte) 0xca, (byte) 0x20,
+            (byte) 0x47, (byte) 0x61, (byte) 0xa1, (byte) 0x89, (byte) 0xb6, (byte) 0xf1,
+            (byte) 0x49, (byte) 0x7b, (byte) 0xd9, (byte) 0xb9, (byte) 0xe8, (byte) 0x1e,
+            (byte) 0x29, (byte) 0x74, (byte) 0x0a, (byte) 0x67, (byte) 0xc0, (byte) 0x7d,
+            (byte) 0xb8, (byte) 0xe6, (byte) 0x39, (byte) 0xa8, (byte) 0x5e, (byte) 0xc3,
+            (byte) 0xb0, (byte) 0xa1, (byte) 0x30, (byte) 0x6a, (byte) 0x1f, (byte) 0x1d,
+            (byte) 0xfc, (byte) 0x11, (byte) 0x59, (byte) 0x0b, (byte) 0xb9, (byte) 0xad,
+            (byte) 0x3a, (byte) 0x4e, (byte) 0x50, (byte) 0x0a, (byte) 0x61, (byte) 0xdb,
+            (byte) 0x75, (byte) 0x6b, (byte) 0xe5, (byte) 0x3f, (byte) 0x8d, (byte) 0xde,
+            (byte) 0x28, (byte) 0x68, (byte) 0xb1, (byte) 0x29, (byte) 0x9a, (byte) 0x18,
+            (byte) 0x8a, (byte) 0xfc, (byte) 0x3f, (byte) 0x13, (byte) 0x93, (byte) 0x29,
+            (byte) 0xed, (byte) 0x22, (byte) 0x7c, (byte) 0xb4, (byte) 0x50, (byte) 0xd5,
+            (byte) 0x4d, (byte) 0x32, (byte) 0x4d, (byte) 0x42, (byte) 0x2b, (byte) 0x29,
+            (byte) 0x97, (byte) 0x86, (byte) 0xc0, (byte) 0x01, (byte) 0x00, (byte) 0x25,
+            (byte) 0xf6, (byte) 0xd3, (byte) 0x2a, (byte) 0xd8, (byte) 0xda, (byte) 0x13,
+            (byte) 0x94, (byte) 0x12, (byte) 0x78, (byte) 0x14, (byte) 0x0b, (byte) 0x51,
+            (byte) 0xc0, (byte) 0x45, (byte) 0xb4, (byte) 0x02, (byte) 0x37, (byte) 0x98,
+            (byte) 0x42, (byte) 0x3c, (byte) 0xcb, (byte) 0x2e, (byte) 0xe4, (byte) 0x38,
+            (byte) 0x69, (byte) 0x1b, (byte) 0x72, (byte) 0xf0, (byte) 0xaa, (byte) 0x89,
+            (byte) 0x7e, (byte) 0xde, (byte) 0xb2
+    };
+
+    /*
+     * The keys and certificates below are generated with:
+     *
+     * openssl req -new -x509 -days 3650 -extensions v3_ca -keyout cakey.pem -out cacert.pem
+     * openssl dsaparam -out dsaparam.pem 1024
+     * openssl req -newkey dsa:dsaparam.pem -keyout userkey.pem -nodes -days 3650 -out userkey.req
+     * mkdir -p demoCA/newcerts
+     * touch demoCA/index.txt
+     * echo "01" > demoCA/serial
+     * openssl ca -out usercert.pem -in userkey.req -cert cacert.pem -keyfile cakey.pem -days 3650
+     */
+
+    /**
+     * Generated from above and converted with:
+     *
+     * openssl x509 -outform d -in cacert.pem | xxd -i | sed 's/0x/(byte) 0x/g'
+     */
+    private static final byte[] FAKE_DSA_CA_1 = new byte[] {
+            (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x8a, (byte) 0x30, (byte) 0x82,
+            (byte) 0x01, (byte) 0xf3, (byte) 0xa0, (byte) 0x03, (byte) 0x02, (byte) 0x01,
+            (byte) 0x02, (byte) 0x02, (byte) 0x09, (byte) 0x00, (byte) 0x87, (byte) 0xc0,
+            (byte) 0x68, (byte) 0x7f, (byte) 0x42, (byte) 0x92, (byte) 0x0b, (byte) 0x7a,
+            (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86,
+            (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01,
+            (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x30, (byte) 0x5e, (byte) 0x31,
+            (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55,
+            (byte) 0x04, (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x41, (byte) 0x55,
+            (byte) 0x31, (byte) 0x13, (byte) 0x30, (byte) 0x11, (byte) 0x06, (byte) 0x03,
+            (byte) 0x55, (byte) 0x04, (byte) 0x08, (byte) 0x0c, (byte) 0x0a, (byte) 0x53,
+            (byte) 0x6f, (byte) 0x6d, (byte) 0x65, (byte) 0x2d, (byte) 0x53, (byte) 0x74,
+            (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x31, (byte) 0x21, (byte) 0x30,
+            (byte) 0x1f, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x0a,
+            (byte) 0x0c, (byte) 0x18, (byte) 0x49, (byte) 0x6e, (byte) 0x74, (byte) 0x65,
+            (byte) 0x72, (byte) 0x6e, (byte) 0x65, (byte) 0x74, (byte) 0x20, (byte) 0x57,
+            (byte) 0x69, (byte) 0x64, (byte) 0x67, (byte) 0x69, (byte) 0x74, (byte) 0x73,
+            (byte) 0x20, (byte) 0x50, (byte) 0x74, (byte) 0x79, (byte) 0x20, (byte) 0x4c,
+            (byte) 0x74, (byte) 0x64, (byte) 0x31, (byte) 0x17, (byte) 0x30, (byte) 0x15,
+            (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x03, (byte) 0x0c,
+            (byte) 0x0e, (byte) 0x63, (byte) 0x61, (byte) 0x2e, (byte) 0x65, (byte) 0x78,
+            (byte) 0x61, (byte) 0x6d, (byte) 0x70, (byte) 0x6c, (byte) 0x65, (byte) 0x2e,
+            (byte) 0x63, (byte) 0x6f, (byte) 0x6d, (byte) 0x30, (byte) 0x1e, (byte) 0x17,
+            (byte) 0x0d, (byte) 0x31, (byte) 0x33, (byte) 0x30, (byte) 0x38, (byte) 0x32,
+            (byte) 0x37, (byte) 0x32, (byte) 0x33, (byte) 0x33, (byte) 0x31, (byte) 0x32,
+            (byte) 0x39, (byte) 0x5a, (byte) 0x17, (byte) 0x0d, (byte) 0x32, (byte) 0x33,
+            (byte) 0x30, (byte) 0x38, (byte) 0x32, (byte) 0x35, (byte) 0x32, (byte) 0x33,
+            (byte) 0x33, (byte) 0x31, (byte) 0x32, (byte) 0x39, (byte) 0x5a, (byte) 0x30,
+            (byte) 0x5e, (byte) 0x31, (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06,
+            (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x06, (byte) 0x13, (byte) 0x02,
+            (byte) 0x41, (byte) 0x55, (byte) 0x31, (byte) 0x13, (byte) 0x30, (byte) 0x11,
+            (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x08, (byte) 0x0c,
+            (byte) 0x0a, (byte) 0x53, (byte) 0x6f, (byte) 0x6d, (byte) 0x65, (byte) 0x2d,
+            (byte) 0x53, (byte) 0x74, (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x31,
+            (byte) 0x21, (byte) 0x30, (byte) 0x1f, (byte) 0x06, (byte) 0x03, (byte) 0x55,
+            (byte) 0x04, (byte) 0x0a, (byte) 0x0c, (byte) 0x18, (byte) 0x49, (byte) 0x6e,
+            (byte) 0x74, (byte) 0x65, (byte) 0x72, (byte) 0x6e, (byte) 0x65, (byte) 0x74,
+            (byte) 0x20, (byte) 0x57, (byte) 0x69, (byte) 0x64, (byte) 0x67, (byte) 0x69,
+            (byte) 0x74, (byte) 0x73, (byte) 0x20, (byte) 0x50, (byte) 0x74, (byte) 0x79,
+            (byte) 0x20, (byte) 0x4c, (byte) 0x74, (byte) 0x64, (byte) 0x31, (byte) 0x17,
+            (byte) 0x30, (byte) 0x15, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
+            (byte) 0x03, (byte) 0x0c, (byte) 0x0e, (byte) 0x63, (byte) 0x61, (byte) 0x2e,
+            (byte) 0x65, (byte) 0x78, (byte) 0x61, (byte) 0x6d, (byte) 0x70, (byte) 0x6c,
+            (byte) 0x65, (byte) 0x2e, (byte) 0x63, (byte) 0x6f, (byte) 0x6d, (byte) 0x30,
+            (byte) 0x81, (byte) 0x9f, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09,
+            (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d,
+            (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x03,
+            (byte) 0x81, (byte) 0x8d, (byte) 0x00, (byte) 0x30, (byte) 0x81, (byte) 0x89,
+            (byte) 0x02, (byte) 0x81, (byte) 0x81, (byte) 0x00, (byte) 0xa4, (byte) 0xc7,
+            (byte) 0x06, (byte) 0xba, (byte) 0xdf, (byte) 0x2b, (byte) 0xee, (byte) 0xd2,
+            (byte) 0xb9, (byte) 0xe4, (byte) 0x52, (byte) 0x21, (byte) 0x68, (byte) 0x2b,
+            (byte) 0x83, (byte) 0xdf, (byte) 0xe3, (byte) 0x9c, (byte) 0x08, (byte) 0x73,
+            (byte) 0xdd, (byte) 0x90, (byte) 0xea, (byte) 0x97, (byte) 0x0c, (byte) 0x96,
+            (byte) 0x20, (byte) 0xb1, (byte) 0xee, (byte) 0x11, (byte) 0xd5, (byte) 0xd4,
+            (byte) 0x7c, (byte) 0x44, (byte) 0x96, (byte) 0x2e, (byte) 0x6e, (byte) 0xa2,
+            (byte) 0xb2, (byte) 0xa3, (byte) 0x4b, (byte) 0x0f, (byte) 0x32, (byte) 0x90,
+            (byte) 0xaf, (byte) 0x5c, (byte) 0x6f, (byte) 0x00, (byte) 0x88, (byte) 0x45,
+            (byte) 0x4e, (byte) 0x9b, (byte) 0x26, (byte) 0xc1, (byte) 0x94, (byte) 0x3c,
+            (byte) 0xfe, (byte) 0x10, (byte) 0xbd, (byte) 0xda, (byte) 0xf2, (byte) 0x8d,
+            (byte) 0x03, (byte) 0x52, (byte) 0x32, (byte) 0x11, (byte) 0xff, (byte) 0xf6,
+            (byte) 0xf9, (byte) 0x6e, (byte) 0x8f, (byte) 0x0f, (byte) 0xc8, (byte) 0x0a,
+            (byte) 0x48, (byte) 0x39, (byte) 0x33, (byte) 0xb9, (byte) 0x0c, (byte) 0xb3,
+            (byte) 0x2b, (byte) 0xab, (byte) 0x7d, (byte) 0x79, (byte) 0x6f, (byte) 0x57,
+            (byte) 0x5b, (byte) 0xb8, (byte) 0x84, (byte) 0xb6, (byte) 0xcc, (byte) 0xe8,
+            (byte) 0x30, (byte) 0x78, (byte) 0xff, (byte) 0x92, (byte) 0xe5, (byte) 0x43,
+            (byte) 0x2e, (byte) 0xef, (byte) 0x66, (byte) 0x98, (byte) 0xb4, (byte) 0xfe,
+            (byte) 0xa2, (byte) 0x40, (byte) 0xf2, (byte) 0x1f, (byte) 0xd0, (byte) 0x86,
+            (byte) 0x16, (byte) 0xc8, (byte) 0x45, (byte) 0xc4, (byte) 0x52, (byte) 0xcb,
+            (byte) 0x31, (byte) 0x5c, (byte) 0x9f, (byte) 0x32, (byte) 0x3b, (byte) 0xf7,
+            (byte) 0x19, (byte) 0x08, (byte) 0xc7, (byte) 0x00, (byte) 0x21, (byte) 0x7d,
+            (byte) 0x02, (byte) 0x03, (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0xa3,
+            (byte) 0x50, (byte) 0x30, (byte) 0x4e, (byte) 0x30, (byte) 0x1d, (byte) 0x06,
+            (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x0e, (byte) 0x04, (byte) 0x16,
+            (byte) 0x04, (byte) 0x14, (byte) 0x47, (byte) 0x82, (byte) 0xa3, (byte) 0xf1,
+            (byte) 0xc2, (byte) 0x7e, (byte) 0x3a, (byte) 0xde, (byte) 0x4f, (byte) 0x30,
+            (byte) 0x4c, (byte) 0x7f, (byte) 0x72, (byte) 0x81, (byte) 0x15, (byte) 0x32,
+            (byte) 0xda, (byte) 0x7f, (byte) 0x58, (byte) 0x18, (byte) 0x30, (byte) 0x1f,
+            (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x23, (byte) 0x04,
+            (byte) 0x18, (byte) 0x30, (byte) 0x16, (byte) 0x80, (byte) 0x14, (byte) 0x47,
+            (byte) 0x82, (byte) 0xa3, (byte) 0xf1, (byte) 0xc2, (byte) 0x7e, (byte) 0x3a,
+            (byte) 0xde, (byte) 0x4f, (byte) 0x30, (byte) 0x4c, (byte) 0x7f, (byte) 0x72,
+            (byte) 0x81, (byte) 0x15, (byte) 0x32, (byte) 0xda, (byte) 0x7f, (byte) 0x58,
+            (byte) 0x18, (byte) 0x30, (byte) 0x0c, (byte) 0x06, (byte) 0x03, (byte) 0x55,
+            (byte) 0x1d, (byte) 0x13, (byte) 0x04, (byte) 0x05, (byte) 0x30, (byte) 0x03,
+            (byte) 0x01, (byte) 0x01, (byte) 0xff, (byte) 0x30, (byte) 0x0d, (byte) 0x06,
+            (byte) 0x09, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7,
+            (byte) 0x0d, (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x00,
+            (byte) 0x03, (byte) 0x81, (byte) 0x81, (byte) 0x00, (byte) 0x08, (byte) 0x7f,
+            (byte) 0x6a, (byte) 0x48, (byte) 0x90, (byte) 0x7b, (byte) 0x9b, (byte) 0x72,
+            (byte) 0x13, (byte) 0xa7, (byte) 0xef, (byte) 0x6b, (byte) 0x0b, (byte) 0x59,
+            (byte) 0xe5, (byte) 0x49, (byte) 0x72, (byte) 0x3a, (byte) 0xc8, (byte) 0x84,
+            (byte) 0xcc, (byte) 0x23, (byte) 0x18, (byte) 0x4c, (byte) 0xec, (byte) 0xc7,
+            (byte) 0xef, (byte) 0xcb, (byte) 0xa7, (byte) 0xbe, (byte) 0xe4, (byte) 0xef,
+            (byte) 0x8f, (byte) 0xc6, (byte) 0x06, (byte) 0x8c, (byte) 0xc0, (byte) 0xe4,
+            (byte) 0x2f, (byte) 0x2a, (byte) 0xc0, (byte) 0x35, (byte) 0x7d, (byte) 0x5e,
+            (byte) 0x19, (byte) 0x29, (byte) 0x8c, (byte) 0xb9, (byte) 0xf1, (byte) 0x1e,
+            (byte) 0xaf, (byte) 0x82, (byte) 0xd8, (byte) 0xe3, (byte) 0x88, (byte) 0xe1,
+            (byte) 0x31, (byte) 0xc8, (byte) 0x82, (byte) 0x1f, (byte) 0x83, (byte) 0xa9,
+            (byte) 0xde, (byte) 0xfe, (byte) 0x4b, (byte) 0xe2, (byte) 0x78, (byte) 0x64,
+            (byte) 0xed, (byte) 0xa4, (byte) 0x7b, (byte) 0xee, (byte) 0x8d, (byte) 0x71,
+            (byte) 0x1b, (byte) 0x44, (byte) 0xe6, (byte) 0xb7, (byte) 0xe8, (byte) 0xc5,
+            (byte) 0x9a, (byte) 0x93, (byte) 0x92, (byte) 0x6f, (byte) 0x6f, (byte) 0xdb,
+            (byte) 0xbd, (byte) 0xd7, (byte) 0x03, (byte) 0x85, (byte) 0xa9, (byte) 0x5f,
+            (byte) 0x53, (byte) 0x5f, (byte) 0x5d, (byte) 0x30, (byte) 0xc6, (byte) 0xd9,
+            (byte) 0xce, (byte) 0x34, (byte) 0xa8, (byte) 0xbe, (byte) 0x31, (byte) 0x47,
+            (byte) 0x1c, (byte) 0xa4, (byte) 0x7f, (byte) 0xc0, (byte) 0x2c, (byte) 0xbc,
+            (byte) 0xfe, (byte) 0x1a, (byte) 0x31, (byte) 0xd8, (byte) 0x77, (byte) 0x4d,
+            (byte) 0xfc, (byte) 0x45, (byte) 0x84, (byte) 0xfc, (byte) 0x45, (byte) 0x12,
+            (byte) 0xab, (byte) 0x50, (byte) 0xe4, (byte) 0x45, (byte) 0xe5, (byte) 0x11
+    };
+
+    /**
+     * Generated from above and converted with: openssl pkcs8 -topk8 -outform d
+     * -in userkey.pem -nocrypt | xxd -i | sed 's/0x/(byte) 0x/g'
+     */
+    private static final byte[] FAKE_DSA_KEY_1 = new byte[] {
+            (byte) 0x30, (byte) 0x82, (byte) 0x01, (byte) 0x4c, (byte) 0x02, (byte) 0x01,
+            (byte) 0x00, (byte) 0x30, (byte) 0x82, (byte) 0x01, (byte) 0x2c, (byte) 0x06,
+            (byte) 0x07, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x38,
+            (byte) 0x04, (byte) 0x01, (byte) 0x30, (byte) 0x82, (byte) 0x01, (byte) 0x1f,
+            (byte) 0x02, (byte) 0x81, (byte) 0x81, (byte) 0x00, (byte) 0xb3, (byte) 0x23,
+            (byte) 0xf7, (byte) 0x86, (byte) 0xbd, (byte) 0x3b, (byte) 0x86, (byte) 0xcc,
+            (byte) 0xc3, (byte) 0x91, (byte) 0xc0, (byte) 0x30, (byte) 0x32, (byte) 0x02,
+            (byte) 0x47, (byte) 0x35, (byte) 0x01, (byte) 0xef, (byte) 0xee, (byte) 0x98,
+            (byte) 0x13, (byte) 0x56, (byte) 0x49, (byte) 0x47, (byte) 0xb5, (byte) 0x20,
+            (byte) 0xa8, (byte) 0x60, (byte) 0xcb, (byte) 0xc0, (byte) 0xd5, (byte) 0x77,
+            (byte) 0xc1, (byte) 0x69, (byte) 0xcd, (byte) 0x18, (byte) 0x34, (byte) 0x92,
+            (byte) 0xf2, (byte) 0x6a, (byte) 0x2a, (byte) 0x10, (byte) 0x59, (byte) 0x1c,
+            (byte) 0x91, (byte) 0x20, (byte) 0x51, (byte) 0xca, (byte) 0x37, (byte) 0xb2,
+            (byte) 0x87, (byte) 0xa6, (byte) 0x8a, (byte) 0x02, (byte) 0xfd, (byte) 0x45,
+            (byte) 0x46, (byte) 0xf9, (byte) 0x76, (byte) 0xb1, (byte) 0x35, (byte) 0x38,
+            (byte) 0x8d, (byte) 0xff, (byte) 0x4c, (byte) 0x5d, (byte) 0x75, (byte) 0x8f,
+            (byte) 0x66, (byte) 0x15, (byte) 0x7d, (byte) 0x7b, (byte) 0xda, (byte) 0xdb,
+            (byte) 0x57, (byte) 0x39, (byte) 0xff, (byte) 0x91, (byte) 0x3f, (byte) 0xdd,
+            (byte) 0xe2, (byte) 0xb4, (byte) 0x22, (byte) 0x60, (byte) 0x4c, (byte) 0x32,
+            (byte) 0x3b, (byte) 0x9d, (byte) 0x34, (byte) 0x9f, (byte) 0xb9, (byte) 0x5d,
+            (byte) 0x75, (byte) 0xb9, (byte) 0xd3, (byte) 0x7f, (byte) 0x11, (byte) 0xba,
+            (byte) 0xb7, (byte) 0xc8, (byte) 0x32, (byte) 0xc6, (byte) 0xce, (byte) 0x71,
+            (byte) 0x91, (byte) 0xd3, (byte) 0x32, (byte) 0xaf, (byte) 0x4d, (byte) 0x7e,
+            (byte) 0x7c, (byte) 0x15, (byte) 0xf7, (byte) 0x71, (byte) 0x2c, (byte) 0x52,
+            (byte) 0x65, (byte) 0x4d, (byte) 0xa9, (byte) 0x81, (byte) 0x25, (byte) 0x35,
+            (byte) 0xce, (byte) 0x0b, (byte) 0x5b, (byte) 0x56, (byte) 0xfe, (byte) 0xf1,
+            (byte) 0x02, (byte) 0x15, (byte) 0x00, (byte) 0xeb, (byte) 0x4e, (byte) 0x7f,
+            (byte) 0x7a, (byte) 0x31, (byte) 0xb3, (byte) 0x7d, (byte) 0x8d, (byte) 0xb2,
+            (byte) 0xf7, (byte) 0xaf, (byte) 0xad, (byte) 0xb1, (byte) 0x42, (byte) 0x92,
+            (byte) 0xf3, (byte) 0x6c, (byte) 0xe4, (byte) 0xed, (byte) 0x8b, (byte) 0x02,
+            (byte) 0x81, (byte) 0x81, (byte) 0x00, (byte) 0x81, (byte) 0xc8, (byte) 0x36,
+            (byte) 0x48, (byte) 0xdb, (byte) 0x71, (byte) 0x2b, (byte) 0x91, (byte) 0xce,
+            (byte) 0x6d, (byte) 0xbc, (byte) 0xb8, (byte) 0xf9, (byte) 0xcb, (byte) 0x50,
+            (byte) 0x91, (byte) 0x10, (byte) 0x8a, (byte) 0xf8, (byte) 0x37, (byte) 0x50,
+            (byte) 0xda, (byte) 0x4f, (byte) 0xc8, (byte) 0x4d, (byte) 0x73, (byte) 0xcb,
+            (byte) 0x4d, (byte) 0xb0, (byte) 0x19, (byte) 0x54, (byte) 0x5a, (byte) 0xf3,
+            (byte) 0x6c, (byte) 0xc9, (byte) 0xd8, (byte) 0x96, (byte) 0xd9, (byte) 0xb0,
+            (byte) 0x54, (byte) 0x7e, (byte) 0x7d, (byte) 0xe2, (byte) 0x58, (byte) 0x0e,
+            (byte) 0x5f, (byte) 0xc0, (byte) 0xce, (byte) 0xb9, (byte) 0x5c, (byte) 0xe3,
+            (byte) 0xd3, (byte) 0xdf, (byte) 0xcf, (byte) 0x45, (byte) 0x74, (byte) 0xfb,
+            (byte) 0xe6, (byte) 0x20, (byte) 0xe7, (byte) 0xfc, (byte) 0x0f, (byte) 0xca,
+            (byte) 0xdb, (byte) 0xc0, (byte) 0x0b, (byte) 0xe1, (byte) 0x5a, (byte) 0x16,
+            (byte) 0x1d, (byte) 0xb3, (byte) 0x2e, (byte) 0xe5, (byte) 0x5f, (byte) 0x89,
+            (byte) 0x17, (byte) 0x73, (byte) 0x50, (byte) 0xd1, (byte) 0x4a, (byte) 0x60,
+            (byte) 0xb7, (byte) 0xaa, (byte) 0xf0, (byte) 0xc7, (byte) 0xc5, (byte) 0x03,
+            (byte) 0x4e, (byte) 0x36, (byte) 0x51, (byte) 0x9e, (byte) 0x2f, (byte) 0xfa,
+            (byte) 0xf3, (byte) 0xd6, (byte) 0x58, (byte) 0x14, (byte) 0x02, (byte) 0xb4,
+            (byte) 0x41, (byte) 0xd6, (byte) 0x72, (byte) 0x6f, (byte) 0x58, (byte) 0x5b,
+            (byte) 0x2d, (byte) 0x23, (byte) 0xc0, (byte) 0x75, (byte) 0x4f, (byte) 0x39,
+            (byte) 0xa8, (byte) 0x6a, (byte) 0xdf, (byte) 0x79, (byte) 0x21, (byte) 0xf2,
+            (byte) 0x77, (byte) 0x91, (byte) 0x3f, (byte) 0x1c, (byte) 0x4d, (byte) 0x48,
+            (byte) 0x78, (byte) 0xcd, (byte) 0xed, (byte) 0x79, (byte) 0x23, (byte) 0x04,
+            (byte) 0x17, (byte) 0x02, (byte) 0x15, (byte) 0x00, (byte) 0xc7, (byte) 0xe7,
+            (byte) 0xe2, (byte) 0x6b, (byte) 0x14, (byte) 0xe6, (byte) 0x31, (byte) 0x12,
+            (byte) 0xb2, (byte) 0x1e, (byte) 0xd4, (byte) 0xf2, (byte) 0x9b, (byte) 0x2c,
+            (byte) 0xf6, (byte) 0x54, (byte) 0x4c, (byte) 0x12, (byte) 0xe8, (byte) 0x22
+    };
+
+    /**
+     * Generated from above and converted with: openssl x509 -outform d -in
+     * usercert.pem | xxd -i | sed 's/0x/(byte) 0x/g'
+     */
+    private static final byte[] FAKE_DSA_USER_1 = new byte[] {
+            (byte) 0x30, (byte) 0x82, (byte) 0x03, (byte) 0xca, (byte) 0x30, (byte) 0x82,
+            (byte) 0x03, (byte) 0x33, (byte) 0xa0, (byte) 0x03, (byte) 0x02, (byte) 0x01,
+            (byte) 0x02, (byte) 0x02, (byte) 0x01, (byte) 0x01, (byte) 0x30, (byte) 0x0d,
+            (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86,
+            (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x05,
+            (byte) 0x00, (byte) 0x30, (byte) 0x5e, (byte) 0x31, (byte) 0x0b, (byte) 0x30,
+            (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x06,
+            (byte) 0x13, (byte) 0x02, (byte) 0x41, (byte) 0x55, (byte) 0x31, (byte) 0x13,
+            (byte) 0x30, (byte) 0x11, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
+            (byte) 0x08, (byte) 0x0c, (byte) 0x0a, (byte) 0x53, (byte) 0x6f, (byte) 0x6d,
+            (byte) 0x65, (byte) 0x2d, (byte) 0x53, (byte) 0x74, (byte) 0x61, (byte) 0x74,
+            (byte) 0x65, (byte) 0x31, (byte) 0x21, (byte) 0x30, (byte) 0x1f, (byte) 0x06,
+            (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x0a, (byte) 0x0c, (byte) 0x18,
+            (byte) 0x49, (byte) 0x6e, (byte) 0x74, (byte) 0x65, (byte) 0x72, (byte) 0x6e,
+            (byte) 0x65, (byte) 0x74, (byte) 0x20, (byte) 0x57, (byte) 0x69, (byte) 0x64,
+            (byte) 0x67, (byte) 0x69, (byte) 0x74, (byte) 0x73, (byte) 0x20, (byte) 0x50,
+            (byte) 0x74, (byte) 0x79, (byte) 0x20, (byte) 0x4c, (byte) 0x74, (byte) 0x64,
+            (byte) 0x31, (byte) 0x17, (byte) 0x30, (byte) 0x15, (byte) 0x06, (byte) 0x03,
+            (byte) 0x55, (byte) 0x04, (byte) 0x03, (byte) 0x0c, (byte) 0x0e, (byte) 0x63,
+            (byte) 0x61, (byte) 0x2e, (byte) 0x65, (byte) 0x78, (byte) 0x61, (byte) 0x6d,
+            (byte) 0x70, (byte) 0x6c, (byte) 0x65, (byte) 0x2e, (byte) 0x63, (byte) 0x6f,
+            (byte) 0x6d, (byte) 0x30, (byte) 0x1e, (byte) 0x17, (byte) 0x0d, (byte) 0x31,
+            (byte) 0x33, (byte) 0x30, (byte) 0x38, (byte) 0x32, (byte) 0x37, (byte) 0x32,
+            (byte) 0x33, (byte) 0x33, (byte) 0x34, (byte) 0x32, (byte) 0x32, (byte) 0x5a,
+            (byte) 0x17, (byte) 0x0d, (byte) 0x32, (byte) 0x33, (byte) 0x30, (byte) 0x38,
+            (byte) 0x32, (byte) 0x35, (byte) 0x32, (byte) 0x33, (byte) 0x33, (byte) 0x34,
+            (byte) 0x32, (byte) 0x32, (byte) 0x5a, (byte) 0x30, (byte) 0x62, (byte) 0x31,
+            (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55,
+            (byte) 0x04, (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x41, (byte) 0x55,
+            (byte) 0x31, (byte) 0x13, (byte) 0x30, (byte) 0x11, (byte) 0x06, (byte) 0x03,
+            (byte) 0x55, (byte) 0x04, (byte) 0x08, (byte) 0x0c, (byte) 0x0a, (byte) 0x53,
+            (byte) 0x6f, (byte) 0x6d, (byte) 0x65, (byte) 0x2d, (byte) 0x53, (byte) 0x74,
+            (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x31, (byte) 0x21, (byte) 0x30,
+            (byte) 0x1f, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x0a,
+            (byte) 0x0c, (byte) 0x18, (byte) 0x49, (byte) 0x6e, (byte) 0x74, (byte) 0x65,
+            (byte) 0x72, (byte) 0x6e, (byte) 0x65, (byte) 0x74, (byte) 0x20, (byte) 0x57,
+            (byte) 0x69, (byte) 0x64, (byte) 0x67, (byte) 0x69, (byte) 0x74, (byte) 0x73,
+            (byte) 0x20, (byte) 0x50, (byte) 0x74, (byte) 0x79, (byte) 0x20, (byte) 0x4c,
+            (byte) 0x74, (byte) 0x64, (byte) 0x31, (byte) 0x1b, (byte) 0x30, (byte) 0x19,
+            (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x03, (byte) 0x0c,
+            (byte) 0x12, (byte) 0x73, (byte) 0x65, (byte) 0x72, (byte) 0x76, (byte) 0x65,
+            (byte) 0x72, (byte) 0x2e, (byte) 0x65, (byte) 0x78, (byte) 0x61, (byte) 0x6d,
+            (byte) 0x70, (byte) 0x6c, (byte) 0x65, (byte) 0x2e, (byte) 0x63, (byte) 0x6f,
+            (byte) 0x6d, (byte) 0x30, (byte) 0x82, (byte) 0x01, (byte) 0xb7, (byte) 0x30,
+            (byte) 0x82, (byte) 0x01, (byte) 0x2c, (byte) 0x06, (byte) 0x07, (byte) 0x2a,
+            (byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x38, (byte) 0x04, (byte) 0x01,
+            (byte) 0x30, (byte) 0x82, (byte) 0x01, (byte) 0x1f, (byte) 0x02, (byte) 0x81,
+            (byte) 0x81, (byte) 0x00, (byte) 0xb3, (byte) 0x23, (byte) 0xf7, (byte) 0x86,
+            (byte) 0xbd, (byte) 0x3b, (byte) 0x86, (byte) 0xcc, (byte) 0xc3, (byte) 0x91,
+            (byte) 0xc0, (byte) 0x30, (byte) 0x32, (byte) 0x02, (byte) 0x47, (byte) 0x35,
+            (byte) 0x01, (byte) 0xef, (byte) 0xee, (byte) 0x98, (byte) 0x13, (byte) 0x56,
+            (byte) 0x49, (byte) 0x47, (byte) 0xb5, (byte) 0x20, (byte) 0xa8, (byte) 0x60,
+            (byte) 0xcb, (byte) 0xc0, (byte) 0xd5, (byte) 0x77, (byte) 0xc1, (byte) 0x69,
+            (byte) 0xcd, (byte) 0x18, (byte) 0x34, (byte) 0x92, (byte) 0xf2, (byte) 0x6a,
+            (byte) 0x2a, (byte) 0x10, (byte) 0x59, (byte) 0x1c, (byte) 0x91, (byte) 0x20,
+            (byte) 0x51, (byte) 0xca, (byte) 0x37, (byte) 0xb2, (byte) 0x87, (byte) 0xa6,
+            (byte) 0x8a, (byte) 0x02, (byte) 0xfd, (byte) 0x45, (byte) 0x46, (byte) 0xf9,
+            (byte) 0x76, (byte) 0xb1, (byte) 0x35, (byte) 0x38, (byte) 0x8d, (byte) 0xff,
+            (byte) 0x4c, (byte) 0x5d, (byte) 0x75, (byte) 0x8f, (byte) 0x66, (byte) 0x15,
+            (byte) 0x7d, (byte) 0x7b, (byte) 0xda, (byte) 0xdb, (byte) 0x57, (byte) 0x39,
+            (byte) 0xff, (byte) 0x91, (byte) 0x3f, (byte) 0xdd, (byte) 0xe2, (byte) 0xb4,
+            (byte) 0x22, (byte) 0x60, (byte) 0x4c, (byte) 0x32, (byte) 0x3b, (byte) 0x9d,
+            (byte) 0x34, (byte) 0x9f, (byte) 0xb9, (byte) 0x5d, (byte) 0x75, (byte) 0xb9,
+            (byte) 0xd3, (byte) 0x7f, (byte) 0x11, (byte) 0xba, (byte) 0xb7, (byte) 0xc8,
+            (byte) 0x32, (byte) 0xc6, (byte) 0xce, (byte) 0x71, (byte) 0x91, (byte) 0xd3,
+            (byte) 0x32, (byte) 0xaf, (byte) 0x4d, (byte) 0x7e, (byte) 0x7c, (byte) 0x15,
+            (byte) 0xf7, (byte) 0x71, (byte) 0x2c, (byte) 0x52, (byte) 0x65, (byte) 0x4d,
+            (byte) 0xa9, (byte) 0x81, (byte) 0x25, (byte) 0x35, (byte) 0xce, (byte) 0x0b,
+            (byte) 0x5b, (byte) 0x56, (byte) 0xfe, (byte) 0xf1, (byte) 0x02, (byte) 0x15,
+            (byte) 0x00, (byte) 0xeb, (byte) 0x4e, (byte) 0x7f, (byte) 0x7a, (byte) 0x31,
+            (byte) 0xb3, (byte) 0x7d, (byte) 0x8d, (byte) 0xb2, (byte) 0xf7, (byte) 0xaf,
+            (byte) 0xad, (byte) 0xb1, (byte) 0x42, (byte) 0x92, (byte) 0xf3, (byte) 0x6c,
+            (byte) 0xe4, (byte) 0xed, (byte) 0x8b, (byte) 0x02, (byte) 0x81, (byte) 0x81,
+            (byte) 0x00, (byte) 0x81, (byte) 0xc8, (byte) 0x36, (byte) 0x48, (byte) 0xdb,
+            (byte) 0x71, (byte) 0x2b, (byte) 0x91, (byte) 0xce, (byte) 0x6d, (byte) 0xbc,
+            (byte) 0xb8, (byte) 0xf9, (byte) 0xcb, (byte) 0x50, (byte) 0x91, (byte) 0x10,
+            (byte) 0x8a, (byte) 0xf8, (byte) 0x37, (byte) 0x50, (byte) 0xda, (byte) 0x4f,
+            (byte) 0xc8, (byte) 0x4d, (byte) 0x73, (byte) 0xcb, (byte) 0x4d, (byte) 0xb0,
+            (byte) 0x19, (byte) 0x54, (byte) 0x5a, (byte) 0xf3, (byte) 0x6c, (byte) 0xc9,
+            (byte) 0xd8, (byte) 0x96, (byte) 0xd9, (byte) 0xb0, (byte) 0x54, (byte) 0x7e,
+            (byte) 0x7d, (byte) 0xe2, (byte) 0x58, (byte) 0x0e, (byte) 0x5f, (byte) 0xc0,
+            (byte) 0xce, (byte) 0xb9, (byte) 0x5c, (byte) 0xe3, (byte) 0xd3, (byte) 0xdf,
+            (byte) 0xcf, (byte) 0x45, (byte) 0x74, (byte) 0xfb, (byte) 0xe6, (byte) 0x20,
+            (byte) 0xe7, (byte) 0xfc, (byte) 0x0f, (byte) 0xca, (byte) 0xdb, (byte) 0xc0,
+            (byte) 0x0b, (byte) 0xe1, (byte) 0x5a, (byte) 0x16, (byte) 0x1d, (byte) 0xb3,
+            (byte) 0x2e, (byte) 0xe5, (byte) 0x5f, (byte) 0x89, (byte) 0x17, (byte) 0x73,
+            (byte) 0x50, (byte) 0xd1, (byte) 0x4a, (byte) 0x60, (byte) 0xb7, (byte) 0xaa,
+            (byte) 0xf0, (byte) 0xc7, (byte) 0xc5, (byte) 0x03, (byte) 0x4e, (byte) 0x36,
+            (byte) 0x51, (byte) 0x9e, (byte) 0x2f, (byte) 0xfa, (byte) 0xf3, (byte) 0xd6,
+            (byte) 0x58, (byte) 0x14, (byte) 0x02, (byte) 0xb4, (byte) 0x41, (byte) 0xd6,
+            (byte) 0x72, (byte) 0x6f, (byte) 0x58, (byte) 0x5b, (byte) 0x2d, (byte) 0x23,
+            (byte) 0xc0, (byte) 0x75, (byte) 0x4f, (byte) 0x39, (byte) 0xa8, (byte) 0x6a,
+            (byte) 0xdf, (byte) 0x79, (byte) 0x21, (byte) 0xf2, (byte) 0x77, (byte) 0x91,
+            (byte) 0x3f, (byte) 0x1c, (byte) 0x4d, (byte) 0x48, (byte) 0x78, (byte) 0xcd,
+            (byte) 0xed, (byte) 0x79, (byte) 0x23, (byte) 0x03, (byte) 0x81, (byte) 0x84,
+            (byte) 0x00, (byte) 0x02, (byte) 0x81, (byte) 0x80, (byte) 0x1a, (byte) 0x50,
+            (byte) 0x9d, (byte) 0x3e, (byte) 0xa1, (byte) 0x6c, (byte) 0x99, (byte) 0x35,
+            (byte) 0x36, (byte) 0x26, (byte) 0x22, (byte) 0x6b, (byte) 0x47, (byte) 0x45,
+            (byte) 0x80, (byte) 0x5b, (byte) 0xd5, (byte) 0xc1, (byte) 0xc5, (byte) 0x70,
+            (byte) 0x75, (byte) 0x55, (byte) 0x66, (byte) 0x33, (byte) 0x1d, (byte) 0xae,
+            (byte) 0xd0, (byte) 0x01, (byte) 0x64, (byte) 0x8b, (byte) 0xae, (byte) 0x9d,
+            (byte) 0x66, (byte) 0x58, (byte) 0xf9, (byte) 0x42, (byte) 0x74, (byte) 0x3a,
+            (byte) 0x32, (byte) 0xc7, (byte) 0x7f, (byte) 0x25, (byte) 0x64, (byte) 0x7d,
+            (byte) 0x08, (byte) 0x26, (byte) 0xbf, (byte) 0x21, (byte) 0x3a, (byte) 0x84,
+            (byte) 0xcc, (byte) 0x2c, (byte) 0x66, (byte) 0x7d, (byte) 0xc7, (byte) 0xd6,
+            (byte) 0xb1, (byte) 0x69, (byte) 0x57, (byte) 0x67, (byte) 0x52, (byte) 0x73,
+            (byte) 0x3f, (byte) 0x79, (byte) 0x60, (byte) 0xaa, (byte) 0xf4, (byte) 0x8a,
+            (byte) 0x48, (byte) 0x42, (byte) 0x46, (byte) 0x41, (byte) 0xd0, (byte) 0x50,
+            (byte) 0x9b, (byte) 0xa2, (byte) 0x4e, (byte) 0xa5, (byte) 0x88, (byte) 0x10,
+            (byte) 0xf7, (byte) 0x61, (byte) 0xa2, (byte) 0xfa, (byte) 0x8d, (byte) 0xa6,
+            (byte) 0x13, (byte) 0x9e, (byte) 0x36, (byte) 0x86, (byte) 0x62, (byte) 0xf0,
+            (byte) 0x97, (byte) 0xef, (byte) 0x11, (byte) 0xc6, (byte) 0x35, (byte) 0xd3,
+            (byte) 0x79, (byte) 0x30, (byte) 0xde, (byte) 0xf2, (byte) 0x7f, (byte) 0x7a,
+            (byte) 0x3c, (byte) 0x03, (byte) 0xa3, (byte) 0xc5, (byte) 0xbc, (byte) 0xb1,
+            (byte) 0xbc, (byte) 0x2f, (byte) 0x10, (byte) 0xf4, (byte) 0x51, (byte) 0x89,
+            (byte) 0xe2, (byte) 0xaf, (byte) 0xf7, (byte) 0x61, (byte) 0x1a, (byte) 0xf0,
+            (byte) 0x87, (byte) 0x5e, (byte) 0xa5, (byte) 0x02, (byte) 0xd2, (byte) 0xe4,
+            (byte) 0xa3, (byte) 0x7b, (byte) 0x30, (byte) 0x79, (byte) 0x30, (byte) 0x09,
+            (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x13, (byte) 0x04,
+            (byte) 0x02, (byte) 0x30, (byte) 0x00, (byte) 0x30, (byte) 0x2c, (byte) 0x06,
+            (byte) 0x09, (byte) 0x60, (byte) 0x86, (byte) 0x48, (byte) 0x01, (byte) 0x86,
+            (byte) 0xf8, (byte) 0x42, (byte) 0x01, (byte) 0x0d, (byte) 0x04, (byte) 0x1f,
+            (byte) 0x16, (byte) 0x1d, (byte) 0x4f, (byte) 0x70, (byte) 0x65, (byte) 0x6e,
+            (byte) 0x53, (byte) 0x53, (byte) 0x4c, (byte) 0x20, (byte) 0x47, (byte) 0x65,
+            (byte) 0x6e, (byte) 0x65, (byte) 0x72, (byte) 0x61, (byte) 0x74, (byte) 0x65,
+            (byte) 0x64, (byte) 0x20, (byte) 0x43, (byte) 0x65, (byte) 0x72, (byte) 0x74,
+            (byte) 0x69, (byte) 0x66, (byte) 0x69, (byte) 0x63, (byte) 0x61, (byte) 0x74,
+            (byte) 0x65, (byte) 0x30, (byte) 0x1d, (byte) 0x06, (byte) 0x03, (byte) 0x55,
+            (byte) 0x1d, (byte) 0x0e, (byte) 0x04, (byte) 0x16, (byte) 0x04, (byte) 0x14,
+            (byte) 0xd1, (byte) 0x6c, (byte) 0x36, (byte) 0x36, (byte) 0x61, (byte) 0x6c,
+            (byte) 0xf6, (byte) 0x90, (byte) 0x82, (byte) 0x82, (byte) 0x87, (byte) 0x93,
+            (byte) 0xbe, (byte) 0x99, (byte) 0x60, (byte) 0x1b, (byte) 0x03, (byte) 0x58,
+            (byte) 0x36, (byte) 0x63, (byte) 0x30, (byte) 0x1f, (byte) 0x06, (byte) 0x03,
+            (byte) 0x55, (byte) 0x1d, (byte) 0x23, (byte) 0x04, (byte) 0x18, (byte) 0x30,
+            (byte) 0x16, (byte) 0x80, (byte) 0x14, (byte) 0x47, (byte) 0x82, (byte) 0xa3,
+            (byte) 0xf1, (byte) 0xc2, (byte) 0x7e, (byte) 0x3a, (byte) 0xde, (byte) 0x4f,
+            (byte) 0x30, (byte) 0x4c, (byte) 0x7f, (byte) 0x72, (byte) 0x81, (byte) 0x15,
+            (byte) 0x32, (byte) 0xda, (byte) 0x7f, (byte) 0x58, (byte) 0x18, (byte) 0x30,
+            (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, (byte) 0x48,
+            (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, (byte) 0x05,
+            (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81, (byte) 0x81, (byte) 0x00,
+            (byte) 0x81, (byte) 0xde, (byte) 0x20, (byte) 0xa1, (byte) 0xb2, (byte) 0x50,
+            (byte) 0x03, (byte) 0xcd, (byte) 0x90, (byte) 0x4f, (byte) 0x2b, (byte) 0x47,
+            (byte) 0x1d, (byte) 0xac, (byte) 0x6e, (byte) 0xb4, (byte) 0xc7, (byte) 0x14,
+            (byte) 0xc6, (byte) 0x4f, (byte) 0x45, (byte) 0xaf, (byte) 0x81, (byte) 0x5d,
+            (byte) 0x5a, (byte) 0x31, (byte) 0xff, (byte) 0x9c, (byte) 0x4d, (byte) 0xdc,
+            (byte) 0x9e, (byte) 0x36, (byte) 0x9f, (byte) 0x9b, (byte) 0xb1, (byte) 0xc9,
+            (byte) 0x50, (byte) 0xa3, (byte) 0xf6, (byte) 0x9c, (byte) 0x68, (byte) 0x6f,
+            (byte) 0x68, (byte) 0xd9, (byte) 0x56, (byte) 0x1b, (byte) 0xe5, (byte) 0x1b,
+            (byte) 0x41, (byte) 0xd4, (byte) 0xcc, (byte) 0xb6, (byte) 0x37, (byte) 0xd5,
+            (byte) 0x69, (byte) 0x6b, (byte) 0x39, (byte) 0xaf, (byte) 0xc6, (byte) 0xb8,
+            (byte) 0x39, (byte) 0x76, (byte) 0xe3, (byte) 0xf7, (byte) 0x97, (byte) 0x74,
+            (byte) 0x31, (byte) 0xc4, (byte) 0x2d, (byte) 0xb7, (byte) 0x9a, (byte) 0xa4,
+            (byte) 0xfa, (byte) 0x9f, (byte) 0xa8, (byte) 0xe3, (byte) 0x41, (byte) 0xda,
+            (byte) 0x2f, (byte) 0x0c, (byte) 0x9d, (byte) 0x83, (byte) 0xdc, (byte) 0x86,
+            (byte) 0x1f, (byte) 0x5c, (byte) 0x0f, (byte) 0x87, (byte) 0x05, (byte) 0xc9,
+            (byte) 0xb0, (byte) 0x63, (byte) 0xca, (byte) 0x9b, (byte) 0xdb, (byte) 0xe6,
+            (byte) 0x3c, (byte) 0xe9, (byte) 0x23, (byte) 0x9e, (byte) 0x23, (byte) 0x44,
+            (byte) 0x1d, (byte) 0x5b, (byte) 0x60, (byte) 0x66, (byte) 0xb6, (byte) 0x72,
+            (byte) 0x8c, (byte) 0x87, (byte) 0x86, (byte) 0xe8, (byte) 0xdb, (byte) 0x29,
+            (byte) 0x67, (byte) 0x9c, (byte) 0x33, (byte) 0x5c, (byte) 0x39, (byte) 0xf1,
+            (byte) 0xb5, (byte) 0x9b, (byte) 0xb8, (byte) 0xe1, (byte) 0x42, (byte) 0x51,
+            (byte) 0xed, (byte) 0x2c
+    };
+
     /**
      * The amount of time to allow before and after expected time for variance
      * in timing tests.
@@ -505,11 +1133,12 @@
         assertAliases(new String[] {});
 
         assertTrue(mAndroidKeyStore.generate(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+                KeyStore.UID_SELF, NativeCrypto.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED,
+                null));
 
         assertAliases(new String[] { TEST_ALIAS_1 });
 
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_CA_1,
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2 });
@@ -533,11 +1162,12 @@
         assertAliases(new String[] {});
 
         assertTrue(mAndroidKeyStore.generate(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+                KeyStore.UID_SELF, NativeCrypto.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED,
+                null));
 
         assertTrue("Should contain generated private key", mKeyStore.containsAlias(TEST_ALIAS_1));
 
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_CA_1,
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertTrue("Should contain added CA certificate", mKeyStore.containsAlias(TEST_ALIAS_2));
@@ -551,7 +1181,7 @@
 
         mKeyStore.load(null, null);
 
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_CA_1,
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertTrue("Should contain added CA certificate", mKeyStore.containsAlias(TEST_ALIAS_2));
@@ -572,18 +1202,18 @@
 
         // TEST_ALIAS_1
         assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1,
+                FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         // TEST_ALIAS_2
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_CA_1,
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         // TEST_ALIAS_3
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_3, FAKE_CA_1,
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_3, FAKE_RSA_CA_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2, TEST_ALIAS_3 });
@@ -617,10 +1247,10 @@
 
         // TEST_ALIAS_1
         assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1,
+                FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         // Should not throw when a non-existent entry is requested for delete.
@@ -632,7 +1262,7 @@
 
         mKeyStore.load(null, null);
 
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertAliases(new String[] { TEST_ALIAS_1 });
@@ -645,7 +1275,7 @@
         assertNotNull("Retrieved certificate should not be null", retrieved);
 
         CertificateFactory f = CertificateFactory.getInstance("X.509");
-        Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1));
+        Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
 
         assertEquals("Actual and retrieved certificates should be the same", actual, retrieved);
     }
@@ -664,11 +1294,11 @@
 
         mKeyStore.load(null, null);
 
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         CertificateFactory f = CertificateFactory.getInstance("X.509");
-        Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1));
+        Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
 
         assertEquals("Stored certificate alias should be found", TEST_ALIAS_1,
                 mKeyStore.getCertificateAlias(actual));
@@ -681,14 +1311,14 @@
         mKeyStore.load(null, null);
 
         assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1,
+                FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         CertificateFactory f = CertificateFactory.getInstance("X.509");
-        Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1));
+        Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
 
         assertEquals("Stored certificate alias should be found", TEST_ALIAS_1,
                 mKeyStore.getCertificateAlias(actual));
@@ -701,19 +1331,19 @@
         mKeyStore.load(null, null);
 
         // Insert TrustedCertificateEntry with CA name
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_CA_1,
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         // Insert PrivateKeyEntry that uses the same CA
         assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1,
+                FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         CertificateFactory f = CertificateFactory.getInstance("X.509");
-        Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1));
+        Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
 
         assertEquals("Stored certificate alias should be found", TEST_ALIAS_2,
                 mKeyStore.getCertificateAlias(actual));
@@ -726,7 +1356,7 @@
         mKeyStore.load(null, null);
 
         CertificateFactory f = CertificateFactory.getInstance("X.509");
-        Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1));
+        Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
 
         assertNull("Stored certificate alias should not be found",
                 mKeyStore.getCertificateAlias(actual));
@@ -737,11 +1367,11 @@
 
         mKeyStore.load(null, null);
 
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         CertificateFactory f = CertificateFactory.getInstance("X.509");
-        Certificate userCert = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1));
+        Certificate userCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
 
         assertNull("Stored certificate alias should be found",
                 mKeyStore.getCertificateAlias(userCert));
@@ -753,16 +1383,16 @@
         mKeyStore.load(null, null);
 
         assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1,
+                FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         CertificateFactory cf = CertificateFactory.getInstance("X.509");
         Certificate[] expected = new Certificate[2];
-        expected[0] = cf.generateCertificate(new ByteArrayInputStream(FAKE_USER_1));
-        expected[1] = cf.generateCertificate(new ByteArrayInputStream(FAKE_CA_1));
+        expected[0] = cf.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
+        expected[1] = cf.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
 
         Certificate[] actual = mKeyStore.getCertificateChain(TEST_ALIAS_1);
 
@@ -792,10 +1422,10 @@
         mKeyStore.load(null, null);
 
         assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1,
+                FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         Date now = new Date();
@@ -812,10 +1442,10 @@
         mKeyStore.load(null, null);
 
         assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1,
+                FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE));
+        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_NONE));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_NONE));
 
         Date now = new Date();
@@ -833,7 +1463,7 @@
 
         mKeyStore.load(null, null);
 
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         Date now = new Date();
@@ -853,10 +1483,10 @@
         mKeyStore.load(null, null);
 
         assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1,
+                FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null);
@@ -866,17 +1496,18 @@
 
         PrivateKeyEntry keyEntry = (PrivateKeyEntry) entry;
 
-        assertPrivateKeyEntryEquals(keyEntry, FAKE_KEY_1, FAKE_USER_1, FAKE_CA_1);
+        assertPrivateKeyEntryEquals(keyEntry, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
+                FAKE_RSA_CA_1);
     }
 
-    public void testKeyStore_GetEntry_NullParams_Unencrypted_Success() throws Exception {
+    public void testKeyStore_GetEntry_DSA_NullParams_Unencrypted_Success() throws Exception {
         mKeyStore.load(null, null);
 
         assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1,
-                KeyStore.UID_SELF, KeyStore.FLAG_NONE));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+                FAKE_DSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE));
+        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1,
+                FAKE_DSA_USER_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE));
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_DSA_CA_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_NONE));
 
         Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null);
@@ -886,13 +1517,54 @@
 
         PrivateKeyEntry keyEntry = (PrivateKeyEntry) entry;
 
-        assertPrivateKeyEntryEquals(keyEntry, FAKE_KEY_1, FAKE_USER_1, FAKE_CA_1);
+        assertPrivateKeyEntryEquals(keyEntry, "DSA", FAKE_DSA_KEY_1, FAKE_DSA_USER_1, FAKE_DSA_CA_1);
+    }
+
+    public void testKeyStore_GetEntry_EC_NullParams_Unencrypted_Success() throws Exception {
+        mKeyStore.load(null, null);
+
+        assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
+                FAKE_EC_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE));
+        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1,
+                FAKE_EC_USER_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE));
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_EC_CA_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_NONE));
+
+        Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+        assertNotNull("Entry should exist", entry);
+
+        assertTrue("Should be a PrivateKeyEntry", entry instanceof PrivateKeyEntry);
+
+        PrivateKeyEntry keyEntry = (PrivateKeyEntry) entry;
+
+        assertPrivateKeyEntryEquals(keyEntry, "EC", FAKE_EC_KEY_1, FAKE_EC_USER_1, FAKE_EC_CA_1);
+    }
+
+    public void testKeyStore_GetEntry_RSA_NullParams_Unencrypted_Success() throws Exception {
+        mKeyStore.load(null, null);
+
+        assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
+                FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE));
+        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1,
+                FAKE_RSA_USER_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE));
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
+                KeyStore.UID_SELF, KeyStore.FLAG_NONE));
+
+        Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+        assertNotNull("Entry should exist", entry);
+
+        assertTrue("Should be a PrivateKeyEntry", entry instanceof PrivateKeyEntry);
+
+        PrivateKeyEntry keyEntry = (PrivateKeyEntry) entry;
+
+        assertPrivateKeyEntryEquals(keyEntry, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
+                FAKE_RSA_CA_1);
     }
 
     @SuppressWarnings("unchecked")
-    private void assertPrivateKeyEntryEquals(PrivateKeyEntry keyEntry, byte[] key, byte[] cert,
-            byte[] ca) throws Exception {
-        KeyFactory keyFact = KeyFactory.getInstance("RSA");
+    private void assertPrivateKeyEntryEquals(PrivateKeyEntry keyEntry, String keyType, byte[] key,
+            byte[] cert, byte[] ca) throws Exception {
+        KeyFactory keyFact = KeyFactory.getInstance(keyType);
         PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(key));
 
         CertificateFactory certFact = CertificateFactory.getInstance("X.509");
@@ -911,9 +1583,19 @@
 
     private void assertPrivateKeyEntryEquals(PrivateKeyEntry keyEntry, PrivateKey expectedKey,
             Certificate expectedCert, Collection<Certificate> expectedChain) throws Exception {
-        assertEquals("Returned PrivateKey should be what we inserted",
-                ((RSAPrivateKey) expectedKey).getModulus(),
-                ((RSAPrivateKey) keyEntry.getPrivateKey()).getModulus());
+        if (expectedKey instanceof DSAPrivateKey) {
+            assertEquals("Returned PrivateKey should be what we inserted",
+                    ((DSAPrivateKey) expectedKey).getParams(),
+                    ((DSAPublicKey) keyEntry.getCertificate().getPublicKey()).getParams());
+        } else if (expectedKey instanceof ECPrivateKey) {
+            assertEquals("Returned PrivateKey should be what we inserted",
+                    ((ECPrivateKey) expectedKey).getParams().getCurve(),
+                    ((ECPublicKey) keyEntry.getCertificate().getPublicKey()).getParams().getCurve());
+        } else if (expectedKey instanceof RSAPrivateKey) {
+            assertEquals("Returned PrivateKey should be what we inserted",
+                    ((RSAPrivateKey) expectedKey).getModulus(),
+                    ((RSAPrivateKey) keyEntry.getPrivateKey()).getModulus());
+        }
 
         assertEquals("Returned Certificate should be what we inserted", expectedCert,
                 keyEntry.getCertificate());
@@ -956,10 +1638,10 @@
         mKeyStore.load(null, null);
 
         assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1,
+                FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         Key key = mKeyStore.getKey(TEST_ALIAS_1, null);
@@ -970,7 +1652,7 @@
         RSAPrivateKey actualKey = (RSAPrivateKey) key;
 
         KeyFactory keyFact = KeyFactory.getInstance("RSA");
-        PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1));
+        PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
 
         assertEquals("Inserted key should be same as retrieved key",
                 ((RSAPrivateKey) expectedKey).getModulus(), actualKey.getModulus());
@@ -980,10 +1662,10 @@
         mKeyStore.load(null, null);
 
         assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1,
+                FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE));
+        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_NONE));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_NONE));
 
         Key key = mKeyStore.getKey(TEST_ALIAS_1, null);
@@ -994,7 +1676,7 @@
         RSAPrivateKey actualKey = (RSAPrivateKey) key;
 
         KeyFactory keyFact = KeyFactory.getInstance("RSA");
-        PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1));
+        PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
 
         assertEquals("Inserted key should be same as retrieved key",
                 ((RSAPrivateKey) expectedKey).getModulus(), actualKey.getModulus());
@@ -1005,7 +1687,7 @@
 
         mKeyStore.load(null, null);
 
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertNull("Certificate entries should return null", mKeyStore.getKey(TEST_ALIAS_1, null));
@@ -1035,7 +1717,7 @@
         setupPassword();
         mKeyStore.load(null, null);
 
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertTrue("Should return true for CA certificate",
@@ -1047,10 +1729,10 @@
         mKeyStore.load(null, null);
 
         assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1,
+                FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertFalse("Should return false for PrivateKeyEntry",
@@ -1077,10 +1759,10 @@
         mKeyStore.load(null, null);
 
         assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1,
+                FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertTrue("Should return true for PrivateKeyEntry", mKeyStore.isKeyEntry(TEST_ALIAS_1));
@@ -1090,7 +1772,7 @@
         setupPassword();
         mKeyStore.load(null, null);
 
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertFalse("Should return false for CA certificate", mKeyStore.isKeyEntry(TEST_ALIAS_1));
@@ -1106,7 +1788,7 @@
 
     public void testKeyStore_SetCertificate_CA_Encrypted_Success() throws Exception {
         final CertificateFactory f = CertificateFactory.getInstance("X.509");
-        final Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1));
+        final Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
 
         setupPassword();
         mKeyStore.load(null, null);
@@ -1124,13 +1806,13 @@
         setupPassword();
         mKeyStore.load(null, null);
 
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertAliases(new String[] { TEST_ALIAS_1 });
 
         final CertificateFactory f = CertificateFactory.getInstance("X.509");
-        final Certificate cert = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1));
+        final Certificate cert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
 
         // TODO have separate FAKE_CA for second test
         mKeyStore.setCertificateEntry(TEST_ALIAS_1, cert);
@@ -1143,16 +1825,16 @@
         mKeyStore.load(null, null);
 
         assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
-                FAKE_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_USER_1,
+                FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+        assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertAliases(new String[] { TEST_ALIAS_1 });
 
         final CertificateFactory f = CertificateFactory.getInstance("X.509");
-        final Certificate cert = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1));
+        final Certificate cert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
 
         try {
             mKeyStore.setCertificateEntry(TEST_ALIAS_1, cert);
@@ -1166,13 +1848,13 @@
         mKeyStore.load(null, null);
 
         KeyFactory keyFact = KeyFactory.getInstance("RSA");
-        PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1));
+        PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
 
         final CertificateFactory f = CertificateFactory.getInstance("X.509");
 
         final Certificate[] expectedChain = new Certificate[2];
-        expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1));
-        expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1));
+        expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
+        expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
 
         PrivateKeyEntry expected = new PrivateKeyEntry(expectedKey, expectedChain);
 
@@ -1186,20 +1868,74 @@
 
         PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry;
 
-        assertPrivateKeyEntryEquals(actual, FAKE_KEY_1, FAKE_USER_1, FAKE_CA_1);
+        assertPrivateKeyEntryEquals(actual, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, FAKE_RSA_CA_1);
     }
 
-    public void testKeyStore_SetEntry_PrivateKeyEntry_Unencrypted_Success() throws Exception {
+    public void testKeyStore_SetEntry_PrivateKeyEntry_DSA_Unencrypted_Success() throws Exception {
+        mKeyStore.load(null, null);
+
+        KeyFactory keyFact = KeyFactory.getInstance("DSA");
+        PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_DSA_KEY_1));
+
+        final CertificateFactory f = CertificateFactory.getInstance("X.509");
+
+        final Certificate[] expectedChain = new Certificate[2];
+        expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_DSA_USER_1));
+        expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_DSA_CA_1));
+
+        PrivateKeyEntry expected = new PrivateKeyEntry(expectedKey, expectedChain);
+
+        mKeyStore.setEntry(TEST_ALIAS_1, expected, null);
+
+        Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+        assertNotNull("Retrieved entry should exist", actualEntry);
+
+        assertTrue("Retrieved entry should be of type PrivateKeyEntry",
+                actualEntry instanceof PrivateKeyEntry);
+
+        PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry;
+
+        assertPrivateKeyEntryEquals(actual, "DSA", FAKE_DSA_KEY_1, FAKE_DSA_USER_1, FAKE_DSA_CA_1);
+    }
+
+    public void testKeyStore_SetEntry_PrivateKeyEntry_EC_Unencrypted_Success() throws Exception {
+        mKeyStore.load(null, null);
+
+        KeyFactory keyFact = KeyFactory.getInstance("EC");
+        PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_EC_KEY_1));
+
+        final CertificateFactory f = CertificateFactory.getInstance("X.509");
+
+        final Certificate[] expectedChain = new Certificate[2];
+        expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_EC_USER_1));
+        expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_EC_CA_1));
+
+        PrivateKeyEntry expected = new PrivateKeyEntry(expectedKey, expectedChain);
+
+        mKeyStore.setEntry(TEST_ALIAS_1, expected, null);
+
+        Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+        assertNotNull("Retrieved entry should exist", actualEntry);
+
+        assertTrue("Retrieved entry should be of type PrivateKeyEntry",
+                actualEntry instanceof PrivateKeyEntry);
+
+        PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry;
+
+        assertPrivateKeyEntryEquals(actual, "EC", FAKE_EC_KEY_1, FAKE_EC_USER_1, FAKE_EC_CA_1);
+    }
+
+    public void testKeyStore_SetEntry_PrivateKeyEntry_RSA_Unencrypted_Success() throws Exception {
         mKeyStore.load(null, null);
 
         KeyFactory keyFact = KeyFactory.getInstance("RSA");
-        PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1));
+        PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
 
         final CertificateFactory f = CertificateFactory.getInstance("X.509");
 
         final Certificate[] expectedChain = new Certificate[2];
-        expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1));
-        expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1));
+        expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
+        expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
 
         PrivateKeyEntry expected = new PrivateKeyEntry(expectedKey, expectedChain);
 
@@ -1213,20 +1949,20 @@
 
         PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry;
 
-        assertPrivateKeyEntryEquals(actual, FAKE_KEY_1, FAKE_USER_1, FAKE_CA_1);
+        assertPrivateKeyEntryEquals(actual, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, FAKE_RSA_CA_1);
     }
 
     public void testKeyStore_SetEntry_PrivateKeyEntry_Params_Unencrypted_Failure() throws Exception {
         mKeyStore.load(null, null);
 
         KeyFactory keyFact = KeyFactory.getInstance("RSA");
-        PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1));
+        PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
 
         final CertificateFactory f = CertificateFactory.getInstance("X.509");
 
         final Certificate[] expectedChain = new Certificate[2];
-        expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1));
-        expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1));
+        expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
+        expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
 
         PrivateKeyEntry entry = new PrivateKeyEntry(expectedKey, expectedChain);
 
@@ -1253,11 +1989,11 @@
 
         // Start with PrivateKeyEntry
         {
-            PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1));
+            PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
 
             final Certificate[] expectedChain = new Certificate[2];
-            expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1));
-            expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1));
+            expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
+            expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
 
             PrivateKeyEntry expected = new PrivateKeyEntry(expectedKey, expectedChain);
 
@@ -1271,17 +2007,18 @@
 
             PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry;
 
-            assertPrivateKeyEntryEquals(actual, FAKE_KEY_1, FAKE_USER_1, FAKE_CA_1);
+            assertPrivateKeyEntryEquals(actual, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
+                    FAKE_RSA_CA_1);
         }
 
         // TODO make entirely new test vector for the overwrite
         // Replace with PrivateKeyEntry
         {
-            PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1));
+            PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
 
             final Certificate[] expectedChain = new Certificate[2];
-            expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1));
-            expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1));
+            expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
+            expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
 
             PrivateKeyEntry expected = new PrivateKeyEntry(expectedKey, expectedChain);
 
@@ -1295,7 +2032,8 @@
 
             PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry;
 
-            assertPrivateKeyEntryEquals(actual, FAKE_KEY_1, FAKE_USER_1, FAKE_CA_1);
+            assertPrivateKeyEntryEquals(actual, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
+                    FAKE_RSA_CA_1);
         }
     }
 
@@ -1308,7 +2046,7 @@
 
         // Start with TrustedCertificateEntry
         {
-            final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1));
+            final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
 
             TrustedCertificateEntry expectedCertEntry = new TrustedCertificateEntry(caCert);
             mKeyStore.setEntry(TEST_ALIAS_1, expectedCertEntry, null);
@@ -1326,10 +2064,10 @@
         // Replace with PrivateKeyEntry
         {
             KeyFactory keyFact = KeyFactory.getInstance("RSA");
-            PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1));
+            PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
             final Certificate[] expectedChain = new Certificate[2];
-            expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1));
-            expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1));
+            expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
+            expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
 
             PrivateKeyEntry expectedPrivEntry = new PrivateKeyEntry(expectedKey, expectedChain);
 
@@ -1341,7 +2079,8 @@
                     actualEntry instanceof PrivateKeyEntry);
 
             PrivateKeyEntry actualPrivEntry = (PrivateKeyEntry) actualEntry;
-            assertPrivateKeyEntryEquals(actualPrivEntry, FAKE_KEY_1, FAKE_USER_1, FAKE_CA_1);
+            assertPrivateKeyEntryEquals(actualPrivEntry, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
+                    FAKE_RSA_CA_1);
         }
     }
 
@@ -1352,14 +2091,14 @@
 
         final CertificateFactory f = CertificateFactory.getInstance("X.509");
 
-        final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1));
+        final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
 
         // Start with PrivateKeyEntry
         {
             KeyFactory keyFact = KeyFactory.getInstance("RSA");
-            PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1));
+            PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
             final Certificate[] expectedChain = new Certificate[2];
-            expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1));
+            expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
             expectedChain[1] = caCert;
 
             PrivateKeyEntry expectedPrivEntry = new PrivateKeyEntry(expectedKey, expectedChain);
@@ -1372,7 +2111,8 @@
                     actualEntry instanceof PrivateKeyEntry);
 
             PrivateKeyEntry actualPrivEntry = (PrivateKeyEntry) actualEntry;
-            assertPrivateKeyEntryEquals(actualPrivEntry, FAKE_KEY_1, FAKE_USER_1, FAKE_CA_1);
+            assertPrivateKeyEntryEquals(actualPrivEntry, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
+                    FAKE_RSA_CA_1);
         }
 
         // Replace with TrustedCertificateEntry
@@ -1400,14 +2140,14 @@
 
         final CertificateFactory f = CertificateFactory.getInstance("X.509");
 
-        final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1));
+        final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
 
         // Start with PrivateKeyEntry
         {
             KeyFactory keyFact = KeyFactory.getInstance("RSA");
-            PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1));
+            PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
             final Certificate[] expectedChain = new Certificate[2];
-            expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1));
+            expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
             expectedChain[1] = caCert;
 
             PrivateKeyEntry expectedPrivEntry = new PrivateKeyEntry(expectedKey, expectedChain);
@@ -1420,15 +2160,16 @@
                     actualEntry instanceof PrivateKeyEntry);
 
             PrivateKeyEntry actualPrivEntry = (PrivateKeyEntry) actualEntry;
-            assertPrivateKeyEntryEquals(actualPrivEntry, FAKE_KEY_1, FAKE_USER_1, FAKE_CA_1);
+            assertPrivateKeyEntryEquals(actualPrivEntry, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
+                    FAKE_RSA_CA_1);
         }
 
         // Replace with PrivateKeyEntry that has no chain
         {
             KeyFactory keyFact = KeyFactory.getInstance("RSA");
-            PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1));
+            PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
             final Certificate[] expectedChain = new Certificate[1];
-            expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1));
+            expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
 
             PrivateKeyEntry expectedPrivEntry = new PrivateKeyEntry(expectedKey, expectedChain);
 
@@ -1440,7 +2181,8 @@
                     actualEntry instanceof PrivateKeyEntry);
 
             PrivateKeyEntry actualPrivEntry = (PrivateKeyEntry) actualEntry;
-            assertPrivateKeyEntryEquals(actualPrivEntry, FAKE_KEY_1, FAKE_USER_1, null);
+            assertPrivateKeyEntryEquals(actualPrivEntry, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
+                    null);
         }
     }
 
@@ -1453,7 +2195,7 @@
 
         // Insert TrustedCertificateEntry
         {
-            final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1));
+            final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
 
             TrustedCertificateEntry expectedCertEntry = new TrustedCertificateEntry(caCert);
             mKeyStore.setEntry(TEST_ALIAS_1, expectedCertEntry, null);
@@ -1471,7 +2213,7 @@
         // Replace with TrustedCertificateEntry of USER
         {
             final Certificate userCert = f
-                    .generateCertificate(new ByteArrayInputStream(FAKE_USER_1));
+                    .generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
 
             TrustedCertificateEntry expectedUserEntry = new TrustedCertificateEntry(userCert);
             mKeyStore.setEntry(TEST_ALIAS_1, expectedUserEntry, null);
@@ -1493,12 +2235,12 @@
 
         final CertificateFactory f = CertificateFactory.getInstance("X.509");
 
-        final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1));
+        final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
 
         KeyFactory keyFact = KeyFactory.getInstance("RSA");
-        PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1));
+        PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
         final Certificate[] chain = new Certificate[2];
-        chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1));
+        chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
         chain[1] = caCert;
 
         try {
@@ -1514,12 +2256,12 @@
 
         final CertificateFactory f = CertificateFactory.getInstance("X.509");
 
-        final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1));
+        final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
 
         KeyFactory keyFact = KeyFactory.getInstance("RSA");
-        PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1));
+        PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
         final Certificate[] chain = new Certificate[2];
-        chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1));
+        chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
         chain[1] = caCert;
 
         mKeyStore.setKeyEntry(TEST_ALIAS_1, privKey, null, chain);
@@ -1532,7 +2274,7 @@
 
         PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry;
 
-        assertPrivateKeyEntryEquals(actual, FAKE_KEY_1, FAKE_USER_1, FAKE_CA_1);
+        assertPrivateKeyEntryEquals(actual, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, FAKE_RSA_CA_1);
     }
 
     public void testKeyStore_SetKeyEntry_Replaced_Encrypted_Success() throws Exception {
@@ -1541,14 +2283,14 @@
 
         final CertificateFactory f = CertificateFactory.getInstance("X.509");
 
-        final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_CA_1));
+        final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
 
         // Insert initial key
         {
             KeyFactory keyFact = KeyFactory.getInstance("RSA");
-            PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1));
+            PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
             final Certificate[] chain = new Certificate[2];
-            chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1));
+            chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
             chain[1] = caCert;
 
             mKeyStore.setKeyEntry(TEST_ALIAS_1, privKey, null, chain);
@@ -1561,16 +2303,17 @@
 
             PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry;
 
-            assertPrivateKeyEntryEquals(actual, FAKE_KEY_1, FAKE_USER_1, FAKE_CA_1);
+            assertPrivateKeyEntryEquals(actual, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
+                    FAKE_RSA_CA_1);
         }
 
         // TODO make a separate key
         // Replace key
         {
             KeyFactory keyFact = KeyFactory.getInstance("RSA");
-            PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_KEY_1));
+            PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
             final Certificate[] chain = new Certificate[2];
-            chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_USER_1));
+            chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
             chain[1] = caCert;
 
             mKeyStore.setKeyEntry(TEST_ALIAS_1, privKey, null, chain);
@@ -1583,7 +2326,8 @@
 
             PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry;
 
-            assertPrivateKeyEntryEquals(actual, FAKE_KEY_1, FAKE_USER_1, FAKE_CA_1);
+            assertPrivateKeyEntryEquals(actual, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
+                    FAKE_RSA_CA_1);
         }
     }
 
@@ -1635,7 +2379,7 @@
         {
             final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1;
             assertTrue(mAndroidKeyStore.generate(privateKeyAlias, KeyStore.UID_SELF,
-                    KeyStore.FLAG_ENCRYPTED));
+                    NativeCrypto.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED, null));
 
             Key key = mKeyStore.getKey(TEST_ALIAS_1, null);
 
@@ -1691,7 +2435,7 @@
         {
             final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1;
             assertTrue(mAndroidKeyStore.generate(privateKeyAlias, KeyStore.UID_SELF,
-                    KeyStore.FLAG_ENCRYPTED));
+                    NativeCrypto.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED, null));
 
             X509Certificate cert = generateCertificate(mAndroidKeyStore, TEST_ALIAS_1,
                     TEST_SERIAL_1, TEST_DN_1, NOW, NOW_PLUS_10_YEARS);
@@ -1704,7 +2448,7 @@
         {
             final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + TEST_ALIAS_2;
             assertTrue(mAndroidKeyStore.generate(privateKeyAlias, KeyStore.UID_SELF,
-                    KeyStore.FLAG_ENCRYPTED));
+                    NativeCrypto.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED, null));
 
             X509Certificate cert = generateCertificate(mAndroidKeyStore, TEST_ALIAS_2,
                     TEST_SERIAL_2, TEST_DN_2, NOW, NOW_PLUS_10_YEARS);
@@ -1736,7 +2480,8 @@
         {
             final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1;
             assertTrue(mAndroidKeyStore.generate(privateKeyAlias,
-                    android.security.KeyStore.UID_SELF, android.security.KeyStore.FLAG_NONE));
+                    android.security.KeyStore.UID_SELF, NativeCrypto.EVP_PKEY_RSA, 1024,
+                    android.security.KeyStore.FLAG_NONE, null));
 
             X509Certificate cert =
                     generateCertificate(mAndroidKeyStore, TEST_ALIAS_1, TEST_SERIAL_1, TEST_DN_1,
@@ -1774,20 +2519,21 @@
         setupPassword();
         mKeyStore.load(null, null);
 
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_CA_1,
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertEquals("The keystore size should match expected", 1, mKeyStore.size());
         assertAliases(new String[] { TEST_ALIAS_1 });
 
-        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_CA_1,
+        assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1,
                 KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
 
         assertEquals("The keystore size should match expected", 2, mKeyStore.size());
         assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2 });
 
         assertTrue(mAndroidKeyStore.generate(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_3,
-                KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+                KeyStore.UID_SELF, NativeCrypto.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED,
+                null));
 
         assertEquals("The keystore size should match expected", 3, mKeyStore.size());
         assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2, TEST_ALIAS_3 });
@@ -1854,7 +2600,8 @@
     private void setupKey() throws Exception {
         final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1;
         assertTrue(mAndroidKeyStore
-                .generate(privateKeyAlias, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+                .generate(privateKeyAlias, KeyStore.UID_SELF, NativeCrypto.EVP_PKEY_RSA, 1024,
+                        KeyStore.FLAG_ENCRYPTED, null));
 
         X509Certificate cert = generateCertificate(mAndroidKeyStore, TEST_ALIAS_1, TEST_SERIAL_1,
                 TEST_DN_1, NOW, NOW_PLUS_10_YEARS);
diff --git a/keystore/tests/src/android/security/KeyPairGeneratorSpecTest.java b/keystore/tests/src/android/security/KeyPairGeneratorSpecTest.java
index 113d730..bc8dd13 100644
--- a/keystore/tests/src/android/security/KeyPairGeneratorSpecTest.java
+++ b/keystore/tests/src/android/security/KeyPairGeneratorSpecTest.java
@@ -40,13 +40,17 @@
 
     public void testConstructor_Success() throws Exception {
         KeyPairGeneratorSpec spec =
-                new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, TEST_DN_1, SERIAL_1,
-                        NOW, NOW_PLUS_10_YEARS, 0);
+                new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, "RSA", 1024, null, TEST_DN_1,
+                        SERIAL_1, NOW, NOW_PLUS_10_YEARS, 0);
 
         assertEquals("Context should be the one specified", getContext(), spec.getContext());
 
         assertEquals("Alias should be the one specified", TEST_ALIAS_1, spec.getKeystoreAlias());
 
+        assertEquals("Key algorithm should be the one specified", "RSA", spec.getKeyType());
+
+        assertEquals("Key size should be the one specified", 1024, spec.getKeySize());
+
         assertEquals("subjectDN should be the one specified", TEST_DN_1, spec.getSubjectDN());
 
         assertEquals("startDate should be the one specified", NOW, spec.getStartDate());
@@ -57,6 +61,8 @@
     public void testBuilder_Success() throws Exception {
         KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(getContext())
                 .setAlias(TEST_ALIAS_1)
+                .setKeyType("RSA")
+                .setKeySize(1024)
                 .setSubject(TEST_DN_1)
                 .setSerialNumber(SERIAL_1)
                 .setStartDate(NOW)
@@ -68,6 +74,10 @@
 
         assertEquals("Alias should be the one specified", TEST_ALIAS_1, spec.getKeystoreAlias());
 
+        assertEquals("Key algorithm should be the one specified", "RSA", spec.getKeyType());
+
+        assertEquals("Key size should be the one specified", 1024, spec.getKeySize());
+
         assertEquals("subjectDN should be the one specified", TEST_DN_1, spec.getSubjectDN());
 
         assertEquals("startDate should be the one specified", NOW, spec.getStartDate());
@@ -79,7 +89,7 @@
 
     public void testConstructor_NullContext_Failure() throws Exception {
         try {
-            new KeyPairGeneratorSpec(null, TEST_ALIAS_1, TEST_DN_1, SERIAL_1, NOW,
+            new KeyPairGeneratorSpec(null, TEST_ALIAS_1, "RSA", 1024, null, TEST_DN_1, SERIAL_1, NOW,
                     NOW_PLUS_10_YEARS, 0);
             fail("Should throw IllegalArgumentException when context is null");
         } catch (IllegalArgumentException success) {
@@ -88,7 +98,7 @@
 
     public void testConstructor_NullKeystoreAlias_Failure() throws Exception {
         try {
-            new KeyPairGeneratorSpec(getContext(), null, TEST_DN_1, SERIAL_1, NOW,
+            new KeyPairGeneratorSpec(getContext(), null, "RSA", 1024, null, TEST_DN_1, SERIAL_1, NOW,
                     NOW_PLUS_10_YEARS, 0);
             fail("Should throw IllegalArgumentException when keystoreAlias is null");
         } catch (IllegalArgumentException success) {
@@ -97,7 +107,7 @@
 
     public void testConstructor_NullSubjectDN_Failure() throws Exception {
         try {
-            new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, null, SERIAL_1, NOW,
+            new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, "RSA", 1024, null, null, SERIAL_1, NOW,
                     NOW_PLUS_10_YEARS, 0);
             fail("Should throw IllegalArgumentException when subjectDN is null");
         } catch (IllegalArgumentException success) {
@@ -106,7 +116,7 @@
 
     public void testConstructor_NullSerial_Failure() throws Exception {
         try {
-            new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, TEST_DN_1, null, NOW,
+            new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, "RSA", 1024, null, TEST_DN_1, null, NOW,
                     NOW_PLUS_10_YEARS, 0);
             fail("Should throw IllegalArgumentException when startDate is null");
         } catch (IllegalArgumentException success) {
@@ -115,8 +125,8 @@
 
     public void testConstructor_NullStartDate_Failure() throws Exception {
         try {
-            new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, TEST_DN_1, SERIAL_1, null,
-                    NOW_PLUS_10_YEARS, 0);
+            new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, "RSA", 1024, null, TEST_DN_1, SERIAL_1,
+                    null, NOW_PLUS_10_YEARS, 0);
             fail("Should throw IllegalArgumentException when startDate is null");
         } catch (IllegalArgumentException success) {
         }
@@ -124,8 +134,8 @@
 
     public void testConstructor_NullEndDate_Failure() throws Exception {
         try {
-            new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, TEST_DN_1, SERIAL_1, NOW,
-                    null, 0);
+            new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, "RSA", 1024, null, TEST_DN_1, SERIAL_1,
+                    NOW, null, 0);
             fail("Should throw IllegalArgumentException when keystoreAlias is null");
         } catch (IllegalArgumentException success) {
         }
@@ -133,7 +143,7 @@
 
     public void testConstructor_EndBeforeStart_Failure() throws Exception {
         try {
-            new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, TEST_DN_1, SERIAL_1,
+            new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, "RSA", 1024, null, TEST_DN_1, SERIAL_1,
                     NOW_PLUS_10_YEARS, NOW, 0);
             fail("Should throw IllegalArgumentException when end is before start");
         } catch (IllegalArgumentException success) {
diff --git a/keystore/tests/src/android/security/KeyStoreTest.java b/keystore/tests/src/android/security/KeyStoreTest.java
index 9bf88d3..7a142cc 100644
--- a/keystore/tests/src/android/security/KeyStoreTest.java
+++ b/keystore/tests/src/android/security/KeyStoreTest.java
@@ -22,6 +22,7 @@
 import android.test.ActivityUnitTestCase;
 import android.test.AssertionFailedError;
 import android.test.suitebuilder.annotation.MediumTest;
+import com.android.org.conscrypt.NativeCrypto;
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.Date;
@@ -347,21 +348,24 @@
 
     public void testGenerate_NotInitialized_Fail() throws Exception {
         assertFalse("Should fail when keystore is not initialized",
-                mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+                mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeCrypto.EVP_PKEY_RSA,
+                        1024, KeyStore.FLAG_ENCRYPTED, null));
     }
 
     public void testGenerate_Locked_Fail() throws Exception {
         mKeyStore.password(TEST_PASSWD);
         mKeyStore.lock();
         assertFalse("Should fail when keystore is locked",
-                mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+                mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeCrypto.EVP_PKEY_RSA,
+                        1024, KeyStore.FLAG_ENCRYPTED, null));
     }
 
     public void testGenerate_Success() throws Exception {
         assertTrue(mKeyStore.password(TEST_PASSWD));
 
         assertTrue("Should be able to generate key when unlocked",
-                mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+                mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeCrypto.EVP_PKEY_RSA,
+                        1024, KeyStore.FLAG_ENCRYPTED, null));
         assertTrue(mKeyStore.contains(TEST_KEYNAME));
         assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
     }
@@ -370,7 +374,8 @@
         assertTrue(mKeyStore.password(TEST_PASSWD));
 
         assertTrue("Should be able to generate key when unlocked",
-                mKeyStore.generate(TEST_KEYNAME, Process.WIFI_UID, KeyStore.FLAG_ENCRYPTED));
+                mKeyStore.generate(TEST_KEYNAME, Process.WIFI_UID, NativeCrypto.EVP_PKEY_RSA,
+                        1024, KeyStore.FLAG_ENCRYPTED, null));
         assertTrue(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
         assertFalse(mKeyStore.contains(TEST_KEYNAME));
     }
@@ -378,7 +383,8 @@
     public void testGenerate_ungrantedUid_Bluetooth_Failure() throws Exception {
         assertTrue(mKeyStore.password(TEST_PASSWD));
 
-        assertFalse(mKeyStore.generate(TEST_KEYNAME, Process.BLUETOOTH_UID, KeyStore.FLAG_ENCRYPTED));
+        assertFalse(mKeyStore.generate(TEST_KEYNAME, Process.BLUETOOTH_UID,
+                    NativeCrypto.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED, null));
         assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
         assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
         assertFalse(mKeyStore.contains(TEST_KEYNAME));
@@ -424,7 +430,8 @@
     public void testSign_Success() throws Exception {
         mKeyStore.password(TEST_PASSWD);
 
-        assertTrue(mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+        assertTrue(mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeCrypto.EVP_PKEY_RSA,
+                    1024, KeyStore.FLAG_ENCRYPTED, null));
         assertTrue(mKeyStore.contains(TEST_KEYNAME));
         final byte[] signature = mKeyStore.sign(TEST_KEYNAME, TEST_DATA);
 
@@ -434,7 +441,8 @@
     public void testVerify_Success() throws Exception {
         mKeyStore.password(TEST_PASSWD);
 
-        assertTrue(mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+        assertTrue(mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeCrypto.EVP_PKEY_RSA,
+                    1024, KeyStore.FLAG_ENCRYPTED, null));
         assertTrue(mKeyStore.contains(TEST_KEYNAME));
         final byte[] signature = mKeyStore.sign(TEST_KEYNAME, TEST_DATA);
 
@@ -461,7 +469,8 @@
                 mKeyStore.password(TEST_PASSWD));
 
         assertTrue("Should be able to generate key for testcase",
-                mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+                mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeCrypto.EVP_PKEY_RSA,
+                        1024, KeyStore.FLAG_ENCRYPTED, null));
 
         assertTrue("Should be able to grant key to other user",
                 mKeyStore.grant(TEST_KEYNAME, 0));
@@ -494,7 +503,8 @@
                 mKeyStore.password(TEST_PASSWD));
 
         assertTrue("Should be able to generate key for testcase",
-                mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+                mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeCrypto.EVP_PKEY_RSA,
+                        1024, KeyStore.FLAG_ENCRYPTED, null));
 
         assertTrue("Should be able to grant key to other user",
                 mKeyStore.grant(TEST_KEYNAME, 0));
@@ -527,7 +537,8 @@
                 mKeyStore.password(TEST_PASSWD));
 
         assertTrue("Should be able to generate key for testcase",
-                mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+                mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeCrypto.EVP_PKEY_RSA,
+                        1024, KeyStore.FLAG_ENCRYPTED, null));
 
         assertFalse("Should not be able to revoke not existent grant",
                 mKeyStore.ungrant(TEST_KEYNAME, 0));
@@ -538,7 +549,8 @@
                 mKeyStore.password(TEST_PASSWD));
 
         assertTrue("Should be able to generate key for testcase",
-                mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+                mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeCrypto.EVP_PKEY_RSA,
+                        1024, KeyStore.FLAG_ENCRYPTED, null));
 
         assertTrue("Should be able to grant key to other user",
                 mKeyStore.grant(TEST_KEYNAME, 0));
@@ -555,7 +567,8 @@
                 mKeyStore.password(TEST_PASSWD));
 
         assertTrue("Should be able to generate key for testcase",
-                mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+                mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeCrypto.EVP_PKEY_RSA,
+                        1024, KeyStore.FLAG_ENCRYPTED, null));
 
         assertTrue("Should be able to grant key to other user",
                 mKeyStore.grant(TEST_KEYNAME, 0));
@@ -575,7 +588,8 @@
 
         assertFalse(mKeyStore.contains(TEST_KEYNAME));
 
-        assertTrue(mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+        assertTrue(mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeCrypto.EVP_PKEY_RSA,
+                    1024, KeyStore.FLAG_ENCRYPTED, null));
 
         assertTrue(mKeyStore.contains(TEST_KEYNAME));
         assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
@@ -613,7 +627,8 @@
 
         assertFalse(mKeyStore.contains(TEST_KEYNAME));
 
-        assertTrue(mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
+        assertTrue(mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeCrypto.EVP_PKEY_RSA,
+                    1024, KeyStore.FLAG_ENCRYPTED, null));
 
         assertTrue(mKeyStore.contains(TEST_KEYNAME));
         assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java
index 4dcac31..58f5d55 100644
--- a/media/java/android/media/RemoteControlClient.java
+++ b/media/java/android/media/RemoteControlClient.java
@@ -294,14 +294,13 @@
      */
     public final static int FLAG_KEY_MEDIA_POSITION_UPDATE = 1 << 8;
     /**
-     * @hide
-     * CANDIDATE FOR PUBLIC API
      * Flag indicating a RemoteControlClient supports ratings.
      * This flag must be set in order for components that display the RemoteControlClient
      * information, to display ratings information, and, if ratings are declared editable
      * (by calling {@link MetadataEditor#addEditableKey(int)} with the
      * {@link MetadataEditor#LONG_KEY_RATING_BY_USER} key), it will enable the user to rate
-     * the media.
+     * the media, with values being received through the interface set with
+     * {@link #setMetadataUpdateListener(OnMetadataUpdateListener)}.
      * @see #setTransportControlFlags(int)
      */
     public final static int FLAG_KEY_MEDIA_RATING = 1 << 9;
@@ -452,8 +451,6 @@
          */
         public final static int BITMAP_KEY_ARTWORK = 100;
         /**
-         * @hide
-         * CANDIDATE FOR PUBLIC API
          * The metadata key qualifying the content rating.
          * The value associated with this key may be: {@link #RATING_HEART},
          * {@link #RATING_THUMB_UP_DOWN}, or a non-null positive integer expressing a maximum
@@ -461,8 +458,6 @@
          */
         public final static int LONG_KEY_RATING_TYPE = 101;
         /**
-         * @hide
-         * CANDIDATE FOR PUBLIC API
          * The metadata key for the content's average rating, not the user's rating.
          * The value associated with this key may be: an integer value between 0 and 100,
          * or {@link #RATING_NOT_RATED} to express that no average rating is available.
@@ -472,11 +467,12 @@
          * <p></p>
          * When the rating type is:
          * <ul>
-         * <li>{@link #RATING_HEART}, a rating of 50 to 100 means "heart selected",</li>
-         * <li>{@link #RATING_THUMB_UP_DOWN}, a rating of 0 to 49 means "thumb down", 50 means
-         *     both "thumb up" and "thumb down" selected, 51 to 100 means "thumb up"</li>
+         * <li>{@link #RATING_HEART}, a rating of 51 to 100 means "heart selected",</li>
+         * <li>{@link #RATING_THUMB_UP_DOWN}, a rating of 0 to 50 means "thumb down",
+         *     51 to 100 means "thumb up"</li>
          * <li>a non-null positive integer, the rating value is mapped to the number of stars, e.g.
-         *     with a maximum of 5 stars, a rating of 21 to 40 maps to 2 stars.</li>
+         *     with a maximum of 5 stars, a rating of 0 maps to 0 stars, 1 to 20 maps to 1 star,
+         *     21 to 40 maps to 2 stars, etc.</li>
          * </ul>
          * @see #LONG_KEY_RATING_BY_USER
          */
@@ -489,34 +485,29 @@
          */
         public final static int KEY_EDITABLE_MASK = 0x1FFFFFFF;
         /**
-         * @hide
-         * CANDIDATE FOR PUBLIC API
-         * The metadata key for the content's rating by the user.
+         * The metadata key for the content's user rating.
          * The value associated with this key may be: an integer value between 0 and 100,
          * or {@link #RATING_NOT_RATED} to express that the user hasn't rated this content.
          * Rules for the interpretation of the rating value according to the rating style are
-         * the same as for {@link #LONG_KEY_RATING_BY_OTHERS}
+         * the same as for {@link #LONG_KEY_RATING_BY_OTHERS}.
+         * This key can be flagged as "editable" (with {@link #addEditableKey(int)}) to enable
+         * receiving user rating values through the
+         * {@link android.media.RemoteControlClient.OnMetadataUpdateListener} interface.
          */
         public final static int LONG_KEY_RATING_BY_USER = 0x10000001;
 
         /**
-         * @hide
-         * CANDIDATE FOR PUBLIC API
          * A rating style with a single degree of rating, "heart" vs "no heart". Can be used to
          * indicate the content referred to is a favorite (or not).
          * @see #LONG_KEY_RATING_TYPE
          */
         public final static long RATING_HEART = -1;
         /**
-         * @hide
-         * CANDIDATE FOR PUBLIC API
          * A rating style for "thumb up" vs "thumb down".
          * @see #LONG_KEY_RATING_TYPE
          */
         public final static long RATING_THUMB_UP_DOWN = -2;
         /**
-         * @hide
-         * CANDIDATE FOR PUBLIC API
          * A rating value indicating no rating is available.
          * @see #LONG_KEY_RATING_BY_OTHERS
          * @see #LONG_KEY_RATING_BY_USER
@@ -573,7 +564,9 @@
          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_DISC_NUMBER},
          *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} (with a value
          *      expressed in milliseconds),
-         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_YEAR}.
+         *      {@link android.media.MediaMetadataRetriever#METADATA_KEY_YEAR},
+         *      {@link #LONG_KEY_RATING_BY_OTHERS}, {@link #LONG_KEY_RATING_BY_USER},
+         *      {@link #LONG_KEY_RATING_TYPE}.
          * @param value The long value for the given key
          * @return Returns a reference to the same MetadataEditor object, so you can chain put
          *      calls together.
@@ -620,8 +613,9 @@
         /**
          * Clears all the metadata that has been set since the MetadataEditor instance was
          *     created with {@link RemoteControlClient#editMetadata(boolean)}.
+         * Note that clearing the metadata doesn't reset the editable keys
+         * (use {@link #clearEditableKeys()} instead).
          */
-        // TODO add in javadoc that this doesn't call clearEditableKeys()
         public synchronized void clear() {
             if (mApplied) {
                 Log.e(TAG, "Can't clear a previously applied MetadataEditor");
@@ -632,8 +626,11 @@
         }
 
         /**
-         * @hide
-         * CANDIDATE FOR PUBLIC API
+         * Flag the given key as being editable.
+         * This will declare the metadata field as eligible to be updated, with new values
+         * received through the {@link RemoteControlClient.OnMetadataUpdateListener} interface.
+         * @param key the type of metadata that can be edited. The supported key is
+         *     {@link #LONG_KEY_RATING_BY_USER}.
          */
         public synchronized void addEditableKey(int key) {
             if (mApplied) {
@@ -651,8 +648,7 @@
         }
 
         /**
-         * @hide
-         * CANDIDATE FOR PUBLIC API
+         * Causes all metadata fields to be read-only.
          */
         public synchronized void clearEditableKeys() {
             if (mApplied) {
@@ -869,7 +865,8 @@
      *      {@link #FLAG_KEY_MEDIA_STOP},
      *      {@link #FLAG_KEY_MEDIA_FAST_FORWARD},
      *      {@link #FLAG_KEY_MEDIA_NEXT},
-     *      {@link #FLAG_KEY_MEDIA_POSITION_UPDATE}
+     *      {@link #FLAG_KEY_MEDIA_POSITION_UPDATE},
+     *      {@link #FLAG_KEY_MEDIA_RATING}.
      */
     public void setTransportControlFlags(int transportControlFlags) {
         synchronized(mCacheLock) {
@@ -882,36 +879,39 @@
     }
 
     /**
-     * @hide
-     * CANDIDATE FOR PUBLIC API
-     * TODO ADD DESCRIPTION
+     * Interface definition for a callback to be invoked when one of the metadata values has
+     * been updated.
      */
     public interface OnMetadataUpdateListener  {
         /**
-         * TODO ADD DESCRIPTION
-         * @param key
-         * @param newValue
+         * Called on the implementer to notify that the metadata field for the given key has
+         * been updated to the new value of type <code>long</long>.
+         * @param key the identifier of the updated metadata field of type <code>long</long>.
+         * @param newValue the new <code>long</long> value for the key
          */
         void onMetadataUpdateLong(int key, long newValue);
         /**
-         * TODO ADD DESCRIPTION
-         * @param key
-         * @param newValue
+         * Called on the implementer to notify that the metadata field for the given key has
+         * been updated to the new <code>String</long>.
+         * @param key the identifier of the updated metadata field of type <code>String</long>.
+         * @param newValue the new <code>String</long> value for the key
          */
         void onMetadataUpdateString(int key, String newValue);
         /**
-         * TODO ADD DESCRIPTION
-         * @param key
-         * @param newValue
+         * Called on the implementer to notify that the metadata field for the given key has
+         * been updated to the new {@link android.graphics.Bitmap}.
+         * @param key the identifier of the updated metadata field of type
+         *     {@link android.graphics.Bitmap}.
+         * @param newValue the new {@link android.graphics.Bitmap} for the key
          */
         void onMetadataUpdateBitmap(int key, Bitmap newValue);
     }
 
     /**
-     * @hide
-     * CANDIDATE FOR PUBLIC API
-     * TODO ADD DESCRIPTION
-     * @param l
+     * Sets the listener to be called whenever the metadata is updated.
+     * New metadata values will be received in the same thread as the one in which
+     * RemoteControlClient was created.
+     * @param l the metadata update listener
      */
     public void setMetadataUpdateListener(OnMetadataUpdateListener l) {
         synchronized(mCacheLock) {
diff --git a/packages/DocumentsUI/Android.mk b/packages/DocumentsUI/Android.mk
index 853353d..7900953 100644
--- a/packages/DocumentsUI/Android.mk
+++ b/packages/DocumentsUI/Android.mk
@@ -5,7 +5,7 @@
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
 
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 guava
 
 LOCAL_PACKAGE_NAME := DocumentsUI
 LOCAL_CERTIFICATE := platform
diff --git a/packages/DocumentsUI/AndroidManifest.xml b/packages/DocumentsUI/AndroidManifest.xml
index 6cc92e3..45e2650 100644
--- a/packages/DocumentsUI/AndroidManifest.xml
+++ b/packages/DocumentsUI/AndroidManifest.xml
@@ -11,10 +11,7 @@
         <!-- TODO: allow rotation when state saving is in better shape -->
         <activity
             android:name=".DocumentsActivity"
-            android:finishOnCloseSystemDialogs="true"
-            android:excludeFromRecents="true"
-            android:theme="@android:style/Theme.Holo.Light"
-            android:screenOrientation="nosensor">
+            android:theme="@android:style/Theme.Holo.Light">
             <intent-filter android:priority="100">
                 <action android:name="android.intent.action.OPEN_DOCUMENT" />
                 <category android:name="android.intent.category.DEFAULT" />
@@ -37,7 +34,7 @@
             <intent-filter>
                 <action android:name="android.provider.action.MANAGE_DOCUMENTS" />
                 <category android:name="android.intent.category.DEFAULT" />
-                <data android:mimeType="vnd.android.doc/dir" />
+                <data android:mimeType="vnd.android.document/directory" />
             </intent-filter>
         </activity>
 
diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml
index 928ba85..f4a822d 100644
--- a/packages/DocumentsUI/res/values/strings.xml
+++ b/packages/DocumentsUI/res/values/strings.xml
@@ -63,4 +63,6 @@
     <string name="more">More</string>
     <string name="loading">Loading\u2026</string>
 
+    <string name="share_via">Share via</string>
+
 </resources>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
index 6bc554f..e0b8d19 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
@@ -20,7 +20,6 @@
 import android.app.Dialog;
 import android.app.DialogFragment;
 import android.app.FragmentManager;
-import android.content.ContentProviderClient;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -28,13 +27,13 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.provider.DocumentsContract;
-import android.provider.DocumentsContract.Documents;
+import android.provider.DocumentsContract.Document;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.EditText;
 import android.widget.Toast;
 
-import com.android.documentsui.model.Document;
+import com.android.documentsui.model.DocumentInfo;
 
 /**
  * Dialog to create a new directory.
@@ -67,24 +66,17 @@
                 final String displayName = text1.getText().toString();
 
                 final DocumentsActivity activity = (DocumentsActivity) getActivity();
-                final Document cwd = activity.getCurrentDirectory();
+                final DocumentInfo cwd = activity.getCurrentDirectory();
 
-                final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
-                        cwd.uri.getAuthority());
                 try {
-                    final String docId = DocumentsContract.createDocument(client,
-                            DocumentsContract.getDocId(cwd.uri), Documents.MIME_TYPE_DIR,
-                            displayName);
+                    final Uri childUri = DocumentsContract.createDocument(
+                            resolver, cwd.uri, Document.MIME_TYPE_DIR, displayName);
 
                     // Navigate into newly created child
-                    final Uri childUri = DocumentsContract.buildDocumentUri(
-                            cwd.uri.getAuthority(), docId);
-                    final Document childDoc = Document.fromUri(resolver, childUri);
+                    final DocumentInfo childDoc = DocumentInfo.fromUri(resolver, childUri);
                     activity.onDocumentPicked(childDoc);
                 } catch (Exception e) {
                     Toast.makeText(context, R.string.save_error, Toast.LENGTH_SHORT).show();
-                } finally {
-                    ContentProviderClient.closeQuietly(client);
                 }
             }
         });
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index 783b6ff..5b23ca5 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
@@ -17,12 +17,12 @@
 package com.android.documentsui;
 
 import static com.android.documentsui.DocumentsActivity.TAG;
-import static com.android.documentsui.DocumentsActivity.DisplayState.ACTION_MANAGE;
-import static com.android.documentsui.DocumentsActivity.DisplayState.MODE_GRID;
-import static com.android.documentsui.DocumentsActivity.DisplayState.MODE_LIST;
-import static com.android.documentsui.DocumentsActivity.DisplayState.SORT_ORDER_DISPLAY_NAME;
-import static com.android.documentsui.DocumentsActivity.DisplayState.SORT_ORDER_LAST_MODIFIED;
-import static com.android.documentsui.DocumentsActivity.DisplayState.SORT_ORDER_SIZE;
+import static com.android.documentsui.DocumentsActivity.State.ACTION_MANAGE;
+import static com.android.documentsui.DocumentsActivity.State.MODE_GRID;
+import static com.android.documentsui.DocumentsActivity.State.MODE_LIST;
+import static com.android.documentsui.model.DocumentInfo.getCursorInt;
+import static com.android.documentsui.model.DocumentInfo.getCursorLong;
+import static com.android.documentsui.model.DocumentInfo.getCursorString;
 
 import android.app.Fragment;
 import android.app.FragmentManager;
@@ -32,12 +32,14 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.Loader;
+import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.Point;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.provider.DocumentsContract;
+import android.provider.DocumentsContract.Document;
 import android.text.format.DateUtils;
 import android.text.format.Formatter;
 import android.text.format.Time;
@@ -60,13 +62,13 @@
 import android.widget.TextView;
 import android.widget.Toast;
 
-import com.android.documentsui.DocumentsActivity.DisplayState;
-import com.android.documentsui.model.Document;
+import com.android.documentsui.DocumentsActivity.State;
+import com.android.documentsui.model.DocumentInfo;
+import com.android.documentsui.model.RootInfo;
 import com.android.internal.util.Predicate;
 import com.google.android.collect.Lists;
 
 import java.util.ArrayList;
-import java.util.Comparator;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -81,7 +83,7 @@
 
     private AbsListView mCurrentView;
 
-    private Predicate<Document> mFilter;
+    private Predicate<DocumentInfo> mFilter;
 
     public static final int TYPE_NORMAL = 1;
     public static final int TYPE_SEARCH = 2;
@@ -95,30 +97,38 @@
     private LoaderCallbacks<DirectoryResult> mCallbacks;
 
     private static final String EXTRA_TYPE = "type";
-    private static final String EXTRA_URI = "uri";
+    private static final String EXTRA_AUTHORITY = "authority";
+    private static final String EXTRA_ROOT_ID = "rootId";
+    private static final String EXTRA_DOC_ID = "docId";
+    private static final String EXTRA_QUERY = "query";
 
     private static AtomicInteger sLoaderId = new AtomicInteger(4000);
 
+    private int mLastSortOrder = -1;
+
     private final int mLoaderId = sLoaderId.incrementAndGet();
 
     public static void showNormal(FragmentManager fm, Uri uri) {
-        show(fm, TYPE_NORMAL, uri);
+        show(fm, TYPE_NORMAL, uri.getAuthority(), null, DocumentsContract.getDocumentId(uri), null);
     }
 
     public static void showSearch(FragmentManager fm, Uri uri, String query) {
-        final Uri searchUri = DocumentsContract.buildSearchUri(
-                uri.getAuthority(), DocumentsContract.getDocId(uri), query);
-        show(fm, TYPE_SEARCH, searchUri);
+        show(fm, TYPE_SEARCH, uri.getAuthority(), null, DocumentsContract.getDocumentId(uri),
+                query);
     }
 
     public static void showRecentsOpen(FragmentManager fm) {
-        show(fm, TYPE_RECENT_OPEN, null);
+        show(fm, TYPE_RECENT_OPEN, null, null, null, null);
     }
 
-    private static void show(FragmentManager fm, int type, Uri uri) {
+    private static void show(FragmentManager fm, int type, String authority, String rootId,
+            String docId, String query) {
         final Bundle args = new Bundle();
         args.putInt(EXTRA_TYPE, type);
-        args.putParcelable(EXTRA_URI, uri);
+        args.putString(EXTRA_AUTHORITY, authority);
+        args.putString(EXTRA_ROOT_ID, rootId);
+        args.putString(EXTRA_DOC_ID, docId);
+        args.putString(EXTRA_QUERY, query);
 
         final DirectoryFragment fragment = new DirectoryFragment();
         fragment.setArguments(args);
@@ -137,7 +147,6 @@
     public View onCreateView(
             LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
         final Context context = inflater.getContext();
-
         final View view = inflater.inflate(R.layout.fragment_directory, container, false);
 
         mEmptyView = view.findViewById(android.R.id.empty);
@@ -150,80 +159,77 @@
         mGridView.setOnItemClickListener(mItemListener);
         mGridView.setMultiChoiceModeListener(mMultiListener);
 
-        mAdapter = new DocumentsAdapter();
+        return view;
+    }
 
-        final Uri uri = getArguments().getParcelable(EXTRA_URI);
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+        final Context context = getActivity();
+
+        mAdapter = new DocumentsAdapter();
         mType = getArguments().getInt(EXTRA_TYPE);
 
         mCallbacks = new LoaderCallbacks<DirectoryResult>() {
             @Override
             public Loader<DirectoryResult> onCreateLoader(int id, Bundle args) {
-                final DisplayState state = getDisplayState(DirectoryFragment.this);
-                mFilter = new MimePredicate(state.acceptMimes);
+                final State state = getDisplayState(DirectoryFragment.this);
+
+                final String authority = getArguments().getString(EXTRA_AUTHORITY);
+                final String rootId = getArguments().getString(EXTRA_ROOT_ID);
+                final String docId = getArguments().getString(EXTRA_DOC_ID);
+                final String query = getArguments().getString(EXTRA_QUERY);
 
                 Uri contentsUri;
-                if (mType == TYPE_NORMAL) {
-                    contentsUri = DocumentsContract.buildChildrenUri(
-                            uri.getAuthority(), DocumentsContract.getDocId(uri));
-                } else if (mType == TYPE_RECENT_OPEN) {
-                    contentsUri = RecentsProvider.buildRecentOpen();
-                } else {
-                    contentsUri = uri;
-                }
+                switch (mType) {
+                    case TYPE_NORMAL:
+                        contentsUri = DocumentsContract.buildChildDocumentsUri(authority, docId);
+                        return new DirectoryLoader(context, rootId, contentsUri, state.sortOrder);
+                    case TYPE_SEARCH:
+                        contentsUri = DocumentsContract.buildSearchDocumentsUri(
+                                authority, docId, query);
+                        return new DirectoryLoader(context, rootId, contentsUri, state.sortOrder);
+                    case TYPE_RECENT_OPEN:
+                        final RootsCache roots = DocumentsApplication.getRootsCache(context);
+                        final List<RootInfo> matchingRoots = roots.getMatchingRoots(state);
+                        return new RecentLoader(context, matchingRoots);
+                    default:
+                        throw new IllegalStateException("Unknown type " + mType);
 
-                final Comparator<Document> sortOrder;
-                if (state.sortOrder == SORT_ORDER_LAST_MODIFIED || mType == TYPE_RECENT_OPEN) {
-                    sortOrder = new Document.LastModifiedComparator();
-                } else if (state.sortOrder == SORT_ORDER_DISPLAY_NAME) {
-                    sortOrder = new Document.DisplayNameComparator();
-                } else if (state.sortOrder == SORT_ORDER_SIZE) {
-                    sortOrder = new Document.SizeComparator();
-                } else {
-                    throw new IllegalArgumentException("Unknown sort order " + state.sortOrder);
                 }
-
-                return new DirectoryLoader(context, contentsUri, mType, null, sortOrder);
             }
 
             @Override
             public void onLoadFinished(Loader<DirectoryResult> loader, DirectoryResult result) {
-                mAdapter.swapDocuments(result.contents);
+                mAdapter.swapCursor(result.cursor);
             }
 
             @Override
             public void onLoaderReset(Loader<DirectoryResult> loader) {
-                mAdapter.swapDocuments(null);
+                mAdapter.swapCursor(null);
             }
         };
 
         updateDisplayState();
-
-        return view;
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-        getLoaderManager().restartLoader(mLoaderId, getArguments(), mCallbacks);
-    }
-
-    @Override
-    public void onStop() {
-        super.onStop();
-        getLoaderManager().destroyLoader(mLoaderId);
     }
 
     public void updateDisplayState() {
-        final DisplayState state = getDisplayState(this);
+        final State state = getDisplayState(this);
 
-        // TODO: avoid kicking loader when nothing changed
-        getLoaderManager().restartLoader(mLoaderId, getArguments(), mCallbacks);
+        if (mLastSortOrder != state.sortOrder) {
+            getLoaderManager().restartLoader(mLoaderId, null, mCallbacks);
+            mLastSortOrder = state.sortOrder;
+        }
+
         mListView.smoothScrollToPosition(0);
         mGridView.smoothScrollToPosition(0);
 
         mListView.setVisibility(state.mode == MODE_LIST ? View.VISIBLE : View.GONE);
         mGridView.setVisibility(state.mode == MODE_GRID ? View.VISIBLE : View.GONE);
 
+        mFilter = new MimePredicate(state.acceptMimes);
+
         final int choiceMode;
         if (state.allowMultiple) {
             choiceMode = ListView.CHOICE_MODE_MULTIPLE_MODAL;
@@ -258,7 +264,8 @@
     private OnItemClickListener mItemListener = new OnItemClickListener() {
         @Override
         public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-            final Document doc = mAdapter.getItem(position);
+            final Cursor cursor = mAdapter.getItem(position);
+            final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(cursor);
             if (mFilter.apply(doc)) {
                 ((DocumentsActivity) getActivity()).onDocumentPicked(doc);
             }
@@ -274,7 +281,7 @@
 
         @Override
         public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
-            final DisplayState state = getDisplayState(DirectoryFragment.this);
+            final State state = getDisplayState(DirectoryFragment.this);
 
             final MenuItem open = menu.findItem(R.id.menu_open);
             final MenuItem share = menu.findItem(R.id.menu_share);
@@ -291,11 +298,12 @@
         @Override
         public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
             final SparseBooleanArray checked = mCurrentView.getCheckedItemPositions();
-            final ArrayList<Document> docs = Lists.newArrayList();
+            final ArrayList<DocumentInfo> docs = Lists.newArrayList();
             final int size = checked.size();
             for (int i = 0; i < size; i++) {
                 if (checked.valueAt(i)) {
-                    final Document doc = mAdapter.getItem(checked.keyAt(i));
+                    final Cursor cursor = mAdapter.getItem(checked.keyAt(i));
+                    final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(cursor);
                     docs.add(doc);
                 }
             }
@@ -303,14 +311,17 @@
             final int id = item.getItemId();
             if (id == R.id.menu_open) {
                 DocumentsActivity.get(DirectoryFragment.this).onDocumentsPicked(docs);
+                mode.finish();
                 return true;
 
             } else if (id == R.id.menu_share) {
                 onShareDocuments(docs);
+                mode.finish();
                 return true;
 
             } else if (id == R.id.menu_delete) {
                 onDeleteDocuments(docs);
+                mode.finish();
                 return true;
 
             } else {
@@ -328,8 +339,9 @@
                 ActionMode mode, int position, long id, boolean checked) {
             if (checked) {
                 // Directories cannot be checked
-                final Document doc = mAdapter.getItem(position);
-                if (doc.isDirectory()) {
+                final Cursor cursor = mAdapter.getItem(position);
+                final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
+                if (Document.MIME_TYPE_DIR.equals(docMimeType)) {
                     mCurrentView.setItemChecked(position, false);
                 }
             }
@@ -339,36 +351,46 @@
         }
     };
 
-    private void onShareDocuments(List<Document> docs) {
-        final ArrayList<Uri> uris = Lists.newArrayList();
-        for (Document doc : docs) {
-            uris.add(doc.uri);
-        }
+    private void onShareDocuments(List<DocumentInfo> docs) {
+        Intent intent;
+        if (docs.size() == 1) {
+            final DocumentInfo doc = docs.get(0);
 
-        final Intent intent;
-        if (uris.size() > 1) {
-            intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
-            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-            intent.addCategory(Intent.CATEGORY_DEFAULT);
-            // TODO: find common mimetype
-            intent.setType("*/*");
-            intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
-        } else {
             intent = new Intent(Intent.ACTION_SEND);
             intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
             intent.addCategory(Intent.CATEGORY_DEFAULT);
-            intent.setData(uris.get(0));
+            intent.setType(doc.mimeType);
+            intent.putExtra(Intent.EXTRA_STREAM, doc.uri);
+
+        } else if (docs.size() > 1) {
+            intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
+            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+            intent.addCategory(Intent.CATEGORY_DEFAULT);
+
+            final ArrayList<String> mimeTypes = Lists.newArrayList();
+            final ArrayList<Uri> uris = Lists.newArrayList();
+            for (DocumentInfo doc : docs) {
+                mimeTypes.add(doc.mimeType);
+                uris.add(doc.uri);
+            }
+
+            intent.setType(findCommonMimeType(mimeTypes));
+            intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
+
+        } else {
+            return;
         }
 
+        intent = Intent.createChooser(intent, getActivity().getText(R.string.share_via));
         startActivity(intent);
     }
 
-    private void onDeleteDocuments(List<Document> docs) {
+    private void onDeleteDocuments(List<DocumentInfo> docs) {
         final Context context = getActivity();
         final ContentResolver resolver = context.getContentResolver();
 
         boolean hadTrouble = false;
-        for (Document doc : docs) {
+        for (DocumentInfo doc : docs) {
             if (!doc.isDeleteSupported()) {
                 Log.w(TAG, "Skipping " + doc);
                 hadTrouble = true;
@@ -391,20 +413,17 @@
         }
     }
 
-    private static DisplayState getDisplayState(Fragment fragment) {
+    private static State getDisplayState(Fragment fragment) {
         return ((DocumentsActivity) fragment.getActivity()).getDisplayState();
     }
 
     private class DocumentsAdapter extends BaseAdapter {
-        private List<Document> mDocuments;
+        private Cursor mCursor;
 
-        public DocumentsAdapter() {
-        }
+        public void swapCursor(Cursor cursor) {
+            mCursor = cursor;
 
-        public void swapDocuments(List<Document> documents) {
-            mDocuments = documents;
-
-            if (mDocuments != null && mDocuments.isEmpty()) {
+            if (isEmpty()) {
                 mEmptyView.setVisibility(View.VISIBLE);
             } else {
                 mEmptyView.setVisibility(View.GONE);
@@ -416,7 +435,7 @@
         @Override
         public View getView(int position, View convertView, ViewGroup parent) {
             final Context context = parent.getContext();
-            final DisplayState state = getDisplayState(DirectoryFragment.this);
+            final State state = getDisplayState(DirectoryFragment.this);
 
             final RootsCache roots = DocumentsApplication.getRootsCache(context);
             final ThumbnailCache thumbs = DocumentsApplication.getThumbnailsCache(
@@ -433,7 +452,18 @@
                 }
             }
 
-            final Document doc = getItem(position);
+            final Cursor cursor = getItem(position);
+
+            final String docAuthority = getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY);
+            final String docRootId = getCursorString(cursor, RootCursorWrapper.COLUMN_ROOT_ID);
+            final String docId = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID);
+            final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
+            final String docDisplayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
+            final long docLastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED);
+            final int docIcon = getCursorInt(cursor, Document.COLUMN_ICON);
+            final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
+            final String docSummary = getCursorString(cursor, Document.COLUMN_SUMMARY);
+            final long docSize = getCursorLong(cursor, Document.COLUMN_SIZE);
 
             final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
             final TextView title = (TextView) convertView.findViewById(android.R.id.title);
@@ -448,32 +478,39 @@
                 oldTask.cancel(false);
             }
 
-            if (doc.isThumbnailSupported()) {
-                final Bitmap cachedResult = thumbs.get(doc.uri);
+            if ((docFlags & Document.FLAG_SUPPORTS_THUMBNAIL) != 0) {
+                final Uri uri = DocumentsContract.buildDocumentUri(docAuthority, docId);
+                final Bitmap cachedResult = thumbs.get(uri);
                 if (cachedResult != null) {
                     icon.setImageBitmap(cachedResult);
                 } else {
                     final ThumbnailAsyncTask task = new ThumbnailAsyncTask(icon, mThumbSize);
                     icon.setImageBitmap(null);
                     icon.setTag(task);
-                    task.execute(doc.uri);
+                    task.execute(uri);
                 }
+            } else if (docIcon != 0) {
+                icon.setImageDrawable(DocumentInfo.loadIcon(context, docAuthority, docIcon));
             } else {
-                icon.setImageDrawable(RootsCache.resolveDocumentIcon(context, doc.mimeType));
+                icon.setImageDrawable(RootsCache.resolveDocumentIcon(context, docMimeType));
             }
 
-            title.setText(doc.displayName);
+            title.setText(docDisplayName);
 
-            if (mType == TYPE_NORMAL || mType == TYPE_SEARCH) {
+            if (mType == TYPE_RECENT_OPEN) {
+                final RootInfo root = roots.getRoot(docAuthority, docRootId);
+                icon1.setVisibility(View.VISIBLE);
+                icon1.setImageDrawable(root.loadIcon(context));
+                summary.setText(root.getDirectoryString());
+                summary.setVisibility(View.VISIBLE);
+            } else {
                 icon1.setVisibility(View.GONE);
-                if (doc.summary != null) {
-                    summary.setText(doc.summary);
+                if (docSummary != null) {
+                    summary.setText(docSummary);
                     summary.setVisibility(View.VISIBLE);
                 } else {
                     summary.setVisibility(View.INVISIBLE);
                 }
-            } else if (mType == TYPE_RECENT_OPEN) {
-                // TODO: resolve storage root
             }
 
             if (summaryGrid != null) {
@@ -481,18 +518,18 @@
                         (summary.getVisibility() == View.VISIBLE) ? View.VISIBLE : View.GONE);
             }
 
-            if (doc.lastModified == -1) {
+            if (docLastModified == -1) {
                 date.setText(null);
             } else {
-                date.setText(formatTime(context, doc.lastModified));
+                date.setText(formatTime(context, docLastModified));
             }
 
             if (state.showSize) {
                 size.setVisibility(View.VISIBLE);
-                if (doc.isDirectory() || doc.size == -1) {
+                if (Document.MIME_TYPE_DIR.equals(docMimeType) || docSize == -1) {
                     size.setText(null);
                 } else {
-                    size.setText(Formatter.formatFileSize(context, doc.size));
+                    size.setText(Formatter.formatFileSize(context, docSize));
                 }
             } else {
                 size.setVisibility(View.GONE);
@@ -503,17 +540,20 @@
 
         @Override
         public int getCount() {
-            return mDocuments != null ? mDocuments.size() : 0;
+            return mCursor != null ? mCursor.getCount() : 0;
         }
 
         @Override
-        public Document getItem(int position) {
-            return mDocuments.get(position);
+        public Cursor getItem(int position) {
+            if (mCursor != null) {
+                mCursor.moveToPosition(position);
+            }
+            return mCursor;
         }
 
         @Override
         public long getItemId(int position) {
-            return getItem(position).uri.hashCode();
+            return position;
         }
     }
 
@@ -538,8 +578,8 @@
 
             Bitmap result = null;
             try {
-                result = DocumentsContract.getThumbnail(
-                        context.getContentResolver(), uri, mThumbSize);
+                result = DocumentsContract.getDocumentThumbnail(
+                        context.getContentResolver(), uri, mThumbSize, null);
                 if (result != null) {
                     final ThumbnailCache thumbs = DocumentsApplication.getThumbnailsCache(
                             context, mThumbSize);
@@ -580,4 +620,28 @@
 
         return DateUtils.formatDateTime(context, when, flags);
     }
+
+    private String findCommonMimeType(List<String> mimeTypes) {
+        String[] commonType = mimeTypes.get(0).split("/");
+        if (commonType.length != 2) {
+            return "*/*";
+        }
+
+        for (int i = 1; i < mimeTypes.size(); i++) {
+            String[] type = mimeTypes.get(i).split("/");
+            if (type.length != 2) continue;
+
+            if (!commonType[1].equals(type[1])) {
+                commonType[1] = "*";
+            }
+
+            if (!commonType[0].equals(type[0])) {
+                commonType[0] = "*";
+                commonType[1] = "*";
+                break;
+            }
+        }
+
+        return commonType[0] + "/" + commonType[1];
+    }
 }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
index 4ce5ef8..3f016b5 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
@@ -16,98 +16,155 @@
 
 package com.android.documentsui;
 
-import static com.android.documentsui.DirectoryFragment.TYPE_NORMAL;
-import static com.android.documentsui.DirectoryFragment.TYPE_RECENT_OPEN;
-import static com.android.documentsui.DirectoryFragment.TYPE_SEARCH;
-import static com.android.documentsui.DocumentsActivity.TAG;
+import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_DISPLAY_NAME;
+import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_LAST_MODIFIED;
+import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_SIZE;
 
-import android.content.ContentResolver;
+import android.content.AsyncTaskLoader;
+import android.content.ContentProviderClient;
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.CancellationSignal;
-import android.util.Log;
-
-import com.android.documentsui.model.Document;
-import com.android.internal.util.Predicate;
-import com.google.android.collect.Lists;
+import android.os.OperationCanceledException;
+import android.provider.DocumentsContract.Document;
 
 import libcore.io.IoUtils;
 
-import java.io.FileNotFoundException;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-
 class DirectoryResult implements AutoCloseable {
+    ContentProviderClient client;
     Cursor cursor;
-    List<Document> contents = Lists.newArrayList();
-    Exception e;
+    Exception exception;
 
     @Override
-    public void close() throws Exception {
+    public void close() {
         IoUtils.closeQuietly(cursor);
+        ContentProviderClient.closeQuietly(client);
+        cursor = null;
+        client = null;
     }
 }
 
-public class DirectoryLoader extends UriDerivativeLoader<Uri, DirectoryResult> {
+public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
+    private final ForceLoadContentObserver mObserver = new ForceLoadContentObserver();
 
-    private final int mType;
-    private Predicate<Document> mFilter;
-    private Comparator<Document> mSortOrder;
+    private final String mRootId;
+    private final Uri mUri;
+    private final int mSortOrder;
 
-    public DirectoryLoader(Context context, Uri uri, int type, Predicate<Document> filter,
-            Comparator<Document> sortOrder) {
-        super(context, uri);
-        mType = type;
-        mFilter = filter;
+    private CancellationSignal mSignal;
+    private DirectoryResult mResult;
+
+    public DirectoryLoader(Context context, String rootId, Uri uri, int sortOrder) {
+        super(context);
+        mRootId = rootId;
+        mUri = uri;
         mSortOrder = sortOrder;
     }
 
     @Override
-    public DirectoryResult loadInBackground(Uri uri, CancellationSignal signal) {
+    public final DirectoryResult loadInBackground() {
+        synchronized (this) {
+            if (isLoadInBackgroundCanceled()) {
+                throw new OperationCanceledException();
+            }
+            mSignal = new CancellationSignal();
+        }
         final DirectoryResult result = new DirectoryResult();
+        final String authority = mUri.getAuthority();
         try {
-            loadInBackgroundInternal(result, uri, signal);
+            result.client = getContext()
+                    .getContentResolver().acquireUnstableContentProviderClient(authority);
+            final Cursor cursor = result.client.query(
+                    mUri, null, null, null, getQuerySortOrder(mSortOrder), mSignal);
+            final Cursor withRoot = new RootCursorWrapper(mUri.getAuthority(), mRootId, cursor, -1);
+            final Cursor sorted = new SortingCursorWrapper(withRoot, mSortOrder);
+
+            result.cursor = sorted;
+            result.cursor.registerContentObserver(mObserver);
         } catch (Exception e) {
-            result.e = e;
+            result.exception = e;
+            ContentProviderClient.closeQuietly(result.client);
+        } finally {
+            synchronized (this) {
+                mSignal = null;
+            }
         }
         return result;
     }
 
-    private void loadInBackgroundInternal(
-            DirectoryResult result, Uri uri, CancellationSignal signal) throws RuntimeException {
-        // TODO: switch to using unstable CPC
-        final ContentResolver resolver = getContext().getContentResolver();
-        final Cursor cursor = resolver.query(uri, null, null, null, null, signal);
-        result.cursor = cursor;
-        result.cursor.registerContentObserver(mObserver);
+    @Override
+    public void cancelLoadInBackground() {
+        super.cancelLoadInBackground();
 
-        while (cursor.moveToNext()) {
-            Document doc = null;
-            switch (mType) {
-                case TYPE_NORMAL:
-                case TYPE_SEARCH:
-                    doc = Document.fromDirectoryCursor(uri, cursor);
-                    break;
-                case TYPE_RECENT_OPEN:
-                    try {
-                        doc = Document.fromRecentOpenCursor(resolver, cursor);
-                    } catch (FileNotFoundException e) {
-                        Log.w(TAG, "Failed to find recent: " + e);
-                    }
-                    break;
-                default:
-                    throw new IllegalArgumentException("Unknown type");
-            }
-
-            if (doc != null && (mFilter == null || mFilter.apply(doc))) {
-                result.contents.add(doc);
+        synchronized (this) {
+            if (mSignal != null) {
+                mSignal.cancel();
             }
         }
+    }
 
-        if (mSortOrder != null) {
-            Collections.sort(result.contents, mSortOrder);
+    @Override
+    public void deliverResult(DirectoryResult result) {
+        if (isReset()) {
+            IoUtils.closeQuietly(result);
+            return;
+        }
+        DirectoryResult oldResult = mResult;
+        mResult = result;
+
+        if (isStarted()) {
+            super.deliverResult(result);
+        }
+
+        if (oldResult != null && oldResult != result) {
+            IoUtils.closeQuietly(oldResult);
+        }
+    }
+
+    @Override
+    protected void onStartLoading() {
+        if (mResult != null) {
+            deliverResult(mResult);
+        }
+        if (takeContentChanged() || mResult == null) {
+            forceLoad();
+        }
+    }
+
+    @Override
+    protected void onStopLoading() {
+        cancelLoad();
+    }
+
+    @Override
+    public void onCanceled(DirectoryResult result) {
+        IoUtils.closeQuietly(result);
+    }
+
+    @Override
+    protected void onReset() {
+        super.onReset();
+
+        // Ensure the loader is stopped
+        onStopLoading();
+
+        IoUtils.closeQuietly(mResult);
+        mResult = null;
+
+        getContext().getContentResolver().unregisterContentObserver(mObserver);
+    }
+
+    public static String getQuerySortOrder(int sortOrder) {
+        switch (sortOrder) {
+            case SORT_ORDER_DISPLAY_NAME:
+                return Document.COLUMN_DISPLAY_NAME + " ASC";
+            case SORT_ORDER_LAST_MODIFIED:
+                return Document.COLUMN_LAST_MODIFIED + " DESC";
+            case SORT_ORDER_SIZE:
+                return Document.COLUMN_SIZE + " DESC";
+            default:
+                return null;
         }
     }
 }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentChangedReceiver.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentChangedReceiver.java
index 0ce5968..54f62ef 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentChangedReceiver.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentChangedReceiver.java
@@ -21,11 +21,10 @@
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.provider.DocumentsContract.DocumentRoot;
 import android.util.Log;
 
 /**
- * Handles {@link DocumentRoot} changes which invalidate cached data.
+ * Handles changes which invalidate cached data.
  */
 public class DocumentChangedReceiver extends BroadcastReceiver {
     @Override
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index 73ca8fa..f569f5a 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -16,13 +16,13 @@
 
 package com.android.documentsui;
 
-import static com.android.documentsui.DocumentsActivity.DisplayState.ACTION_CREATE;
-import static com.android.documentsui.DocumentsActivity.DisplayState.ACTION_GET_CONTENT;
-import static com.android.documentsui.DocumentsActivity.DisplayState.ACTION_MANAGE;
-import static com.android.documentsui.DocumentsActivity.DisplayState.ACTION_OPEN;
-import static com.android.documentsui.DocumentsActivity.DisplayState.MODE_GRID;
-import static com.android.documentsui.DocumentsActivity.DisplayState.MODE_LIST;
-import static com.android.documentsui.DocumentsActivity.DisplayState.SORT_ORDER_LAST_MODIFIED;
+import static com.android.documentsui.DocumentsActivity.State.ACTION_CREATE;
+import static com.android.documentsui.DocumentsActivity.State.ACTION_GET_CONTENT;
+import static com.android.documentsui.DocumentsActivity.State.ACTION_MANAGE;
+import static com.android.documentsui.DocumentsActivity.State.ACTION_OPEN;
+import static com.android.documentsui.DocumentsActivity.State.MODE_GRID;
+import static com.android.documentsui.DocumentsActivity.State.MODE_LIST;
+import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_LAST_MODIFIED;
 
 import android.app.ActionBar;
 import android.app.ActionBar.OnNavigationListener;
@@ -41,8 +41,8 @@
 import android.graphics.drawable.ColorDrawable;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Parcel;
 import android.provider.DocumentsContract;
-import android.provider.DocumentsContract.DocumentRoot;
 import android.support.v4.app.ActionBarDrawerToggle;
 import android.support.v4.view.GravityCompat;
 import android.support.v4.widget.DrawerLayout;
@@ -60,32 +60,29 @@
 import android.widget.TextView;
 import android.widget.Toast;
 
-import com.android.documentsui.model.Document;
+import com.android.documentsui.model.DocumentInfo;
 import com.android.documentsui.model.DocumentStack;
+import com.android.documentsui.model.DurableUtils;
+import com.android.documentsui.model.RootInfo;
 
 import java.io.FileNotFoundException;
+import java.io.IOException;
 import java.util.Arrays;
 import java.util.List;
 
 public class DocumentsActivity extends Activity {
     public static final String TAG = "Documents";
 
-    private int mAction;
-
     private SearchView mSearchView;
 
     private View mRootsContainer;
     private DrawerLayout mDrawerLayout;
     private ActionBarDrawerToggle mDrawerToggle;
 
-    private final DisplayState mDisplayState = new DisplayState();
+    private static final String EXTRA_STATE = "state";
 
     private RootsCache mRoots;
-
-    /** Current user navigation stack; empty implies recents. */
-    private DocumentStack mStack = new DocumentStack();
-    /** Currently active search, overriding any stack. */
-    private String mCurrentSearch;
+    private State mState;
 
     @Override
     public void onCreate(Bundle icicle) {
@@ -93,59 +90,9 @@
 
         mRoots = DocumentsApplication.getRootsCache(this);
 
-        final Intent intent = getIntent();
-        final String action = intent.getAction();
-        if (Intent.ACTION_OPEN_DOCUMENT.equals(action)) {
-            mAction = ACTION_OPEN;
-        } else if (Intent.ACTION_CREATE_DOCUMENT.equals(action)) {
-            mAction = ACTION_CREATE;
-        } else if (Intent.ACTION_GET_CONTENT.equals(action)) {
-            mAction = ACTION_GET_CONTENT;
-        } else if (DocumentsContract.ACTION_MANAGE_DOCUMENTS.equals(action)) {
-            mAction = ACTION_MANAGE;
-        }
-
-        // TODO: unify action in single place
-        mDisplayState.action = mAction;
-
-        if (mAction == ACTION_OPEN || mAction == ACTION_GET_CONTENT) {
-            mDisplayState.allowMultiple = intent.getBooleanExtra(
-                    Intent.EXTRA_ALLOW_MULTIPLE, false);
-        }
-
-        if (mAction == ACTION_MANAGE) {
-            mDisplayState.acceptMimes = new String[] { "*/*" };
-            mDisplayState.allowMultiple = true;
-        } else if (intent.hasExtra(Intent.EXTRA_MIME_TYPES)) {
-            mDisplayState.acceptMimes = intent.getStringArrayExtra(Intent.EXTRA_MIME_TYPES);
-        } else {
-            mDisplayState.acceptMimes = new String[] { intent.getType() };
-        }
-
-        mDisplayState.localOnly = intent.getBooleanExtra(Intent.EXTRA_LOCAL_ONLY, false);
-
         setResult(Activity.RESULT_CANCELED);
         setContentView(R.layout.activity);
 
-        if (mAction == ACTION_CREATE) {
-            final String mimeType = getIntent().getType();
-            final String title = getIntent().getStringExtra(Intent.EXTRA_TITLE);
-            SaveFragment.show(getFragmentManager(), mimeType, title);
-        }
-
-        if (mAction == ACTION_GET_CONTENT) {
-            final Intent moreApps = new Intent(getIntent());
-            moreApps.setComponent(null);
-            moreApps.setPackage(null);
-            RootsFragment.show(getFragmentManager(), moreApps);
-        } else if (mAction == ACTION_OPEN || mAction == ACTION_CREATE) {
-            RootsFragment.show(getFragmentManager(), null);
-        }
-
-        if (mAction == ACTION_MANAGE) {
-            mDisplayState.sortOrder = SORT_ORDER_LAST_MODIFIED;
-        }
-
         mRootsContainer = findViewById(R.id.container_roots);
 
         mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
@@ -156,11 +103,73 @@
         mDrawerLayout.setDrawerListener(mDrawerListener);
         mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
 
-        if (mAction == ACTION_MANAGE) {
-            mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
+        if (icicle != null) {
+            mState = icicle.getParcelable(EXTRA_STATE);
+        } else {
+            buildDefaultState();
+        }
 
+        if (mState.action == ACTION_MANAGE) {
+            mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
+        }
+
+        if (mState.action == ACTION_CREATE) {
+            final String mimeType = getIntent().getType();
+            final String title = getIntent().getStringExtra(Intent.EXTRA_TITLE);
+            SaveFragment.show(getFragmentManager(), mimeType, title);
+        }
+
+        if (mState.action == ACTION_GET_CONTENT) {
+            final Intent moreApps = new Intent(getIntent());
+            moreApps.setComponent(null);
+            moreApps.setPackage(null);
+            RootsFragment.show(getFragmentManager(), moreApps);
+        } else if (mState.action == ACTION_OPEN || mState.action == ACTION_CREATE) {
+            RootsFragment.show(getFragmentManager(), null);
+        }
+
+        onCurrentDirectoryChanged();
+    }
+
+    private void buildDefaultState() {
+        mState = new State();
+
+        final Intent intent = getIntent();
+        final String action = intent.getAction();
+        if (Intent.ACTION_OPEN_DOCUMENT.equals(action)) {
+            mState.action = ACTION_OPEN;
+        } else if (Intent.ACTION_CREATE_DOCUMENT.equals(action)) {
+            mState.action = ACTION_CREATE;
+        } else if (Intent.ACTION_GET_CONTENT.equals(action)) {
+            mState.action = ACTION_GET_CONTENT;
+        } else if (DocumentsContract.ACTION_MANAGE_DOCUMENTS.equals(action)) {
+            mState.action = ACTION_MANAGE;
+        }
+
+        if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
+            mState.allowMultiple = intent.getBooleanExtra(
+                    Intent.EXTRA_ALLOW_MULTIPLE, false);
+        }
+
+        if (mState.action == ACTION_MANAGE) {
+            mState.acceptMimes = new String[] { "*/*" };
+            mState.allowMultiple = true;
+        } else if (intent.hasExtra(Intent.EXTRA_MIME_TYPES)) {
+            mState.acceptMimes = intent.getStringArrayExtra(Intent.EXTRA_MIME_TYPES);
+        } else {
+            mState.acceptMimes = new String[] { intent.getType() };
+        }
+
+        mState.localOnly = intent.getBooleanExtra(Intent.EXTRA_LOCAL_ONLY, false);
+        mState.showAdvanced = SettingsActivity.getDisplayAdvancedDevices(this);
+
+        if (mState.action == ACTION_MANAGE) {
+            mState.sortOrder = SORT_ORDER_LAST_MODIFIED;
+        }
+
+        if (mState.action == ACTION_MANAGE) {
             final Uri rootUri = intent.getData();
-            final DocumentRoot root = mRoots.findRoot(rootUri);
+            final RootInfo root = mRoots.findRoot(rootUri);
             if (root != null) {
                 onRootPicked(root, true);
             } else {
@@ -169,8 +178,6 @@
             }
 
         } else {
-            mDrawerLayout.openDrawer(mRootsContainer);
-
             // Restore last stack for calling package
             // TODO: move into async loader
             final String packageName = getCallingPackage();
@@ -178,17 +185,17 @@
                     .query(RecentsProvider.buildResume(packageName), null, null, null, null);
             try {
                 if (cursor.moveToFirst()) {
-                    final String raw = cursor.getString(
+                    final byte[] rawStack = cursor.getBlob(
                             cursor.getColumnIndex(RecentsProvider.COL_PATH));
-                    mStack = DocumentStack.deserialize(getContentResolver(), raw);
+                    DurableUtils.readFromArray(rawStack, mState.stack);
                 }
-            } catch (FileNotFoundException e) {
+            } catch (IOException e) {
                 Log.w(TAG, "Failed to resume", e);
             } finally {
                 cursor.close();
             }
 
-            onCurrentDirectoryChanged();
+            mDrawerLayout.openDrawer(mRootsContainer);
         }
     }
 
@@ -196,10 +203,10 @@
     public void onStart() {
         super.onStart();
 
-        if (mAction == ACTION_MANAGE) {
-            mDisplayState.showSize = true;
+        if (mState.action == ACTION_MANAGE) {
+            mState.showSize = true;
         } else {
-            mDisplayState.showSize = SettingsActivity.getDisplayFileSize(this);
+            mState.showSize = SettingsActivity.getDisplayFileSize(this);
         }
     }
 
@@ -242,9 +249,9 @@
             actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
             actionBar.setIcon(new ColorDrawable());
 
-            if (mAction == ACTION_OPEN || mAction == ACTION_GET_CONTENT) {
+            if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
                 actionBar.setTitle(R.string.title_open);
-            } else if (mAction == ACTION_CREATE) {
+            } else if (mState.action == ACTION_CREATE) {
                 actionBar.setTitle(R.string.title_save);
             }
 
@@ -252,7 +259,7 @@
             mDrawerToggle.setDrawerIndicatorEnabled(true);
 
         } else {
-            final DocumentRoot root = getCurrentRoot();
+            final RootInfo root = getCurrentRoot();
             actionBar.setIcon(root != null ? root.loadIcon(this) : null);
 
             if (mRoots.isRecentsRoot(root)) {
@@ -262,13 +269,13 @@
                 actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
                 actionBar.setTitle(null);
                 actionBar.setListNavigationCallbacks(mSortAdapter, mSortListener);
-                actionBar.setSelectedNavigationItem(mDisplayState.sortOrder);
+                actionBar.setSelectedNavigationItem(mState.sortOrder);
             }
 
-            if (mStack.size() > 1) {
+            if (mState.stack.size() > 1) {
                 actionBar.setDisplayHomeAsUpEnabled(true);
                 mDrawerToggle.setDrawerIndicatorEnabled(false);
-            } else if (mAction == ACTION_MANAGE) {
+            } else if (mState.action == ACTION_MANAGE) {
                 actionBar.setDisplayHomeAsUpEnabled(false);
                 mDrawerToggle.setDrawerIndicatorEnabled(false);
             } else {
@@ -288,7 +295,7 @@
         mSearchView.setOnQueryTextListener(new OnQueryTextListener() {
             @Override
             public boolean onQueryTextSubmit(String query) {
-                mCurrentSearch = query;
+                mState.currentSearch = query;
                 onCurrentDirectoryChanged();
                 mSearchView.setIconified(true);
                 return true;
@@ -303,7 +310,7 @@
         mSearchView.setOnCloseListener(new OnCloseListener() {
             @Override
             public boolean onClose() {
-                mCurrentSearch = null;
+                mState.currentSearch = null;
                 onCurrentDirectoryChanged();
                 return false;
             }
@@ -317,7 +324,7 @@
         super.onPrepareOptionsMenu(menu);
 
         final FragmentManager fm = getFragmentManager();
-        final Document cwd = getCurrentDirectory();
+        final DocumentInfo cwd = getCurrentDirectory();
 
         final MenuItem createDir = menu.findItem(R.id.menu_create_dir);
         final MenuItem search = menu.findItem(R.id.menu_search);
@@ -325,11 +332,11 @@
         final MenuItem list = menu.findItem(R.id.menu_list);
         final MenuItem settings = menu.findItem(R.id.menu_settings);
 
-        grid.setVisible(mDisplayState.mode != MODE_GRID);
-        list.setVisible(mDisplayState.mode != MODE_LIST);
+        grid.setVisible(mState.mode != MODE_GRID);
+        list.setVisible(mState.mode != MODE_LIST);
 
         final boolean searchVisible;
-        if (mAction == ACTION_CREATE) {
+        if (mState.action == ACTION_CREATE) {
             createDir.setVisible(cwd != null && cwd.isCreateSupported());
             searchVisible = false;
 
@@ -348,7 +355,7 @@
         // TODO: close any search in-progress when hiding
         search.setVisible(searchVisible);
 
-        settings.setVisible(mAction != ACTION_MANAGE);
+        settings.setVisible(mState.action != ACTION_MANAGE);
 
         return true;
     }
@@ -370,13 +377,13 @@
             return false;
         } else if (id == R.id.menu_grid) {
             // TODO: persist explicit user mode for cwd
-            mDisplayState.mode = MODE_GRID;
+            mState.mode = MODE_GRID;
             updateDisplayState();
             invalidateOptionsMenu();
             return true;
         } else if (id == R.id.menu_list) {
             // TODO: persist explicit user mode for cwd
-            mDisplayState.mode = MODE_LIST;
+            mState.mode = MODE_LIST;
             updateDisplayState();
             invalidateOptionsMenu();
             return true;
@@ -390,9 +397,9 @@
 
     @Override
     public void onBackPressed() {
-        final int size = mStack.size();
+        final int size = mState.stack.size();
         if (size > 1) {
-            mStack.pop();
+            mState.stack.pop();
             onCurrentDirectoryChanged();
         } else if (size == 1 && !mDrawerLayout.isDrawerOpen(mRootsContainer)) {
             // TODO: open root drawer once we can capture back key
@@ -402,11 +409,23 @@
         }
     }
 
+    @Override
+    protected void onSaveInstanceState(Bundle state) {
+        super.onSaveInstanceState(state);
+        state.putParcelable(EXTRA_STATE, mState);
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Bundle state) {
+        super.onRestoreInstanceState(state);
+        updateActionBar();
+    }
+
     // TODO: support additional sort orders
     private BaseAdapter mSortAdapter = new BaseAdapter() {
         @Override
         public int getCount() {
-            return mDisplayState.showSize ? 3 : 2;
+            return mState.showSize ? 3 : 2;
         }
 
         @Override
@@ -438,8 +457,8 @@
             final TextView title = (TextView) convertView.findViewById(android.R.id.title);
             final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
 
-            if (mStack.size() > 0) {
-                title.setText(mStack.getTitle(mRoots));
+            if (mState.stack.size() > 0) {
+                title.setText(mState.stack.getTitle(mRoots));
             } else {
                 // No directory means recents
                 title.setText(R.string.root_recent);
@@ -467,43 +486,43 @@
     private OnNavigationListener mSortListener = new OnNavigationListener() {
         @Override
         public boolean onNavigationItemSelected(int itemPosition, long itemId) {
-            mDisplayState.sortOrder = itemPosition;
+            mState.sortOrder = itemPosition;
             updateDisplayState();
             return true;
         }
     };
 
-    public DocumentRoot getCurrentRoot() {
-        if (mStack.size() > 0) {
-            return mStack.getRoot(mRoots);
+    public RootInfo getCurrentRoot() {
+        if (mState.stack.size() > 0) {
+            return mState.stack.getRoot(mRoots);
         } else {
             return mRoots.getRecentsRoot();
         }
     }
 
-    public Document getCurrentDirectory() {
-        return mStack.peek();
+    public DocumentInfo getCurrentDirectory() {
+        return mState.stack.peek();
     }
 
-    public DisplayState getDisplayState() {
-        return mDisplayState;
+    public State getDisplayState() {
+        return mState;
     }
 
     private void onCurrentDirectoryChanged() {
         final FragmentManager fm = getFragmentManager();
-        final Document cwd = getCurrentDirectory();
+        final DocumentInfo cwd = getCurrentDirectory();
 
         if (cwd == null) {
             // No directory means recents
-            if (mAction == ACTION_CREATE) {
+            if (mState.action == ACTION_CREATE) {
                 RecentsCreateFragment.show(fm);
             } else {
                 DirectoryFragment.showRecentsOpen(fm);
             }
         } else {
-            if (mCurrentSearch != null) {
+            if (mState.currentSearch != null) {
                 // Ongoing search
-                DirectoryFragment.showSearch(fm, cwd.uri, mCurrentSearch);
+                DirectoryFragment.showSearch(fm, cwd.uri, mState.currentSearch);
             } else {
                 // Normal boring directory
                 DirectoryFragment.showNormal(fm, cwd.uri);
@@ -511,7 +530,7 @@
         }
 
         // Forget any replacement target
-        if (mAction == ACTION_CREATE) {
+        if (mState.action == ACTION_CREATE) {
             final SaveFragment save = SaveFragment.get(fm);
             if (save != null) {
                 save.setReplaceTarget(null);
@@ -529,18 +548,18 @@
     }
 
     public void onStackPicked(DocumentStack stack) {
-        mStack = stack;
+        mState.stack = stack;
         onCurrentDirectoryChanged();
     }
 
-    public void onRootPicked(DocumentRoot root, boolean closeDrawer) {
+    public void onRootPicked(RootInfo root, boolean closeDrawer) {
         // Clear entire backstack and start in new root
-        mStack.clear();
+        mState.stack.clear();
 
         if (!mRoots.isRecentsRoot(root)) {
             try {
-                final Uri uri = DocumentsContract.buildDocumentUri(root.authority, root.docId);
-                onDocumentPicked(Document.fromUri(getContentResolver(), uri));
+                final Uri uri = DocumentsContract.buildDocumentUri(root.authority, root.documentId);
+                onDocumentPicked(DocumentInfo.fromUri(getContentResolver(), uri));
             } catch (FileNotFoundException e) {
             }
         } else {
@@ -561,24 +580,24 @@
         finish();
     }
 
-    public void onDocumentPicked(Document doc) {
+    public void onDocumentPicked(DocumentInfo doc) {
         final FragmentManager fm = getFragmentManager();
         if (doc.isDirectory()) {
             // TODO: query display mode user preference for this dir
             if (doc.isGridPreferred()) {
-                mDisplayState.mode = MODE_GRID;
+                mState.mode = MODE_GRID;
             } else {
-                mDisplayState.mode = MODE_LIST;
+                mState.mode = MODE_LIST;
             }
-            mStack.push(doc);
+            mState.stack.push(doc);
             onCurrentDirectoryChanged();
-        } else if (mAction == ACTION_OPEN || mAction == ACTION_GET_CONTENT) {
+        } else if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
             // Explicit file picked, return
             onFinished(doc.uri);
-        } else if (mAction == ACTION_CREATE) {
+        } else if (mState.action == ACTION_CREATE) {
             // Replace selected file
             SaveFragment.get(fm).setReplaceTarget(doc);
-        } else if (mAction == ACTION_MANAGE) {
+        } else if (mState.action == ACTION_MANAGE) {
             // Open the document
             final Intent intent = new Intent(Intent.ACTION_VIEW);
             intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
@@ -591,8 +610,8 @@
         }
     }
 
-    public void onDocumentsPicked(List<Document> docs) {
-        if (mAction == ACTION_OPEN || mAction == ACTION_GET_CONTENT) {
+    public void onDocumentsPicked(List<DocumentInfo> docs) {
+        if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
             final int size = docs.size();
             final Uri[] uris = new Uri[size];
             for (int i = 0; i < size; i++) {
@@ -602,21 +621,19 @@
         }
     }
 
-    public void onSaveRequested(Document replaceTarget) {
+    public void onSaveRequested(DocumentInfo replaceTarget) {
         onFinished(replaceTarget.uri);
     }
 
     public void onSaveRequested(String mimeType, String displayName) {
-        final Document cwd = getCurrentDirectory();
+        final DocumentInfo cwd = getCurrentDirectory();
         final String authority = cwd.uri.getAuthority();
 
         final ContentProviderClient client = getContentResolver()
                 .acquireUnstableContentProviderClient(authority);
         try {
-            final String docId = DocumentsContract.createDocument(client,
-                    DocumentsContract.getDocId(cwd.uri), mimeType, displayName);
-
-            final Uri childUri = DocumentsContract.buildDocumentUri(authority, docId);
+            final Uri childUri = DocumentsContract.createDocument(
+                    getContentResolver(), cwd.uri, mimeType, displayName);
             onFinished(childUri);
         } catch (Exception e) {
             Toast.makeText(this, R.string.save_error, Toast.LENGTH_SHORT).show();
@@ -631,14 +648,14 @@
         final ContentResolver resolver = getContentResolver();
         final ContentValues values = new ContentValues();
 
-        final String rawStack = DocumentStack.serialize(mStack);
-        if (mAction == ACTION_CREATE) {
+        final byte[] rawStack = DurableUtils.writeToArrayOrNull(mState.stack);
+        if (mState.action == ACTION_CREATE) {
             // Remember stack for last create
             values.clear();
             values.put(RecentsProvider.COL_PATH, rawStack);
             resolver.insert(RecentsProvider.buildRecentCreate(), values);
 
-        } else if (mAction == ACTION_OPEN || mAction == ACTION_GET_CONTENT) {
+        } else if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
             // Remember opened items
             for (Uri uri : uris) {
                 values.clear();
@@ -658,14 +675,14 @@
             intent.setData(uris[0]);
         } else if (uris.length > 1) {
             final ClipData clipData = new ClipData(
-                    null, mDisplayState.acceptMimes, new ClipData.Item(uris[0]));
+                    null, mState.acceptMimes, new ClipData.Item(uris[0]));
             for (int i = 1; i < uris.length; i++) {
                 clipData.addItem(new ClipData.Item(uris[i]));
             }
             intent.setClipData(clipData);
         }
 
-        if (mAction == ACTION_GET_CONTENT) {
+        if (mState.action == ACTION_GET_CONTENT) {
             intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
         } else {
             intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
@@ -677,7 +694,7 @@
         finish();
     }
 
-    public static class DisplayState {
+    public static class State implements android.os.Parcelable {
         public int action;
         public int mode = MODE_LIST;
         public String[] acceptMimes;
@@ -685,6 +702,12 @@
         public boolean allowMultiple = false;
         public boolean showSize = false;
         public boolean localOnly = false;
+        public boolean showAdvanced = false;
+
+        /** Current user navigation stack; empty implies recents. */
+        public DocumentStack stack = new DocumentStack();
+        /** Currently active search, overriding any stack. */
+        public String currentSearch;
 
         public static final int ACTION_OPEN = 1;
         public static final int ACTION_CREATE = 2;
@@ -697,11 +720,53 @@
         public static final int SORT_ORDER_DISPLAY_NAME = 0;
         public static final int SORT_ORDER_LAST_MODIFIED = 1;
         public static final int SORT_ORDER_SIZE = 2;
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeInt(action);
+            out.writeInt(mode);
+            out.writeStringArray(acceptMimes);
+            out.writeInt(sortOrder);
+            out.writeInt(allowMultiple ? 1 : 0);
+            out.writeInt(showSize ? 1 : 0);
+            out.writeInt(localOnly ? 1 : 0);
+            out.writeInt(showAdvanced ? 1 : 0);
+            DurableUtils.writeToParcel(out, stack);
+            out.writeString(currentSearch);
+        }
+
+        public static final Creator<State> CREATOR = new Creator<State>() {
+            @Override
+            public State createFromParcel(Parcel in) {
+                final State state = new State();
+                state.action = in.readInt();
+                state.mode = in.readInt();
+                state.acceptMimes = in.readStringArray();
+                state.sortOrder = in.readInt();
+                state.allowMultiple = in.readInt() != 0;
+                state.showSize = in.readInt() != 0;
+                state.localOnly = in.readInt() != 0;
+                state.showAdvanced = in.readInt() != 0;
+                DurableUtils.readFromParcel(in, state.stack);
+                state.currentSearch = in.readString();
+                return state;
+            }
+
+            @Override
+            public State[] newArray(int size) {
+                return new State[size];
+            }
+        };
     }
 
     private void dumpStack() {
         Log.d(TAG, "Current stack:");
-        for (Document doc : mStack) {
+        for (DocumentInfo doc : mState.stack) {
             Log.d(TAG, "--> " + doc);
         }
     }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java b/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java
index a9929de..15ad061 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java
@@ -16,10 +16,10 @@
 
 package com.android.documentsui;
 
-import com.android.documentsui.model.Document;
+import com.android.documentsui.model.DocumentInfo;
 import com.android.internal.util.Predicate;
 
-public class MimePredicate implements Predicate<Document> {
+public class MimePredicate implements Predicate<DocumentInfo> {
     private final String[] mFilters;
 
     public MimePredicate(String[] filters) {
@@ -27,7 +27,7 @@
     }
 
     @Override
-    public boolean apply(Document doc) {
+    public boolean apply(DocumentInfo doc) {
         if (doc.isDirectory()) {
             return true;
         }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java
new file mode 100644
index 0000000..756a297
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java
@@ -0,0 +1,255 @@
+/*
+ * 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.DocumentsActivity.TAG;
+
+import android.content.AsyncTaskLoader;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.MergeCursor;
+import android.net.Uri;
+import android.provider.DocumentsContract;
+import android.provider.DocumentsContract.Root;
+import android.util.Log;
+
+import com.android.documentsui.DocumentsActivity.State;
+import com.android.documentsui.model.RootInfo;
+import com.google.android.collect.Maps;
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.AbstractFuture;
+
+import libcore.io.IoUtils;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+public class RecentLoader extends AsyncTaskLoader<DirectoryResult> {
+
+    public static final int MAX_OUTSTANDING_RECENTS = 2;
+
+    /**
+     * Time to wait for first pass to complete before returning partial results.
+     */
+    public static final int MAX_FIRST_PASS_WAIT_MILLIS = 500;
+
+    /**
+     * Maximum documents from a single root.
+     */
+    public static final int MAX_DOCS_FROM_ROOT = 24;
+
+    private static final ExecutorService sExecutor = buildExecutor();
+
+    /**
+     * Create a bounded thread pool for fetching recents; it creates threads as
+     * needed (up to maximum) and reclaims them when finished.
+     */
+    private static ExecutorService buildExecutor() {
+        // Create a bounded thread pool for fetching recents; it creates
+        // threads as needed (up to maximum) and reclaims them when finished.
+        final ThreadPoolExecutor executor = new ThreadPoolExecutor(
+                MAX_OUTSTANDING_RECENTS, MAX_OUTSTANDING_RECENTS, 10, TimeUnit.SECONDS,
+                new LinkedBlockingQueue<Runnable>());
+        executor.allowCoreThreadTimeOut(true);
+        return executor;
+    }
+
+    private final List<RootInfo> mRoots;
+
+    private final HashMap<RootInfo, RecentTask> mTasks = Maps.newHashMap();
+
+    private final int mSortOrder = State.SORT_ORDER_LAST_MODIFIED;
+
+    private CountDownLatch mFirstPassLatch;
+    private volatile boolean mFirstPassDone;
+
+    private DirectoryResult mResult;
+
+    // TODO: create better transfer of ownership around cursor to ensure its
+    // closed in all edge cases.
+
+    public class RecentTask extends AbstractFuture<Cursor> implements Runnable, Closeable {
+        public final String authority;
+        public final String rootId;
+
+        private Cursor mWithRoot;
+
+        public RecentTask(String authority, String rootId) {
+            this.authority = authority;
+            this.rootId = rootId;
+        }
+
+        @Override
+        public void run() {
+            if (isCancelled()) return;
+
+            final ContentResolver resolver = getContext().getContentResolver();
+            final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
+                    authority);
+            try {
+                final Uri uri = DocumentsContract.buildRecentDocumentsUri(authority, rootId);
+                final Cursor cursor = client.query(
+                        uri, null, null, null, DirectoryLoader.getQuerySortOrder(mSortOrder));
+                mWithRoot = new RootCursorWrapper(authority, rootId, cursor, MAX_DOCS_FROM_ROOT);
+                set(mWithRoot);
+
+                mFirstPassLatch.countDown();
+                if (mFirstPassDone) {
+                    onContentChanged();
+                }
+
+            } catch (Exception e) {
+                setException(e);
+            } finally {
+                ContentProviderClient.closeQuietly(client);
+            }
+        }
+
+        @Override
+        public void close() throws IOException {
+            IoUtils.closeQuietly(mWithRoot);
+        }
+    }
+
+    public RecentLoader(Context context, List<RootInfo> roots) {
+        super(context);
+        mRoots = roots;
+    }
+
+    @Override
+    public DirectoryResult loadInBackground() {
+        if (mFirstPassLatch == null) {
+            // First time through we kick off all the recent tasks, and wait
+            // around to see if everyone finishes quickly.
+
+            for (RootInfo root : mRoots) {
+                if ((root.flags & Root.FLAG_SUPPORTS_RECENTS) != 0) {
+                    final RecentTask task = new RecentTask(root.authority, root.rootId);
+                    mTasks.put(root, task);
+                }
+            }
+
+            mFirstPassLatch = new CountDownLatch(mTasks.size());
+            for (RecentTask task : mTasks.values()) {
+                sExecutor.execute(task);
+            }
+
+            try {
+                mFirstPassLatch.await(MAX_FIRST_PASS_WAIT_MILLIS, TimeUnit.MILLISECONDS);
+                mFirstPassDone = true;
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        // Collect all finished tasks
+        List<Cursor> cursors = Lists.newArrayList();
+        for (RecentTask task : mTasks.values()) {
+            if (task.isDone()) {
+                try {
+                    cursors.add(task.get());
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                } catch (ExecutionException e) {
+                    Log.w(TAG, "Failed to load " + task.authority + ", " + task.rootId, e);
+                }
+            }
+        }
+
+        final DirectoryResult result = new DirectoryResult();
+        if (cursors.size() > 0) {
+            final MergeCursor merged = new MergeCursor(cursors.toArray(new Cursor[cursors.size()]));
+            final SortingCursorWrapper sorted = new SortingCursorWrapper(
+                    merged, State.SORT_ORDER_LAST_MODIFIED) {
+                @Override
+                public void close() {
+                    // Ignored, since we manage cursor lifecycle internally
+                }
+            };
+            result.cursor = sorted;
+        }
+        return result;
+    }
+
+    @Override
+    public void cancelLoadInBackground() {
+        super.cancelLoadInBackground();
+    }
+
+    @Override
+    public void deliverResult(DirectoryResult result) {
+        if (isReset()) {
+            IoUtils.closeQuietly(result);
+            return;
+        }
+        DirectoryResult oldResult = mResult;
+        mResult = result;
+
+        if (isStarted()) {
+            super.deliverResult(result);
+        }
+
+        if (oldResult != null && oldResult != result) {
+            IoUtils.closeQuietly(oldResult);
+        }
+    }
+
+    @Override
+    protected void onStartLoading() {
+        if (mResult != null) {
+            deliverResult(mResult);
+        }
+        if (takeContentChanged() || mResult == null) {
+            forceLoad();
+        }
+    }
+
+    @Override
+    protected void onStopLoading() {
+        cancelLoad();
+    }
+
+    @Override
+    public void onCanceled(DirectoryResult result) {
+        IoUtils.closeQuietly(result);
+    }
+
+    @Override
+    protected void onReset() {
+        super.onReset();
+
+        // Ensure the loader is stopped
+        onStopLoading();
+
+        for (RecentTask task : mTasks.values()) {
+            IoUtils.closeQuietly(task);
+        }
+
+        IoUtils.closeQuietly(mResult);
+        mResult = null;
+    }
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java
index 3447a51..fd7293d 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java
@@ -29,7 +29,6 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.CancellationSignal;
-import android.provider.DocumentsContract.DocumentRoot;
 import android.text.TextUtils.TruncateAt;
 import android.util.Log;
 import android.view.LayoutInflater;
@@ -43,11 +42,14 @@
 import android.widget.TextView;
 
 import com.android.documentsui.model.DocumentStack;
+import com.android.documentsui.model.RootInfo;
 import com.google.android.collect.Lists;
 
 import libcore.io.IoUtils;
 
-import java.io.FileNotFoundException;
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -138,12 +140,13 @@
                     uri, null, null, null, RecentsProvider.COL_TIMESTAMP + " DESC", signal);
             try {
                 while (cursor != null && cursor.moveToNext()) {
-                    final String rawStack = cursor.getString(
+                    final byte[] raw = cursor.getBlob(
                             cursor.getColumnIndex(RecentsProvider.COL_PATH));
                     try {
-                        final DocumentStack stack = DocumentStack.deserialize(resolver, rawStack);
+                        final DocumentStack stack = new DocumentStack();
+                        stack.read(new DataInputStream(new ByteArrayInputStream(raw)));
                         result.add(stack);
-                    } catch (FileNotFoundException e) {
+                    } catch (IOException e) {
                         Log.w(TAG, "Failed to resolve stack: " + e);
                     }
                 }
@@ -181,7 +184,7 @@
             final View summaryList = convertView.findViewById(R.id.summary_list);
 
             final DocumentStack stack = getItem(position);
-            final DocumentRoot root = stack.getRoot(roots);
+            final RootInfo root = stack.getRoot(roots);
             icon.setImageDrawable(root.loadIcon(context));
 
             final StringBuilder builder = new StringBuilder();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java
index dbcb039..0c87783 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java
@@ -61,6 +61,7 @@
     public static final String COL_PACKAGE_NAME = "package_name";
     public static final String COL_TIMESTAMP = "timestamp";
 
+    @Deprecated
     public static Uri buildRecentOpen() {
         return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
                 .authority(AUTHORITY).appendPath("recent_open").build();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootCursorWrapper.java b/packages/DocumentsUI/src/com/android/documentsui/RootCursorWrapper.java
new file mode 100644
index 0000000..d0e5ff6
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootCursorWrapper.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui;
+
+import android.database.AbstractCursor;
+import android.database.Cursor;
+
+/**
+ * Cursor wrapper that adds columns to identify which root a document came from.
+ */
+public class RootCursorWrapper extends AbstractCursor {
+    private final String mAuthority;
+    private final String mRootId;
+
+    private final Cursor mCursor;
+    private final int mCount;
+
+    private final String[] mColumnNames;
+
+    private final int mAuthorityIndex;
+    private final int mRootIdIndex;
+
+    public static final String COLUMN_AUTHORITY = "android:authority";
+    public static final String COLUMN_ROOT_ID = "android:rootId";
+
+    public RootCursorWrapper(String authority, String rootId, Cursor cursor, int maxCount) {
+        mAuthority = authority;
+        mRootId = rootId;
+        mCursor = cursor;
+
+        final int count = cursor.getCount();
+        if (maxCount > 0 && count > maxCount) {
+            mCount = maxCount;
+        } else {
+            mCount = count;
+        }
+
+        if (cursor.getColumnIndex(COLUMN_AUTHORITY) != -1
+                || cursor.getColumnIndex(COLUMN_ROOT_ID) != -1) {
+            throw new IllegalArgumentException("Cursor contains internal columns!");
+        }
+        final String[] before = cursor.getColumnNames();
+        mColumnNames = new String[before.length + 2];
+        System.arraycopy(before, 0, mColumnNames, 0, before.length);
+        mAuthorityIndex = before.length;
+        mRootIdIndex = before.length + 1;
+        mColumnNames[mAuthorityIndex] = COLUMN_AUTHORITY;
+        mColumnNames[mRootIdIndex] = COLUMN_ROOT_ID;
+    }
+
+    @Override
+    public void close() {
+        super.close();
+        mCursor.close();
+    }
+
+    @Override
+    public boolean onMove(int oldPosition, int newPosition) {
+        return mCursor.moveToPosition(newPosition);
+    }
+
+    @Override
+    public String[] getColumnNames() {
+        return mColumnNames;
+    }
+
+    @Override
+    public int getCount() {
+        return mCount;
+    }
+
+    @Override
+    public double getDouble(int column) {
+        return mCursor.getDouble(column);
+    }
+
+    @Override
+    public float getFloat(int column) {
+        return mCursor.getFloat(column);
+    }
+
+    @Override
+    public int getInt(int column) {
+        return mCursor.getInt(column);
+    }
+
+    @Override
+    public long getLong(int column) {
+        return mCursor.getLong(column);
+    }
+
+    @Override
+    public short getShort(int column) {
+        return mCursor.getShort(column);
+    }
+
+    @Override
+    public String getString(int column) {
+        if (column == mAuthorityIndex) {
+            return mAuthority;
+        } else if (column == mRootIdIndex) {
+            return mRootId;
+        } else {
+            return mCursor.getString(column);
+        }
+    }
+
+    @Override
+    public int getType(int column) {
+        return mCursor.getType(column);
+    }
+
+    @Override
+    public boolean isNull(int column) {
+        return mCursor.isNull(column);
+    }
+
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
index aa21457..0b10f19 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
@@ -25,17 +25,23 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
+import android.database.Cursor;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.provider.DocumentsContract;
-import android.provider.DocumentsContract.DocumentRoot;
-import android.provider.DocumentsContract.Documents;
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract.Root;
 import android.util.Log;
 
+import com.android.documentsui.DocumentsActivity.State;
+import com.android.documentsui.model.RootInfo;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Objects;
 import com.google.android.collect.Lists;
 
+import libcore.io.IoUtils;
+
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -46,11 +52,13 @@
     // TODO: cache roots in local provider to avoid spinning up backends
     // TODO: root updates should trigger UI refresh
 
+    private static final boolean RECENTS_ENABLED = true;
+
     private final Context mContext;
 
-    public List<DocumentRoot> mRoots = Lists.newArrayList();
+    public List<RootInfo> mRoots = Lists.newArrayList();
 
-    private DocumentRoot mRecentsRoot;
+    private RootInfo mRecentsRoot;
 
     public RootsCache(Context context) {
         mContext = context;
@@ -64,14 +72,13 @@
     public void update() {
         mRoots.clear();
 
-        {
+        if (RECENTS_ENABLED) {
             // Create special root for recents
-            final DocumentRoot root = new DocumentRoot();
-            root.rootType = DocumentRoot.ROOT_TYPE_SHORTCUT;
-            root.docId = null;
+            final RootInfo root = new RootInfo();
+            root.rootType = Root.ROOT_TYPE_SHORTCUT;
             root.icon = R.drawable.ic_dir;
+            root.flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_CREATE;
             root.title = mContext.getString(R.string.root_recent);
-            root.summary = null;
             root.availableBytes = -1;
 
             mRoots.add(root);
@@ -89,28 +96,32 @@
 
                 // TODO: remove deprecated customRoots flag
                 // TODO: populate roots on background thread, and cache results
+                final Uri rootsUri = DocumentsContract.buildRootsUri(info.authority);
                 final ContentProviderClient client = resolver
                         .acquireUnstableContentProviderClient(info.authority);
+                Cursor cursor = null;
                 try {
-                    final List<DocumentRoot> roots = DocumentsContract.getDocumentRoots(client);
-                    for (DocumentRoot root : roots) {
-                        root.authority = info.authority;
+                    cursor = client.query(rootsUri, null, null, null, null);
+                    while (cursor.moveToNext()) {
+                        final RootInfo root = RootInfo.fromRootsCursor(info.authority, cursor);
+                        mRoots.add(root);
                     }
-                    mRoots.addAll(roots);
                 } catch (Exception e) {
                     Log.w(TAG, "Failed to load some roots from " + info.authority + ": " + e);
                 } finally {
+                    IoUtils.closeQuietly(cursor);
                     ContentProviderClient.closeQuietly(client);
                 }
             }
         }
     }
 
-    public DocumentRoot findRoot(Uri uri) {
+    @Deprecated
+    public RootInfo findRoot(Uri uri) {
         final String authority = uri.getAuthority();
-        final String docId = DocumentsContract.getDocId(uri);
-        for (DocumentRoot root : mRoots) {
-            if (Objects.equal(root.authority, authority) && Objects.equal(root.docId, docId)) {
+        final String docId = DocumentsContract.getDocumentId(uri);
+        for (RootInfo root : mRoots) {
+            if (Objects.equal(root.authority, authority) && Objects.equal(root.documentId, docId)) {
                 return root;
             }
         }
@@ -118,23 +129,87 @@
     }
 
     @GuardedBy("ActivityThread")
-    public DocumentRoot getRecentsRoot() {
+    public RootInfo getRoot(String authority, String rootId) {
+        for (RootInfo root : mRoots) {
+            if (Objects.equal(root.authority, authority) && Objects.equal(root.rootId, rootId)) {
+                return root;
+            }
+        }
+        return null;
+    }
+
+    @GuardedBy("ActivityThread")
+    public RootInfo getRecentsRoot() {
         return mRecentsRoot;
     }
 
     @GuardedBy("ActivityThread")
-    public boolean isRecentsRoot(DocumentRoot root) {
+    public boolean isRecentsRoot(RootInfo root) {
         return mRecentsRoot == root;
     }
 
     @GuardedBy("ActivityThread")
-    public List<DocumentRoot> getRoots() {
+    public List<RootInfo> getRoots() {
         return mRoots;
     }
 
+    /**
+     * Flags that declare explicit content types.
+     */
+    private static final int FLAGS_CONTENT_MASK = Root.FLAG_PROVIDES_IMAGES
+            | Root.FLAG_PROVIDES_AUDIO | Root.FLAG_PROVIDES_VIDEO;
+
+    @GuardedBy("ActivityThread")
+    public List<RootInfo> getMatchingRoots(State state) {
+
+        // Determine acceptable content flags
+        int includeFlags = 0;
+        for (String acceptMime : state.acceptMimes) {
+            final String[] type = acceptMime.split("/");
+            if (type.length != 2) continue;
+
+            if ("image".equals(type[0])) {
+                includeFlags |= Root.FLAG_PROVIDES_IMAGES;
+            } else if ("audio".equals(type[0])) {
+                includeFlags |= Root.FLAG_PROVIDES_AUDIO;
+            } else if ("video".equals(type[0])) {
+                includeFlags |= Root.FLAG_PROVIDES_VIDEO;
+            } else if ("*".equals(type[0])) {
+                includeFlags |= Root.FLAG_PROVIDES_IMAGES | Root.FLAG_PROVIDES_AUDIO
+                        | Root.FLAG_PROVIDES_VIDEO;
+            }
+        }
+
+        ArrayList<RootInfo> matching = Lists.newArrayList();
+        for (RootInfo root : mRoots) {
+            final boolean supportsCreate = (root.flags & Root.FLAG_SUPPORTS_CREATE) != 0;
+            final boolean advanced = (root.flags & Root.FLAG_ADVANCED) != 0;
+            final boolean localOnly = (root.flags & Root.FLAG_LOCAL_ONLY) != 0;
+
+            // Exclude read-only devices when creating
+            if (state.action == State.ACTION_CREATE && !supportsCreate) continue;
+            // Exclude advanced devices when not requested
+            if (!state.showAdvanced && advanced) continue;
+            // Exclude non-local devices when local only
+            if (state.localOnly && !localOnly) continue;
+
+            if ((root.flags & FLAGS_CONTENT_MASK) != 0) {
+                // This root offers specific content, so only include if the
+                // caller asked for that content type.
+                if ((root.flags & includeFlags) == 0) {
+                    // Sorry, no overlap.
+                    continue;
+                }
+            }
+
+            matching.add(root);
+        }
+        return matching;
+    }
+
     @GuardedBy("ActivityThread")
     public static Drawable resolveDocumentIcon(Context context, String mimeType) {
-        if (Documents.MIME_TYPE_DIR.equals(mimeType)) {
+        if (Document.MIME_TYPE_DIR.equals(mimeType)) {
             return context.getResources().getDrawable(R.drawable.ic_dir);
         } else {
             final PackageManager pm = context.getPackageManager();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
index 2cfa841..ef3a31d 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
@@ -16,8 +16,6 @@
 
 package com.android.documentsui;
 
-import static com.android.documentsui.DocumentsActivity.TAG;
-
 import android.app.Fragment;
 import android.app.FragmentManager;
 import android.app.FragmentTransaction;
@@ -26,9 +24,8 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.os.Bundle;
-import android.provider.DocumentsContract.DocumentRoot;
+import android.provider.DocumentsContract.Root;
 import android.text.format.Formatter;
-import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -39,8 +36,10 @@
 import android.widget.ListView;
 import android.widget.TextView;
 
+import com.android.documentsui.DocumentsActivity.State;
 import com.android.documentsui.SectionedListAdapter.SectionAdapter;
-import com.android.documentsui.model.Document;
+import com.android.documentsui.model.DocumentInfo;
+import com.android.documentsui.model.RootInfo;
 
 import java.util.Comparator;
 import java.util.List;
@@ -75,24 +74,31 @@
     public View onCreateView(
             LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
         final Context context = inflater.getContext();
-        final RootsCache roots = DocumentsApplication.getRootsCache(context);
 
         final View view = inflater.inflate(R.layout.fragment_roots, container, false);
         mList = (ListView) view.findViewById(android.R.id.list);
         mList.setOnItemClickListener(mItemListener);
 
-        final Intent includeApps = getArguments().getParcelable(EXTRA_INCLUDE_APPS);
-        mAdapter = new SectionedRootsAdapter(context, roots.getRoots(), includeApps);
-
         return view;
     }
 
     @Override
     public void onStart() {
         super.onStart();
+        updateRootsAdapter();
+    }
 
+    private void updateRootsAdapter() {
         final Context context = getActivity();
-        mAdapter.updateVisible(SettingsActivity.getDisplayAdvancedDevices(context));
+
+        final State state = ((DocumentsActivity) context).getDisplayState();
+        state.showAdvanced = SettingsActivity.getDisplayAdvancedDevices(context);
+
+        final RootsCache roots = DocumentsApplication.getRootsCache(context);
+        final List<RootInfo> matchingRoots = roots.getMatchingRoots(state);
+        final Intent includeApps = getArguments().getParcelable(EXTRA_INCLUDE_APPS);
+
+        mAdapter = new SectionedRootsAdapter(context, matchingRoots, includeApps);
         mList.setAdapter(mAdapter);
     }
 
@@ -101,8 +107,8 @@
         public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
             final DocumentsActivity activity = DocumentsActivity.get(RootsFragment.this);
             final Object item = mAdapter.getItem(position);
-            if (item instanceof DocumentRoot) {
-                activity.onRootPicked((DocumentRoot) item, true);
+            if (item instanceof RootInfo) {
+                activity.onRootPicked((RootInfo) item, true);
             } else if (item instanceof ResolveInfo) {
                 activity.onAppPicked((ResolveInfo) item);
             } else {
@@ -111,7 +117,7 @@
         }
     };
 
-    private static class RootsAdapter extends ArrayAdapter<DocumentRoot> implements SectionAdapter {
+    private static class RootsAdapter extends ArrayAdapter<RootInfo> implements SectionAdapter {
         private int mHeaderId;
 
         public RootsAdapter(Context context, int headerId) {
@@ -131,15 +137,13 @@
             final TextView title = (TextView) convertView.findViewById(android.R.id.title);
             final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
 
-            final DocumentRoot root = getItem(position);
+            final RootInfo root = getItem(position);
             icon.setImageDrawable(root.loadIcon(context));
             title.setText(root.title);
 
             // Device summary is always available space
             final String summaryText;
-            if ((root.rootType == DocumentRoot.ROOT_TYPE_DEVICE
-                    || root.rootType == DocumentRoot.ROOT_TYPE_DEVICE_ADVANCED)
-                    && root.availableBytes >= 0) {
+            if (root.rootType == Root.ROOT_TYPE_DEVICE && root.availableBytes >= 0) {
                 summaryText = context.getString(R.string.root_available_bytes,
                         Formatter.formatFileSize(context, root.availableBytes));
             } else {
@@ -212,31 +216,24 @@
         private final RootsAdapter mServices;
         private final RootsAdapter mShortcuts;
         private final RootsAdapter mDevices;
-        private final RootsAdapter mDevicesAdvanced;
         private final AppsAdapter mApps;
 
-        public SectionedRootsAdapter(Context context, List<DocumentRoot> roots, Intent includeApps) {
+        public SectionedRootsAdapter(Context context, List<RootInfo> roots, Intent includeApps) {
             mServices = new RootsAdapter(context, R.string.root_type_service);
             mShortcuts = new RootsAdapter(context, R.string.root_type_shortcut);
             mDevices = new RootsAdapter(context, R.string.root_type_device);
-            mDevicesAdvanced = new RootsAdapter(context, R.string.root_type_device);
             mApps = new AppsAdapter(context);
 
-            for (DocumentRoot root : roots) {
-                Log.d(TAG, "Found rootType=" + root.rootType);
+            for (RootInfo root : roots) {
                 switch (root.rootType) {
-                    case DocumentRoot.ROOT_TYPE_SERVICE:
+                    case Root.ROOT_TYPE_SERVICE:
                         mServices.add(root);
                         break;
-                    case DocumentRoot.ROOT_TYPE_SHORTCUT:
+                    case Root.ROOT_TYPE_SHORTCUT:
                         mShortcuts.add(root);
                         break;
-                    case DocumentRoot.ROOT_TYPE_DEVICE:
+                    case Root.ROOT_TYPE_DEVICE:
                         mDevices.add(root);
-                        mDevicesAdvanced.add(root);
-                        break;
-                    case DocumentRoot.ROOT_TYPE_DEVICE_ADVANCED:
-                        mDevicesAdvanced.add(root);
                         break;
                 }
             }
@@ -258,37 +255,36 @@
             mServices.sort(comp);
             mShortcuts.sort(comp);
             mDevices.sort(comp);
-            mDevicesAdvanced.sort(comp);
-        }
 
-        public void updateVisible(boolean showAdvanced) {
-            clearSections();
             if (mServices.getCount() > 0) {
                 addSection(mServices);
             }
             if (mShortcuts.getCount() > 0) {
                 addSection(mShortcuts);
             }
-
-            final RootsAdapter devices = showAdvanced ? mDevicesAdvanced : mDevices;
-            if (devices.getCount() > 0) {
-                addSection(devices);
+            if (mDevices.getCount() > 0) {
+                addSection(mDevices);
             }
-
             if (mApps.getCount() > 0) {
                 addSection(mApps);
             }
         }
     }
 
-    public static class RootComparator implements Comparator<DocumentRoot> {
+    public static class RootComparator implements Comparator<RootInfo> {
         @Override
-        public int compare(DocumentRoot lhs, DocumentRoot rhs) {
-            final int score = Document.compareToIgnoreCaseNullable(lhs.title, rhs.title);
+        public int compare(RootInfo lhs, RootInfo rhs) {
+            if (lhs.authority == null) {
+                return -1;
+            } else if (rhs.authority == null) {
+                return 1;
+            }
+
+            final int score = DocumentInfo.compareToIgnoreCaseNullable(lhs.title, rhs.title);
             if (score != 0) {
                 return score;
             } else {
-                return Document.compareToIgnoreCaseNullable(lhs.summary, rhs.summary);
+                return DocumentInfo.compareToIgnoreCaseNullable(lhs.summary, rhs.summary);
             }
         }
     }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java b/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java
index 7e1a297..8b0a974 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java
@@ -31,7 +31,7 @@
 import android.widget.EditText;
 import android.widget.ImageView;
 
-import com.android.documentsui.model.Document;
+import com.android.documentsui.model.DocumentInfo;
 
 /**
  * Display document title editor and save button.
@@ -39,7 +39,7 @@
 public class SaveFragment extends Fragment {
     public static final String TAG = "SaveFragment";
 
-    private Document mReplaceTarget;
+    private DocumentInfo mReplaceTarget;
     private EditText mDisplayName;
     private Button mSave;
     private boolean mIgnoreNextEdit;
@@ -128,7 +128,7 @@
      * without changing the filename. Can be set to {@code null} if user
      * navigates outside the target directory.
      */
-    public void setReplaceTarget(Document replaceTarget) {
+    public void setReplaceTarget(DocumentInfo replaceTarget) {
         mReplaceTarget = replaceTarget;
 
         if (mReplaceTarget != null) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/SortingCursorWrapper.java b/packages/DocumentsUI/src/com/android/documentsui/SortingCursorWrapper.java
new file mode 100644
index 0000000..b434a35
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/SortingCursorWrapper.java
@@ -0,0 +1,256 @@
+/*
+ * 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.DocumentsActivity.State.SORT_ORDER_DISPLAY_NAME;
+import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_LAST_MODIFIED;
+import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_SIZE;
+
+import android.database.AbstractCursor;
+import android.database.Cursor;
+import android.provider.DocumentsContract.Document;
+
+/**
+ * Cursor wrapper that presents a sorted view of the underlying cursor. Handles
+ * common {@link Document} sorting modes, such as ordering directories first.
+ */
+public class SortingCursorWrapper extends AbstractCursor {
+    private final Cursor mCursor;
+
+    private final int[] mPosition;
+    private final String[] mValueString;
+    private final long[] mValueLong;
+
+    public SortingCursorWrapper(Cursor cursor, int sortOrder) {
+        mCursor = cursor;
+
+        final int count = cursor.getCount();
+        mPosition = new int[count];
+        switch (sortOrder) {
+            case SORT_ORDER_DISPLAY_NAME:
+                mValueString = new String[count];
+                mValueLong = null;
+                break;
+            case SORT_ORDER_LAST_MODIFIED:
+            case SORT_ORDER_SIZE:
+                mValueString = null;
+                mValueLong = new long[count];
+                break;
+            default:
+                throw new IllegalArgumentException();
+        }
+
+        cursor.moveToPosition(-1);
+        for (int i = 0; i < count; i++) {
+            cursor.moveToNext();
+            mPosition[i] = i;
+
+            switch (sortOrder) {
+                case SORT_ORDER_DISPLAY_NAME:
+                    final String mimeType = cursor.getString(
+                            cursor.getColumnIndex(Document.COLUMN_MIME_TYPE));
+                    final String displayName = cursor.getString(
+                            cursor.getColumnIndex(Document.COLUMN_DISPLAY_NAME));
+                    if (Document.MIME_TYPE_DIR.equals(mimeType)) {
+                        mValueString[i] = '\001' + displayName;
+                    } else {
+                        mValueString[i] = displayName;
+                    }
+                    break;
+                case SORT_ORDER_LAST_MODIFIED:
+                    mValueLong[i] = cursor.getLong(
+                            cursor.getColumnIndex(Document.COLUMN_LAST_MODIFIED));
+                    break;
+                case SORT_ORDER_SIZE:
+                    mValueLong[i] = cursor.getLong(cursor.getColumnIndex(Document.COLUMN_SIZE));
+                    break;
+            }
+        }
+
+        switch (sortOrder) {
+            case SORT_ORDER_DISPLAY_NAME:
+                synchronized (SortingCursorWrapper.class) {
+
+                    binarySort(mPosition, mValueString);
+                }
+                break;
+            case SORT_ORDER_LAST_MODIFIED:
+            case SORT_ORDER_SIZE:
+                binarySort(mPosition, mValueLong);
+                break;
+        }
+    }
+
+    @Override
+    public void close() {
+        super.close();
+        mCursor.close();
+    }
+
+    @Override
+    public boolean onMove(int oldPosition, int newPosition) {
+        return mCursor.moveToPosition(mPosition[newPosition]);
+    }
+
+    @Override
+    public String[] getColumnNames() {
+        return mCursor.getColumnNames();
+    }
+
+    @Override
+    public int getCount() {
+        return mCursor.getCount();
+    }
+
+    @Override
+    public double getDouble(int column) {
+        return mCursor.getDouble(column);
+    }
+
+    @Override
+    public float getFloat(int column) {
+        return mCursor.getFloat(column);
+    }
+
+    @Override
+    public int getInt(int column) {
+        return mCursor.getInt(column);
+    }
+
+    @Override
+    public long getLong(int column) {
+        return mCursor.getLong(column);
+    }
+
+    @Override
+    public short getShort(int column) {
+        return mCursor.getShort(column);
+    }
+
+    @Override
+    public String getString(int column) {
+        return mCursor.getString(column);
+    }
+
+    @Override
+    public int getType(int column) {
+        return mCursor.getType(column);
+    }
+
+    @Override
+    public boolean isNull(int column) {
+        return mCursor.isNull(column);
+    }
+
+    /**
+     * Borrowed from TimSort.binarySort(), but modified to sort two column
+     * dataset.
+     */
+    private static void binarySort(int[] position, String[] value) {
+        final int count = position.length;
+        for (int start = 1; start < count; start++) {
+            final int pivotPosition = position[start];
+            final String pivotValue = value[start];
+
+            int left = 0;
+            int right = start;
+
+            while (left < right) {
+                int mid = (left + right) >>> 1;
+
+                final String lhs = pivotValue;
+                final String rhs = value[mid];
+                final int compare;
+                if (lhs == null) {
+                    compare = -1;
+                } else if (rhs == null) {
+                    compare = 1;
+                } else {
+                    compare = lhs.compareToIgnoreCase(rhs);
+                }
+
+                if (compare < 0) {
+                    right = mid;
+                } else {
+                    left = mid + 1;
+                }
+            }
+
+            int n = start - left;
+            switch (n) {
+                case 2:
+                    position[left + 2] = position[left + 1];
+                    value[left + 2] = value[left + 1];
+                case 1:
+                    position[left + 1] = position[left];
+                    value[left + 1] = value[left];
+                    break;
+                default:
+                    System.arraycopy(position, left, position, left + 1, n);
+                    System.arraycopy(value, left, value, left + 1, n);
+            }
+
+            position[left] = pivotPosition;
+            value[left] = pivotValue;
+        }
+    }
+
+    /**
+     * Borrowed from TimSort.binarySort(), but modified to sort two column
+     * dataset.
+     */
+    private static void binarySort(int[] position, long[] value) {
+        final int count = position.length;
+        for (int start = 1; start < count; start++) {
+            final int pivotPosition = position[start];
+            final long pivotValue = value[start];
+
+            int left = 0;
+            int right = start;
+
+            while (left < right) {
+                int mid = (left + right) >>> 1;
+
+                final long lhs = pivotValue;
+                final long rhs = value[mid];
+                final int compare = Long.compare(lhs, rhs);
+                if (compare > 0) {
+                    right = mid;
+                } else {
+                    left = mid + 1;
+                }
+            }
+
+            int n = start - left;
+            switch (n) {
+                case 2:
+                    position[left + 2] = position[left + 1];
+                    value[left + 2] = value[left + 1];
+                case 1:
+                    position[left + 1] = position[left];
+                    value[left + 1] = value[left];
+                    break;
+                default:
+                    System.arraycopy(position, left, position, left + 1, n);
+                    System.arraycopy(value, left, value, left + 1, n);
+            }
+
+            position[left] = pivotPosition;
+            value[left] = pivotValue;
+        }
+    }
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java b/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java
index f6548e8..2405cb5 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java
@@ -32,7 +32,6 @@
 import libcore.io.IoUtils;
 import libcore.io.Streams;
 
-import java.io.IOException;
 import java.io.InputStream;
 
 public class TestActivity extends Activity {
@@ -50,8 +49,11 @@
         view.setOrientation(LinearLayout.VERTICAL);
 
         final CheckBox multiple = new CheckBox(context);
-        multiple.setText("ALLOW_MULTIPLE");
+        multiple.setText("\nALLOW_MULTIPLE\n");
         view.addView(multiple);
+        final CheckBox localOnly = new CheckBox(context);
+        localOnly.setText("\nLOCAL_ONLY\n");
+        view.addView(localOnly);
 
         Button button;
         button = new Button(context);
@@ -65,6 +67,9 @@
                 if (multiple.isChecked()) {
                     intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
                 }
+                if (localOnly.isChecked()) {
+                    intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
+                }
                 startActivityForResult(intent, 42);
             }
         });
@@ -81,6 +86,28 @@
                 if (multiple.isChecked()) {
                     intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
                 }
+                if (localOnly.isChecked()) {
+                    intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
+                }
+                startActivityForResult(intent, 42);
+            }
+        });
+        view.addView(button);
+
+        button = new Button(context);
+        button.setText("OPEN_DOC audio/ogg");
+        button.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+                intent.addCategory(Intent.CATEGORY_OPENABLE);
+                intent.setType("audio/ogg");
+                if (multiple.isChecked()) {
+                    intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
+                }
+                if (localOnly.isChecked()) {
+                    intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
+                }
                 startActivityForResult(intent, 42);
             }
         });
@@ -99,6 +126,9 @@
                 if (multiple.isChecked()) {
                     intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
                 }
+                if (localOnly.isChecked()) {
+                    intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
+                }
                 startActivityForResult(intent, 42);
             }
         });
@@ -113,6 +143,9 @@
                 intent.addCategory(Intent.CATEGORY_OPENABLE);
                 intent.setType("text/plain");
                 intent.putExtra(Intent.EXTRA_TITLE, "foobar.txt");
+                if (localOnly.isChecked()) {
+                    intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
+                }
                 startActivityForResult(intent, 42);
             }
         });
@@ -129,6 +162,9 @@
                 if (multiple.isChecked()) {
                     intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
                 }
+                if (localOnly.isChecked()) {
+                    intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
+                }
                 startActivityForResult(Intent.createChooser(intent, "Kittens!"), 42);
             }
         });
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/Document.java b/packages/DocumentsUI/src/com/android/documentsui/model/Document.java
deleted file mode 100644
index 692d171..0000000
--- a/packages/DocumentsUI/src/com/android/documentsui/model/Document.java
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * 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.model;
-
-import android.content.ContentResolver;
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.DocumentsContract;
-import android.provider.DocumentsContract.DocumentColumns;
-import android.provider.DocumentsContract.Documents;
-
-import com.android.documentsui.RecentsProvider;
-
-import libcore.io.IoUtils;
-
-import java.io.FileNotFoundException;
-import java.util.Comparator;
-
-/**
- * Representation of a single document.
- */
-public class Document {
-    public final Uri uri;
-    public final String mimeType;
-    public final String displayName;
-    public final long lastModified;
-    public final int flags;
-    public final String summary;
-    public final long size;
-
-    private Document(Uri uri, String mimeType, String displayName, long lastModified, int flags,
-            String summary, long size) {
-        this.uri = uri;
-        this.mimeType = mimeType;
-        this.displayName = displayName;
-        this.lastModified = lastModified;
-        this.flags = flags;
-        this.summary = summary;
-        this.size = size;
-    }
-
-    public static Document fromDirectoryCursor(Uri parent, Cursor cursor) {
-        final String authority = parent.getAuthority();
-        final String docId = getCursorString(cursor, DocumentColumns.DOC_ID);
-
-        final Uri uri = DocumentsContract.buildDocumentUri(authority, docId);
-        final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE);
-        final String displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME);
-        final long lastModified = getCursorLong(cursor, DocumentColumns.LAST_MODIFIED);
-        final int flags = getCursorInt(cursor, DocumentColumns.FLAGS);
-        final String summary = getCursorString(cursor, DocumentColumns.SUMMARY);
-        final long size = getCursorLong(cursor, DocumentColumns.SIZE);
-
-        return new Document(uri, mimeType, displayName, lastModified, flags, summary, size);
-    }
-
-    @Deprecated
-    public static Document fromRecentOpenCursor(ContentResolver resolver, Cursor recentCursor)
-            throws FileNotFoundException {
-        final Uri uri = Uri.parse(getCursorString(recentCursor, RecentsProvider.COL_URI));
-        final long lastModified = getCursorLong(recentCursor, RecentsProvider.COL_TIMESTAMP);
-
-        Cursor cursor = null;
-        try {
-            cursor = resolver.query(uri, null, null, null, null);
-            if (!cursor.moveToFirst()) {
-                throw new FileNotFoundException("Missing details for " + uri);
-            }
-            final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE);
-            final String displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME);
-            final int flags = getCursorInt(cursor, DocumentColumns.FLAGS)
-                    & Documents.FLAG_SUPPORTS_THUMBNAIL;
-            final String summary = getCursorString(cursor, DocumentColumns.SUMMARY);
-            final long size = getCursorLong(cursor, DocumentColumns.SIZE);
-
-            return new Document(uri, mimeType, displayName, lastModified, flags, summary, size);
-        } catch (Throwable t) {
-            throw asFileNotFoundException(t);
-        } finally {
-            IoUtils.closeQuietly(cursor);
-        }
-    }
-
-    public static Document fromUri(ContentResolver resolver, Uri uri) throws FileNotFoundException {
-        Cursor cursor = null;
-        try {
-            cursor = resolver.query(uri, null, null, null, null);
-            if (!cursor.moveToFirst()) {
-                throw new FileNotFoundException("Missing details for " + uri);
-            }
-            final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE);
-            final String displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME);
-            final long lastModified = getCursorLong(cursor, DocumentColumns.LAST_MODIFIED);
-            final int flags = getCursorInt(cursor, DocumentColumns.FLAGS);
-            final String summary = getCursorString(cursor, DocumentColumns.SUMMARY);
-            final long size = getCursorLong(cursor, DocumentColumns.SIZE);
-
-            return new Document(uri, mimeType, displayName, lastModified, flags, summary, size);
-        } catch (Throwable t) {
-            throw asFileNotFoundException(t);
-        } finally {
-            IoUtils.closeQuietly(cursor);
-        }
-    }
-
-    @Override
-    public String toString() {
-        return "Document{name=" + displayName + ", uri=" + uri + "}";
-    }
-
-    public boolean isCreateSupported() {
-        return (flags & Documents.FLAG_SUPPORTS_CREATE) != 0;
-    }
-
-    public boolean isSearchSupported() {
-        return (flags & Documents.FLAG_SUPPORTS_SEARCH) != 0;
-    }
-
-    public boolean isThumbnailSupported() {
-        return (flags & Documents.FLAG_SUPPORTS_THUMBNAIL) != 0;
-    }
-
-    public boolean isDirectory() {
-        return Documents.MIME_TYPE_DIR.equals(mimeType);
-    }
-
-    public boolean isGridPreferred() {
-        return (flags & Documents.FLAG_PREFERS_GRID) != 0;
-    }
-
-    public boolean isDeleteSupported() {
-        return (flags & Documents.FLAG_SUPPORTS_DELETE) != 0;
-    }
-
-    private static String getCursorString(Cursor cursor, String columnName) {
-        final int index = cursor.getColumnIndex(columnName);
-        return (index != -1) ? cursor.getString(index) : null;
-    }
-
-    /**
-     * Missing or null values are returned as -1.
-     */
-    private static long getCursorLong(Cursor cursor, String columnName) {
-        final int index = cursor.getColumnIndex(columnName);
-        if (index == -1) return -1;
-        final String value = cursor.getString(index);
-        if (value == null) return -1;
-        try {
-            return Long.parseLong(value);
-        } catch (NumberFormatException e) {
-            return -1;
-        }
-    }
-
-    private static int getCursorInt(Cursor cursor, String columnName) {
-        final int index = cursor.getColumnIndex(columnName);
-        return (index != -1) ? cursor.getInt(index) : 0;
-    }
-
-    public static class DisplayNameComparator implements Comparator<Document> {
-        @Override
-        public int compare(Document lhs, Document rhs) {
-            final boolean leftDir = lhs.isDirectory();
-            final boolean rightDir = rhs.isDirectory();
-
-            if (leftDir != rightDir) {
-                return leftDir ? -1 : 1;
-            } else {
-                return compareToIgnoreCaseNullable(lhs.displayName, rhs.displayName);
-            }
-        }
-    }
-
-    public static class LastModifiedComparator implements Comparator<Document> {
-        @Override
-        public int compare(Document lhs, Document rhs) {
-            return Long.compare(rhs.lastModified, lhs.lastModified);
-        }
-    }
-
-    public static class SizeComparator implements Comparator<Document> {
-        @Override
-        public int compare(Document lhs, Document rhs) {
-            return Long.compare(rhs.size, lhs.size);
-        }
-    }
-
-    public static FileNotFoundException asFileNotFoundException(Throwable t)
-            throws FileNotFoundException {
-        if (t instanceof FileNotFoundException) {
-            throw (FileNotFoundException) t;
-        }
-        final FileNotFoundException fnfe = new FileNotFoundException(t.getMessage());
-        fnfe.initCause(t);
-        throw fnfe;
-    }
-
-    public static int compareToIgnoreCaseNullable(String lhs, String rhs) {
-        if (lhs == null) return -1;
-        if (rhs == null) return 1;
-        return lhs.compareToIgnoreCase(rhs);
-    }
-}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
new file mode 100644
index 0000000..7721bcc
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
@@ -0,0 +1,294 @@
+/*
+ * 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.model;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.database.Cursor;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.provider.DocumentsContract;
+import android.provider.DocumentsContract.Document;
+
+import com.android.documentsui.RecentsProvider;
+import com.android.documentsui.RootCursorWrapper;
+
+import libcore.io.IoUtils;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.ProtocolException;
+import java.util.Comparator;
+
+/**
+ * Representation of a {@link Document}.
+ */
+public class DocumentInfo implements Durable {
+    private static final int VERSION_INIT = 1;
+
+    public Uri uri;
+    public String mimeType;
+    public String displayName;
+    public long lastModified;
+    public int flags;
+    public String summary;
+    public long size;
+    public int icon;
+
+    public DocumentInfo() {
+        reset();
+    }
+
+    @Override
+    public void reset() {
+        uri = null;
+        mimeType = null;
+        displayName = null;
+        lastModified = -1;
+        flags = 0;
+        summary = null;
+        size = -1;
+        icon = 0;
+    }
+
+    @Override
+    public void read(DataInputStream in) throws IOException {
+        final int version = in.readInt();
+        switch (version) {
+            case VERSION_INIT:
+                final String rawUri = DurableUtils.readNullableString(in);
+                uri = rawUri != null ? Uri.parse(rawUri) : null;
+                mimeType = DurableUtils.readNullableString(in);
+                displayName = DurableUtils.readNullableString(in);
+                lastModified = in.readLong();
+                flags = in.readInt();
+                summary = DurableUtils.readNullableString(in);
+                size = in.readLong();
+                icon = in.readInt();
+                break;
+            default:
+                throw new ProtocolException("Unknown version " + version);
+        }
+    }
+
+    @Override
+    public void write(DataOutputStream out) throws IOException {
+        out.writeInt(VERSION_INIT);
+        DurableUtils.writeNullableString(out, uri.toString());
+        DurableUtils.writeNullableString(out, mimeType);
+        DurableUtils.writeNullableString(out, displayName);
+        out.writeLong(lastModified);
+        out.writeInt(flags);
+        DurableUtils.writeNullableString(out, summary);
+        out.writeLong(size);
+        out.writeInt(icon);
+    }
+
+    public static DocumentInfo fromDirectoryCursor(Cursor cursor) {
+        final DocumentInfo doc = new DocumentInfo();
+        final String authority = getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY);
+        final String docId = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID);
+        doc.uri = DocumentsContract.buildDocumentUri(authority, docId);
+        doc.mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
+        doc.displayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
+        doc.lastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED);
+        doc.flags = getCursorInt(cursor, Document.COLUMN_FLAGS);
+        doc.summary = getCursorString(cursor, Document.COLUMN_SUMMARY);
+        doc.size = getCursorLong(cursor, Document.COLUMN_SIZE);
+        doc.icon = getCursorInt(cursor, Document.COLUMN_ICON);
+        return doc;
+    }
+
+    @Deprecated
+    public static DocumentInfo fromRecentOpenCursor(ContentResolver resolver, Cursor recentCursor)
+            throws FileNotFoundException {
+        final Uri uri = Uri.parse(getCursorString(recentCursor, RecentsProvider.COL_URI));
+        final long lastModified = getCursorLong(recentCursor, RecentsProvider.COL_TIMESTAMP);
+
+        Cursor cursor = null;
+        try {
+            cursor = resolver.query(uri, null, null, null, null);
+            if (!cursor.moveToFirst()) {
+                throw new FileNotFoundException("Missing details for " + uri);
+            }
+
+            final DocumentInfo doc = new DocumentInfo();
+            doc.uri = uri;
+            doc.mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
+            doc.displayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
+            doc.lastModified = lastModified;
+            doc.flags = getCursorInt(cursor, Document.COLUMN_FLAGS)
+                    & Document.FLAG_SUPPORTS_THUMBNAIL;
+            doc.summary = getCursorString(cursor, Document.COLUMN_SUMMARY);
+            doc.size = getCursorLong(cursor, Document.COLUMN_SIZE);
+            doc.icon = getCursorInt(cursor, Document.COLUMN_ICON);
+            return doc;
+        } catch (Throwable t) {
+            throw asFileNotFoundException(t);
+        } finally {
+            IoUtils.closeQuietly(cursor);
+        }
+    }
+
+    public static DocumentInfo fromUri(ContentResolver resolver, Uri uri) throws FileNotFoundException {
+        Cursor cursor = null;
+        try {
+            cursor = resolver.query(uri, null, null, null, null);
+            if (!cursor.moveToFirst()) {
+                throw new FileNotFoundException("Missing details for " + uri);
+            }
+            final DocumentInfo doc = new DocumentInfo();
+            doc.uri = uri;
+            doc.mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
+            doc.displayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
+            doc.lastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED);
+            doc.flags = getCursorInt(cursor, Document.COLUMN_FLAGS);
+            doc.summary = getCursorString(cursor, Document.COLUMN_SUMMARY);
+            doc.size = getCursorLong(cursor, Document.COLUMN_SIZE);
+            doc.icon = getCursorInt(cursor, Document.COLUMN_ICON);
+            return doc;
+        } catch (Throwable t) {
+            throw asFileNotFoundException(t);
+        } finally {
+            IoUtils.closeQuietly(cursor);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "Document{name=" + displayName + ", uri=" + uri + "}";
+    }
+
+    public boolean isCreateSupported() {
+        return (flags & Document.FLAG_DIR_SUPPORTS_CREATE) != 0;
+    }
+
+    public boolean isSearchSupported() {
+        return (flags & Document.FLAG_DIR_SUPPORTS_SEARCH) != 0;
+    }
+
+    public boolean isThumbnailSupported() {
+        return (flags & Document.FLAG_SUPPORTS_THUMBNAIL) != 0;
+    }
+
+    public boolean isDirectory() {
+        return Document.MIME_TYPE_DIR.equals(mimeType);
+    }
+
+    public boolean isGridPreferred() {
+        return (flags & Document.FLAG_DIR_PREFERS_GRID) != 0;
+    }
+
+    public boolean isDeleteSupported() {
+        return (flags & Document.FLAG_SUPPORTS_DELETE) != 0;
+    }
+
+    public Drawable loadIcon(Context context) {
+        return loadIcon(context, uri.getAuthority(), icon);
+    }
+
+    public static Drawable loadIcon(Context context, String authority, int icon) {
+        if (icon != 0) {
+            if (authority != null) {
+                final PackageManager pm = context.getPackageManager();
+                final ProviderInfo info = pm.resolveContentProvider(authority, 0);
+                if (info != null) {
+                    return pm.getDrawable(info.packageName, icon, info.applicationInfo);
+                }
+            } else {
+                return context.getResources().getDrawable(icon);
+            }
+        }
+        return null;
+    }
+
+    public static String getCursorString(Cursor cursor, String columnName) {
+        final int index = cursor.getColumnIndex(columnName);
+        return (index != -1) ? cursor.getString(index) : null;
+    }
+
+    /**
+     * Missing or null values are returned as -1.
+     */
+    public static long getCursorLong(Cursor cursor, String columnName) {
+        final int index = cursor.getColumnIndex(columnName);
+        if (index == -1) return -1;
+        final String value = cursor.getString(index);
+        if (value == null) return -1;
+        try {
+            return Long.parseLong(value);
+        } catch (NumberFormatException e) {
+            return -1;
+        }
+    }
+
+    public static int getCursorInt(Cursor cursor, String columnName) {
+        final int index = cursor.getColumnIndex(columnName);
+        return (index != -1) ? cursor.getInt(index) : 0;
+    }
+
+    @Deprecated
+    public static class DisplayNameComparator implements Comparator<DocumentInfo> {
+        @Override
+        public int compare(DocumentInfo lhs, DocumentInfo rhs) {
+            final boolean leftDir = lhs.isDirectory();
+            final boolean rightDir = rhs.isDirectory();
+
+            if (leftDir != rightDir) {
+                return leftDir ? -1 : 1;
+            } else {
+                return compareToIgnoreCaseNullable(lhs.displayName, rhs.displayName);
+            }
+        }
+    }
+
+    @Deprecated
+    public static class LastModifiedComparator implements Comparator<DocumentInfo> {
+        @Override
+        public int compare(DocumentInfo lhs, DocumentInfo rhs) {
+            return Long.compare(rhs.lastModified, lhs.lastModified);
+        }
+    }
+
+    @Deprecated
+    public static class SizeComparator implements Comparator<DocumentInfo> {
+        @Override
+        public int compare(DocumentInfo lhs, DocumentInfo rhs) {
+            return Long.compare(rhs.size, lhs.size);
+        }
+    }
+
+    public static FileNotFoundException asFileNotFoundException(Throwable t)
+            throws FileNotFoundException {
+        if (t instanceof FileNotFoundException) {
+            throw (FileNotFoundException) t;
+        }
+        final FileNotFoundException fnfe = new FileNotFoundException(t.getMessage());
+        fnfe.initCause(t);
+        throw fnfe;
+    }
+
+    public static int compareToIgnoreCaseNullable(String lhs, String rhs) {
+        if (lhs == null) return -1;
+        if (rhs == null) return 1;
+        return lhs.compareToIgnoreCase(rhs);
+    }
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java
index 81f75d2..64631ab 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java
@@ -16,57 +16,22 @@
 
 package com.android.documentsui.model;
 
-import static com.android.documentsui.DocumentsActivity.TAG;
-import static com.android.documentsui.model.Document.asFileNotFoundException;
-
-import android.content.ContentResolver;
-import android.net.Uri;
-import android.provider.DocumentsContract.DocumentRoot;
-import android.util.Log;
-
 import com.android.documentsui.RootsCache;
 
-import org.json.JSONArray;
-import org.json.JSONException;
-
-import java.io.FileNotFoundException;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.net.ProtocolException;
 import java.util.LinkedList;
 
 /**
- * Representation of a stack of {@link Document}, usually the result of a
+ * Representation of a stack of {@link DocumentInfo}, usually the result of a
  * user-driven traversal.
  */
-public class DocumentStack extends LinkedList<Document> {
+public class DocumentStack extends LinkedList<DocumentInfo> implements Durable {
+    private static final int VERSION_INIT = 1;
 
-    public static String serialize(DocumentStack stack) {
-        final JSONArray json = new JSONArray();
-        for (int i = 0; i < stack.size(); i++) {
-            json.put(stack.get(i).uri);
-        }
-        return json.toString();
-    }
-
-    public static DocumentStack deserialize(ContentResolver resolver, String raw)
-            throws FileNotFoundException {
-        Log.d(TAG, "deserialize: " + raw);
-
-        final DocumentStack stack = new DocumentStack();
-        try {
-            final JSONArray json = new JSONArray(raw);
-            for (int i = 0; i < json.length(); i++) {
-                final Uri uri = Uri.parse(json.getString(i));
-                final Document doc = Document.fromUri(resolver, uri);
-                stack.add(doc);
-            }
-        } catch (JSONException e) {
-            throw asFileNotFoundException(e);
-        }
-
-        // TODO: handle roots that have gone missing
-        return stack;
-    }
-
-    public DocumentRoot getRoot(RootsCache roots) {
+    public RootInfo getRoot(RootsCache roots) {
         return roots.findRoot(getLast().uri);
     }
 
@@ -79,4 +44,37 @@
             return null;
         }
     }
+
+    @Override
+    public void reset() {
+        clear();
+    }
+
+    @Override
+    public void read(DataInputStream in) throws IOException {
+        final int version = in.readInt();
+        switch (version) {
+            case VERSION_INIT:
+                final int size = in.readInt();
+                for (int i = 0; i < size; i++) {
+                    final DocumentInfo doc = new DocumentInfo();
+                    doc.read(in);
+                    add(doc);
+                }
+                break;
+            default:
+                throw new ProtocolException("Unknown version " + version);
+        }
+    }
+
+    @Override
+    public void write(DataOutputStream out) throws IOException {
+        out.writeInt(VERSION_INIT);
+        final int size = size();
+        out.writeInt(size);
+        for (int i = 0; i < size; i++) {
+            final DocumentInfo doc = get(i);
+            doc.write(out);
+        }
+    }
 }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/Durable.java b/packages/DocumentsUI/src/com/android/documentsui/model/Durable.java
new file mode 100644
index 0000000..01633ed
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/Durable.java
@@ -0,0 +1,27 @@
+/*
+ * 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.model;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+public interface Durable {
+    public void reset();
+    public void read(DataInputStream in) throws IOException;
+    public void write(DataOutputStream out) throws IOException;
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DurableUtils.java b/packages/DocumentsUI/src/com/android/documentsui/model/DurableUtils.java
new file mode 100644
index 0000000..214fb14
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/DurableUtils.java
@@ -0,0 +1,100 @@
+/*
+ * 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.model;
+
+import static com.android.documentsui.DocumentsActivity.TAG;
+
+import android.os.BadParcelableException;
+import android.os.Parcel;
+import android.util.Log;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+public class DurableUtils {
+    public static <D extends Durable> byte[] writeToArray(D d) throws IOException {
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        d.write(new DataOutputStream(out));
+        return out.toByteArray();
+    }
+
+    public static <D extends Durable> D readFromArray(byte[] data, D d) throws IOException {
+        final ByteArrayInputStream in = new ByteArrayInputStream(data);
+        d.reset();
+        try {
+            d.read(new DataInputStream(in));
+        } catch (IOException e) {
+            d.reset();
+            throw e;
+        }
+        return d;
+    }
+
+    public static <D extends Durable> byte[] writeToArrayOrNull(D d) {
+        try {
+            return writeToArray(d);
+        } catch (IOException e) {
+            Log.w(TAG, "Failed to write", e);
+            return null;
+        }
+    }
+
+    public static <D extends Durable> D readFromArrayOrNull(byte[] data, D d) {
+        try {
+            return readFromArray(data, d);
+        } catch (IOException e) {
+            Log.w(TAG, "Failed to read", e);
+            return null;
+        }
+    }
+
+    public static <D extends Durable> void writeToParcel(Parcel parcel, D d) {
+        try {
+            parcel.writeByteArray(writeToArray(d));
+        } catch (IOException e) {
+            throw new BadParcelableException(e);
+        }
+    }
+
+    public static <D extends Durable> D readFromParcel(Parcel parcel, D d) {
+        try {
+            return readFromArray(parcel.createByteArray(), d);
+        } catch (IOException e) {
+            throw new BadParcelableException(e);
+        }
+    }
+
+    public static void writeNullableString(DataOutputStream out, String value) throws IOException {
+        if (value != null) {
+            out.write(1);
+            out.writeUTF(value);
+        } else {
+            out.write(0);
+        }
+    }
+
+    public static String readNullableString(DataInputStream in) throws IOException {
+        if (in.read() != 0) {
+            return in.readUTF();
+        } else {
+            return null;
+        }
+    }
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
new file mode 100644
index 0000000..189284b
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
@@ -0,0 +1,80 @@
+/*
+ * 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.model;
+
+import static com.android.documentsui.model.DocumentInfo.getCursorInt;
+import static com.android.documentsui.model.DocumentInfo.getCursorLong;
+import static com.android.documentsui.model.DocumentInfo.getCursorString;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.drawable.Drawable;
+import android.provider.DocumentsContract.Root;
+
+import java.util.Objects;
+
+/**
+ * Representation of a {@link Root}.
+ */
+public class RootInfo {
+    public String authority;
+    public String rootId;
+    public int rootType;
+    public int flags;
+    public int icon;
+    public String title;
+    public String summary;
+    public String documentId;
+    public long availableBytes;
+
+    public static RootInfo fromRootsCursor(String authority, Cursor cursor) {
+        final RootInfo root = new RootInfo();
+        root.authority = authority;
+        root.rootId = getCursorString(cursor, Root.COLUMN_ROOT_ID);
+        root.rootType = getCursorInt(cursor, Root.COLUMN_ROOT_TYPE);
+        root.flags = getCursorInt(cursor, Root.COLUMN_FLAGS);
+        root.icon = getCursorInt(cursor, Root.COLUMN_ICON);
+        root.title = getCursorString(cursor, Root.COLUMN_TITLE);
+        root.summary = getCursorString(cursor, Root.COLUMN_SUMMARY);
+        root.documentId = getCursorString(cursor, Root.COLUMN_DOCUMENT_ID);
+        root.availableBytes = getCursorLong(cursor, Root.COLUMN_AVAILABLE_BYTES);
+        return root;
+    }
+
+    public Drawable loadIcon(Context context) {
+        return DocumentInfo.loadIcon(context, authority, icon);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof RootInfo) {
+            final RootInfo root = (RootInfo) o;
+            return Objects.equals(authority, root.authority) && Objects.equals(rootId, root.rootId);
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(authority, rootId);
+    }
+
+    public String getDirectoryString() {
+        return (summary != null) ? summary : title;
+    }
+}
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 583ecc9..bbe3b45 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -26,9 +26,8 @@
 import android.os.CancellationSignal;
 import android.os.Environment;
 import android.os.ParcelFileDescriptor;
-import android.provider.DocumentsContract.DocumentColumns;
-import android.provider.DocumentsContract.DocumentRoot;
-import android.provider.DocumentsContract.Documents;
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract.Root;
 import android.provider.DocumentsProvider;
 import android.webkit.MimeTypeMap;
 
@@ -41,7 +40,6 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.LinkedList;
-import java.util.List;
 import java.util.Map;
 
 public class ExternalStorageProvider extends DocumentsProvider {
@@ -49,36 +47,52 @@
 
     // docId format: root:path/to/file
 
-    private static final String[] SUPPORTED_COLUMNS = new String[] {
-            DocumentColumns.DOC_ID, DocumentColumns.DISPLAY_NAME, DocumentColumns.SIZE,
-            DocumentColumns.MIME_TYPE, DocumentColumns.LAST_MODIFIED, DocumentColumns.FLAGS
+    private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
+            Root.COLUMN_ROOT_ID, Root.COLUMN_ROOT_TYPE, Root.COLUMN_FLAGS, Root.COLUMN_ICON,
+            Root.COLUMN_TITLE, Root.COLUMN_SUMMARY, Root.COLUMN_DOCUMENT_ID,
+            Root.COLUMN_AVAILABLE_BYTES,
     };
 
-    private ArrayList<DocumentRoot> mRoots;
-    private HashMap<String, DocumentRoot> mTagToRoot;
-    private HashMap<String, File> mTagToPath;
+    private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
+            Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, Document.COLUMN_DISPLAY_NAME,
+            Document.COLUMN_LAST_MODIFIED, Document.COLUMN_FLAGS, Document.COLUMN_SIZE,
+    };
+
+    private static class RootInfo {
+        public String rootId;
+        public int rootType;
+        public int flags;
+        public int icon;
+        public String title;
+        public String docId;
+    }
+
+    private ArrayList<RootInfo> mRoots;
+    private HashMap<String, RootInfo> mIdToRoot;
+    private HashMap<String, File> mIdToPath;
 
     @Override
     public boolean onCreate() {
         mRoots = Lists.newArrayList();
-        mTagToRoot = Maps.newHashMap();
-        mTagToPath = Maps.newHashMap();
+        mIdToRoot = Maps.newHashMap();
+        mIdToPath = Maps.newHashMap();
 
         // TODO: support multiple storage devices
 
         try {
-            final String tag = "primary";
+            final String rootId = "primary";
             final File path = Environment.getExternalStorageDirectory();
-            mTagToPath.put(tag, path);
+            mIdToPath.put(rootId, path);
 
-            final DocumentRoot root = new DocumentRoot();
-            root.docId = getDocIdForFile(path);
-            root.rootType = DocumentRoot.ROOT_TYPE_DEVICE_ADVANCED;
-            root.title = getContext().getString(R.string.root_internal_storage);
+            final RootInfo root = new RootInfo();
+            root.rootId = "primary";
+            root.rootType = Root.ROOT_TYPE_DEVICE;
+            root.flags = Root.FLAG_SUPPORTS_CREATE | Root.FLAG_LOCAL_ONLY | Root.FLAG_ADVANCED;
             root.icon = R.drawable.ic_pdf;
-            root.flags = DocumentRoot.FLAG_LOCAL_ONLY;
+            root.title = getContext().getString(R.string.root_internal_storage);
+            root.docId = getDocIdForFile(path);
             mRoots.add(root);
-            mTagToRoot.put(tag, root);
+            mIdToRoot.put(rootId, root);
         } catch (FileNotFoundException e) {
             throw new IllegalStateException(e);
         }
@@ -86,12 +100,20 @@
         return true;
     }
 
+    private static String[] resolveRootProjection(String[] projection) {
+        return projection != null ? projection : DEFAULT_ROOT_PROJECTION;
+    }
+
+    private static String[] resolveDocumentProjection(String[] projection) {
+        return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION;
+    }
+
     private String getDocIdForFile(File file) throws FileNotFoundException {
         String path = file.getAbsolutePath();
 
         // Find the most-specific root path
         Map.Entry<String, File> mostSpecific = null;
-        for (Map.Entry<String, File> root : mTagToPath.entrySet()) {
+        for (Map.Entry<String, File> root : mIdToPath.entrySet()) {
             final String rootPath = root.getValue().getPath();
             if (path.startsWith(rootPath) && (mostSpecific == null
                     || rootPath.length() > mostSpecific.getValue().getPath().length())) {
@@ -121,7 +143,7 @@
         final String tag = docId.substring(0, splitIndex);
         final String path = docId.substring(splitIndex + 1);
 
-        File target = mTagToPath.get(tag);
+        File target = mIdToPath.get(tag);
         if (target == null) {
             throw new FileNotFoundException("No root for " + tag);
         }
@@ -143,41 +165,48 @@
         int flags = 0;
 
         if (file.isDirectory()) {
-            flags |= Documents.FLAG_SUPPORTS_SEARCH;
+            flags |= Document.FLAG_DIR_SUPPORTS_SEARCH;
         }
         if (file.isDirectory() && file.canWrite()) {
-            flags |= Documents.FLAG_SUPPORTS_CREATE;
+            flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
         }
         if (file.canWrite()) {
-            flags |= Documents.FLAG_SUPPORTS_WRITE;
-            flags |= Documents.FLAG_SUPPORTS_RENAME;
-            flags |= Documents.FLAG_SUPPORTS_DELETE;
+            flags |= Document.FLAG_SUPPORTS_WRITE;
+            flags |= Document.FLAG_SUPPORTS_DELETE;
         }
 
         final String displayName = file.getName();
         final String mimeType = getTypeForFile(file);
         if (mimeType.startsWith("image/")) {
-            flags |= Documents.FLAG_SUPPORTS_THUMBNAIL;
+            flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
         }
 
         final RowBuilder row = result.newRow();
-        row.offer(DocumentColumns.DOC_ID, docId);
-        row.offer(DocumentColumns.DISPLAY_NAME, displayName);
-        row.offer(DocumentColumns.SIZE, file.length());
-        row.offer(DocumentColumns.MIME_TYPE, mimeType);
-        row.offer(DocumentColumns.LAST_MODIFIED, file.lastModified());
-        row.offer(DocumentColumns.FLAGS, flags);
+        row.offer(Document.COLUMN_DOCUMENT_ID, docId);
+        row.offer(Document.COLUMN_DISPLAY_NAME, displayName);
+        row.offer(Document.COLUMN_SIZE, file.length());
+        row.offer(Document.COLUMN_MIME_TYPE, mimeType);
+        row.offer(Document.COLUMN_LAST_MODIFIED, file.lastModified());
+        row.offer(Document.COLUMN_FLAGS, flags);
     }
 
     @Override
-    public List<DocumentRoot> getDocumentRoots() {
-        // Update free space
-        for (String tag : mTagToRoot.keySet()) {
-            final DocumentRoot root = mTagToRoot.get(tag);
-            final File path = mTagToPath.get(tag);
-            root.availableBytes = path.getFreeSpace();
+    public Cursor queryRoots(String[] projection) throws FileNotFoundException {
+        final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
+        for (String rootId : mIdToPath.keySet()) {
+            final RootInfo root = mIdToRoot.get(rootId);
+            final File path = mIdToPath.get(rootId);
+
+            final RowBuilder row = result.newRow();
+            row.offer(Root.COLUMN_ROOT_ID, root.rootId);
+            row.offer(Root.COLUMN_ROOT_TYPE, root.rootType);
+            row.offer(Root.COLUMN_FLAGS, root.flags);
+            row.offer(Root.COLUMN_ICON, root.icon);
+            row.offer(Root.COLUMN_TITLE, root.title);
+            row.offer(Root.COLUMN_DOCUMENT_ID, root.docId);
+            row.offer(Root.COLUMN_AVAILABLE_BYTES, path.getFreeSpace());
         }
-        return mRoots;
+        return result;
     }
 
     @Override
@@ -187,7 +216,7 @@
         displayName = validateDisplayName(mimeType, displayName);
 
         final File file = new File(parent, displayName);
-        if (Documents.MIME_TYPE_DIR.equals(mimeType)) {
+        if (Document.MIME_TYPE_DIR.equals(mimeType)) {
             if (!file.mkdir()) {
                 throw new IllegalStateException("Failed to mkdir " + file);
             }
@@ -204,16 +233,6 @@
     }
 
     @Override
-    public void renameDocument(String docId, String displayName) throws FileNotFoundException {
-        final File file = getFileForDocId(docId);
-        final File newFile = new File(file.getParentFile(), displayName);
-        if (!file.renameTo(newFile)) {
-            throw new IllegalStateException("Failed to rename " + docId);
-        }
-        // TODO: update any outstanding grants
-    }
-
-    @Override
     public void deleteDocument(String docId) throws FileNotFoundException {
         final File file = getFileForDocId(docId);
         if (!file.delete()) {
@@ -222,16 +241,19 @@
     }
 
     @Override
-    public Cursor queryDocument(String docId) throws FileNotFoundException {
-        final MatrixCursor result = new MatrixCursor(SUPPORTED_COLUMNS);
-        includeFile(result, docId, null);
+    public Cursor queryDocument(String documentId, String[] projection)
+            throws FileNotFoundException {
+        final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
+        includeFile(result, documentId, null);
         return result;
     }
 
     @Override
-    public Cursor queryDocumentChildren(String docId) throws FileNotFoundException {
-        final MatrixCursor result = new MatrixCursor(SUPPORTED_COLUMNS);
-        final File parent = getFileForDocId(docId);
+    public Cursor queryChildDocuments(
+            String parentDocumentId, String[] projection, String sortOrder)
+            throws FileNotFoundException {
+        final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
+        final File parent = getFileForDocId(parentDocumentId);
         for (File file : parent.listFiles()) {
             includeFile(result, null, file);
         }
@@ -239,9 +261,10 @@
     }
 
     @Override
-    public Cursor querySearch(String docId, String query) throws FileNotFoundException {
-        final MatrixCursor result = new MatrixCursor(SUPPORTED_COLUMNS);
-        final File parent = getFileForDocId(docId);
+    public Cursor querySearchDocuments(String parentDocumentId, String query, String[] projection)
+            throws FileNotFoundException {
+        final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
+        final File parent = getFileForDocId(parentDocumentId);
 
         final LinkedList<File> pending = new LinkedList<File>();
         pending.add(parent);
@@ -261,22 +284,24 @@
     }
 
     @Override
-    public String getType(String docId) throws FileNotFoundException {
-        final File file = getFileForDocId(docId);
+    public String getDocumentType(String documentId) throws FileNotFoundException {
+        final File file = getFileForDocId(documentId);
         return getTypeForFile(file);
     }
 
     @Override
-    public ParcelFileDescriptor openDocument(String docId, String mode, CancellationSignal signal)
+    public ParcelFileDescriptor openDocument(
+            String documentId, String mode, CancellationSignal signal)
             throws FileNotFoundException {
-        final File file = getFileForDocId(docId);
+        final File file = getFileForDocId(documentId);
         return ParcelFileDescriptor.open(file, ContentResolver.modeToMode(null, mode));
     }
 
     @Override
     public AssetFileDescriptor openDocumentThumbnail(
-            String docId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
-        final File file = getFileForDocId(docId);
+            String documentId, Point sizeHint, CancellationSignal signal)
+            throws FileNotFoundException {
+        final File file = getFileForDocId(documentId);
         final ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
                 file, ParcelFileDescriptor.MODE_READ_ONLY);
 
@@ -294,7 +319,7 @@
 
     private static String getTypeForFile(File file) {
         if (file.isDirectory()) {
-            return Documents.MIME_TYPE_DIR;
+            return Document.MIME_TYPE_DIR;
         } else {
             return getTypeForName(file.getName());
         }
@@ -314,7 +339,7 @@
     }
 
     private static String validateDisplayName(String mimeType, String displayName) {
-        if (Documents.MIME_TYPE_DIR.equals(mimeType)) {
+        if (Document.MIME_TYPE_DIR.equals(mimeType)) {
             return displayName;
         } else {
             // Try appending meaningful extension if needed
diff --git a/services/java/com/android/server/accessibility/TouchExplorer.java b/services/java/com/android/server/accessibility/TouchExplorer.java
index 8fb3998..1b8876d 100644
--- a/services/java/com/android/server/accessibility/TouchExplorer.java
+++ b/services/java/com/android/server/accessibility/TouchExplorer.java
@@ -417,9 +417,6 @@
                     mSendTouchInteractionEndDelayed.forceSendAndRemove();
                 }
 
-                // Cache the event until we discern exploration from gesturing.
-                mSendHoverEnterAndMoveDelayed.addEvent(event);
-
                 // If we have the first tap, schedule a long press and break
                 // since we do not want to schedule hover enter because
                 // the delayed callback will kick in before the long click.
@@ -432,11 +429,16 @@
                     break;
                 }
                 if (!mTouchExplorationInProgress) {
-                    // Deliver hover enter with a delay to have a chance
-                    // to detect what the user is trying to do.
-                    final int pointerId = receivedTracker.getPrimaryPointerId();
-                    final int pointerIdBits = (1 << pointerId);
-                    mSendHoverEnterAndMoveDelayed.post(event, true, pointerIdBits, policyFlags);
+                    if (!mSendHoverEnterAndMoveDelayed.isPending()) {
+                        // Deliver hover enter with a delay to have a chance
+                        // to detect what the user is trying to do.
+                        final int pointerId = receivedTracker.getPrimaryPointerId();
+                        final int pointerIdBits = (1 << pointerId);
+                        mSendHoverEnterAndMoveDelayed.post(event, true, pointerIdBits,
+                                policyFlags);
+                    }
+                    // Cache the event until we discern exploration from gesturing.
+                    mSendHoverEnterAndMoveDelayed.addEvent(event);
                 }
             } break;
             case MotionEvent.ACTION_POINTER_DOWN: {
@@ -1719,7 +1721,7 @@
                 } break;
             }
             if (DEBUG) {
-                Slog.i(LOG_TAG_RECEIVED_POINTER_TRACKER, "Received pointer: " + toString());
+                Slog.i(LOG_TAG_RECEIVED_POINTER_TRACKER, "Received pointer:\n" + toString());
             }
         }
 
@@ -1777,7 +1779,7 @@
          */
         public int getPrimaryPointerId() {
             if (mPrimaryPointerId == INVALID_POINTER_ID) {
-                mPrimaryPointerId = findPrimaryPointer();
+                mPrimaryPointerId = findPrimaryPointerId();
             }
             return mPrimaryPointerId;
         }
@@ -1861,17 +1863,21 @@
         }
 
         /**
-         * @return The primary pointer.
+         * @return The primary pointer id.
          */
-        private int findPrimaryPointer() {
+        private int findPrimaryPointerId() {
             int primaryPointerId = INVALID_POINTER_ID;
             long minDownTime = Long.MAX_VALUE;
+
             // Find the pointer that went down first.
-            for (int i = 0, count = mReceivedPointerDownTime.length; i < count; i++) {
-                final long downPointerTime = mReceivedPointerDownTime[i];
+            int pointerIdBits = mReceivedPointersDown;
+            while (pointerIdBits > 0) {
+                final int pointerId = Integer.numberOfTrailingZeros(pointerIdBits);
+                pointerIdBits &= ~(1 << pointerId);
+                final long downPointerTime = mReceivedPointerDownTime[pointerId];
                 if (downPointerTime < minDownTime) {
                     minDownTime = downPointerTime;
-                    primaryPointerId = i;
+                    primaryPointerId = pointerId;
                 }
             }
             return primaryPointerId;