Merge changes from topic 'no_internet'

* changes:
  Add a noInternetAccessExpected boolean to WifiConfiguration.
  Prompt if a network without an Internet connection is selected
  Give SystemUI the OVERRIDE_WIFI_CONFIG permission.
  Add an UNKNOWN_UID constant to WifiConfiguration.
diff --git a/api/current.txt b/api/current.txt
index 5346895..b307d61 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -13774,6 +13774,7 @@
     field public static final int FINGERPRINT_ACQUIRED_VENDOR_BASE = 1000; // 0x3e8
     field public static final int FINGERPRINT_ERROR_CANCELED = 5; // 0x5
     field public static final int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1; // 0x1
+    field public static final int FINGERPRINT_ERROR_LOCKOUT = 7; // 0x7
     field public static final int FINGERPRINT_ERROR_NO_SPACE = 4; // 0x4
     field public static final int FINGERPRINT_ERROR_TIMEOUT = 3; // 0x3
     field public static final int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2; // 0x2
@@ -17809,6 +17810,7 @@
     method public final int getType();
     method public final float getVideoFrameRate();
     method public final int getVideoHeight();
+    method public final float getVideoPixelAspectRatio();
     method public final int getVideoWidth();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.media.tv.TvTrackInfo> CREATOR;
@@ -17827,6 +17829,7 @@
     method public final android.media.tv.TvTrackInfo.Builder setLanguage(java.lang.String);
     method public final android.media.tv.TvTrackInfo.Builder setVideoFrameRate(float);
     method public final android.media.tv.TvTrackInfo.Builder setVideoHeight(int);
+    method public final android.media.tv.TvTrackInfo.Builder setVideoPixelAspectRatio(float);
     method public final android.media.tv.TvTrackInfo.Builder setVideoWidth(int);
   }
 
@@ -18203,6 +18206,7 @@
     method public int describeContents();
     method public java.net.InetAddress[] getAllByName(java.lang.String) throws java.net.UnknownHostException;
     method public java.net.InetAddress getByName(java.lang.String) throws java.net.UnknownHostException;
+    method public long getNetworkHandle();
     method public javax.net.SocketFactory getSocketFactory();
     method public java.net.URLConnection openConnection(java.net.URL) throws java.io.IOException;
     method public java.net.URLConnection openConnection(java.net.URL, java.net.Proxy) throws java.io.IOException;
@@ -39806,6 +39810,7 @@
     method public boolean getOverlapAnchor();
     method public int getSoftInputMode();
     method public int getWidth();
+    method public int getWindowLayoutType();
     method public boolean isAboveAnchor();
     method public boolean isAttachedInDecor();
     method public boolean isClippingEnabled();
@@ -39835,6 +39840,7 @@
     method public void setTouchable(boolean);
     method public void setWidth(int);
     method public deprecated void setWindowLayoutMode(int, int);
+    method public void setWindowLayoutType(int);
     method public void showAsDropDown(android.view.View);
     method public void showAsDropDown(android.view.View, int, int);
     method public void showAsDropDown(android.view.View, int, int, int);
diff --git a/api/system-current.txt b/api/system-current.txt
index c7db72b..9184d8b 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -14070,6 +14070,7 @@
     field public static final int FINGERPRINT_ACQUIRED_VENDOR_BASE = 1000; // 0x3e8
     field public static final int FINGERPRINT_ERROR_CANCELED = 5; // 0x5
     field public static final int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1; // 0x1
+    field public static final int FINGERPRINT_ERROR_LOCKOUT = 7; // 0x7
     field public static final int FINGERPRINT_ERROR_NO_SPACE = 4; // 0x4
     field public static final int FINGERPRINT_ERROR_TIMEOUT = 3; // 0x3
     field public static final int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2; // 0x2
@@ -19260,6 +19261,7 @@
     method public final int getType();
     method public final float getVideoFrameRate();
     method public final int getVideoHeight();
+    method public final float getVideoPixelAspectRatio();
     method public final int getVideoWidth();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.media.tv.TvTrackInfo> CREATOR;
@@ -19278,6 +19280,7 @@
     method public final android.media.tv.TvTrackInfo.Builder setLanguage(java.lang.String);
     method public final android.media.tv.TvTrackInfo.Builder setVideoFrameRate(float);
     method public final android.media.tv.TvTrackInfo.Builder setVideoHeight(int);
+    method public final android.media.tv.TvTrackInfo.Builder setVideoPixelAspectRatio(float);
     method public final android.media.tv.TvTrackInfo.Builder setVideoWidth(int);
   }
 
@@ -19661,6 +19664,7 @@
     method public int describeContents();
     method public java.net.InetAddress[] getAllByName(java.lang.String) throws java.net.UnknownHostException;
     method public java.net.InetAddress getByName(java.lang.String) throws java.net.UnknownHostException;
+    method public long getNetworkHandle();
     method public javax.net.SocketFactory getSocketFactory();
     method public java.net.URLConnection openConnection(java.net.URL) throws java.io.IOException;
     method public java.net.URLConnection openConnection(java.net.URL, java.net.Proxy) throws java.io.IOException;
@@ -42731,6 +42735,7 @@
     method public boolean getOverlapAnchor();
     method public int getSoftInputMode();
     method public int getWidth();
+    method public int getWindowLayoutType();
     method public boolean isAboveAnchor();
     method public boolean isAttachedInDecor();
     method public boolean isClippingEnabled();
@@ -42760,6 +42765,7 @@
     method public void setTouchable(boolean);
     method public void setWidth(int);
     method public deprecated void setWindowLayoutMode(int, int);
+    method public void setWindowLayoutType(int);
     method public void showAsDropDown(android.view.View);
     method public void showAsDropDown(android.view.View, int, int);
     method public void showAsDropDown(android.view.View, int, int, int);
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 5f85e7d..b757a9a 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -56,7 +56,7 @@
     private static final boolean DEBUG = true;
     private static final int MSG_ENROLL_RESULT = 100;
     private static final int MSG_ACQUIRED = 101;
-    private static final int MSG_PROCESSED = 102;
+    private static final int MSG_AUTHENTICATED = 102;
     private static final int MSG_ERROR = 103;
     private static final int MSG_REMOVED = 104;
 
@@ -103,6 +103,11 @@
      */
     public static final int FINGERPRINT_ERROR_UNABLE_TO_REMOVE = 6;
 
+   /**
+     * The operation was canceled because the API is locked out due to too many attempts.
+     */
+    public static final int FINGERPRINT_ERROR_LOCKOUT = 7;
+
     /**
      * Hardware vendors may extend this list if there are conditions that do not fall under one of
      * the above categories. Vendors are responsible for providing error strings for these errors.
@@ -169,15 +174,9 @@
     private Fingerprint mRemovalFingerprint;
 
     private class OnEnrollCancelListener implements OnCancelListener {
-        private long mChallenge;
-
-        public OnEnrollCancelListener(long challenge) {
-            mChallenge = challenge;
-        }
-
         @Override
         public void onCancel() {
-            cancelEnrollment(mChallenge);
+            cancelEnrollment();
         }
     }
 
@@ -437,14 +436,14 @@
      * {@link EnrollmentCallback#onEnrollmentProgress(int) is called with remaining == 0, at
      * which point the object is no longer valid. The operation can be canceled by using the
      * provided cancel object.
-     * @param challenge a unique id provided by a recent verification of device credentials
-     *     (e.g. pin, pattern or password).
+     * @param token a unique token provided by a recent creation or verification of device
+     * credentials (e.g. pin, pattern or password).
      * @param cancel an object that can be used to cancel enrollment
      * @param callback an object to receive enrollment events
      * @param flags optional flags
      * @hide
      */
-    public void enroll(long challenge, CancellationSignal cancel, EnrollmentCallback callback,
+    public void enroll(byte [] token, CancellationSignal cancel, EnrollmentCallback callback,
             int flags) {
         if (callback == null) {
             throw new IllegalArgumentException("Must supply an enrollment callback");
@@ -455,13 +454,13 @@
                 Log.w(TAG, "enrollment already canceled");
                 return;
             } else {
-                cancel.setOnCancelListener(new OnEnrollCancelListener(challenge));
+                cancel.setOnCancelListener(new OnEnrollCancelListener());
             }
         }
 
         if (mService != null) try {
             mEnrollmentCallback = callback;
-            mService.enroll(mToken, challenge, getCurrentUserId(), mServiceReceiver, flags);
+            mService.enroll(mToken, token, getCurrentUserId(), mServiceReceiver, flags);
         } catch (RemoteException e) {
             Log.w(TAG, "Remote exception in enroll: ", e);
             if (callback != null) {
@@ -574,8 +573,8 @@
                 case MSG_ACQUIRED:
                     sendAcquiredResult((Long) msg.obj /* deviceId */, msg.arg1 /* acquire info */);
                     break;
-                case MSG_PROCESSED:
-                    sendProcessedResult((Fingerprint) msg.obj);
+                case MSG_AUTHENTICATED:
+                    sendAuthenticatedResult((Fingerprint) msg.obj);
                     break;
                 case MSG_ERROR:
                     sendErrorResult((Long) msg.obj /* deviceId */, msg.arg1 /* errMsgId */);
@@ -617,7 +616,7 @@
             }
         }
 
-        private void sendProcessedResult(Fingerprint fp) {
+        private void sendAuthenticatedResult(Fingerprint fp) {
             if (mAuthenticationCallback != null) {
                 if (fp.getFingerId() == 0 && fp.getGroupId() == 0) {
                     // Fingerprint template valid but doesn't match one in database
@@ -667,7 +666,7 @@
         mRemovalCallback = null;
     }
 
-    private void cancelEnrollment(long challenge) {
+    private void cancelEnrollment() {
         if (mService != null) try {
             mService.cancelEnrollment(mToken);
         } catch (RemoteException e) {
@@ -695,8 +694,11 @@
                 return mContext.getString(
                     com.android.internal.R.string.fingerprint_error_no_space);
             case FINGERPRINT_ERROR_TIMEOUT:
-                return mContext.getString(
-                    com.android.internal.R.string.fingerprint_error_timeout);
+                return mContext.getString(com.android.internal.R.string.fingerprint_error_timeout);
+            case FINGERPRINT_ERROR_CANCELED:
+                return mContext.getString(com.android.internal.R.string.fingerprint_error_canceled);
+            case FINGERPRINT_ERROR_LOCKOUT:
+                return mContext.getString(com.android.internal.R.string.fingerprint_error_lockout);
             default:
                 if (errMsg >= FINGERPRINT_ERROR_VENDOR_BASE) {
                     int msgNumber = errMsg - FINGERPRINT_ERROR_VENDOR_BASE;
@@ -753,8 +755,8 @@
             mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, 0, deviceId).sendToTarget();
         }
 
-        public void onProcessed(long deviceId, int fingerId, int groupId) {
-            mHandler.obtainMessage(MSG_PROCESSED,
+        public void onAuthenticated(long deviceId, int fingerId, int groupId) {
+            mHandler.obtainMessage(MSG_AUTHENTICATED,
                     new Fingerprint(null, groupId, fingerId, deviceId)).sendToTarget();
         }
 
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index 2fcb20e..6fe72d5 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -33,7 +33,7 @@
     void cancelAuthentication(IBinder token);
 
     // Start fingerprint enrollment
-    void enroll(IBinder token, long challenge, int groupId, IFingerprintServiceReceiver receiver,
+    void enroll(IBinder token, in byte [] cryptoToken, int groupId, IFingerprintServiceReceiver receiver,
             int flags);
 
     // Cancel enrollment in progress
diff --git a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
index e82395f..a2d74b8d 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
@@ -25,7 +25,7 @@
 oneway interface IFingerprintServiceReceiver {
     void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining);
     void onAcquired(long deviceId, int acquiredInfo);
-    void onProcessed(long deviceId, int fingerId, int groupId);
+    void onAuthenticated(long deviceId, int fingerId, int groupId);
     void onError(long deviceId, int error);
     void onRemoved(long deviceId, int fingerId, int groupId);
 }
diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java
index 65d325a1..67ecb5d 100644
--- a/core/java/android/net/Network.java
+++ b/core/java/android/net/Network.java
@@ -340,6 +340,35 @@
         }
     }
 
+    /**
+     * Returns a handle representing this {@code Network}, for use with the NDK API.
+     */
+    public long getNetworkHandle() {
+        // The network handle is explicitly not the same as the netId.
+        //
+        // The netId is an implementation detail which might be changed in the
+        // future, or which alone (i.e. in the absence of some additional
+        // context) might not be sufficient to fully identify a Network.
+        //
+        // As such, the intention is to prevent accidental misuse of the API
+        // that might result if a developer assumed that handles and netIds
+        // were identical and passing a netId to a call expecting a handle
+        // "just worked".  Such accidental misuse, if widely deployed, might
+        // prevent future changes to the semantics of the netId field or
+        // inhibit the expansion of state required for Network objects.
+        //
+        // This extra layer of indirection might be seen as paranoia, and might
+        // never end up being necessary, but the added complexity is trivial.
+        // At some future date it may be desirable to realign the handle with
+        // Multiple Provisioning Domains API recommendations, as made by the
+        // IETF mif working group.
+        //
+        // The HANDLE_MAGIC value MUST be kept in sync with the corresponding
+        // value in the native/android/net.c NDK implementation.
+        final long HANDLE_MAGIC = 0xfacade;
+        return (((long) netId) << 32) | HANDLE_MAGIC;
+    }
+
     // implement the Parcelable interface
     public int describeContents() {
         return 0;
diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
index 00b2ee3..f10e530 100644
--- a/core/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -40,6 +40,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -262,7 +263,7 @@
      * for that category.
      * @return List of AIDs registered by the service
      */
-    public ArrayList<String> getAids() {
+    public List<String> getAids() {
         final ArrayList<String> aids = new ArrayList<String>();
         for (AidGroup group : getAidGroups()) {
             aids.addAll(group.aids);
@@ -270,6 +271,18 @@
         return aids;
     }
 
+    public List<String> getPrefixAids() {
+        final ArrayList<String> prefixAids = new ArrayList<String>();
+        for (AidGroup group : getAidGroups()) {
+            for (String aid : group.aids) {
+                if (aid.endsWith("*")) {
+                    prefixAids.add(aid);
+                }
+            }
+        }
+        return prefixAids;
+    }
+
     /**
      * Returns the registered AID group for this category.
      */
diff --git a/core/java/android/os/storage/DiskInfo.java b/core/java/android/os/storage/DiskInfo.java
index e6160aa..dc96640 100644
--- a/core/java/android/os/storage/DiskInfo.java
+++ b/core/java/android/os/storage/DiskInfo.java
@@ -16,6 +16,7 @@
 
 package android.os.storage;
 
+import android.annotation.NonNull;
 import android.content.res.Resources;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -59,6 +60,10 @@
         volumeIds = parcel.readStringArray();
     }
 
+    public @NonNull String getId() {
+        return id;
+    }
+
     public String getDescription() {
         // TODO: splice vendor label into these strings
         if ((flags & FLAG_SD) != 0) {
diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java
index 10ffd48..0a8187e 100644
--- a/core/java/android/os/storage/IMountService.java
+++ b/core/java/android/os/storage/IMountService.java
@@ -923,12 +923,13 @@
             }
 
             @Override
-            public VolumeInfo[] getVolumes() throws RemoteException {
+            public VolumeInfo[] getVolumes(int _flags) throws RemoteException {
                 Parcel _data = Parcel.obtain();
                 Parcel _reply = Parcel.obtain();
                 VolumeInfo[] _result;
                 try {
                     _data.writeInterfaceToken(DESCRIPTOR);
+                    _data.writeInt(_flags);
                     mRemote.transact(Stub.TRANSACTION_getVolumes, _data, _reply, 0);
                     _reply.readException();
                     _result = _reply.createTypedArray(VolumeInfo.CREATOR);
@@ -1029,6 +1030,39 @@
                     _data.recycle();
                 }
             }
+
+            @Override
+            public void setVolumeNickname(String volId, String nickname) throws RemoteException {
+                Parcel _data = Parcel.obtain();
+                Parcel _reply = Parcel.obtain();
+                try {
+                    _data.writeInterfaceToken(DESCRIPTOR);
+                    _data.writeString(volId);
+                    _data.writeString(nickname);
+                    mRemote.transact(Stub.TRANSACTION_setVolumeNickname, _data, _reply, 0);
+                    _reply.readException();
+                } finally {
+                    _reply.recycle();
+                    _data.recycle();
+                }
+            }
+
+            @Override
+            public void setVolumeUserFlags(String volId, int flags, int mask) throws RemoteException {
+                Parcel _data = Parcel.obtain();
+                Parcel _reply = Parcel.obtain();
+                try {
+                    _data.writeInterfaceToken(DESCRIPTOR);
+                    _data.writeString(volId);
+                    _data.writeInt(flags);
+                    _data.writeInt(mask);
+                    mRemote.transact(Stub.TRANSACTION_setVolumeUserFlags, _data, _reply, 0);
+                    _reply.readException();
+                } finally {
+                    _reply.recycle();
+                    _data.recycle();
+                }
+            }
         }
 
         private static final String DESCRIPTOR = "IMountService";
@@ -1132,6 +1166,9 @@
         static final int TRANSACTION_partitionPrivate = IBinder.FIRST_CALL_TRANSACTION + 50;
         static final int TRANSACTION_partitionMixed = IBinder.FIRST_CALL_TRANSACTION + 51;
 
+        static final int TRANSACTION_setVolumeNickname = IBinder.FIRST_CALL_TRANSACTION + 52;
+        static final int TRANSACTION_setVolumeUserFlags = IBinder.FIRST_CALL_TRANSACTION + 53;
+
         /**
          * Cast an IBinder object into an IMountService interface, generating a
          * proxy if needed.
@@ -1566,7 +1603,8 @@
                 }
                 case TRANSACTION_getVolumes: {
                     data.enforceInterface(DESCRIPTOR);
-                    VolumeInfo[] volumes = getVolumes();
+                    int _flags = data.readInt();
+                    VolumeInfo[] volumes = getVolumes(_flags);
                     reply.writeNoException();
                     reply.writeTypedArray(volumes, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                     return true;
@@ -1614,6 +1652,23 @@
                     reply.writeNoException();
                     return true;
                 }
+                case TRANSACTION_setVolumeNickname: {
+                    data.enforceInterface(DESCRIPTOR);
+                    String volId = data.readString();
+                    String nickname = data.readString();
+                    setVolumeNickname(volId, nickname);
+                    reply.writeNoException();
+                    return true;
+                }
+                case TRANSACTION_setVolumeUserFlags: {
+                    data.enforceInterface(DESCRIPTOR);
+                    String volId = data.readString();
+                    int _flags = data.readInt();
+                    int _mask = data.readInt();
+                    setVolumeUserFlags(volId, _flags, _mask);
+                    reply.writeNoException();
+                    return true;
+                }
             }
             return super.onTransact(code, data, reply, flags);
         }
@@ -1902,7 +1957,7 @@
     public void waitForAsecScan() throws RemoteException;
 
     public DiskInfo[] getDisks() throws RemoteException;
-    public VolumeInfo[] getVolumes() throws RemoteException;
+    public VolumeInfo[] getVolumes(int flags) throws RemoteException;
 
     public void mount(String volId) throws RemoteException;
     public void unmount(String volId) throws RemoteException;
@@ -1911,4 +1966,7 @@
     public void partitionPublic(String diskId) throws RemoteException;
     public void partitionPrivate(String diskId) throws RemoteException;
     public void partitionMixed(String diskId, int ratio) throws RemoteException;
+
+    public void setVolumeNickname(String volId, String nickname) throws RemoteException;
+    public void setVolumeUserFlags(String volId, int flags, int mask) throws RemoteException;
 }
diff --git a/core/java/android/os/storage/IMountServiceListener.java b/core/java/android/os/storage/IMountServiceListener.java
index 3965f9d..fd914bc 100644
--- a/core/java/android/os/storage/IMountServiceListener.java
+++ b/core/java/android/os/storage/IMountServiceListener.java
@@ -91,6 +91,13 @@
                     reply.writeNoException();
                     return true;
                 }
+                case TRANSACTION_onVolumeMetadataChanged: {
+                    data.enforceInterface(DESCRIPTOR);
+                    final VolumeInfo vol = (VolumeInfo) data.readParcelable(null);
+                    onVolumeMetadataChanged(vol);
+                    reply.writeNoException();
+                    return true;
+                }
             }
             return super.onTransact(code, data, reply, flags);
         }
@@ -175,6 +182,22 @@
                     _data.recycle();
                 }
             }
+
+            @Override
+            public void onVolumeMetadataChanged(VolumeInfo vol) throws RemoteException {
+                Parcel _data = Parcel.obtain();
+                Parcel _reply = Parcel.obtain();
+                try {
+                    _data.writeInterfaceToken(DESCRIPTOR);
+                    _data.writeParcelable(vol, 0);
+                    mRemote.transact(Stub.TRANSACTION_onVolumeMetadataChanged, _data, _reply,
+                            android.os.IBinder.FLAG_ONEWAY);
+                    _reply.readException();
+                } finally {
+                    _reply.recycle();
+                    _data.recycle();
+                }
+            }
         }
 
         static final int TRANSACTION_onUsbMassStorageConnectionChanged = (IBinder.FIRST_CALL_TRANSACTION + 0);
@@ -182,6 +205,7 @@
         static final int TRANSACTION_onStorageStateChanged = (IBinder.FIRST_CALL_TRANSACTION + 1);
 
         static final int TRANSACTION_onVolumeStateChanged = (IBinder.FIRST_CALL_TRANSACTION + 2);
+        static final int TRANSACTION_onVolumeMetadataChanged = (IBinder.FIRST_CALL_TRANSACTION + 3);
     }
 
     /**
@@ -204,4 +228,6 @@
 
     public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState)
             throws RemoteException;
+
+    public void onVolumeMetadataChanged(VolumeInfo vol) throws RemoteException;
 }
diff --git a/core/java/android/os/storage/StorageEventListener.java b/core/java/android/os/storage/StorageEventListener.java
index 29d5494..28a187d 100644
--- a/core/java/android/os/storage/StorageEventListener.java
+++ b/core/java/android/os/storage/StorageEventListener.java
@@ -40,4 +40,7 @@
 
     public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
     }
+
+    public void onVolumeMetadataChanged(VolumeInfo vol) {
+    }
 }
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index eb77477..0e977ff 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -70,6 +70,9 @@
     /** {@hide} */
     public static final String PROP_PRIMARY_PHYSICAL = "ro.vold.primary_physical";
 
+    /** {@hide} */
+    public static final int FLAG_ALL_METADATA = 1 << 0;
+
     private final Context mContext;
     private final ContentResolver mResolver;
 
@@ -83,6 +86,7 @@
             Handler.Callback {
         private static final int MSG_STORAGE_STATE_CHANGED = 1;
         private static final int MSG_VOLUME_STATE_CHANGED = 2;
+        private static final int MSG_VOLUME_METADATA_CHANGED = 3;
 
         final StorageEventListener mCallback;
         final Handler mHandler;
@@ -105,6 +109,10 @@
                     mCallback.onVolumeStateChanged((VolumeInfo) args.arg1, args.argi2, args.argi3);
                     args.recycle();
                     return true;
+                case MSG_VOLUME_METADATA_CHANGED:
+                    mCallback.onVolumeMetadataChanged((VolumeInfo) args.arg1);
+                    args.recycle();
+                    return true;
             }
             args.recycle();
             return false;
@@ -132,6 +140,13 @@
             args.argi3 = newState;
             mHandler.obtainMessage(MSG_VOLUME_STATE_CHANGED, args).sendToTarget();
         }
+
+        @Override
+        public void onVolumeMetadataChanged(VolumeInfo vol) {
+            final SomeArgs args = SomeArgs.obtain();
+            args.arg1 = vol;
+            mHandler.obtainMessage(MSG_VOLUME_METADATA_CHANGED, args).sendToTarget();
+        }
     }
 
     /**
@@ -480,8 +495,13 @@
 
     /** {@hide} */
     public @NonNull List<VolumeInfo> getVolumes() {
+        return getVolumes(0);
+    }
+
+    /** {@hide} */
+    public @NonNull List<VolumeInfo> getVolumes(int flags) {
         try {
-            return Arrays.asList(mMountService.getVolumes());
+            return Arrays.asList(mMountService.getVolumes(flags));
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
@@ -556,6 +576,35 @@
     }
 
     /** {@hide} */
+    public void setVolumeNickname(String volId, String nickname) {
+        try {
+            mMountService.setVolumeNickname(volId, nickname);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /** {@hide} */
+    public void setVolumeInited(String volId, boolean inited) {
+        try {
+            mMountService.setVolumeUserFlags(volId, inited ? VolumeInfo.USER_FLAG_INITED : 0,
+                    VolumeInfo.USER_FLAG_INITED);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /** {@hide} */
+    public void setVolumeSnoozed(String volId, boolean snoozed) {
+        try {
+            mMountService.setVolumeUserFlags(volId, snoozed ? VolumeInfo.USER_FLAG_SNOOZED : 0,
+                    VolumeInfo.USER_FLAG_SNOOZED);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /** {@hide} */
     public @Nullable StorageVolume getStorageVolume(File file) {
         return getStorageVolume(getVolumeList(), file);
     }
diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java
index fe1e206..f06fc8c 100644
--- a/core/java/android/os/storage/VolumeInfo.java
+++ b/core/java/android/os/storage/VolumeInfo.java
@@ -71,6 +71,9 @@
     public static final int FLAG_PRIMARY = 1 << 0;
     public static final int FLAG_VISIBLE = 1 << 1;
 
+    public static final int USER_FLAG_INITED = 1 << 0;
+    public static final int USER_FLAG_SNOOZED = 1 << 1;
+
     private static SparseArray<String> sStateToEnvironment = new SparseArray<>();
     private static ArrayMap<String, String> sEnvironmentToBroadcast = new ArrayMap<>();
 
@@ -104,8 +107,9 @@
 
     /** Framework state */
     public final int mtpIndex;
-    public String nickname;
     public String diskId;
+    public String nickname;
+    public int userFlags = 0;
 
     public VolumeInfo(String id, int type, int mtpIndex) {
         this.id = Preconditions.checkNotNull(id);
@@ -124,8 +128,9 @@
         fsLabel = parcel.readString();
         path = parcel.readString();
         mtpIndex = parcel.readInt();
-        nickname = parcel.readString();
         diskId = parcel.readString();
+        nickname = parcel.readString();
+        userFlags = parcel.readInt();
     }
 
     public static @NonNull String getEnvironmentForState(int state) {
@@ -145,6 +150,30 @@
         return getBroadcastForEnvironment(getEnvironmentForState(state));
     }
 
+    public @NonNull String getId() {
+        return id;
+    }
+
+    public @Nullable String getDiskId() {
+        return diskId;
+    }
+
+    public int getType() {
+        return type;
+    }
+
+    public int getState() {
+        return state;
+    }
+
+    public @Nullable String getFsUuid() {
+        return fsUuid;
+    }
+
+    public @Nullable String getNickname() {
+        return nickname;
+    }
+
     public @Nullable String getDescription() {
         if (ID_PRIVATE_INTERNAL.equals(id)) {
             return Resources.getSystem().getString(com.android.internal.R.string.storage_internal);
@@ -165,6 +194,14 @@
         return (flags & FLAG_VISIBLE) != 0;
     }
 
+    public boolean isInited() {
+        return (userFlags & USER_FLAG_INITED) != 0;
+    }
+
+    public boolean isSnoozed() {
+        return (userFlags & USER_FLAG_SNOOZED) != 0;
+    }
+
     public boolean isVisibleToUser(int userId) {
         if (type == TYPE_PUBLIC && userId == this.userId) {
             return isVisible();
@@ -175,6 +212,10 @@
         }
     }
 
+    public File getPath() {
+        return new File(path);
+    }
+
     public File getPathForUser(int userId) {
         if (path == null) {
             return null;
@@ -284,8 +325,9 @@
         pw.println();
         pw.printPair("path", path);
         pw.printPair("mtpIndex", mtpIndex);
-        pw.printPair("nickname", nickname);
         pw.printPair("diskId", diskId);
+        pw.printPair("nickname", nickname);
+        pw.printPair("userFlags", DebugUtils.flagsToString(getClass(), "USER_FLAG_", userFlags));
         pw.decreaseIndent();
         pw.println();
     }
@@ -331,7 +373,8 @@
         parcel.writeString(fsLabel);
         parcel.writeString(path);
         parcel.writeInt(mtpIndex);
-        parcel.writeString(nickname);
         parcel.writeString(diskId);
+        parcel.writeString(nickname);
+        parcel.writeInt(userFlags);
     }
 }
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 2bcb352..7828851 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -811,7 +811,7 @@
                 float sum = 0;
                 int i;
 
-                for (i = len; i >= 0; i--) {
+                for (i = len; i > 0; i--) {
                     float w = widths[i - 1 + lineStart - widthStart];
 
                     if (w + sum + ellipsisWidth > avail) {
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 8792323..c5b5c84 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -860,19 +860,22 @@
     }
 
     /**
-     * Set the layout type for this window. Should be one of the TYPE constants defined in
-     * {@link WindowManager.LayoutParams}.
+     * Set the layout type for this window. This value will be passed through to
+     * {@link WindowManager.LayoutParams#type} therefore the value should match any value
+     * {@link WindowManager.LayoutParams#type} accepts.
      *
      * @param layoutType Layout type for this window.
-     * @hide
+     *
+     * @see WindowManager.LayoutParams#type
      */
     public void setWindowLayoutType(int layoutType) {
         mWindowLayoutType = layoutType;
     }
 
     /**
-     * @return The layout type for this window.
-     * @hide
+     * Returns the layout type for this window.
+     *
+     * @see #setWindowLayoutType(int)
      */
     public int getWindowLayoutType() {
         return mWindowLayoutType;
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d6886bd..9ec5221 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1242,23 +1242,26 @@
     <!-- Message shown during fingerprint acquisision when the fingerprint sensor needs cleaning -->
     <string name="fingerprint_acquired_imager_dirty">Fingerprint sensor is dirty. Please clean and try again.</string>
     <!-- Message shown during fingerprint acquisision when the user removes their finger from the sensor too quickly -->
-    <string name="fingerprint_acquired_too_fast">Finger moved to fast. Please try again.</string>
+    <string name="fingerprint_acquired_too_fast">Finger moved too fast. Please try again.</string>
     <!-- Message shown during fingerprint acquisision when the user moves their finger too slowly -->
     <string name="fingerprint_acquired_too_slow">Finger moved to slow. Please try again.</string>
     <!-- Array containing custom messages shown during fingerprint acquisision from vendor.  Vendor is expected to add and translate these strings -->
     <string-array name="fingerprint_acquired_vendor">
     </string-array>
 
-    <!-- Generic error message shown when the fingerprint hardware can't recognize the fingerprint -->
-    <string name="fingerprint_error_unable_to_process">Unable to process. Try again.</string>
     <!-- Error message shown when the fingerprint hardware can't be accessed -->
-    <string name="fingerprint_error_hw_not_available">Hardware not available.</string>
+    <string name="fingerprint_error_hw_not_available">Fingerprint hardware not available.</string>
     <!-- Error message shown when the fingerprint hardware has run out of room for storing fingerprints -->
     <string name="fingerprint_error_no_space">Fingerprint can\'t be stored. Please remove an existing fingerprint.</string>
     <!-- Error message shown when the fingerprint hardware timer has expired and the user needs to restart the operation. -->
     <string name="fingerprint_error_timeout">Fingerprint time out reached. Try again.</string>
-    <!-- Error message shown when the fingerprint hardware timer has expired and the user needs to restart the operation. -->
-    <string name="fingerprint_error_vendor">Fingerprint time out reached. Try again.</string>
+    <!-- Generic error message shown when the fingerprint operation (e.g. enrollment or authentication) is canceled. Generally not shown to the user-->
+    <string name="fingerprint_error_canceled">Fingerprint operation canceled.</string>
+    <!-- Generic error message shown when the fingerprint operation fails because too many attempts have been made. -->
+    <string name="fingerprint_error_lockout">Too many attempts. Try again later.</string>
+    <!-- Generic error message shown when the fingerprint hardware can't recognize the fingerprint -->
+    <string name="fingerprint_error_unable_to_process">Try again.</string>
+
     <!-- Array containing custom error messages from vendor.  Vendor is expected to add and translate these strings -->
     <string-array name="fingerprint_error_vendor">
     </string-array>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index fd75d01..e5b1cb5 100755
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2091,6 +2091,8 @@
   <java-symbol type="string" name="fingerprint_acquired_too_slow" />
   <java-symbol type="string" name="fingerprint_acquired_too_fast" />
   <java-symbol type="array" name="fingerprint_acquired_vendor" />
+  <java-symbol type="string" name="fingerprint_error_canceled" />
+  <java-symbol type="string" name="fingerprint_error_lockout" />
 
   <!-- From various Material changes -->
   <java-symbol type="attr" name="titleTextAppearance" />
diff --git a/data/keyboards/AVRCP.idc b/data/keyboards/AVRCP.idc
new file mode 100644
index 0000000..610b7f9
--- /dev/null
+++ b/data/keyboards/AVRCP.idc
@@ -0,0 +1,19 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# AVRCP
+#
+
+device.internal = 1
diff --git a/media/java/android/media/tv/TvTrackInfo.java b/media/java/android/media/tv/TvTrackInfo.java
index 0284171..8394517 100644
--- a/media/java/android/media/tv/TvTrackInfo.java
+++ b/media/java/android/media/tv/TvTrackInfo.java
@@ -48,11 +48,12 @@
     private final int mVideoWidth;
     private final int mVideoHeight;
     private final float mVideoFrameRate;
+    private final float mVideoPixelAspectRatio;
     private final Bundle mExtra;
 
     private TvTrackInfo(int type, String id, String language, String description,
             int audioChannelCount, int audioSampleRate, int videoWidth, int videoHeight,
-            float videoFrameRate, Bundle extra) {
+            float videoFrameRate, float videoPixelAspectRatio, Bundle extra) {
         mType = type;
         mId = id;
         mLanguage = language;
@@ -62,6 +63,7 @@
         mVideoWidth = videoWidth;
         mVideoHeight = videoHeight;
         mVideoFrameRate = videoFrameRate;
+        mVideoPixelAspectRatio = videoPixelAspectRatio;
         mExtra = extra;
     }
 
@@ -75,6 +77,7 @@
         mVideoWidth = in.readInt();
         mVideoHeight = in.readInt();
         mVideoFrameRate = in.readFloat();
+        mVideoPixelAspectRatio = in.readFloat();
         mExtra = in.readBundle();
     }
 
@@ -162,6 +165,17 @@
     }
 
     /**
+     * Returns the pixel aspect ratio (the ratio of a pixel's width to its height) of the video.
+     * Valid only for {@link #TYPE_VIDEO} tracks.
+     */
+    public final float getVideoPixelAspectRatio() {
+        if (mType != TYPE_VIDEO) {
+            throw new IllegalStateException("Not a video track");
+        }
+        return mVideoPixelAspectRatio;
+    }
+
+    /**
      * Returns the extra information about the current track.
      */
     public final Bundle getExtra() {
@@ -190,6 +204,7 @@
         dest.writeInt(mVideoWidth);
         dest.writeInt(mVideoHeight);
         dest.writeFloat(mVideoFrameRate);
+        dest.writeFloat(mVideoPixelAspectRatio);
         dest.writeBundle(mExtra);
     }
 
@@ -219,6 +234,7 @@
         private int mVideoWidth;
         private int mVideoHeight;
         private float mVideoFrameRate;
+        private float mVideoPixelAspectRatio = 1.0f;
         private Bundle mExtra;
 
         /**
@@ -332,6 +348,26 @@
         }
 
         /**
+         * Sets the pixel aspect ratio (the ratio of a pixel's width to its height) of the video.
+         * Valid only for {@link #TYPE_VIDEO} tracks.
+         * <p>
+         * This is needed for applications to be able to scale the video properly for some video
+         * formats such as 720x576 4:3 and 720x576 16:9 where pixels are not square. By default,
+         * applications assume the value of 1.0 (square pixels), so it is not necessary to set the
+         * pixel aspect ratio for most video formats.
+         * </p>
+         *
+         * @param videoPixelAspectRatio The pixel aspect ratio of the video.
+         */
+        public final Builder setVideoPixelAspectRatio(float videoPixelAspectRatio) {
+            if (mType != TYPE_VIDEO) {
+                throw new IllegalStateException("Not a video track");
+            }
+            mVideoPixelAspectRatio = videoPixelAspectRatio;
+            return this;
+        }
+
+        /**
          * Sets the extra information about the current track.
          *
          * @param extra The extra information.
@@ -348,7 +384,8 @@
          */
         public TvTrackInfo build() {
             return new TvTrackInfo(mType, mId, mLanguage, mDescription, mAudioChannelCount,
-                    mAudioSampleRate, mVideoWidth, mVideoHeight, mVideoFrameRate, mExtra);
+                    mAudioSampleRate, mVideoWidth, mVideoHeight, mVideoFrameRate,
+                    mVideoPixelAspectRatio, mExtra);
         }
     }
 }
diff --git a/native/android/Android.mk b/native/android/Android.mk
index b3a74a8..12fdf71 100644
--- a/native/android/Android.mk
+++ b/native/android/Android.mk
@@ -12,9 +12,10 @@
     looper.cpp \
     native_activity.cpp \
     native_window.cpp \
+    net.c \
     obb.cpp \
     sensor.cpp \
-    storage_manager.cpp
+    storage_manager.cpp \
 
 LOCAL_SHARED_LIBRARIES := \
     liblog \
@@ -25,14 +26,17 @@
     libbinder \
     libui \
     libgui \
-    libandroid_runtime
+    libandroid_runtime \
+    libnetd_client \
 
 LOCAL_STATIC_LIBRARIES := \
     libstorage
 
 LOCAL_C_INCLUDES += \
     frameworks/base/native/include \
-    frameworks/base/core/jni/android
+    frameworks/base/core/jni/android \
+    bionic/libc/dns/include \
+    system/netd/include \
 
 LOCAL_MODULE := libandroid
 
diff --git a/native/android/net.c b/native/android/net.c
new file mode 100644
index 0000000..de4b90c
--- /dev/null
+++ b/native/android/net.c
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#include <android/multinetwork.h>
+#include <errno.h>
+#include <NetdClient.h>    // the functions that communicate with netd
+#include <resolv_netid.h>  // android_getaddrinfofornet()
+#include <stdlib.h>
+#include <sys/limits.h>
+
+
+static int getnetidfromhandle(net_handle_t handle, unsigned *netid) {
+    static const uint32_t k32BitMask = 0xffffffff;
+    // This value MUST be kept in sync with the corresponding value in
+    // the android.net.Network#getNetworkHandle() implementation.
+    static const uint32_t kHandleMagic = 0xfacade;
+
+    // Check for minimum acceptable version of the API in the low bits.
+    if (handle != NETWORK_UNSPECIFIED &&
+        (handle & k32BitMask) != kHandleMagic) {
+        return 0;
+    }
+
+    if (netid != NULL) {
+        *netid = ((handle >> (CHAR_BIT * sizeof(k32BitMask))) & k32BitMask);
+    }
+    return 1;
+}
+
+
+int android_setsocknetwork(net_handle_t network, int fd) {
+    unsigned netid;
+    if (!getnetidfromhandle(network, &netid)) {
+        errno = EINVAL;
+        return -1;
+    }
+
+    int rval = setNetworkForSocket(netid, fd);
+    if (rval < 0) {
+        errno = -rval;
+        rval = -1;
+    }
+    return rval;
+}
+
+int android_setprocnetwork(net_handle_t network) {
+    unsigned netid;
+    if (!getnetidfromhandle(network, &netid)) {
+        errno = EINVAL;
+        return -1;
+    }
+
+    int rval = setNetworkForProcess(netid);
+    if (rval < 0) {
+        errno = -rval;
+        rval = -1;
+    }
+    return rval;
+}
+
+int android_getaddrinfofornetwork(net_handle_t network,
+        const char *node, const char *service,
+        const struct addrinfo *hints, struct addrinfo **res) {
+    unsigned netid;
+    if (!getnetidfromhandle(network, &netid)) {
+        errno = EINVAL;
+        return EAI_SYSTEM;
+    }
+
+    return android_getaddrinfofornet(node, service, hints, netid, 0, res);
+}
diff --git a/packages/DocumentsUI/res/layout/fragment_pick.xml b/packages/DocumentsUI/res/layout/fragment_pick.xml
index 5735871..87dc4f8 100644
--- a/packages/DocumentsUI/res/layout/fragment_pick.xml
+++ b/packages/DocumentsUI/res/layout/fragment_pick.xml
@@ -21,12 +21,19 @@
     android:baselineAligned="false"
     android:gravity="center_vertical"
     android:minHeight="?android:attr/listPreferredItemHeightSmall">
-
+    <Button
+        android:id="@android:id/button2"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_weight="1"
+        android:text="@android:string/cancel"
+        android:visibility="gone"
+        style="?android:attr/buttonBarNegativeButtonStyle" />
     <Button
         android:id="@android:id/button1"
-        android:layout_width="match_parent"
+        android:layout_width="wrap_content"
         android:layout_height="match_parent"
+        android:layout_weight="1"
         android:textAllCaps="false"
         style="?android:attr/buttonBarPositiveButtonStyle" />
-
 </LinearLayout>
diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml
index 3ca239a..062d433 100644
--- a/packages/DocumentsUI/res/values/strings.xml
+++ b/packages/DocumentsUI/res/values/strings.xml
@@ -65,6 +65,9 @@
     <!-- Menu item that hides the sizes of displayed files [CHAR LIMIT=24] -->
     <string name="menu_file_size_hide">Hide file size</string>
 
+    <!-- Button label that copies files to the current directory [CHAR LIMIT=48] -->
+    <string name="button_copy">Copy</string>
+
     <!-- Action mode title summarizing the number of documents selected [CHAR LIMIT=32] -->
     <string name="mode_selected_count"><xliff:g id="count" example="3">%1$d</xliff:g> selected</string>
 
@@ -112,8 +115,6 @@
     <!-- Title of dialog when prompting user to select an app to share documents with [CHAR LIMIT=32] -->
     <string name="share_via">Share via</string>
 
-    <!-- Title of the cancel button [CHAR LIMIT=24] -->
-    <string name="cancel">Cancel</string>
     <!-- Title of the copy notification [CHAR LIMIT=24] -->
     <string name="copy_notification_title">Copying files</string>
     <!-- Text shown on the copy notification to indicate remaining time, in minutes [CHAR LIMIT=24] -->
@@ -125,5 +126,17 @@
     </plurals>
     <!-- Text shown on the copy notification while DocumentsUI performs setup in preparation for copying files [CHAR LIMIT=32] -->
     <string name="copy_preparing">Preparing for copy\u2026</string>
-
+    <!-- Title of the copy error notification [CHAR LIMIT=48] -->
+    <plurals name="copy_error_notification_title">
+      <item quantity="one">Error copying <xliff:g id="count" example="1">%1$d</xliff:g> file.</item>
+      <item quantity="other">Error copying <xliff:g id="count" example="1">%1$d</xliff:g> files.</item>
+    </plurals>
+    <!-- Second line for notifications saying that more information will be shown after touching [CHAR LIMIT=48] -->
+    <string name="notification_touch_for_details">Touch to view details</string>
+    <!-- Label of a dialog button for retrying a failed operation [CHAR LIMIT=24] -->
+    <string name="retry">Retry</string>
+    <!-- Title of the copying failure alert dialog. [CHAR LIMIT=48] -->
+    <string name="copy_failure_alert_title">Error copying files</string>
+    <!-- Contents of the copying failure alert dialog. [CHAR LIMIT=48] -->
+    <string name="copy_failure_alert_content">Following files are not copied: <xliff:g id="list">%1$s</xliff:g></string>
 </resources>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
index bd17401..66792da 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
@@ -33,10 +33,6 @@
 import com.google.common.collect.Maps;
 
 abstract class BaseActivity extends Activity {
-    /** Intent action name to open copy destination. */
-    public static String ACTION_OPEN_COPY_DESTINATION_STRING =
-        "com.android.documentsui.OPEN_COPY_DESTINATION";
-
     public abstract State getDisplayState();
     public abstract RootInfo getCurrentRoot();
     public abstract void onStateChanged();
@@ -56,6 +52,18 @@
         return (BaseActivity) fragment.getActivity();
     }
 
+    public static abstract class DocumentsIntent {
+        /** Intent action name to open copy destination. */
+        public static String ACTION_OPEN_COPY_DESTINATION =
+                "com.android.documentsui.OPEN_COPY_DESTINATION";
+
+        /**
+         * Extra boolean flag for ACTION_OPEN_COPY_DESTINATION_STRING, which
+         * specifies if the destination directory needs to create new directory or not.
+         */
+        public static String EXTRA_DIRECTORY_COPY = "com.android.documentsui.DIRECTORY_COPY";
+    }
+
     public static class State implements android.os.Parcelable {
         public int action;
         public String[] acceptMimes;
@@ -77,6 +85,7 @@
         public boolean showAdvanced = false;
         public boolean stackTouched = false;
         public boolean restored = false;
+        public boolean directoryCopy = false;
 
         /** Current user navigation stack; empty implies recents. */
         public DocumentStack stack = new DocumentStack();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
index e140f33..d2ef3d7 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
@@ -57,6 +57,10 @@
     private static final String EXTRA_CANCEL = "com.android.documentsui.CANCEL";
     public static final String EXTRA_SRC_LIST = "com.android.documentsui.SRC_LIST";
     public static final String EXTRA_STACK = "com.android.documentsui.STACK";
+    public static final String EXTRA_FAILURE = "com.android.documentsui.FAILURE";
+
+    // TODO: Move it to a shared file when more operations are implemented.
+    public static final int FAILURE_COPY = 1;
 
     private NotificationManager mNotificationManager;
     private Notification.Builder mProgressBuilder;
@@ -66,7 +70,7 @@
     private volatile boolean mIsCancelled;
     // Parameters of the copy job. Requests to an IntentService are serialized so this code only
     // needs to deal with one job at a time.
-    private final List<Uri> mFailedFiles;
+    private final ArrayList<Uri> mFailedFiles;
     private long mBatchSize;
     private long mBytesCopied;
     private long mStartTime;
@@ -128,7 +132,23 @@
             mNotificationManager.cancel(mJobId, 0);
 
             if (mFailedFiles.size() > 0) {
-                // TODO: Display a notification when an error has occurred.
+                final Context context = getApplicationContext();
+                final Intent navigateIntent = new Intent(context, StandaloneActivity.class);
+                navigateIntent.putExtra(EXTRA_STACK, (Parcelable) stack);
+                navigateIntent.putExtra(EXTRA_FAILURE, FAILURE_COPY);
+                navigateIntent.putParcelableArrayListExtra(EXTRA_SRC_LIST, mFailedFiles);
+
+                final Notification.Builder errorBuilder = new Notification.Builder(this)
+                        .setContentTitle(context.getResources().
+                                getQuantityString(R.plurals.copy_error_notification_title,
+                                        mFailedFiles.size(), mFailedFiles.size()))
+                        .setContentText(getString(R.string.notification_touch_for_details))
+                        .setContentIntent(PendingIntent.getActivity(context, 0, navigateIntent,
+                                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT))
+                        .setCategory(Notification.CATEGORY_ERROR)
+                        .setSmallIcon(R.drawable.ic_menu_copy)
+                        .setAutoCancel(true);
+                mNotificationManager.notify(mJobId, 0, errorBuilder.build());
             }
 
             // TODO: Display a toast if the copy was cancelled.
@@ -158,18 +178,19 @@
 
         final Context context = getApplicationContext();
         final Intent navigateIntent = new Intent(context, StandaloneActivity.class);
-        navigateIntent.putExtra(EXTRA_STACK, (Parcelable)stack);
+        navigateIntent.putExtra(EXTRA_STACK, (Parcelable) stack);
 
         mProgressBuilder = new Notification.Builder(this)
                 .setContentTitle(getString(R.string.copy_notification_title))
                 .setContentIntent(PendingIntent.getActivity(context, 0, navigateIntent, 0))
                 .setCategory(Notification.CATEGORY_PROGRESS)
-                .setSmallIcon(R.drawable.ic_menu_copy).setOngoing(true);
+                .setSmallIcon(R.drawable.ic_menu_copy)
+                .setOngoing(true);
 
         final Intent cancelIntent = new Intent(this, CopyService.class);
         cancelIntent.putExtra(EXTRA_CANCEL, mJobId);
         mProgressBuilder.addAction(R.drawable.ic_cab_cancel,
-                getString(R.string.cancel), PendingIntent.getService(this, 0,
+                getString(android.R.string.cancel), PendingIntent.getService(this, 0,
                         cancelIntent, PendingIntent.FLAG_ONE_SHOT));
 
         // Send an initial progress notification.
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index 61fcad2..e2e9807 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
@@ -683,10 +683,18 @@
         // Pop up a dialog to pick a destination.  This is inadequate but works for now.
         // TODO: Implement a picker that is to spec.
         final Intent intent = new Intent(
-                BaseActivity.ACTION_OPEN_COPY_DESTINATION_STRING,
+                BaseActivity.DocumentsIntent.ACTION_OPEN_COPY_DESTINATION,
                 Uri.EMPTY,
                 getActivity(),
                 DocumentsActivity.class);
+        boolean directoryCopy = false;
+        for (DocumentInfo info : docs) {
+            if (Document.MIME_TYPE_DIR.equals(info.mimeType)) {
+                directoryCopy = true;
+                break;
+            }
+        }
+        intent.putExtra(BaseActivity.DocumentsIntent.EXTRA_DIRECTORY_COPY, directoryCopy);
         startActivityForResult(intent, REQUEST_COPY_DESTINATION);
     }
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index 1e798eb..a2a789f 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -237,7 +237,7 @@
             mState.action = ACTION_MANAGE;
         } else if (DocumentsContract.ACTION_BROWSE_DOCUMENT_ROOT.equals(action)) {
             mState.action = ACTION_BROWSE;
-        } else if (ACTION_OPEN_COPY_DESTINATION_STRING.equals(action)) {
+        } else if (DocumentsIntent.ACTION_OPEN_COPY_DESTINATION.equals(action)) {
             mState.action = ACTION_OPEN_COPY_DESTINATION;
         }
 
@@ -265,6 +265,10 @@
         } else {
             mState.showSize = LocalPreferences.getDisplayFileSize(this);
         }
+        if (mState.action == ACTION_OPEN_COPY_DESTINATION) {
+            mState.directoryCopy = intent.getBooleanExtra(
+                    BaseActivity.DocumentsIntent.EXTRA_DIRECTORY_COPY, false);
+        }
     }
 
     private class RestoreRootTask extends AsyncTask<Void, Void, RootInfo> {
@@ -906,7 +910,7 @@
             if (pick != null) {
                 final CharSequence displayName = (mState.stack.size() <= 1) ? root.title
                         : cwd.displayName;
-                pick.setPickTarget(cwd, displayName);
+                pick.setPickTarget(mState.action, cwd, displayName);
             }
         }
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/FailureDialogFragment.java b/packages/DocumentsUI/src/com/android/documentsui/FailureDialogFragment.java
new file mode 100644
index 0000000..1748c9c
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/FailureDialogFragment.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.content.DialogInterface;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.Html;
+
+import com.android.documentsui.model.DocumentInfo;
+
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+
+/**
+ * Alert dialog for failed operations.
+ */
+public class FailureDialogFragment extends DialogFragment
+        implements DialogInterface.OnClickListener {
+    private static final String TAG = "FailureDialogFragment";
+
+    private int mFailure;
+    private ArrayList<Uri> mFailedSrcList;
+
+    public static void show(FragmentManager fm, int failure, ArrayList<Uri> failedSrcList) {
+        // TODO: Add support for other failures than copy.
+        if (failure != CopyService.FAILURE_COPY) {
+            return;
+        }
+
+        final Bundle args = new Bundle();
+        args.putInt(CopyService.EXTRA_FAILURE, failure);
+        args.putParcelableArrayList(CopyService.EXTRA_SRC_LIST, failedSrcList);
+
+        final FragmentTransaction ft = fm.beginTransaction();
+        final FailureDialogFragment fragment = new FailureDialogFragment();
+        fragment.setArguments(args);
+
+        ft.add(fragment, TAG);
+        ft.commitAllowingStateLoss();
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int whichButton) {
+      // TODO: Pass mFailure and mFailedSrcList to the parent fragment.
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle inState) {
+        super.onCreate(inState);
+
+        mFailure = getArguments().getInt(CopyService.EXTRA_FAILURE);
+        mFailedSrcList = getArguments().getParcelableArrayList(CopyService.EXTRA_SRC_LIST);
+
+        final StringBuilder list = new StringBuilder("<p>");
+        for (Uri documentUri : mFailedSrcList) {
+            try {
+                final DocumentInfo documentInfo = DocumentInfo.fromUri(
+                    getActivity().getContentResolver(), documentUri);
+                list.append(String.format("&#8226; %s<br>", documentInfo.displayName));
+            }
+            catch (FileNotFoundException ignore) {
+                // Source file most probably gone.
+            }
+        }
+        list.append("</p>");
+        final String message = String.format(getString(R.string.copy_failure_alert_content),
+                list.toString());
+
+        return new AlertDialog.Builder(getActivity())
+            .setTitle(getString(R.string.copy_failure_alert_title))
+            .setMessage(Html.fromHtml(message))
+            // TODO: Implement retrying the copy operation.
+            .setPositiveButton(R.string.retry, this)
+            .setNegativeButton(android.R.string.cancel, this)
+            .setIcon(android.R.drawable.ic_dialog_alert)
+            .create();
+    }
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java b/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
index 4b008ca..5e565bf 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
@@ -16,6 +16,8 @@
 
 package com.android.documentsui;
 
+import android.R.string;
+import android.app.Activity;
 import android.app.Fragment;
 import android.app.FragmentManager;
 import android.app.FragmentTransaction;
@@ -40,6 +42,7 @@
 
     private View mContainer;
     private Button mPick;
+    private Button mCancel;
 
     public static void show(FragmentManager fm) {
         final PickFragment fragment = new PickFragment();
@@ -61,7 +64,10 @@
         mPick = (Button) mContainer.findViewById(android.R.id.button1);
         mPick.setOnClickListener(mPickListener);
 
-        setPickTarget(null, null);
+        mCancel = (Button) mContainer.findViewById(android.R.id.button2);
+        mCancel.setOnClickListener(mCancelListener);
+
+        setPickTarget(0, null, null);
 
         return mContainer;
     }
@@ -74,18 +80,43 @@
         }
     };
 
-    public void setPickTarget(DocumentInfo pickTarget, CharSequence displayName) {
-        mPickTarget = pickTarget;
+    private View.OnClickListener mCancelListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            final BaseActivity activity = BaseActivity.get(PickFragment.this);
+            activity.setResult(Activity.RESULT_CANCELED);
+            activity.finish();
+        }
+    };
 
+    /**
+     * @param action Which action defined in BaseActivity.State is the picker shown for.
+     */
+    public void setPickTarget(int action,
+                              DocumentInfo pickTarget,
+                              CharSequence displayName) {
         if (mContainer != null) {
-            if (mPickTarget != null) {
+            if (pickTarget != null) {
                 mContainer.setVisibility(View.VISIBLE);
                 final Locale locale = getResources().getConfiguration().locale;
-                final String raw = getString(R.string.menu_select).toUpperCase(locale);
-                mPick.setText(TextUtils.expandTemplate(raw, displayName));
+                switch (action) {
+                    case BaseActivity.State.ACTION_OPEN_TREE:
+                        final String raw = getString(R.string.menu_select).toUpperCase(locale);
+                        mPick.setText(TextUtils.expandTemplate(raw, displayName));
+                        mCancel.setVisibility(View.GONE);
+                        break;
+                    case BaseActivity.State.ACTION_OPEN_COPY_DESTINATION:
+                        mPick.setText(getString(R.string.button_copy).toUpperCase(locale));
+                        mCancel.setVisibility(View.VISIBLE);
+                        break;
+                    default:
+                        throw new IllegalArgumentException("Illegal action for PickFragment.");
+                }
+
             } else {
                 mContainer.setVisibility(View.GONE);
             }
         }
+        mPickTarget = pickTarget;
     }
 }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
index d2267b1..27e8f20 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
@@ -367,6 +367,9 @@
             if (!state.showAdvanced && advanced) continue;
             // Exclude non-local devices when local only
             if (state.localOnly && !localOnly) continue;
+            // Exclude downloads roots that don't support directory creation
+            // TODO: Add flag to check the root supports directory creation or not.
+            if (state.directoryCopy && root.isDownloads()) continue;
             // Only show empty roots when creating
             if ((state.action != State.ACTION_CREATE ||
                  state.action != State.ACTION_OPEN_TREE ||
diff --git a/packages/DocumentsUI/src/com/android/documentsui/StandaloneActivity.java b/packages/DocumentsUI/src/com/android/documentsui/StandaloneActivity.java
index f542838..976f21d 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/StandaloneActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/StandaloneActivity.java
@@ -63,6 +63,7 @@
 import android.widget.Toast;
 import android.widget.Toolbar;
 
+import com.android.documentsui.FailureDialogFragment;
 import com.android.documentsui.RecentsProvider.ResumeColumns;
 import com.android.documentsui.model.DocumentInfo;
 import com.android.documentsui.model.DocumentStack;
@@ -73,6 +74,7 @@
 
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
@@ -153,6 +155,13 @@
         RootsFragment.show(getFragmentManager(), null);
         if (!mState.restored) {
             new RestoreStackTask().execute();
+            final Intent intent = getIntent();
+            final int failure = intent.getIntExtra(CopyService.EXTRA_FAILURE, 0);
+            if (failure != 0) {
+                final ArrayList<Uri> failedSrcList = intent.getParcelableArrayListExtra(
+                        CopyService.EXTRA_SRC_LIST);
+                FailureDialogFragment.show(getFragmentManager(), failure, failedSrcList);
+            }
         } else {
             onCurrentDirectoryChanged(ANIM_NONE);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 410a7e4..094cd1f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -28,6 +28,7 @@
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.InsetDrawable;
+import android.hardware.fingerprint.FingerprintManager;
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.RemoteException;
@@ -625,6 +626,7 @@
         public void onFingerprintError(int msgId, String errString) {
             // TODO: Go to bouncer if this is "too many attempts" (lockout) error.
             Log.i(TAG, "FP Error: " + errString);
+            updateLockIcon();
         }
     };
 
diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
index 6830957..818f5ee 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
@@ -20,7 +20,10 @@
 import android.app.Notification.Action;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.os.UserHandle;
 import android.os.storage.DiskInfo;
 import android.os.storage.StorageEventListener;
@@ -38,6 +41,8 @@
 
     private static final int NOTIF_ID = 0x53544f52; // STOR
 
+    private static final String ACTION_SNOOZE_VOLUME = "com.android.systemui.action.SNOOZE_VOLUME";
+
     // TODO: delay some notifications to avoid bumpy fast operations
     // TODO: annoy user when private media is missing
 
@@ -49,6 +54,25 @@
         public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
             onVolumeStateChangedInternal(vol, oldState, newState);
         }
+
+        @Override
+        public void onVolumeMetadataChanged(VolumeInfo vol) {
+            // Avoid kicking notifications when getting early metadata before
+            // mounted. If already mounted, we're being kicked because of a
+            // nickname or init'ed change.
+            if (vol.getState() == VolumeInfo.STATE_MOUNTED) {
+                onVolumeStateChangedInternal(vol, vol.getState(), vol.getState());
+            }
+        }
+    };
+
+    private final BroadcastReceiver mSnoozeReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // TODO: kick this onto background thread
+            final String volId = intent.getStringExtra(VolumeInfo.EXTRA_VOLUME_ID);
+            mStorageManager.setVolumeSnoozed(volId, true);
+        }
     };
 
     @Override
@@ -58,23 +82,26 @@
         mStorageManager = mContext.getSystemService(StorageManager.class);
         mStorageManager.registerListener(mListener);
 
+        mContext.registerReceiver(mSnoozeReceiver, new IntentFilter(ACTION_SNOOZE_VOLUME),
+                android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null);
+
         // Kick current state into place
         final List<VolumeInfo> vols = mStorageManager.getVolumes();
         for (VolumeInfo vol : vols) {
-            onVolumeStateChangedInternal(vol, vol.state, vol.state);
+            onVolumeStateChangedInternal(vol, vol.getState(), vol.getState());
         }
     }
 
     public void onVolumeStateChangedInternal(VolumeInfo vol, int oldState, int newState) {
         // We only care about public volumes
-        if (vol.type != VolumeInfo.TYPE_PUBLIC) {
+        if (vol.getType() != VolumeInfo.TYPE_PUBLIC) {
             return;
         }
 
         Log.d(TAG, vol.toString());
 
         // New state means we tear down any old notifications
-        mNotificationManager.cancelAsUser(vol.id, NOTIF_ID, UserHandle.ALL);
+        mNotificationManager.cancelAsUser(vol.getId(), NOTIF_ID, UserHandle.ALL);
 
         switch (newState) {
             case VolumeInfo.STATE_UNMOUNTED:
@@ -106,7 +133,7 @@
     }
 
     private void onVolumeMounting(VolumeInfo vol) {
-        final DiskInfo disk = mStorageManager.findDiskById(vol.diskId);
+        final DiskInfo disk = mStorageManager.findDiskById(vol.getDiskId());
         final CharSequence title = mContext.getString(
                 R.string.ext_media_checking_notification_title, disk.getDescription());
         final CharSequence text = mContext.getString(
@@ -119,13 +146,16 @@
                 .setOngoing(true)
                 .build();
 
-        mNotificationManager.notifyAsUser(vol.id, NOTIF_ID, notif, UserHandle.ALL);
+        mNotificationManager.notifyAsUser(vol.getId(), NOTIF_ID, notif, UserHandle.ALL);
     }
 
     private void onVolumeMounted(VolumeInfo vol) {
-        final DiskInfo disk = mStorageManager.findDiskById(vol.diskId);
+        // Don't annoy when user dismissed in past
+        if (vol.isSnoozed()) return;
+
+        final DiskInfo disk = mStorageManager.findDiskById(vol.getDiskId());
         final Notification notif;
-        if (disk.isAdoptable()) {
+        if (disk.isAdoptable() && !vol.isInited()) {
             final CharSequence title = disk.getDescription();
             final CharSequence text = mContext.getString(
                     R.string.ext_media_new_notification_message, disk.getDescription());
@@ -136,6 +166,7 @@
                             buildInitPendingIntent(vol)))
                     .addAction(new Action(0, mContext.getString(R.string.ext_media_unmount_action),
                             buildUnmountPendingIntent(vol)))
+                    .setDeleteIntent(buildSnoozeIntent(vol))
                     .setCategory(Notification.CATEGORY_SYSTEM)
                     .build();
 
@@ -150,12 +181,13 @@
                             buildBrowsePendingIntent(vol)))
                     .addAction(new Action(0, mContext.getString(R.string.ext_media_unmount_action),
                             buildUnmountPendingIntent(vol)))
+                    .setDeleteIntent(buildSnoozeIntent(vol))
                     .setCategory(Notification.CATEGORY_SYSTEM)
                     .setPriority(Notification.PRIORITY_LOW)
                     .build();
         }
 
-        mNotificationManager.notifyAsUser(vol.id, NOTIF_ID, notif, UserHandle.ALL);
+        mNotificationManager.notifyAsUser(vol.getId(), NOTIF_ID, notif, UserHandle.ALL);
     }
 
     private void onVolumeFormatting(VolumeInfo vol) {
@@ -163,7 +195,7 @@
     }
 
     private void onVolumeUnmounting(VolumeInfo vol) {
-        final DiskInfo disk = mStorageManager.findDiskById(vol.diskId);
+        final DiskInfo disk = mStorageManager.findDiskById(vol.getDiskId());
         final CharSequence title = mContext.getString(
                 R.string.ext_media_unmounting_notification_title, disk.getDescription());
         final CharSequence text = mContext.getString(
@@ -176,11 +208,11 @@
                 .setOngoing(true)
                 .build();
 
-        mNotificationManager.notifyAsUser(vol.id, NOTIF_ID, notif, UserHandle.ALL);
+        mNotificationManager.notifyAsUser(vol.getId(), NOTIF_ID, notif, UserHandle.ALL);
     }
 
     private void onVolumeUnmountable(VolumeInfo vol) {
-        final DiskInfo disk = mStorageManager.findDiskById(vol.diskId);
+        final DiskInfo disk = mStorageManager.findDiskById(vol.getDiskId());
         final CharSequence title = mContext.getString(
                 R.string.ext_media_unmountable_notification_title, disk.getDescription());
         final CharSequence text = mContext.getString(
@@ -192,7 +224,7 @@
                 .setCategory(Notification.CATEGORY_ERROR)
                 .build();
 
-        mNotificationManager.notifyAsUser(vol.id, NOTIF_ID, notif, UserHandle.ALL);
+        mNotificationManager.notifyAsUser(vol.getId(), NOTIF_ID, notif, UserHandle.ALL);
     }
 
     private void onVolumeRemoved(VolumeInfo vol) {
@@ -201,7 +233,7 @@
             return;
         }
 
-        final DiskInfo disk = mStorageManager.findDiskById(vol.diskId);
+        final DiskInfo disk = mStorageManager.findDiskById(vol.getDiskId());
         final CharSequence title = mContext.getString(
                 R.string.ext_media_nomedia_notification_title, disk.getDescription());
         final CharSequence text = mContext.getString(
@@ -212,7 +244,7 @@
                 .setCategory(Notification.CATEGORY_ERROR)
                 .build();
 
-        mNotificationManager.notifyAsUser(vol.id, NOTIF_ID, notif, UserHandle.ALL);
+        mNotificationManager.notifyAsUser(vol.getId(), NOTIF_ID, notif, UserHandle.ALL);
     }
 
     private Notification.Builder buildNotificationBuilder(CharSequence title, CharSequence text) {
@@ -229,28 +261,49 @@
         final Intent intent = new Intent();
         intent.setClassName("com.android.settings",
                 "com.android.settings.deviceinfo.StorageWizardInit");
-        intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.id);
-        return PendingIntent.getActivityAsUser(mContext, 0, intent, 0, null, UserHandle.CURRENT);
+        intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.getId());
+
+        final int requestKey = vol.getId().hashCode();
+        return PendingIntent.getActivityAsUser(mContext, requestKey, intent,
+                PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
     }
 
     private PendingIntent buildUnmountPendingIntent(VolumeInfo vol) {
         final Intent intent = new Intent();
         intent.setClassName("com.android.settings",
                 "com.android.settings.deviceinfo.StorageUnmountReceiver");
-        intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.id);
-        return PendingIntent.getBroadcastAsUser(mContext, 0, intent, 0, UserHandle.CURRENT);
+        intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.getId());
+
+        final int requestKey = vol.getId().hashCode();
+        return PendingIntent.getBroadcastAsUser(mContext, requestKey, intent,
+                PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.CURRENT);
     }
 
     private PendingIntent buildBrowsePendingIntent(VolumeInfo vol) {
         final Intent intent = vol.buildBrowseIntent();
-        return PendingIntent.getActivityAsUser(mContext, 0, intent, 0, null, UserHandle.CURRENT);
+
+        final int requestKey = vol.getId().hashCode();
+        return PendingIntent.getActivityAsUser(mContext, requestKey, intent,
+                PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
     }
 
     private PendingIntent buildDetailsPendingIntent(VolumeInfo vol) {
         final Intent intent = new Intent();
         intent.setClassName("com.android.settings",
                 "com.android.settings.Settings$StorageVolumeSettingsActivity");
-        intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.id);
-        return PendingIntent.getActivityAsUser(mContext, 0, intent, 0, null, UserHandle.CURRENT);
+        intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.getId());
+
+        final int requestKey = vol.getId().hashCode();
+        return PendingIntent.getActivityAsUser(mContext, requestKey, intent,
+                PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
+    }
+
+    private PendingIntent buildSnoozeIntent(VolumeInfo vol) {
+        final Intent intent = new Intent(ACTION_SNOOZE_VOLUME);
+        intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.getId());
+
+        final int requestKey = vol.getId().hashCode();
+        return PendingIntent.getBroadcastAsUser(mContext, requestKey, intent,
+                PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.CURRENT);
     }
 }
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index a99f387..4c937f7 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -16,6 +16,13 @@
 
 package com.android.server;
 
+import static com.android.internal.util.XmlUtils.readIntAttribute;
+import static com.android.internal.util.XmlUtils.readStringAttribute;
+import static com.android.internal.util.XmlUtils.writeIntAttribute;
+import static com.android.internal.util.XmlUtils.writeStringAttribute;
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.START_TAG;
+
 import android.Manifest;
 import android.app.ActivityManagerNative;
 import android.app.AppOpsManager;
@@ -55,9 +62,13 @@
 import android.os.storage.VolumeInfo;
 import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.DebugUtils;
 import android.util.Log;
 import android.util.Slog;
+import android.util.Xml;
 
+import libcore.io.IoUtils;
 import libcore.util.EmptyArray;
 import libcore.util.HexEncoding;
 
@@ -66,14 +77,21 @@
 import com.android.internal.app.IMediaContainerService;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.server.NativeDaemonConnector.Command;
 import com.android.server.NativeDaemonConnector.SensitiveArg;
 import com.android.server.pm.PackageManagerService;
 
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
 import java.io.File;
 import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -214,6 +232,57 @@
         public static final int FstrimCompleted                = 700;
     }
 
+    private static final String TAG_VOLUMES = "volumes";
+    private static final String TAG_VOLUME = "volume";
+    private static final String ATTR_TYPE = "type";
+    private static final String ATTR_FS_UUID = "fsUuid";
+    private static final String ATTR_NICKNAME = "nickname";
+    private static final String ATTR_USER_FLAGS = "userFlags";
+
+    private final AtomicFile mMetadataFile;
+
+    private static class VolumeMetadata {
+        public final int type;
+        public final String fsUuid;
+        public String nickname;
+        public int userFlags;
+
+        public VolumeMetadata(int type, String fsUuid) {
+            this.type = type;
+            this.fsUuid = Preconditions.checkNotNull(fsUuid);
+        }
+
+        public static VolumeMetadata read(XmlPullParser in) throws IOException {
+            final int type = readIntAttribute(in, ATTR_TYPE);
+            final String fsUuid = readStringAttribute(in, ATTR_FS_UUID);
+            final VolumeMetadata meta = new VolumeMetadata(type, fsUuid);
+            meta.nickname = readStringAttribute(in, ATTR_NICKNAME);
+            meta.userFlags = readIntAttribute(in, ATTR_USER_FLAGS);
+            return meta;
+        }
+
+        public static void write(XmlSerializer out, VolumeMetadata meta) throws IOException {
+            out.startTag(null, TAG_VOLUME);
+            writeIntAttribute(out, ATTR_TYPE, meta.type);
+            writeStringAttribute(out, ATTR_FS_UUID, meta.fsUuid);
+            writeStringAttribute(out, ATTR_NICKNAME, meta.nickname);
+            writeIntAttribute(out, ATTR_USER_FLAGS, meta.userFlags);
+            out.endTag(null, TAG_VOLUME);
+        }
+
+        public void dump(IndentingPrintWriter pw) {
+            pw.println("VolumeMetadata:");
+            pw.increaseIndent();
+            pw.printPair("type", DebugUtils.valueToString(VolumeInfo.class, "TYPE_", type));
+            pw.printPair("fsUuid", fsUuid);
+            pw.printPair("nickname", nickname);
+            pw.printPair("userFlags",
+                    DebugUtils.flagsToString(VolumeInfo.class, "USER_FLAG_", userFlags));
+            pw.decreaseIndent();
+            pw.println();
+        }
+    }
+
     /**
      * <em>Never</em> hold the lock while performing downcalls into vold, since
      * unsolicited events can suddenly appear to update data structures.
@@ -222,11 +291,18 @@
 
     @GuardedBy("mLock")
     private int[] mStartedUsers = EmptyArray.INT;
+
+    /** Map from disk ID to disk */
     @GuardedBy("mLock")
     private ArrayMap<String, DiskInfo> mDisks = new ArrayMap<>();
+    /** Map from volume ID to disk */
     @GuardedBy("mLock")
     private ArrayMap<String, VolumeInfo> mVolumes = new ArrayMap<>();
 
+    /** Map from UUID to metadata */
+    @GuardedBy("mLock")
+    private ArrayMap<String, VolumeMetadata> mMetadata = new ArrayMap<>();
+
     private DiskInfo findDiskById(String id) {
         synchronized (mLock) {
             final DiskInfo disk = mDisks.get(id);
@@ -260,6 +336,15 @@
         throw new IllegalArgumentException("No volume found for path " + path);
     }
 
+    private VolumeMetadata findOrCreateMetadataLocked(VolumeInfo vol) {
+        VolumeMetadata meta = mMetadata.get(vol.fsUuid);
+        if (meta == null) {
+            meta = new VolumeMetadata(vol.type, vol.fsUuid);
+            mMetadata.put(meta.fsUuid, meta);
+        }
+        return meta;
+    }
+
     private static int sNextMtpIndex = 1;
 
     private static int allocateMtpIndex(String volId) {
@@ -799,6 +884,7 @@
                 if (vol != null) {
                     vol.fsType = cooked[2];
                 }
+                mCallbacks.notifyVolumeMetadataChanged(vol.clone());
                 break;
             }
             case VoldResponseCode.VOLUME_FS_UUID_CHANGED: {
@@ -807,6 +893,8 @@
                 if (vol != null) {
                     vol.fsUuid = cooked[2];
                 }
+                refreshMetadataLocked();
+                mCallbacks.notifyVolumeMetadataChanged(vol.clone());
                 break;
             }
             case VoldResponseCode.VOLUME_FS_LABEL_CHANGED: {
@@ -815,6 +903,7 @@
                 if (vol != null) {
                     vol.fsLabel = cooked[2];
                 }
+                mCallbacks.notifyVolumeMetadataChanged(vol.clone());
                 break;
             }
             case VoldResponseCode.VOLUME_PATH_CHANGED: {
@@ -901,6 +990,25 @@
         }
     }
 
+    /**
+     * Refresh latest metadata into any currently active {@link VolumeInfo}.
+     */
+    private void refreshMetadataLocked() {
+        final int size = mVolumes.size();
+        for (int i = 0; i < size; i++) {
+            final VolumeInfo vol = mVolumes.valueAt(i);
+            final VolumeMetadata meta = mMetadata.get(vol.fsUuid);
+
+            if (meta != null) {
+                vol.nickname = meta.nickname;
+                vol.userFlags = meta.userFlags;
+            } else {
+                vol.nickname = null;
+                vol.userFlags = 0;
+            }
+        }
+    }
+
     private void enforcePermission(String perm) {
         mContext.enforceCallingOrSelfPermission(perm, perm);
     }
@@ -949,6 +1057,13 @@
             mLastMaintenance = mLastMaintenanceFile.lastModified();
         }
 
+        mMetadataFile = new AtomicFile(
+                new File(Environment.getSystemSecureDirectory(), "storage.xml"));
+
+        synchronized (mLock) {
+            readMetadataLocked();
+        }
+
         /*
          * Create the connection to vold with a maximum queue of twice the
          * amount of containers we'd ever expect to have. This keeps an
@@ -972,6 +1087,61 @@
         mHandler.obtainMessage(H_SYSTEM_READY).sendToTarget();
     }
 
+    private void readMetadataLocked() {
+        mMetadata.clear();
+
+        FileInputStream fis = null;
+        try {
+            fis = mMetadataFile.openRead();
+            final XmlPullParser in = Xml.newPullParser();
+            in.setInput(fis, null);
+
+            int type;
+            while ((type = in.next()) != END_DOCUMENT) {
+                if (type == START_TAG) {
+                    final String tag = in.getName();
+                    if (TAG_VOLUME.equals(tag)) {
+                        final VolumeMetadata meta = VolumeMetadata.read(in);
+                        mMetadata.put(meta.fsUuid, meta);
+                    }
+                }
+            }
+        } catch (FileNotFoundException e) {
+            // Missing metadata is okay, probably first boot
+        } catch (IOException e) {
+            Slog.wtf(TAG, "Failed reading metadata", e);
+        } catch (XmlPullParserException e) {
+            Slog.wtf(TAG, "Failed reading metadata", e);
+        } finally {
+            IoUtils.closeQuietly(fis);
+        }
+    }
+
+    private void writeMetadataLocked() {
+        FileOutputStream fos = null;
+        try {
+            fos = mMetadataFile.startWrite();
+
+            XmlSerializer out = new FastXmlSerializer();
+            out.setOutput(fos, "utf-8");
+            out.startDocument(null, true);
+            out.startTag(null, TAG_VOLUMES);
+            final int size = mMetadata.size();
+            for (int i = 0; i < size; i++) {
+                final VolumeMetadata meta = mMetadata.valueAt(i);
+                VolumeMetadata.write(out, meta);
+            }
+            out.endTag(null, TAG_VOLUMES);
+            out.endDocument();
+
+            mMetadataFile.finishWrite(fos);
+        } catch (IOException e) {
+            if (fos != null) {
+                mMetadataFile.failWrite(fos);
+            }
+        }
+    }
+
     /**
      * Exposed API calls below here
      */
@@ -1126,6 +1296,36 @@
     }
 
     @Override
+    public void setVolumeNickname(String volId, String nickname) {
+        enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
+        waitForReady();
+
+        synchronized (mLock) {
+            final VolumeInfo vol = findVolumeById(volId);
+            final VolumeMetadata meta = findOrCreateMetadataLocked(vol);
+            meta.nickname = nickname;
+            refreshMetadataLocked();
+            writeMetadataLocked();
+            mCallbacks.notifyVolumeMetadataChanged(vol.clone());
+        }
+    }
+
+    @Override
+    public void setVolumeUserFlags(String volId, int flags, int mask) {
+        enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
+        waitForReady();
+
+        synchronized (mLock) {
+            final VolumeInfo vol = findVolumeById(volId);
+            final VolumeMetadata meta = findOrCreateMetadataLocked(vol);
+            meta.userFlags = (meta.userFlags & ~mask) | (flags & mask);
+            refreshMetadataLocked();
+            writeMetadataLocked();
+            mCallbacks.notifyVolumeMetadataChanged(vol.clone());
+        }
+    }
+
+    @Override
     public int[] getStorageUsers(String path) {
         enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
         waitForReady();
@@ -1909,7 +2109,12 @@
     }
 
     @Override
-    public VolumeInfo[] getVolumes() {
+    public VolumeInfo[] getVolumes(int flags) {
+        if ((flags & StorageManager.FLAG_ALL_METADATA) != 0) {
+            // TODO: implement support for returning all metadata
+            throw new UnsupportedOperationException();
+        }
+
         synchronized (mLock) {
             final VolumeInfo[] res = new VolumeInfo[mVolumes.size()];
             for (int i = 0; i < mVolumes.size(); i++) {
@@ -2422,6 +2627,7 @@
     private static class Callbacks extends Handler {
         private static final int MSG_STORAGE_STATE_CHANGED = 1;
         private static final int MSG_VOLUME_STATE_CHANGED = 2;
+        private static final int MSG_VOLUME_METADATA_CHANGED = 3;
 
         private final RemoteCallbackList<IMountServiceListener>
                 mCallbacks = new RemoteCallbackList<>();
@@ -2465,6 +2671,10 @@
                     callback.onVolumeStateChanged((VolumeInfo) args.arg1, args.argi2, args.argi3);
                     break;
                 }
+                case MSG_VOLUME_METADATA_CHANGED: {
+                    callback.onVolumeMetadataChanged((VolumeInfo) args.arg1);
+                    break;
+                }
             }
         }
 
@@ -2483,6 +2693,12 @@
             args.argi3 = newState;
             obtainMessage(MSG_VOLUME_STATE_CHANGED, args).sendToTarget();
         }
+
+        private void notifyVolumeMetadataChanged(VolumeInfo vol) {
+            final SomeArgs args = SomeArgs.obtain();
+            args.arg1 = vol;
+            obtainMessage(MSG_VOLUME_METADATA_CHANGED, args).sendToTarget();
+        }
     }
 
     @Override
@@ -2539,6 +2755,15 @@
                 vol.dump(pw);
             }
             pw.decreaseIndent();
+
+            pw.println();
+            pw.println("Metadata:");
+            pw.increaseIndent();
+            for (int i = 0; i < mMetadata.size(); i++) {
+                final VolumeMetadata meta = mMetadata.valueAt(i);
+                meta.dump(pw);
+            }
+            pw.decreaseIndent();
         }
 
         pw.println();
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java
index c150b60..90e69d7 100644
--- a/services/core/java/com/android/server/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java
@@ -39,6 +39,7 @@
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -51,9 +52,9 @@
 public class FingerprintService extends SystemService {
     private static final String TAG = "FingerprintService";
     private static final boolean DEBUG = true;
-    private ClientData mAuthClient = null;
-    private ClientData mEnrollClient = null;
-    private ClientData mRemoveClient = null;
+    private ClientMonitor mAuthClient = null;
+    private ClientMonitor mEnrollClient = null;
+    private ClientMonitor mRemoveClient = null;
 
     private static final int MSG_NOTIFY = 10;
 
@@ -63,7 +64,6 @@
     // Must agree with the list in fingerprint.h
     private static final int FINGERPRINT_ERROR = -1;
     private static final int FINGERPRINT_ACQUIRED = 1;
-    private static final int FINGERPRINT_PROCESSED = 2;
     private static final int FINGERPRINT_TEMPLATE_ENROLLING = 3;
     private static final int FINGERPRINT_TEMPLATE_REMOVED = 4;
     private static final int FINGERPRINT_AUTHENTICATED = 5;
@@ -83,80 +83,21 @@
     };
     private Context mContext;
     private int mHalDeviceId;
+    private int mFailedAttempts;
+    private final Runnable mLockoutReset = new Runnable() {
+        @Override
+        public void run() {
+            resetFailedAttempts();
+        }
+    };
 
     private static final int STATE_IDLE = 0;
     private static final int STATE_AUTHENTICATING = 1;
     private static final int STATE_ENROLLING = 2;
     private static final int STATE_REMOVING = 3;
     private static final long MS_PER_SEC = 1000;
-
-    private class ClientData {
-        IBinder token;
-        IFingerprintServiceReceiver receiver;
-        int userId;
-        long opId;
-        private TokenWatcher tokenWatcher;
-        public ClientData(IBinder token, long opId, IFingerprintServiceReceiver receiver,
-                int userId) {
-            this.token = token;
-            this.opId = opId;
-            this.receiver = receiver;
-            this.userId = userId;
-            tokenWatcher = new TokenWatcher(token);
-            try {
-                token.linkToDeath(tokenWatcher, 0);
-            } catch (RemoteException e) {
-                Slog.w(TAG, "caught remote exception in linkToDeath: ", e);
-            }
-        }
-
-        IBinder getToken() {
-            return tokenWatcher.getToken();
-        }
-
-        public void destroy() {
-            token.unlinkToDeath(tokenWatcher, 0);
-            tokenWatcher.token = null;
-        }
-    }
-
-    private class TokenWatcher implements IBinder.DeathRecipient {
-        WeakReference<IBinder> token;
-
-        TokenWatcher(IBinder token) {
-            this.token = new WeakReference<IBinder>(token);
-        }
-
-        IBinder getToken() {
-            return token.get();
-        }
-
-        public void binderDied() {
-            if (mAuthClient != null & mAuthClient.token == token)
-                mAuthClient = null;
-            if (mEnrollClient != null && mEnrollClient.token == token)
-                mEnrollClient = null;
-            this.token = null;
-        }
-
-        protected void finalize() throws Throwable {
-            try {
-                if (token != null) {
-                    if (DEBUG) Slog.w(TAG, "removing leaked reference: " + token);
-                    if (mAuthClient != null && mAuthClient.token == token) {
-                        mAuthClient.destroy();
-                        mAuthClient = null;
-                    }
-                    if (mEnrollClient != null && mEnrollClient.token == token) {
-                        mAuthClient.destroy();
-                        mEnrollClient = null;
-                    }
-                }
-            } finally {
-                super.finalize();
-            }
-        }
-    }
+    private static final long FAIL_LOCKOUT_TIMEOUT_MS = 30*1000;
+    private static final int MAX_FAILED_ATTEMPTS = 5;
 
     public FingerprintService(Context context) {
         super(context);
@@ -166,11 +107,11 @@
 
     // TODO: Move these into separate process
     // JNI methods to communicate from FingerprintService to HAL
-    static native int nativeEnroll(long challenge, int groupId, int timeout);
+    static native int nativeEnroll(byte [] token, int groupId, int timeout);
     static native long nativePreEnroll();
     static native int nativeStopEnrollment();
     static native int nativeAuthenticate(long sessionId, int groupId);
-    static native int nativeStopAuthentication(long sessionId);
+    static native int nativeStopAuthentication();
     static native int nativeRemove(int fingerId, int groupId);
     static native int nativeOpenHal();
     static native int nativeCloseHal();
@@ -201,82 +142,62 @@
         Slog.v(TAG, "handleNotify(type=" + type + ", arg1=" + arg1 + ", arg2=" + arg2 + ")"
                     + ", mAuthClients = " + mAuthClient + ", mEnrollClient = " + mEnrollClient);
         if (mEnrollClient != null) {
-            try {
-                final IBinder token = mEnrollClient.token;
-                if (doNotify(mEnrollClient, type, arg1, arg2, arg3)) {
-                    stopEnrollment(token);
-                }
-            } catch (RemoteException e) {
-                Slog.e(TAG, "can't send message to mEnrollClient. Did it die?", e);
-                mEnrollClient.destroy();
-                mEnrollClient = null;
+            final IBinder token = mEnrollClient.token;
+            if (doNotify(mEnrollClient, type, arg1, arg2, arg3)) {
+                stopEnrollment(token);
             }
         }
         if (mAuthClient != null) {
-            try {
-                final IBinder token = mAuthClient.getToken();
-                if (doNotify(mAuthClient, type, arg1, arg2, arg3)) {
-                    stopAuthentication(token);
-                }
-            } catch (RemoteException e) {
-                Slog.e(TAG, "can't send message to mAuthClient. Did it die?", e);
-                mAuthClient.destroy();
-                mAuthClient = null;
+            final IBinder token = mAuthClient.token;
+            if (doNotify(mAuthClient, type, arg1, arg2, arg3)) {
+                stopAuthentication(token);
             }
         }
         if (mRemoveClient != null) {
-            try {
-                if (doNotify(mRemoveClient, type, arg1, arg2, arg3)) {
-                    mRemoveClient.destroy();
-                    mRemoveClient = null;
-                }
-            } catch (RemoteException e) {
-                Slog.e(TAG, "can't send message to mRemoveClient. Did it die?", e);
-                mRemoveClient.destroy();
-                mRemoveClient = null;
+            if (doNotify(mRemoveClient, type, arg1, arg2, arg3)) {
+                removeClient(mRemoveClient);
             }
         }
     }
 
     // Returns true if the operation is done, i.e. authentication completed
-    boolean doNotify(ClientData clientData, int type, int arg1, int arg2, int arg3)
-            throws RemoteException {
-        if (clientData.receiver == null) {
-            if (DEBUG) Slog.v(TAG, "receiver not registered!!");
-            return false;
-        }
+    boolean doNotify(ClientMonitor clientMonitor, int type, int arg1, int arg2, int arg3) {
         ContentResolver contentResolver = mContext.getContentResolver();
         boolean operationCompleted = false;
         switch (type) {
             case FINGERPRINT_ERROR:
-                clientData.receiver.onError(mHalDeviceId, arg1 /* error */);
-                if (arg1 == FingerprintManager.FINGERPRINT_ERROR_CANCELED) {
-                    if (mEnrollClient != null) {
-                        mEnrollClient.destroy();
-                        mEnrollClient = null;
-                    }
-                    if (mAuthClient != null) {
-                        mAuthClient.destroy();
-                        mAuthClient = null;
-                    }
+                {
+                    final int error = arg1;
+                    clientMonitor.sendError(error);
+                    removeClient(clientMonitor);
+                    operationCompleted = true; // any error means the operation is done
                 }
-                operationCompleted = true; // any error means the operation is done
                 break;
             case FINGERPRINT_ACQUIRED:
-                clientData.receiver.onAcquired(mHalDeviceId, arg1 /* acquireInfo */);
+                clientMonitor.sendAcquired(arg1 /* acquireInfo */);
                 break;
-            case FINGERPRINT_PROCESSED:
-                clientData.receiver.onProcessed(mHalDeviceId, arg1 /* fpId */, arg2 /* gpId */);
-                operationCompleted = true; // we either got a positive or negative match
+            case FINGERPRINT_AUTHENTICATED:
+                {
+                    final int fpId = arg1;
+                    final int groupId = arg2;
+                    clientMonitor.sendAuthenticated(fpId, groupId);
+                    if (fpId == 0) {
+                        if (clientMonitor == mAuthClient) {
+                            operationCompleted = handleFailedAttempt(clientMonitor);
+                        }
+                    } else {
+                        mLockoutReset.run(); // a valid fingerprint resets lockout
+                    }
+                }
                 break;
             case FINGERPRINT_TEMPLATE_ENROLLING:
                 {
                     final int fpId = arg1;
                     final int groupId = arg2;
                     final int remaining = arg3;
-                    clientData.receiver.onEnrollResult(mHalDeviceId, fpId, groupId, remaining);
+                    clientMonitor.sendEnrollResult(fpId, groupId, remaining);
                     if (remaining == 0) {
-                        addTemplateForUser(clientData, contentResolver, fpId);
+                        addTemplateForUser(clientMonitor, contentResolver, fpId);
                         operationCompleted = true; // enroll completed
                     }
                 }
@@ -285,11 +206,11 @@
                 {
                     final int fingerId = arg1;
                     final int groupId = arg2;
-                    removeTemplateForUser(clientData, contentResolver, fingerId);
+                    removeTemplateForUser(clientMonitor, contentResolver, fingerId);
                     if (fingerId == 0) {
                         operationCompleted = true; // remove completed
                     } else {
-                        clientData.receiver.onRemoved(mHalDeviceId, fingerId, groupId);
+                        clientMonitor.sendRemoved(fingerId, groupId);
                     }
                 }
                 break;
@@ -297,24 +218,60 @@
         return operationCompleted;
     }
 
-    private void removeTemplateForUser(ClientData clientData, ContentResolver contentResolver,
+    private void removeClient(ClientMonitor clientMonitor) {
+        if (clientMonitor == null) return;
+        clientMonitor.destroy();
+        if (clientMonitor == mAuthClient) {
+            mAuthClient = null;
+        } else if (clientMonitor == mEnrollClient) {
+            mEnrollClient = null;
+        } else if (clientMonitor == mRemoveClient) {
+            mRemoveClient = null;
+        }
+    }
+
+    private boolean inLockoutMode() {
+        return mFailedAttempts > MAX_FAILED_ATTEMPTS;
+    }
+
+    private void resetFailedAttempts() {
+        if (DEBUG) Slog.v(TAG, "Reset fingerprint lockout");
+        mFailedAttempts = 0;
+    }
+
+    private boolean handleFailedAttempt(ClientMonitor clientMonitor) {
+        mFailedAttempts++;
+        if (mFailedAttempts > MAX_FAILED_ATTEMPTS) {
+            // Failing multiple times will continue to push out the lockout time.
+            mHandler.removeCallbacks(mLockoutReset);
+            mHandler.postDelayed(mLockoutReset, FAIL_LOCKOUT_TIMEOUT_MS);
+            if (clientMonitor != null
+                    && !clientMonitor.sendError(FingerprintManager.FINGERPRINT_ERROR_LOCKOUT)) {
+                Slog.w(TAG, "Cannot send lockout message to client");
+            }
+            return true;
+        }
+        return false;
+    }
+
+    private void removeTemplateForUser(ClientMonitor clientMonitor, ContentResolver contentResolver,
             final int fingerId) {
         FingerprintUtils.removeFingerprintIdForUser(fingerId, contentResolver,
-                clientData.userId);
+                clientMonitor.userId);
     }
 
-    private void addTemplateForUser(ClientData clientData, ContentResolver contentResolver,
+    private void addTemplateForUser(ClientMonitor clientMonitor, ContentResolver contentResolver,
             final int fingerId) {
         FingerprintUtils.addFingerprintIdForUser(contentResolver, fingerId,
-                clientData.userId);
+                clientMonitor.userId);
     }
 
-    void startEnrollment(IBinder token, long opId,
-            int groupId, IFingerprintServiceReceiver receiver, int flags) {
+    void startEnrollment(IBinder token, byte[] cryptoToken, int groupId,
+            IFingerprintServiceReceiver receiver, int flags) {
         stopPendingOperations();
-        mEnrollClient = new ClientData(token, opId, receiver, groupId);
+        mEnrollClient = new ClientMonitor(token, receiver, groupId);
         final int timeout = (int) (ENROLLMENT_TIMEOUT_MS / MS_PER_SEC);
-        final int result = nativeEnroll(opId, groupId, timeout);
+        final int result = nativeEnroll(cryptoToken, groupId, timeout);
         if (result != 0) {
             Slog.w(TAG, "startEnroll failed, result=" + result);
         }
@@ -335,8 +292,11 @@
     }
 
     void stopEnrollment(IBinder token) {
-        if (mEnrollClient == null || mEnrollClient.token != token) return;
+        final ClientMonitor client = mEnrollClient;
+        if (client == null || client.token != token) return;
         int result = nativeStopEnrollment();
+        client.sendError(FingerprintManager.FINGERPRINT_ERROR_CANCELED);
+        removeClient(mEnrollClient);
         if (result != 0) {
             Slog.w(TAG, "startEnrollCancel failed, result=" + result);
         }
@@ -345,7 +305,15 @@
     void startAuthentication(IBinder token, long opId, int groupId,
             IFingerprintServiceReceiver receiver, int flags) {
         stopPendingOperations();
-        mAuthClient = new ClientData(token, opId, receiver, groupId);
+        mAuthClient = new ClientMonitor(token, receiver, groupId);
+        if (inLockoutMode()) {
+            Slog.v(TAG, "In lockout mode; disallowing authentication");
+            if (!mAuthClient.sendError(FingerprintManager.FINGERPRINT_ERROR_LOCKOUT)) {
+                Slog.w(TAG, "Cannot send timeout message to client");
+            }
+            mAuthClient = null;
+            return;
+        }
         final int timeout = (int) (ENROLLMENT_TIMEOUT_MS / MS_PER_SEC);
         final int result = nativeAuthenticate(opId, groupId);
         if (result != 0) {
@@ -354,8 +322,11 @@
     }
 
     void stopAuthentication(IBinder token) {
-        if (mAuthClient == null || mAuthClient.token != token) return;
-        int result = nativeStopAuthentication(mAuthClient.opId);
+        final ClientMonitor client = mAuthClient;
+        if (client == null || client.token != token) return;
+        int result = nativeStopAuthentication();
+        client.sendError(FingerprintManager.FINGERPRINT_ERROR_CANCELED);
+        removeClient(mAuthClient);
         if (result != 0) {
             Slog.w(TAG, "stopAuthentication failed, result=" + result);
         }
@@ -363,7 +334,7 @@
 
     void startRemove(IBinder token, int fingerId, int userId,
             IFingerprintServiceReceiver receiver) {
-        mRemoveClient = new ClientData(token, 0, receiver, userId);
+        mRemoveClient = new ClientMonitor(token, receiver, userId);
         // The fingerprint template ids will be removed when we get confirmation from the HAL
         final int result = nativeRemove(fingerId, userId);
         if (result != 0) {
@@ -392,17 +363,114 @@
                 "Must have " + permission + " permission.");
     }
 
-    private static final class Message {
+    private class ClientMonitor implements IBinder.DeathRecipient {
         IBinder token;
-        long opId;
-        int groupId;
-        int flags;
+        WeakReference<IFingerprintServiceReceiver> receiver;
+        int userId;
 
-        public Message(IBinder token, long challenge, int groupId, int flags) {
+        public ClientMonitor(IBinder token, IFingerprintServiceReceiver receiver, int userId) {
             this.token = token;
-            this.opId = challenge;
-            this.groupId = groupId;
-            this.flags = flags;
+            this.receiver = new WeakReference<IFingerprintServiceReceiver>(receiver);
+            this.userId = userId;
+            try {
+                token.linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "caught remote exception in linkToDeath: ", e);
+            }
+        }
+
+        public void destroy() {
+            if (token != null) {
+                token.unlinkToDeath(this, 0);
+                token = null;
+            }
+            receiver = null;
+        }
+
+        public void binderDied() {
+            token = null;
+            removeClient(this);
+        }
+
+        protected void finalize() throws Throwable {
+            try {
+                if (token != null) {
+                    if (DEBUG) Slog.w(TAG, "removing leaked reference: " + token);
+                    removeClient(this);
+                }
+            } finally {
+                super.finalize();
+            }
+        }
+
+        private boolean sendRemoved(int fingerId, int groupId) {
+            IFingerprintServiceReceiver rx = receiver.get();
+            if (rx != null) {
+                try {
+                    rx.onRemoved(mHalDeviceId, fingerId, groupId);
+                    return true;
+                } catch (RemoteException e) {
+                    if (DEBUG) Slog.v(TAG, "Failed to invoke sendRemoved:", e);
+                }
+            }
+            removeClient(this);
+            return false;
+        }
+
+        private boolean sendEnrollResult(int fpId, int groupId, int remaining) {
+            IFingerprintServiceReceiver rx = receiver.get();
+            if (rx != null) {
+                try {
+                    rx.onEnrollResult(mHalDeviceId, fpId, groupId, remaining);
+                    return true;
+                } catch (RemoteException e) {
+                    if (DEBUG) Slog.v(TAG, "Failed to invoke sendEnrollResult:", e);
+                }
+            }
+            removeClient(this);
+            return false;
+        }
+
+        private boolean sendAuthenticated(int fpId, int groupId) {
+            IFingerprintServiceReceiver rx = receiver.get();
+            if (rx != null) {
+                try {
+                    rx.onAuthenticated(mHalDeviceId, fpId, groupId);
+                    return true;
+                } catch (RemoteException e) {
+                    if (DEBUG) Slog.v(TAG, "Failed to invoke sendProcessed:", e);
+                }
+            }
+            removeClient(this);
+            return false;
+        }
+
+        private boolean sendAcquired(int acquiredInfo) {
+            IFingerprintServiceReceiver rx = receiver.get();
+            if (rx != null) {
+                try {
+                    rx.onAcquired(mHalDeviceId, acquiredInfo);
+                    return true;
+                } catch (RemoteException e) {
+                    if (DEBUG) Slog.v(TAG, "Failed to invoke sendAcquired:", e);
+                }
+            }
+            removeClient(this);
+            return false;
+        }
+
+        private boolean sendError(int error) {
+            IFingerprintServiceReceiver rx = receiver.get();
+            if (rx != null) {
+                try {
+                    rx.onError(mHalDeviceId, error);
+                    return true;
+                } catch (RemoteException e) {
+                    if (DEBUG) Slog.v(TAG, "Failed to invoke sendError:", e);
+                }
+            }
+            removeClient(this);
+            return false;
         }
     }
 
@@ -415,13 +483,14 @@
 
         @Override
         // Binder call
-        public void enroll(final IBinder token, final long opid, final int groupId,
+        public void enroll(final IBinder token, final byte[] cryptoToken, final int groupId,
                 final IFingerprintServiceReceiver receiver, final int flags) {
             checkPermission(MANAGE_FINGERPRINT);
+            final byte [] cryptoClone = Arrays.copyOf(cryptoToken, cryptoToken.length);
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
-                    startEnrollment(token, opid, groupId, receiver, flags);
+                    startEnrollment(token, cryptoClone, groupId, receiver, flags);
                 }
             });
         }
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 55dd911..bb53534 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -148,7 +148,7 @@
                 if (timeout >= 0) {
                     // The activity manager declined to abort dispatching.
                     // Wait a bit longer and timeout again later.
-                    return timeout;
+                    return timeout * 1000000L; // nanoseconds
                 }
             } catch (RemoteException ex) {
             }
diff --git a/services/core/jni/com_android_server_fingerprint_FingerprintService.cpp b/services/core/jni/com_android_server_fingerprint_FingerprintService.cpp
index a6cdbc4..17f86ca 100644
--- a/services/core/jni/com_android_server_fingerprint_FingerprintService.cpp
+++ b/services/core/jni/com_android_server_fingerprint_FingerprintService.cpp
@@ -128,11 +128,16 @@
     gLooper = android_os_MessageQueue_getMessageQueue(env, mQueue)->getLooper();
 }
 
-static jint nativeEnroll(JNIEnv* env, jobject clazz, jint groupId, jint timeout) {
-    hw_auth_token_t *hat = NULL;  // This is here as a placeholder,
-    // please figure out your favorite way to send the hat struct through JNI
+static jint nativeEnroll(JNIEnv* env, jobject clazz, jbyteArray token, jint groupId, jint timeout) {
     ALOG(LOG_VERBOSE, LOG_TAG, "nativeEnroll(gid=%d, timeout=%d)\n", groupId, timeout);
-    int ret = gContext.device->enroll(gContext.device, hat, groupId, timeout);
+    const int tokenSize = env->GetArrayLength(token);
+    jbyte* tokenData = env->GetByteArrayElements(token, 0);
+    if (tokenSize != sizeof(hw_auth_token_t)) {
+        ALOG(LOG_VERBOSE, LOG_TAG, "nativeEnroll() : invalid token size %d\n", tokenSize);
+        return -1;
+    }
+    int ret = gContext.device->enroll(gContext.device, (hw_auth_token_t*) tokenData, groupId, timeout);
+    env->ReleaseByteArrayElements(token, tokenData, 0);
     return reinterpret_cast<jint>(ret);
 }
 
@@ -154,7 +159,7 @@
     return reinterpret_cast<jint>(ret);
 }
 
-static jint nativeStopAuthentication(JNIEnv* env, jobject clazz, jlong sessionId) {
+static jint nativeStopAuthentication(JNIEnv* env, jobject clazz) {
     ALOG(LOG_VERBOSE, LOG_TAG, "nativeStopAuthentication()\n");
     int ret = gContext.device->cancel(gContext.device);
     return reinterpret_cast<jint>(ret);
@@ -227,8 +232,8 @@
 // TODO: clean up void methods
 static const JNINativeMethod g_methods[] = {
     { "nativeAuthenticate", "(JI)I", (void*)nativeAuthenticate },
-    { "nativeStopAuthentication", "(J)I", (void*)nativeStopAuthentication },
-    { "nativeEnroll", "(JII)I", (void*)nativeEnroll },
+    { "nativeStopAuthentication", "()I", (void*)nativeStopAuthentication },
+    { "nativeEnroll", "([BII)I", (void*)nativeEnroll },
     { "nativePreEnroll", "()J", (void*)nativePreEnroll },
     { "nativeStopEnrollment", "()I", (void*)nativeStopEnrollment },
     { "nativeRemove", "(II)I", (void*)nativeRemove },
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index c5e1933..7e59943 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -5817,12 +5817,12 @@
         final int userId = UserHandle.getCallingUserId();
         LockPatternUtils utils = new LockPatternUtils(mContext);
 
-        // disallow disabling the keyguard if a password is currently set
-        if (!enabled && utils.isSecure(userId)) {
-            return false;
-        }
         long ident = Binder.clearCallingIdentity();
         try {
+            // disallow disabling the keyguard if a password is currently set
+            if (!enabled && utils.isSecure(userId)) {
+                return false;
+            }
             utils.setLockScreenDisabled(!enabled, userId);
         } finally {
             Binder.restoreCallingIdentity(ident);