Merge "Suppress warnings of MultiDexLegacyAndException" into nyc-dev
diff --git a/api/current.txt b/api/current.txt
index 2eba30f..5d28d2c 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -8534,7 +8534,6 @@
     field public static final java.lang.String ACTION_NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL";
     field public static final java.lang.String ACTION_OPEN_DOCUMENT = "android.intent.action.OPEN_DOCUMENT";
     field public static final java.lang.String ACTION_OPEN_DOCUMENT_TREE = "android.intent.action.OPEN_DOCUMENT_TREE";
-    field public static final java.lang.String ACTION_OPEN_EXTERNAL_DIRECTORY = "android.intent.action.OPEN_EXTERNAL_DIRECTORY";
     field public static final java.lang.String ACTION_PACKAGES_SUSPENDED = "android.intent.action.PACKAGES_SUSPENDED";
     field public static final java.lang.String ACTION_PACKAGES_UNSUSPENDED = "android.intent.action.PACKAGES_UNSUSPENDED";
     field public static final java.lang.String ACTION_PACKAGE_ADDED = "android.intent.action.PACKAGE_ADDED";
@@ -29365,11 +29364,27 @@
 
   public class StorageManager {
     method public java.lang.String getMountedObbPath(java.lang.String);
+    method public android.os.storage.StorageVolume getPrimaryVolume();
+    method public android.os.storage.StorageVolume[] getVolumeList();
     method public boolean isObbMounted(java.lang.String);
     method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
     method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
   }
 
+  public class StorageVolume implements android.os.Parcelable {
+    method public android.content.Intent createAccessIntent(java.lang.String);
+    method public int describeContents();
+    method public java.lang.String getDescription(android.content.Context);
+    method public java.lang.String getState();
+    method public java.lang.String getUuid();
+    method public boolean isEmulated();
+    method public boolean isPrimary();
+    method public boolean isRemovable();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.os.storage.StorageVolume> CREATOR;
+    field public static final java.lang.String EXTRA_STORAGE_VOLUME = "android.os.storage.extra.STORAGE_VOLUME";
+  }
+
 }
 
 package android.preference {
diff --git a/api/system-current.txt b/api/system-current.txt
index 433090e..a2943ff 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -8840,7 +8840,6 @@
     field public static final java.lang.String ACTION_NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL";
     field public static final java.lang.String ACTION_OPEN_DOCUMENT = "android.intent.action.OPEN_DOCUMENT";
     field public static final java.lang.String ACTION_OPEN_DOCUMENT_TREE = "android.intent.action.OPEN_DOCUMENT_TREE";
-    field public static final java.lang.String ACTION_OPEN_EXTERNAL_DIRECTORY = "android.intent.action.OPEN_EXTERNAL_DIRECTORY";
     field public static final java.lang.String ACTION_PACKAGES_SUSPENDED = "android.intent.action.PACKAGES_SUSPENDED";
     field public static final java.lang.String ACTION_PACKAGES_UNSUSPENDED = "android.intent.action.PACKAGES_UNSUSPENDED";
     field public static final java.lang.String ACTION_PACKAGE_ADDED = "android.intent.action.PACKAGE_ADDED";
@@ -24282,19 +24281,26 @@
 package android.media.soundtrigger {
 
   public final class SoundTriggerDetector {
-    method public boolean startRecognition();
+    method public boolean startRecognition(int);
     method public boolean stopRecognition();
+    field public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 2; // 0x2
+    field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1
   }
 
-  public abstract class SoundTriggerDetector.Callback {
+  public static abstract class SoundTriggerDetector.Callback {
     ctor public SoundTriggerDetector.Callback();
     method public abstract void onAvailabilityChanged(int);
-    method public abstract void onDetected();
+    method public abstract void onDetected(android.media.soundtrigger.SoundTriggerDetector.EventPayload);
     method public abstract void onError();
     method public abstract void onRecognitionPaused();
     method public abstract void onRecognitionResumed();
   }
 
+  public static class SoundTriggerDetector.EventPayload {
+    method public android.media.AudioFormat getCaptureAudioFormat();
+    method public byte[] getTriggerAudio();
+  }
+
   public final class SoundTriggerManager {
     method public android.media.soundtrigger.SoundTriggerDetector createSoundTriggerDetector(java.util.UUID, android.media.soundtrigger.SoundTriggerDetector.Callback, android.os.Handler);
     method public void deleteModel(java.util.UUID);
@@ -31712,11 +31718,27 @@
 
   public class StorageManager {
     method public java.lang.String getMountedObbPath(java.lang.String);
+    method public android.os.storage.StorageVolume getPrimaryVolume();
+    method public android.os.storage.StorageVolume[] getVolumeList();
     method public boolean isObbMounted(java.lang.String);
     method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
     method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
   }
 
+  public class StorageVolume implements android.os.Parcelable {
+    method public android.content.Intent createAccessIntent(java.lang.String);
+    method public int describeContents();
+    method public java.lang.String getDescription(android.content.Context);
+    method public java.lang.String getState();
+    method public java.lang.String getUuid();
+    method public boolean isEmulated();
+    method public boolean isPrimary();
+    method public boolean isRemovable();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.os.storage.StorageVolume> CREATOR;
+    field public static final java.lang.String EXTRA_STORAGE_VOLUME = "android.os.storage.extra.STORAGE_VOLUME";
+  }
+
 }
 
 package android.preference {
diff --git a/api/test-current.txt b/api/test-current.txt
index 2fff729..d42c18c 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -8539,7 +8539,6 @@
     field public static final java.lang.String ACTION_NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL";
     field public static final java.lang.String ACTION_OPEN_DOCUMENT = "android.intent.action.OPEN_DOCUMENT";
     field public static final java.lang.String ACTION_OPEN_DOCUMENT_TREE = "android.intent.action.OPEN_DOCUMENT_TREE";
-    field public static final java.lang.String ACTION_OPEN_EXTERNAL_DIRECTORY = "android.intent.action.OPEN_EXTERNAL_DIRECTORY";
     field public static final java.lang.String ACTION_PACKAGES_SUSPENDED = "android.intent.action.PACKAGES_SUSPENDED";
     field public static final java.lang.String ACTION_PACKAGES_UNSUSPENDED = "android.intent.action.PACKAGES_UNSUSPENDED";
     field public static final java.lang.String ACTION_PACKAGE_ADDED = "android.intent.action.PACKAGE_ADDED";
@@ -29375,11 +29374,27 @@
 
   public class StorageManager {
     method public java.lang.String getMountedObbPath(java.lang.String);
+    method public android.os.storage.StorageVolume getPrimaryVolume();
+    method public android.os.storage.StorageVolume[] getVolumeList();
     method public boolean isObbMounted(java.lang.String);
     method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
     method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
   }
 
+  public class StorageVolume implements android.os.Parcelable {
+    method public android.content.Intent createAccessIntent(java.lang.String);
+    method public int describeContents();
+    method public java.lang.String getDescription(android.content.Context);
+    method public java.lang.String getState();
+    method public java.lang.String getUuid();
+    method public boolean isEmulated();
+    method public boolean isPrimary();
+    method public boolean isRemovable();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.os.storage.StorageVolume> CREATOR;
+    field public static final java.lang.String EXTRA_STORAGE_VOLUME = "android.os.storage.extra.STORAGE_VOLUME";
+  }
+
 }
 
 package android.preference {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index ea58e29..e3adbda 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -49,6 +49,7 @@
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
@@ -3988,8 +3989,12 @@
             a.recycle();
             if (colorPrimary != 0) {
                 ActivityManager.TaskDescription td = new ActivityManager.TaskDescription();
-                td.setPrimaryColor(colorPrimary);
-                td.setBackgroundColor(colorBg);
+                if (Color.alpha(colorPrimary) == 0xFF) {
+                    td.setPrimaryColor(colorPrimary);
+                }
+                if (Color.alpha(colorBg) == 0xFF) {
+                    td.setBackgroundColor(colorBg);
+                }
                 setTaskDescription(td);
             }
         }
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index ab7d708..91eabcc 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -21,6 +21,7 @@
 import android.annotation.Nullable;
 import android.annotation.StringRes;
 import android.annotation.XmlRes;
+import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Intent;
@@ -1759,7 +1760,7 @@
         return candidates;
     }
 
-    private static boolean isPackageCandidateVolume(
+    private boolean isPackageCandidateVolume(
             ContextImpl context, ApplicationInfo app, VolumeInfo vol) {
         final boolean forceAllowOnExternal = Settings.Global.getInt(
                 context.getContentResolver(), Settings.Global.FORCE_ALLOW_ON_EXTERNAL, 0) != 0;
@@ -1789,6 +1790,15 @@
             return app.isInternal();
         }
 
+        // Some apps can't be moved. (e.g. device admins)
+        try {
+            if (mPM.isPackageDeviceAdminOnAnyUser(app.packageName)) {
+                return false;
+            }
+        } catch (RemoteException e) {
+            throw new RuntimeException("Package manager has died", e);
+        }
+
         // Otherwise we can move to any private volume
         return (vol.getType() == VolumeInfo.TYPE_PRIVATE);
     }
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 5b8e09c..7e7c5ec 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1033,9 +1033,18 @@
      * @hide
      */
     public boolean packageHasActiveAdmins(String packageName) {
+        return packageHasActiveAdmins(packageName, myUserId());
+    }
+
+    /**
+     * Used by package administration code to determine if a package can be stopped
+     * or uninstalled.
+     * @hide
+     */
+    public boolean packageHasActiveAdmins(String packageName, int userId) {
         if (mService != null) {
             try {
-                return mService.packageHasActiveAdmins(packageName, myUserId());
+                return mService.packageHasActiveAdmins(packageName, userId);
             } catch (RemoteException e) {
                 Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, e);
             }
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index b476a25..8f2b9c8 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3187,38 +3187,6 @@
             ACTION_OPEN_DOCUMENT_TREE = "android.intent.action.OPEN_DOCUMENT_TREE";
 
     /**
-     * Activity Action: Give access to a standard storage directory after obtaining the user's
-     * approval.
-     * <p>
-     * When invoked, the system will ask the user to grant access to the requested directory (and
-     * its descendants).
-     * <p>
-     * To gain access to descendant (child, grandchild, etc) documents, use
-     * {@link DocumentsContract#buildDocumentUriUsingTree(Uri, String)} and
-     * {@link DocumentsContract#buildChildDocumentsUriUsingTree(Uri, String)} with the returned URI.
-     * <p>
-     * Input: full path to a standard directory, in the form of
-     * {@code STORAGE_ROOT + STANDARD_DIRECTORY}, where {@code STORAGE_ROOT} is the physical path of
-     * a storage container, and {@code STANDARD_DIRECTORY} is one of
-     * {@link Environment#DIRECTORY_MUSIC}, {@link Environment#DIRECTORY_PODCASTS},
-     * {@link Environment#DIRECTORY_RINGTONES}, {@link Environment#DIRECTORY_ALARMS},
-     * {@link Environment#DIRECTORY_NOTIFICATIONS}, {@link Environment#DIRECTORY_PICTURES},
-     * {@link Environment#DIRECTORY_MOVIES}, {@link Environment#DIRECTORY_DOWNLOADS},
-     * {@link Environment#DIRECTORY_DCIM}, or {@link Environment#DIRECTORY_DOCUMENTS}
-     * <p>
-     * For example, to open the "Pictures" folder in the default external storage, the intent's data
-     * would be: {@code Uri.fromFile(new File(Environment.getExternalStorageDirectory(),
-     * Environment.DIRECTORY_PICTURES))}.
-     * <p>
-     * Output: The URI representing the requested directory tree.
-     *
-     * @see DocumentsContract
-     */
-    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
-    public static final String
-            ACTION_OPEN_EXTERNAL_DIRECTORY = "android.intent.action.OPEN_EXTERNAL_DIRECTORY";
-
-    /**
      * Broadcast Action: List of dynamic sensor is changed due to new sensor being connected or
      * exisiting sensor being disconnected.
      *
@@ -8952,7 +8920,6 @@
                 case ACTION_MEDIA_SCANNER_SCAN_FILE:
                 case ACTION_PACKAGE_NEEDS_VERIFICATION:
                 case ACTION_PACKAGE_VERIFIED:
-                case ACTION_OPEN_EXTERNAL_DIRECTORY: // TODO: temporary until bug 26742218 is fixed
                     // Ignore legacy actions
                     break;
                 default:
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index ccb5f82..d6b674c 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -537,4 +537,6 @@
     boolean setRequiredForSystemUser(String packageName, boolean systemUserApp);
 
     String getServicesSystemSharedLibraryPackageName();
+
+    boolean isPackageDeviceAdminOnAnyUser(String packageName);
 }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index c9ee4f3..0967608 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1233,6 +1233,14 @@
     public static final int MOVE_FAILED_OPERATION_PENDING = -7;
 
     /**
+     * Error code that is passed to the {@link IPackageMoveObserver} if the
+     * specified package cannot be moved since it contains a device admin.
+     *
+     * @hide
+     */
+    public static final int MOVE_FAILED_DEVICE_ADMIN = -8;
+
+    /**
      * Flag parameter for {@link #movePackage} to indicate that
      * the package should be moved to internal storage if its
      * been installed on external media.
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 70f9cc5..1085b1e 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -339,7 +339,7 @@
      * <p>
      * Writing to this path requires the
      * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} permission,
-     * and starting in read access requires the
+     * and starting in {@link android.os.Build.VERSION_CODES#KITKAT}, read access requires the
      * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission,
      * which is automatically granted if you hold the write permission.
      * <p>
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index e7dfbd7..97ee90d 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -865,7 +865,12 @@
         }
     }
 
-    /** {@hide} */
+    /**
+     * Gets the list of shared/external storage volumes available to the current user.
+     *
+     * <p>It always contains the primary storage volume, plus any additional external volume(s)
+     * available in the device, such as SD cards or attached USB drives.
+     */
     public @NonNull StorageVolume[] getVolumeList() {
         return getVolumeList(mContext.getUserId(), 0);
     }
@@ -914,7 +919,9 @@
         return paths;
     }
 
-    /** {@hide} */
+    /**
+     * Gets the primary shared/external storage volume available to the current user.
+     */
     public @NonNull StorageVolume getPrimaryVolume() {
         return getPrimaryVolume(getVolumeList());
     }
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index 1408202..d860c7d 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -16,11 +16,17 @@
 
 package android.os.storage;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
+import android.content.Intent;
 import android.net.TrafficStats;
+import android.net.Uri;
+import android.os.Environment;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.UserHandle;
+import android.provider.DocumentsContract;
 
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
@@ -29,14 +35,47 @@
 import java.io.File;
 
 /**
- * Information about a storage volume that may be mounted. This is a legacy
- * specialization of {@link VolumeInfo} which describes the volume for a
- * specific user.
- * <p>
- * This class may be deprecated in the future.
+ * Information about a shared/external storage volume for a specific user.
  *
- * @hide
+ * <p>
+ * A device always has one (and one only) primary storage volume, but it could have extra volumes,
+ * like SD cards and USB drives. This object represents the logical view of a storage
+ * volume for a specific user: different users might have different views for the same physical
+ * volume (for example, if the volume is a built-in emulated storage).
+ *
+ * <p>
+ * The storage volume is not necessarily mounted, applications should use {@link #getState()} to
+ * verify its state.
+ *
+ * <p>
+ * Applications willing to read or write to this storage volume needs to get a permission from the
+ * user first, which can be achieved in the following ways:
+ *
+ * <ul>
+ * <li>To get access to standard directories (like the {@link Environment#DIRECTORY_PICTURES}), they
+ * can use the {@link #createAccessIntent(String)}. This is the recommend way, since it provides a
+ * simpler API and narrows the access to the given directory (and its descendants).
+ * <li>To get access to any directory (and its descendants), they can use the Storage Acess
+ * Framework APIs (such as {@link Intent#ACTION_OPEN_DOCUMENT} and
+ * {@link Intent#ACTION_OPEN_DOCUMENT_TREE}, although these APIs do not guarantee the user will
+ * select this specific volume.
+ * <li>To get read and write access to the primary storage volume, applications can declare the
+ * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} and
+ * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} permissions respectively, with the
+ * latter including the former. This approach is discouraged, since users may be hesitant to grant
+ * broad access to all files contained on a storage device.
+ * </ul>
+ *
+ * <p>It can be obtained through {@link StorageManager#getVolumeList()} and
+ * {@link StorageManager#getPrimaryVolume()} and also as an extra in some broadcasts
+ * (see {@link #EXTRA_STORAGE_VOLUME}).
+ *
+ * <p>
+ * See {@link Environment#getExternalStorageDirectory()} for more info about shared/external
+ * storage semantics.
  */
+// NOTE: This is a legacy specialization of VolumeInfo which describes the volume for a specific
+// user, but is now part of the public API.
 public class StorageVolume implements Parcelable {
 
     private final String mId;
@@ -53,14 +92,36 @@
     private final String mFsUuid;
     private final String mState;
 
-    // StorageVolume extra for ACTION_MEDIA_REMOVED, ACTION_MEDIA_UNMOUNTED, ACTION_MEDIA_CHECKING,
-    // ACTION_MEDIA_NOFS, ACTION_MEDIA_MOUNTED, ACTION_MEDIA_SHARED, ACTION_MEDIA_UNSHARED,
-    // ACTION_MEDIA_BAD_REMOVAL, ACTION_MEDIA_UNMOUNTABLE and ACTION_MEDIA_EJECT broadcasts.
-    public static final String EXTRA_STORAGE_VOLUME = "storage_volume";
+    /**
+     * Name of the {@link Parcelable} extra in the {@link Intent#ACTION_MEDIA_REMOVED},
+     * {@link Intent#ACTION_MEDIA_UNMOUNTED}, {@link Intent#ACTION_MEDIA_CHECKING},
+     * {@link Intent#ACTION_MEDIA_NOFS}, {@link Intent#ACTION_MEDIA_MOUNTED},
+     * {@link Intent#ACTION_MEDIA_SHARED}, {@link Intent#ACTION_MEDIA_BAD_REMOVAL},
+     * {@link Intent#ACTION_MEDIA_UNMOUNTABLE}, and {@link Intent#ACTION_MEDIA_EJECT} broadcast that
+     * contains a {@link StorageVolume}.
+     */
+    // Also sent on ACTION_MEDIA_UNSHARED, which is @hide
+    public static final String EXTRA_STORAGE_VOLUME = "android.os.storage.extra.STORAGE_VOLUME";
 
+    /**
+     * Name of the String extra used by {@link #createAccessIntent(String) createAccessIntent}.
+     *
+     * @hide
+     */
+    public static final String EXTRA_DIRECTORY_NAME = "android.os.storage.extra.DIRECTORY_NAME";
+
+    /**
+     * Name of the intent used by {@link #createAccessIntent(String) createAccessIntent}.
+     */
+    private static final String ACTION_OPEN_EXTERNAL_DIRECTORY =
+            "android.os.storage.action.OPEN_EXTERNAL_DIRECTORY";
+
+    /** {@hide} */
     public static final int STORAGE_ID_INVALID = 0x00000000;
+    /** {@hide} */
     public static final int STORAGE_ID_PRIMARY = 0x00010001;
 
+    /** {@hide} */
     public StorageVolume(String id, int storageId, File path, String description, boolean primary,
             boolean removable, boolean emulated, long mtpReserveSize, boolean allowMassStorage,
             long maxFileSize, UserHandle owner, String fsUuid, String state) {
@@ -95,6 +156,7 @@
         mState = in.readString();
     }
 
+    /** {@hide} */
     public String getId() {
         return mId;
     }
@@ -103,17 +165,19 @@
      * Returns the mount path for the volume.
      *
      * @return the mount path
+     * @hide
      */
     public String getPath() {
         return mPath.toString();
     }
 
+    /** {@hide} */
     public File getPathFile() {
         return mPath;
     }
 
     /**
-     * Returns a user visible description of the volume.
+     * Returns a user-visible description of the volume.
      *
      * @return the volume description
      */
@@ -121,6 +185,10 @@
         return mDescription;
     }
 
+    /**
+     * Returns true if the volume is the primary shared/external storage, which is the volume
+     * backed by {@link Environment#getExternalStorageDirectory()}.
+     */
     public boolean isPrimary() {
         return mPrimary;
     }
@@ -148,6 +216,7 @@
      * this is also used for the storage_id column in the media provider.
      *
      * @return MTP storage ID
+     * @hide
      */
     public int getStorageId() {
         return mStorageId;
@@ -164,6 +233,7 @@
      * too close to full.
      *
      * @return MTP reserve space
+     * @hide
      */
     public int getMtpReserveSpace() {
         return (int) (mMtpReserveSize / TrafficStats.MB_IN_BYTES);
@@ -173,6 +243,7 @@
      * Returns true if this volume can be shared via USB mass storage.
      *
      * @return whether mass storage is allowed
+     * @hide
      */
     public boolean allowMassStorage() {
         return mAllowMassStorage;
@@ -182,22 +253,28 @@
      * Returns maximum file size for the volume, or zero if it is unbounded.
      *
      * @return maximum file size
+     * @hide
      */
     public long getMaxFileSize() {
         return mMaxFileSize;
     }
 
+    /** {@hide} */
     public UserHandle getOwner() {
         return mOwner;
     }
 
-    public String getUuid() {
+    /**
+     * Gets the volume UUID, if any.
+     */
+    public @Nullable String getUuid() {
         return mFsUuid;
     }
 
     /**
      * Parse and return volume UUID as FAT volume ID, or return -1 if unable to
      * parse or UUID is unknown.
+     * @hide
      */
     public int getFatVolumeId() {
         if (mFsUuid == null || mFsUuid.length() != 9) {
@@ -210,14 +287,57 @@
         }
     }
 
+    /** {@hide} */
     public String getUserLabel() {
         return mDescription;
     }
 
+    /**
+     * Returns the current state of the volume.
+     *
+     * @return one of {@link Environment#MEDIA_UNKNOWN}, {@link Environment#MEDIA_REMOVED},
+     *         {@link Environment#MEDIA_UNMOUNTED}, {@link Environment#MEDIA_CHECKING},
+     *         {@link Environment#MEDIA_NOFS}, {@link Environment#MEDIA_MOUNTED},
+     *         {@link Environment#MEDIA_MOUNTED_READ_ONLY}, {@link Environment#MEDIA_SHARED},
+     *         {@link Environment#MEDIA_BAD_REMOVAL}, or {@link Environment#MEDIA_UNMOUNTABLE}.
+     */
     public String getState() {
         return mState;
     }
 
+    /**
+     * Builds an intent to give access to a standard storage directory after obtaining the user's
+     * approval.
+     * <p>
+     * When invoked, the system will ask the user to grant access to the requested directory (and
+     * its descendants). The result of the request will be returned to the activity through the
+     * {@code onActivityResult} method.
+     * <p>
+     * To gain access to descendants (child, grandchild, etc) documents, use
+     * {@link DocumentsContract#buildDocumentUriUsingTree(Uri, String)}, or
+     * {@link DocumentsContract#buildChildDocumentsUriUsingTree(Uri, String)} with the returned URI.
+     *
+     * <b>If your application only needs to store internal data, consider using
+     * {@link Context#getExternalFilesDirs(String) Context.getExternalFilesDirs},
+     * {@link Context#getExternalCacheDirs()}, or
+     * {@link Context#getExternalMediaDirs()}, which require no permissions to read or write.
+     *
+     * @param directoryName must be one of
+     * {@link Environment#DIRECTORY_MUSIC}, {@link Environment#DIRECTORY_PODCASTS},
+     * {@link Environment#DIRECTORY_RINGTONES}, {@link Environment#DIRECTORY_ALARMS},
+     * {@link Environment#DIRECTORY_NOTIFICATIONS}, {@link Environment#DIRECTORY_PICTURES},
+     * {@link Environment#DIRECTORY_MOVIES}, {@link Environment#DIRECTORY_DOWNLOADS},
+     * {@link Environment#DIRECTORY_DCIM}, or {@link Environment#DIRECTORY_DOCUMENTS}
+     *
+     * @see DocumentsContract
+     */
+    public Intent createAccessIntent(@NonNull String directoryName) {
+        final Intent intent = new Intent(ACTION_OPEN_EXTERNAL_DIRECTORY);
+        intent.putExtra(EXTRA_STORAGE_VOLUME, this);
+        intent.putExtra(EXTRA_DIRECTORY_NAME, directoryName);
+        return intent;
+    }
+
     @Override
     public boolean equals(Object obj) {
         if (obj instanceof StorageVolume && mPath != null) {
@@ -234,11 +354,23 @@
 
     @Override
     public String toString() {
+        final StringBuilder buffer = new StringBuilder("StorageVolume: ").append(mDescription);
+        if (mFsUuid != null) {
+            buffer.append(" (").append(mFsUuid).append(")");
+        }
+        return buffer.toString();
+    }
+
+    /** {@hide} */
+    // TODO(b/26742218): find out where toString() is called internally and replace these calls by
+    // dump().
+    public String dump() {
         final CharArrayWriter writer = new CharArrayWriter();
         dump(new IndentingPrintWriter(writer, "    ", 80));
         return writer.toString();
     }
 
+    /** {@hide} */
     public void dump(IndentingPrintWriter pw) {
         pw.println("StorageVolume:");
         pw.increaseIndent();
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index b7f071d..3700098 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -39,6 +39,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.ParcelFileDescriptor.OnCloseListener;
 import android.os.RemoteException;
+import android.os.storage.StorageVolume;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.util.Log;
@@ -62,7 +63,8 @@
  * All client apps must hold a valid URI permission grant to access documents,
  * typically issued when a user makes a selection through
  * {@link Intent#ACTION_OPEN_DOCUMENT}, {@link Intent#ACTION_CREATE_DOCUMENT},
- * {@link Intent#ACTION_OPEN_DOCUMENT_TREE}, or {@link Intent#ACTION_OPEN_EXTERNAL_DIRECTORY}.
+ * {@link Intent#ACTION_OPEN_DOCUMENT_TREE}, or
+ * {@link StorageVolume#createAccessIntent(String) StorageVolume.createAccessIntent}.
  *
  * @see DocumentsProvider
  */
diff --git a/core/java/android/security/net/config/NetworkSecurityTrustManager.java b/core/java/android/security/net/config/NetworkSecurityTrustManager.java
index 982ed68..81cad79 100644
--- a/core/java/android/security/net/config/NetworkSecurityTrustManager.java
+++ b/core/java/android/security/net/config/NetworkSecurityTrustManager.java
@@ -40,6 +40,9 @@
     // TODO: Replace this with a general X509TrustManager and use duck-typing.
     private final TrustManagerImpl mDelegate;
     private final NetworkSecurityConfig mNetworkSecurityConfig;
+    private final Object mIssuersLock = new Object();
+
+    private X509Certificate[] mIssuers;
 
     public NetworkSecurityTrustManager(NetworkSecurityConfig config) {
         if (config == null) {
@@ -139,6 +142,19 @@
 
     @Override
     public X509Certificate[] getAcceptedIssuers() {
-        return mDelegate.getAcceptedIssuers();
+        // TrustManagerImpl only looks at the provided KeyStore and not the TrustedCertificateStore
+        // for getAcceptedIssuers, so implement it here instead of delegating.
+        synchronized (mIssuersLock) {
+            if (mIssuers == null) {
+                Set<TrustAnchor> anchors = mNetworkSecurityConfig.getTrustAnchors();
+                X509Certificate[] issuers = new X509Certificate[anchors.size()];
+                int i = 0;
+                for (TrustAnchor anchor : anchors) {
+                    issuers[i++] = anchor.certificate;
+                }
+                mIssuers = issuers;
+            }
+            return mIssuers.clone();
+        }
     }
 }
diff --git a/core/java/com/android/internal/app/ISoundTriggerService.aidl b/core/java/com/android/internal/app/ISoundTriggerService.aidl
index 9de4a6c..f4c18c3 100644
--- a/core/java/com/android/internal/app/ISoundTriggerService.aidl
+++ b/core/java/com/android/internal/app/ISoundTriggerService.aidl
@@ -33,10 +33,11 @@
 
     void deleteSoundModel(in ParcelUuid soundModelId);
 
-    void startRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback);
+    int startRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback,
+         in SoundTrigger.RecognitionConfig config);
 
     /**
      * Stops recognition.
      */
-    void stopRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback);
+    int stopRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback);
 }
diff --git a/core/res/res/layout/notification_material_action.xml b/core/res/res/layout/notification_material_action.xml
index 398f52d..548ee05 100644
--- a/core/res/res/layout/notification_material_action.xml
+++ b/core/res/res/layout/notification_material_action.xml
@@ -21,6 +21,7 @@
     android:layout_width="wrap_content"
     android:layout_height="48dp"
     android:layout_gravity="center"
+    android:gravity="start|center_vertical"
     android:layout_marginStart="4dp"
     android:textColor="@color/notification_default_color"
     android:singleLine="true"
diff --git a/core/res/res/layout/notification_material_action_tombstone.xml b/core/res/res/layout/notification_material_action_tombstone.xml
index 976448b..1f59ea0 100644
--- a/core/res/res/layout/notification_material_action_tombstone.xml
+++ b/core/res/res/layout/notification_material_action_tombstone.xml
@@ -18,16 +18,15 @@
 <Button xmlns:android="http://schemas.android.com/apk/res/android"
     style="@android:style/Widget.Material.Light.Button.Borderless.Small"
     android:id="@+id/action0"
-    android:layout_width="0dp"
+    android:layout_width="wrap_content"
     android:layout_height="48dp"
-    android:layout_weight="1"
+    android:layout_marginStart="4dp"
+    android:layout_gravity="center"
     android:gravity="start|center_vertical"
-    android:drawablePadding="8dp"
-    android:paddingStart="8dp"
     android:textColor="#555555"
-    android:textSize="@dimen/notification_text_size"
     android:singleLine="true"
     android:ellipsize="end"
     android:alpha="0.5"
     android:enabled="false"
+    android:background="@drawable/notification_material_action_background"
     />
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index be8577a..4480944 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5030,7 +5030,7 @@
         <attr name="firstDayOfWeek" format="integer" />
         <!-- The minimal date shown by this calendar view in mm/dd/yyyy format. -->
         <attr name="minDate" />
-        <!-- The minimal date shown by this calendar view in mm/dd/yyyy format. -->
+        <!-- The maximal date shown by this calendar view in mm/dd/yyyy format. -->
         <attr name="maxDate" />
         <!-- The text appearance for the month and year in the calendar header. -->
         <attr name="monthTextAppearance" format="reference" />
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index f09f654..60444e0 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -277,6 +277,30 @@
         public static final int HOTWORD = 1999;
     }
 
+    // TODO make AudioSource static (API change) and move this method inside the AudioSource class
+    /**
+     * @hide
+     * @param source An audio source to test
+     * @return true if the source is only visible to system components
+     */
+    public static boolean isSystemOnlyAudioSource(int source) {
+        switch(source) {
+        case AudioSource.DEFAULT:
+        case AudioSource.MIC:
+        case AudioSource.VOICE_UPLINK:
+        case AudioSource.VOICE_DOWNLINK:
+        case AudioSource.VOICE_CALL:
+        case AudioSource.CAMCORDER:
+        case AudioSource.VOICE_RECOGNITION:
+        case AudioSource.VOICE_COMMUNICATION:
+        //case REMOTE_SUBMIX:  considered "system" as it requires system permissions
+        case AudioSource.UNPROCESSED:
+            return false;
+        default:
+            return true;
+        }
+    }
+
     /**
      * Defines the video source. These constants are used with
      * {@link MediaRecorder#setVideoSource(int)}.
diff --git a/media/java/android/media/soundtrigger/SoundTriggerDetector.java b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
index 707db06..8f022db 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerDetector.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
@@ -16,12 +16,17 @@
 
 package android.media.soundtrigger;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.hardware.soundtrigger.IRecognitionStatusCallback;
 import android.hardware.soundtrigger.SoundTrigger;
+import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
+import android.media.AudioFormat;
 import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
 import android.os.ParcelUuid;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -29,6 +34,8 @@
 import com.android.internal.app.ISoundTriggerService;
 
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.UUID;
 
 /**
@@ -45,6 +52,12 @@
     private static final boolean DBG = false;
     private static final String TAG = "SoundTriggerDetector";
 
+    private static final int MSG_AVAILABILITY_CHANGED = 1;
+    private static final int MSG_SOUND_TRIGGER_DETECTED = 2;
+    private static final int MSG_DETECTION_ERROR = 3;
+    private static final int MSG_DETECTION_PAUSE = 4;
+    private static final int MSG_DETECTION_RESUME = 5;
+
     private final Object mLock = new Object();
 
     private final ISoundTriggerService mSoundTriggerService;
@@ -53,7 +66,121 @@
     private final Handler mHandler;
     private final RecognitionCallback mRecognitionCallback;
 
-    public abstract class Callback {
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true,
+            value = {
+                RECOGNITION_FLAG_NONE,
+                RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO,
+                RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS
+            })
+    public @interface RecognitionFlags {}
+
+    /**
+     * Empty flag for {@link #startRecognition(int)}.
+     *
+     *  @hide
+     */
+    public static final int RECOGNITION_FLAG_NONE = 0;
+
+    /**
+     * Recognition flag for {@link #startRecognition(int)} that indicates
+     * whether the trigger audio for hotword needs to be captured.
+     */
+    public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 0x1;
+
+    /**
+     * Recognition flag for {@link #startRecognition(int)} that indicates
+     * whether the recognition should keep going on even after the
+     * model triggers.
+     * If this flag is specified, it's possible to get multiple
+     * triggers after a call to {@link #startRecognition(int)}, if the model
+     * triggers multiple times.
+     * When this isn't specified, the default behavior is to stop recognition once the
+     * trigger happenss, till the caller starts recognition again.
+     */
+    public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 0x2;
+
+    /**
+     * Additional payload for {@link Callback#onDetected}.
+     */
+    public static class EventPayload {
+        private final boolean mTriggerAvailable;
+
+        // Indicates if {@code captureSession} can be used to continue capturing more audio
+        // from the DSP hardware.
+        private final boolean mCaptureAvailable;
+        // The session to use when attempting to capture more audio from the DSP hardware.
+        private final int mCaptureSession;
+        private final AudioFormat mAudioFormat;
+        // Raw data associated with the event.
+        // This is the audio that triggered the keyphrase if {@code isTriggerAudio} is true.
+        private final byte[] mData;
+
+        private EventPayload(boolean triggerAvailable, boolean captureAvailable,
+                AudioFormat audioFormat, int captureSession, byte[] data) {
+            mTriggerAvailable = triggerAvailable;
+            mCaptureAvailable = captureAvailable;
+            mCaptureSession = captureSession;
+            mAudioFormat = audioFormat;
+            mData = data;
+        }
+
+        /**
+         * Gets the format of the audio obtained using {@link #getTriggerAudio()}.
+         * May be null if there's no audio present.
+         */
+        @Nullable
+        public AudioFormat getCaptureAudioFormat() {
+            return mAudioFormat;
+        }
+
+        /**
+         * Gets the raw audio that triggered the keyphrase.
+         * This may be null if the trigger audio isn't available.
+         * If non-null, the format of the audio can be obtained by calling
+         * {@link #getCaptureAudioFormat()}.
+         *
+         * @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO
+         */
+        @Nullable
+        public byte[] getTriggerAudio() {
+            if (mTriggerAvailable) {
+                return mData;
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets the session ID to start a capture from the DSP.
+         * This may be null if streaming capture isn't possible.
+         * If non-null, the format of the audio that can be captured can be
+         * obtained using {@link #getCaptureAudioFormat()}.
+         *
+         * TODO: Candidate for Public API when the API to start capture with a session ID
+         * is made public.
+         *
+         * TODO: Add this to {@link #getCaptureAudioFormat()}:
+         * "Gets the format of the audio obtained using {@link #getTriggerAudio()}
+         * or {@link #getCaptureSession()}. May be null if no audio can be obtained
+         * for either the trigger or a streaming session."
+         *
+         * TODO: Should this return a known invalid value instead?
+         *
+         * @hide
+         */
+        @Nullable
+        public Integer getCaptureSession() {
+            if (mCaptureAvailable) {
+                return mCaptureSession;
+            } else {
+                return null;
+            }
+        }
+    }
+
+    public static abstract class Callback {
         /**
          * Called when the availability of the sound model changes.
          */
@@ -63,7 +190,7 @@
          * Called when the sound model has triggered (such as when it matched a
          * given sound pattern).
          */
-        public abstract void onDetected();
+        public abstract void onDetected(@NonNull EventPayload eventPayload);
 
         /**
          *  Called when the detection fails due to an error.
@@ -95,9 +222,9 @@
         mSoundModelId = soundModelId;
         mCallback = callback;
         if (handler == null) {
-            mHandler = new Handler();
+            mHandler = new MyHandler();
         } else {
-            mHandler = handler;
+            mHandler = new MyHandler(handler.getLooper());
         }
         mRecognitionCallback = new RecognitionCallback();
     }
@@ -107,13 +234,19 @@
      * {@link Callback}.
      * @return Indicates whether the call succeeded or not.
      */
-    public boolean startRecognition() {
+    public boolean startRecognition(@RecognitionFlags int recognitionFlags) {
         if (DBG) {
             Slog.d(TAG, "startRecognition()");
         }
+        boolean captureTriggerAudio =
+                (recognitionFlags & RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO) != 0;
+
+        boolean allowMultipleTriggers =
+                (recognitionFlags & RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS) != 0;
         try {
             mSoundTriggerService.startRecognition(new ParcelUuid(mSoundModelId),
-                    mRecognitionCallback);
+                    mRecognitionCallback, new RecognitionConfig(captureTriggerAudio,
+                        allowMultipleTriggers, null, null));
         } catch (RemoteException e) {
             return false;
         }
@@ -144,17 +277,25 @@
 
     /**
      * Callback that handles events from the lower sound trigger layer.
+     *
+     * Note that these callbacks will be called synchronously from the SoundTriggerService
+     * layer and thus should do minimal work (such as sending a message on a handler to do
+     * the real work).
      * @hide
      */
-    private static class RecognitionCallback extends
-            IRecognitionStatusCallback.Stub {
+    private class RecognitionCallback extends IRecognitionStatusCallback.Stub {
 
         /**
          * @hide
          */
         @Override
         public void onDetected(SoundTrigger.RecognitionEvent event) {
-            Slog.e(TAG, "onDetected()" + event);
+            Slog.d(TAG, "onDetected()" + event);
+            Message.obtain(mHandler,
+                    MSG_SOUND_TRIGGER_DETECTED,
+                    new EventPayload(event.triggerInData, event.captureAvailable,
+                            event.captureFormat, event.captureSession, event.data))
+                    .sendToTarget();
         }
 
         /**
@@ -162,7 +303,8 @@
          */
         @Override
         public void onError(int status) {
-            Slog.e(TAG, "onError()" + status);
+            Slog.d(TAG, "onError()" + status);
+            mHandler.sendEmptyMessage(MSG_DETECTION_ERROR);
         }
 
         /**
@@ -170,7 +312,8 @@
          */
         @Override
         public void onRecognitionPaused() {
-            Slog.e(TAG, "onRecognitionPaused()");
+            Slog.d(TAG, "onRecognitionPaused()");
+            mHandler.sendEmptyMessage(MSG_DETECTION_PAUSE);
         }
 
         /**
@@ -178,7 +321,44 @@
          */
         @Override
         public void onRecognitionResumed() {
-            Slog.e(TAG, "onRecognitionResumed()");
+            Slog.d(TAG, "onRecognitionResumed()");
+            mHandler.sendEmptyMessage(MSG_DETECTION_RESUME);
+        }
+    }
+
+    private class MyHandler extends Handler {
+
+        MyHandler() {
+            super();
+        }
+
+        MyHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (mCallback == null) {
+                  Slog.w(TAG, "Received message: " + msg.what + " for NULL callback.");
+                  return;
+            }
+            switch (msg.what) {
+                case MSG_SOUND_TRIGGER_DETECTED:
+                    mCallback.onDetected((EventPayload) msg.obj);
+                    break;
+                case MSG_DETECTION_ERROR:
+                    mCallback.onError();
+                    break;
+                case MSG_DETECTION_PAUSE:
+                    mCallback.onRecognitionPaused();
+                    break;
+                case MSG_DETECTION_RESUME:
+                    mCallback.onRecognitionResumed();
+                    break;
+                default:
+                    super.handleMessage(msg);
+
+            }
         }
     }
 }
diff --git a/packages/DocumentsUI/AndroidManifest.xml b/packages/DocumentsUI/AndroidManifest.xml
index 9ac929b..58e7709 100644
--- a/packages/DocumentsUI/AndroidManifest.xml
+++ b/packages/DocumentsUI/AndroidManifest.xml
@@ -94,9 +94,8 @@
             android:name=".OpenExternalDirectoryActivity"
             android:theme="@android:style/Theme.Translucent.NoTitleBar">
             <intent-filter>
-                <action android:name="android.intent.action.OPEN_EXTERNAL_DIRECTORY" />
+                <action android:name="android.os.storage.action.OPEN_EXTERNAL_DIRECTORY" />
                 <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="file" />
             </intent-filter>
         </activity>
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index d0bb7e0..29bb5e4 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -134,9 +134,13 @@
         }
 
         if (state.action == ACTION_PICK_COPY_DESTINATION) {
+            // Indicates that a copy operation (or move) includes a directory.
+            // Why? Directory creation isn't supported by some roots (like Downloads).
+            // This allows us to restrict available roots to just those with support.
             state.directoryCopy = intent.getBooleanExtra(
                     Shared.EXTRA_DIRECTORY_COPY, false);
-            state.transferMode = intent.getIntExtra(FileOperationService.EXTRA_OPERATION,
+            state.copyOperationSubType = intent.getIntExtra(
+                    FileOperationService.EXTRA_OPERATION,
                     FileOperationService.OPERATION_COPY);
         }
     }
@@ -156,6 +160,9 @@
         if (external && mState.action == ACTION_GET_CONTENT) {
             showDrawer = true;
         }
+        if (mState.action == ACTION_PICK_COPY_DESTINATION) {
+            showDrawer = true;
+        }
 
         if (showDrawer) {
             mNavigator.revealRootsDrawer(true);
@@ -307,7 +314,7 @@
             mState.action == ACTION_PICK_COPY_DESTINATION) {
             final PickFragment pick = PickFragment.get(fm);
             if (pick != null) {
-                pick.setPickTarget(mState.action, mState.transferMode, cwd);
+                pick.setPickTarget(mState.action, mState.copyOperationSubType, cwd);
             }
         }
     }
@@ -420,7 +427,7 @@
             // Picking a copy destination is only used internally by us, so we
             // don't need to extend permissions to the caller.
             intent.putExtra(Shared.EXTRA_STACK, (Parcelable) mState.stack);
-            intent.putExtra(FileOperationService.EXTRA_OPERATION, mState.transferMode);
+            intent.putExtra(FileOperationService.EXTRA_OPERATION, mState.copyOperationSubType);
         } else {
             intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
                     | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
diff --git a/packages/DocumentsUI/src/com/android/documentsui/OpenExternalDirectoryActivity.java b/packages/DocumentsUI/src/com/android/documentsui/OpenExternalDirectoryActivity.java
index d601550..025faea 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/OpenExternalDirectoryActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/OpenExternalDirectoryActivity.java
@@ -17,6 +17,8 @@
 package com.android.documentsui;
 
 import static android.os.Environment.isStandardDirectory;
+import static android.os.storage.StorageVolume.EXTRA_DIRECTORY_NAME;
+import static android.os.storage.StorageVolume.EXTRA_STORAGE_VOLUME;
 import static com.android.documentsui.Shared.DEBUG;
 
 import android.app.Activity;
@@ -35,9 +37,11 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.storage.StorageManager;
+import android.os.storage.StorageVolume;
 import android.os.storage.VolumeInfo;
 import android.provider.DocumentsContract;
 import android.text.TextUtils;
@@ -63,16 +67,31 @@
         super.onCreate(savedInstanceState);
 
         final Intent intent = getIntent();
-        if (intent == null || intent.getData() == null) {
-            Log.d(TAG, "missing intent or intent data: " + intent);
+        if (intent == null) {
+            if (DEBUG) Log.d(TAG, "missing intent");
+            setResult(RESULT_CANCELED);
+            finish();
+            return;
+        }
+        final Parcelable storageVolume = intent.getParcelableExtra(EXTRA_STORAGE_VOLUME);
+        if (!(storageVolume instanceof StorageVolume)) {
+            if (DEBUG)
+                Log.d(TAG, "extra " + EXTRA_STORAGE_VOLUME + " is not a StorageVolume: "
+                        + storageVolume);
+            setResult(RESULT_CANCELED);
+            finish();
+            return;
+        }
+        final String directoryName = intent.getStringExtra(EXTRA_DIRECTORY_NAME);
+        if (directoryName == null) {
+            if (DEBUG) Log.d(TAG, "missing extra " + EXTRA_DIRECTORY_NAME + " on " + intent);
             setResult(RESULT_CANCELED);
             finish();
             return;
         }
 
-        final String path = intent.getData().getPath();
         final int userId = UserHandle.myUserId();
-        if (!showFragment(this, userId, path)) {
+        if (!showFragment(this, userId, (StorageVolume) storageVolume, directoryName)) {
             setResult(RESULT_CANCELED);
             finish();
             return;
@@ -80,20 +99,20 @@
     }
 
     /**
-     * Validates the given {@code path} and display the appropriate dialog asking the user to grant
-     * access to it.
+     * Validates the given path (volume + directory) and display the appropriate dialog asking the
+     * user to grant access to it.
      */
-    static boolean showFragment(Activity activity, int userId, String path) {
-        Log.d(TAG, "showFragment() for path " + path + " and user " + userId);
-        if (path == null) {
-            Log.e(TAG, "INTERNAL ERROR: showFragment() with null path");
-            return false;
-        }
+    private static boolean showFragment(Activity activity, int userId, StorageVolume storageVolume,
+            String directoryName) {
+        if (DEBUG)
+            Log.d(TAG, "showFragment() for volume " + storageVolume.dump() + ", directory "
+                    + directoryName + ", and user " + userId);
         File file;
         try {
-            file = new File(new File(path).getCanonicalPath());
+            file = new File(storageVolume.getPathFile(), directoryName).getCanonicalFile();
         } catch (IOException e) {
-            Log.e(TAG, "Could not get canonical file from " + path);
+            Log.e(TAG, "Could not get canonical file for volume " + storageVolume.dump()
+                    + " and directory " + directoryName);
             return false;
         }
         final StorageManager sm =
@@ -104,7 +123,9 @@
 
         // Verify directory is valid.
         if (TextUtils.isEmpty(directory) || !isStandardDirectory(directory)) {
-            Log.d(TAG, "Directory '" + directory + "' is not standard (full path: '" + path + "')");
+            if (DEBUG)
+                Log.d(TAG, "Directory '" + directory + "' is not standard (full path: '"
+                        + file.getAbsolutePath() + "')");
             return false;
         }
 
@@ -123,7 +144,7 @@
             }
         }
         if (volumeLabel == null) {
-            Log.e(TAG, "Could not get volume for " + path);
+            Log.e(TAG, "Could not get volume for " + file);
             return false;
         }
 
@@ -165,13 +186,13 @@
         final File userPath = volume.getPathForUser(userId);
         final String path = userPath == null ? null : volume.getPathForUser(userId).getPath();
         final boolean isVisible = volume.isVisibleForWrite(userId);
-        if (DEBUG) {
+        if (DEBUG)
             Log.d(TAG, "Volume: " + volume + " userId: " + userId + " root: " + root
                     + " volumePath: " + volume.getPath().getPath()
                     + " pathForUser: " + path
                     + " internalPathForUser: " + volume.getInternalPath()
                     + " isVisible: " + isVisible);
-        }
+
         return volume.isVisibleForWrite(userId) && root.equals(path);
     }
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java b/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
index bbf4682..287c904 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
@@ -16,6 +16,10 @@
 
 package com.android.documentsui;
 
+import static com.android.documentsui.services.FileOperationService.OPERATION_COPY;
+import static com.android.documentsui.services.FileOperationService.OPERATION_MOVE;
+import static com.android.internal.util.Preconditions.checkArgument;
+
 import android.app.Activity;
 import android.app.Fragment;
 import android.app.FragmentManager;
@@ -27,6 +31,7 @@
 import android.widget.Button;
 
 import com.android.documentsui.model.DocumentInfo;
+import com.android.documentsui.services.FileOperationService.OpType;
 
 /**
  * Display pick confirmation bar, usually for selecting a directory.
@@ -35,7 +40,7 @@
     public static final String TAG = "PickFragment";
 
     private int mAction;
-    private int mTransferMode;
+    private @OpType int mOperationType;
     private DocumentInfo mPickTarget;
     private View mContainer;
     private Button mPick;
@@ -92,9 +97,10 @@
     /**
      * @param action Which action defined in State is the picker shown for.
      */
-    public void setPickTarget(int action, int transferMode, DocumentInfo pickTarget) {
+    public void setPickTarget(int action, @OpType int operationType, DocumentInfo pickTarget) {
+        checkArgument(operationType == OPERATION_COPY || operationType == OPERATION_MOVE);
         mAction = action;
-        mTransferMode = transferMode;
+        mOperationType = operationType;
         mPickTarget = pickTarget;
         if (mContainer != null) {
             updateView();
@@ -111,7 +117,8 @@
                 mCancel.setVisibility(View.GONE);
                 break;
             case State.ACTION_PICK_COPY_DESTINATION:
-                mPick.setText(R.string.button_copy);
+                mPick.setText(mOperationType == OPERATION_MOVE
+                        ? R.string.button_move : R.string.button_copy);
                 mCancel.setVisibility(View.VISIBLE);
                 break;
             default:
diff --git a/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java b/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java
index c34cec0..a77a9b3 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java
@@ -33,16 +33,19 @@
 import android.support.annotation.Nullable;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.Range;
 
 import com.android.documentsui.dirlist.Model;
 import com.android.documentsui.model.DocumentInfo;
 
+import java.util.ArrayList;
 import java.util.List;
 
 /**
  * Provides support for gather a list of quick-viewable files into a quick view intent.
  */
 final class QuickViewIntentBuilder {
+    private static final int MAX_CLIP_ITEMS = 1000;
 
     private final DocumentInfo mDocument;
     private final Model mModel;
@@ -50,9 +53,6 @@
     private final PackageManager mPkgManager;
     private final Resources mResources;
 
-    private ClipData mClipData;
-    private int mDocumentLocation;
-
     public QuickViewIntentBuilder(
             PackageManager pkgManager,
             Resources resources,
@@ -80,12 +80,28 @@
             intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
             intent.setPackage(trustedPkg);
             if (hasRegisteredHandler(intent)) {
-                List<String> siblingIds = mModel.getModelIds();
-                for (int i = 0; i < siblingIds.size(); i++) {
-                    onNextItem(i, siblingIds);
+                final ArrayList<Uri> uris = new ArrayList<Uri>();
+                final int documentLocation = collectViewableUris(uris);
+                final Range<Integer> range = computeSiblingsRange(uris, documentLocation);
+
+                ClipData clipData = null;
+                ClipData.Item item;
+                Uri uri;
+                for (int i = range.getLower(); i <= range.getUpper(); i++) {
+                    uri = uris.get(i);
+                    item = new ClipData.Item(uri);
+                    if (DEBUG) Log.d(TAG, "Including file: " + uri);
+                    if (clipData == null) {
+                        clipData = new ClipData(
+                                "URIs", new String[] { ClipDescription.MIMETYPE_TEXT_URILIST },
+                                item);
+                    } else {
+                        clipData.addItem(item);
+                    }
                 }
-                intent.putExtra(Intent.EXTRA_INDEX, mDocumentLocation);
-                intent.setClipData(mClipData);
+
+                intent.putExtra(Intent.EXTRA_INDEX, documentLocation);
+                intent.setClipData(clipData);
 
                 return intent;
             } else {
@@ -96,39 +112,63 @@
         return null;
     }
 
+    private int collectViewableUris(ArrayList<Uri> uris) {
+        final List<String> siblingIds = mModel.getModelIds();
+        uris.ensureCapacity(siblingIds.size());
+
+        int documentLocation = 0;
+        Cursor cursor;
+        String mimeType;
+        String id;
+        String authority;
+        Uri uri;
+
+        // Cursor's are not guaranteed to be immutable. Hence, traverse it only once.
+        for (int i = 0; i < siblingIds.size(); i++) {
+            cursor = mModel.getItem(siblingIds.get(i));
+
+            mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
+            if (Document.MIME_TYPE_DIR.equals(mimeType)) {
+                continue;
+            }
+
+            id = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID);
+            authority = getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY);
+            uri = DocumentsContract.buildDocumentUri(authority, id);
+
+            if (id.equals(mDocument.documentId)) {
+                if (DEBUG) Log.d(TAG, "Found starting point for QV. " + i);
+                documentLocation = i;
+            }
+
+            uris.add(uri);
+        }
+
+        return documentLocation;
+    }
+
+    private static Range<Integer> computeSiblingsRange(List<Uri> uris, int documentLocation) {
+        // Restrict number of siblings to avoid hitting the IPC limit.
+        // TODO: Remove this restriction once ClipData can hold an arbitrary number of
+        // items.
+        int firstSibling;
+        int lastSibling;
+        if (documentLocation < uris.size() / 2) {
+            firstSibling = Math.max(0, documentLocation - MAX_CLIP_ITEMS / 2);
+            lastSibling = Math.min(uris.size() - 1, firstSibling + MAX_CLIP_ITEMS - 1);
+        } else {
+            lastSibling = Math.min(uris.size() - 1, documentLocation + MAX_CLIP_ITEMS / 2);
+            firstSibling = Math.max(0, lastSibling - MAX_CLIP_ITEMS + 1);
+        }
+
+        if (DEBUG) Log.d(TAG, "Copmuted siblings from index: " + firstSibling
+                + " to: " + lastSibling);
+
+        return new Range(firstSibling, lastSibling);
+    }
+
     private boolean hasRegisteredHandler(Intent intent) {
         // Try to resolve the intent. If a matching app isn't installed, it won't resolve.
         return intent.resolveActivity(mPkgManager) != null;
     }
-
-    private void onNextItem(int index, List<String> siblingIds) {
-        final Cursor cursor = mModel.getItem(siblingIds.get(index));
-
-        if (cursor == null) {
-            return;
-        }
-
-        String mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
-        if (Document.MIME_TYPE_DIR.equals(mimeType)) {
-            return;
-        }
-
-        String id = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID);
-        String authority = getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY);
-        Uri uri = DocumentsContract.buildDocumentUri(authority, id);
-        if (DEBUG) Log.d(TAG, "Including file[" + id + "] @ " + uri);
-
-        if (id.equals(mDocument.documentId)) {
-            if (DEBUG) Log.d(TAG, "Found starting point for QV. " + index);
-            mDocumentLocation = index;
-        }
-
-        ClipData.Item item = new ClipData.Item(uri);
-        if (mClipData == null) {
-            mClipData = new ClipData(
-                    "URIs", new String[]{ClipDescription.MIMETYPE_TEXT_URILIST}, item);
-        } else {
-            mClipData.addItem(item);
-        }
-    }
 }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/State.java b/packages/DocumentsUI/src/com/android/documentsui/State.java
index 7dca8a7..81a0635 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/State.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/State.java
@@ -30,6 +30,7 @@
 import com.android.documentsui.model.DocumentStack;
 import com.android.documentsui.model.DurableUtils;
 import com.android.documentsui.model.RootInfo;
+import com.android.documentsui.services.FileOperationService.OpType;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -83,10 +84,18 @@
     public boolean forceAdvanced;
     public boolean showAdvanced;
     public boolean restored;
+
+    // Indicates that a copy operation (or move) includes a directory.
+    // Why? Directory creation isn't supported by some roots (like Downloads).
+    // This allows us to restrict available roots to just those with support.
     public boolean directoryCopy;
     public boolean openableOnly;
-    /** Transfer mode for file copy/move operations. */
-    public int transferMode;
+
+    /**
+     * This is basically a sub-type for the copy operation. It can be either COPY or MOVE.
+     * The only legal values are: OPERATION_COPY, OPERATION_MOVE.
+     */
+    public @OpType int copyOperationSubType;
 
     /** Current user navigation stack; empty implies recents. */
     public DocumentStack stack = new DocumentStack();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 0ae2a5c..0c851c8 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -101,6 +101,7 @@
 import com.android.documentsui.services.FileOperationService;
 import com.android.documentsui.services.FileOperationService.OpType;
 import com.android.documentsui.services.FileOperations;
+
 import com.google.common.collect.Lists;
 
 import java.lang.annotation.Retention;
@@ -130,6 +131,11 @@
     public static final int ANIM_LEAVE = 3;
     public static final int ANIM_ENTER = 4;
 
+    @IntDef(flag = true, value = {
+            REQUEST_COPY_DESTINATION
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RequestCode {}
     public static final int REQUEST_COPY_DESTINATION = 1;
 
     static final boolean DEBUG_ENABLE_DND = true;
@@ -193,11 +199,6 @@
 
         mRecView.setItemAnimator(new DirectoryItemAnimator(getActivity()));
 
-        // Make the RecyclerView unfocusable. This is needed in order for the focus search code in
-        // FocusManager to work correctly. Setting android:focusable=false in the layout xml doesn't
-        // work, for some reason.
-        mRecView.setFocusable(false);
-
         // TODO: Add a divider between views (which might use RecyclerView.ItemDecoration).
         if (DEBUG_ENABLE_DND) {
             setupDragAndDropOnDirectoryView(mRecView);
@@ -377,19 +378,24 @@
     }
 
     @Override
-    public void onActivityResult(int requestCode, int resultCode, Intent data) {
-        // There's only one request code right now. Replace this with a switch statement or
-        // something more scalable when more codes are added.
-        if (requestCode != REQUEST_COPY_DESTINATION) {
-            return;
+    public void onActivityResult(@RequestCode int requestCode, int resultCode, Intent data) {
+        switch(requestCode) {
+            case REQUEST_COPY_DESTINATION:
+                handleCopyResult(resultCode, data);
+                break;
+            default:
+                throw new UnsupportedOperationException("Unknown request code: " + requestCode);
         }
+    }
+
+    private void handleCopyResult(int resultCode, Intent data) {
         if (resultCode == Activity.RESULT_CANCELED || data == null) {
             // User pressed the back button or otherwise cancelled the destination pick. Don't
             // proceed with the copy.
             return;
         }
 
-        int operationType = data.getIntExtra(
+        @OpType int operationType = data.getIntExtra(
                 FileOperationService.EXTRA_OPERATION,
                 FileOperationService.OPERATION_COPY);
 
@@ -808,25 +814,43 @@
                 getActivity(),
                 DocumentsActivity.class);
 
+        // Set an appropriate title on the drawer when it is shown in the picker.
+        // Coupled with the fact that we auto-open the drawer for copy/move operations
+        // it should basically be the thing people see first.
+        int drawerTitleId = mode == FileOperationService.OPERATION_MOVE
+                ? R.string.menu_move : R.string.menu_copy;
+        intent.putExtra(DocumentsContract.EXTRA_PROMPT, getResources().getString(drawerTitleId));
+
         new GetDocumentsTask() {
             @Override
             void onDocumentsReady(List<DocumentInfo> docs) {
+                // TODO: Can this move to Fragment bundle state?
                 getDisplayState().selectedDocumentsForCopy = docs;
 
-                boolean directoryCopy = false;
-                for (DocumentInfo info : docs) {
-                    if (Document.MIME_TYPE_DIR.equals(info.mimeType)) {
-                        directoryCopy = true;
-                        break;
-                    }
-                }
-                intent.putExtra(Shared.EXTRA_DIRECTORY_COPY, directoryCopy);
+                // Determine if there is a directory in the set of documents
+                // to be copied? Why? Directory creation isn't supported by some roots
+                // (like Downloads). This informs DocumentsActivity (the "picker")
+                // to restrict available roots to just those with support.
+                intent.putExtra(Shared.EXTRA_DIRECTORY_COPY, hasDirectory(docs));
                 intent.putExtra(FileOperationService.EXTRA_OPERATION, mode);
+
+                // This just identifies the type of request...we'll check it
+                // when we reveive a response.
                 startActivityForResult(intent, REQUEST_COPY_DESTINATION);
             }
+
         }.execute(selected);
     }
 
+    private static boolean hasDirectory(List<DocumentInfo> docs) {
+        for (DocumentInfo info : docs) {
+            if (Document.MIME_TYPE_DIR.equals(info.mimeType)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private void renameDocuments(Selection selected) {
         // Batch renaming not supported
         // Rename option is only available in menu when 1 document selected
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FocusManager.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FocusManager.java
index 93ec842..e90a447 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FocusManager.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FocusManager.java
@@ -158,7 +158,14 @@
         }
 
         if (searchDir != -1) {
+            // Focus search behaves badly if the parent RecyclerView is focused. However, focusable
+            // shouldn't be unset on RecyclerView, otherwise focus isn't properly restored after
+            // events that cause a UI rebuild (like rotating the device). Compromise: turn focusable
+            // off while performing the focus search.
+            // TODO: Revisit this when RV focus issues are resolved.
+            mView.setFocusable(false);
             View targetView = view.focusSearch(searchDir);
+            mView.setFocusable(true);
             // TargetView can be null, for example, if the user pressed <down> at the bottom
             // of the list.
             if (targetView != null) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/FileOperationService.java b/packages/DocumentsUI/src/com/android/documentsui/services/FileOperationService.java
index 3a025c2..05a3f11 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/services/FileOperationService.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/FileOperationService.java
@@ -71,11 +71,6 @@
     // such case, this needs to be replaced with pairs of parent and child.
     public static final String EXTRA_SRC_PARENT = "com.android.documentsui.SRC_PARENT";
 
-    public static final int OPERATION_UNKNOWN = -1;
-    public static final int OPERATION_COPY = 1;
-    public static final int OPERATION_MOVE = 2;
-    public static final int OPERATION_DELETE = 3;
-
     @IntDef(flag = true, value = {
             OPERATION_UNKNOWN,
             OPERATION_COPY,
@@ -84,6 +79,10 @@
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface OpType {}
+    public static final int OPERATION_UNKNOWN = -1;
+    public static final int OPERATION_COPY = 1;
+    public static final int OPERATION_MOVE = 2;
+    public static final int OPERATION_DELETE = 3;
 
     // TODO: Move it to a shared file when more operations are implemented.
     public static final int FAILURE_COPY = 1;
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
index 609dc0c..95515db 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
@@ -52,7 +52,7 @@
                 "Videos",
                 "Audio",
                 "Downloads",
-                "Home",
+                "Documents",
                 ROOT_0_ID,
                 ROOT_1_ID);
     }
@@ -64,11 +64,11 @@
         bot.assertHasDocuments("file0.log", "file1.png", "file2.csv");
     }
 
-    public void testLoadsHomeByDefault() throws Exception {
+    public void testLoadsHomeDirectoryByDefault() throws Exception {
         initTestFiles();
 
         device.waitForIdle();
-        bot.assertWindowTitle("Home");
+        bot.assertWindowTitle("Documents");
     }
 
     public void testRootClickSetsWindowTitle() throws Exception {
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 9650651..7012eb2 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -626,12 +626,12 @@
     <!-- UI debug setting: force all activites to be resizable for multiwindow [CHAR LIMIT=50] -->
     <string name="force_resizable_activities">Force activities to be resizable</string>
     <!-- UI debug setting: force allow on external summary [CHAR LIMIT=150] -->
-    <string name="force_resizable_activities_summary">Makes all activities resizable for multi-window, regardless of manifest values.</string>
+    <string name="force_resizable_activities_summary">Make all activities resizable for multi-window, regardless of manifest values.</string>
 
     <!-- UI debug setting: enable freeform window support [CHAR LIMIT=50] -->
     <string name="enable_freeform_support">Enable freeform windows</string>
     <!-- UI debug setting: enable freeform window support summary [CHAR LIMIT=150] -->
-    <string name="enable_freeform_support_summary">Enables support for experimental freeform windows.</string>
+    <string name="enable_freeform_support_summary">Enable support for experimental freeform windows.</string>
 
     <!-- Local (desktop) backup password menu title [CHAR LIMIT=25] -->
     <string name="local_backup_password_title">Desktop backup password</string>
diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
index 84df0d6..26152cd 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -34,19 +34,31 @@
     <LinearLayout
         android:id="@+id/expanded_group"
         android:layout_width="wrap_content"
-        android:layout_height="match_parent"
+        android:layout_height="48dp"
         android:gravity="center"
         android:clipChildren="false"
         android:clipToPadding="false"
         android:orientation="horizontal"
         android:layout_alignParentEnd="true"
-        android:layout_marginTop="30dp"
-        android:layout_marginEnd="16dp">
+        android:layout_marginTop="28dp"
+        android:layout_marginEnd="12dp">
+
+        <com.android.systemui.statusbar.phone.MultiUserSwitch android:id="@+id/multi_user_switch"
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:layout_alignParentEnd="true"
+            android:background="@drawable/ripple_drawable" >
+            <ImageView android:id="@+id/multi_user_avatar"
+                android:layout_width="@dimen/multi_user_avatar_expanded_size"
+                android:layout_height="@dimen/multi_user_avatar_expanded_size"
+                android:layout_gravity="center"
+                android:scaleType="centerInside"/>
+        </com.android.systemui.statusbar.phone.MultiUserSwitch>
 
         <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
             android:id="@+id/settings_button_container"
             android:layout_width="48dp"
-            android:layout_height="@dimen/status_bar_header_height"
+            android:layout_height="48dp"
             android:clipChildren="false"
             android:clipToPadding="false">
 
@@ -68,18 +80,6 @@
 
         </com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
 
-        <com.android.systemui.statusbar.phone.MultiUserSwitch android:id="@+id/multi_user_switch"
-            android:layout_width="48dp"
-            android:layout_height="48dp"
-            android:layout_alignParentEnd="true"
-            android:background="@drawable/ripple_drawable" >
-            <ImageView android:id="@+id/multi_user_avatar"
-                android:layout_width="@dimen/multi_user_avatar_expanded_size"
-                android:layout_height="@dimen/multi_user_avatar_expanded_size"
-                android:layout_gravity="center"
-                android:scaleType="centerInside"/>
-        </com.android.systemui.statusbar.phone.MultiUserSwitch>
-
         <ImageView
             android:layout_width="48dp"
             android:layout_height="48dp"
@@ -104,44 +104,62 @@
         android:gravity="center_vertical" />
 
     <LinearLayout
-        android:id="@+id/date_time_group"
+        android:id="@+id/date_time_alarm_group"
         android:layout_width="wrap_content"
-        android:layout_height="25dp"
+        android:layout_height="wrap_content"
         android:layout_alignParentStart="true"
         android:layout_alignParentTop="true"
-        android:orientation="horizontal">
-
-        <include layout="@layout/split_clock_view"
+        android:layout_marginStart="16dp"
+        android:gravity="start"
+        android:orientation="vertical">
+        <LinearLayout
+            android:id="@+id/date_time_group"
             android:layout_width="wrap_content"
-            android:layout_height="match_parent"
-            android:layout_marginStart="16dp"
+            android:layout_height="19dp"
             android:layout_marginTop="4dp"
-            android:id="@+id/clock" />
+            android:orientation="horizontal">
 
-        <com.android.systemui.statusbar.policy.DateView
-            android:id="@+id/date"
-            android:layout_width="wrap_content"
-            android:layout_height="match_parent"
-            android:layout_marginStart="6dp"
-            android:layout_marginTop="4dp"
-            android:drawableStart="@drawable/header_dot"
-            android:drawablePadding="6dp"
-            android:singleLine="true"
-            android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock"
-            android:textSize="@dimen/qs_time_collapsed_size"
-            android:gravity="top"
-            systemui:datePattern="@string/abbrev_wday_month_day_no_year_alarm" />
+            <include layout="@layout/split_clock_view"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:id="@+id/clock" />
+
+            <com.android.systemui.statusbar.policy.DateView
+                android:id="@+id/date"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="6dp"
+                android:drawableStart="@drawable/header_dot"
+                android:drawablePadding="6dp"
+                android:singleLine="true"
+                android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock"
+                android:textSize="@dimen/qs_time_collapsed_size"
+                android:gravity="top"
+                systemui:datePattern="@string/abbrev_wday_month_day_no_year_alarm" />
+
+            <com.android.systemui.statusbar.AlphaOptimizedButton
+                android:id="@+id/alarm_status_collapsed"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:drawablePadding="6dp"
+                android:drawableStart="@drawable/ic_access_alarms_small"
+                android:textColor="#64ffffff"
+                android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Date"
+                android:paddingStart="6dp"
+                android:gravity="top"
+                android:background="?android:attr/selectableItemBackground"
+                android:visibility="gone" />
+        </LinearLayout>
 
         <com.android.systemui.statusbar.AlphaOptimizedButton
             android:id="@+id/alarm_status"
             android:layout_width="wrap_content"
-            android:layout_height="match_parent"
-            android:layout_marginTop="4dp"
-            android:drawablePadding="6dp"
+            android:layout_height="20dp"
+            android:paddingTop="3dp"
+            android:drawablePadding="8dp"
             android:drawableStart="@drawable/ic_access_alarms_small"
             android:textColor="#64ffffff"
             android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Date"
-            android:paddingStart="6dp"
             android:gravity="top"
             android:background="?android:attr/selectableItemBackground"
             android:visibility="gone" />
@@ -152,7 +170,7 @@
         android:background="#0000"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_marginTop="25dp"
+        android:layout_marginTop="28dp"
         android:layout_marginStart="12dp"
         android:layout_marginEnd="12dp"
         android:layout_alignParentEnd="true"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 11c13e1..aed5ab2 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -162,7 +162,10 @@
     <dimen name="qs_tile_margin">16dp</dimen>
     <dimen name="qs_quick_tile_size">48dp</dimen>
     <dimen name="qs_quick_tile_padding">12dp</dimen>
-    <dimen name="qs_date_anim_translation">44.5dp</dimen>
+    <dimen name="qs_date_anim_translation">36dp</dimen>
+    <dimen name="qs_date_alarm_anim_translation">26dp</dimen>
+    <dimen name="qs_date_collapsed_text_size">14sp</dimen>
+    <dimen name="qs_date_text_size">16sp</dimen>
     <dimen name="qs_page_indicator_size">12dp</dimen>
     <dimen name="qs_tile_icon_size">24dp</dimen>
     <dimen name="qs_tile_text_size">12sp</dimen>
@@ -598,9 +601,6 @@
     <dimen name="fab_elevation">12dp</dimen>
     <dimen name="fab_press_translation_z">9dp</dimen>
 
-    <!-- TODO: Remove this -->
-    <dimen name="qs_header_neg_padding">-8dp</dimen>
-
     <!-- How high we lift the divider when touching -->
     <dimen name="docked_stack_divider_lift_elevation">4dp</dimen>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index ec04861..fa5b1a9 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1182,7 +1182,7 @@
     <string name="overview_disable_fast_toggle_via_button_desc">Disable launch timeout while paging</string>
 
     <!-- Toggle to enable the gesture to enter split-screen by swiping up from the Overview button. [CHAR LIMIT=60]-->
-    <string name="overview_nav_bar_gesture">Enable split-screen swipe-up accelerator</string>
+    <string name="overview_nav_bar_gesture">Enable split-screen swipe-up gesture</string>
     <!-- Description for the toggle to enable the gesture to enter split-screen by swiping up from the Overview button. [CHAR LIMIT=NONE]-->
     <string name="overview_nav_bar_gesture_desc">Enable gesture to enter split-screen by swiping up from the Overview button</string>
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
index 11d99ff..3bb141a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
@@ -20,6 +20,7 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
+import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.RippleDrawable;
@@ -30,6 +31,7 @@
 import android.widget.TextView;
 import android.widget.Toast;
 import com.android.keyguard.KeyguardStatusView;
+import com.android.systemui.FontSizeUtils;
 import com.android.systemui.R;
 import com.android.systemui.qs.QSPanel;
 import com.android.systemui.qs.QSTile;
@@ -47,7 +49,9 @@
     private NextAlarmController mNextAlarmController;
     private SettingsButton mSettingsButton;
     private View mSettingsContainer;
+
     private TextView mAlarmStatus;
+    private TextView mAlarmStatusCollapsed;
 
     private QSPanel mQsPanel;
 
@@ -56,19 +60,21 @@
 
     private ViewGroup mExpandedGroup;
     private ViewGroup mDateTimeGroup;
-    private View mEmergencyOnly;
-    private TextView mQsDetailHeaderTitle;
+    private ViewGroup mDateTimeAlarmGroup;
+    private TextView mEmergencyOnly;
+
     private boolean mListening;
     private AlarmManager.AlarmClockInfo mNextAlarm;
 
     private QuickQSPanel mHeaderQsPanel;
     private boolean mShowEmergencyCallsOnly;
-    private float mDateTimeTranslation;
     private MultiUserSwitch mMultiUserSwitch;
     private ImageView mMultiUserAvatar;
-    private View mQsDetailHeaderBack;
 
-    private final int[] mTmpInt2 = new int[2];
+    private float mDateTimeTranslation;
+    private float mDateTimeAlarmTranslation;
+    private float mExpansionFraction;
+    private float mDateScaleFactor;
 
     public QuickStatusBarHeader(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -78,11 +84,12 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
 
-        mEmergencyOnly = findViewById(R.id.header_emergency_calls_only);
-        mDateTimeTranslation = mContext.getResources().getDimension(
-                R.dimen.qs_date_anim_translation);
+        mEmergencyOnly = (TextView) findViewById(R.id.header_emergency_calls_only);
+
+        mDateTimeAlarmGroup = (ViewGroup) findViewById(R.id.date_time_alarm_group);
+        mDateTimeAlarmGroup.findViewById(R.id.empty_time_view).setVisibility(View.GONE);
         mDateTimeGroup = (ViewGroup) findViewById(R.id.date_time_group);
-        mDateTimeGroup.findViewById(R.id.empty_time_view).setVisibility(View.GONE);
+
         mExpandedGroup = (ViewGroup) findViewById(R.id.expanded_group);
 
         mHeaderQsPanel = (QuickQSPanel) findViewById(R.id.quick_qs_panel);
@@ -91,6 +98,7 @@
         mSettingsContainer = findViewById(R.id.settings_button_container);
         mSettingsButton.setOnClickListener(this);
 
+        mAlarmStatusCollapsed = (TextView) findViewById(R.id.alarm_status_collapsed);
         mAlarmStatus = (TextView) findViewById(R.id.alarm_status);
         mAlarmStatus.setOnClickListener(this);
 
@@ -110,6 +118,29 @@
                         getHeight()));
             }
         });
+        updateResources();
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        updateResources();
+    }
+
+    private void updateResources() {
+        FontSizeUtils.updateFontSize(mAlarmStatus, R.dimen.qs_date_collapsed_size);
+        FontSizeUtils.updateFontSize(mEmergencyOnly, R.dimen.qs_emergency_calls_only_text_size);
+
+        mDateTimeTranslation = mContext.getResources().getDimension(
+                R.dimen.qs_date_anim_translation);
+        mDateTimeAlarmTranslation = mContext.getResources().getDimension(
+                R.dimen.qs_date_alarm_anim_translation);
+        float dateCollapsedSize = mContext.getResources().getDimension(
+                R.dimen.qs_date_collapsed_text_size);
+        float dateExpandedSize = mContext.getResources().getDimension(
+                R.dimen.qs_date_text_size);
+        mDateScaleFactor = dateExpandedSize / dateCollapsedSize - 1;
+        updateDateTimePosition();
     }
 
     @Override
@@ -140,15 +171,41 @@
 
     @Override
     public void setExpansion(float headerExpansionFraction) {
+        mExpansionFraction = headerExpansionFraction;
+
         mExpandedGroup.setAlpha(headerExpansionFraction);
         mExpandedGroup.setVisibility(headerExpansionFraction > 0 ? View.VISIBLE : View.INVISIBLE);
+
         mHeaderQsPanel.setAlpha(1 - headerExpansionFraction);
         mHeaderQsPanel.setVisibility(headerExpansionFraction < 1 ? View.VISIBLE : View.INVISIBLE);
 
-        mDateTimeGroup.setTranslationY(headerExpansionFraction * mDateTimeTranslation);
+        mAlarmStatus.setAlpha(headerExpansionFraction);
+        mAlarmStatusCollapsed.setAlpha(1 - headerExpansionFraction);
+        updateAlarmVisibilities();
+
+        float textScale = headerExpansionFraction * mDateScaleFactor;
+        mDateTimeGroup.setScaleX(1 + textScale);
+        mDateTimeGroup.setScaleY(1 + textScale);
+        mDateTimeGroup.setTranslationX(textScale * mDateTimeGroup.getWidth() / 2);
+        mDateTimeGroup.setTranslationY(textScale * mDateTimeGroup.getHeight() / 2);
+        updateDateTimePosition();
+
         mEmergencyOnly.setAlpha(headerExpansionFraction);
     }
 
+    private void updateAlarmVisibilities() {
+        mAlarmStatus.setVisibility(mAlarmShowing && mExpansionFraction > 0
+                ? View.VISIBLE : View.INVISIBLE);
+        mAlarmStatusCollapsed.setVisibility(mAlarmShowing && mExpansionFraction < 1
+                ? View.VISIBLE : View.INVISIBLE);
+    }
+
+    private void updateDateTimePosition() {
+        float translation = mAlarmShowing ? mDateTimeAlarmTranslation
+                : mDateTimeTranslation;
+        mDateTimeAlarmGroup.setTranslationY(mExpansionFraction * translation);
+    }
+
     public void setListening(boolean listening) {
         if (listening == mListening) {
             return;
@@ -160,11 +217,12 @@
 
     @Override
     public void updateEverything() {
+        updateDateTimePosition();
         updateVisibilities();
     }
 
     private void updateVisibilities() {
-        mAlarmStatus.setVisibility(mAlarmShowing ? View.VISIBLE : View.GONE);
+        updateAlarmVisibilities();
         mEmergencyOnly.setVisibility(mExpanded && mShowEmergencyCallsOnly
                 ? View.VISIBLE : View.INVISIBLE);
         mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility(
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java
index b407935..7b1764f 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java
@@ -50,8 +50,9 @@
     }
 
     @Override
-    protected void onStart() {
-        super.onStart();
+    protected void onResume() {
+        super.onResume();
+        mGuideOverlayView.setVisibility(View.VISIBLE);
         mHandler.removeCallbacks(mHideGuideOverlayRunnable);
         mHandler.postDelayed(mHideGuideOverlayRunnable, SHOW_GUIDE_OVERLAY_VIEW_DURATION_MS);
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 3335315..3e7466f 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -275,6 +275,9 @@
 
     private void processBatchedEvents(long frameNanos) {
         MotionEventHolder current = mEventQueue;
+        if (current == null) {
+            return;
+        }
         while (current.next != null) {
             current = current.next;
         }
@@ -403,6 +406,9 @@
     }
 
     private void disableFeatures() {
+        // Give the features a chance to process any batched events so we'll keep a consistent
+        // event stream
+        processBatchedEvents(Long.MAX_VALUE);
         if (mMotionEventInjector != null) {
             mAms.setMotionEventInjector(null);
             mMotionEventInjector.onDestroy();
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index f522288..7770d53 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -3004,6 +3004,25 @@
             }
         }
 
+        // TODO: The following code should find better place to live.
+        if (!resetDefaultEnabledIme) {
+            boolean enabledImeFound = false;
+            final List<InputMethodInfo> enabledImes = mSettings.getEnabledInputMethodListLocked();
+            final int N = enabledImes.size();
+            for (int i = 0; i < N; ++i) {
+                final InputMethodInfo imi = enabledImes.get(i);
+                if (mMethodList.contains(imi)) {
+                    enabledImeFound = true;
+                    break;
+                }
+            }
+            if (!enabledImeFound) {
+                Slog.i(TAG, "All the enabled IMEs are gone. Reset default enabled IMEs.");
+                resetDefaultEnabledIme = true;
+                resetSelectedInputMethodAndSubtypeLocked("");
+            }
+        }
+
         if (resetDefaultEnabledIme) {
             final ArrayList<InputMethodInfo> defaultEnabledIme =
                     InputMethodUtils.getDefaultEnabledImes(mContext, mSystemReady, mMethodList);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ccaa1d2..c7f7378 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -6430,6 +6430,9 @@
                 if (mLockScreenShown == LOCK_SCREEN_SHOWN) {
                     mLockScreenShown = LOCK_SCREEN_HIDDEN;
                     updateSleepIfNeededLocked();
+
+                    // Some stack visibility might change (e.g. docked stack)
+                    mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
                 }
             }
         } finally {
diff --git a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
index 5806f3f..a6325a4 100644
--- a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
@@ -20,6 +20,7 @@
 import android.media.AudioRecordConfiguration;
 import android.media.AudioSystem;
 import android.media.IRecordingConfigDispatcher;
+import android.media.MediaRecorder;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
@@ -48,6 +49,9 @@
      * Implementation of android.media.AudioSystem.AudioRecordingCallback
      */
     public void onRecordingConfigurationChanged(int event, int session, int source) {
+        if (MediaRecorder.isSystemOnlyAudioSource(source)) {
+            return;
+        }
         if (updateSnapshot(event, session, source)) {
             final Iterator<RecMonitorClient> clientIterator = mClients.iterator();
             synchronized(mClients) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 5db7e63..cc5b80e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -65,6 +65,7 @@
 import static android.content.pm.PackageManager.MATCH_ENCRYPTION_AWARE_AND_UNAWARE;
 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
 import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
+import static android.content.pm.PackageManager.MOVE_FAILED_DEVICE_ADMIN;
 import static android.content.pm.PackageManager.MOVE_FAILED_DOESNT_EXIST;
 import static android.content.pm.PackageManager.MOVE_FAILED_INTERNAL_ERROR;
 import static android.content.pm.PackageManager.MOVE_FAILED_OPERATION_PENDING;
@@ -14104,6 +14105,11 @@
         });
     }
 
+    @Override
+    public boolean isPackageDeviceAdminOnAnyUser(String packageName) {
+        return isPackageDeviceAdmin(packageName, UserHandle.USER_ALL);
+    }
+
     private boolean isPackageDeviceAdmin(String packageName, int userId) {
         IDevicePolicyManager dpm = IDevicePolicyManager.Stub.asInterface(
                 ServiceManager.getService(Context.DEVICE_POLICY_SERVICE));
@@ -18170,6 +18176,10 @@
                 throw new PackageManagerException(MOVE_FAILED_INTERNAL_ERROR,
                         "Package already moved to " + volumeUuid);
             }
+            if (pkg.applicationInfo.isInternal() && isPackageDeviceAdminOnAnyUser(packageName)) {
+                throw new PackageManagerException(MOVE_FAILED_DEVICE_ADMIN,
+                        "Device admin cannot be moved");
+            }
 
             if (ps.frozen) {
                 throw new PackageManagerException(MOVE_FAILED_OPERATION_PENDING,
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 6c2e4d4..e88b72f 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2409,7 +2409,6 @@
             case TYPE_WALLPAPER:
             case TYPE_DREAM:
             case TYPE_KEYGUARD_SCRIM:
-            case TYPE_DOCK_DIVIDER:
                 return false;
             default:
                 // Hide only windows below the keyguard host window.
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 0c429e5..144d7ac 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -19,6 +19,9 @@
 import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
 import static android.app.ActivityManager.StackId.HOME_STACK_ID;
 import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowState.RESIZE_HANDLE_WIDTH_IN_DP;
@@ -607,4 +610,39 @@
         final TaskStack stack = mService.mStackIdToStack.get(DOCKED_STACK_ID);
         return (stack != null && stack.isVisibleLocked()) ? stack : null;
     }
+
+    /**
+     * Find the visible, touch-deliverable window under the given point
+     */
+    WindowState getTouchableWinAtPointLocked(float xf, float yf) {
+        WindowState touchedWin = null;
+        final int x = (int) xf;
+        final int y = (int) yf;
+
+        for (int i = mWindows.size() - 1; i >= 0; i--) {
+            WindowState window = mWindows.get(i);
+            final int flags = window.mAttrs.flags;
+            if (!window.isVisibleLw()) {
+                continue;
+            }
+            if ((flags & FLAG_NOT_TOUCHABLE) != 0) {
+                continue;
+            }
+
+            window.getVisibleBounds(mTmpRect);
+            if (!mTmpRect.contains(x, y)) {
+                continue;
+            }
+
+            window.getTouchableRegion(mTmpRegion);
+
+            final int touchFlags = flags & (FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL);
+            if (mTmpRegion.contains(x, y) || touchFlags == 0) {
+                touchedWin = window;
+                break;
+            }
+        }
+
+        return touchedWin;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 685403c..75c06ff 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -102,7 +102,9 @@
             return;
         }
         TaskStack stack = mDisplayContent.mService.mStackIdToStack.get(DOCKED_STACK_ID);
-        final boolean visible = stack != null && stack.isVisibleLocked();
+
+        // If the stack is invisible, we policy force hide it in WindowAnimator.shouldForceHide
+        final boolean visible = stack != null;
         if (mLastVisibility == visible && !force) {
             return;
         }
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 9a3aaa5..cf27b97 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -27,8 +27,6 @@
 import android.content.Context;
 import android.graphics.Matrix;
 import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.Region;
 import android.hardware.input.InputManager;
 import android.os.IBinder;
 import android.os.Message;
@@ -71,6 +69,13 @@
 class DragState {
     private static final long ANIMATION_DURATION_MS = 500;
 
+    private static final int DRAG_FLAGS_URI_ACCESS = View.DRAG_FLAG_GLOBAL_URI_READ |
+            View.DRAG_FLAG_GLOBAL_URI_WRITE;
+
+    private static final int DRAG_FLAGS_URI_PERMISSIONS = DRAG_FLAGS_URI_ACCESS |
+            View.DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION |
+            View.DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION;
+
     final WindowManagerService mService;
     IBinder mToken;
     SurfaceControl mSurfaceControl;
@@ -95,10 +100,7 @@
     WindowState mTargetWindow;
     ArrayList<WindowState> mNotifiedWindows;
     boolean mDragInProgress;
-    Display mDisplay;
-
-    private final Region mTmpRegion = new Region();
-    private final Rect mTmpRect = new Rect();
+    DisplayContent mDisplayContent;
 
     private Animation mAnimation;
     final Transformation mTransformation = new Transformation();
@@ -131,11 +133,12 @@
      * @param display The Display that the window being dragged is on.
      */
     void register(Display display) {
-        mDisplay = display;
         if (DEBUG_DRAG) Slog.d(TAG_WM, "registering drag input channel");
         if (mClientChannel != null) {
             Slog.e(TAG_WM, "Duplicate register of drag input channel");
         } else {
+            mDisplayContent = mService.getDisplayContentLocked(display.getDisplayId());
+
             InputChannel[] channels = InputChannel.openInputChannelPair("drag");
             mServerChannel = channels[0];
             mClientChannel = channels[1];
@@ -149,7 +152,7 @@
                     WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
 
             mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null,
-                    mDisplay.getDisplayId());
+                    display.getDisplayId());
             mDragWindowHandle.name = "drag";
             mDragWindowHandle.inputChannel = mServerChannel;
             mDragWindowHandle.layer = getDragLayerLw();
@@ -174,7 +177,7 @@
             mDragWindowHandle.frameLeft = 0;
             mDragWindowHandle.frameTop = 0;
             Point p = new Point();
-            mDisplay.getRealSize(p);
+            display.getRealSize(p);
             mDragWindowHandle.frameRight = p.x;
             mDragWindowHandle.frameBottom = p.y;
 
@@ -244,12 +247,10 @@
             Slog.d(TAG_WM, "broadcasting DRAG_STARTED at (" + touchX + ", " + touchY + ")");
         }
 
-        final WindowList windows = mService.getWindowListLocked(mDisplay);
-        if (windows != null) {
-            final int N = windows.size();
-            for (int i = 0; i < N; i++) {
-                sendDragStartedLw(windows.get(i), touchX, touchY, mDataDescription);
-            }
+        final WindowList windows = mDisplayContent.getWindowList();
+        final int N = windows.size();
+        for (int i = 0; i < N; i++) {
+            sendDragStartedLw(windows.get(i), touchX, touchY, mDataDescription);
         }
     }
 
@@ -379,7 +380,7 @@
     private void cleanUpDragLw() {
         broadcastDragEndedLw();
         if (isFromSource(InputDevice.SOURCE_MOUSE)) {
-            mService.restorePointerIconLocked(mDisplay, mCurrentX, mCurrentY);
+            mService.restorePointerIconLocked(mDisplayContent, mCurrentX, mCurrentY);
         }
 
         // stop intercepting input
@@ -418,7 +419,7 @@
 
     void notifyLocationLw(float x, float y) {
         // Tell the affected window
-        WindowState touchedWin = mService.getTouchableWinAtPointLocked(mDisplay, x, y);
+        WindowState touchedWin = mDisplayContent.getTouchableWinAtPointLocked(x, y);
         if (touchedWin == null) {
             if (DEBUG_DRAG) Slog.d(TAG_WM, "No touched win at x=" + x + " y=" + y);
             return;
@@ -463,17 +464,18 @@
         mTargetWindow = touchedWin;
     }
 
-    // Tell the drop target about the data.  Returns 'true' if we can immediately
+    // Find the drop target and tell it about the data.  Returns 'true' if we can immediately
     // dispatch the global drag-ended message, 'false' if we need to wait for a
     // result from the recipient.
-    boolean notifyDropLw(WindowState touchedWin, IDropPermissions dropPermissions,
-            float x, float y) {
+    boolean notifyDropLw(float x, float y) {
         if (mAnimation != null) {
             return false;
         }
         mCurrentX = x;
         mCurrentY = y;
 
+        WindowState touchedWin = mDisplayContent.getTouchableWinAtPointLocked(x, y);
+
         if (!isWindowNotified(touchedWin)) {
             // "drop" outside a valid window -- no recipient to apply a
             // timeout to, and we can send the drag-ended message immediately.
@@ -484,7 +486,21 @@
         if (DEBUG_DRAG) {
             Slog.d(TAG_WM, "sending DROP to " + touchedWin);
         }
-        if (mSourceUserId != UserHandle.getUserId(touchedWin.getOwningUid())){
+
+        final int targetUserId = UserHandle.getUserId(touchedWin.getOwningUid());
+
+        DropPermissionsHandler dropPermissions = null;
+        if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 &&
+                (mFlags & DRAG_FLAGS_URI_ACCESS) != 0) {
+            dropPermissions = new DropPermissionsHandler(
+                    mData,
+                    mUid,
+                    touchedWin.getOwningPackage(),
+                    mFlags & DRAG_FLAGS_URI_PERMISSIONS,
+                    mSourceUserId,
+                    targetUserId);
+        }
+        if (mSourceUserId != targetUserId){
             mData.fixUris(mSourceUserId);
         }
         final int myPid = Process.myPid();
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index d169b34..8409058 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -898,7 +898,8 @@
     }
 
     boolean isVisibleLocked() {
-        final boolean keyguardOn = mService.mPolicy.isKeyguardShowingOrOccluded();
+        final boolean keyguardOn = mService.mPolicy.isKeyguardShowingOrOccluded()
+                && !mService.mAnimator.mKeyguardGoingAway;
         if (keyguardOn && !StackId.isAllowedOverLockscreen(mStackId)) {
             // The keyguard is showing and the stack shouldn't show on top of the keyguard.
             return false;
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 8d2fb9b..f8a4d33 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -21,6 +21,7 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_ERROR;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_KEYGUARD;
@@ -231,7 +232,10 @@
         // Only hide windows if the keyguard is active and not animating away.
         boolean keyguardOn = mPolicy.isKeyguardShowingOrOccluded()
                 && mForceHiding != KEYGUARD_ANIMATING_OUT;
-        return keyguardOn && !allowWhenLocked && (win.getDisplayId() == Display.DEFAULT_DISPLAY);
+        boolean hideDockDivider = win.mAttrs.type == TYPE_DOCK_DIVIDER
+                && win.getDisplayContent().getDockedStackLocked() == null;
+        return keyguardOn && !allowWhenLocked && (win.getDisplayId() == Display.DEFAULT_DISPLAY)
+                || hideDockDivider;
     }
 
     private void updateWindowsLocked(final int displayId) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 5cd14c9..7169375 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -664,13 +664,6 @@
 
     private WindowContentFrameStats mTempWindowRenderStats;
 
-    private static final int DRAG_FLAGS_URI_ACCESS = View.DRAG_FLAG_GLOBAL_URI_READ |
-            View.DRAG_FLAG_GLOBAL_URI_WRITE;
-
-    private static final int DRAG_FLAGS_URI_PERMISSIONS = DRAG_FLAGS_URI_ACCESS |
-            View.DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION |
-            View.DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION;
-
     final class DragInputEventReceiver extends InputEventReceiver {
         // Set, if stylus button was down at the start of the drag.
         private boolean mStylusButtonDownAtStart;
@@ -716,7 +709,7 @@
                             if (DEBUG_DRAG) Slog.d(TAG_WM, "Button no longer pressed; dropping at "
                                     + newX + "," + newY);
                             synchronized (mWindowMap) {
-                                endDrag = completeDropLw(newX, newY);
+                                endDrag = mDragState.notifyDropLw(newX, newY);
                             }
                         } else {
                             synchronized (mWindowMap) {
@@ -730,7 +723,7 @@
                         if (DEBUG_DRAG) Slog.d(TAG_WM, "Got UP on move channel; dropping at "
                                 + newX + "," + newY);
                         synchronized (mWindowMap) {
-                            endDrag = completeDropLw(newX, newY);
+                            endDrag = mDragState.notifyDropLw(newX, newY);
                         }
                     } break;
 
@@ -760,25 +753,6 @@
         }
     }
 
-    private boolean completeDropLw(float x, float y) {
-        WindowState dropTargetWin = getTouchableWinAtPointLocked(mDragState.mDisplay, x, y);
-
-        DropPermissionsHandler dropPermissions = null;
-        if (dropTargetWin != null &&
-                (mDragState.mFlags & View.DRAG_FLAG_GLOBAL) != 0 &&
-                (mDragState.mFlags & DRAG_FLAGS_URI_ACCESS) != 0) {
-            dropPermissions = new DropPermissionsHandler(
-                    mDragState.mData,
-                    mDragState.mUid,
-                    dropTargetWin.getOwningPackage(),
-                    mDragState.mFlags & DRAG_FLAGS_URI_PERMISSIONS,
-                    mDragState.mSourceUserId,
-                    UserHandle.getUserId(dropTargetWin.getOwningUid()));
-        }
-
-        return mDragState.notifyDropLw(dropTargetWin, dropPermissions, x, y);
-    }
-
     /**
      * Whether the UI is currently running in touch mode (not showing
      * navigational focus because the user is directly pressing the screen).
@@ -10467,43 +10441,6 @@
         }
     }
 
-    /**
-     * Find the visible, touch-deliverable window under the given point
-     */
-    WindowState getTouchableWinAtPointLocked(Display display, float xf, float yf) {
-        WindowState touchedWin = null;
-        final int x = (int) xf;
-        final int y = (int) yf;
-
-        final WindowList windows = getWindowListLocked(display);
-        if (windows == null) {
-            return null;
-        }
-        final int N = windows.size();
-        for (int i = N - 1; i >= 0; i--) {
-            WindowState child = windows.get(i);
-            final int flags = child.mAttrs.flags;
-            if (!child.isVisibleLw()) {
-                continue;
-            }
-            if ((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) {
-                continue;
-            }
-
-            child.getTouchableRegion(mTmpRegion);
-
-            final int touchFlags = flags &
-                    (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                            | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
-            if (mTmpRegion.contains(x, y) || touchFlags == 0) {
-                touchedWin = child;
-                break;
-            }
-        }
-
-        return touchedWin;
-    }
-
     private MousePositionTracker mMousePositionTracker = new MousePositionTracker();
 
     private static class MousePositionTracker implements PointerEventListener {
@@ -10556,8 +10493,8 @@
             if (displayContent == null) {
                 return;
             }
-            Display display = displayContent.getDisplay();
-            WindowState windowUnderPointer = getTouchableWinAtPointLocked(display, mouseX, mouseY);
+            WindowState windowUnderPointer =
+                    displayContent.getTouchableWinAtPointLocked(mouseX, mouseY);
             if (windowUnderPointer != callingWin) {
                 return;
             }
@@ -10571,11 +10508,12 @@
         }
     }
 
-    void restorePointerIconLocked(Display display, float latestX, float latestY) {
+    void restorePointerIconLocked(DisplayContent displayContent, float latestX, float latestY) {
         // Mouse position tracker has not been getting updates while dragging, update it now.
         mMousePositionTracker.updatePosition(latestX, latestY);
 
-        WindowState windowUnderPointer = getTouchableWinAtPointLocked(display, latestX, latestY);
+        WindowState windowUnderPointer =
+                displayContent.getTouchableWinAtPointLocked(latestX, latestY);
         if (windowUnderPointer != null) {
             try {
                 windowUnderPointer.mClient.updatePointerIcon(
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index fa727d4..79d2307 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -2702,6 +2702,10 @@
         if (info == null) {
             throw new IllegalArgumentException("Bad admin: " + adminReceiver);
         }
+        if (!info.getActivityInfo().applicationInfo.isInternal()) {
+            throw new IllegalArgumentException("Only apps in internal storage can be active admin: "
+                    + adminReceiver);
+        }
         synchronized (this) {
             long ident = mInjector.binderClearCallingIdentity();
             try {
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 46ad8a1..acc752a 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -74,6 +74,7 @@
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.SomeArgs;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.SystemService;
 
@@ -125,6 +126,7 @@
     Handler mHandler;
     AppOpsManager mAppOps;
     UserManager mUserManager;
+    PackageManager mPackageManager;
     AppWidgetManager mAppWidgetManager;
     IDeviceIdleController mDeviceIdleController;
     private DisplayManager mDisplayManager;
@@ -157,7 +159,7 @@
     public void onStart() {
         mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
         mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
-
+        mPackageManager = getContext().getPackageManager();
         mHandler = new H(BackgroundThread.get().getLooper());
 
         File systemDataDir = new File(Environment.getDataDirectory(), "system");
@@ -296,9 +298,8 @@
     private void initializeDefaultsForSystemApps(int userId) {
         Slog.d(TAG, "Initializing defaults for system apps on user " + userId);
         final long elapsedRealtime = SystemClock.elapsedRealtime();
-        List<PackageInfo> packages = getContext().getPackageManager().getInstalledPackagesAsUser(
-                PackageManager.MATCH_DISABLED_COMPONENTS
-                | PackageManager.MATCH_UNINSTALLED_PACKAGES,
+        List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(
+                PackageManager.MATCH_DISABLED_COMPONENTS,
                 userId);
         final int packageCount = packages.size();
         for (int i = 0; i < packageCount; i++) {
@@ -398,31 +399,38 @@
         }
     }
 
-    /** Check all running users' or specified user's apps to see if they enter an idle state. */
-    void checkIdleStates(int checkUserId) {
+    /**
+     * Check all running users' or specified user's apps to see if they enter an idle state.
+     * @return Returns whether checking should continue periodically.
+     */
+    boolean checkIdleStates(int checkUserId) {
         if (!mAppIdleEnabled) {
-            return;
+            return false;
         }
 
-        final int[] userIds;
+        final int[] runningUserIds;
         try {
-            if (checkUserId == UserHandle.USER_ALL) {
-                userIds = ActivityManagerNative.getDefault().getRunningUserIds();
-            } else {
-                userIds = new int[] { checkUserId };
+            runningUserIds = ActivityManagerNative.getDefault().getRunningUserIds();
+            if (checkUserId != UserHandle.USER_ALL
+                    && !ArrayUtils.contains(runningUserIds, checkUserId)) {
+                return false;
             }
         } catch (RemoteException re) {
-            return;
+            return false;
         }
 
         final long elapsedRealtime = SystemClock.elapsedRealtime();
-        for (int i = 0; i < userIds.length; i++) {
-            final int userId = userIds[i];
-            List<PackageInfo> packages =
-                    getContext().getPackageManager().getInstalledPackagesAsUser(
-                            PackageManager.MATCH_DISABLED_COMPONENTS
-                                | PackageManager.MATCH_UNINSTALLED_PACKAGES,
-                            userId);
+        for (int i = 0; i < runningUserIds.length; i++) {
+            final int userId = runningUserIds[i];
+            if (checkUserId != UserHandle.USER_ALL && checkUserId != userId) {
+                continue;
+            }
+            if (DEBUG) {
+                Slog.d(TAG, "Checking idle state for user " + userId);
+            }
+            List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(
+                    PackageManager.MATCH_DISABLED_COMPONENTS,
+                    userId);
             synchronized (mLock) {
                 final int packageCount = packages.size();
                 for (int p = 0; p < packageCount; p++) {
@@ -439,6 +447,11 @@
                 }
             }
         }
+        if (DEBUG) {
+            Slog.d(TAG, "checkIdleStates took "
+                    + (SystemClock.elapsedRealtime() - elapsedRealtime));
+        }
+        return true;
     }
 
     /** Check if it's been a while since last parole and let idle apps do some work */
@@ -459,7 +472,7 @@
 
     private void notifyBatteryStats(String packageName, int userId, boolean idle) {
         try {
-            final int uid = AppGlobals.getPackageManager().getPackageUid(packageName,
+            final int uid = mPackageManager.getPackageUidAsUser(packageName,
                     PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
             if (idle) {
                 mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE,
@@ -468,7 +481,7 @@
                 mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE,
                         packageName, uid);
             }
-        } catch (RemoteException re) {
+        } catch (NameNotFoundException | RemoteException e) {
         }
     }
 
@@ -592,7 +605,7 @@
             // Only force the sync adapters to active if the provider is not in the same package and
             // the sync adapter is a system package.
             try {
-                PackageInfo pi = AppGlobals.getPackageManager().getPackageInfo(
+                PackageInfo pi = mPackageManager.getPackageInfoAsUser(
                         packageName, PackageManager.MATCH_SYSTEM_ONLY, userId);
                 if (pi == null || pi.applicationInfo == null) {
                     continue;
@@ -600,7 +613,7 @@
                 if (!packageName.equals(providerPkgName)) {
                     forceIdleState(packageName, userId, false);
                 }
-            } catch (RemoteException re) {
+            } catch (NameNotFoundException e) {
                 // Shouldn't happen
             }
         }
@@ -725,7 +738,7 @@
 
     int getAppId(String packageName) {
         try {
-            ApplicationInfo ai = getContext().getPackageManager().getApplicationInfo(packageName,
+            ApplicationInfo ai = mPackageManager.getApplicationInfo(packageName,
                     PackageManager.MATCH_UNINSTALLED_PACKAGES
                             | PackageManager.MATCH_DISABLED_COMPONENTS);
             return ai.uid;
@@ -772,12 +785,8 @@
             }
         } catch (RemoteException re) {
         }
-        // TODO: Optimize this check
-        if (isActiveDeviceAdmin(packageName, userId)) {
-            return false;
-        }
 
-        if (isCarrierApp(packageName)) {
+        if (isActiveDeviceAdmin(packageName, userId)) {
             return false;
         }
 
@@ -790,7 +799,17 @@
             return false;
         }
 
-        return isAppIdleUnfiltered(packageName, userId, elapsedRealtime);
+        if (!isAppIdleUnfiltered(packageName, userId, elapsedRealtime)) {
+            return false;
+        }
+
+        // Check this last, as it is the most expensive check
+        // TODO: Optimize this by fetching the carrier privileged apps ahead of time
+        if (isCarrierApp(packageName)) {
+            return false;
+        }
+
+        return true;
     }
 
     int[] getIdleUidsForUser(int userId) {
@@ -803,7 +822,7 @@
         List<ApplicationInfo> apps;
         try {
             ParceledListSlice<ApplicationInfo> slice = AppGlobals.getPackageManager()
-                    .getInstalledApplications(PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
+                    .getInstalledApplications(/* flags= */ 0, userId);
             if (slice == null) {
                 return new int[0];
             }
@@ -833,7 +852,9 @@
                 uidStates.setValueAt(index, value + 1 + (idle ? 1<<16 : 0));
             }
         }
-
+        if (DEBUG) {
+            Slog.d(TAG, "getIdleUids took " + (SystemClock.elapsedRealtime() - elapsedRealtime));
+        }
         int numIdle = 0;
         for (int i = uidStates.size() - 1; i >= 0; i--) {
             int value = uidStates.valueAt(i);
@@ -865,15 +886,7 @@
     private boolean isActiveDeviceAdmin(String packageName, int userId) {
         DevicePolicyManager dpm = getContext().getSystemService(DevicePolicyManager.class);
         if (dpm == null) return false;
-        List<ComponentName> components = dpm.getActiveAdminsAsUser(userId);
-        if (components == null) return false;
-        final int size = components.size();
-        for (int i = 0; i < size; i++) {
-            if (components.get(i).getPackageName().equals(packageName)) {
-                return true;
-            }
-        }
-        return false;
+        return dpm.packageHasActiveAdmins(packageName, userId);
     }
 
     private boolean isCarrierApp(String packageName) {
@@ -1011,10 +1024,11 @@
                     break;
 
                 case MSG_CHECK_IDLE_STATES:
-                    checkIdleStates(msg.arg1);
-                    mHandler.sendMessageDelayed(mHandler.obtainMessage(
-                            MSG_CHECK_IDLE_STATES, msg.arg1, 0),
-                            mCheckIdleIntervalMillis);
+                    if (checkIdleStates(msg.arg1)) {
+                        mHandler.sendMessageDelayed(mHandler.obtainMessage(
+                                MSG_CHECK_IDLE_STATES, msg.arg1, 0),
+                                mCheckIdleIntervalMillis);
+                    }
                     break;
 
                 case MSG_ONE_TIME_CHECK_IDLE_STATES:
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java
index 18a5d59..f7cd6a3 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java
@@ -54,6 +54,7 @@
     private static final String CREATE_TABLE_ST_SOUND_MODEL = "CREATE TABLE "
             + GenericSoundModelContract.TABLE + "("
             + GenericSoundModelContract.KEY_MODEL_UUID + " TEXT PRIMARY KEY,"
+            + GenericSoundModelContract.KEY_VENDOR_UUID + " TEXT,"
             + GenericSoundModelContract.KEY_DATA + " BLOB" + " )";
 
 
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index 354075e..cde47bd 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -16,12 +16,16 @@
 
 package com.android.server.soundtrigger;
 
+import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR;
+
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.hardware.soundtrigger.IRecognitionStatusCallback;
 import android.hardware.soundtrigger.SoundTrigger;
+import android.hardware.soundtrigger.SoundTrigger.GenericRecognitionEvent;
+import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
 import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
 import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent;
 import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra;
@@ -29,6 +33,7 @@
 import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
 import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent;
+import android.hardware.soundtrigger.SoundTrigger.SoundModel;
 import android.hardware.soundtrigger.SoundTrigger.SoundModelEvent;
 import android.hardware.soundtrigger.SoundTriggerModule;
 import android.os.PowerManager;
@@ -40,9 +45,16 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.UUID;
 
 /**
- * Helper for {@link SoundTrigger} APIs.
+ * Helper for {@link SoundTrigger} APIs. Supports two types of models:
+ * (i) A voice model which is exported via the {@link VoiceInteractionService}. There can only be
+ * a single voice model running on the DSP at any given time.
+ *
+ * (ii) Generic sound-trigger models: Supports multiple of these.
+ *
  * Currently this just acts as an abstraction over all SoundTrigger API calls.
  *
  * @hide
@@ -62,7 +74,7 @@
     private static final int INVALID_VALUE = Integer.MIN_VALUE;
 
     /** The {@link ModuleProperties} for the system, or null if none exists. */
-    final ModuleProperties moduleProperties;
+    final ModuleProperties mModuleProperties;
 
     /** The properties for the DSP module */
     private SoundTriggerModule mModule;
@@ -72,21 +84,36 @@
     private final PhoneStateListener mPhoneStateListener;
     private final PowerManager mPowerManager;
 
-    // TODO: Since many layers currently only deal with one recognition
+    // TODO: Since the voice layer currently only handles one recognition
     // we simplify things by assuming one listener here too.
-    private IRecognitionStatusCallback mActiveListener;
+    private IRecognitionStatusCallback mKeyphraseListener;
+
+    // The SoundTriggerManager layer handles multiple generic recognition models. We store the
+    // ModelData here in a hashmap.
+    private final HashMap<UUID, ModelData> mGenericModelDataMap;
+
+    // Note: KeyphraseId is not really used.
     private int mKeyphraseId = INVALID_VALUE;
-    private int mCurrentSoundModelHandle = INVALID_VALUE;
+
+    // Current voice sound model handle. We only allow one voice model to run at any given time.
+    private int mCurrentKeyphraseModelHandle = INVALID_VALUE;
     private KeyphraseSoundModel mCurrentSoundModel = null;
     // FIXME: Ideally this should not be stored if allowMultipleTriggers happens at a lower layer.
     private RecognitionConfig mRecognitionConfig = null;
+
+    // Whether we are requesting recognition to start.
     private boolean mRequested = false;
     private boolean mCallActive = false;
     private boolean mIsPowerSaveMode = false;
     // Indicates if the native sound trigger service is disabled or not.
     // This is an indirect indication of the microphone being open in some other application.
     private boolean mServiceDisabled = false;
-    private boolean mStarted = false;
+
+    // Whether we have ANY recognition (keyphrase or generic) running.
+    private boolean mRecognitionRunning = false;
+
+    // Keeps track of whether the keyphrase recognition is running.
+    private boolean mKeyphraseStarted = false;
     private boolean mRecognitionAborted = false;
     private PowerSaveModeListener mPowerSaveModeListener;
 
@@ -96,14 +123,87 @@
         mContext = context;
         mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
         mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+        mGenericModelDataMap = new HashMap<UUID, ModelData>();
         mPhoneStateListener = new MyCallStateListener();
         if (status != SoundTrigger.STATUS_OK || modules.size() == 0) {
             Slog.w(TAG, "listModules status=" + status + ", # of modules=" + modules.size());
-            moduleProperties = null;
+            mModuleProperties = null;
             mModule = null;
         } else {
             // TODO: Figure out how to determine which module corresponds to the DSP hardware.
-            moduleProperties = modules.get(0);
+            mModuleProperties = modules.get(0);
+        }
+    }
+
+    /**
+     * Starts recognition for the given generic sound model ID.
+     *
+     * @param soundModel The sound model to use for recognition.
+     * @param listener The listener for the recognition events related to the given keyphrase.
+     * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
+     */
+    int startGenericRecognition(UUID modelId, GenericSoundModel soundModel,
+            IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig) {
+        if (soundModel == null || callback == null || recognitionConfig == null) {
+            Slog.w(TAG, "Passed in bad data to startGenericRecognition().");
+            return STATUS_ERROR;
+        }
+
+        synchronized (mLock) {
+
+            if (mModuleProperties == null) {
+                Slog.w(TAG, "Attempting startRecognition without the capability");
+                return STATUS_ERROR;
+            }
+
+            if (mModule == null) {
+                mModule = SoundTrigger.attachModule(mModuleProperties.id, this, null);
+                if (mModule == null) {
+                    Slog.w(TAG, "startRecognition cannot attach to sound trigger module");
+                    return STATUS_ERROR;
+                }
+            }
+
+            // Initialize power save, call active state monitoring logic.
+            if (!mRecognitionRunning) {
+                initializeTelephonyAndPowerStateListeners();
+            }
+
+            // Fetch a ModelData instance from the hash map. Creates a new one if none
+            // exists.
+            ModelData modelData = getOrCreateGenericModelData(modelId);
+
+            IRecognitionStatusCallback oldCallback = modelData.getCallback();
+            if (oldCallback != null) {
+                Slog.w(TAG, "Canceling previous recognition for model id: " + modelId);
+                try {
+                    oldCallback.onError(STATUS_ERROR);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "RemoteException in onDetectionStopped", e);
+                }
+                modelData.clearCallback();
+            }
+
+            // Load the model if its not loaded.
+            if (!modelData.isModelLoaded()) {
+                // Load the model
+                int[] handle = new int[] { INVALID_VALUE };
+                int status = mModule.loadSoundModel(soundModel, handle);
+                if (status != SoundTrigger.STATUS_OK) {
+                    Slog.w(TAG, "loadSoundModel call failed with " + status);
+                    return status;
+                }
+                if (handle[0] == INVALID_VALUE) {
+                    Slog.w(TAG, "loadSoundModel call returned invalid sound model handle");
+                    return STATUS_ERROR;
+                }
+                modelData.setHandle(handle[0]);
+            }
+            modelData.setCallback(callback);
+            modelData.setRecognitionConfig(recognitionConfig);
+
+            // Don't notify for synchronous calls.
+            return startGenericRecognitionLocked(modelData, false);
         }
     }
 
@@ -116,7 +216,7 @@
      * @param listener The listener for the recognition events related to the given keyphrase.
      * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
      */
-    int startRecognition(int keyphraseId,
+    int startKeyphraseRecognition(int keyphraseId,
             KeyphraseSoundModel soundModel,
             IRecognitionStatusCallback listener,
             RecognitionConfig recognitionConfig) {
@@ -129,36 +229,24 @@
                 Slog.d(TAG, "startRecognition for keyphraseId=" + keyphraseId
                         + " soundModel=" + soundModel + ", listener=" + listener.asBinder()
                         + ", recognitionConfig=" + recognitionConfig);
-                Slog.d(TAG, "moduleProperties=" + moduleProperties);
+                Slog.d(TAG, "moduleProperties=" + mModuleProperties);
                 Slog.d(TAG, "current listener="
-                        + (mActiveListener == null ? "null" : mActiveListener.asBinder()));
-                Slog.d(TAG, "current SoundModel handle=" + mCurrentSoundModelHandle);
+                        + (mKeyphraseListener == null ? "null" : mKeyphraseListener.asBinder()));
+                Slog.d(TAG, "current SoundModel handle=" + mCurrentKeyphraseModelHandle);
                 Slog.d(TAG, "current SoundModel UUID="
                         + (mCurrentSoundModel == null ? null : mCurrentSoundModel.uuid));
             }
 
-            if (!mStarted) {
-                // Get the current call state synchronously for the first recognition.
-                mCallActive = mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE;
-                // Register for call state changes when the first call to start recognition occurs.
-                mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
-
-                // Register for power saver mode changes when the first call to start recognition
-                // occurs.
-                if (mPowerSaveModeListener == null) {
-                    mPowerSaveModeListener = new PowerSaveModeListener();
-                    mContext.registerReceiver(mPowerSaveModeListener,
-                            new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED));
-                }
-                mIsPowerSaveMode = mPowerManager.isPowerSaveMode();
+            if (!mRecognitionRunning) {
+                initializeTelephonyAndPowerStateListeners();
             }
 
-            if (moduleProperties == null) {
+            if (mModuleProperties == null) {
                 Slog.w(TAG, "Attempting startRecognition without the capability");
                 return STATUS_ERROR;
             }
             if (mModule == null) {
-                mModule = SoundTrigger.attachModule(moduleProperties.id, this, null);
+                mModule = SoundTrigger.attachModule(mModuleProperties.id, this, null);
                 if (mModule == null) {
                     Slog.w(TAG, "startRecognition cannot attach to sound trigger module");
                     return STATUS_ERROR;
@@ -168,32 +256,32 @@
             // Unload the previous model if the current one isn't invalid
             // and, it's not the same as the new one.
             // This helps use cache and reuse the model and just start/stop it when necessary.
-            if (mCurrentSoundModelHandle != INVALID_VALUE
+            if (mCurrentKeyphraseModelHandle != INVALID_VALUE
                     && !soundModel.equals(mCurrentSoundModel)) {
                 Slog.w(TAG, "Unloading previous sound model");
-                int status = mModule.unloadSoundModel(mCurrentSoundModelHandle);
+                int status = mModule.unloadSoundModel(mCurrentKeyphraseModelHandle);
                 if (status != SoundTrigger.STATUS_OK) {
                     Slog.w(TAG, "unloadSoundModel call failed with " + status);
                 }
-                internalClearSoundModelLocked();
-                mStarted = false;
+                internalClearKeyphraseSoundModelLocked();
+                mKeyphraseStarted = false;
             }
 
             // If the previous recognition was by a different listener,
             // Notify them that it was stopped.
-            if (mActiveListener != null && mActiveListener.asBinder() != listener.asBinder()) {
+            if (mKeyphraseListener != null && mKeyphraseListener.asBinder() != listener.asBinder()) {
                 Slog.w(TAG, "Canceling previous recognition");
                 try {
-                    mActiveListener.onError(STATUS_ERROR);
+                    mKeyphraseListener.onError(STATUS_ERROR);
                 } catch (RemoteException e) {
                     Slog.w(TAG, "RemoteException in onDetectionStopped", e);
                 }
-                mActiveListener = null;
+                mKeyphraseListener = null;
             }
 
             // Load the sound model if the current one is null.
-            int soundModelHandle = mCurrentSoundModelHandle;
-            if (mCurrentSoundModelHandle == INVALID_VALUE
+            int soundModelHandle = mCurrentKeyphraseModelHandle;
+            if (mCurrentKeyphraseModelHandle == INVALID_VALUE
                     || mCurrentSoundModel == null) {
                 int[] handle = new int[] { INVALID_VALUE };
                 int status = mModule.loadSoundModel(soundModel, handle);
@@ -213,18 +301,81 @@
             // Start the recognition.
             mRequested = true;
             mKeyphraseId = keyphraseId;
-            mCurrentSoundModelHandle = soundModelHandle;
+            mCurrentKeyphraseModelHandle = soundModelHandle;
             mCurrentSoundModel = soundModel;
             mRecognitionConfig = recognitionConfig;
             // Register the new listener. This replaces the old one.
             // There can only be a maximum of one active listener at any given time.
-            mActiveListener = listener;
+            mKeyphraseListener = listener;
 
             return updateRecognitionLocked(false /* don't notify for synchronous calls */);
         }
     }
 
     /**
+     * Stops recognition for the given generic sound model.
+     *
+     * @param modelId The identifier of the generic sound model for which
+     *        the recognition is to be stopped.
+     * @param listener The listener for the recognition events related to the given sound model.
+     *
+     * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
+     */
+    int stopGenericRecognition(UUID modelId, IRecognitionStatusCallback listener) {
+        if (listener == null) {
+            return STATUS_ERROR;
+        }
+
+        synchronized (mLock) {
+            ModelData modelData = mGenericModelDataMap.get(modelId);
+            if (modelData == null) {
+                Slog.w(TAG, "Attempting stopRecognition on invalid model with id:" + modelId);
+                return STATUS_ERROR;
+            }
+
+            IRecognitionStatusCallback currentCallback = modelData.getCallback();
+            if (DBG) {
+                Slog.d(TAG, "stopRecognition for modelId=" + modelId
+                        + ", listener=" + listener.asBinder());
+                Slog.d(TAG, "current callback ="
+                        + (currentCallback == null ? "null" : currentCallback.asBinder()));
+            }
+
+            if (mModuleProperties == null || mModule == null) {
+                Slog.w(TAG, "Attempting stopRecognition without the capability");
+                return STATUS_ERROR;
+            }
+
+            if (currentCallback == null || !modelData.modelStarted()) {
+                // startRecognition hasn't been called or it failed.
+                Slog.w(TAG, "Attempting stopRecognition without a successful startRecognition");
+                return STATUS_ERROR;
+            }
+            if (currentCallback.asBinder() != listener.asBinder()) {
+                // We don't allow a different listener to stop the recognition than the one
+                // that started it.
+                Slog.w(TAG, "Attempting stopRecognition for another recognition");
+                return STATUS_ERROR;
+            }
+
+            int status = stopGenericRecognitionLocked(modelData, false /* don't notify for synchronous calls */);
+            if (status != SoundTrigger.STATUS_OK) {
+                return status;
+            }
+
+            // We leave the sound model loaded but not started, this helps us when we start
+            // back.
+            // Also clear the internal state once the recognition has been stopped.
+            modelData.clearState();
+            modelData.clearCallback();
+            if (!computeRecognitionRunning()) {
+                internalClearGlobalStateLocked();
+            }
+            return status;
+        }
+    }
+
+    /**
      * Stops recognition for the given {@link Keyphrase} if a recognition is
      * currently active.
      *
@@ -234,7 +385,7 @@
      *
      * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
      */
-    int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
+    int stopKeyphraseRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
         if (listener == null) {
             return STATUS_ERROR;
         }
@@ -244,20 +395,20 @@
                 Slog.d(TAG, "stopRecognition for keyphraseId=" + keyphraseId
                         + ", listener=" + listener.asBinder());
                 Slog.d(TAG, "current listener="
-                        + (mActiveListener == null ? "null" : mActiveListener.asBinder()));
+                        + (mKeyphraseListener == null ? "null" : mKeyphraseListener.asBinder()));
             }
 
-            if (moduleProperties == null || mModule == null) {
+            if (mModuleProperties == null || mModule == null) {
                 Slog.w(TAG, "Attempting stopRecognition without the capability");
                 return STATUS_ERROR;
             }
 
-            if (mActiveListener == null) {
+            if (mKeyphraseListener == null) {
                 // startRecognition hasn't been called or it failed.
                 Slog.w(TAG, "Attempting stopRecognition without a successful startRecognition");
                 return STATUS_ERROR;
             }
-            if (mActiveListener.asBinder() != listener.asBinder()) {
+            if (mKeyphraseListener.asBinder() != listener.asBinder()) {
                 // We don't allow a different listener to stop the recognition than the one
                 // that started it.
                 Slog.w(TAG, "Attempting stopRecognition for another recognition");
@@ -274,7 +425,8 @@
             // We leave the sound model loaded but not started, this helps us when we start
             // back.
             // Also clear the internal state once the recognition has been stopped.
-            internalClearStateLocked();
+            internalClearKeyphraseStateLocked();
+            internalClearGlobalStateLocked();
             return status;
         }
     }
@@ -284,38 +436,56 @@
      */
     void stopAllRecognitions() {
         synchronized (mLock) {
-            if (moduleProperties == null || mModule == null) {
+            if (mModuleProperties == null || mModule == null) {
                 return;
             }
 
-            if (mCurrentSoundModelHandle == INVALID_VALUE) {
-                return;
+            // Stop Keyphrase recognition if one exists.
+            if (mCurrentKeyphraseModelHandle != INVALID_VALUE) {
+
+                mRequested = false;
+                int status = updateRecognitionLocked(
+                        false /* don't notify for synchronous calls */);
+                internalClearKeyphraseStateLocked();
             }
 
-            mRequested = false;
-            int status = updateRecognitionLocked(false /* don't notify for synchronous calls */);
-            internalClearStateLocked();
+            // Stop all generic recognition models.
+            for (ModelData model : mGenericModelDataMap.values()) {
+                if (model.modelStarted()) {
+                    int status = stopGenericRecognitionLocked(model,
+                            false /* do not notify for synchronous calls */);
+                    if (status != STATUS_OK) {
+                        // What else can we do if there is an error here.
+                        Slog.w(TAG, "Error stopping generic model: " + model.getHandle());
+                    }
+                    model.clearState();
+                    model.clearCallback();
+                }
+            }
+            internalClearGlobalStateLocked();
         }
     }
 
     public ModuleProperties getModuleProperties() {
-        return moduleProperties;
+        return mModuleProperties;
     }
 
     //---- SoundTrigger.StatusListener methods
     @Override
     public void onRecognition(RecognitionEvent event) {
-        if (event == null || !(event instanceof KeyphraseRecognitionEvent)) {
-            Slog.w(TAG, "Invalid recognition event!");
+        if (event == null) {
+            Slog.w(TAG, "Null recognition event!");
+            return;
+        }
+
+        if (!(event instanceof KeyphraseRecognitionEvent) &&
+                !(event instanceof GenericRecognitionEvent)) {
+            Slog.w(TAG, "Invalid recognition event type (not one of generic or keyphrase) !");
             return;
         }
 
         if (DBG) Slog.d(TAG, "onRecognition: " + event);
         synchronized (mLock) {
-            if (mActiveListener == null) {
-                Slog.w(TAG, "received onRecognition event without any listener for it");
-                return;
-            }
             switch (event.status) {
                 // Fire aborts/failures to all listeners since it's not tied to a keyphrase.
                 case SoundTrigger.RECOGNITION_STATUS_ABORT:
@@ -325,12 +495,60 @@
                     onRecognitionFailureLocked();
                     break;
                 case SoundTrigger.RECOGNITION_STATUS_SUCCESS:
-                    onRecognitionSuccessLocked((KeyphraseRecognitionEvent) event);
+
+                    if (isKeyphraseRecognitionEvent(event)) {
+                        onKeyphraseRecognitionSuccessLocked((KeyphraseRecognitionEvent) event);
+                    } else {
+                        onGenericRecognitionSuccessLocked((GenericRecognitionEvent) event);
+                    }
+
                     break;
             }
         }
     }
 
+    private boolean isKeyphraseRecognitionEvent(RecognitionEvent event) {
+        return mCurrentKeyphraseModelHandle == event.soundModelHandle;
+    }
+
+    private void onGenericRecognitionSuccessLocked(GenericRecognitionEvent event) {
+        if (event.status != SoundTrigger.RECOGNITION_STATUS_SUCCESS) {
+            return;
+        }
+        ModelData model = getModelDataFor(event.soundModelHandle);
+        if (model == null) {
+            Slog.w(TAG, "Generic recognition event: Model does not exist for handle: " +
+                    event.soundModelHandle);
+            return;
+        }
+
+        IRecognitionStatusCallback callback = model.getCallback();
+        if (callback == null) {
+            Slog.w(TAG, "Generic recognition event: Null callback for model handle: " +
+                    event.soundModelHandle);
+            return;
+        }
+
+        try {
+            callback.onDetected((GenericRecognitionEvent) event);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "RemoteException in onDetected", e);
+        }
+
+        model.setStopped();
+        RecognitionConfig config = model.getRecognitionConfig();
+        if (config == null) {
+            Slog.w(TAG, "Generic recognition event: Null RecognitionConfig for model handle: " +
+                    event.soundModelHandle);
+            return;
+        }
+
+        // TODO: Remove this block if the lower layer supports multiple triggers.
+        if (config.allowMultipleTriggers) {
+            startGenericRecognitionLocked(model, true /* notify */);
+        }
+    }
+
     @Override
     public void onSoundModelUpdate(SoundModelEvent event) {
         if (event == null) {
@@ -399,18 +617,25 @@
     private void onRecognitionFailureLocked() {
         Slog.w(TAG, "Recognition failure");
         try {
-            if (mActiveListener != null) {
-                mActiveListener.onError(STATUS_ERROR);
+            if (mKeyphraseListener != null) {
+                mKeyphraseListener.onError(STATUS_ERROR);
             }
         } catch (RemoteException e) {
             Slog.w(TAG, "RemoteException in onError", e);
         } finally {
-            internalClearStateLocked();
+            internalClearKeyphraseStateLocked();
+            internalClearGlobalStateLocked();
         }
     }
 
-    private void onRecognitionSuccessLocked(KeyphraseRecognitionEvent event) {
+    private void onKeyphraseRecognitionSuccessLocked(KeyphraseRecognitionEvent event) {
         Slog.i(TAG, "Recognition success");
+
+        if (mKeyphraseListener == null) {
+            Slog.w(TAG, "received onRecognition event without any listener for it");
+            return;
+        }
+
         KeyphraseRecognitionExtra[] keyphraseExtras =
                 ((KeyphraseRecognitionEvent) event).keyphraseExtras;
         if (keyphraseExtras == null || keyphraseExtras.length == 0) {
@@ -424,14 +649,14 @@
         }
 
         try {
-            if (mActiveListener != null) {
-                mActiveListener.onDetected((KeyphraseRecognitionEvent) event);
+            if (mKeyphraseListener != null) {
+                mKeyphraseListener.onDetected((KeyphraseRecognitionEvent) event);
             }
         } catch (RemoteException e) {
             Slog.w(TAG, "RemoteException in onDetected", e);
         }
 
-        mStarted = false;
+        mKeyphraseStarted = false;
         mRequested = mRecognitionConfig.allowMultipleTriggers;
         // TODO: Remove this block if the lower layer supports multiple triggers.
         if (mRequested) {
@@ -441,14 +666,16 @@
 
     private void onServiceDiedLocked() {
         try {
-            if (mActiveListener != null) {
-                mActiveListener.onError(SoundTrigger.STATUS_DEAD_OBJECT);
+            if (mKeyphraseListener != null) {
+                mKeyphraseListener.onError(SoundTrigger.STATUS_DEAD_OBJECT);
             }
         } catch (RemoteException e) {
             Slog.w(TAG, "RemoteException in onError", e);
         } finally {
-            internalClearSoundModelLocked();
-            internalClearStateLocked();
+            internalClearKeyphraseSoundModelLocked();
+            internalClearKeyphraseStateLocked();
+            internalClearGenericModelStateLocked();
+            internalClearGlobalStateLocked();
             if (mModule != null) {
                 mModule.detach();
                 mModule = null;
@@ -457,14 +684,14 @@
     }
 
     private int updateRecognitionLocked(boolean notify) {
-        if (mModule == null || moduleProperties == null
-                || mCurrentSoundModelHandle == INVALID_VALUE || mActiveListener == null) {
+        if (mModule == null || mModuleProperties == null
+                || mCurrentKeyphraseModelHandle == INVALID_VALUE || mKeyphraseListener == null) {
             // Nothing to do here.
             return STATUS_OK;
         }
 
         boolean start = mRequested && !mCallActive && !mServiceDisabled && !mIsPowerSaveMode;
-        if (start == mStarted) {
+        if (start == mKeyphraseStarted) {
             // No-op.
             return STATUS_OK;
         }
@@ -472,23 +699,24 @@
         // See if the recognition needs to be started.
         if (start) {
             // Start recognition.
-            int status = mModule.startRecognition(mCurrentSoundModelHandle, mRecognitionConfig);
+            int status = mModule.startRecognition(mCurrentKeyphraseModelHandle,
+                    mRecognitionConfig);
             if (status != SoundTrigger.STATUS_OK) {
                 Slog.w(TAG, "startRecognition failed with " + status);
                 // Notify of error if needed.
                 if (notify) {
                     try {
-                        mActiveListener.onError(status);
+                        mKeyphraseListener.onError(status);
                     } catch (RemoteException e) {
                         Slog.w(TAG, "RemoteException in onError", e);
                     }
                 }
             } else {
-                mStarted = true;
+                mKeyphraseStarted = true;
                 // Notify of resume if needed.
                 if (notify) {
                     try {
-                        mActiveListener.onRecognitionResumed();
+                        mKeyphraseListener.onRecognitionResumed();
                     } catch (RemoteException e) {
                         Slog.w(TAG, "RemoteException in onRecognitionResumed", e);
                     }
@@ -499,7 +727,7 @@
             // Stop recognition (only if we haven't been aborted).
             int status = STATUS_OK;
             if (!mRecognitionAborted) {
-                status = mModule.stopRecognition(mCurrentSoundModelHandle);
+                status = mModule.stopRecognition(mCurrentKeyphraseModelHandle);
             } else {
                 mRecognitionAborted = false;
             }
@@ -507,17 +735,17 @@
                 Slog.w(TAG, "stopRecognition call failed with " + status);
                 if (notify) {
                     try {
-                        mActiveListener.onError(status);
+                        mKeyphraseListener.onError(status);
                     } catch (RemoteException e) {
                         Slog.w(TAG, "RemoteException in onError", e);
                     }
                 }
             } else {
-                mStarted = false;
+                mKeyphraseStarted = false;
                 // Notify of pause if needed.
                 if (notify) {
                     try {
-                        mActiveListener.onRecognitionPaused();
+                        mKeyphraseListener.onRecognitionPaused();
                     } catch (RemoteException e) {
                         Slog.w(TAG, "RemoteException in onRecognitionPaused", e);
                     }
@@ -527,14 +755,11 @@
         }
     }
 
-    private void internalClearStateLocked() {
-        mStarted = false;
-        mRequested = false;
-
-        mKeyphraseId = INVALID_VALUE;
-        mRecognitionConfig = null;
-        mActiveListener = null;
-
+    // internalClearGlobalStateLocked() gets split into two routines. Cleanup that is
+    // specific to keyphrase sound models named as internalClearKeyphraseStateLocked() and
+    // internalClearGlobalStateLocked() for global state. The global cleanup routine will be used
+    // by the cleanup happening with the generic sound models.
+    private void internalClearGlobalStateLocked() {
         // Unregister from call state changes.
         mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
 
@@ -545,8 +770,27 @@
         }
     }
 
-    private void internalClearSoundModelLocked() {
-        mCurrentSoundModelHandle = INVALID_VALUE;
+    private void internalClearKeyphraseStateLocked() {
+        mKeyphraseStarted = false;
+        mRequested = false;
+
+        mKeyphraseId = INVALID_VALUE;
+        mRecognitionConfig = null;
+        mKeyphraseListener = null;
+    }
+
+    private void internalClearGenericModelStateLocked() {
+        for (UUID modelId : mGenericModelDataMap.keySet()) {
+            ModelData modelData = mGenericModelDataMap.get(modelId);
+            modelData.clearState();
+            modelData.clearCallback();
+        }
+    }
+
+    // This routine is a replacement for internalClearSoundModelLocked(). However, we
+    // should see why this should be different from internalClearKeyphraseStateLocked().
+    private void internalClearKeyphraseSoundModelLocked() {
+        mCurrentKeyphraseModelHandle = INVALID_VALUE;
         mCurrentSoundModel = null;
     }
 
@@ -577,19 +821,251 @@
     void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         synchronized (mLock) {
             pw.print("  module properties=");
-            pw.println(moduleProperties == null ? "null" : moduleProperties);
+            pw.println(mModuleProperties == null ? "null" : mModuleProperties);
             pw.print("  keyphrase ID="); pw.println(mKeyphraseId);
-            pw.print("  sound model handle="); pw.println(mCurrentSoundModelHandle);
+            pw.print("  sound model handle="); pw.println(mCurrentKeyphraseModelHandle);
             pw.print("  sound model UUID=");
             pw.println(mCurrentSoundModel == null ? "null" : mCurrentSoundModel.uuid);
             pw.print("  current listener=");
-            pw.println(mActiveListener == null ? "null" : mActiveListener.asBinder());
+            pw.println(mKeyphraseListener == null ? "null" : mKeyphraseListener.asBinder());
 
             pw.print("  requested="); pw.println(mRequested);
-            pw.print("  started="); pw.println(mStarted);
+            pw.print("  started="); pw.println(mKeyphraseStarted);
             pw.print("  call active="); pw.println(mCallActive);
             pw.print("  power save mode active="); pw.println(mIsPowerSaveMode);
             pw.print("  service disabled="); pw.println(mServiceDisabled);
         }
     }
+
+    private void initializeTelephonyAndPowerStateListeners() {
+        // Get the current call state synchronously for the first recognition.
+        mCallActive = mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE;
+
+        // Register for call state changes when the first call to start recognition occurs.
+        mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
+
+        // Register for power saver mode changes when the first call to start recognition
+        // occurs.
+        if (mPowerSaveModeListener == null) {
+            mPowerSaveModeListener = new PowerSaveModeListener();
+            mContext.registerReceiver(mPowerSaveModeListener,
+                    new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED));
+        }
+        mIsPowerSaveMode = mPowerManager.isPowerSaveMode();
+    }
+
+    private ModelData getOrCreateGenericModelData(UUID modelId) {
+        ModelData modelData = mGenericModelDataMap.get(modelId);
+        if (modelData == null) {
+            modelData = new ModelData(modelId);
+            modelData.setTypeGeneric();
+            mGenericModelDataMap.put(modelId, modelData);
+        }
+        return modelData;
+    }
+
+    // Instead of maintaining a second hashmap of modelHandle -> ModelData, we just
+    // iterate through to find the right object (since we don't expect 100s of models
+    // to be stored).
+    private ModelData getModelDataFor(int modelHandle) {
+        // Fetch ModelData object corresponding to the model handle.
+        for (ModelData model : mGenericModelDataMap.values()) {
+            if (model.getHandle() == modelHandle) {
+                return model;
+            }
+        }
+        return null;
+    }
+
+    // Whether we are allowed to run any recognition at all. The conditions that let us run
+    // a recognition include: no active phone call or not being in a power save mode. Also,
+    // the native service should be enabled.
+    private boolean isRecognitionAllowed() {
+        return !mCallActive && !mServiceDisabled && !mIsPowerSaveMode;
+    }
+
+    private int startGenericRecognitionLocked(ModelData modelData, boolean notify) {
+        IRecognitionStatusCallback callback = modelData.getCallback();
+        int handle = modelData.getHandle();
+        RecognitionConfig config = modelData.getRecognitionConfig();
+        if (callback == null || handle == INVALID_VALUE || config == null) {
+            // Nothing to do here.
+            Slog.w(TAG, "startGenericRecognition: Bad data passed in.");
+            return STATUS_ERROR;
+        }
+
+        if (!isRecognitionAllowed()) {
+            // Nothing to do here.
+            Slog.w(TAG, "startGenericRecognition requested but not allowed.");
+            return STATUS_OK;
+        }
+
+        int status = mModule.startRecognition(handle, config);
+        if (status != SoundTrigger.STATUS_OK) {
+            Slog.w(TAG, "startRecognition failed with " + status);
+            // Notify of error if needed.
+            if (notify) {
+                try {
+                    callback.onError(status);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "RemoteException in onError", e);
+                }
+            }
+        } else {
+            modelData.setStarted();
+            // Notify of resume if needed.
+            if (notify) {
+                try {
+                    callback.onRecognitionResumed();
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "RemoteException in onRecognitionResumed", e);
+                }
+            }
+        }
+        return status;
+    }
+
+    private int stopGenericRecognitionLocked(ModelData modelData, boolean notify) {
+        IRecognitionStatusCallback callback = modelData.getCallback();
+
+        // Stop recognition (only if we haven't been aborted).
+        int status = mModule.stopRecognition(modelData.getHandle());
+        if (status != SoundTrigger.STATUS_OK) {
+            Slog.w(TAG, "stopRecognition call failed with " + status);
+            if (notify) {
+                try {
+                    callback.onError(status);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "RemoteException in onError", e);
+                }
+            }
+        } else {
+            modelData.setStopped();
+            // Notify of pause if needed.
+            if (notify) {
+                try {
+                    callback.onRecognitionPaused();
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "RemoteException in onRecognitionPaused", e);
+                }
+            }
+        }
+        return status;
+    }
+
+    // Computes whether we have any recognition running at all (voice or generic). Sets
+    // the mRecognitionRunning variable with the result.
+    private boolean computeRecognitionRunning() {
+        synchronized (mLock) {
+            if (mModuleProperties == null || mModule == null) {
+                mRecognitionRunning = false;
+                return mRecognitionRunning;
+            }
+            if (mKeyphraseListener != null &&
+                    mKeyphraseStarted &&
+                    mCurrentKeyphraseModelHandle != INVALID_VALUE &&
+                    mCurrentSoundModel != null) {
+                mRecognitionRunning = true;
+                return mRecognitionRunning;
+            }
+            for (UUID modelId : mGenericModelDataMap.keySet()) {
+                ModelData modelData = mGenericModelDataMap.get(modelId);
+                if (modelData.modelStarted()) {
+                    mRecognitionRunning = true;
+                    return mRecognitionRunning;
+                }
+            }
+            mRecognitionRunning = false;
+        }
+        return mRecognitionRunning;
+    }
+
+    // This class encapsulates the callbacks, state, handles and any other information that
+    // represents a model.
+    private static class ModelData {
+        // Model not loaded (and hence not started).
+        static final int MODEL_NOTLOADED = 0;
+
+        // Loaded implies model was successfully loaded. Model not started yet.
+        static final int MODEL_LOADED = 1;
+
+        // Started implies model was successfully loaded and start was called.
+        static final int MODEL_STARTED = 2;
+
+        // One of MODEL_NOTLOADED, MODEL_LOADED, MODEL_STARTED (which implies loaded).
+        private int mModelState;
+
+        private UUID mModelId;
+
+        // One of SoundModel.TYPE_GENERIC or SoundModel.TYPE_KEYPHRASE. Initially set
+        // to SoundModel.TYPE_UNKNOWN;
+        private int mModelType = SoundModel.TYPE_UNKNOWN;
+        private IRecognitionStatusCallback mCallback = null;
+        private SoundModel mSoundModel = null;
+        private RecognitionConfig mRecognitionConfig = null;
+
+
+        // Model handle is an integer used by the HAL as an identifier for sound
+        // models.
+        private int mModelHandle = INVALID_VALUE;
+
+        ModelData(UUID modelId) {
+            mModelId = modelId;
+        }
+
+        synchronized void setTypeGeneric() {
+            mModelType = SoundModel.TYPE_GENERIC_SOUND;
+        }
+
+        synchronized void setCallback(IRecognitionStatusCallback callback) {
+            mCallback = callback;
+        }
+
+        synchronized IRecognitionStatusCallback getCallback() {
+            return mCallback;
+        }
+
+        synchronized boolean isModelLoaded() {
+            return (mModelState == MODEL_LOADED || mModelState == MODEL_STARTED) &&
+                    mSoundModel != null;
+        }
+
+        synchronized void setStarted() {
+            mModelState = MODEL_STARTED;
+        }
+
+        synchronized void setStopped() {
+            mModelState = MODEL_LOADED;
+        }
+
+        synchronized boolean modelStarted() {
+            return mModelState == MODEL_STARTED;
+        }
+
+        synchronized void clearState() {
+            mModelState = MODEL_NOTLOADED;
+            mSoundModel = null;
+            mModelHandle = INVALID_VALUE;
+        }
+
+        synchronized void clearCallback() {
+            mCallback = null;
+        }
+
+        synchronized void setHandle(int handle) {
+            mModelHandle = handle;
+        }
+
+        synchronized void setRecognitionConfig(RecognitionConfig config) {
+            mRecognitionConfig = config;
+        }
+
+        synchronized int getHandle() {
+            return mModelHandle;
+        }
+
+        synchronized RecognitionConfig getRecognitionConfig() {
+            return mRecognitionConfig;
+        }
+    }
 }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 682f4a4..251f314 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -15,6 +15,7 @@
  */
 
 package com.android.server.soundtrigger;
+import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR;
 
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -47,13 +48,14 @@
  * @hide
  */
 public class SoundTriggerService extends SystemService {
-    static final String TAG = "SoundTriggerService";
-    static final boolean DEBUG = false;
+    private static final String TAG = "SoundTriggerService";
+    private static final boolean DEBUG = true;
 
     final Context mContext;
     private final SoundTriggerServiceStub mServiceStub;
     private final LocalSoundTriggerService mLocalSoundTriggerService;
     private SoundTriggerDbHelper mDbHelper;
+    private SoundTriggerHelper mSoundTriggerHelper;
 
     public SoundTriggerService(Context context) {
         super(context);
@@ -71,7 +73,8 @@
     @Override
     public void onBootPhase(int phase) {
         if (PHASE_SYSTEM_SERVICES_READY == phase) {
-            mLocalSoundTriggerService.initSoundTriggerHelper();
+            initSoundTriggerHelper();
+            mLocalSoundTriggerService.setSoundTriggerHelper(mSoundTriggerHelper);
         } else if (PHASE_THIRD_PARTY_APPS_CAN_START == phase) {
             mDbHelper = new SoundTriggerDbHelper(mContext);
         }
@@ -85,6 +88,20 @@
     public void onSwitchUser(int userHandle) {
     }
 
+    private synchronized void initSoundTriggerHelper() {
+        if (mSoundTriggerHelper == null) {
+            mSoundTriggerHelper = new SoundTriggerHelper(mContext);
+        }
+    }
+
+    private synchronized boolean isInitialized() {
+        if (mSoundTriggerHelper == null ) {
+            Slog.e(TAG, "SoundTriggerHelper not initialized.");
+            return false;
+        }
+        return true;
+    }
+
     class SoundTriggerServiceStub extends ISoundTriggerService.Stub {
         @Override
         public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
@@ -102,19 +119,32 @@
         }
 
         @Override
-        public void startRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback) {
+        public int startRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback,
+                RecognitionConfig config) {
             enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
             if (DEBUG) {
                 Slog.i(TAG, "startRecognition(): Uuid : " + parcelUuid);
             }
+            if (!isInitialized()) return STATUS_ERROR;
+
+            GenericSoundModel model = getSoundModel(parcelUuid);
+            if (model == null) {
+                Slog.e(TAG, "Null model in database for id: " + parcelUuid);
+                return STATUS_ERROR;
+            }
+
+            return mSoundTriggerHelper.startGenericRecognition(parcelUuid.getUuid(), model,
+                    callback, config);
         }
 
         @Override
-        public void stopRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback) {
+        public int stopRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback) {
             enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
             if (DEBUG) {
                 Slog.i(TAG, "stopRecognition(): Uuid : " + parcelUuid);
             }
+            if (!isInitialized()) return STATUS_ERROR;
+            return mSoundTriggerHelper.stopGenericRecognition(parcelUuid.getUuid(), callback);
         }
 
         @Override
@@ -123,10 +153,8 @@
             if (DEBUG) {
                 Slog.i(TAG, "getSoundModel(): id = " + soundModelId);
             }
-            SoundTrigger.GenericSoundModel model = mDbHelper.getGenericSoundModel(soundModelId.getUuid());
-            if (model == null) {
-                Slog.e(TAG, "Null model in database.");
-            }
+            SoundTrigger.GenericSoundModel model = mDbHelper.getGenericSoundModel(
+                    soundModelId.getUuid());
             return model;
         }
 
@@ -157,38 +185,49 @@
             mContext = context;
         }
 
-        void initSoundTriggerHelper() {
-            if (mSoundTriggerHelper == null) {
-                mSoundTriggerHelper = new SoundTriggerHelper(mContext);
-            }
+        synchronized void setSoundTriggerHelper(SoundTriggerHelper helper) {
+            mSoundTriggerHelper = helper;
         }
 
         @Override
         public int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
                 IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig) {
-            return mSoundTriggerHelper.startRecognition(keyphraseId, soundModel, listener,
+            if (!isInitialized()) return STATUS_ERROR;
+            return mSoundTriggerHelper.startKeyphraseRecognition(keyphraseId, soundModel, listener,
                     recognitionConfig);
         }
 
         @Override
-        public int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
-            return mSoundTriggerHelper.stopRecognition(keyphraseId, listener);
+        public synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
+            if (!isInitialized()) return STATUS_ERROR;
+            return mSoundTriggerHelper.stopKeyphraseRecognition(keyphraseId, listener);
         }
 
         @Override
         public void stopAllRecognitions() {
+            if (!isInitialized()) return;
             mSoundTriggerHelper.stopAllRecognitions();
         }
 
         @Override
         public ModuleProperties getModuleProperties() {
+            if (!isInitialized()) return null;
             return mSoundTriggerHelper.getModuleProperties();
         }
 
         @Override
         public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            if (!isInitialized()) return;
             mSoundTriggerHelper.dump(fd, pw, args);
         }
+
+        private synchronized boolean isInitialized() {
+            if (mSoundTriggerHelper == null ) {
+                Slog.e(TAG, "SoundTriggerHelper not initialized.");
+                return false;
+            }
+            return true;
+        }
     }
 
     private void enforceCallingPermission(String permission) {
diff --git a/tests/SoundTriggerTestApp/Android.mk b/tests/SoundTriggerTestApp/Android.mk
index 7bcab5e..c327b09 100644
--- a/tests/SoundTriggerTestApp/Android.mk
+++ b/tests/SoundTriggerTestApp/Android.mk
@@ -8,5 +8,6 @@
 LOCAL_MODULE_TAGS := optional
 
 LOCAL_PRIVILEGED_MODULE := true
+LOCAL_CERTIFICATE := platform
 
 include $(BUILD_PACKAGE)
diff --git a/tests/SoundTriggerTestApp/AndroidManifest.xml b/tests/SoundTriggerTestApp/AndroidManifest.xml
index 40619da..a72b3dd 100644
--- a/tests/SoundTriggerTestApp/AndroidManifest.xml
+++ b/tests/SoundTriggerTestApp/AndroidManifest.xml
@@ -2,16 +2,22 @@
         package="com.android.test.soundtrigger">
 
     <uses-permission android:name="android.permission.MANAGE_SOUND_TRIGGER" />
-    <application
-         android:permission="android.permission.MANAGE_SOUND_TRIGGER">
+    <application>
         <activity
             android:name="TestSoundTriggerActivity"
             android:label="SoundTrigger Test Application"
-            android:theme="@android:style/Theme.Material.Light.Voice">
+            android:theme="@android:style/Theme.Material">
+            <!--
             <intent-filter>
                 <action android:name="com.android.intent.action.MANAGE_SOUND_TRIGGER" />
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
+            -->
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
         </activity>
     </application>
 </manifest>
diff --git a/tests/SoundTriggerTestApp/res/layout/main.xml b/tests/SoundTriggerTestApp/res/layout/main.xml
index 9d2b9d9..5ecc770 100644
--- a/tests/SoundTriggerTestApp/res/layout/main.xml
+++ b/tests/SoundTriggerTestApp/res/layout/main.xml
@@ -18,6 +18,11 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    android:orientation="vertical"
+    >
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
     >
 
     <Button
@@ -37,7 +42,57 @@
     <Button
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
+        android:text="@string/start_recog"
+        android:onClick="onStartRecognitionButtonClicked"
+        android:padding="20dp" />
+
+    <Button
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/stop_recog"
+        android:onClick="onStopRecognitionButtonClicked"
+        android:padding="20dp" />
+
+    <Button
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
         android:text="@string/unenroll"
         android:onClick="onUnEnrollButtonClicked"
         android:padding="20dp" />
-</LinearLayout>
\ No newline at end of file
+
+</LinearLayout>
+
+<RadioGroup xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:padding="20dp"
+        android:orientation="vertical">
+   <RadioButton android:id="@+id/model_one"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/model_one"
+        android:onClick="onRadioButtonClicked"/>
+   <RadioButton android:id="@+id/model_two"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/model_two"
+        android:onClick="onRadioButtonClicked"/>
+   <RadioButton android:id="@+id/model_three"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/model_three"
+        android:onClick="onRadioButtonClicked"/>
+</RadioGroup>
+
+    <TextView
+        android:id="@+id/console"
+        android:gravity="left"
+        android:paddingTop="20pt"
+        android:layout_height="fill_parent"
+        android:layout_width="match_parent"
+        android:maxLines="40"
+        android:textSize="14dp"
+        android:scrollbars = "vertical"
+        android:text="@string/none">
+     </TextView>
+</LinearLayout>
diff --git a/tests/SoundTriggerTestApp/res/values/strings.xml b/tests/SoundTriggerTestApp/res/values/strings.xml
index 07bac2a..5f0fb1d 100644
--- a/tests/SoundTriggerTestApp/res/values/strings.xml
+++ b/tests/SoundTriggerTestApp/res/values/strings.xml
@@ -16,7 +16,13 @@
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
 
-    <string name="enroll">Enroll</string>
-    <string name="reenroll">Re-enroll</string>
-    <string name="unenroll">Un-enroll</string>
-</resources>
\ No newline at end of file
+    <string name="enroll">Load</string>
+    <string name="reenroll">Re-load</string>
+    <string name="unenroll">Un-load</string>
+    <string name="start_recog">Start</string>
+    <string name="stop_recog">Stop</string>
+    <string name="model_one">Model One</string>
+    <string name="model_two">Model Two</string>
+    <string name="model_three">Model Three</string>
+    <string name="none">Debug messages appear here:</string>
+</resources>
diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java
index 4702835..1c95c25 100644
--- a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java
+++ b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.hardware.soundtrigger.SoundTrigger;
 import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
+import android.media.soundtrigger.SoundTriggerDetector;
 import android.media.soundtrigger.SoundTriggerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -28,6 +29,7 @@
 
 import com.android.internal.app.ISoundTriggerService;
 
+import java.lang.RuntimeException;
 import java.util.UUID;
 
 /**
@@ -56,6 +58,9 @@
      */
     public boolean addOrUpdateSoundModel(GenericSoundModel soundModel) {
         try {
+            if (soundModel == null) {
+                throw new RuntimeException("Bad sound model");
+            }
             mSoundTriggerService.updateSoundModel(soundModel);
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException in updateSoundModel", e);
@@ -112,4 +117,10 @@
     public void deleteSoundModelUsingManager(UUID modelId) {
             mSoundTriggerManager.deleteModel(modelId);
     }
+
+    public SoundTriggerDetector createSoundTriggerDetector(UUID modelId,
+            SoundTriggerDetector.Callback callback) {
+        return mSoundTriggerManager.createSoundTriggerDetector(modelId, callback, null);
+    }
+
 }
diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/TestSoundTriggerActivity.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/TestSoundTriggerActivity.java
index 966179b..96a6966 100644
--- a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/TestSoundTriggerActivity.java
+++ b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/TestSoundTriggerActivity.java
@@ -22,11 +22,17 @@
 import android.app.Activity;
 import android.hardware.soundtrigger.SoundTrigger;
 import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
+import android.media.AudioFormat;
+import android.media.soundtrigger.SoundTriggerDetector;
 import android.media.soundtrigger.SoundTriggerManager;
+import android.text.Editable;
+import android.text.method.ScrollingMovementMethod;
 import android.os.Bundle;
 import android.os.UserManager;
 import android.util.Log;
 import android.view.View;
+import android.widget.RadioButton;
+import android.widget.TextView;
 import android.widget.Toast;
 
 public class TestSoundTriggerActivity extends Activity {
@@ -35,42 +41,75 @@
 
     private SoundTriggerUtil mSoundTriggerUtil;
     private Random mRandom;
-    private UUID mModelUuid = UUID.randomUUID();
+    private UUID mModelUuid1 = UUID.randomUUID();
     private UUID mModelUuid2 = UUID.randomUUID();
+    private UUID mModelUuid3 = UUID.randomUUID();
     private UUID mVendorUuid = UUID.randomUUID();
 
+    private SoundTriggerDetector mDetector1 = null;
+    private SoundTriggerDetector mDetector2 = null;
+    private SoundTriggerDetector mDetector3 = null;
+
+    private TextView mDebugView = null;
+    private int mSelectedModelId = 1;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         if (DBG) Log.d(TAG, "onCreate");
         super.onCreate(savedInstanceState);
         setContentView(R.layout.main);
+        mDebugView = (TextView) findViewById(R.id.console);
+        mDebugView.setText(mDebugView.getText(), TextView.BufferType.EDITABLE);
+        mDebugView.setMovementMethod(new ScrollingMovementMethod());
         mSoundTriggerUtil = new SoundTriggerUtil(this);
         mRandom = new Random();
     }
 
+    private void postMessage(String msg) {
+        Log.i(TAG, "Posted: " + msg);
+        ((Editable) mDebugView.getText()).append(msg + "\n");
+    }
+
+    private UUID getSelectedUuid() {
+        if (mSelectedModelId == 2) return mModelUuid2;
+        if (mSelectedModelId == 3) return mModelUuid3;
+        return mModelUuid1;  // Default.
+    }
+
+    private void setDetector(SoundTriggerDetector detector) {
+        if (mSelectedModelId == 2) mDetector2 = detector;
+        if (mSelectedModelId == 3) mDetector3 = detector;
+        mDetector1 = detector;
+    }
+
+    private SoundTriggerDetector getDetector() {
+        if (mSelectedModelId == 2) return mDetector2;
+        if (mSelectedModelId == 3) return mDetector3;
+        return mDetector1;
+    }
+
     /**
      * Called when the user clicks the enroll button.
      * Performs a fresh enrollment.
      */
     public void onEnrollButtonClicked(View v) {
+        postMessage("Loading model: " + mSelectedModelId);
         // Generate a fake model to push.
         byte[] data = new byte[1024];
         mRandom.nextBytes(data);
-        GenericSoundModel model = new GenericSoundModel(mModelUuid, mVendorUuid, data);
+        UUID modelUuid = getSelectedUuid();
+        GenericSoundModel model = new GenericSoundModel(modelUuid, mVendorUuid, data);
 
         boolean status = mSoundTriggerUtil.addOrUpdateSoundModel(model);
         if (status) {
             Toast.makeText(
-                    this, "Successfully created sound trigger model UUID=" + mModelUuid, Toast.LENGTH_SHORT)
-                    .show();
+                    this, "Successfully created sound trigger model UUID=" + modelUuid,
+                    Toast.LENGTH_SHORT).show();
         } else {
-            Toast.makeText(this, "Failed to enroll!!!" + mModelUuid, Toast.LENGTH_SHORT).show();
+            Toast.makeText(this, "Failed to enroll!!!" + modelUuid, Toast.LENGTH_SHORT).show();
         }
 
         // Test the SoundManager API.
-        SoundTriggerManager.Model tmpModel = SoundTriggerManager.Model.create(mModelUuid2,
-                mVendorUuid, data);
-        mSoundTriggerUtil.addOrUpdateSoundModel(tmpModel);
     }
 
     /**
@@ -78,12 +117,14 @@
      * Clears the enrollment information for the user.
      */
     public void onUnEnrollButtonClicked(View v) {
-        GenericSoundModel soundModel = mSoundTriggerUtil.getSoundModel(mModelUuid);
+        postMessage("Unloading model: " + mSelectedModelId);
+        UUID modelUuid = getSelectedUuid();
+        GenericSoundModel soundModel = mSoundTriggerUtil.getSoundModel(modelUuid);
         if (soundModel == null) {
             Toast.makeText(this, "Sound model not found!!!", Toast.LENGTH_SHORT).show();
             return;
         }
-        boolean status = mSoundTriggerUtil.deleteSoundModel(mModelUuid);
+        boolean status = mSoundTriggerUtil.deleteSoundModel(mModelUuid1);
         if (status) {
             Toast.makeText(this, "Successfully deleted model UUID=" + soundModel.uuid,
                     Toast.LENGTH_SHORT)
@@ -91,7 +132,6 @@
         } else {
             Toast.makeText(this, "Failed to delete sound model!!!", Toast.LENGTH_SHORT).show();
         }
-        mSoundTriggerUtil.deleteSoundModelUsingManager(mModelUuid2);
     }
 
     /**
@@ -99,7 +139,9 @@
      * Uses the previously enrolled sound model and makes changes to it before pushing it back.
      */
     public void onReEnrollButtonClicked(View v) {
-        GenericSoundModel soundModel = mSoundTriggerUtil.getSoundModel(mModelUuid);
+        postMessage("Re-loading model: " + mSelectedModelId);
+        UUID modelUuid = getSelectedUuid();
+        GenericSoundModel soundModel = mSoundTriggerUtil.getSoundModel(modelUuid);
         if (soundModel == null) {
             Toast.makeText(this, "Sound model not found!!!", Toast.LENGTH_SHORT).show();
             return;
@@ -118,4 +160,86 @@
             Toast.makeText(this, "Failed to re-enroll!!!", Toast.LENGTH_SHORT).show();
         }
     }
+
+    public void onStartRecognitionButtonClicked(View v) {
+        UUID modelUuid = getSelectedUuid();
+        SoundTriggerDetector detector = getDetector();
+        if (detector == null) {
+            Log.i(TAG, "Created an instance of the SoundTriggerDetector.");
+            detector = mSoundTriggerUtil.createSoundTriggerDetector(modelUuid,
+                    new DetectorCallback());
+            setDetector(detector);
+        }
+        postMessage("Triggering start recognition for model: " + mSelectedModelId);
+        if (!detector.startRecognition(
+                SoundTriggerDetector.RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS)) {
+            Log.e(TAG, "Fast failure attempting to start recognition.");
+        }
+    }
+
+    public void onStopRecognitionButtonClicked(View v) {
+        SoundTriggerDetector detector = getDetector();
+        if (detector == null) {
+            Log.e(TAG, "Stop called on null detector.");
+            return;
+        }
+        postMessage("Triggering stop recognition for model: " + mSelectedModelId);
+        if (!detector.stopRecognition()) {
+            Log.e(TAG, "Fast failure attempting to stop recognition.");
+        }
+    }
+
+    public void onRadioButtonClicked(View view) {
+        // Is the button now checked?
+        boolean checked = ((RadioButton) view).isChecked();
+        // Check which radio button was clicked
+        switch(view.getId()) {
+            case R.id.model_one:
+                if (checked) mSelectedModelId = 1;
+                postMessage("Selected model one.");
+                break;
+            case R.id.model_two:
+                if (checked) mSelectedModelId = 2;
+                postMessage("Selected model two.");
+                break;
+            case R.id.model_three:
+                if (checked) mSelectedModelId = 3;
+                postMessage("Selected model three.");
+                break;
+        }
+    }
+
+    // Implementation of SoundTriggerDetector.Callback.
+    public class DetectorCallback extends SoundTriggerDetector.Callback {
+        public void onAvailabilityChanged(int status) {
+            postMessage("Availability changed to: " + status);
+        }
+
+        public void onDetected(SoundTriggerDetector.EventPayload event) {
+            postMessage("onDetected(): " + eventPayloadToString(event));
+        }
+
+        public void onError() {
+            postMessage("onError()");
+        }
+
+        public void onRecognitionPaused() {
+            postMessage("onRecognitionPaused()");
+        }
+
+        public void onRecognitionResumed() {
+            postMessage("onRecognitionResumed()");
+        }
+    }
+
+    private String eventPayloadToString(SoundTriggerDetector.EventPayload event) {
+        String result = "EventPayload(";
+        AudioFormat format =  event.getCaptureAudioFormat();
+        result = result + "AudioFormat: " + ((format == null) ? "null" : format.toString());
+        byte[] triggerAudio = event.getTriggerAudio();
+        result = result + "TriggerAudio: " + (triggerAudio == null ? "null" : triggerAudio.length);
+        result = result + "CaptureSession: " + event.getCaptureSession();
+        result += " )";
+        return result;
+    }
 }