Merge "multinetwork API: native implementation"
diff --git a/api/current.txt b/api/current.txt
index 912535f..38850ab 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
@@ -14780,6 +14781,7 @@
     method public int getChannelConfiguration();
     method public int getChannelCount();
     method public static int getMinBufferSize(int, int, int);
+    method public int getNativeFrameCount() throws java.lang.IllegalStateException;
     method public int getNotificationMarkerPosition();
     method public int getPositionNotificationPeriod();
     method public int getRecordingState();
@@ -17808,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;
@@ -17826,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);
   }
 
diff --git a/api/system-current.txt b/api/system-current.txt
index 28e8e7f..a570d86 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
@@ -15990,6 +15991,7 @@
     method public int getChannelConfiguration();
     method public int getChannelCount();
     method public static int getMinBufferSize(int, int, int);
+    method public int getNativeFrameCount() throws java.lang.IllegalStateException;
     method public int getNotificationMarkerPosition();
     method public int getPositionNotificationPeriod();
     method public int getRecordingState();
@@ -18054,6 +18056,10 @@
 package android.media.audiopolicy {
 
   public class AudioMix {
+    method public int getMixState();
+    field public static final int MIX_STATE_DISABLED = -1; // 0xffffffff
+    field public static final int MIX_STATE_IDLE = 0; // 0x0
+    field public static final int MIX_STATE_MIXING = 1; // 0x1
     field public static final int ROUTE_FLAG_LOOP_BACK = 2; // 0x2
     field public static final int ROUTE_FLAG_RENDER = 1; // 0x1
   }
@@ -19255,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;
@@ -19273,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);
   }
 
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/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/jni/android_media_AudioRecord.cpp b/core/jni/android_media_AudioRecord.cpp
index ec2b590..c9b0e76 100644
--- a/core/jni/android_media_AudioRecord.cpp
+++ b/core/jni/android_media_AudioRecord.cpp
@@ -524,6 +524,17 @@
 
 
 // ----------------------------------------------------------------------------
+static jint android_media_AudioRecord_get_native_frame_count(JNIEnv *env,  jobject thiz) {
+    sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz);
+    if (lpRecorder == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Unable to retrieve AudioRecord pointer for getNativeFrameCount()");
+        return (jint)AUDIO_JAVA_ERROR;
+    }
+    return lpRecorder->frameCount();
+}
+
+// ----------------------------------------------------------------------------
 static jint android_media_AudioRecord_set_marker_pos(JNIEnv *env,  jobject thiz,
         jint markerPos) {
 
@@ -629,6 +640,8 @@
                              "([FIIZ)I", (void *)android_media_AudioRecord_readInFloatArray},
     {"native_read_in_direct_buffer","(Ljava/lang/Object;I)I",
                                        (void *)android_media_AudioRecord_readInDirectBuffer},
+    {"native_get_native_frame_count",
+                             "()I",    (void *)android_media_AudioRecord_get_native_frame_count},
     {"native_set_marker_pos","(I)I",   (void *)android_media_AudioRecord_set_marker_pos},
     {"native_get_marker_pos","()I",    (void *)android_media_AudioRecord_get_marker_pos},
     {"native_set_pos_update_period",
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/docs/html/design/building-blocks/buttons.jd b/docs/html/design/building-blocks/buttons.jd
index 3a34601..e698f38 100644
--- a/docs/html/design/building-blocks/buttons.jd
+++ b/docs/html/design/building-blocks/buttons.jd
@@ -2,6 +2,14 @@
 page.tags=button,input
 @jd:body
 
+<a class="notice-designers-material"
+  href="http://www.google.com/design/spec/components/buttons.html">
+  <div>
+    <h3>Material Design</h3>
+    <p>Buttons<p>
+  </div>
+</a>
+
 <a class="notice-developers" href="{@docRoot}guide/topics/ui/controls/button.html">
   <div>
     <h3>Developer Docs</h3>
diff --git a/docs/html/design/building-blocks/dialogs.jd b/docs/html/design/building-blocks/dialogs.jd
index 53d99b8..9c91abf 100644
--- a/docs/html/design/building-blocks/dialogs.jd
+++ b/docs/html/design/building-blocks/dialogs.jd
@@ -2,6 +2,14 @@
 page.tags=dialog,alert,popup,toast
 @jd:body
 
+<a class="notice-designers-material"
+  href="http://www.google.com/design/spec/components/dialogs.html">
+  <div>
+    <h3>Material Design</h3>
+    <p>Dialogs<p>
+  </div>
+</a>
+
 <a class="notice-developers" href="{@docRoot}guide/topics/ui/dialogs.html">
   <div>
     <h3>Developer Docs</h3>
@@ -35,7 +43,7 @@
   <h4>Action buttons</h4>
   <p>Action buttons are typically Cancel and/or OK, with OK indicating the preferred or most likely action. However, if the options consist of specific actions such as Close or Wait rather than a confirmation or cancellation of the action described in the content, then all the buttons should be active verbs. Order actions following these rules:</p>
     <ul>
-    
+
     <li>The dismissive action of a dialog is always on the left. Dismissive actions return to the user to the previous state.</li>
     <li>The affirmative actions are on the right. Affirmative actions continue progress toward the user goal that triggered the dialog.</li>
     </ul>
@@ -152,6 +160,15 @@
 away from an email before you send it triggers a "Draft saved" toast to let you know that you can
 continue editing later. Toasts automatically disappear after a timeout.</p>
 
+<a class="notice-designers-material left"
+  href="http://www.google.com/design/spec/components/snackbars-toasts.html">
+  <div>
+    <h3>Material Design</h3>
+    <p>Toasts<p>
+  </div>
+</a>
+
+
 <a class="notice-developers left" href="{@docRoot}guide/topics/ui/notifiers/toasts.html">
   <div>
     <h3>Developer Docs</h3>
diff --git a/docs/html/design/building-blocks/grid-lists.jd b/docs/html/design/building-blocks/grid-lists.jd
index d98637cc..ac3a3ebf 100644
--- a/docs/html/design/building-blocks/grid-lists.jd
+++ b/docs/html/design/building-blocks/grid-lists.jd
@@ -4,6 +4,15 @@
 
 <img src="{@docRoot}design/media/gridview_overview.png">
 
+
+<a class="notice-designers-material"
+  href="http://www.google.com/design/spec/components/grid-lists.html">
+  <div>
+    <h3>Material Design</h3>
+    <p>Grid lists<p>
+  </div>
+</a>
+
 <a class="notice-developers" href="{@docRoot}guide/topics/ui/layout/gridview.html">
   <div>
     <h3>Developer Docs</h3>
diff --git a/docs/html/design/building-blocks/lists.jd b/docs/html/design/building-blocks/lists.jd
index 4949d00..6f69feb 100644
--- a/docs/html/design/building-blocks/lists.jd
+++ b/docs/html/design/building-blocks/lists.jd
@@ -2,10 +2,11 @@
 page.tags=listview,layout
 @jd:body
 
-<a class="notice-developers" href="{@docRoot}guide/topics/ui/layout/listview.html">
+<a class="notice-designers-material"
+  href="http://www.google.com/design/spec/components/lists.html">
   <div>
-    <h3>Developer Docs</h3>
-    <p>List View</p>
+    <h3>Material Design</h3>
+    <p>Lists<p>
   </div>
 </a>
 
@@ -22,7 +23,7 @@
   </div>
   <div class="layout-content-col span-4 with-callouts">
 
-<ol>
+<ol style="margin-bottom: 60px;">
 <li>
 <h4>Section Divider</h4>
 <p>Use section dividers to organize the content of your list into groups and facilitate scanning.</p>
@@ -35,5 +36,21 @@
 </li>
 </ol>
 
+<a class="notice-developers" href="{@docRoot}guide/topics/ui/layout/listview.html">
+  <div>
+    <h3>Developer Docs</h3>
+    <p>List View</p>
+  </div>
+</a>
+
+<a class="notice-designers-material"
+  href="http://www.google.com/design/spec/components/lists-controls.html">
+  <div>
+    <h3>Material Design</h3>
+    <p>Lists: Controls<p>
+  </div>
+</a>
+
+
   </div>
 </div>
diff --git a/docs/html/design/building-blocks/progress.jd b/docs/html/design/building-blocks/progress.jd
index 6946a75..2de75dc 100644
--- a/docs/html/design/building-blocks/progress.jd
+++ b/docs/html/design/building-blocks/progress.jd
@@ -2,6 +2,14 @@
 page.tags=progressbar,download,network
 @jd:body
 
+<a class="notice-designers-material"
+  href="http://www.google.com/design/spec/components/progress-activity.html">
+  <div>
+    <h3>Material Design</h3>
+    <p>Progress and Activity<p>
+  </div>
+</a>
+
 <p>Progress bars and activity indicators signal to users that something is happening that will take a moment.</p>
 <h2 id="progress">Progress bars</h2>
 
diff --git a/docs/html/design/building-blocks/scrolling.jd b/docs/html/design/building-blocks/scrolling.jd
index 41e7cec..04b1e4a 100644
--- a/docs/html/design/building-blocks/scrolling.jd
+++ b/docs/html/design/building-blocks/scrolling.jd
@@ -2,6 +2,7 @@
 page.tags=scrollview,listview
 @jd:body
 
+
 <p>Scrolling allows the user to navigate to content in the overflow using a swipe gesture. The
 scrolling speed is proportional to the speed of the gesture.</p>
 <h2 id="indicator">Scroll Indicator</h2>
diff --git a/docs/html/design/building-blocks/seek-bars.jd b/docs/html/design/building-blocks/seek-bars.jd
index 1465688..5c42102 100644
--- a/docs/html/design/building-blocks/seek-bars.jd
+++ b/docs/html/design/building-blocks/seek-bars.jd
@@ -2,6 +2,14 @@
 page.tags=seekbar,progressbar
 @jd:body
 
+<a class="notice-designers-material"
+  href="http://www.google.com/design/spec/components/sliders.html">
+  <div>
+    <h3>Material Design</h3>
+    <p>Sliders<p>
+  </div>
+</a>
+
 <p>Interactive sliders make it possible to select a value from a continuous or discrete range of values
 by moving the slider thumb. The smallest value is to the left, the largest to the right. The
 interactive nature of the slider makes it a great choice for settings that reflect intensity levels,
diff --git a/docs/html/design/building-blocks/spinners.jd b/docs/html/design/building-blocks/spinners.jd
index f7d80e7..3a74ccf 100644
--- a/docs/html/design/building-blocks/spinners.jd
+++ b/docs/html/design/building-blocks/spinners.jd
@@ -2,6 +2,14 @@
 page.tags=spinner,dropdown
 @jd:body
 
+<a class="notice-designers-material"
+  href="http://www.google.com/design/spec/components/menus.html">
+  <div>
+    <h3>Material Design</h3>
+    <p>Menus<p>
+  </div>
+</a>
+
 <a class="notice-developers" href="{@docRoot}guide/topics/ui/controls/spinner.html">
   <div>
     <h3>Developer Docs</h3>
diff --git a/docs/html/design/building-blocks/switches.jd b/docs/html/design/building-blocks/switches.jd
index d435657..9dd09ca 100644
--- a/docs/html/design/building-blocks/switches.jd
+++ b/docs/html/design/building-blocks/switches.jd
@@ -2,6 +2,14 @@
 page.tags=switch,checkbox,radiobutton,button
 @jd:body
 
+<a class="notice-designers-material"
+  href="http://www.google.com/design/spec/components/selection-controls.html">
+  <div>
+    <h3>Material Design</h3>
+    <p>Selection Controls<p>
+  </div>
+</a>
+
 <p>Switches allow the user to select options. There are three kinds of switches: checkboxes, radio
 buttons, and on/off switches.</p>
 
diff --git a/docs/html/design/building-blocks/tabs.jd b/docs/html/design/building-blocks/tabs.jd
index 93818c3..078de92 100644
--- a/docs/html/design/building-blocks/tabs.jd
+++ b/docs/html/design/building-blocks/tabs.jd
@@ -4,10 +4,18 @@
 
 <img src="{@docRoot}design/media/tabs_overview.png">
 
+<a class="notice-designers-material"
+  href="http://www.google.com/design/spec/components/tabs.html">
+  <div>
+    <h3>Material Design</h3>
+    <p>Tabs<p>
+  </div>
+</a>
+
 <a class="notice-developers" href="{@docRoot}training/implementing-navigation/lateral.html">
   <div>
     <h3>Developer Docs</h3>
-    <p>Creating Swipe Views with Tabs</p>
+    <p>Creating Swipe Views<br>with Tabs</p>
   </div>
 </a>
 
diff --git a/docs/html/design/building-blocks/text-fields.jd b/docs/html/design/building-blocks/text-fields.jd
index e109d5f..19c22f9 100644
--- a/docs/html/design/building-blocks/text-fields.jd
+++ b/docs/html/design/building-blocks/text-fields.jd
@@ -2,6 +2,15 @@
 page.tags=text,edittext,input
 @jd:body
 
+
+<a class="notice-designers-material"
+  href="http://www.google.com/design/spec/components/text-fields.html">
+  <div>
+    <h3>Material Design</h3>
+    <p>Text Fields<p>
+  </div>
+</a>
+
 <a class="notice-developers" href="{@docRoot}guide/topics/ui/controls/text.html">
   <div>
     <h3>Developer Docs</h3>
diff --git a/docs/html/design/downloads/index.jd b/docs/html/design/downloads/index.jd
index 16d61e7..4111bca 100644
--- a/docs/html/design/downloads/index.jd
+++ b/docs/html/design/downloads/index.jd
@@ -12,6 +12,7 @@
 <div class="layout-content-row">
   <div class="layout-content-col span-5">
 
+
 <p>Drag and drop your way to beautifully designed Android apps. The stencils feature the
 rich typography, colors, interactive controls, and icons found throughout Android, along with
 phone and tablet outlines to frame your creations. Source files for icons and controls are also
@@ -23,15 +24,31 @@
     <img src="{@docRoot}design/media/downloads_stencils.png">
 
   </div>
-  <div class="layout-content-col span-4">
 
+  <div class="layout-content-col span-4">
+    <a class="notice-designers-material"
+      style="width:218px;"
+      href="http://www.google.com/design/spec/resources/layout-templates.html">
+      <div>
+        <h3>Material Design</h3>
+        <p>Layout Templates<p>
+      </div>
+    </a>
+  </div>
+
+  <div class="layout-content-col span-4">
+    <a class="notice-designers-material"
+      style="width:218px;"
+      href="http://www.google.com/design/spec/resources/sticker-sheets-icons.html">
+      <div>
+        <h3>Material Design</h3>
+        <p>Sticker Sheets<p>
+      </div>
+    </a>
+  </div>
+
+  <div class="layout-content-col span-4">
 <p>
-  <!--<a class="download-button"  onClick="ga('send', 'event', 'Design', 'Download', 'Fireworks Stencil');"
-    href="{@docRoot}downloads/design/Android_Design_Fireworks_Stencil_20120814.png">Adobe&reg; Fireworks&reg; PNG Stencil</a>
-  <a class="download-button"  onClick="ga('send', 'event', 'Design', 'Download', 'Illustrator Stencil');"
-    href="{@docRoot}downloads/design/Android_Design_Illustrator_Vectors_20120814.ai">Adobe&reg; Illustrator&reg; Stencil</a>
-  <a class="download-button"  onClick="ga('send', 'event', 'Design', 'Download', 'OmniGraffle Stencil');"
-    href="{@docRoot}downloads/design/Android_Design_OmniGraffle_Stencil_20120814.graffle">Omni&reg; OmniGraffle&reg; Stencil</a>-->
   <a class="download-button"  onClick="ga('send', 'event', 'Design', 'Download', 'Photoshop Sources');"
     href="{@docRoot}downloads/design/Android_Design_Stencils_Sources_20131106.zip">Adobe&reg; Photoshop&reg; Stencils and Sources</a>
 </p>
@@ -61,6 +78,18 @@
     <img src="{@docRoot}design/media/iconography_actionbar_style.png">
 
   </div>
+
+  <div class="layout-content-col span-4">
+    <a class="notice-designers-material"
+      style="width:218px;"
+      href="http://www.google.com/design/spec/resources/sticker-sheets-icons.html">
+      <div>
+        <h3>Material Design</h3>
+        <p>Sticker Sheets<p>
+      </div>
+    </a>
+  </div>
+
   <div class="layout-content-col span-4">
 
 <p>
@@ -238,6 +267,18 @@
     <img src="{@docRoot}design/media/downloads_roboto_specimen_preview.png">
 
   </div>
+
+  <div class="layout-content-col span-4">
+    <a class="notice-designers-material"
+      style="width:218px;"
+      href="http://www.google.com/design/spec/resources/roboto-noto-fonts.html">
+      <div>
+        <h3>Material Design</h3>
+        <p>Roboto Font<p>
+      </div>
+    </a>
+  </div>
+
   <div class="layout-content-col span-4">
 
 <p>
@@ -264,6 +305,18 @@
     <img src="{@docRoot}design/media/downloads_color_swatches.png">
 
   </div>
+
+  <div class="layout-content-col span-4">
+    <a class="notice-designers-material"
+      style="width:218px;"
+      href="http://www.google.com/design/spec/resources/color-palettes.html">
+      <div>
+        <h3>Material Design</h3>
+        <p>Color Palettes<p>
+      </div>
+    </a>
+  </div>
+
   <div class="layout-content-col span-4">
 
 <p>
diff --git a/docs/html/design/patterns/accessibility.jd b/docs/html/design/patterns/accessibility.jd
index aaa6f16..b968237 100644
--- a/docs/html/design/patterns/accessibility.jd
+++ b/docs/html/design/patterns/accessibility.jd
@@ -3,6 +3,14 @@
 page.metaDescription=Design an app that's universally accessible to people with visual impairment, color deficiency, hearing loss, and limited dexterity.
 @jd:body
 
+<a class="notice-designers-material"
+  href="http://www.google.com/design/spec/usability/accessibility.html">
+  <div>
+    <h3>Material Design</h3>
+    <p>Accessibility<p>
+  </div>
+</a>
+
 <a class="notice-developers" href="{@docRoot}training/accessibility/index.html">
   <div>
     <h3>Developer Docs</h3>
diff --git a/docs/html/design/patterns/actionbar.jd b/docs/html/design/patterns/actionbar.jd
index f28df01..5467722 100644
--- a/docs/html/design/patterns/actionbar.jd
+++ b/docs/html/design/patterns/actionbar.jd
@@ -5,6 +5,14 @@
 
 <img src="{@docRoot}design/media/action_bar_pattern_overview.png">
 
+
+<a class="notice-designers-material" href="http://www.google.com/design/spec/layout/structure.html#structure-app-bar">
+  <div>
+    <h3>Material Design</h3>
+    <p>App Bar<p>
+  </div>
+</a>
+
 <a class="notice-developers" href="{@docRoot}guide/topics/ui/actionbar.html">
   <div>
     <h3>Developer Docs</h3>
@@ -12,7 +20,6 @@
   </div>
 </a>
 
-
 <p>The <em>action bar</em> is a dedicated piece of real estate at the top of each screen that is generally persistent throughout the app.</p>
 <p><strong>It provides several key functions</strong>:</p>
 <ul>
diff --git a/docs/html/design/patterns/app-structure.jd b/docs/html/design/patterns/app-structure.jd
index e0a11ed..404dd4d 100644
--- a/docs/html/design/patterns/app-structure.jd
+++ b/docs/html/design/patterns/app-structure.jd
@@ -2,6 +2,15 @@
 page.tags=navigation,layout,tablet
 @jd:body
 
+
+<a class="notice-designers-material"
+  href="http://www.google.com/design/spec/patterns/app-structure.html">
+  <div>
+    <h3>Material Design</h3>
+    <p>App Structure<p>
+  </div>
+</a>
+
     <p>Apps come in many varieties that address very different needs. For example:</p>
 <ul>
 <li>Apps such as Calculator or Camera that are built around a single focused activity handled from a
@@ -63,7 +72,7 @@
     <div class="figure-caption">
       Play Music allows navigation among artists, albums, and playlists through rich content display.
       It is also enriched with tailored recommendations and promotions that surface content of interest
-      to the user. Search is readily available from the action bar. 
+      to the user. Search is readily available from the action bar.
     </div>
 
   </div>
@@ -92,7 +101,7 @@
     <img src="{@docRoot}design/media/app_structure_gmail.png">
     <div class="figure-caption">
       A calendar is about productivity, so an efficient, easy-to-skim view with higher data density works
-      well. Navigation supports switching views of day, week, month, and agenda views. 
+      well. Navigation supports switching views of day, week, month, and agenda views.
     </div>
 
   </div>
diff --git a/docs/html/design/patterns/gestures.jd b/docs/html/design/patterns/gestures.jd
index 1ec7094..ada0735 100644
--- a/docs/html/design/patterns/gestures.jd
+++ b/docs/html/design/patterns/gestures.jd
@@ -2,6 +2,14 @@
 page.tags=gesture,input,touch
 @jd:body
 
+<a class="notice-designers-material"
+   href="http://www.google.com/design/spec/patterns/gestures.html">
+  <div>
+    <h3>Material Design</h3>
+    <p>Gestures<p>
+  </div>
+</a>
+
 <p>Gestures allow users to interact with your app by manipulating the screen objects you provide. The
 following table shows the core gesture set that is supported in Android.</p>
 
@@ -92,7 +100,7 @@
       </li>
     </ul>
   </div>
-  
+
 </div>
 
 
diff --git a/docs/html/design/patterns/multi-pane-layouts.jd b/docs/html/design/patterns/multi-pane-layouts.jd
index 4e99462..dbe7d01 100644
--- a/docs/html/design/patterns/multi-pane-layouts.jd
+++ b/docs/html/design/patterns/multi-pane-layouts.jd
@@ -5,6 +5,14 @@
 @jd:body
 
 
+<a class="notice-designers-material"
+  href="http://www.google.com/design/spec/layout/structure.html#structure-ui-regions">
+  <div>
+    <h3>Material Design</h3>
+    <p>UI Regions and Guidance<p>
+  </div>
+</a>
+
 <a class="notice-developers" href="{@docRoot}training/basics/fragments/index.html">
   <div>
     <h3>Developer Docs</h3>
@@ -47,7 +55,7 @@
   <div class="layout-content-col span-8">
 
     <img src="{@docRoot}design/media/multipane_stretch.png">
-    
+
   </div>
   <div class="layout-content-col span-5">
 
@@ -61,7 +69,7 @@
   <div class="layout-content-col span-8">
 
     <img src="{@docRoot}design/media/multipane_stack.png">
-    
+
   </div>
   <div class="layout-content-col span-5">
 
@@ -75,7 +83,7 @@
   <div class="layout-content-col span-8">
 
     <img src="{@docRoot}design/media/multipane_expand.png">
-    
+
   </div>
   <div class="layout-content-col span-5">
 
@@ -89,7 +97,7 @@
   <div class="layout-content-col span-8">
 
     <img src="{@docRoot}design/media/multipane_show.png">
-    
+
   </div>
   <div class="layout-content-col span-5">
 
diff --git a/docs/html/design/patterns/navigation-drawer.jd b/docs/html/design/patterns/navigation-drawer.jd
index 7e63ba6c..dbac459 100644
--- a/docs/html/design/patterns/navigation-drawer.jd
+++ b/docs/html/design/patterns/navigation-drawer.jd
@@ -3,6 +3,14 @@
 @jd:body
 
 
+<a class="notice-designers-material"
+  href="http://www.google.com/design/spec/layout/structure.html#structure-side-nav">
+  <div>
+    <h3>Material Design</h3>
+    <p>Side Nav<p>
+  </div>
+</a>
+
 <a class="notice-developers" href="{@docRoot}training/implementing-navigation/nav-drawer.html">
   <div>
     <h3>Developer Docs</h3>
@@ -12,7 +20,7 @@
 
 
 <p>The navigation drawer is a panel that transitions in from the left edge of the screen and
-displays the app’s main navigation options.</p> 
+displays the app’s main navigation options.</p>
 
 
 <h4>Displaying the navigation drawer</h4>
@@ -92,7 +100,7 @@
 
 <p>The navigation drawer is a reflection of your app’s structure and displays its major
 navigation hubs. Think of navigation hubs as those places in your app that a user will want
-to visit frequently or use as a jumping-off point to other parts of the app. 
+to visit frequently or use as a jumping-off point to other parts of the app.
 At a minimum, the navigation hubs are the top-level views, since they correspond to your app’s
 major functional areas.</p>
 <p> If your app’s structure is deep, you can add screens from lower levels that your users will
@@ -125,8 +133,8 @@
 <h2 id="Content">Content of the Navigation Drawer</h2>
 
 <p> Keep the content of the navigation drawer focused on app navigation. Expose the navigation
-hubs of your app as list items inside the navigation drawer - one item per row. 
-    
+hubs of your app as list items inside the navigation drawer - one item per row.
+
 <div class="layout-content-row">
   <div class="layout-content-col span-8">
   <h4>Titles, icons, and counters</h4>
@@ -139,7 +147,7 @@
   <div class="layout-content-col span-5">
   <img src="{@docRoot}design/media/navigation_drawer_titles_icons.png">
   <div class="figure-caption">
-    Use titles and icons to organize your drawer. 
+    Use titles and icons to organize your drawer.
   </div>
   </div>
 </div>
@@ -149,13 +157,13 @@
   <img src="{@docRoot}design/media/navigation_drawer_collapse.png">
   <div class="figure-caption">
     Collapsible navigation items are split. Use the left side for navigation and the right
-    to collapse and expand items. 
+    to collapse and expand items.
     </div>
   </div>
   <div class="layout-content-col span-5">
   <h4>Collapsible navigation items</h4>
   <p>If you have many views with some subordinate to others, consider collapsing them into one
-  expandable item to conserve space. 
+  expandable item to conserve space.
   The parent in the navigation drawer then turns into a split item. The left side allows
   navigation to the parent item’s view, and the right side collapses or expands the list of
   child items. </p>
@@ -189,7 +197,7 @@
 <img src="{@docRoot}design/media/navigation_drawer_open_overflow.png">
 <div class="figure-caption">
   Clean up the action bar when the drawer is fully expanded. Remove actions that are not needed
-  and display your app's name in the title area. 
+  and display your app's name in the title area.
 </div>
 
 <h4>Actions</h4>
@@ -232,7 +240,7 @@
 
 <img src="{@docRoot}design/media/navigation_drawer_CAB.png">
 <div class="figure-caption">
-  Hide contextual action bars while the drawer is visible. 
+  Hide contextual action bars while the drawer is visible.
 </div>
 
 <p>If the user navigates away from a view with selected content, deselect the content before
diff --git a/docs/html/design/patterns/selection.jd b/docs/html/design/patterns/selection.jd
index be31677..7ed6dcc 100644
--- a/docs/html/design/patterns/selection.jd
+++ b/docs/html/design/patterns/selection.jd
@@ -2,6 +2,14 @@
 page.tags=actionmode,navigation,contextual
 @jd:body
 
+<a class="notice-designers-material"
+  href="http://www.google.com/design/spec/patterns/selection.html">
+  <div>
+    <h3>Material Design</h3>
+    <p>Selection<p>
+  </div>
+</a>
+
 <a class="notice-developers" href="{@docRoot}guide/topics/ui/menus.html#context-menu">
   <div>
     <h3>Developer Docs</h3>
@@ -9,6 +17,7 @@
   </div>
 </a>
 
+
 <p>Android 3.0 changed the <em>long press</em> gesture&mdash;that is, a touch that's held in the same position for a moment&mdash;to be the global gesture to select data.. This affects the way you should
 handle multi-select and contextual actions in your apps.</p>
 
diff --git a/docs/html/design/patterns/settings.jd b/docs/html/design/patterns/settings.jd
index e3a3f05..a24d6c0 100644
--- a/docs/html/design/patterns/settings.jd
+++ b/docs/html/design/patterns/settings.jd
@@ -2,6 +2,14 @@
 page.tags=preferences,sharedpreferences
 @jd:body
 
+<a class="notice-designers-material"
+  href="http://www.google.com/design/spec/patterns/settings.html">
+  <div>
+    <h3>Material Design</h3>
+    <p>Settings<p>
+  </div>
+</a>
+
 <a class="notice-developers" href="{@docRoot}guide/topics/ui/settings.html">
   <div>
     <h3>Developer Docs</h3>
@@ -9,6 +17,7 @@
   </div>
 </a>
 
+
 <p itemprop="description">Settings is a place in your app where users indicate their preferences for how your app should
 behave. This benefits users because:</p>
 
diff --git a/docs/html/design/style/branding.jd b/docs/html/design/style/branding.jd
index 2353a93..b5bb77c 100644
--- a/docs/html/design/style/branding.jd
+++ b/docs/html/design/style/branding.jd
@@ -7,6 +7,14 @@
 
 <h2 id="color">Color</h2>
 
+<a class="notice-designers-material"
+  href="http://www.google.com/design/spec/style/color.html#color-ui-color-application">
+  <div>
+    <h3>Material Design</h3>
+    <p>UI Color Application<p>
+  </div>
+</a>
+
 <p>Use your brand color for accent by overriding the Android framework's default blue in UI elements like checkboxes, progress bars, radio buttons, sliders, tabs, and scroll indicators.</p>
 
 <p>Look for opportunities to use high-contrast color for emphasis, for example, as the background color of the action bar or a primary button. But don't go overboard: not all actions are equal, so use it only for the one or two most important things.</p>
@@ -62,17 +70,32 @@
       Example of a the logo in the action bar. This works well in cases where the brand's logo matches the name of the app.
     </div>
   </div>
-</div> 
+</div>
 
 <h2 id="logo">Icons</h2>
 
+<a class="notice-designers-material" href="http://www.google.com/design/spec/style/icons.html">
+  <div>
+    <h3>Material Design</h3>
+    <p>Icons<p>
+  </div>
+</a>
+
+<p>If you have icons that you're already using for your app on other platforms
+and they have a distinctive look intended to fit your brand, use them on your
+Android app as well. <strong>If you take this approach, make sure your brand styling is
+applied to every single icon in your app.</strong></p>
+
 
 <div class="layout-content-row">
   <div class="layout-content-col span-6">
-    <p>If you have icons that you're already using for your app on other platforms
-    and they have a distinctive look intended to fit your brand, use them on your
-    Android app as well. <strong>If you take this approach, make sure your brand styling is
-    applied to every single icon in your app</strong>.</p>
+    <p>One exception: For any icon in your existing set where the symbol is different from
+    Android's, use Android's symbol but give it your brand's styling. That way, users will
+    understand what the purpose of the icon is based on what they've learned in other
+    Android apps (Design principle:
+    <a href="{@docRoot}design/get-started/principles.html#give-me-tricks">Give me tricks that
+    work everywhere</a>). But the icon will still look like it belongs with all of
+    your other icons as a part of your brand.</p>
 
   </div>
 
@@ -81,13 +104,6 @@
     </div>
   </div>
 </div>
-  <p>One exception: For any icon in your existing set where the symbol is different from
-  Android's, use Android's symbol but give it your brand's styling. That way, users will
-  understand what the purpose of the icon is based on what they've learned in other
-  Android apps (Design principle:
-  <a href="{@docRoot}design/get-started/principles.html#give-me-tricks">Give me tricks that
-  work everywhere</a>). But the icon will still look like it belongs with all of
-  your other icons as a part of your brand.</p>
 
 <div class="layout-content-row">
   <div class="layout-content-col span-6">
diff --git a/docs/html/design/style/color.jd b/docs/html/design/style/color.jd
index e00f6bc..4c5f5ab 100644
--- a/docs/html/design/style/color.jd
+++ b/docs/html/design/style/color.jd
@@ -88,6 +88,13 @@
   }
 </style>
 
+<a class="notice-designers-material" href="http://www.google.com/design/spec/style/color.html">
+  <div>
+    <h3>Material Design</h3>
+    <p>Color<p>
+  </div>
+</a>
+
 <p>Use color primarily for emphasis. Choose colors that fit with your brand and provide good contrast
 between visual components. Note that red and green may be indistinguishable to color-blind users.</p>
 
@@ -129,6 +136,6 @@
           .css('color', color)
           .text(color.toUpperCase());
     });
-    
+
   });
 </script>
diff --git a/docs/html/design/style/devices-displays.jd b/docs/html/design/style/devices-displays.jd
index 6a7234b..1590363 100644
--- a/docs/html/design/style/devices-displays.jd
+++ b/docs/html/design/style/devices-displays.jd
@@ -3,6 +3,14 @@
 
 @jd:body
 
+<a class="notice-designers-material"
+  href="http://www.google.com/design/spec/layout/principles.html">
+  <div>
+    <h3>Material Design</h3>
+    <p>Layout Principles<p>
+  </div>
+</a>
+
 <p>Android powers hundreds of millions of phones, tablets, and other devices in a wide variety of screen sizes and
 form factors. By taking advantage of Android's flexible layout system, you can create apps that
 gracefully scale from large tablets to smaller phones.</p>
@@ -15,12 +23,14 @@
   <div class="layout-content-col span-4">
 
 <h4>Be flexible</h4>
+
 <p>Stretch and compress your layouts to accommodate various heights and widths.</p>
 
   </div>
   <div class="layout-content-col span-5">
 
 <h4>Optimize layouts</h4>
+
 <p>On larger devices, take advantage of extra screen real estate. Create compound views that combine
 multiple views to reveal more content and ease navigation.</p>
 
@@ -37,6 +47,15 @@
   <img src="{@docRoot}design/media/devices_displays_density@2x.png" alt="" height="160" />
 
 <h4>Strategies</h4>
+
+<a class="notice-designers-material"
+  href="http://www.google.com/design/spec/layout/structure.html">
+  <div>
+    <h3>Material Design</h3>
+    <p>Layout Structure<p>
+  </div>
+</a>
+
 <p>So where do you begin when designing for multiple screens? One approach is to work in the base
 standard (normal size and <acronym title="Medium density (160 dpi)">MDPI</acronym>) and scale it up or
 down for the other buckets. Another approach is to start with the device with the largest screen
diff --git a/docs/html/design/style/iconography.jd b/docs/html/design/style/iconography.jd
index 75f541a..e2cdf3f 100644
--- a/docs/html/design/style/iconography.jd
+++ b/docs/html/design/style/iconography.jd
@@ -41,6 +41,14 @@
 
 <h2 id="launcher">Launcher</h2>
 
+<a class="notice-designers-material"
+  href="http://www.google.com/design/spec/style/icons.html#icons-product-icons">
+  <div>
+    <h3>Material Design</h3>
+    <p>Product Icons<p>
+  </div>
+</a>
+
 <p>The launcher icon is the visual representation of your app on the Home or All Apps screen. Since the
 user can change the Home screen's wallpaper, make sure that your launcher icon is clearly visible on
 any type of background.</p>
@@ -123,6 +131,14 @@
 
 <h2 id="action-bar">Action Bar</h2>
 
+<a class="notice-designers-material"
+  href="http://www.google.com/design/spec/style/icons.html#icons-system-icons">
+  <div>
+    <h3>Material Design</h3>
+    <p>System Icons<p>
+  </div>
+</a>
+
 <p>
 
 Action bar icons are graphic buttons that represent the most important actions people can take
@@ -220,6 +236,14 @@
 
 <h2 id="small-contextual">Small / Contextual Icons</h2>
 
+<a class="notice-designers-material"
+  href="http://www.google.com/design/spec/style/icons.html#icons-system-icons">
+  <div>
+    <h3>Material Design</h3>
+    <p>System Icons<p>
+  </div>
+</a>
+
 <p>Within the body of your app, use small icons to surface actions and/or provide status for specific
 items. For example, in the Gmail app, each message has a star icon that marks the message as
 important.</p>
@@ -300,6 +324,15 @@
 
 <h2 id="notification">Notification Icons</h2>
 
+<a class="notice-designers-material"
+  href="http://www.google.com/design/spec/style/icons.html#icons-system-icons">
+  <div>
+    <h3>Material Design</h3>
+    <p>System Icons<p>
+  </div>
+</a>
+
+
 <p>If your app generates notifications, provide an icon that the system can display in the status bar
 whenever a new notification is available.</p>
 
diff --git a/docs/html/design/style/metrics-grids.jd b/docs/html/design/style/metrics-grids.jd
index e92d57e..d7b5f78 100644
--- a/docs/html/design/style/metrics-grids.jd
+++ b/docs/html/design/style/metrics-grids.jd
@@ -5,6 +5,16 @@
 page.image=/design/media/metrics_closeup.png
 @jd:body
 
+
+<a class="notice-designers-material"
+  href="http://www.google.com/design/spec/layout/metrics-keylines.html">
+  <div>
+    <h3>Material Design</h3>
+    <p>Metrics and Keylines<p>
+  </div>
+</a>
+
+
 <p>Devices vary not only in physical size, but also in screen density (<acronym title="Dots per
 inch">DPI</acronym>). To simplify the way you design for multiple screens, think of each device as
 falling into a particular size bucket and density bucket:</p>
diff --git a/docs/html/design/style/themes.jd b/docs/html/design/style/themes.jd
index 2dc8ead..3313a2b 100644
--- a/docs/html/design/style/themes.jd
+++ b/docs/html/design/style/themes.jd
@@ -17,6 +17,16 @@
   </div>
   <div class="layout-content-col span-7">
 
+
+<a class="notice-designers-material"
+  href="http://www.google.com/design/spec/style/color.html#color-themes">
+  <div>
+    <h3>Material Design</h3>
+    <p>Color Themes<p>
+  </div>
+</a>
+
+
 <p>Themes are Android's mechanism for applying a consistent style to an app or activity.
 The style specifies the visual properties of the elements that make up your user interface,
 such as color, height, padding and font size. To promote greater cohesion between all apps
@@ -34,7 +44,7 @@
 
 <div class="note develop">
 <p><strong>Developer Guide</strong></p>
-  <p>For information about how to apply themes such as Holo Light and Dark, and 
+  <p>For information about how to apply themes such as Holo Light and Dark, and
   how to build your own themes, see the
   <a href="{@docRoot}guide/topics/ui/themes.html">Styles and Themes</a> API guide.</p>
 </div>
diff --git a/docs/html/design/style/touch-feedback.jd b/docs/html/design/style/touch-feedback.jd
index 9f36fed..b6d6407 100644
--- a/docs/html/design/style/touch-feedback.jd
+++ b/docs/html/design/style/touch-feedback.jd
@@ -2,8 +2,17 @@
 page.tags=input,button
 @jd:body
 
-    <div class="layout-content-row" style="margin-bottom: -100px">
-  <div class="layout-content-col span-7">
+<div class="layout-content-row" style="margin-bottom: -100px">
+<div class="layout-content-col span-7">
+
+<a class="notice-designers-material"
+  href="http://www.google.com/design/spec/animation/responsive-interaction.html">
+  <div>
+    <h3>Material Design</h3>
+    <p>Responsive Interaction<p>
+  </div>
+</a>
+
 
 <p>Use illumination and dimming to respond to touches, reinforce the resulting behaviors
 of gestures, and indicate what actions are enabled and disabled.</p>
diff --git a/docs/html/design/style/typography.jd b/docs/html/design/style/typography.jd
index a665097..2f8e91b 100644
--- a/docs/html/design/style/typography.jd
+++ b/docs/html/design/style/typography.jd
@@ -9,7 +9,17 @@
     <img src="{@docRoot}design/media/typography_main.png">
 
   </div>
-  <div class="layout-content-col span-5">
+
+<a class="notice-designers-material"
+  style="width: 278px;"
+  href="http://www.google.com/design/spec/style/typography.html">
+  <div>
+    <h3>Material Design</h3>
+    <p>Typography<p>
+  </div>
+</a>
+
+<div class="layout-content-col span-5">
 
 <p>
   <a class="download-button"  onClick="ga('send', 'event', 'Design', 'Download', 'Roboto ZIP');"
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index fc0189c..9e1e055 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -398,8 +398,8 @@
      * default output sample rate of the device (see
      * {@link AudioManager#PROPERTY_OUTPUT_SAMPLE_RATE}), its channel configuration will be
      * {@link AudioFormat#CHANNEL_IN_DEFAULT}.
-     * <br>Failing to set an adequate buffer size with {@link #setBufferSizeInBytes(int)} will
-     * prevent the successful creation of an <code>AudioRecord</code> instance.
+     * <br>If the buffer size is not specified with {@link #setBufferSizeInBytes(int)},
+     * the minimum buffer size for the source is used.
      */
     public static class Builder {
         private AudioAttributes mAttributes;
@@ -473,7 +473,9 @@
          * during the recording. New audio data can be read from this buffer in smaller chunks
          * than this size. See {@link #getMinBufferSize(int, int, int)} to determine the minimum
          * required buffer size for the successful creation of an AudioRecord instance.
-         * Using values smaller than getMinBufferSize() will result in an initialization failure.
+         * Since bufferSizeInBytes may be internally increased to accommodate the source
+         * requirements, use {@link #getNativeFrameCount()} to determine the actual buffer size
+         * in frames.
          * @param bufferSizeInBytes a value strictly greater than 0
          * @return the same Builder instance.
          * @throws IllegalArgumentException
@@ -520,6 +522,13 @@
                         .build();
             }
             try {
+                // If the buffer size is not specified,
+                // use a single frame for the buffer size and let the
+                // native code figure out the minimum buffer size.
+                if (mBufferSizeInBytes == 0) {
+                    mBufferSizeInBytes = mFormat.getChannelCount()
+                            * mFormat.getBytesPerSample(mFormat.getEncoding());
+                }
                 return new AudioRecord(mAttributes, mFormat, mBufferSizeInBytes, mSessionId);
             } catch (IllegalArgumentException e) {
                 throw new UnsupportedOperationException(e.getMessage());
@@ -711,6 +720,20 @@
     }
 
     /**
+     *  Returns the "native frame count" of the <code>AudioRecord</code> buffer.
+     *  This is greater than or equal to the bufferSizeInBytes converted to frame units
+     *  specified in the <code>AudioRecord</code> constructor or Builder.
+     *  The native frame count may be enlarged to accommodate the requirements of the
+     *  source on creation or if the <code>AudioRecord</code>
+     *  is subsequently rerouted.
+     *  @return current size in frames of the <code>AudioRecord</code> buffer.
+     *  @throws IllegalStateException
+     */
+    public int getNativeFrameCount() throws IllegalStateException {
+        return native_get_native_frame_count();
+    }
+
+    /**
      * Returns the notification marker position expressed in frames.
      */
     public int getNotificationMarkerPosition() {
@@ -1173,6 +1196,8 @@
 
     private native final int native_read_in_direct_buffer(Object jBuffer, int sizeInBytes);
 
+    private native final int native_get_native_frame_count();
+
     private native final int native_set_marker_pos(int marker);
     private native final int native_get_marker_pos();
 
diff --git a/media/java/android/media/audiopolicy/AudioMix.java b/media/java/android/media/audiopolicy/AudioMix.java
index 1806662..6aa4d8a 100644
--- a/media/java/android/media/audiopolicy/AudioMix.java
+++ b/media/java/android/media/audiopolicy/AudioMix.java
@@ -36,6 +36,7 @@
     private int mRouteFlags;
     private String mRegistrationId;
     private int mMixType = MIX_TYPE_INVALID;
+    private int mMixState = MIX_STATE_DISABLED;
 
     /**
      * All parameters are guaranteed valid through the Builder.
@@ -48,6 +49,7 @@
         mMixType = rule.getTargetMixType();
     }
 
+    // ROUTE_FLAG_* values to keep in sync with frameworks/av/include/media/AudioPolicy.h
     /**
      * An audio mix behavior where the output of the mix is sent to the original destination of
      * the audio signal, i.e. an output device for an output mix, or a recording for an input mix.
@@ -62,6 +64,7 @@
     @SystemApi
     public static final int ROUTE_FLAG_LOOP_BACK = 0x1 << 1;
 
+    // MIX_TYPE_* values to keep in sync with frameworks/av/include/media/AudioPolicy.h
     /**
      * @hide
      * Invalid mix type, default value.
@@ -78,6 +81,39 @@
      */
     public static final int MIX_TYPE_RECORDERS = 1;
 
+
+    // MIX_STATE_* values to keep in sync with frameworks/av/include/media/AudioPolicy.h
+    /**
+     * @hide
+     * State of a mix before its policy is enabled.
+     */
+    @SystemApi
+    public static final int MIX_STATE_DISABLED = -1;
+    /**
+     * @hide
+     * State of a mix when there is no audio to mix.
+     */
+    @SystemApi
+    public static final int MIX_STATE_IDLE = 0;
+    /**
+     * @hide
+     * State of a mix that is actively mixing audio.
+     */
+    @SystemApi
+    public static final int MIX_STATE_MIXING = 1;
+
+    /**
+     * @hide
+     * The current mixing state.
+     * @return one of {@link #MIX_STATE_DISABLED}, {@link #MIX_STATE_IDLE},
+     *          {@link #MIX_STATE_MIXING}.
+     */
+    @SystemApi
+    public int getMixState() {
+        return mMixState;
+    }
+
+
     int getRouteFlags() {
         return mRouteFlags;
     }
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/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/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 },