Merge "Launch application details for correct profile from recents." into lmp-dev
diff --git a/api/current.txt b/api/current.txt
index a51216e3..5cafd6c 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -13236,10 +13236,10 @@
     method public void registerDisplayListener(android.hardware.display.DisplayManager.DisplayListener, android.os.Handler);
     method public void unregisterDisplayListener(android.hardware.display.DisplayManager.DisplayListener);
     field public static final java.lang.String DISPLAY_CATEGORY_PRESENTATION = "android.hardware.display.category.PRESENTATION";
+    field public static final int VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR = 16; // 0x10
     field public static final int VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY = 8; // 0x8
     field public static final int VIRTUAL_DISPLAY_FLAG_PRESENTATION = 2; // 0x2
     field public static final int VIRTUAL_DISPLAY_FLAG_PUBLIC = 1; // 0x1
-    field public static final int VIRTUAL_DISPLAY_FLAG_SCREEN_SHARE = 16; // 0x10
     field public static final int VIRTUAL_DISPLAY_FLAG_SECURE = 4; // 0x4
   }
 
@@ -16401,7 +16401,7 @@
   public final class MediaProjection {
     method public void addCallback(android.media.projection.MediaProjection.Callback, android.os.Handler);
     method public android.media.AudioRecord createAudioRecord(int, int, int, int);
-    method public android.hardware.display.VirtualDisplay createVirtualDisplay(java.lang.String, int, int, int, boolean, android.view.Surface, android.hardware.display.VirtualDisplay.Callbacks, android.os.Handler);
+    method public android.hardware.display.VirtualDisplay createVirtualDisplay(java.lang.String, int, int, int, int, android.view.Surface, android.hardware.display.VirtualDisplay.Callbacks, android.os.Handler);
     method public void removeCallback(android.media.projection.MediaProjection.Callback);
     method public void stop();
   }
@@ -27533,13 +27533,16 @@
 
   public static abstract interface AlwaysOnHotwordDetector.Callback {
     method public abstract void onAvailabilityChanged(int);
-    method public abstract void onDetected(android.service.voice.AlwaysOnHotwordDetector.TriggerAudio);
+    method public abstract void onDetected(android.service.voice.AlwaysOnHotwordDetector.EventPayload);
     method public abstract void onError();
+    method public abstract void onRecognitionPaused();
+    method public abstract void onRecognitionResumed();
   }
 
-  public static class AlwaysOnHotwordDetector.TriggerAudio {
+  public static class AlwaysOnHotwordDetector.EventPayload {
     field public final android.media.AudioFormat audioFormat;
     field public final byte[] data;
+    field public final boolean isTriggerAudio;
   }
 
   public class VoiceInteractionService extends android.app.Service {
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index fc200550..3bf8e2e 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -700,6 +700,13 @@
          */
         public int affiliatedTaskId;
 
+        /**
+         * Task affiliation color of the source task with the affiliated task id.
+         *
+         * @hide
+         */
+        public int affiliatedTaskColor;
+
         public RecentTaskInfo() {
         }
 
@@ -732,6 +739,7 @@
             dest.writeLong(firstActiveTime);
             dest.writeLong(lastActiveTime);
             dest.writeInt(affiliatedTaskId);
+            dest.writeInt(affiliatedTaskColor);
         }
 
         public void readFromParcel(Parcel source) {
@@ -747,6 +755,7 @@
             firstActiveTime = source.readLong();
             lastActiveTime = source.readLong();
             affiliatedTaskId = source.readInt();
+            affiliatedTaskColor = source.readInt();
         }
 
         public static final Creator<RecentTaskInfo> CREATOR
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index be8108c..df0365e 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -16,6 +16,7 @@
 
 package android.app.backup;
 
+import android.annotation.SystemApi;
 import android.app.backup.RestoreSession;
 import android.app.backup.IBackupManager;
 import android.app.backup.IRestoreSession;
@@ -114,7 +115,7 @@
             try {
                 sService.dataChanged(packageName);
             } catch (RemoteException e) {
-                Log.d(TAG, "dataChanged(pkg) couldn't connect");
+                Log.e(TAG, "dataChanged(pkg) couldn't connect");
             }
         }
     }
@@ -150,7 +151,7 @@
                     result = session.restorePackage(mContext.getPackageName(), observer);
                 }
             } catch (RemoteException e) {
-                Log.w(TAG, "restoreSelf() unable to contact service");
+                Log.e(TAG, "restoreSelf() unable to contact service");
             } finally {
                 if (session != null) {
                     session.endRestoreSession();
@@ -176,9 +177,142 @@
                     session = new RestoreSession(mContext, binder);
                 }
             } catch (RemoteException e) {
-                Log.w(TAG, "beginRestoreSession() couldn't connect");
+                Log.e(TAG, "beginRestoreSession() couldn't connect");
             }
         }
         return session;
     }
+
+    // system APIs start here
+
+    /**
+     * Enable/disable the backup service entirely.  When disabled, no backup
+     * or restore operations will take place.  Data-changed notifications will
+     * still be observed and collected, however, so that changes made while the
+     * mechanism was disabled will still be backed up properly if it is enabled
+     * at some point in the future.
+     *
+     * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+     *
+     * @hide
+     */
+    @SystemApi
+    public void setBackupEnabled(boolean isEnabled) {
+        checkServiceBinder();
+        if (sService != null) {
+            try {
+                sService.setBackupEnabled(isEnabled);
+            } catch (RemoteException e) {
+                Log.e(TAG, "setBackupEnabled() couldn't connect");
+            }
+        }
+    }
+
+    /**
+     * Report whether the backup mechanism is currently enabled.
+     *
+     * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+     *
+     * @hide
+     */
+    @SystemApi
+    public boolean isBackupEnabled() {
+        if (sService != null) {
+            try {
+                return sService.isBackupEnabled();
+            } catch (RemoteException e) {
+                Log.e(TAG, "isBackupEnabled() couldn't connect");
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Identify the currently selected transport.  Callers must hold the
+     * android.permission.BACKUP permission to use this method.
+     * @return The name of the currently active backup transport.  In case of
+     *   failure or if no transport is currently active, this method returns {@code null}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public String getCurrentTransport() {
+        checkServiceBinder();
+        if (sService != null) {
+            try {
+                return sService.getCurrentTransport();
+            } catch (RemoteException e) {
+                Log.e(TAG, "getCurrentTransport() couldn't connect");
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Request a list of all available backup transports' names.  Callers must
+     * hold the android.permission.BACKUP permission to use this method.
+     *
+     * @hide
+     */
+    @SystemApi
+    public String[] listAllTransports() {
+        checkServiceBinder();
+        if (sService != null) {
+            try {
+                return sService.listAllTransports();
+            } catch (RemoteException e) {
+                Log.e(TAG, "listAllTransports() couldn't connect");
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Specify the current backup transport.  Callers must hold the
+     * android.permission.BACKUP permission to use this method.
+     *
+     * @param transport The name of the transport to select.  This should be one
+     *   of the names returned by {@link #listAllTransports()}.
+     * @return The name of the previously selected transport.  If the given transport
+     *   name is not one of the currently available transports, no change is made to
+     *   the current transport setting and the method returns null.
+     *
+     * @hide
+     */
+    @SystemApi
+    public String selectBackupTransport(String transport) {
+        checkServiceBinder();
+        if (sService != null) {
+            try {
+                return sService.selectBackupTransport(transport);
+            } catch (RemoteException e) {
+                Log.e(TAG, "selectBackupTransport() couldn't connect");
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Schedule an immediate backup attempt for all pending key/value updates.  This
+     * is primarily intended for transports to use when they detect a suitable
+     * opportunity for doing a backup pass.  If there are no pending updates to
+     * be sent, no action will be taken.  Even if some updates are pending, the
+     * transport will still be asked to confirm via the usual requestBackupTime()
+     * method.
+     *
+     * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+     *
+     * @hide
+     */
+    @SystemApi
+    public void backupNow() {
+        checkServiceBinder();
+        if (sService != null) {
+            try {
+                sService.backupNow();
+            } catch (RemoteException e) {
+                Log.e(TAG, "backupNow() couldn't connect");
+            }
+        }
+    }
 }
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
index 446c03e..dc3bbc0 100644
--- a/core/java/android/app/backup/BackupTransport.java
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -16,6 +16,7 @@
 
 package android.app.backup;
 
+import android.annotation.SystemApi;
 import android.content.Intent;
 import android.content.pm.PackageInfo;
 import android.os.IBinder;
@@ -30,6 +31,7 @@
  *
  * @hide
  */
+@SystemApi
 public class BackupTransport {
     // Zero return always means things are okay.  If returned from
     // getNextFullRestoreDataChunk(), it means that no data could be delivered at
diff --git a/core/java/android/app/backup/RestoreDescription.java b/core/java/android/app/backup/RestoreDescription.java
index 50ab0b4..611ff07 100644
--- a/core/java/android/app/backup/RestoreDescription.java
+++ b/core/java/android/app/backup/RestoreDescription.java
@@ -16,6 +16,7 @@
 
 package android.app.backup;
 
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -28,6 +29,7 @@
  *
  * @hide
  */
+@SystemApi
 public class RestoreDescription implements Parcelable {
     private final String mPackageName;
     private final int mDataType;
diff --git a/core/java/android/app/backup/RestoreSession.java b/core/java/android/app/backup/RestoreSession.java
index 7181c61..0a885b6 100644
--- a/core/java/android/app/backup/RestoreSession.java
+++ b/core/java/android/app/backup/RestoreSession.java
@@ -16,6 +16,7 @@
 
 package android.app.backup;
 
+import android.annotation.SystemApi;
 import android.app.backup.RestoreObserver;
 import android.app.backup.RestoreSet;
 import android.app.backup.IRestoreObserver;
@@ -30,6 +31,7 @@
  * Interface for managing a restore session.
  * @hide
  */
+@SystemApi
 public class RestoreSession {
     static final String TAG = "RestoreSession";
 
diff --git a/core/java/android/app/backup/RestoreSet.java b/core/java/android/app/backup/RestoreSet.java
index 0431977..aacaf7c 100644
--- a/core/java/android/app/backup/RestoreSet.java
+++ b/core/java/android/app/backup/RestoreSet.java
@@ -16,6 +16,7 @@
 
 package android.app.backup;
 
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -25,6 +26,7 @@
  *
  * @hide
  */
+@SystemApi
 public class RestoreSet implements Parcelable {
     /**
      * Name of this restore set.  May be user generated, may simply be the name
diff --git a/core/java/android/bluetooth/BluetoothGattCallbackWrapper.java b/core/java/android/bluetooth/BluetoothGattCallbackWrapper.java
index 0eb9d21..da992f5 100644
--- a/core/java/android/bluetooth/BluetoothGattCallbackWrapper.java
+++ b/core/java/android/bluetooth/BluetoothGattCallbackWrapper.java
@@ -124,8 +124,7 @@
     }
 
     @Override
-    public void onFoundOrLost(boolean onFound, String address, int rssi, byte[] advData)
-            throws RemoteException {
+    public void onFoundOrLost(boolean onFound, ScanResult scanResult) throws RemoteException {
     }
 
 }
diff --git a/core/java/android/bluetooth/IBluetoothGattCallback.aidl b/core/java/android/bluetooth/IBluetoothGattCallback.aidl
index f14cce0..00b6b1b 100644
--- a/core/java/android/bluetooth/IBluetoothGattCallback.aidl
+++ b/core/java/android/bluetooth/IBluetoothGattCallback.aidl
@@ -69,6 +69,5 @@
                                   in AdvertiseSettings advertiseSettings);
     void onConfigureMTU(in String address, in int mtu, in int status);
     void onConnectionCongested(in String address, in boolean congested);
-    void onFoundOrLost(in boolean onFound, in String address, in int rssi,
-                             in byte[] advData);
+    void onFoundOrLost(in boolean onFound, in ScanResult scanResult);
 }
diff --git a/core/java/android/bluetooth/le/BluetoothLeScanner.java b/core/java/android/bluetooth/le/BluetoothLeScanner.java
index 45e466f..6667cc4 100644
--- a/core/java/android/bluetooth/le/BluetoothLeScanner.java
+++ b/core/java/android/bluetooth/le/BluetoothLeScanner.java
@@ -322,22 +322,27 @@
         }
 
         @Override
-        public void onFoundOrLost(boolean onFound, String address, int rssi,
-                byte[] advData) {
+        public void onFoundOrLost(final boolean onFound, final ScanResult scanResult) {
             if (DBG) {
-                Log.d(TAG, "onFoundOrLost() - Device=" + address);
+                Log.d(TAG, "onFoundOrLost() - onFound = " + onFound +
+                        " " + scanResult.toString());
             }
-            // ToDo: Fix issue with underlying reporting from chipset
-            BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(
-                    address);
-            long scanNanos = SystemClock.elapsedRealtimeNanos();
-            ScanResult result = new ScanResult(device, ScanRecord.parseFromBytes(advData), rssi,
-                    scanNanos);
-            if (onFound) {
-                mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_FIRST_MATCH, result);
-            } else {
-                mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_MATCH_LOST, result);
+
+            // Check null in case the scan has been stopped
+            synchronized (this) {
+                if (mClientIf <= 0) return;
             }
+            Handler handler = new Handler(Looper.getMainLooper());
+            handler.post(new Runnable() {
+                    @Override
+                public void run() {
+                    if (onFound) {
+                        mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_FIRST_MATCH, scanResult);
+                    } else {
+                        mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_MATCH_LOST, scanResult);
+                    }
+                }
+            });
         }
     }
 
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 7417208..5f046c5 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2616,6 +2616,7 @@
      *
      * @see #getSystemService
      */
+    @SystemApi
     public static final String BACKUP_SERVICE = "backup";
 
     /**
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index 176a81c..cc0d569 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -21,7 +21,6 @@
 import android.content.pm.IPackageInstallerSession;
 import android.content.pm.InstallSessionInfo;
 import android.content.pm.InstallSessionParams;
-import android.os.ParcelFileDescriptor;
 
 /** {@hide} */
 interface IPackageInstaller {
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index c461511..6a9d565 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -130,15 +130,6 @@
     public abstract String getId();
 
     /**
-     * <p>Set up a new output set of Surfaces for the camera device.</p>
-     *
-     * @deprecated Use {@link #createCaptureSession} instead
-     * @hide
-     */
-    @Deprecated
-    public abstract void configureOutputs(List<Surface> outputs) throws CameraAccessException;
-
-    /**
      * <p>Create a new camera capture session by providing the target output set of Surfaces to the
      * camera device.</p>
      *
@@ -276,68 +267,6 @@
             throws CameraAccessException;
 
     /**
-     * <p>Submit a request for an image to be captured by this CameraDevice.</p>
-     *
-     * @deprecated Use {@link CameraCaptureSession#capture} instead
-     * @hide
-     */
-    @Deprecated
-    public abstract int capture(CaptureRequest request, CaptureListener listener, Handler handler)
-            throws CameraAccessException;
-
-    /**
-     * Submit a list of requests to be captured in sequence as a burst.
-     *
-     * @deprecated Use {@link CameraCaptureSession#captureBurst} instead
-     * @hide
-     */
-    @Deprecated
-    public abstract int captureBurst(List<CaptureRequest> requests, CaptureListener listener,
-            Handler handler) throws CameraAccessException;
-
-    /**
-     * Request endlessly repeating capture of images by this CameraDevice.
-     *
-     * @deprecated Use {@link CameraCaptureSession#setRepeatingRequest} instead
-     * @hide
-     */
-    @Deprecated
-    public abstract int setRepeatingRequest(CaptureRequest request, CaptureListener listener,
-            Handler handler) throws CameraAccessException;
-
-    /**
-     * <p>Request endlessly repeating capture of a sequence of images by this
-     * CameraDevice.</p>
-     *
-     * @deprecated Use {@link CameraCaptureSession#setRepeatingBurst} instead
-     * @hide
-     */
-    @Deprecated
-    public abstract int setRepeatingBurst(List<CaptureRequest> requests, CaptureListener listener,
-            Handler handler) throws CameraAccessException;
-
-    /**
-     * <p>Cancel any ongoing repeating capture set by either
-     * {@link #setRepeatingRequest setRepeatingRequest} or
-     * {@link #setRepeatingBurst}.
-     *
-     * @deprecated Use {@link CameraCaptureSession#stopRepeating} instead
-     * @hide
-     */
-    @Deprecated
-    public abstract void stopRepeating() throws CameraAccessException;
-
-    /**
-     * Flush all captures currently pending and in-progress as fast as
-     * possible.
-     *
-     * @deprecated Use {@link CameraCaptureSession#abortCaptures} instead
-     * @hide
-     */
-    @Deprecated
-    public abstract void flush() throws CameraAccessException;
-
-    /**
      * Close the connection to this camera device as quickly as possible.
      *
      * <p>Immediately after this call, all calls to the camera device or active session interface
@@ -356,96 +285,6 @@
     public abstract void close();
 
     /**
-     * <p>A listener for tracking the progress of a {@link CaptureRequest}
-     * submitted to the camera device.</p>
-     *
-     * @deprecated Use {@link CameraCaptureSession.CaptureListener} instead
-     * @hide
-     */
-    @Deprecated
-    public static abstract class CaptureListener {
-
-        /**
-         * This constant is used to indicate that no images were captured for
-         * the request.
-         *
-         * @hide
-         */
-        public static final int NO_FRAMES_CAPTURED = -1;
-
-        /**
-         * This method is called when the camera device has started capturing
-         * the output image for the request, at the beginning of image exposure.
-         *
-         * @see android.media.MediaActionSound
-         */
-        public void onCaptureStarted(CameraDevice camera,
-                CaptureRequest request, long timestamp) {
-            // default empty implementation
-        }
-
-        /**
-         * This method is called when some results from an image capture are
-         * available.
-         *
-         * @hide
-         */
-        public void onCapturePartial(CameraDevice camera,
-                CaptureRequest request, CaptureResult result) {
-            // default empty implementation
-        }
-
-        /**
-         * This method is called when an image capture makes partial forward progress; some
-         * (but not all) results from an image capture are available.
-         *
-         */
-        public void onCaptureProgressed(CameraDevice camera,
-                CaptureRequest request, CaptureResult partialResult) {
-            // default empty implementation
-        }
-
-        /**
-         * This method is called when an image capture has fully completed and all the
-         * result metadata is available.
-         */
-        public void onCaptureCompleted(CameraDevice camera,
-                CaptureRequest request, TotalCaptureResult result) {
-            // default empty implementation
-        }
-
-        /**
-         * This method is called instead of {@link #onCaptureCompleted} when the
-         * camera device failed to produce a {@link CaptureResult} for the
-         * request.
-         */
-        public void onCaptureFailed(CameraDevice camera,
-                CaptureRequest request, CaptureFailure failure) {
-            // default empty implementation
-        }
-
-        /**
-         * This method is called independently of the others in CaptureListener,
-         * when a capture sequence finishes and all {@link CaptureResult}
-         * or {@link CaptureFailure} for it have been returned via this listener.
-         */
-        public void onCaptureSequenceCompleted(CameraDevice camera,
-                int sequenceId, long frameNumber) {
-            // default empty implementation
-        }
-
-        /**
-         * This method is called independently of the others in CaptureListener,
-         * when a capture sequence aborts before any {@link CaptureResult}
-         * or {@link CaptureFailure} for it have been returned via this listener.
-         */
-        public void onCaptureSequenceAborted(CameraDevice camera,
-                int sequenceId) {
-            // default empty implementation
-        }
-    }
-
-    /**
      * A listener for notifications about the state of a camera
      * device.
      *
@@ -542,40 +381,6 @@
         public abstract void onOpened(CameraDevice camera); // Must implement
 
         /**
-         * The method called when a camera device has no outputs configured.
-         *
-         * @deprecated Use {@link #onOpened} instead.
-         * @hide
-         */
-        @Deprecated
-        public void onUnconfigured(CameraDevice camera) {
-            // Default empty implementation
-        }
-
-        /**
-         * The method called when a camera device begins processing
-         * {@link CaptureRequest capture requests}.
-         *
-         * @deprecated Use {@link CameraCaptureSession.StateListener#onActive} instead.
-         * @hide
-         */
-        @Deprecated
-        public void onActive(CameraDevice camera) {
-            // Default empty implementation
-        }
-
-        /**
-         * The method called when a camera device is busy.
-         *
-         * @deprecated Use {@link CameraCaptureSession.StateListener#onConfigured} instead.
-         * @hide
-         */
-        @Deprecated
-        public void onBusy(CameraDevice camera) {
-            // Default empty implementation
-        }
-
-        /**
          * The method called when a camera device has been closed with
          * {@link CameraDevice#close}.
          *
@@ -591,18 +396,6 @@
         }
 
         /**
-         * The method called when a camera device has finished processing all
-         * submitted capture requests and has reached an idle state.
-         *
-         * @deprecated Use {@link CameraCaptureSession.StateListener#onReady} instead.
-         * @hide
-         */
-        @Deprecated
-        public void onIdle(CameraDevice camera) {
-            // Default empty implementation
-        }
-
-        /**
          * The method called when a camera device is no longer available for
          * use.
          *
diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
index f829f5e..a15028c 100644
--- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
@@ -103,7 +103,7 @@
          * Use the same handler as the device's StateListener for all the internal coming events
          *
          * This ensures total ordering between CameraDevice.StateListener and
-         * CameraDevice.CaptureListener events.
+         * CameraDeviceImpl.CaptureListener events.
          */
         mSequenceDrainer = new TaskDrainer<>(mDeviceHandler, new SequenceDrainListener(),
                 /*name*/"seq");
@@ -141,7 +141,7 @@
         checkNotClosed();
         checkLegalToCapture();
 
-        handler = checkHandler(handler);
+        handler = checkHandler(handler, listener);
 
         if (VERBOSE) {
             Log.v(TAG, "capture - request " + request + ", listener " + listener + " handler" +
@@ -164,7 +164,7 @@
         checkNotClosed();
         checkLegalToCapture();
 
-        handler = checkHandler(handler);
+        handler = checkHandler(handler, listener);
 
         if (VERBOSE) {
             CaptureRequest[] requestArray = requests.toArray(new CaptureRequest[0]);
@@ -186,7 +186,7 @@
         checkNotClosed();
         checkLegalToCapture();
 
-        handler = checkHandler(handler);
+        handler = checkHandler(handler, listener);
 
         if (VERBOSE) {
             Log.v(TAG, "setRepeatingRequest - request " + request + ", listener " + listener +
@@ -209,7 +209,7 @@
         checkNotClosed();
         checkLegalToCapture();
 
-        handler = checkHandler(handler);
+        handler = checkHandler(handler, listener);
 
         if (VERBOSE) {
             CaptureRequest[] requestArray = requests.toArray(new CaptureRequest[0]);
@@ -261,9 +261,12 @@
      * <p>The semantics are identical to {@link #close}, except that unconfiguring will be skipped.
      * <p>
      *
+     * <p>After this call completes, the session will not call any further methods on the camera
+     * device.</p>
+     *
      * @see CameraCaptureSession#close
      */
-    synchronized void replaceSessionClose(CameraCaptureSession other) {
+    synchronized void replaceSessionClose() {
         /*
          * In order for creating new sessions to be fast, the new session should be created
          * before the old session is closed.
@@ -278,13 +281,17 @@
 
         if (VERBOSE) Log.v(TAG, "replaceSessionClose");
 
-        // #close was already called explicitly, keep going the slow route
-        if (mClosed) {
-            if (VERBOSE) Log.v(TAG, "replaceSessionClose - close was already called");
-            return;
-        }
-
+        // Set up fast shutdown. Possible alternative paths:
+        // - This session is active, so close() below starts the shutdown drain
+        // - This session is mid-shutdown drain, and hasn't yet reached the idle drain listener.
+        // - This session is already closed and has executed the idle drain listener, and
+        //   configureOutputs(null) has already been called.
+        //
+        // Do not call configureOutputs(null) going forward, since it would race with the
+        // configuration for the new session. If it was already called, then we don't care, since it
+        // won't get called again.
         mSkipUnconfigure = true;
+
         close();
     }
 
@@ -347,7 +354,7 @@
 
     /**
      * Forward callbacks from
-     * CameraDevice.CaptureListener to the CameraCaptureSession.CaptureListener.
+     * CameraDeviceImpl.CaptureListener to the CameraCaptureSession.CaptureListener.
      *
      * <p>In particular, all calls are automatically split to go both to our own
      * internal listener, and to the user-specified listener (by transparently posting
@@ -356,9 +363,9 @@
      * <p>When a capture sequence finishes, update the pending checked sequences set.</p>
      */
     @SuppressWarnings("deprecation")
-    private CameraDevice.CaptureListener createCaptureListenerProxy(
+    private CameraDeviceImpl.CaptureListener createCaptureListenerProxy(
             Handler handler, CaptureListener listener) {
-        CameraDevice.CaptureListener localListener = new CameraDevice.CaptureListener() {
+        CameraDeviceImpl.CaptureListener localListener = new CameraDeviceImpl.CaptureListener() {
             @Override
             public void onCaptureSequenceCompleted(CameraDevice camera,
                     int sequenceId, long frameNumber) {
@@ -379,27 +386,30 @@
          * - then forward the call to a handler
          * - then finally invoke the destination method on the session listener object
          */
-        Dispatchable<CaptureListener> userListenerSink;
-        if (listener == null) { // OK: API allows the user to not specify a listener
-            userListenerSink = new NullDispatcher<>();
-        } else {
-            userListenerSink = new InvokeDispatcher<>(listener);
+        if (listener == null) {
+            // OK: API allows the user to not specify a listener, and the handler may
+            // also be null in that case. Collapse whole dispatch chain to only call the local
+            // listener
+            return localListener;
         }
 
-        InvokeDispatcher<CameraDevice.CaptureListener> localSink =
+        InvokeDispatcher<CameraDeviceImpl.CaptureListener> localSink =
                 new InvokeDispatcher<>(localListener);
+
+        InvokeDispatcher<CaptureListener> userListenerSink =
+                new InvokeDispatcher<>(listener);
         HandlerDispatcher<CaptureListener> handlerPassthrough =
                 new HandlerDispatcher<>(userListenerSink, handler);
-        DuckTypingDispatcher<CameraDevice.CaptureListener, CaptureListener> duckToSession
+        DuckTypingDispatcher<CameraDeviceImpl.CaptureListener, CaptureListener> duckToSession
                 = new DuckTypingDispatcher<>(handlerPassthrough, CaptureListener.class);
-        ArgumentReplacingDispatcher<CameraDevice.CaptureListener, CameraCaptureSessionImpl>
-            replaceDeviceWithSession = new ArgumentReplacingDispatcher<>(duckToSession,
-                    /*argumentIndex*/0, this);
+        ArgumentReplacingDispatcher<CameraDeviceImpl.CaptureListener, CameraCaptureSessionImpl>
+                replaceDeviceWithSession = new ArgumentReplacingDispatcher<>(duckToSession,
+                        /*argumentIndex*/0, this);
 
-        BroadcastDispatcher<CameraDevice.CaptureListener> broadcaster =
-                new BroadcastDispatcher<CameraDevice.CaptureListener>(
-                        replaceDeviceWithSession,
-                        localSink);
+        BroadcastDispatcher<CameraDeviceImpl.CaptureListener> broadcaster =
+                new BroadcastDispatcher<CameraDeviceImpl.CaptureListener>(
+                    replaceDeviceWithSession,
+                    localSink);
 
         return new ListenerProxies.DeviceCaptureListenerProxy(broadcaster);
     }
@@ -415,10 +425,10 @@
      * </ul>
      * </p>
      * */
-    CameraDevice.StateListener getDeviceStateListener() {
+    CameraDeviceImpl.StateListenerKK getDeviceStateListener() {
         final CameraCaptureSession session = this;
 
-        return new CameraDevice.StateListener() {
+        return new CameraDeviceImpl.StateListenerKK() {
             private boolean mBusy = false;
             private boolean mActive = false;
 
@@ -596,6 +606,8 @@
                  *
                  * This operation is idempotent; a session will not be closed twice.
                  */
+                if (VERBOSE) Log.v(TAG, "Session drain complete, skip unconfigure: " +
+                        mSkipUnconfigure);
 
                 // Fast path: A new capture session has replaced this one; don't unconfigure.
                 if (mSkipUnconfigure) {
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 18b1202..71eb0e9 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -21,8 +21,10 @@
 import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.CameraCaptureSession;
 import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.CaptureFailure;
 import android.hardware.camera2.ICameraDeviceCallbacks;
 import android.hardware.camera2.ICameraDeviceUser;
 import android.hardware.camera2.TotalCaptureResult;
@@ -47,7 +49,7 @@
 /**
  * HAL2.1+ implementation of CameraDevice. Use CameraManager#open to instantiate
  */
-public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice {
+public class CameraDeviceImpl extends CameraDevice {
 
     private final String TAG;
     private final boolean DEBUG;
@@ -62,7 +64,7 @@
     private final CameraDeviceCallbacks mCallbacks = new CameraDeviceCallbacks();
 
     private final StateListener mDeviceListener;
-    private volatile StateListener mSessionStateListener;
+    private volatile StateListenerKK mSessionStateListener;
     private final Handler mDeviceHandler;
 
     private volatile boolean mClosing = false;
@@ -103,7 +105,7 @@
     private final Runnable mCallOnOpened = new Runnable() {
         @Override
         public void run() {
-            StateListener sessionListener = null;
+            StateListenerKK sessionListener = null;
             synchronized(mInterfaceLock) {
                 if (mRemoteDevice == null) return; // Camera already closed
 
@@ -119,7 +121,7 @@
     private final Runnable mCallOnUnconfigured = new Runnable() {
         @Override
         public void run() {
-            StateListener sessionListener = null;
+            StateListenerKK sessionListener = null;
             synchronized(mInterfaceLock) {
                 if (mRemoteDevice == null) return; // Camera already closed
 
@@ -128,14 +130,13 @@
             if (sessionListener != null) {
                 sessionListener.onUnconfigured(CameraDeviceImpl.this);
             }
-            mDeviceListener.onUnconfigured(CameraDeviceImpl.this);
         }
     };
 
     private final Runnable mCallOnActive = new Runnable() {
         @Override
         public void run() {
-            StateListener sessionListener = null;
+            StateListenerKK sessionListener = null;
             synchronized(mInterfaceLock) {
                 if (mRemoteDevice == null) return; // Camera already closed
 
@@ -144,14 +145,13 @@
             if (sessionListener != null) {
                 sessionListener.onActive(CameraDeviceImpl.this);
             }
-            mDeviceListener.onActive(CameraDeviceImpl.this);
         }
     };
 
     private final Runnable mCallOnBusy = new Runnable() {
         @Override
         public void run() {
-            StateListener sessionListener = null;
+            StateListenerKK sessionListener = null;
             synchronized(mInterfaceLock) {
                 if (mRemoteDevice == null) return; // Camera already closed
 
@@ -160,7 +160,6 @@
             if (sessionListener != null) {
                 sessionListener.onBusy(CameraDeviceImpl.this);
             }
-            mDeviceListener.onBusy(CameraDeviceImpl.this);
         }
     };
 
@@ -172,7 +171,7 @@
             if (mClosedOnce) {
                 throw new AssertionError("Don't post #onClosed more than once");
             }
-            StateListener sessionListener = null;
+            StateListenerKK sessionListener = null;
             synchronized(mInterfaceLock) {
                 sessionListener = mSessionStateListener;
             }
@@ -187,7 +186,7 @@
     private final Runnable mCallOnIdle = new Runnable() {
         @Override
         public void run() {
-            StateListener sessionListener = null;
+            StateListenerKK sessionListener = null;
             synchronized(mInterfaceLock) {
                 if (mRemoteDevice == null) return; // Camera already closed
 
@@ -196,14 +195,13 @@
             if (sessionListener != null) {
                 sessionListener.onIdle(CameraDeviceImpl.this);
             }
-            mDeviceListener.onIdle(CameraDeviceImpl.this);
         }
     };
 
     private final Runnable mCallOnDisconnected = new Runnable() {
         @Override
         public void run() {
-            StateListener sessionListener = null;
+            StateListenerKK sessionListener = null;
             synchronized(mInterfaceLock) {
                 if (mRemoteDevice == null) return; // Camera already closed
 
@@ -313,7 +311,6 @@
         return mCameraId;
     }
 
-    @Override
     public void configureOutputs(List<Surface> outputs) throws CameraAccessException {
         // Treat a null input the same an empty list
         if (outputs == null) {
@@ -390,7 +387,11 @@
 
             checkIfCameraClosedOrInError();
 
-            // TODO: we must be in UNCONFIGURED mode to begin with, or using another session
+            // Notify current session that it's going away, before starting camera operations
+            // After this call completes, the session is not allowed to call into CameraDeviceImpl
+            if (mCurrentSession != null) {
+                mCurrentSession.replaceSessionClose();
+            }
 
             // TODO: dont block for this
             boolean configureSuccess = true;
@@ -410,10 +411,6 @@
                     new CameraCaptureSessionImpl(outputs, listener, handler, this, mDeviceHandler,
                             configureSuccess);
 
-            if (mCurrentSession != null) {
-                mCurrentSession.replaceSessionClose(newSession);
-            }
-
             // TODO: wait until current session closes, then create the new session
             mCurrentSession = newSession;
 
@@ -425,6 +422,15 @@
         }
     }
 
+    /**
+     * For use by backwards-compatibility code only.
+     */
+    public void setSessionListener(StateListenerKK sessionListener) {
+        synchronized(mInterfaceLock) {
+            mSessionStateListener = sessionListener;
+        }
+    }
+
     @Override
     public CaptureRequest.Builder createCaptureRequest(int templateType)
             throws CameraAccessException {
@@ -449,7 +455,6 @@
         }
     }
 
-    @Override
     public int capture(CaptureRequest request, CaptureListener listener, Handler handler)
             throws CameraAccessException {
         if (DEBUG) {
@@ -460,7 +465,6 @@
         return submitCaptureRequest(requestList, listener, handler, /*streaming*/false);
     }
 
-    @Override
     public int captureBurst(List<CaptureRequest> requests, CaptureListener listener,
             Handler handler) throws CameraAccessException {
         if (requests == null || requests.isEmpty()) {
@@ -543,9 +547,7 @@
 
         // Need a valid handler, or current thread needs to have a looper, if
         // listener is valid
-        if (listener != null) {
-            handler = checkHandler(handler);
-        }
+        handler = checkHandler(handler, listener);
 
         // Make sure that there all requests have at least 1 surface; all surfaces are non-null
         for (CaptureRequest request : requestList) {
@@ -613,7 +615,6 @@
         }
     }
 
-    @Override
     public int setRepeatingRequest(CaptureRequest request, CaptureListener listener,
             Handler handler) throws CameraAccessException {
         List<CaptureRequest> requestList = new ArrayList<CaptureRequest>();
@@ -621,7 +622,6 @@
         return submitCaptureRequest(requestList, listener, handler, /*streaming*/true);
     }
 
-    @Override
     public int setRepeatingBurst(List<CaptureRequest> requests, CaptureListener listener,
             Handler handler) throws CameraAccessException {
         if (requests == null || requests.isEmpty()) {
@@ -630,7 +630,6 @@
         return submitCaptureRequest(requests, listener, handler, /*streaming*/true);
     }
 
-    @Override
     public void stopRepeating() throws CameraAccessException {
 
         synchronized(mInterfaceLock) {
@@ -681,7 +680,6 @@
         }
     }
 
-    @Override
     public void flush() throws CameraAccessException {
         synchronized(mInterfaceLock) {
             checkIfCameraClosedOrInError();
@@ -739,6 +737,133 @@
         }
     }
 
+    /**
+     * <p>A listener for tracking the progress of a {@link CaptureRequest}
+     * submitted to the camera device.</p>
+     *
+     */
+    public static abstract class CaptureListener {
+
+        /**
+         * This constant is used to indicate that no images were captured for
+         * the request.
+         *
+         * @hide
+         */
+        public static final int NO_FRAMES_CAPTURED = -1;
+
+        /**
+         * This method is called when the camera device has started capturing
+         * the output image for the request, at the beginning of image exposure.
+         *
+         * @see android.media.MediaActionSound
+         */
+        public void onCaptureStarted(CameraDevice camera,
+                CaptureRequest request, long timestamp) {
+            // default empty implementation
+        }
+
+        /**
+         * This method is called when some results from an image capture are
+         * available.
+         *
+         * @hide
+         */
+        public void onCapturePartial(CameraDevice camera,
+                CaptureRequest request, CaptureResult result) {
+            // default empty implementation
+        }
+
+        /**
+         * This method is called when an image capture makes partial forward progress; some
+         * (but not all) results from an image capture are available.
+         *
+         */
+        public void onCaptureProgressed(CameraDevice camera,
+                CaptureRequest request, CaptureResult partialResult) {
+            // default empty implementation
+        }
+
+        /**
+         * This method is called when an image capture has fully completed and all the
+         * result metadata is available.
+         */
+        public void onCaptureCompleted(CameraDevice camera,
+                CaptureRequest request, TotalCaptureResult result) {
+            // default empty implementation
+        }
+
+        /**
+         * This method is called instead of {@link #onCaptureCompleted} when the
+         * camera device failed to produce a {@link CaptureResult} for the
+         * request.
+         */
+        public void onCaptureFailed(CameraDevice camera,
+                CaptureRequest request, CaptureFailure failure) {
+            // default empty implementation
+        }
+
+        /**
+         * This method is called independently of the others in CaptureListener,
+         * when a capture sequence finishes and all {@link CaptureResult}
+         * or {@link CaptureFailure} for it have been returned via this listener.
+         */
+        public void onCaptureSequenceCompleted(CameraDevice camera,
+                int sequenceId, long frameNumber) {
+            // default empty implementation
+        }
+
+        /**
+         * This method is called independently of the others in CaptureListener,
+         * when a capture sequence aborts before any {@link CaptureResult}
+         * or {@link CaptureFailure} for it have been returned via this listener.
+         */
+        public void onCaptureSequenceAborted(CameraDevice camera,
+                int sequenceId) {
+            // default empty implementation
+        }
+    }
+
+    /**
+     * A listener for notifications about the state of a camera device, adding in the callbacks that
+     * were part of the earlier KK API design, but now only used internally.
+     */
+    public static abstract class StateListenerKK extends StateListener {
+        /**
+         * The method called when a camera device has no outputs configured.
+         *
+         */
+        public void onUnconfigured(CameraDevice camera) {
+            // Default empty implementation
+        }
+
+        /**
+         * The method called when a camera device begins processing
+         * {@link CaptureRequest capture requests}.
+         *
+         */
+        public void onActive(CameraDevice camera) {
+            // Default empty implementation
+        }
+
+        /**
+         * The method called when a camera device is busy.
+         *
+         */
+        public void onBusy(CameraDevice camera) {
+            // Default empty implementation
+        }
+
+        /**
+         * The method called when a camera device has finished processing all
+         * submitted capture requests and has reached an idle state.
+         *
+         */
+        public void onIdle(CameraDevice camera) {
+            // Default empty implementation
+        }
+    }
+
     static class CaptureListenerHolder {
 
         private final boolean mRepeating;
@@ -1155,6 +1280,18 @@
         return handler;
     }
 
+    /**
+     * Default handler management, conditional on there being a listener.
+     *
+     * <p>If the listener isn't null, check the handler, otherwise pass it through.</p>
+     */
+    static <T> Handler checkHandler(Handler handler, T listener) {
+        if (listener != null) {
+            return checkHandler(handler);
+        }
+        return handler;
+    }
+
     private void checkIfCameraClosedOrInError() throws CameraAccessException {
         if (mInError) {
             throw new CameraAccessException(CameraAccessException.CAMERA_ERROR,
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index dc71a06..febb015 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -67,6 +67,7 @@
 import java.nio.ByteOrder;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 
 /**
  * Implementation of camera metadata marshal/unmarshal across Binder to
@@ -227,6 +228,7 @@
 
     private static final String CELLID_PROCESS = "CELLID";
     private static final String GPS_PROCESS = "GPS";
+    private static final int FACE_LANDMARK_SIZE = 6;
 
     private static String translateLocationProviderToProcess(final String provider) {
         if (provider == null) {
@@ -347,7 +349,7 @@
         // Check if key has been overridden to use a wrapper class on the java side.
         GetCommand g = sGetCommandMap.get(key);
         if (g != null) {
-            return (T) g.getValue(this, key);
+            return g.getValue(this, key);
         }
         return getBase(key);
     }
@@ -587,9 +589,71 @@
         return availableFormats;
     }
 
-    private Face[] getFaces() {
-        final int FACE_LANDMARK_SIZE = 6;
+    private boolean setFaces(Face[] faces) {
+        if (faces == null) {
+            return false;
+        }
 
+        int numFaces = faces.length;
+
+        // Detect if all faces are SIMPLE or not; count # of valid faces
+        boolean fullMode = true;
+        for (Face face : faces) {
+            if (face == null) {
+                numFaces--;
+                Log.w(TAG, "setFaces - null face detected, skipping");
+                continue;
+            }
+
+            if (face.getId() == Face.ID_UNSUPPORTED) {
+                fullMode = false;
+            }
+        }
+
+        Rect[] faceRectangles = new Rect[numFaces];
+        byte[] faceScores = new byte[numFaces];
+        int[] faceIds = null;
+        int[] faceLandmarks = null;
+
+        if (fullMode) {
+            faceIds = new int[numFaces];
+            faceLandmarks = new int[numFaces * FACE_LANDMARK_SIZE];
+        }
+
+        int i = 0;
+        for (Face face : faces) {
+            if (face == null) {
+                continue;
+            }
+
+            faceRectangles[i] = face.getBounds();
+            faceScores[i] = (byte)face.getScore();
+
+            if (fullMode) {
+                faceIds[i] = face.getId();
+
+                int j = 0;
+
+                faceLandmarks[i * FACE_LANDMARK_SIZE + j++] = face.getLeftEyePosition().x;
+                faceLandmarks[i * FACE_LANDMARK_SIZE + j++] = face.getLeftEyePosition().y;
+                faceLandmarks[i * FACE_LANDMARK_SIZE + j++] = face.getRightEyePosition().x;
+                faceLandmarks[i * FACE_LANDMARK_SIZE + j++] = face.getRightEyePosition().y;
+                faceLandmarks[i * FACE_LANDMARK_SIZE + j++] = face.getMouthPosition().x;
+                faceLandmarks[i * FACE_LANDMARK_SIZE + j++] = face.getMouthPosition().y;
+            }
+
+            i++;
+        }
+
+        set(CaptureResult.STATISTICS_FACE_RECTANGLES, faceRectangles);
+        set(CaptureResult.STATISTICS_FACE_IDS, faceIds);
+        set(CaptureResult.STATISTICS_FACE_LANDMARKS, faceLandmarks);
+        set(CaptureResult.STATISTICS_FACE_SCORES, faceScores);
+
+        return true;
+    }
+
+    private Face[] getFaces() {
         Integer faceDetectMode = get(CaptureResult.STATISTICS_FACE_DETECT_MODE);
         if (faceDetectMode == null) {
             Log.w(TAG, "Face detect mode metadata is null, assuming the mode is SIMPLE");
@@ -653,9 +717,12 @@
                 if (faceScores[i] <= Face.SCORE_MAX &&
                         faceScores[i] >= Face.SCORE_MIN &&
                         faceIds[i] >= 0) {
-                    Point leftEye = new Point(faceLandmarks[i*6], faceLandmarks[i*6+1]);
-                    Point rightEye = new Point(faceLandmarks[i*6+2], faceLandmarks[i*6+3]);
-                    Point mouth = new Point(faceLandmarks[i*6+4], faceLandmarks[i*6+5]);
+                    Point leftEye = new Point(faceLandmarks[i*FACE_LANDMARK_SIZE],
+                            faceLandmarks[i*FACE_LANDMARK_SIZE+1]);
+                    Point rightEye = new Point(faceLandmarks[i*FACE_LANDMARK_SIZE+2],
+                            faceLandmarks[i*FACE_LANDMARK_SIZE+3]);
+                    Point mouth = new Point(faceLandmarks[i*FACE_LANDMARK_SIZE+4],
+                            faceLandmarks[i*FACE_LANDMARK_SIZE+5]);
                     Face face = new Face(faceRectangles[i], faceScores[i], faceIds[i],
                             leftEye, rightEye, mouth);
                     faceList.add(face);
@@ -865,6 +932,13 @@
                 metadata.setFaceRectangles((Rect[]) value);
             }
         });
+        sSetCommandMap.put(CaptureResult.STATISTICS_FACES.getNativeKey(),
+                new SetCommand() {
+            @Override
+            public <T> void setValue(CameraMetadataNative metadata, T value) {
+                metadata.setFaces((Face[])value);
+            }
+        });
         sSetCommandMap.put(CaptureRequest.TONEMAP_CURVE.getNativeKey(), new SetCommand() {
             @Override
             public <T> void setValue(CameraMetadataNative metadata, T value) {
diff --git a/core/java/android/hardware/camera2/impl/ListenerProxies.java b/core/java/android/hardware/camera2/impl/ListenerProxies.java
index ab9a4d5..f44f9ad 100644
--- a/core/java/android/hardware/camera2/impl/ListenerProxies.java
+++ b/core/java/android/hardware/camera2/impl/ListenerProxies.java
@@ -36,13 +36,13 @@
 
     // TODO: replace with codegen
 
-    public static class DeviceStateListenerProxy extends CameraDevice.StateListener {
-        private final MethodNameInvoker<CameraDevice.StateListener> mProxy;
+    public static class DeviceStateListenerProxy extends CameraDeviceImpl.StateListenerKK {
+        private final MethodNameInvoker<CameraDeviceImpl.StateListenerKK> mProxy;
 
         public DeviceStateListenerProxy(
-                Dispatchable<CameraDevice.StateListener> dispatchTarget) {
+                Dispatchable<CameraDeviceImpl.StateListenerKK> dispatchTarget) {
             dispatchTarget = checkNotNull(dispatchTarget, "dispatchTarget must not be null");
-            mProxy = new MethodNameInvoker<>(dispatchTarget, CameraDevice.StateListener.class);
+            mProxy = new MethodNameInvoker<>(dispatchTarget, CameraDeviceImpl.StateListenerKK.class);
         }
 
         @Override
@@ -87,13 +87,13 @@
     }
 
     @SuppressWarnings("deprecation")
-    public static class DeviceCaptureListenerProxy extends CameraDevice.CaptureListener {
-        private final MethodNameInvoker<CameraDevice.CaptureListener> mProxy;
+    public static class DeviceCaptureListenerProxy extends CameraDeviceImpl.CaptureListener {
+        private final MethodNameInvoker<CameraDeviceImpl.CaptureListener> mProxy;
 
         public DeviceCaptureListenerProxy(
-                Dispatchable<CameraDevice.CaptureListener> dispatchTarget) {
+                Dispatchable<CameraDeviceImpl.CaptureListener> dispatchTarget) {
             dispatchTarget = checkNotNull(dispatchTarget, "dispatchTarget must not be null");
-            mProxy = new MethodNameInvoker<>(dispatchTarget, CameraDevice.CaptureListener.class);
+            mProxy = new MethodNameInvoker<>(dispatchTarget, CameraDeviceImpl.CaptureListener.class);
         }
 
         @Override
diff --git a/core/java/android/hardware/camera2/legacy/LegacyFaceDetectMapper.java b/core/java/android/hardware/camera2/legacy/LegacyFaceDetectMapper.java
new file mode 100644
index 0000000..1470b70
--- /dev/null
+++ b/core/java/android/hardware/camera2/legacy/LegacyFaceDetectMapper.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2.legacy;
+
+import android.graphics.Rect;
+import android.hardware.Camera;
+import android.hardware.Camera.FaceDetectionListener;
+import android.hardware.camera2.impl.CameraMetadataNative;
+import android.hardware.camera2.legacy.ParameterUtils.ZoomData;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.params.Face;
+import android.hardware.camera2.utils.ListUtils;
+import android.hardware.camera2.utils.ParamsUtils;
+import android.util.Log;
+import android.util.Size;
+
+import com.android.internal.util.ArrayUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static android.hardware.camera2.CaptureRequest.*;
+import static com.android.internal.util.Preconditions.*;
+
+/**
+ * Map legacy face detect callbacks into face detection results.
+ */
+@SuppressWarnings("deprecation")
+public class LegacyFaceDetectMapper {
+    private static String TAG = "LegacyFaceDetectMapper";
+    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+    private final Camera mCamera;
+    private final boolean mFaceDetectSupported;
+    private boolean mFaceDetectEnabled = false;
+
+    private final Object mLock = new Object();
+    private Camera.Face[] mFaces;
+    private Camera.Face[] mFacesPrev;
+    /**
+     * Instantiate a new face detect mapper.
+     *
+     * @param camera a non-{@code null} camera1 device
+     * @param characteristics a  non-{@code null} camera characteristics for that camera1
+     *
+     * @throws NullPointerException if any of the args were {@code null}
+     */
+    public LegacyFaceDetectMapper(Camera camera, CameraCharacteristics characteristics) {
+        mCamera = checkNotNull(camera, "camera must not be null");
+        checkNotNull(characteristics, "characteristics must not be null");
+
+        mFaceDetectSupported = ArrayUtils.contains(
+                characteristics.get(
+                        CameraCharacteristics.STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES),
+                STATISTICS_FACE_DETECT_MODE_SIMPLE);
+
+        if (!mFaceDetectSupported) {
+            return;
+        }
+
+       mCamera.setFaceDetectionListener(new FaceDetectionListener() {
+
+        @Override
+        public void onFaceDetection(Camera.Face[] faces, Camera camera) {
+            int lengthFaces = faces == null ? 0 : faces.length;
+            synchronized (mLock) {
+                if (mFaceDetectEnabled) {
+                    mFaces = faces;
+                } else if (lengthFaces > 0) {
+                    // stopFaceDetectMode could race against the requests, print a debug log
+                    Log.d(TAG,
+                            "onFaceDetection - Ignored some incoming faces since" +
+                            "face detection was disabled");
+                }
+            }
+
+            if (VERBOSE) {
+                Log.v(TAG, "onFaceDetection - read " + lengthFaces + " faces");
+            }
+        }
+       });
+    }
+
+    /**
+     * Process the face detect mode from the capture request into an api1 face detect toggle.
+     *
+     * <p>This method should be called after the parameters are {@link LegacyRequestMapper mapped}
+     * with the request.</p>
+     *
+     * <p>Callbacks are processed in the background, and the next call to {@link #mapResultTriggers}
+     * will have the latest faces detected as reflected by the camera1 callbacks.</p>
+     *
+     * <p>None of the arguments will be mutated.</p>
+     *
+     * @param captureRequest a non-{@code null} request
+     * @param parameters a non-{@code null} parameters corresponding to this request (read-only)
+     */
+    public void processFaceDetectMode(CaptureRequest captureRequest,
+            Camera.Parameters parameters) {
+        checkNotNull(captureRequest, "captureRequest must not be null");
+
+        /*
+         * statistics.faceDetectMode
+         */
+        int fdMode = ParamsUtils.getOrDefault(captureRequest, STATISTICS_FACE_DETECT_MODE,
+                STATISTICS_FACE_DETECT_MODE_OFF);
+
+        if (fdMode != STATISTICS_FACE_DETECT_MODE_OFF && !mFaceDetectSupported) {
+            Log.w(TAG,
+                    "processFaceDetectMode - Ignoring statistics.faceDetectMode; " +
+                    "face detection is not available");
+            return;
+        }
+
+        // Print some warnings out in case the values were wrong
+        switch (fdMode) {
+            case STATISTICS_FACE_DETECT_MODE_OFF:
+            case STATISTICS_FACE_DETECT_MODE_SIMPLE:
+                break;
+            case STATISTICS_FACE_DETECT_MODE_FULL:
+                Log.w(TAG,
+                        "processFaceDetectMode - statistics.faceDetectMode == FULL unsupported, " +
+                        "downgrading to SIMPLE");
+                break;
+            default:
+                Log.w(TAG, "processFaceDetectMode - ignoring unknown statistics.faceDetectMode = "
+                        + fdMode);
+                return;
+        }
+
+        boolean enableFaceDetect = fdMode != STATISTICS_FACE_DETECT_MODE_OFF;
+        synchronized (mLock) {
+            // Enable/disable face detection if it's changed since last time
+            if (enableFaceDetect != mFaceDetectEnabled) {
+                if (enableFaceDetect) {
+                    mCamera.startFaceDetection();
+
+                    if (VERBOSE) {
+                        Log.v(TAG, "processFaceDetectMode - start face detection");
+                    }
+                } else {
+                    mCamera.stopFaceDetection();
+
+                    if (VERBOSE) {
+                        Log.v(TAG, "processFaceDetectMode - stop face detection");
+                    }
+
+                    mFaces = null;
+                }
+
+                mFaceDetectEnabled = enableFaceDetect;
+            }
+        }
+    }
+
+    /**
+     * Update the {@code result} camera metadata map with the new value for the
+     * {@code statistics.faces} and {@code statistics.faceDetectMode}.
+     *
+     * <p>Face detect callbacks are processed in the background, and each call to
+     * {@link #mapResultFaces} will have the latest faces as reflected by the camera1 callbacks.</p>
+     *
+     * @param result a non-{@code null} result
+     * @param legacyRequest a non-{@code null} request (read-only)
+     */
+    public void mapResultFaces(CameraMetadataNative result, LegacyRequest legacyRequest) {
+        checkNotNull(result, "result must not be null");
+        checkNotNull(legacyRequest, "legacyRequest must not be null");
+
+        Camera.Face[] faces, previousFaces;
+        int fdMode;
+        synchronized (mLock) {
+            fdMode = mFaceDetectEnabled ?
+                            STATISTICS_FACE_DETECT_MODE_SIMPLE : STATISTICS_FACE_DETECT_MODE_OFF;
+
+            if (mFaceDetectEnabled) {
+                faces = mFaces;
+            } else {
+                faces = null;
+            }
+
+            previousFaces = mFacesPrev;
+            mFacesPrev = faces;
+        }
+
+        CameraCharacteristics characteristics = legacyRequest.characteristics;
+        CaptureRequest request = legacyRequest.captureRequest;
+        Size previewSize = legacyRequest.previewSize;
+        Camera.Parameters params = legacyRequest.parameters;
+
+        Rect activeArray = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
+        ZoomData zoomData = ParameterUtils.convertScalerCropRegion(activeArray,
+                request.get(CaptureRequest.SCALER_CROP_REGION), previewSize, params);
+
+        List<Face> convertedFaces = new ArrayList<>();
+        if (faces != null) {
+            for (Camera.Face face : faces) {
+                if (face != null) {
+                    convertedFaces.add(
+                            ParameterUtils.convertFaceFromLegacy(face, activeArray, zoomData));
+                } else {
+                    Log.w(TAG, "mapResultFaces - read NULL face from camera1 device");
+                }
+            }
+        }
+
+        if (VERBOSE && previousFaces != faces) { // Log only in verbose and IF the faces changed
+            Log.v(TAG, "mapResultFaces - changed to " + ListUtils.listToString(convertedFaces));
+        }
+
+        result.set(CaptureResult.STATISTICS_FACES, convertedFaces.toArray(new Face[0]));
+        result.set(CaptureResult.STATISTICS_FACE_DETECT_MODE, fdMode);
+    }
+}
diff --git a/core/java/android/hardware/camera2/legacy/LegacyFocusStateMapper.java b/core/java/android/hardware/camera2/legacy/LegacyFocusStateMapper.java
index e576b43..d0a3a3f 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyFocusStateMapper.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyFocusStateMapper.java
@@ -247,7 +247,8 @@
                 // No action necessary. The callbacks will handle transitions.
                 break;
             default:
-                Log.w(TAG, "mapTriggers - ignoring unknown control.afTrigger = " + afTrigger);
+                Log.w(TAG, "processRequestTriggers - ignoring unknown control.afTrigger = "
+                        + afTrigger);
         }
     }
 
diff --git a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
index 711edf4..b05508b 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
@@ -204,6 +204,11 @@
         mapSensor(m, p);
 
         /*
+         * statistics.*
+         */
+        mapStatistics(m, p);
+
+        /*
          * sync.*
          */
         mapSync(m, p);
@@ -487,6 +492,18 @@
 
     private static void mapControlOther(CameraMetadataNative m, Camera.Parameters p) {
         /*
+         * android.control.availableVideoStabilizationModes
+         */
+        {
+            int stabModes[] = p.isVideoStabilizationSupported() ?
+                    new int[] { CONTROL_VIDEO_STABILIZATION_MODE_OFF,
+                                CONTROL_VIDEO_STABILIZATION_MODE_ON } :
+                    new int[] { CONTROL_VIDEO_STABILIZATION_MODE_OFF };
+
+            m.set(CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES, stabModes);
+        }
+
+        /*
          * android.control.maxRegions
          */
         final int AE = 0, AWB = 1, AF = 2;
@@ -742,6 +759,31 @@
         m.set(SENSOR_INFO_PIXEL_ARRAY_SIZE, largestJpegSize);
     }
 
+    private static void mapStatistics(CameraMetadataNative m, Parameters p) {
+        /*
+         * statistics.info.availableFaceDetectModes
+         */
+        int[] fdModes;
+
+        if (p.getMaxNumDetectedFaces() > 0) {
+            fdModes = new int[] {
+                STATISTICS_FACE_DETECT_MODE_OFF,
+                STATISTICS_FACE_DETECT_MODE_SIMPLE
+                // FULL is never-listed, since we have no way to query it statically
+            };
+        } else {
+            fdModes = new int[] {
+                STATISTICS_FACE_DETECT_MODE_OFF
+            };
+        }
+        m.set(STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES, fdModes);
+
+        /*
+         * statistics.info.maxFaceCount
+         */
+        m.set(STATISTICS_INFO_MAX_FACE_COUNT, p.getMaxNumDetectedFaces());
+    }
+
     private static void mapSync(CameraMetadataNative m, Parameters p) {
         /*
          * sync.maxLatency
diff --git a/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java b/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java
index a6fe035..20f3fd2 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java
@@ -150,10 +150,8 @@
             if (supported) {
                 params.setPreviewFpsRange(legacyFps[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
                         legacyFps[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
-                params.setRecordingHint(false);
             } else {
                 Log.w(TAG, "Unsupported FPS range set [" + legacyFps[0] + "," + legacyFps[1] + "]");
-                params.setRecordingHint(true);
             }
         }
 
@@ -248,6 +246,18 @@
          // TODO: Don't add control.awbLock to availableRequestKeys if it's not supported
         }
 
+        // control.videoStabilizationMode
+        {
+            Integer stabMode = getIfSupported(request, CONTROL_VIDEO_STABILIZATION_MODE,
+                    /*defaultValue*/CONTROL_VIDEO_STABILIZATION_MODE_OFF,
+                    params.isVideoStabilizationSupported(),
+                    /*allowedValue*/CONTROL_VIDEO_STABILIZATION_MODE_OFF);
+
+            if (stabMode != null) {
+                params.setVideoStabilization(stabMode == CONTROL_VIDEO_STABILIZATION_MODE_ON);
+            }
+        }
+
         // lens.focusDistance
         {
             boolean infinityFocusSupported =
diff --git a/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java b/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java
index 9eff943..a2487f4 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java
@@ -35,6 +35,9 @@
 import java.util.List;
 
 import static com.android.internal.util.Preconditions.*;
+import static android.hardware.camera2.CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_OFF;
+import static android.hardware.camera2.CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_ON;
+import static android.hardware.camera2.CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE;
 import static android.hardware.camera2.CaptureResult.*;
 
 /**
@@ -142,7 +145,6 @@
          */
         mapAwb(result, /*out*/params);
 
-
         /*
          * control.mode
          */
@@ -171,7 +173,6 @@
             }
         }
 
-
         /*
          * control.effectMode
          */
@@ -187,6 +188,15 @@
             }
         }
 
+        // control.videoStabilizationMode
+        {
+            int stabMode =
+                    (params.isVideoStabilizationSupported() && params.getVideoStabilization()) ?
+                        CONTROL_VIDEO_STABILIZATION_MODE_ON :
+                        CONTROL_VIDEO_STABILIZATION_MODE_OFF;
+            result.set(CONTROL_VIDEO_STABILIZATION_MODE, stabMode);
+        }
+
         /*
          * flash
          */
diff --git a/core/java/android/hardware/camera2/legacy/ParameterUtils.java b/core/java/android/hardware/camera2/legacy/ParameterUtils.java
index efd12f2..385f844 100644
--- a/core/java/android/hardware/camera2/legacy/ParameterUtils.java
+++ b/core/java/android/hardware/camera2/legacy/ParameterUtils.java
@@ -43,6 +43,7 @@
 /**
  * Various utilities for dealing with camera API1 parameters.
  */
+@SuppressWarnings("deprecation")
 public class ParameterUtils {
     /** Upper/left minimal point of a normalized rectangle */
     public static final int NORMALIZED_RECTANGLE_MIN = -1000;
@@ -164,19 +165,23 @@
          * <p>If the score is out of range of {@value Face#SCORE_MIN}, {@value Face#SCORE_MAX},
          * the score is clipped first and a warning is printed to logcat.</p>
          *
+         * <p>If the id is negative, the id is changed to 0 and a warning is printed to
+         * logcat.</p>
+         *
          * <p>All other parameters are passed-through as-is.</p>
          *
          * @return a new face with the optional features set
          */
         public Face toFace(
                 int id, Point leftEyePosition, Point rightEyePosition, Point mouthPosition) {
+            int idSafe = clipLower(id, /*lo*/0, rect, "id");
             int score = clip(weight,
                     Face.SCORE_MIN,
                     Face.SCORE_MAX,
                     rect,
                     "score");
 
-            return new Face(rect, score, id, leftEyePosition, rightEyePosition, mouthPosition);
+            return new Face(rect, score, idSafe, leftEyePosition, rightEyePosition, mouthPosition);
         }
 
         /**
@@ -861,6 +866,61 @@
                 /*usePreviewCrop*/true);
     }
 
+    /**
+     * Convert an api1 face into an active-array based api2 face.
+     *
+     * <p>Out-of-ranges scores and ids will be clipped to be within range (with a warning).</p>
+     *
+     * @param face a non-{@code null} api1 face
+     * @param activeArraySize active array size of the sensor (e.g. max jpeg size)
+     * @param zoomData the calculated zoom data corresponding to this request
+     *
+     * @return a non-{@code null} api2 face
+     *
+     * @throws NullPointerException if the {@code face} was {@code null}
+     */
+    public static Face convertFaceFromLegacy(Camera.Face face, Rect activeArray,
+            ZoomData zoomData) {
+        checkNotNull(face, "face must not be null");
+
+        Face api2Face;
+
+        Camera.Area fakeArea = new Camera.Area(face.rect, /*weight*/1);
+
+        WeightedRectangle faceRect =
+                convertCameraAreaToActiveArrayRectangle(activeArray, zoomData, fakeArea);
+
+        Point leftEye = face.leftEye, rightEye = face.rightEye, mouth = face.mouth;
+        if (leftEye != null && rightEye != null && mouth != null) {
+            leftEye = convertCameraPointToActiveArrayPoint(activeArray, zoomData,
+                    leftEye, /*usePreviewCrop*/true);
+            rightEye = convertCameraPointToActiveArrayPoint(activeArray, zoomData,
+                    leftEye, /*usePreviewCrop*/true);
+            mouth = convertCameraPointToActiveArrayPoint(activeArray, zoomData,
+                    leftEye, /*usePreviewCrop*/true);
+
+            api2Face = faceRect.toFace(face.id, leftEye, rightEye, mouth);
+        } else {
+            api2Face = faceRect.toFace();
+        }
+
+        return api2Face;
+    }
+
+    private static Point convertCameraPointToActiveArrayPoint(
+            Rect activeArray, ZoomData zoomData, Point point, boolean usePreviewCrop) {
+        Rect pointedRect = new Rect(point.x, point.y, point.x, point.y);
+        Camera.Area pointedArea = new Area(pointedRect, /*weight*/1);
+
+        WeightedRectangle adjustedRect =
+                convertCameraAreaToActiveArrayRectangle(activeArray,
+                        zoomData, pointedArea, usePreviewCrop);
+
+        Point transformedPoint = new Point(adjustedRect.rect.left, adjustedRect.rect.top);
+
+        return transformedPoint;
+    }
+
     private static WeightedRectangle convertCameraAreaToActiveArrayRectangle(
             Rect activeArray, ZoomData zoomData, Camera.Area area, boolean usePreviewCrop) {
         Rect previewCrop = zoomData.previewCrop;
diff --git a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
index c556c32..2533a28 100644
--- a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
+++ b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
@@ -39,7 +39,6 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
-import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
 import static com.android.internal.util.Preconditions.*;
@@ -55,18 +54,23 @@
  * - An {@link CameraDeviceState} state machine that manages the callbacks for various operations.
  * </p>
  */
+@SuppressWarnings("deprecation")
 public class RequestThreadManager {
     private final String TAG;
     private final int mCameraId;
     private final RequestHandlerThread mRequestThread;
 
     private static final boolean DEBUG = Log.isLoggable(LegacyCameraDevice.DEBUG_PROP, Log.DEBUG);
+    // For slightly more spammy messages that will get repeated every frame
+    private static final boolean VERBOSE =
+            Log.isLoggable(LegacyCameraDevice.DEBUG_PROP, Log.VERBOSE);
     private final Camera mCamera;
     private final CameraCharacteristics mCharacteristics;
 
     private final CameraDeviceState mDeviceState;
     private final CaptureCollector mCaptureCollector;
     private final LegacyFocusStateMapper mFocusStateMapper;
+    private final LegacyFaceDetectMapper mFaceDetectMapper;
 
     private static final int MSG_CONFIGURE_OUTPUTS = 1;
     private static final int MSG_SUBMIT_CAPTURE_REQUEST = 2;
@@ -219,6 +223,9 @@
             };
 
     private void stopPreview() {
+        if (VERBOSE) {
+            Log.v(TAG, "stopPreview - preview running? " + mPreviewRunning);
+        }
         if (mPreviewRunning) {
             mCamera.stopPreview();
             mPreviewRunning = false;
@@ -226,14 +233,18 @@
     }
 
     private void startPreview() {
+        if (VERBOSE) {
+            Log.v(TAG, "startPreview - preview running? " + mPreviewRunning);
+        }
         if (!mPreviewRunning) {
+            // XX: CameraClient:;startPreview is not getting called after a stop
             mCamera.startPreview();
             mPreviewRunning = true;
         }
     }
 
-    private void doJpegCapture(RequestHolder request) throws IOException {
-        if (DEBUG) Log.d(TAG, "doJpegCapture");
+    private void doJpegCapturePrepare(RequestHolder request) throws IOException {
+        if (DEBUG) Log.d(TAG, "doJpegCapturePrepare - preview running? " + mPreviewRunning);
 
         if (!mPreviewRunning) {
             if (DEBUG) Log.d(TAG, "doJpegCapture - create fake surface");
@@ -242,11 +253,20 @@
             mCamera.setPreviewTexture(mDummyTexture);
             startPreview();
         }
+    }
+
+    private void doJpegCapture(RequestHolder request) {
+        if (DEBUG) Log.d(TAG, "doJpegCapturePrepare");
+
         mCamera.takePicture(mJpegShutterCallback, /*raw*/null, mJpegCallback);
         mPreviewRunning = false;
     }
 
     private void doPreviewCapture(RequestHolder request) throws IOException {
+        if (VERBOSE) {
+            Log.v(TAG, "doPreviewCapture - preview running? " + mPreviewRunning);
+        }
+
         if (mPreviewRunning) {
             return; // Already running
         }
@@ -264,7 +284,20 @@
     }
 
     private void configureOutputs(Collection<Surface> outputs) throws IOException {
+        if (DEBUG) {
+            String outputsStr = outputs == null ? "null" : (outputs.size() + " surfaces");
+
+            Log.d(TAG, "configureOutputs with " + outputsStr);
+        }
+
         stopPreview();
+        /*
+         * Try to release the previous preview's surface texture earlier if we end up
+         * using a different one; this also reduces the likelihood of getting into a deadlock
+         * when disconnecting from the old previous texture at a later time.
+         */
+        mCamera.setPreviewTexture(/*surfaceTexture*/null);
+
         if (mGLThreadManager != null) {
             mGLThreadManager.waitUntilStarted();
             mGLThreadManager.ignoreNewFrames();
@@ -305,7 +338,6 @@
         }
         mParams.setPreviewFpsRange(bestRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
                 bestRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
-        mParams.setRecordingHint(true);
 
         if (mPreviewOutputs.size() > 0) {
             List<Size> outputSizes = new ArrayList<>(outputs.size());
@@ -613,10 +645,6 @@
                             }
                         }
 
-                        // Unconditionally process AF triggers, since they're non-idempotent
-                        // - must be done after setting the most-up-to-date AF mode
-                        mFocusStateMapper.processRequestTriggers(request, mParams);
-
                         try {
                             boolean success = mCaptureCollector.queueRequest(holder,
                                     mLastRequest, JPEG_FRAME_TIMEOUT, TimeUnit.MILLISECONDS);
@@ -624,6 +652,8 @@
                             if (!success) {
                                 Log.e(TAG, "Timed out while queueing capture request.");
                             }
+                            // Starting the preview needs to happen before enabling
+                            // face detection or auto focus
                             if (holder.hasPreviewTargets()) {
                                 doPreviewCapture(holder);
                             }
@@ -635,12 +665,33 @@
                                     Log.e(TAG, "Timed out waiting for prior requests to complete.");
                                 }
                                 mReceivedJpeg.close();
+                                doJpegCapturePrepare(holder);
+                                if (!mReceivedJpeg.block(JPEG_FRAME_TIMEOUT)) {
+                                    // TODO: report error to CameraDevice
+                                    Log.e(TAG, "Hit timeout for jpeg callback!");
+                                }
+                            }
+
+                            /*
+                             * Do all the actions that require a preview to have been started
+                             */
+
+                            // Toggle face detection on/off
+                            // - do this before AF to give AF a chance to use faces
+                            mFaceDetectMapper.processFaceDetectMode(request, /*in*/mParams);
+
+                            // Unconditionally process AF triggers, since they're non-idempotent
+                            // - must be done after setting the most-up-to-date AF mode
+                            mFocusStateMapper.processRequestTriggers(request, mParams);
+
+                            if (holder.hasJpegTargets()) {
                                 doJpegCapture(holder);
                                 if (!mReceivedJpeg.block(JPEG_FRAME_TIMEOUT)) {
                                     // TODO: report error to CameraDevice
                                     Log.e(TAG, "Hit timeout for jpeg callback!");
                                 }
                             }
+
                         } catch (IOException e) {
                             // TODO: report error to CameraDevice
                             throw new IOError(e);
@@ -677,6 +728,8 @@
                                 mLastRequest, timestampMutable.value);
                         // Update AF state
                         mFocusStateMapper.mapResultTriggers(result);
+                        // Update detected faces list
+                        mFaceDetectMapper.mapResultFaces(result, mLastRequest);
 
                         mDeviceState.setCaptureResult(holder, result);
                     }
@@ -731,6 +784,7 @@
         TAG = name;
         mDeviceState = checkNotNull(deviceState, "deviceState must not be null");
         mFocusStateMapper = new LegacyFocusStateMapper(mCamera);
+        mFaceDetectMapper = new LegacyFaceDetectMapper(mCamera, mCharacteristics);
         mCaptureCollector = new CaptureCollector(MAX_IN_FLIGHT_REQUESTS, mDeviceState);
         mRequestThread = new RequestHandlerThread(name, mRequestHandlerCb);
     }
diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
index 1efabb1..2e6b9ae 100644
--- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
@@ -813,6 +813,7 @@
         switch (format) {
             case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED:
             case HAL_PIXEL_FORMAT_BLOB:
+            case HAL_PIXEL_FORMAT_RAW_OPAQUE:
                 return format;
             case ImageFormat.JPEG:
                 throw new IllegalArgumentException(
@@ -843,12 +844,6 @@
      * @throws IllegalArgumentException if the format was not user-defined
      */
     static int checkArgumentFormat(int format) {
-        // TODO: remove this hack , CTS shouldn't have been using internal constants
-        if (format == HAL_PIXEL_FORMAT_RAW_OPAQUE) {
-            Log.w(TAG, "RAW_OPAQUE is not yet a published format; allowing it anyway");
-            return format;
-        }
-
         if (!ImageFormat.isPublicFormat(format) && !PixelFormat.isPublicFormat(format)) {
             throw new IllegalArgumentException(String.format(
                     "format 0x%x was not defined in either ImageFormat or PixelFormat", format));
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index d4e6df5..51b7229 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -96,11 +96,9 @@
      * windows on the display and the system may mirror the contents of other displays
      * onto it.
      * </p><p>
-     * Creating a public virtual display requires the
-     * {@link android.Manifest.permission#CAPTURE_VIDEO_OUTPUT}
-     * or {@link android.Manifest.permission#CAPTURE_SECURE_VIDEO_OUTPUT} permission.
-     * These permissions are reserved for use by system components and are not available to
-     * third-party applications.
+     * Creating a public virtual display that isn't restricted to own-content only implicitly
+     * creates an auto-mirroring display. See {@link #VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR} for
+     * restrictions on who is allowed to create an auto-mirroring display.
      * </p>
      *
      * <h3>Private virtual displays</h3>
@@ -108,6 +106,8 @@
      * When this flag is not set, the virtual display is private as defined by the
      * {@link Display#FLAG_PRIVATE} display flag.
      * </p>
+     *
+     * <p>
      * A private virtual display belongs to the application that created it.
      * Only the a owner of a private virtual display is allowed to place windows upon it.
      * The private virtual display also does not participate in display mirroring: it will
@@ -115,10 +115,11 @@
      * be mirrored elsewhere.  More precisely, the only processes that are allowed to
      * enumerate or interact with the private display are those that have the same UID as the
      * application that originally created the private virtual display.
-      * </p>
+     * </p>
      *
      * @see #createVirtualDisplay
      * @see #VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
+     * @see #VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR
      */
     public static final int VIRTUAL_DISPLAY_FLAG_PUBLIC = 1 << 0;
 
@@ -187,29 +188,51 @@
      * will be blanked instead if it has no windows.
      * </p>
      *
+     * <p>
+     * This flag is mutually exclusive with {@link #VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR}.  If both
+     * flags are specified then the own-content only behavior will be applied.
+     * </p>
+     *
+     * <p>
+     * This behavior of this flag is implied whenever neither {@link #VIRTUAL_DISPLAY_FLAG_PUBLIC}
+     * nor {@link #VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR} have been set.  This flag is only required to
+     * override the default behavior when creating a public display.
+     * </p>
+     *
      * @see #createVirtualDisplay
      */
     public static final int VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY = 1 << 3;
 
 
     /**
-     * Virtual display flag: Indicates that the display is being created for
-     * the purpose of screen sharing.  This implies
-     * VIRTUAL_DISPLAY_FLAG_PRIVATE.  Other flags are not allowed (especially
-     * not VIRTUAL_DISPLAY_FLAG_PUBLIC or PRESENTATION).
+     * Virtual display flag: Allows content to be mirrored on private displays when no content is
+     * being shown.
      *
      * <p>
-     * Requires screen share permission for use.
+     * This flag is mutually exclusive with {@link #VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY}.
+     * If both flags are specified then the own-content only behavior will be applied.
      * </p>
      *
      * <p>
-     * While a display of this type exists, the system will show some sort of
-     * notification to the user indicating that the screen is being shared.
+     * The behavior of this flag is implied whenever {@link #VIRTUAL_DISPLAY_FLAG_PUBLIC} is set
+     * and {@link #VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY} has not been set.   This flag is only
+     * required to override the default behavior when creating a private display.
+     * </p>
+     *
+     * <p>
+     * Creating an auto-mirroing virtual display requires the
+     * {@link android.Manifest.permission#CAPTURE_VIDEO_OUTPUT}
+     * or {@link android.Manifest.permission#CAPTURE_SECURE_VIDEO_OUTPUT} permission.
+     * These permissions are reserved for use by system components and are not available to
+     * third-party applications.
+     *
+     * Alternatively, an appropriate {@link MediaProjection} may be used to create an
+     * auto-mirroring virtual display.
      * </p>
      *
      * @see #createVirtualDisplay
      */
-    public static final int VIRTUAL_DISPLAY_FLAG_SCREEN_SHARE = 1 << 4;
+    public static final int VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR = 1 << 4;
 
     /** @hide */
     public DisplayManager(Context context) {
@@ -489,7 +512,7 @@
      * @param flags A combination of virtual display flags:
      * {@link #VIRTUAL_DISPLAY_FLAG_PUBLIC}, {@link #VIRTUAL_DISPLAY_FLAG_PRESENTATION},
      * {@link #VIRTUAL_DISPLAY_FLAG_SECURE}, {@link #VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY},
-     * or {@link #VIRTUAL_DISPLAY_FLAG_SCREEN_SHARE}.
+     * or {@link #VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR}.
      * @param callbacks Callbacks to call when the state of the {@link VirtualDisplay} changes
      * @param handler The handler on which the listener should be invoked, or null
      * if the listener should be invoked on the calling thread's looper.
diff --git a/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl b/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl
index f279668..2f6dbe7 100644
--- a/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl
+++ b/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl
@@ -34,4 +34,12 @@
      * @param status The error code that was seen.
      */
     void onError(int status);
+    /**
+     * Called when the recognition is paused temporarily for some reason.
+     */
+    void onRecognitionPaused();
+    /**
+     * Called when the recognition is resumed after it was temporarily paused.
+     */
+    void onRecognitionResumed();
 }
\ No newline at end of file
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index 786439e..3f18519 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -16,6 +16,7 @@
 
 package android.net;
 
+import android.annotation.SystemApi;
 import android.app.DownloadManager;
 import android.app.backup.BackupManager;
 import android.content.Context;
@@ -127,6 +128,16 @@
     }
 
     /**
+     * System API for backup-related support components to tag network traffic
+     * appropriately.
+     * @hide
+     */
+    @SystemApi
+    public static void setThreadStatsTagBackup() {
+        setThreadStatsTag(TAG_SYSTEM_BACKUP);
+    }
+
+    /**
      * Get the active tag used when accounting {@link Socket} traffic originating
      * from the current thread. Only one active tag per thread is supported.
      * {@link #tagSocket(Socket)}.
@@ -160,11 +171,13 @@
      *
      * @hide
      */
+    @SystemApi
     public static void setThreadStatsUid(int uid) {
         NetworkManagementSocketTagger.setThreadSocketStatsUid(uid);
     }
 
     /** {@hide} */
+    @SystemApi
     public static void clearThreadStatsUid() {
         NetworkManagementSocketTagger.setThreadSocketStatsUid(-1);
     }
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 545b19a..ba79f91 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -33,6 +33,7 @@
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.util.TimeUtils;
+import android.view.Display;
 import com.android.internal.os.BatterySipper;
 import com.android.internal.os.BatteryStatsHelper;
 
@@ -1596,6 +1597,27 @@
      */
     public abstract long computeBatteryTimeRemaining(long curTime);
 
+    // The part of a step duration that is the actual time.
+    public static final long STEP_LEVEL_TIME_MASK = 0x000000ffffffffffL;
+
+    // Bits in a step duration that are the new battery level we are at.
+    public static final long STEP_LEVEL_LEVEL_MASK = 0x0000ff0000000000L;
+    public static final long STEP_LEVEL_LEVEL_SHIFT = 40;
+
+    // Bits in a step duration that are the initial mode we were in at that step.
+    public static final long STEP_LEVEL_INITIAL_MODE_MASK = 0x00ff000000000000L;
+    public static final long STEP_LEVEL_INITIAL_MODE_SHIFT = 48;
+
+    // Bits in a step duration that indicate which modes changed during that step.
+    public static final long STEP_LEVEL_MODIFIED_MODE_MASK = 0xff00000000000000L;
+    public static final long STEP_LEVEL_MODIFIED_MODE_SHIFT = 56;
+
+    // Step duration mode: the screen is on, off, dozed, etc; value is Display.STATE_* - 1.
+    public static final int STEP_LEVEL_MODE_SCREEN_STATE = 0x03;
+
+    // Step duration mode: power save is on.
+    public static final int STEP_LEVEL_MODE_POWER_SAVE = 0x04;
+
     /**
      * Return the historical number of discharge steps we currently have.
      */
@@ -3620,14 +3642,60 @@
         if (!checkin) {
             pw.println(header);
         }
-        String[] lineArgs = new String[1];
+        String[] lineArgs = new String[4];
         for (int i=0; i<count; i++) {
+            long duration = steps[i] & STEP_LEVEL_TIME_MASK;
+            int level = (int)((steps[i] & STEP_LEVEL_LEVEL_MASK)
+                    >> STEP_LEVEL_LEVEL_SHIFT);
+            long initMode = (steps[i] & STEP_LEVEL_INITIAL_MODE_MASK)
+                    >> STEP_LEVEL_INITIAL_MODE_SHIFT;
+            long modMode = (steps[i] & STEP_LEVEL_MODIFIED_MODE_MASK)
+                    >> STEP_LEVEL_MODIFIED_MODE_SHIFT;
             if (checkin) {
-                lineArgs[0] = Long.toString(steps[i]);
+                lineArgs[0] = Long.toString(duration);
+                lineArgs[1] = Integer.toString(level);
+                if ((modMode&STEP_LEVEL_MODE_SCREEN_STATE) == 0) {
+                    switch ((int)(initMode&STEP_LEVEL_MODE_SCREEN_STATE) + 1) {
+                        case Display.STATE_OFF: lineArgs[2] = "s-"; break;
+                        case Display.STATE_ON: lineArgs[2] = "s+"; break;
+                        case Display.STATE_DOZE: lineArgs[2] = "sd"; break;
+                        case Display.STATE_DOZE_SUSPEND: lineArgs[2] = "sds"; break;
+                        default: lineArgs[1] = "?"; break;
+                    }
+                } else {
+                    lineArgs[2] = "";
+                }
+                if ((modMode&STEP_LEVEL_MODE_POWER_SAVE) == 0) {
+                    lineArgs[3] = (initMode&STEP_LEVEL_MODE_POWER_SAVE) != 0 ? "p+" : "p-";
+                } else {
+                    lineArgs[3] = "";
+                }
                 dumpLine(pw, 0 /* uid */, "i" /* category */, header, (Object[])lineArgs);
             } else {
                 pw.print("  #"); pw.print(i); pw.print(": ");
-                TimeUtils.formatDuration(steps[i], pw);
+                TimeUtils.formatDuration(duration, pw);
+                pw.print(" to "); pw.print(level);
+                boolean haveModes = false;
+                if ((modMode&STEP_LEVEL_MODE_SCREEN_STATE) == 0) {
+                    pw.print(" (");
+                    switch ((int)(initMode&STEP_LEVEL_MODE_SCREEN_STATE) + 1) {
+                        case Display.STATE_OFF: pw.print("screen-off"); break;
+                        case Display.STATE_ON: pw.print("screen-on"); break;
+                        case Display.STATE_DOZE: pw.print("screen-doze"); break;
+                        case Display.STATE_DOZE_SUSPEND: pw.print("screen-doze-suspend"); break;
+                        default: lineArgs[1] = "screen-?"; break;
+                    }
+                    haveModes = true;
+                }
+                if ((modMode&STEP_LEVEL_MODE_POWER_SAVE) == 0) {
+                    pw.print(haveModes ? ", " : " (");
+                    pw.print((initMode&STEP_LEVEL_MODE_POWER_SAVE) != 0
+                            ? "power-save-on" : "power-save-off");
+                    haveModes = true;
+                }
+                if (haveModes) {
+                    pw.print(")");
+                }
                 pw.println();
             }
         }
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index c43644a..713fcd8 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -18,7 +18,6 @@
 package android.os;
 
 import android.os.Bundle;
-import android.os.ParcelFileDescriptor;
 import android.content.pm.UserInfo;
 import android.content.RestrictionEntry;
 import android.graphics.Bitmap;
diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java
index afbf983..5ce9d5c 100644
--- a/core/java/android/os/UserHandle.java
+++ b/core/java/android/os/UserHandle.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.annotation.SystemApi;
 import android.util.SparseArray;
 
 import java.io.PrintWriter;
@@ -237,6 +238,16 @@
         return getUserId(Process.myUid());
     }
 
+    /**
+     * Returns true if this UserHandle refers to the owner user; false otherwise.
+     * @return true if this UserHandle refers to the owner user; false otherwise.
+     * @hide
+     */
+    @SystemApi
+    public final boolean isOwner() {
+        return this.equals(OWNER);
+    }
+
     /** @hide */
     public UserHandle(int h) {
         mHandle = h;
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 3087506..04e6227 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -864,6 +864,15 @@
     }
 
     /**
+     * Kept during L development to simplify updating unbundled apps.
+     * TODO: Remove after 2014-08-04
+     * @hide
+     */
+    public String getBadgedLabelForUser(String label, UserHandle user) {
+        return (String) getBadgedLabelForUser((CharSequence) label, user);
+    }
+
+    /**
      * If the target user is a managed profile of the calling user or the caller
      * is itself a managed profile, then this returns a drawable to use as a small
      * icon to include in a view to distinguish it from the original icon.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index de1df56..733ab32 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2375,6 +2375,16 @@
         public static final String VIBRATE_WHEN_RINGING = "vibrate_when_ringing";
 
         /**
+         * Whether automatic routing of system audio to USB audio peripheral is disabled.
+         * The value is boolean (1 or 0), where 1 means automatic routing is disabled,
+         * and 0 means automatic routing is enabled.
+         *
+         * @hide
+         */
+        public static final String USB_AUDIO_AUTOMATIC_ROUTING_DISABLED =
+                "usb_audio_automatic_routing_disabled";
+
+        /**
          * Whether the audible DTMF tones are played by the dialer when dialing. The value is
          * boolean (1 or 0).
          */
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index b0ff947..4860b44 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -161,6 +161,8 @@
     private static final int MSG_AVAILABILITY_CHANGED = 1;
     private static final int MSG_HOTWORD_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 String mText;
     private final String mLocale;
@@ -180,21 +182,27 @@
     private int mAvailability = STATE_NOT_READY;
 
     /**
-     * Details of the audio that triggered the keyphrase.
+     * Additional payload for {@link Callback#onDetected}.
      */
-    public static class TriggerAudio {
+    public static class EventPayload {
         /**
-         * Format of {@code data}.
+         * Indicates if {@code data} is the audio that triggered the keyphrase.
          */
-        @NonNull
+        public final boolean isTriggerAudio;
+        /**
+         * Format of {@code data}. May be null if {@code isTriggerAudio} is false.
+         */
+        @Nullable
         public final AudioFormat audioFormat;
         /**
-         * Raw audio data that triggered they keyphrase.
+         * Raw data associated with the event.
+         * This is the audio that triggered the keyphrase if {@code isTriggerAudio} is true.
          */
-        @NonNull
+        @Nullable
         public final byte[] data;
 
-        private TriggerAudio(AudioFormat _audioFormat, byte[] _data) {
+        private EventPayload(boolean _isTriggerAudio, AudioFormat _audioFormat, byte[] _data) {
+            isTriggerAudio = _isTriggerAudio;
             audioFormat = _audioFormat;
             data = _data;
         }
@@ -224,14 +232,27 @@
          * Clients should start a recognition again once they are done handling this
          * detection.
          *
-         * @param triggerAudio Optional trigger audio data, if it was requested during
+         * @param eventPayload Payload data for the detection event.
+         *        This may contain the trigger audio, if requested when calling
          *        {@link AlwaysOnHotwordDetector#startRecognition(int)}.
          */
-        void onDetected(@Nullable TriggerAudio triggerAudio);
+        void onDetected(@NonNull EventPayload eventPayload);
         /**
          * Called when the detection fails due to an error.
          */
         void onError();
+        /**
+         * Called when the recognition is paused temporarily for some reason.
+         * This is an informational callback, and the clients shouldn't be doing anything here
+         * except showing an indication on their UI if they have to.
+         */
+        void onRecognitionPaused();
+        /**
+         * Called when the recognition is resumed after it was temporarily paused.
+         * This is an informational callback, and the clients shouldn't be doing anything here
+         * except showing an indication on their UI if they have to.
+         */
+        void onRecognitionResumed();
     }
 
     /**
@@ -487,22 +508,13 @@
         @Override
         public void onDetected(KeyphraseRecognitionEvent event) {
             if (DBG) {
-                Slog.d(TAG, "OnDetected(" + event + ")");
+                Slog.d(TAG, "onDetected(" + event + ")");
             } else {
                 Slog.i(TAG, "onDetected");
             }
-            Message message = Message.obtain(mHandler, MSG_HOTWORD_DETECTED);
-            // FIXME: Check whether the event contains trigger data or not.
-            // FIXME: Read the audio format from the event.
-            if (event.data != null) {
-                AudioFormat audioFormat = new AudioFormat.Builder()
-                        .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
-                        .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
-                        .setSampleRate(16000)
-                        .build();
-                message.obj = new TriggerAudio(audioFormat, event.data);
-            }
-            message.sendToTarget();
+            Message.obtain(mHandler, MSG_HOTWORD_DETECTED,
+                    new EventPayload(event.triggerInData, event.captureFormat, event.data))
+                    .sendToTarget();
         }
 
         @Override
@@ -510,6 +522,18 @@
             Slog.i(TAG, "onError: " + status);
             mHandler.sendEmptyMessage(MSG_DETECTION_ERROR);
         }
+
+        @Override
+        public void onRecognitionPaused() {
+            Slog.i(TAG, "onRecognitionPaused");
+            mHandler.sendEmptyMessage(MSG_DETECTION_PAUSE);
+        }
+
+        @Override
+        public void onRecognitionResumed() {
+            Slog.i(TAG, "onRecognitionResumed");
+            mHandler.sendEmptyMessage(MSG_DETECTION_RESUME);
+        }
     }
 
     class MyHandler extends Handler {
@@ -527,11 +551,17 @@
                     mExternalCallback.onAvailabilityChanged(msg.arg1);
                     break;
                 case MSG_HOTWORD_DETECTED:
-                    mExternalCallback.onDetected((TriggerAudio) msg.obj);
+                    mExternalCallback.onDetected((EventPayload) msg.obj);
                     break;
                 case MSG_DETECTION_ERROR:
                     mExternalCallback.onError();
                     break;
+                case MSG_DETECTION_PAUSE:
+                    mExternalCallback.onRecognitionPaused();
+                    break;
+                case MSG_DETECTION_RESUME:
+                    mExternalCallback.onRecognitionResumed();
+                    break;
                 default:
                     super.handleMessage(msg);
             }
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index e3e328c..19d14bf 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -448,6 +448,7 @@
     }
 
     void doDestroy() {
+        onDestroy();
         if (mInitialized) {
             mRootView.getViewTreeObserver().removeOnComputeInternalInsetsListener(
                     mInsetsComputer);
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index aa0b94f..129476c 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -3857,13 +3857,17 @@
                                     firstChildTop == contentTop - mOverscrollDistance) ||
                               (mFirstPosition + childCount == mItemCount &&
                                     lastChildBottom == contentBottom + mOverscrollDistance))) {
-                        if (mFlingRunnable == null) {
-                            mFlingRunnable = new FlingRunnable();
+                        if (!dispatchNestedPreFling(0, -initialVelocity)) {
+                            if (mFlingRunnable == null) {
+                                mFlingRunnable = new FlingRunnable();
+                            }
+                            reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
+                            mFlingRunnable.start(-initialVelocity);
+                            dispatchNestedFling(0, -initialVelocity, true);
+                        } else {
+                            mTouchMode = TOUCH_MODE_REST;
+                            reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
                         }
-                        reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
-
-                        mFlingRunnable.start(-initialVelocity);
-                        dispatchNestedFling(0, -initialVelocity, true);
                     } else {
                         mTouchMode = TOUCH_MODE_REST;
                         reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
@@ -4029,7 +4033,9 @@
             if (mFlingRunnable == null) {
                 mFlingRunnable = new FlingRunnable();
             }
-            mFlingRunnable.start((int) velocityY);
+            if (!dispatchNestedPreFling(0, velocityY)) {
+                mFlingRunnable.start((int) velocityY);
+            }
             return true;
         }
         return dispatchNestedFling(velocityX, velocityY, consumed);
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index fd04890..dd312a6 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -1577,9 +1577,11 @@
     private void flingWithNestedDispatch(int velocityY) {
         final boolean canFling = (mScrollY > 0 || velocityY > 0) &&
                 (mScrollY < getScrollRange() || velocityY < 0);
-        dispatchNestedFling(0, velocityY, canFling);
-        if (canFling) {
-            fling(velocityY);
+        if (!dispatchNestedPreFling(0, velocityY)) {
+            dispatchNestedFling(0, velocityY, canFling);
+            if (canFling) {
+                fling(velocityY);
+            }
         }
     }
 
diff --git a/core/java/android/widget/TimePickerClockDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java
index 59ec6d3..8102c4a 100644
--- a/core/java/android/widget/TimePickerClockDelegate.java
+++ b/core/java/android/widget/TimePickerClockDelegate.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Color;
 import android.os.Parcel;
@@ -171,7 +172,10 @@
         mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT);
 
             /* Get the localized am/pm strings and use them in the spinner */
-        mAmPmStrings = new DateFormatSymbols().getAmPmStrings();
+        final Resources res = context.getResources();
+        final String amText = res.getString(R.string.time_picker_am_label);
+        final String pmText = res.getString(R.string.time_picker_pm_label);
+        mAmPmStrings = new String[] {amText, pmText};
 
         // am/pm
         View amPmView = mDelegator.findViewById(R.id.amPm);
diff --git a/core/java/android/widget/TimePickerSpinnerDelegate.java b/core/java/android/widget/TimePickerSpinnerDelegate.java
index 4e9a39f..523965c 100644
--- a/core/java/android/widget/TimePickerSpinnerDelegate.java
+++ b/core/java/android/widget/TimePickerSpinnerDelegate.java
@@ -134,6 +134,8 @@
         mSelectHours = res.getString(R.string.select_hours);
         mMinutePickerDescription = res.getString(R.string.minute_picker_description);
         mSelectMinutes = res.getString(R.string.select_minutes);
+        mAmText = res.getString(R.string.time_picker_am_label);
+        mPmText = res.getString(R.string.time_picker_pm_label);
 
         final int layoutResourceId = a.getResourceId(R.styleable.TimePicker_internalLayout,
                 R.layout.time_picker_holo);
@@ -182,10 +184,6 @@
                 R.id.radial_picker);
         mDoneButton = (Button) mainView.findViewById(R.id.done_button);
 
-        String[] amPmTexts = new DateFormatSymbols().getAmPmStrings();
-        mAmText = amPmTexts[0];
-        mPmText = amPmTexts[1];
-
         setupListeners();
 
         mAllowAutoAdvance = true;
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 5655506..40965f0 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -18,6 +18,7 @@
 
 import com.android.internal.os.BatteryStatsImpl;
 
+import android.os.ParcelFileDescriptor;
 import android.os.WorkSource;
 import android.telephony.DataConnectionRealTimeInfo;
 import android.telephony.SignalStrength;
@@ -37,6 +38,8 @@
     // Remaining methods are only used in Java.
     byte[] getStatistics();
 
+    ParcelFileDescriptor getStatisticsStream();
+
     // Return the computed amount of time remaining on battery, in milliseconds.
     // Returns -1 if nothing could be computed.
     long computeBatteryTimeRemaining();
diff --git a/core/java/com/android/internal/os/AtomicFile.java b/core/java/com/android/internal/os/AtomicFile.java
index 445d10a..5a83f33 100644
--- a/core/java/com/android/internal/os/AtomicFile.java
+++ b/core/java/com/android/internal/os/AtomicFile.java
@@ -40,7 +40,7 @@
  * appropriate mutual exclusion invariants whenever it accesses the file.
  * </p>
  */
-public class AtomicFile {
+public final class AtomicFile {
     private final File mBaseName;
     private final File mBackupName;
     
@@ -129,7 +129,16 @@
         } catch (IOException e) {
         }
     }
-    
+
+    public boolean exists() {
+        return mBaseName.exists() || mBackupName.exists();
+    }
+
+    public void delete() {
+        mBaseName.delete();
+        mBackupName.delete();
+    }
+
     public FileInputStream openRead() throws FileNotFoundException {
         if (mBackupName.exists()) {
             mBaseName.delete();
diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java
index 6c9b4b8..63be2d4 100644
--- a/core/java/com/android/internal/os/BatteryStatsHelper.java
+++ b/core/java/com/android/internal/os/BatteryStatsHelper.java
@@ -30,19 +30,26 @@
 import android.os.BatteryStats;
 import android.os.BatteryStats.Uid;
 import android.os.Bundle;
+import android.os.MemoryFile;
 import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.telephony.SignalStrength;
+import android.util.ArrayMap;
 import android.util.Log;
 import android.util.SparseArray;
 
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.os.BatterySipper.DrainType;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
@@ -55,7 +62,7 @@
  * The caller must initialize this class as soon as activity object is ready to use (for example, in
  * onAttach() for Fragment), call create() in onCreate() and call destroy() in onDestroy().
  */
-public class BatteryStatsHelper {
+public final class BatteryStatsHelper {
 
     private static final boolean DEBUG = false;
 
@@ -63,6 +70,7 @@
 
     private static BatteryStats sStatsXfer;
     private static Intent sBatteryBroadcastXfer;
+    private static ArrayMap<File, BatteryStats> sFileXfer = new ArrayMap<>();
 
     final private Context mContext;
     final private boolean mCollectBatteryBroadcast;
@@ -117,6 +125,68 @@
         mCollectBatteryBroadcast = collectBatteryBroadcast;
     }
 
+    public void storeStatsHistoryInFile(String fname) {
+        synchronized (sFileXfer) {
+            File path = makeFilePath(mContext, fname);
+            sFileXfer.put(path, this.getStats());
+            FileOutputStream fout = null;
+            try {
+                fout = new FileOutputStream(path);
+                Parcel hist = Parcel.obtain();
+                getStats().writeToParcelWithoutUids(hist, 0);
+                byte[] histData = hist.marshall();
+                fout.write(histData);
+            } catch (IOException e) {
+                Log.w(TAG, "Unable to write history to file", e);
+            } finally {
+                if (fout != null) {
+                    try {
+                        fout.close();
+                    } catch (IOException e) {
+                    }
+                }
+            }
+        }
+    }
+
+    public static BatteryStats statsFromFile(Context context, String fname) {
+        synchronized (sFileXfer) {
+            File path = makeFilePath(context, fname);
+            BatteryStats stats = sFileXfer.get(path);
+            if (stats != null) {
+                return stats;
+            }
+            FileInputStream fin = null;
+            try {
+                fin = new FileInputStream(path);
+                byte[] data = readFully(fin);
+                Parcel parcel = Parcel.obtain();
+                parcel.unmarshall(data, 0, data.length);
+                parcel.setDataPosition(0);
+                return com.android.internal.os.BatteryStatsImpl.CREATOR.createFromParcel(parcel);
+            } catch (IOException e) {
+                Log.w(TAG, "Unable to read history to file", e);
+            } finally {
+                if (fin != null) {
+                    try {
+                        fin.close();
+                    } catch (IOException e) {
+                    }
+                }
+            }
+        }
+        return getStats(IBatteryStats.Stub.asInterface(
+                        ServiceManager.getService(BatteryStats.SERVICE_NAME)));
+    }
+
+    public static void dropFile(Context context, String fname) {
+        makeFilePath(context, fname).delete();
+    }
+
+    private static File makeFilePath(Context context, String fname) {
+        return new File(context.getFilesDir(), fname);
+    }
+
     /** Clears the current stats and forces recreating for future use. */
     public void clearStats() {
         mStats = null;
@@ -853,25 +923,64 @@
 
     public long getChargeTimeRemaining() { return mChargeTimeRemaining; }
 
+    public static byte[] readFully(FileInputStream stream) throws java.io.IOException {
+        return readFully(stream, stream.available());
+    }
+
+    public static byte[] readFully(FileInputStream stream, int avail) throws java.io.IOException {
+        int pos = 0;
+        byte[] data = new byte[avail];
+        while (true) {
+            int amt = stream.read(data, pos, data.length-pos);
+            //Log.i("foo", "Read " + amt + " bytes at " + pos
+            //        + " of avail " + data.length);
+            if (amt <= 0) {
+                //Log.i("foo", "**** FINISHED READING: pos=" + pos
+                //        + " len=" + data.length);
+                return data;
+            }
+            pos += amt;
+            avail = stream.available();
+            if (avail > data.length-pos) {
+                byte[] newData = new byte[pos+avail];
+                System.arraycopy(data, 0, newData, 0, pos);
+                data = newData;
+            }
+        }
+    }
+
     private void load() {
         if (mBatteryInfo == null) {
             return;
         }
-        try {
-            byte[] data = mBatteryInfo.getStatistics();
-            Parcel parcel = Parcel.obtain();
-            parcel.unmarshall(data, 0, data.length);
-            parcel.setDataPosition(0);
-            BatteryStatsImpl stats = com.android.internal.os.BatteryStatsImpl.CREATOR
-                    .createFromParcel(parcel);
-            stats.distributeWorkLocked(BatteryStats.STATS_SINCE_CHARGED);
-            mStats = stats;
-        } catch (RemoteException e) {
-            Log.e(TAG, "RemoteException:", e);
-        }
+        mStats = getStats(mBatteryInfo);
         if (mCollectBatteryBroadcast) {
             mBatteryBroadcast = mContext.registerReceiver(null,
                     new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
         }
     }
+
+    private static BatteryStatsImpl getStats(IBatteryStats service) {
+        try {
+            ParcelFileDescriptor pfd = service.getStatisticsStream();
+            if (pfd != null) {
+                FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+                try {
+                    byte[] data = readFully(fis, MemoryFile.getSize(pfd.getFileDescriptor()));
+                    Parcel parcel = Parcel.obtain();
+                    parcel.unmarshall(data, 0, data.length);
+                    parcel.setDataPosition(0);
+                    BatteryStatsImpl stats = com.android.internal.os.BatteryStatsImpl.CREATOR
+                            .createFromParcel(parcel);
+                    stats.distributeWorkLocked(BatteryStats.STATS_SINCE_CHARGED);
+                    return stats;
+                } catch (IOException e) {
+                    Log.w(TAG, "Unable to read statistics stream", e);
+                }
+            }
+        } catch (RemoteException e) {
+            Log.w(TAG, "RemoteException:", e);
+        }
+        return null;
+    }
 }
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index aad1156..50b86d0 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -109,6 +109,7 @@
     private static int sNumSpeedSteps;
 
     private final JournaledFile mFile;
+    public final AtomicFile mCheckinFile;
 
     static final int MSG_UPDATE_WAKELOCKS = 1;
     static final int MSG_REPORT_POWER_CHANGE = 2;
@@ -142,7 +143,7 @@
         }
     }
 
-    private final MyHandler mHandler;
+    public final MyHandler mHandler;
 
     private BatteryCallback mCallback;
 
@@ -199,8 +200,8 @@
     boolean mRecordingHistory = false;
     int mNumHistoryItems;
 
-    static final int MAX_HISTORY_BUFFER = 128*1024; // 128KB
-    static final int MAX_MAX_HISTORY_BUFFER = 144*1024; // 144KB
+    static final int MAX_HISTORY_BUFFER = 256*1024; // 256KB
+    static final int MAX_MAX_HISTORY_BUFFER = 320*1024; // 320KB
     final Parcel mHistoryBuffer = Parcel.obtain();
     final HistoryItem mHistoryLastWritten = new HistoryItem();
     final HistoryItem mHistoryLastLastWritten = new HistoryItem();
@@ -240,7 +241,7 @@
 
     int mWakeLockNesting;
     boolean mWakeLockImportant;
-    boolean mRecordAllWakeLocks;
+    boolean mRecordAllHistory;
     boolean mNoAutoReset;
 
     int mScreenState = Display.STATE_UNKNOWN;
@@ -342,6 +343,10 @@
 
     static final int MAX_LEVEL_STEPS = 100;
 
+    int mInitStepMode = 0;
+    int mCurStepMode = 0;
+    int mModStepMode = 0;
+
     int mLastDischargeStepLevel;
     long mLastDischargeStepTime;
     int mMinDischargeStepLevel;
@@ -429,10 +434,11 @@
     @GuardedBy("this")
     private String[] mWifiIfaces = new String[0];
 
-    // For debugging
     public BatteryStatsImpl() {
         mFile = null;
+        mCheckinFile = null;
         mHandler = null;
+        clearHistoryLocked();
     }
 
     public static interface TimeBaseObs {
@@ -2353,6 +2359,9 @@
         if (!mActiveEvents.updateState(HistoryItem.EVENT_PROC_START, name, uid, 0)) {
             return;
         }
+        if (!mRecordAllHistory) {
+            return;
+        }
         final long elapsedRealtime = SystemClock.elapsedRealtime();
         final long uptime = SystemClock.uptimeMillis();
         addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_PROC_START, name, uid);
@@ -2371,9 +2380,12 @@
         }
         final long elapsedRealtime = SystemClock.elapsedRealtime();
         final long uptime = SystemClock.uptimeMillis();
-        addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_PROC_FINISH, name, uid);
         getUidStatsLocked(uid).updateProcessStateLocked(name, Uid.PROCESS_STATE_NONE,
                 elapsedRealtime);
+        if (!mRecordAllHistory) {
+            return;
+        }
+        addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_PROC_FINISH, name, uid);
     }
 
     public void noteSyncStartLocked(String name, int uid) {
@@ -2427,11 +2439,41 @@
         }
     }
 
-    public void setRecordAllWakeLocksLocked(boolean enabled) {
-        mRecordAllWakeLocks = enabled;
+    public void setRecordAllHistoryLocked(boolean enabled) {
+        mRecordAllHistory = enabled;
         if (!enabled) {
             // Clear out any existing state.
             mActiveEvents.removeEvents(HistoryItem.EVENT_WAKE_LOCK);
+            // Record the currently running processes as stopping, now that we are no
+            // longer tracking them.
+            HashMap<String, SparseIntArray> active = mActiveEvents.getStateForEvent(
+                    HistoryItem.EVENT_PROC);
+            if (active != null) {
+                long mSecRealtime = SystemClock.elapsedRealtime();
+                final long mSecUptime = SystemClock.uptimeMillis();
+                for (HashMap.Entry<String, SparseIntArray> ent : active.entrySet()) {
+                    SparseIntArray uids = ent.getValue();
+                    for (int j=0; j<uids.size(); j++) {
+                        addHistoryEventLocked(mSecRealtime, mSecUptime,
+                                HistoryItem.EVENT_PROC_FINISH, ent.getKey(), uids.keyAt(j));
+                    }
+                }
+            }
+        } else {
+            // Record the currently running processes as starting, now that we are tracking them.
+            HashMap<String, SparseIntArray> active = mActiveEvents.getStateForEvent(
+                    HistoryItem.EVENT_PROC);
+            if (active != null) {
+                long mSecRealtime = SystemClock.elapsedRealtime();
+                final long mSecUptime = SystemClock.uptimeMillis();
+                for (HashMap.Entry<String, SparseIntArray> ent : active.entrySet()) {
+                    SparseIntArray uids = ent.getValue();
+                    for (int j=0; j<uids.size(); j++) {
+                        addHistoryEventLocked(mSecRealtime, mSecUptime,
+                                HistoryItem.EVENT_PROC_START, ent.getKey(), uids.keyAt(j));
+                    }
+                }
+            }
         }
     }
 
@@ -2452,7 +2494,7 @@
             if (historyName == null) {
                 historyName = name;
             }
-            if (mRecordAllWakeLocks) {
+            if (mRecordAllHistory) {
                 if (mActiveEvents.updateState(HistoryItem.EVENT_WAKE_LOCK_START, historyName,
                         uid, 0)) {
                     addHistoryEventLocked(elapsedRealtime, uptime,
@@ -2496,7 +2538,7 @@
         uid = mapUid(uid);
         if (type == WAKE_TYPE_PARTIAL) {
             mWakeLockNesting--;
-            if (mRecordAllWakeLocks) {
+            if (mRecordAllHistory) {
                 if (historyName == null) {
                     historyName = name;
                 }
@@ -2788,6 +2830,18 @@
             if (DEBUG) Slog.v(TAG, "Screen state: oldState=" + Display.stateToString(oldState)
                     + ", newState=" + Display.stateToString(state));
 
+            if (state != Display.STATE_UNKNOWN) {
+                int stepState = state-1;
+                if (stepState < 4) {
+                    mModStepMode |= (mCurStepMode&STEP_LEVEL_MODE_SCREEN_STATE) ^ stepState;
+                    mCurStepMode = (mCurStepMode&~STEP_LEVEL_MODE_SCREEN_STATE) | stepState;
+                } else {
+                    Slog.wtf(TAG, "Unexpected screen state: " + state);
+                }
+            }
+
+            mInitStepMode = mCurStepMode;
+            mModStepMode = 0;
             if (state == Display.STATE_ON) {
                 // Screen turning on.
                 final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -2924,6 +2978,9 @@
 
     public void noteLowPowerMode(boolean enabled) {
         if (mLowPowerModeEnabled != enabled) {
+            int stepState = enabled ? STEP_LEVEL_MODE_POWER_SAVE : 0;
+            mModStepMode |= (mCurStepMode&STEP_LEVEL_MODE_POWER_SAVE) ^ stepState;
+            mCurStepMode = (mCurStepMode&~STEP_LEVEL_MODE_POWER_SAVE) | stepState;
             final long elapsedRealtime = SystemClock.elapsedRealtime();
             final long uptime = SystemClock.uptimeMillis();
             mLowPowerModeEnabled = enabled;
@@ -6032,8 +6089,14 @@
         }
     }
 
-    public BatteryStatsImpl(String filename, Handler handler) {
-        mFile = new JournaledFile(new File(filename), new File(filename + ".tmp"));
+    public BatteryStatsImpl(File systemDir, Handler handler) {
+        if (systemDir != null) {
+            mFile = new JournaledFile(new File(systemDir, "batterystats.bin"),
+                    new File(systemDir, "batterystats.bin.tmp"));
+        } else {
+            mFile = null;
+        }
+        mCheckinFile = new AtomicFile(new File(systemDir, "batterystats-checkin.bin"));
         mHandler = new MyHandler(handler.getLooper());
         mStartCount++;
         mScreenOnTimer = new StopwatchTimer(null, -1, null, mOnBatteryTimeBase);
@@ -6095,6 +6158,7 @@
 
     public BatteryStatsImpl(Parcel p) {
         mFile = null;
+        mCheckinFile = null;
         mHandler = null;
         clearHistoryLocked();
         readFromParcel(p);
@@ -6390,6 +6454,10 @@
 
     private void initActiveHistoryEventsLocked(long elapsedRealtimeMs, long uptimeMs) {
         for (int i=0; i<HistoryItem.EVENT_COUNT; i++) {
+            if (!mRecordAllHistory && i == HistoryItem.EVENT_PROC) {
+                // Not recording process starts/stops.
+                continue;
+            }
             HashMap<String, SparseIntArray> active = mActiveEvents.getStateForEvent(i);
             if (active == null) {
                 continue;
@@ -6455,7 +6523,37 @@
             boolean reset = false;
             if (!mNoAutoReset && (oldStatus == BatteryManager.BATTERY_STATUS_FULL
                     || level >= 90
-                    || (mDischargeCurrentLevel < 20 && level >= 80))) {
+                    || getLowDischargeAmountSinceCharge() >= 60)
+                    || (getHighDischargeAmountSinceCharge() >= 60
+                            && mHistoryBuffer.dataSize() >= MAX_HISTORY_BUFFER)) {
+                // Before we write, collect a snapshot of the final aggregated
+                // stats to be reported in the next checkin.  Only do this if we have
+                // a sufficient amount of data to make it interesting.
+                if (getLowDischargeAmountSinceCharge() >= 20) {
+                    final Parcel parcel = Parcel.obtain();
+                    writeSummaryToParcel(parcel, true);
+                    BackgroundThread.getHandler().post(new Runnable() {
+                        @Override public void run() {
+                            synchronized (mCheckinFile) {
+                                FileOutputStream stream = null;
+                                try {
+                                    stream = mCheckinFile.startWrite();
+                                    stream.write(parcel.marshall());
+                                    stream.flush();
+                                    FileUtils.sync(stream);
+                                    stream.close();
+                                    mCheckinFile.finishWrite(stream);
+                                } catch (IOException e) {
+                                    Slog.w("BatteryStats",
+                                            "Error writing checkin battery statistics", e);
+                                    mCheckinFile.failWrite(stream);
+                                } finally {
+                                    parcel.recycle();
+                                }
+                            }
+                        }
+                    });
+                }
                 doWrite = true;
                 resetAllStatsLocked();
                 mDischargeStartLevel = level;
@@ -6465,6 +6563,8 @@
             mLastDischargeStepLevel = level;
             mMinDischargeStepLevel = level;
             mLastDischargeStepTime = -1;
+            mInitStepMode = mCurStepMode;
+            mModStepMode = 0;
             pullPendingStateUpdatesLocked();
             mHistoryCur.batteryLevel = (byte)level;
             mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
@@ -6504,6 +6604,8 @@
             mLastChargeStepLevel = level;
             mMaxChargeStepLevel = level;
             mLastChargeStepTime = -1;
+            mInitStepMode = mCurStepMode;
+            mModStepMode = 0;
         }
         if (doWrite || (mLastWriteTime + (60 * 1000)) < mSecRealtime) {
             if (mFile != null) {
@@ -6529,14 +6631,17 @@
     private static final int BATTERY_PLUGGED_NONE = 0;
 
     private static int addLevelSteps(long[] steps, int stepCount, long lastStepTime,
-            int numStepLevels, long elapsedRealtime) {
+            int numStepLevels, long modeBits, long elapsedRealtime) {
         if (lastStepTime >= 0 && numStepLevels > 0) {
             long duration = elapsedRealtime - lastStepTime;
             for (int i=0; i<numStepLevels; i++) {
                 System.arraycopy(steps, 0, steps, 1, steps.length-1);
                 long thisDuration = duration / (numStepLevels-i);
                 duration -= thisDuration;
-                steps[0] = thisDuration;
+                if (thisDuration > STEP_LEVEL_TIME_MASK) {
+                    thisDuration = STEP_LEVEL_TIME_MASK;
+                }
+                steps[0] = thisDuration | modeBits;
             }
             stepCount += numStepLevels;
             if (stepCount > steps.length) {
@@ -6623,23 +6728,30 @@
                 if (changed) {
                     addHistoryRecordLocked(elapsedRealtime, uptime);
                 }
+                long modeBits = (((long)mInitStepMode) << STEP_LEVEL_INITIAL_MODE_SHIFT)
+                        | (((long)mModStepMode) << STEP_LEVEL_MODIFIED_MODE_SHIFT)
+                        | (((long)(level&0xff)) << STEP_LEVEL_LEVEL_SHIFT);
                 if (onBattery) {
                     if (mLastDischargeStepLevel != level && mMinDischargeStepLevel > level) {
                         mNumDischargeStepDurations = addLevelSteps(mDischargeStepDurations,
                                 mNumDischargeStepDurations, mLastDischargeStepTime,
-                                mLastDischargeStepLevel - level, elapsedRealtime);
+                                mLastDischargeStepLevel - level, modeBits, elapsedRealtime);
                         mLastDischargeStepLevel = level;
                         mMinDischargeStepLevel = level;
                         mLastDischargeStepTime = elapsedRealtime;
+                        mInitStepMode = mCurStepMode;
+                        mModStepMode = 0;
                     }
                 } else {
                     if (mLastChargeStepLevel != level && mMaxChargeStepLevel < level) {
                         mNumChargeStepDurations = addLevelSteps(mChargeStepDurations,
                                 mNumChargeStepDurations, mLastChargeStepTime,
-                                level - mLastChargeStepLevel, elapsedRealtime);
+                                level - mLastChargeStepLevel, modeBits, elapsedRealtime);
                         mLastChargeStepLevel = level;
                         mMaxChargeStepLevel = level;
                         mLastChargeStepTime = elapsedRealtime;
+                        mInitStepMode = mCurStepMode;
+                        mModStepMode = 0;
                     }
                 }
             }
@@ -6863,7 +6975,7 @@
         }
         long total = 0;
         for (int i=0; i<numSteps; i++) {
-            total += steps[i];
+            total += steps[i] & STEP_LEVEL_TIME_MASK;
         }
         return total / numSteps;
         /*
@@ -6875,7 +6987,7 @@
             long totalTime = 0;
             int num = 0;
             for (int j=0; j<numToAverage && (i+j)<numSteps; j++) {
-                totalTime += steps[i+j];
+                totalTime += steps[i+j] & STEP_LEVEL_TIME_MASK;
                 num++;
             }
             buckets[numBuckets] = totalTime / num;
@@ -7213,7 +7325,7 @@
         }
 
         Parcel out = Parcel.obtain();
-        writeSummaryToParcel(out);
+        writeSummaryToParcel(out, true);
         mLastWriteTime = SystemClock.elapsedRealtime();
 
         if (mPendingWrite != null) {
@@ -7224,14 +7336,11 @@
         if (sync) {
             commitPendingDataToDisk();
         } else {
-            Thread thr = new Thread("BatteryStats-Write") {
-                @Override
-                public void run() {
-                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+            BackgroundThread.getHandler().post(new Runnable() {
+                @Override public void run() {
                     commitPendingDataToDisk();
                 }
-            };
-            thr.start();
+            });
         }
     }
 
@@ -7263,29 +7372,6 @@
         }
     }
 
-    static byte[] readFully(FileInputStream stream) throws java.io.IOException {
-        int pos = 0;
-        int avail = stream.available();
-        byte[] data = new byte[avail];
-        while (true) {
-            int amt = stream.read(data, pos, data.length-pos);
-            //Log.i("foo", "Read " + amt + " bytes at " + pos
-            //        + " of avail " + data.length);
-            if (amt <= 0) {
-                //Log.i("foo", "**** FINISHED READING: pos=" + pos
-                //        + " len=" + data.length);
-                return data;
-            }
-            pos += amt;
-            avail = stream.available();
-            if (avail > data.length-pos) {
-                byte[] newData = new byte[pos+avail];
-                System.arraycopy(data, 0, newData, 0, pos);
-                data = newData;
-            }
-        }
-    }
-
     public void readLocked() {
         if (mFile == null) {
             Slog.w("BatteryStats", "readLocked: no file associated with this instance");
@@ -7301,7 +7387,7 @@
             }
             FileInputStream stream = new FileInputStream(file);
 
-            byte[] raw = readFully(stream);
+            byte[] raw = BatteryStatsHelper.readFully(stream);
             Parcel in = Parcel.obtain();
             in.unmarshall(raw, 0, raw.length);
             in.setDataPosition(0);
@@ -7410,7 +7496,7 @@
         }
     }
 
-    void writeHistory(Parcel out, boolean andOldHistory) {
+    void writeHistory(Parcel out, boolean inclData, boolean andOldHistory) {
         if (DEBUG_HISTORY) {
             StringBuilder sb = new StringBuilder(128);
             sb.append("****************** WRITING mHistoryBaseTime: ");
@@ -7420,6 +7506,11 @@
             Slog.i(TAG, sb.toString());
         }
         out.writeLong(mHistoryBaseTime + mLastHistoryElapsedRealtime);
+        if (!inclData) {
+            out.writeInt(0);
+            out.writeInt(0);
+            return;
+        }
         out.writeInt(mHistoryTagPool.size());
         for (HashMap.Entry<HistoryTag, Integer> ent : mHistoryTagPool.entrySet()) {
             HistoryTag tag = ent.getKey();
@@ -7449,7 +7540,7 @@
         out.writeLong(-1);
     }
 
-    private void readSummaryFromParcel(Parcel in) {
+    public void readSummaryFromParcel(Parcel in) {
         final int version = in.readInt();
         if (version != VERSION) {
             Slog.w("BatteryStats", "readFromParcel: version got " + version
@@ -7742,7 +7833,7 @@
      *
      * @param out the Parcel to be written to.
      */
-    public void writeSummaryToParcel(Parcel out) {
+    public void writeSummaryToParcel(Parcel out, boolean inclHistory) {
         pullPendingStateUpdatesLocked();
 
         final long NOW_SYS = SystemClock.uptimeMillis() * 1000;
@@ -7750,7 +7841,7 @@
 
         out.writeInt(VERSION);
 
-        writeHistory(out, true);
+        writeHistory(out, inclHistory, true);
 
         out.writeInt(mStartCount);
         out.writeLong(computeUptime(NOW_SYS, STATS_SINCE_CHARGED));
@@ -8194,7 +8285,7 @@
 
         out.writeInt(MAGIC);
 
-        writeHistory(out, false);
+        writeHistory(out, true, false);
 
         out.writeInt(mStartCount);
         out.writeLong(mStartClockTime);
diff --git a/core/jni/android/graphics/MinikinUtils.cpp b/core/jni/android/graphics/MinikinUtils.cpp
index b00e1ab..f64ad7d 100644
--- a/core/jni/android/graphics/MinikinUtils.cpp
+++ b/core/jni/android/graphics/MinikinUtils.cpp
@@ -45,10 +45,9 @@
     FontStyle resolved = resolvedFace->fStyle;
 
     /* Prepare minikin FontStyle */
-    SkString langStr = paint->getPaintOptionsAndroid().getLanguage().getTag();
-    FontLanguage minikinLang(langStr.c_str(), langStr.size());
-    SkPaintOptionsAndroid::FontVariant var = paint->getPaintOptionsAndroid().getFontVariant();
-    FontVariant minikinVariant = var == SkPaintOptionsAndroid::kElegant_Variant ? VARIANT_ELEGANT
+    std::string lang = paint->getTextLocale();
+    FontLanguage minikinLang(lang.c_str(), lang.size());
+    FontVariant minikinVariant = (paint->getFontVariant() == VARIANT_ELEGANT) ? VARIANT_ELEGANT
             : VARIANT_COMPACT;
     FontStyle minikinStyle(minikinLang, minikinVariant, resolved.getWeight(), resolved.getItalic());
 
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index f6ced09..4bb31fc 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -365,27 +365,19 @@
         char langTag[ULOC_FULLNAME_CAPACITY];
         toLanguageTag(langTag, ULOC_FULLNAME_CAPACITY, localeChars.c_str());
 
-        SkPaintOptionsAndroid paintOpts = obj->getPaintOptionsAndroid();
-        paintOpts.setLanguage(langTag);
-        obj->setPaintOptionsAndroid(paintOpts);
+        obj->setTextLocale(langTag);
     }
 
     static jboolean isElegantTextHeight(JNIEnv* env, jobject paint) {
         NPE_CHECK_RETURN_ZERO(env, paint);
         Paint* obj = GraphicsJNI::getNativePaint(env, paint);
-        SkPaintOptionsAndroid paintOpts = obj->getPaintOptionsAndroid();
-        return paintOpts.getFontVariant() == SkPaintOptionsAndroid::kElegant_Variant;
+        return obj->getFontVariant() == VARIANT_ELEGANT;
     }
 
     static void setElegantTextHeight(JNIEnv* env, jobject paint, jboolean aa) {
         NPE_CHECK_RETURN_VOID(env, paint);
         Paint* obj = GraphicsJNI::getNativePaint(env, paint);
-        SkPaintOptionsAndroid::FontVariant variant =
-            aa ? SkPaintOptionsAndroid::kElegant_Variant :
-            SkPaintOptionsAndroid::kDefault_Variant;
-        SkPaintOptionsAndroid paintOpts = obj->getPaintOptionsAndroid();
-        paintOpts.setFontVariant(variant);
-        obj->setPaintOptionsAndroid(paintOpts);
+        obj->setFontVariant(aa ? VARIANT_ELEGANT : VARIANT_DEFAULT);
     }
 
     static jfloat getTextSize(JNIEnv* env, jobject paint) {
@@ -457,8 +449,7 @@
         // restore the original settings.
         paint->setTextSkewX(saveSkewX);
         paint->setFakeBoldText(savefakeBold);
-        SkPaintOptionsAndroid paintOpts = paint->getPaintOptionsAndroid();
-        if (paintOpts.getFontVariant() == SkPaintOptionsAndroid::kElegant_Variant) {
+        if (paint->getFontVariant() == VARIANT_ELEGANT) {
             SkScalar size = paint->getTextSize();
             metrics->fTop = -size * kElegantTop / 2048;
             metrics->fBottom = -size * kElegantBottom / 2048;
diff --git a/core/jni/android/graphics/Paint.h b/core/jni/android/graphics/Paint.h
index efc65be..a20bb4b 100644
--- a/core/jni/android/graphics/Paint.h
+++ b/core/jni/android/graphics/Paint.h
@@ -20,6 +20,8 @@
 #include <SkPaint.h>
 #include <string>
 
+#include <minikin/FontFamily.h>
+
 namespace android {
 
 class Paint : public SkPaint {
@@ -51,9 +53,27 @@
         return mFontFeatureSettings;
     }
 
+    void setTextLocale(const std::string &textLocale) {
+        mTextLocale = textLocale;
+    }
+
+    std::string getTextLocale() const {
+        return mTextLocale;
+    }
+
+    void setFontVariant(FontVariant variant) {
+        mFontVariant = variant;
+    }
+
+    FontVariant getFontVariant() const {
+        return mFontVariant;
+    }
+
 private:
     float mLetterSpacing;
     std::string mFontFeatureSettings;
+    std::string mTextLocale;
+    FontVariant mFontVariant;
 };
 
 }  // namespace android
diff --git a/core/jni/android/graphics/PaintImpl.cpp b/core/jni/android/graphics/PaintImpl.cpp
index 05020d2..e1b539e 100644
--- a/core/jni/android/graphics/PaintImpl.cpp
+++ b/core/jni/android/graphics/PaintImpl.cpp
@@ -23,11 +23,11 @@
 namespace android {
 
 Paint::Paint() : SkPaint(),
-        mLetterSpacing(0), mFontFeatureSettings() {
+        mLetterSpacing(0), mFontFeatureSettings(), mTextLocale(), mFontVariant(VARIANT_DEFAULT) {
 }
 
 Paint::Paint(const Paint& paint) : SkPaint(paint),
-        mLetterSpacing(0), mFontFeatureSettings() {
+        mLetterSpacing(0), mFontFeatureSettings(), mTextLocale(), mFontVariant(VARIANT_DEFAULT) {
 }
 
 Paint::~Paint() {
@@ -37,13 +37,17 @@
     SkPaint::operator=(other);
     mLetterSpacing = other.mLetterSpacing;
     mFontFeatureSettings = other.mFontFeatureSettings;
+    mTextLocale = other.mTextLocale;
+    mFontVariant = other.mFontVariant;
     return *this;
 }
 
 bool operator==(const Paint& a, const Paint& b) {
     return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b)
             && a.mLetterSpacing == b.mLetterSpacing
-            && a.mFontFeatureSettings == b.mFontFeatureSettings;
+            && a.mFontFeatureSettings == b.mFontFeatureSettings
+            && a.mTextLocale == b.mTextLocale
+            && a.mFontVariant == b.mFontVariant;
 }
 
 }
diff --git a/core/res/res/anim/launch_task_behind_source.xml b/core/res/res/anim/launch_task_behind_source.xml
index 426ee5d..cd3e30a 100644
--- a/core/res/res/anim/launch_task_behind_source.xml
+++ b/core/res/res/anim/launch_task_behind_source.xml
@@ -37,20 +37,20 @@
         android:interpolator="@interpolator/fast_out_slow_in"
         android:duration="350" />
 
-    <alpha android:fromAlpha="1.0" android:toAlpha="1.6"
+    <alpha android:fromAlpha="1.0" android:toAlpha="1.6666666666"
         android:fillEnabled="true" android:fillBefore="false" android:fillAfter="true"
         android:interpolator="@interpolator/decelerate_cubic"
         android:startOffset="433"
         android:duration="133"/>
 
-    <translate android:fromYDelta="0%" android:toYDelta="-10%"
+    <translate android:fromYDelta="0%" android:toYDelta="-8.8888888888%"
         android:fillEnabled="true" android:fillBefore="false" android:fillAfter="true"
         android:interpolator="@interpolator/decelerate_cubic"
         android:startOffset="433"
         android:duration="350"/>
 
-    <scale android:fromXScale="1.0" android:toXScale="1.1"
-        android:fromYScale="1.0" android:toYScale="1.1"
+    <scale android:fromXScale="1.0" android:toXScale="1.1111111111"
+        android:fromYScale="1.0" android:toYScale="1.1111111111"
         android:fillEnabled="true" android:fillBefore="false" android:fillAfter="true"
         android:pivotX="50%p" android:pivotY="50%p"
         android:interpolator="@interpolator/decelerate_cubic"
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 4761f1a..c34560d 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4134,6 +4134,10 @@
     <string name="time_picker_increment_set_pm_button">Set PM</string>
     <!-- Description of the button to decrease the TimePicker's set AM value. [CHAR LIMIT=NONE] -->
     <string name="time_picker_decrement_set_am_button">Set AM</string>
+    <!-- Label for the TimePicker's PM button. [CHAR LIMIT=2] -->
+    <string name="time_picker_pm_label">PM</string>
+    <!-- Label for the TimePicker's AM button. [CHAR LIMIT=2] -->
+    <string name="time_picker_am_label">AM</string>
 
     <!-- DatePicker - accessibility support -->
     <!-- Description of the button to increase the DatePicker's month value. [CHAR LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 84bc62c..6030715 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1969,9 +1969,7 @@
   <java-symbol type="array" name="config_cdma_home_system" />
   <java-symbol type="attr" name="headerSelectedTextColor" />
   <java-symbol type="attr" name="amPmSelectedBackgroundColor" />
-
-  <!--From SmsMessage-->
-  <!--Support decoding the user data payload as pack GSM 8-bit (a GSM alphabet
-     string that's stored in 8-bit unpacked format) characters.-->
   <java-symbol type="bool" name="config_sms_decode_gsm_8bit_data" />
+  <java-symbol type="string" name="time_picker_am_label" />
+  <java-symbol type="string" name="time_picker_pm_label" />
 </resources>
diff --git a/graphics/java/android/graphics/drawable/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
index 6ebb8b5..a6dbcb0 100644
--- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java
+++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
@@ -626,13 +626,18 @@
             mAutoMirrored = autoMirror;
 
             // Sanity check for valid padding when we have optical insets.
-            if (mPadding.left < mOpticalInsets.left) {
-                mPadding.left = mOpticalInsets.left;
-                mPadding.right = mOpticalInsets.right;
-            }
-            if (mPadding.top < mOpticalInsets.top) {
-                mPadding.top = mOpticalInsets.top;
-                mPadding.bottom = mOpticalInsets.bottom;
+            if (!opticalInsets.isEmpty()) {
+                if (mPadding == null) {
+                    mPadding = new Rect();
+                }
+                if (mPadding.left < opticalInsets.left) {
+                    mPadding.left = opticalInsets.left;
+                    mPadding.right = opticalInsets.right;
+                }
+                if (mPadding.top < opticalInsets.top) {
+                    mPadding.top = opticalInsets.top;
+                    mPadding.bottom = opticalInsets.bottom;
+                }
             }
         }
 
diff --git a/graphics/java/android/graphics/drawable/Ripple.java b/graphics/java/android/graphics/drawable/Ripple.java
index 6f21f2e..be2241b 100644
--- a/graphics/java/android/graphics/drawable/Ripple.java
+++ b/graphics/java/android/graphics/drawable/Ripple.java
@@ -276,7 +276,7 @@
     public void getBounds(Rect bounds) {
         final int outerX = (int) mOuterX;
         final int outerY = (int) mOuterY;
-        final int r = (int) mOuterRadius;
+        final int r = (int) mOuterRadius + 1;
         bounds.set(outerX - r, outerY - r, outerX + r, outerY + r);
     }
 
diff --git a/graphics/java/android/graphics/drawable/RippleBackground.java b/graphics/java/android/graphics/drawable/RippleBackground.java
index d404ccd..93df648 100644
--- a/graphics/java/android/graphics/drawable/RippleBackground.java
+++ b/graphics/java/android/graphics/drawable/RippleBackground.java
@@ -264,7 +264,7 @@
     public void getBounds(Rect bounds) {
         final int outerX = (int) mOuterX;
         final int outerY = (int) mOuterY;
-        final int r = (int) mOuterRadius;
+        final int r = (int) mOuterRadius + 1;
         bounds.set(outerX - r, outerY - r, outerX + r, outerY + r);
     }
 
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 705d9c0..a41d4da 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -4573,6 +4573,17 @@
                 outDevice = AudioSystem.DEVICE_OUT_USB_ACCESSORY;
                 setWiredDeviceConnectionState(outDevice, state, params);
             } else if (action.equals(Intent.ACTION_USB_AUDIO_DEVICE_PLUG)) {
+                // FIXME Does not yet handle the case where the setting is changed
+                // after device connection.  Ideally we should handle the settings change
+                // in SettingsObserver. Here we should log that a USB device is connected
+                // and disconnected with its address (card , device) and force the
+                // connection or disconnection when the setting changes.
+                int isDisabled = Settings.System.getInt(mContentResolver,
+                        Settings.System.USB_AUDIO_AUTOMATIC_ROUTING_DISABLED, 0);
+                if (isDisabled != 0) {
+                    return;
+                }
+
                 state = intent.getIntExtra("state", 0);
 
                 int alsaCard = intent.getIntExtra("card", -1);
diff --git a/media/java/android/media/projection/IMediaProjection.aidl b/media/java/android/media/projection/IMediaProjection.aidl
index dd84ad32..3d25aa6 100644
--- a/media/java/android/media/projection/IMediaProjection.aidl
+++ b/media/java/android/media/projection/IMediaProjection.aidl
@@ -25,7 +25,7 @@
     boolean canProjectAudio();
     boolean canProjectVideo();
     boolean canProjectSecureVideo();
-    int getVirtualDisplayFlags();
+    int applyVirtualDisplayFlags(int flags);
     void addCallback(IMediaProjectionCallback callback);
     void removeCallback(IMediaProjectionCallback callback);
 }
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index 7c03171..861039d 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -93,6 +93,19 @@
     }
 
     /**
+     * @hide
+     */
+    public VirtualDisplay createVirtualDisplay(@NonNull String name,
+            int width, int height, int dpi, boolean isSecure, @Nullable Surface surface,
+            @Nullable VirtualDisplay.Callbacks callbacks, @Nullable Handler handler) {
+        DisplayManager dm = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
+        int flags = isSecure ? DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE : 0;
+        return dm.createVirtualDisplay(this, name, width, height, dpi, surface,
+                    flags | DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR |
+                    DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION, callbacks, handler);
+    }
+
+    /**
      * Creates a {@link android.hardware.display.VirtualDisplay} to capture the
      * contents of the screen.
      *
@@ -105,9 +118,8 @@
      * than 0.
      * @param surface The surface to which the content of the virtual display
      * should be rendered, or null if there is none initially.
-     * @param isSecure Whether the display should be considered a secure
-     * display. This typically requires special permissions not available to
-     * third party applications.
+     * @param flags A combination of virtual display flags. See {@link DisplayManager} for the full
+     * list of flags.
      * @param callbacks Callbacks to call when the virtual display's state
      * changes, or null if none.
      * @param handler The {@link android.os.Handler} on which the callback should be
@@ -118,10 +130,9 @@
      * String, int, int, int, int, Surface, VirtualDisplay.Callbacks, Handler)
      */
     public VirtualDisplay createVirtualDisplay(@NonNull String name,
-            int width, int height, int dpi, boolean isSecure, @Nullable Surface surface,
+            int width, int height, int dpi, int flags, @Nullable Surface surface,
             @Nullable VirtualDisplay.Callbacks callbacks, @Nullable Handler handler) {
         DisplayManager dm = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
-        int flags = isSecure ? DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE : 0;
         return dm.createVirtualDisplay(
                     this, name, width, height, dpi, surface, flags, callbacks, handler);
     }
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index 5a0ea0d..a826957 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -20,6 +20,7 @@
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.ContentUris;
+import android.media.tv.TvContract.Programs;
 import android.net.Uri;
 import android.provider.BaseColumns;
 import android.util.ArraySet;
@@ -684,10 +685,11 @@
          * The flag indicating whether this TV channel is searchable or not.
          * <p>
          * In some regions, it is not allowed to surface search results for a given channel without
-         * broadcaster's consent. This is used to impose such restriction. A value of 1 indicates
-         * the channel is searchable and can be included in search results, a value of 0 indicates
-         * the channel and its TV programs are hidden from search. If not specified, this value is
-         * set to 1 (searchable) by default.
+         * broadcaster's consent. This is used to impose such restriction. Channels marked with
+         * "not searchable" cannot be used by other services except for the system service that
+         * shows the TV content. A value of 1 indicates the channel is searchable and can be
+         * included in search results, a value of 0 indicates the channel and its TV programs are
+         * hidden from search. If not specified, this value is set to 1 (searchable) by default.
          * </p><p>
          * Type: INTEGER (boolean)
          * </p>
diff --git a/packages/SystemUI/res/layout/recents_task_view.xml b/packages/SystemUI/res/layout/recents_task_view.xml
index f8dfd65..4a5fffe 100644
--- a/packages/SystemUI/res/layout/recents_task_view.xml
+++ b/packages/SystemUI/res/layout/recents_task_view.xml
@@ -18,11 +18,11 @@
     android:layout_width="match_parent" 
     android:layout_height="match_parent"
     android:focusable="true">
-    <com.android.systemui.recents.views.TaskThumbnailView
+    <com.android.systemui.recents.views.TaskViewThumbnail
         android:id="@+id/task_view_thumbnail"
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
-    <com.android.systemui.recents.views.TaskBarView
+    <com.android.systemui.recents.views.TaskViewHeader
         android:id="@+id/task_view_bar"
         android:layout_width="match_parent"
         android:layout_height="@dimen/recents_task_bar_height"
@@ -61,7 +61,7 @@
             android:background="@drawable/recents_button_bg"
             android:visibility="invisible"
             android:src="@drawable/recents_dismiss_light" />
-    </com.android.systemui.recents.views.TaskBarView>
+    </com.android.systemui.recents.views.TaskViewHeader>
     <FrameLayout
         android:id="@+id/lock_to_app_fab"
         android:layout_width="48dp"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 11a8063..4a4fa41 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -237,6 +237,9 @@
          in dps over one second of time. -->
     <dimen name="recents_animation_movement_in_dps_per_second">800dp</dimen>
 
+    <!-- The min alpha to apply to a task affiliation group color. -->
+    <item name="recents_task_affiliation_color_min_alpha_percentage" format="float" type="dimen">0.6</item>
+
     <!-- Space reserved for the cards behind the top card in the top stack -->
     <dimen name="top_stack_peek_amount">12dp</dimen>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 97efb47..51633dc 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -768,13 +768,13 @@
     <string name="disconnect_vpn">Disconnect VPN</string>
 
     <!-- Monitoring dialog device owner body text [CHAR LIMIT=300] -->
-    <string name="monitoring_description_device_owned">This device is managed by:\n<xliff:g id="organization">%1$s</xliff:g>\n\nYour administrator can monitor your network activity, including emails, apps and secure websites.\n\nFor more information, contact your administrator.</string>
+    <string name="monitoring_description_device_owned">This device is managed by:\n<xliff:g id="organization">%1$s</xliff:g>\n\nYour administrator can monitor your device and network activity, including emails, apps and secure websites.\n\nFor more information, contact your administrator.</string>
 
     <!-- Monitoring dialog non-legacy VPN text [CHAR LIMIT=300] -->
-    <string name="monitoring_description_vpn">You gave \"<xliff:g id="application">%1$s</xliff:g>\" permission to set up a VPN connection.\n\nThis app can monitor your network activity, including emails, apps and secure websites.</string>
+    <string name="monitoring_description_vpn">You gave \"<xliff:g id="application">%1$s</xliff:g>\" permission to set up a VPN connection.\n\nThis app can monitor your device and network activity, including emails, apps and secure websites.</string>
 
     <!-- Monitoring dialog legacy VPN text [CHAR LIMIT=300] -->
-    <string name="monitoring_description_legacy_vpn">You\'re connected to a VPN (\"<xliff:g id="application">%1$s</xliff:g>\").\n\nYour VPN service provider can monitor your network activity including emails, apps, and secure websites.</string>
+    <string name="monitoring_description_legacy_vpn">You\'re connected to a VPN (\"<xliff:g id="application">%1$s</xliff:g>\").\n\nYour VPN service provider can monitor your device and network activity including emails, apps, and secure websites.</string>
 
     <!-- Monitoring dialog non-legacy VPN with device owner text [CHAR LIMIT=300] -->
     <string name="monitoring_description_vpn_device_owned">This device is managed by:\n<xliff:g id="organization">%1$s</xliff:g>\n\nYour administrator is capable of monitoring your network activity including emails, apps, and secure websites. For more information, contact your administrator.\n\nAlso, you gave \"<xliff:g id="application">%2$s</xliff:g>\" permission to set up a VPN connection. This app can monitor network activity too.</string>
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index cf0a1dc..980a4ae 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -86,6 +86,7 @@
     public int taskBarViewLightTextColor;
     public int taskBarViewDarkTextColor;
     public int taskBarViewHighlightColor;
+    public float taskBarViewAffiliationColorMinAlpha;
 
     /** Task bar size & animations */
     public int taskBarHeight;
@@ -221,6 +222,9 @@
                 res.getColor(R.color.recents_task_bar_dark_text_color);
         taskBarViewHighlightColor =
                 res.getColor(R.color.recents_task_bar_highlight_color);
+        TypedValue affMinAlphaPctValue = new TypedValue();
+        res.getValue(R.dimen.recents_task_affiliation_color_min_alpha_percentage, affMinAlphaPctValue, true);
+        taskBarViewAffiliationColorMinAlpha = affMinAlphaPctValue.getFloat();
 
         // Task bar size & animations
         taskBarHeight = res.getDimensionPixelSize(R.dimen.recents_task_bar_height);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index f30e22a..8716184 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -339,8 +339,9 @@
 
             // Create a new task
             Task task = new Task(t.persistentId, (t.id > -1), t.baseIntent, t.affiliatedTaskId,
-                    activityLabel, activityIcon, activityColor, t.userId, t.firstActiveTime,
-                    t.lastActiveTime, (i == (taskCount - 1)), config.lockToAppEnabled);
+                    t.affiliatedTaskColor, activityLabel, activityIcon, activityColor, t.userId,
+                    t.firstActiveTime, t.lastActiveTime, (i == (taskCount - 1)),
+                    config.lockToAppEnabled);
 
             // Preload the specified number of apps
             if (i >= (taskCount - preloadCount)) {
@@ -381,7 +382,7 @@
         }
 
         // Simulate the groupings that we describe
-        stack.createAffiliatedGroupings();
+        stack.createAffiliatedGroupings(config);
 
         // Start the task loader and add all the tasks we need to load
         mLoader.start(context);
@@ -405,11 +406,11 @@
             ActivityInfo info = ssp.getActivityInfo(t.baseIntent.getComponent(), t.userId);
             if (info == null) continue;
 
-            stack.addTask(new Task(t.persistentId, true, t.baseIntent, t.affiliatedTaskId, null,
-                    null, 0, 0, t.firstActiveTime, t.lastActiveTime, (i == (taskCount - 1)),
-                    config.lockToAppEnabled));
+            stack.addTask(new Task(t.persistentId, true, t.baseIntent, t.affiliatedTaskId,
+                    t.affiliatedTaskColor, null, null, 0, 0, t.firstActiveTime, t.lastActiveTime,
+                    (i == (taskCount - 1)), config.lockToAppEnabled));
         }
-        stack.createAffiliatedGroupings();
+        stack.createAffiliatedGroupings(config);
         return stack;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index 41874fc..f6c3a7e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -77,6 +77,7 @@
     public TaskKey key;
     public TaskGrouping group;
     public int taskAffiliation;
+    public int taskAffiliationColor;
     public boolean isLaunchTarget;
     public Drawable applicationIcon;
     public Drawable activityIcon;
@@ -95,16 +96,19 @@
         // Only used by RecentsService for task rect calculations.
     }
 
-    public Task(int id, boolean isActive, Intent intent, int taskAffiliation, String activityTitle,
-                Drawable activityIcon, int colorPrimary, int userId,
+    public Task(int id, boolean isActive, Intent intent, int taskAffiliation, int taskAffiliationColor,
+                String activityTitle, Drawable activityIcon, int colorPrimary, int userId,
                 long firstActiveTime, long lastActiveTime, boolean lockToThisTask,
                 boolean lockToTaskEnabled) {
+        boolean isInAffiliationGroup = (taskAffiliation != id);
+        boolean hasAffiliationGroupColor = isInAffiliationGroup && (taskAffiliationColor != 0);
         this.key = new TaskKey(id, intent, userId, firstActiveTime, lastActiveTime);
         this.taskAffiliation = taskAffiliation;
+        this.taskAffiliationColor = taskAffiliationColor;
         this.activityLabel = activityTitle;
         this.activityIcon = activityIcon;
-        this.colorPrimary = colorPrimary;
-        this.useLightOnPrimaryColor = Utilities.computeContrastBetweenColors(colorPrimary,
+        this.colorPrimary = hasAffiliationGroupColor ? taskAffiliationColor : colorPrimary;
+        this.useLightOnPrimaryColor = Utilities.computeContrastBetweenColors(this.colorPrimary,
                 Color.WHITE) > 3f;
         this.isActive = isActive;
         this.lockToThisTask = lockToTaskEnabled && lockToThisTask;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index fd6303f..435eb42 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -16,7 +16,9 @@
 
 package com.android.systemui.recents.model;
 
+import android.graphics.Color;
 import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.misc.NamedCounter;
 
 import java.util.ArrayList;
@@ -320,7 +322,7 @@
     /**
      * Temporary: This method will simulate affiliation groups by
      */
-    public void createAffiliatedGroupings() {
+    public void createAffiliatedGroupings(RecentsConfiguration config) {
         if (Constants.DebugFlags.App.EnableSimulatedTaskGroups) {
             HashMap<Task.TaskKey, Task> taskMap = new HashMap<Task.TaskKey, Task>();
             // Sort all tasks by increasing firstActiveTime of the task
@@ -387,6 +389,7 @@
             mTaskList.set(tasks);
         } else {
             // Create the task groups
+            HashMap<Task.TaskKey, Task> tasksMap = new HashMap<Task.TaskKey, Task>();
             ArrayList<Task> tasks = mTaskList.getTasks();
             int taskCount = tasks.size();
             for (int i = 0; i < taskCount; i++) {
@@ -401,6 +404,28 @@
                     addGroup(group);
                 }
                 group.addTask(t);
+                tasksMap.put(t.key, t);
+            }
+            // Update the task colors for each of the groups
+            float minAlpha = config.taskBarViewAffiliationColorMinAlpha;
+            int taskGroupCount = mGroups.size();
+            for (int i = 0; i < taskGroupCount; i++) {
+                TaskGrouping group = mGroups.get(i);
+                taskCount = group.getTaskCount();
+                // Ignore the groups that only have one task
+                if (taskCount <= 1) continue;
+                // Calculate the group color distribution
+                int affiliationColor = tasksMap.get(group.mTaskKeys.get(0)).taskAffiliationColor;
+                float alphaStep = (1f - minAlpha) / taskCount;
+                float alpha = 1f;
+                for (int j = 0; j < taskCount; j++) {
+                    Task t = tasksMap.get(group.mTaskKeys.get(j));
+                    t.colorPrimary = Color.rgb(
+                            (int) (alpha * Color.red(affiliationColor) + (1f - alpha) * 0xFF),
+                            (int) (alpha * Color.green(affiliationColor) + (1f - alpha) * 0xFF),
+                            (int) (alpha * Color.blue(affiliationColor) + (1f - alpha) * 0xFF));
+                    alpha -= alphaStep;
+                }
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 57f1274..9e7dbf4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -330,7 +330,7 @@
                         // We can reuse the current task transforms to find the task rects
                         TaskViewTransform transform = mCurrentTaskTransforms.get(mStack.indexOfTask(tv.getTask()));
                         TaskViewTransform nextTransform = mCurrentTaskTransforms.get(mStack.indexOfTask(nextTv.getTask()));
-                        clipBottom = transform.rect.bottom - nextTransform.rect.top - 200;
+                        clipBottom = transform.rect.bottom - nextTransform.rect.top;
                     }
                 }
                 tv.getViewBounds().setClipBottom(clipBottom);
@@ -430,9 +430,8 @@
     public void computeScroll() {
         mStackScroller.computeScroll();
         // Synchronize the views
-        if (synchronizeStackViewsWithModel()) {
-            clipTaskViews();
-        }
+        synchronizeStackViewsWithModel();
+        clipTaskViews();
     }
 
     /** Computes the stack and task rects */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index abf3c50..3b9bcc4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -39,7 +39,7 @@
 
 /* A task view */
 public class TaskView extends FrameLayout implements Task.TaskCallbacks,
-        TaskFooterView.TaskFooterViewCallbacks, View.OnClickListener, View.OnLongClickListener {
+        TaskViewFooter.TaskFooterViewCallbacks, View.OnClickListener, View.OnLongClickListener {
     /** The TaskView callbacks */
     interface TaskViewCallbacks {
         public void onTaskViewAppIconClicked(TaskView tv);
@@ -67,9 +67,9 @@
     AnimateableViewBounds mViewBounds;
     Paint mLayerPaint = new Paint();
 
-    TaskThumbnailView mThumbnailView;
-    TaskBarView mBarView;
-    TaskFooterView mFooterView;
+    TaskViewThumbnail mThumbnailView;
+    TaskViewHeader mBarView;
+    TaskViewFooter mFooterView;
     View mActionButtonView;
     TaskViewCallbacks mCb;
 
@@ -124,8 +124,8 @@
     @Override
     protected void onFinishInflate() {
         // Bind the views
-        mBarView = (TaskBarView) findViewById(R.id.task_view_bar);
-        mThumbnailView = (TaskThumbnailView) findViewById(R.id.task_view_thumbnail);
+        mBarView = (TaskViewHeader) findViewById(R.id.task_view_bar);
+        mThumbnailView = (TaskViewThumbnail) findViewById(R.id.task_view_thumbnail);
         mActionButtonView = findViewById(R.id.lock_to_app_fab);
         if (mFooterView != null) {
             mFooterView.setCallbacks(this);
@@ -712,7 +712,7 @@
         setOnClickListener(enabled ? this : null);
     }
 
-    /**** TaskFooterView.TaskFooterViewCallbacks ****/
+    /**** TaskViewFooter.TaskFooterViewCallbacks ****/
 
     @Override
     public void onTaskFooterHeightChanged(int height, int maxHeight) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskFooterView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewFooter.java
similarity index 90%
rename from packages/SystemUI/src/com/android/systemui/recents/views/TaskFooterView.java
rename to packages/SystemUI/src/com/android/systemui/recents/views/TaskViewFooter.java
index 881bbcf..324169e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskFooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewFooter.java
@@ -24,7 +24,7 @@
 
 
 /** The task footer view */
-public class TaskFooterView extends FrameLayout {
+public class TaskViewFooter extends FrameLayout {
 
     interface TaskFooterViewCallbacks {
         public void onTaskFooterHeightChanged(int height, int maxHeight);
@@ -37,19 +37,19 @@
     int mMaxFooterHeight;
     ObjectAnimator mFooterAnimator;
 
-    public TaskFooterView(Context context) {
+    public TaskViewFooter(Context context) {
         this(context, null);
     }
 
-    public TaskFooterView(Context context, AttributeSet attrs) {
+    public TaskViewFooter(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
 
-    public TaskFooterView(Context context, AttributeSet attrs, int defStyleAttr) {
+    public TaskViewFooter(Context context, AttributeSet attrs, int defStyleAttr) {
         this(context, attrs, defStyleAttr, 0);
     }
 
-    public TaskFooterView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+    public TaskViewFooter(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
         mConfig = RecentsConfiguration.getInstance();
         mMaxFooterHeight = mConfig.taskViewLockToAppButtonHeight;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
rename to packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index 90bf12f..03fc16e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -28,7 +28,6 @@
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewPropertyAnimator;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.TextView;
@@ -39,7 +38,7 @@
 
 
 /* The task bar view */
-class TaskBarView extends FrameLayout {
+class TaskViewHeader extends FrameLayout {
 
     RecentsConfiguration mConfig;
 
@@ -54,19 +53,19 @@
 
     static Paint sHighlightPaint;
 
-    public TaskBarView(Context context) {
+    public TaskViewHeader(Context context) {
         this(context, null);
     }
 
-    public TaskBarView(Context context, AttributeSet attrs) {
+    public TaskViewHeader(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
 
-    public TaskBarView(Context context, AttributeSet attrs, int defStyleAttr) {
+    public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr) {
         this(context, attrs, defStyleAttr, 0);
     }
 
-    public TaskBarView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+    public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
         mConfig = RecentsConfiguration.getInstance();
         setWillNotDraw(false);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskThumbnailView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
similarity index 90%
rename from packages/SystemUI/src/com/android/systemui/recents/views/TaskThumbnailView.java
rename to packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
index 1116d51..f836aa3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskThumbnailView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
@@ -25,24 +25,24 @@
 
 
 /** The task thumbnail view */
-public class TaskThumbnailView extends FixedSizeImageView {
+public class TaskViewThumbnail extends FixedSizeImageView {
 
     // Task bar clipping
     Rect mClipRect = new Rect();
 
-    public TaskThumbnailView(Context context) {
+    public TaskViewThumbnail(Context context) {
         this(context, null);
     }
 
-    public TaskThumbnailView(Context context, AttributeSet attrs) {
+    public TaskViewThumbnail(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
 
-    public TaskThumbnailView(Context context, AttributeSet attrs, int defStyleAttr) {
+    public TaskViewThumbnail(Context context, AttributeSet attrs, int defStyleAttr) {
         this(context, attrs, defStyleAttr, 0);
     }
 
-    public TaskThumbnailView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+    public TaskViewThumbnail(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
         setScaleType(ScaleType.FIT_XY);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 8996197..3b14082 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -82,6 +82,7 @@
 
     public void hide(boolean destroyView) {
         if (mKeyguardView != null) {
+            mKeyguardView.setOnDismissAction(null);
             mKeyguardView.cleanUp();
         }
         if (destroyView) {
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index ff3cd9d..964acbd 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -1314,7 +1314,6 @@
             mBackgroundDrawable = drawable;
             if (mDecor != null) {
                 mDecor.setWindowBackground(drawable);
-                mDecor.setClipToOutline(drawable != null && mClipToOutline);
             }
         }
     }
@@ -3389,10 +3388,6 @@
             }
             mDecor.setWindowBackground(background);
 
-            if (background != null) {
-                mDecor.setClipToOutline(mClipToOutline);
-            }
-
             final Drawable frame;
             if (mFrameResource != 0) {
                 frame = getContext().getDrawable(mFrameResource);
@@ -3402,6 +3397,7 @@
             mDecor.setWindowFrame(frame);
 
             mDecor.setElevation(mElevation);
+            mDecor.setClipToOutline(mClipToOutline);
 
             if (mTitle != null) {
                 setTitle(mTitle);
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 59aef32..77b14ac 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -5129,6 +5129,14 @@
                 }
             }
 
+            // The path needs to be canonical
+            if (info.path.contains("..") || info.path.contains("//")) {
+                if (MORE_DEBUG) {
+                    Slog.w(TAG, "Dropping invalid path " + info.path);
+                }
+                return false;
+            }
+
             // Otherwise we think this file is good to go
             return true;
         }
@@ -5680,6 +5688,14 @@
                                 break;
                         }
 
+                        // The path needs to be canonical
+                        if (info.path.contains("..") || info.path.contains("//")) {
+                            if (MORE_DEBUG) {
+                                Slog.w(TAG, "Dropping invalid path " + info.path);
+                            }
+                            okay = false;
+                        }
+
                         // If the policy is satisfied, go ahead and set up to pipe the
                         // data to the agent.
                         if (DEBUG && okay && mAgent != null) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index e0d4aad..c7c7a92 100755
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2200,8 +2200,7 @@
         File dataDir = Environment.getDataDirectory();
         File systemDir = new File(dataDir, "system");
         systemDir.mkdirs();
-        mBatteryStatsService = new BatteryStatsService(new File(
-                systemDir, "batterystats.bin").toString(), mHandler);
+        mBatteryStatsService = new BatteryStatsService(systemDir, mHandler);
         mBatteryStatsService.getActiveStatistics().readLocked();
         mBatteryStatsService.getActiveStatistics().writeAsyncLocked();
         mOnBattery = DEBUG_POWER ? true
@@ -7471,6 +7470,7 @@
         rti.firstActiveTime = tr.firstActiveTime;
         rti.lastActiveTime = tr.lastActiveTime;
         rti.affiliatedTaskId = tr.mAffiliatedTaskId;
+        rti.affiliatedTaskColor = tr.mAffiliatedTaskColor;
         return rti;
     }
 
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 3fdeb54..d8da700 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -27,6 +27,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
 import android.os.PowerManagerInternal;
 import android.os.Process;
 import android.os.ServiceManager;
@@ -42,7 +43,10 @@
 import com.android.internal.os.PowerProfile;
 import com.android.server.LocalServices;
 
+import java.io.File;
 import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.List;
 
@@ -62,8 +66,8 @@
     private BluetoothHeadset mBluetoothHeadset;
     PowerManagerInternal mPowerManagerInternal;
 
-    BatteryStatsService(String filename, Handler handler) {
-        mStats = new BatteryStatsImpl(filename, handler);
+    BatteryStatsService(File systemDir, Handler handler) {
+        mStats = new BatteryStatsImpl(systemDir, handler);
     }
     
     public void publish(Context context) {
@@ -117,7 +121,7 @@
     public BatteryStatsImpl getActiveStatistics() {
         return mStats;
     }
-    
+
     public byte[] getStatistics() {
         mContext.enforceCallingPermission(
                 android.Manifest.permission.BATTERY_STATS, null);
@@ -129,7 +133,24 @@
         out.recycle();
         return data;
     }
-    
+
+    public ParcelFileDescriptor getStatisticsStream() {
+        mContext.enforceCallingPermission(
+                android.Manifest.permission.BATTERY_STATS, null);
+        //Slog.i("foo", "SENDING BATTERY INFO:");
+        //mStats.dumpLocked(new LogPrinter(Log.INFO, "foo", Log.LOG_ID_SYSTEM));
+        Parcel out = Parcel.obtain();
+        mStats.writeToParcel(out, 0);
+        byte[] data = out.marshall();
+        out.recycle();
+        try {
+            return ParcelFileDescriptor.fromData(data, "battery-stats");
+        } catch (IOException e) {
+            Slog.w(TAG, "Unable to create shared memory", e);
+            return null;
+        }
+    }
+
     public long computeBatteryTimeRemaining() {
         synchronized (mStats) {
             long time = mStats.computeBatteryTimeRemaining(SystemClock.elapsedRealtime());
@@ -726,7 +747,8 @@
         pw.println("  enable|disable <option>");
         pw.println("    Enable or disable a running option.  Option state is not saved across boots.");
         pw.println("    Options are:");
-        pw.println("      full-wake-history: include wake_lock_in battery history, full wake details.");
+        pw.println("      full-history: include additional detailed events in battery history:");
+        pw.println("          wake_lock_in and proc events");
         pw.println("      no-auto-reset: don't automatically reset stats when unplugged");
     }
 
@@ -737,9 +759,9 @@
             dumpHelp(pw);
             return -1;
         }
-        if ("full-wake-history".equals(args[i])) {
+        if ("full-wake-history".equals(args[i]) || "full-history".equals(args[i])) {
             synchronized (mStats) {
-                mStats.setRecordAllWakeLocksLocked(enable);
+                mStats.setRecordAllHistoryLocked(enable);
             }
         } else if ("no-auto-reset".equals(args[i])) {
             synchronized (mStats) {
@@ -764,7 +786,8 @@
         }
 
         int flags = 0;
-        boolean isCheckin = false;
+        boolean useCheckinFormat = false;
+        boolean isRealCheckin = false;
         boolean noOutput = false;
         boolean writeData = false;
         long historyStart = -1;
@@ -773,7 +796,8 @@
             for (int i=0; i<args.length; i++) {
                 String arg = args[i];
                 if ("--checkin".equals(arg)) {
-                    isCheckin = true;
+                    useCheckinFormat = true;
+                    isRealCheckin = true;
                 } else if ("--history".equals(arg)) {
                     flags |= BatteryStats.DUMP_HISTORY_ONLY;
                 } else if ("--history-start".equals(arg)) {
@@ -787,7 +811,7 @@
                     historyStart = Long.parseLong(args[i]);
                     writeData = true;
                 } else if ("-c".equals(arg)) {
-                    isCheckin = true;
+                    useCheckinFormat = true;
                     flags |= BatteryStats.DUMP_INCLUDE_HISTORY;
                 } else if ("--unplugged".equals(arg)) {
                     flags |= BatteryStats.DUMP_UNPLUGGED_ONLY;
@@ -844,8 +868,35 @@
         if (noOutput) {
             return;
         }
-        if (isCheckin) {
+        if (useCheckinFormat) {
             List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications(0);
+            if (isRealCheckin) {
+                // For a real checkin, first we want to prefer to use the last complete checkin
+                // file if there is one.
+                synchronized (mStats.mCheckinFile) {
+                    if (mStats.mCheckinFile.exists()) {
+                        try {
+                            byte[] raw = mStats.mCheckinFile.readFully();
+                            if (raw != null) {
+                                Parcel in = Parcel.obtain();
+                                in.unmarshall(raw, 0, raw.length);
+                                in.setDataPosition(0);
+                                BatteryStatsImpl checkinStats = new BatteryStatsImpl(
+                                        null, mStats.mHandler);
+                                checkinStats.readSummaryFromParcel(in);
+                                in.recycle();
+                                checkinStats.dumpCheckinLocked(mContext, pw, apps, flags,
+                                        historyStart);
+                                mStats.mCheckinFile.delete();
+                                return;
+                            }
+                        } catch (IOException e) {
+                            Slog.w(TAG, "Failure reading checkin file "
+                                    + mStats.mCheckinFile.getBaseFile(), e);
+                        }
+                    }
+                }
+            }
             synchronized (mStats) {
                 mStats.dumpCheckinLocked(mContext, pw, apps, flags, historyStart);
                 if (writeData) {
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 4a84941..d0ec106 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -68,6 +68,7 @@
     private static final String ATTR_TASK_AFFILIATION = "task_affiliation";
     private static final String ATTR_PREV_AFFILIATION = "prev_affiliation";
     private static final String ATTR_NEXT_AFFILIATION = "next_affiliation";
+    private static final String ATTR_TASK_AFFILIATION_COLOR = "task_affiliation_color";
     private static final String ATTR_CALLING_UID = "calling_uid";
     private static final String ATTR_CALLING_PACKAGE = "calling_package";
     private static final String LAST_ACTIVITY_ICON_SUFFIX = "_last_activity_icon_";
@@ -140,6 +141,7 @@
     CharSequence lastDescription; // Last description captured for this item.
 
     int mAffiliatedTaskId; // taskId of parent affiliation or self if no parent.
+    int mAffiliatedTaskColor; // color of the parent task affiliation.
     TaskRecord mPrevAffiliate; // previous task in affiliated chain.
     int mPrevAffiliateTaskId = -1; // previous id for persistence.
     TaskRecord mNextAffiliate; // next task in affiliated chain.
@@ -172,7 +174,8 @@
             String _lastDescription, ArrayList<ActivityRecord> activities, long _firstActiveTime,
             long _lastActiveTime, long lastTimeMoved, boolean neverRelinquishIdentity,
             ActivityManager.TaskDescription _lastTaskDescription, int taskAffiliation,
-            int prevTaskId, int nextTaskId, int callingUid, String callingPackage) {
+            int prevTaskId, int nextTaskId, int taskAffiliationColor, int callingUid,
+            String callingPackage) {
         mService = service;
         mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
                 TaskPersister.IMAGE_EXTENSION;
@@ -199,6 +202,7 @@
         mNeverRelinquishIdentity = neverRelinquishIdentity;
         lastTaskDescription = _lastTaskDescription;
         mAffiliatedTaskId = taskAffiliation;
+        mAffiliatedTaskColor = taskAffiliationColor;
         mPrevAffiliateTaskId = prevTaskId;
         mNextAffiliateTaskId = nextTaskId;
         mCallingUid = callingUid;
@@ -326,6 +330,7 @@
     void setTaskToAffiliateWith(TaskRecord taskToAffiliateWith) {
         closeRecentsChain();
         mAffiliatedTaskId = taskToAffiliateWith.mAffiliatedTaskId;
+        mAffiliatedTaskColor = taskToAffiliateWith.mAffiliatedTaskColor;
         // Find the end
         while (taskToAffiliateWith.mNextAffiliate != null) {
             final TaskRecord nextRecents = taskToAffiliateWith.mNextAffiliate;
@@ -689,11 +694,14 @@
                     }
                     if (colorPrimary == 0) {
                         colorPrimary = r.taskDescription.getPrimaryColor();
-
                     }
                 }
             }
             lastTaskDescription = new ActivityManager.TaskDescription(label, icon, colorPrimary);
+            // Update the task affiliation color if we are the parent of the group
+            if (taskId == mAffiliatedTaskId) {
+                mAffiliatedTaskColor = lastTaskDescription.getPrimaryColor();
+            }
         }
     }
 
@@ -777,6 +785,7 @@
             saveTaskDescription(lastTaskDescription, String.valueOf(taskId) +
                     LAST_ACTIVITY_ICON_SUFFIX + lastActiveTime, out);
         }
+        out.attribute(null, ATTR_TASK_AFFILIATION_COLOR, String.valueOf(mAffiliatedTaskColor));
         out.attribute(null, ATTR_TASK_AFFILIATION, String.valueOf(mAffiliatedTaskId));
         out.attribute(null, ATTR_PREV_AFFILIATION, String.valueOf(mPrevAffiliateTaskId));
         out.attribute(null, ATTR_NEXT_AFFILIATION, String.valueOf(mNextAffiliateTaskId));
@@ -831,6 +840,7 @@
         final int outerDepth = in.getDepth();
         ActivityManager.TaskDescription taskDescription = new ActivityManager.TaskDescription();
         int taskAffiliation = -1;
+        int taskAffiliationColor = 0;
         int prevTaskId = -1;
         int nextTaskId = -1;
         int callingUid = -1;
@@ -877,6 +887,8 @@
                 prevTaskId = Integer.valueOf(attrValue);
             } else if (ATTR_NEXT_AFFILIATION.equals(attrName)) {
                 nextTaskId = Integer.valueOf(attrValue);
+            } else if (ATTR_TASK_AFFILIATION_COLOR.equals(attrName)) {
+                taskAffiliationColor = Integer.valueOf(attrValue);
             } else if (ATTR_CALLING_UID.equals(attrName)) {
                 callingUid = Integer.valueOf(attrValue);
             } else if (ATTR_CALLING_PACKAGE.equals(attrName)) {
@@ -921,8 +933,8 @@
                 affinityIntent, affinity, realActivity, origActivity, rootHasReset,
                 autoRemoveRecents, askedCompatMode, taskType, userId, lastDescription, activities,
                 firstActiveTime, lastActiveTime, lastTimeOnTop, neverRelinquishIdentity,
-                taskDescription, taskAffiliation, prevTaskId, nextTaskId, callingUid,
-                callingPackage);
+                taskDescription, taskAffiliation, prevTaskId, nextTaskId, taskAffiliationColor,
+                callingUid, callingPackage);
 
         for (int activityNdx = activities.size() - 1; activityNdx >=0; --activityNdx) {
             activities.get(activityNdx).task = task;
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 2dd150a..e31f177 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1269,28 +1269,26 @@
                         + "greater than 0");
             }
 
+            if ((flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC) != 0) {
+                flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
+            }
+            if ((flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY) != 0) {
+                flags &= ~DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
+            }
+
             if (projection != null) {
                 try {
                     if (!getProjectionService().isValidMediaProjection(projection)) {
                         throw new SecurityException("Invalid media projection");
                     }
+                    flags = projection.applyVirtualDisplayFlags(flags);
                 } catch (RemoteException e) {
-                    throw new SecurityException("unable to validate media projection");
-                }
-                flags &= DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE;
-                try {
-                    flags |= projection.getVirtualDisplayFlags();
-                } catch (RemoteException e) {
-                    throw new RuntimeException("unable to retrieve media projection flags");
+                    throw new SecurityException("unable to validate media projection or flags");
                 }
             }
 
-            if ((flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_SCREEN_SHARE) != 0) {
-                if ((flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC) != 0 ||
-                        (flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION) != 0) {
-                    throw new IllegalArgumentException("screen sharing virtual displays must not "
-                            + "be public or presentation displays");
-                }
+            if (callingUid != Process.SYSTEM_UID &&
+                    (flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) {
                 if (!canProjectVideo(projection)) {
                     throw new SecurityException("Requires CAPTURE_VIDEO_OUTPUT or "
                             + "CAPTURE_SECURE_VIDEO_OUTPUT permission, or an appropriate "
@@ -1298,16 +1296,6 @@
                             + "display.");
                 }
             }
-
-
-            if (callingUid != Process.SYSTEM_UID &&
-                    (flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC) != 0) {
-                if (!canProjectVideo(projection)) {
-                    throw new SecurityException("Requires CAPTURE_VIDEO_OUTPUT or "
-                            + "CAPTURE_SECURE_VIDEO_OUTPUT permission, or an appropriate "
-                            + "MediaProjection token to create a public virtual display.");
-                }
-            }
             if ((flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE) != 0) {
                 if (!canProjectSecureVideo(projection)) {
                     throw new SecurityException("Requires CAPTURE_SECURE_VIDEO_OUTPUT "
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 0ebd2de..72ac29a 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -262,14 +262,15 @@
                 mInfo.presentationDeadlineNanos = 1000000000L / (int) mInfo.refreshRate; // 1 frame
                 mInfo.flags = 0;
                 if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC) == 0) {
-                    mInfo.flags |= DisplayDeviceInfo.FLAG_PRIVATE;
-                    if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_SCREEN_SHARE) == 0) {
-                        mInfo.flags |=  DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY
-                                | DisplayDeviceInfo.FLAG_NEVER_BLANK;
-                    }
-                } else if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY) != 0) {
+                    mInfo.flags |= DisplayDeviceInfo.FLAG_PRIVATE
+                            | DisplayDeviceInfo.FLAG_NEVER_BLANK;
+                }
+                if ((mInfo.flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) {
+                    mInfo.flags &= ~DisplayDeviceInfo.FLAG_NEVER_BLANK;
+                } else {
                     mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY;
                 }
+
                 if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE) != 0) {
                     mInfo.flags |= DisplayDeviceInfo.FLAG_SECURE;
                 }
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 7b28699..289b5aa 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -103,7 +103,7 @@
             try {
                 hasPermission |= checkPermission(packageName,
                         android.Manifest.permission.CAPTURE_VIDEO_OUTPUT)
-                        || mAppOps.checkOpNoThrow(
+                        || mAppOps.noteOpNoThrow(
                                 AppOpsManager.OP_PROJECT_MEDIA, uid, packageName)
                         == AppOpsManager.MODE_ALLOWED;
             } finally {
@@ -196,18 +196,27 @@
         }
 
         @Override // Binder call
-        public int getVirtualDisplayFlags() {
-            switch (mType) {
-                case MediaProjectionManager.TYPE_SCREEN_CAPTURE:
-                    return DisplayManager.VIRTUAL_DISPLAY_FLAG_SCREEN_SHARE;
-                case MediaProjectionManager.TYPE_MIRRORING:
-                    return DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC |
-                            DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
-                case MediaProjectionManager.TYPE_PRESENTATION:
-                    return DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION |
-                            DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+        public int applyVirtualDisplayFlags(int flags) {
+            if (mType == MediaProjectionManager.TYPE_SCREEN_CAPTURE) {
+                flags &= ~DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+                flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR
+                        | DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
+                return flags;
+            } else if (mType == MediaProjectionManager.TYPE_MIRRORING) {
+                flags &= ~(DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC |
+                        DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR);
+                flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY |
+                        DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
+                return flags;
+            } else if (mType == MediaProjectionManager.TYPE_PRESENTATION) {
+                flags &= ~DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+                flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC |
+                        DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION |
+                        DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
+                return flags;
+            } else  {
+                throw new RuntimeException("Unknown MediaProjection type");
             }
-            throw new RuntimeException("Unknown MediaProjection type");
         }
 
         @Override // Binder call
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index c095905..14d1ec4 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -35,7 +35,6 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
-import android.content.res.Resources;
 import android.database.Cursor;
 import android.graphics.Rect;
 import android.hardware.hdmi.HdmiDeviceInfo;
@@ -283,7 +282,6 @@
         userState.inputMap.clear();
         userState.inputMap = inputMap;
 
-        Resources r = Resources.getSystem();
         userState.ratingSystemXmlUriSet.clear();
         userState.ratingSystemXmlUriSet.add(TvContentRating.SYSTEM_CONTENT_RATING_SYSTEM_XML);
         for (TvInputState state : userState.inputMap.values()) {
@@ -1262,6 +1260,7 @@
                         args.arg1 = sessionState.mLogUri;
                         args.arg2 = ContentUris.parseId(channelUri);
                         args.arg3 = currentTime;
+                        args.arg4 = sessionState;
                         mLogHandler.obtainMessage(LogHandler.MSG_OPEN_ENTRY, args).sendToTarget();
                     } catch (RemoteException e) {
                         Slog.e(TAG, "error in tune", e);
@@ -2076,7 +2075,8 @@
                     Uri uri = (Uri) args.arg1;
                     long channelId = (long) args.arg2;
                     long time = (long) args.arg3;
-                    onOpenEntry(uri, channelId, time);
+                    SessionState sessionState = (SessionState) args.arg4;
+                    onOpenEntry(uri, channelId, time, sessionState);
                     args.recycle();
                     return;
                 }
@@ -2085,7 +2085,8 @@
                     Uri uri = (Uri) args.arg1;
                     long channelId = (long) args.arg2;
                     long time = (long) args.arg3;
-                    onUpdateEntry(uri, channelId, time);
+                    SessionState sessionState = (SessionState) args.arg4;
+                    onUpdateEntry(uri, channelId, time, sessionState);
                     args.recycle();
                     return;
                 }
@@ -2104,7 +2105,17 @@
             }
         }
 
-        private void onOpenEntry(Uri uri, long channelId, long watchStarttime) {
+        private void onOpenEntry(Uri logUri, long channelId, long watchStarttime,
+                SessionState sessionState) {
+            if (!isChannelSearchable(channelId)) {
+                // Do not log anything about non-searchable channels.
+                synchronized (mLock) {
+                    sessionState.mLogUri = null;
+                }
+                mContentResolver.delete(logUri, null, null);
+                return;
+            }
+
             String[] projection = {
                     TvContract.Programs.COLUMN_TITLE,
                     TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS,
@@ -2132,11 +2143,11 @@
                     long endTime = cursor.getLong(2);
                     values.put(TvContract.WatchedPrograms.COLUMN_END_TIME_UTC_MILLIS, endTime);
                     values.put(TvContract.WatchedPrograms.COLUMN_DESCRIPTION, cursor.getString(3));
-                    mContentResolver.update(uri, values, null, null);
+                    mContentResolver.update(logUri, values, null, null);
 
                     // Schedule an update when the current program ends.
                     SomeArgs args = SomeArgs.obtain();
-                    args.arg1 = uri;
+                    args.arg1 = logUri;
                     args.arg2 = channelId;
                     args.arg3 = endTime;
                     Message msg = obtainMessage(LogHandler.MSG_UPDATE_ENTRY, args);
@@ -2149,7 +2160,7 @@
             }
         }
 
-        private void onUpdateEntry(Uri uri, long channelId, long time) {
+        private void onUpdateEntry(Uri uri, long channelId, long time, SessionState sessionState) {
             String[] projection = {
                     TvContract.WatchedPrograms.COLUMN_WATCH_START_TIME_UTC_MILLIS,
                     TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS,
@@ -2193,7 +2204,7 @@
                 }
             }
             // Re-open the current log entry with the next program information.
-            onOpenEntry(uri, channelId, time);
+            onOpenEntry(uri, channelId, time, sessionState);
         }
 
         private void onCloseEntry(Uri uri, long watchEndTime) {
@@ -2201,6 +2212,26 @@
             values.put(TvContract.WatchedPrograms.COLUMN_WATCH_END_TIME_UTC_MILLIS, watchEndTime);
             mContentResolver.update(uri, values, null, null);
         }
+
+        private boolean isChannelSearchable(long channelId) {
+            String[] projection = { TvContract.Channels.COLUMN_SEARCHABLE };
+            String selection = TvContract.Channels._ID + "=?";
+            String[] selectionArgs = { String.valueOf(channelId) };
+            Cursor cursor = null;
+            try {
+                cursor = mContentResolver.query(TvContract.Channels.CONTENT_URI, projection,
+                        selection, selectionArgs, null);
+                if (cursor != null && cursor.moveToNext()) {
+                    return cursor.getLong(0) == 1 ? true : false;
+                }
+            } finally {
+                if (cursor != null) {
+                    cursor.close();
+                }
+            }
+            // Unless explicitly specified non-searchable, by default the channel is searchable.
+            return true;
+        }
     }
 
     final class HardwareListener implements TvInputHardwareManager.Listener {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
index b4c221f..f0ecafe 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
@@ -41,12 +41,12 @@
     static final boolean DBG = true;
 
     private static final String NAME = "sound_model.db";
-    private static final int VERSION = 3;
+    private static final int VERSION = 4;
 
     public static interface SoundModelContract {
         public static final String TABLE = "sound_model";
-        public static final String KEY_KEYPHRASE_ID = "keyphrase_id";
         public static final String KEY_MODEL_UUID = "model_uuid";
+        public static final String KEY_KEYPHRASE_ID = "keyphrase_id";
         public static final String KEY_TYPE = "type";
         public static final String KEY_DATA = "data";
         public static final String KEY_RECOGNITION_MODES = "recognition_modes";
@@ -58,8 +58,8 @@
     // Table Create Statement
     private static final String CREATE_TABLE_SOUND_MODEL = "CREATE TABLE "
             + SoundModelContract.TABLE + "("
-            + SoundModelContract.KEY_KEYPHRASE_ID + " INTEGER PRIMARY KEY,"
-            + SoundModelContract.KEY_MODEL_UUID + " TEXT,"
+            + SoundModelContract.KEY_MODEL_UUID + " TEXT PRIMARY KEY,"
+            + SoundModelContract.KEY_KEYPHRASE_ID + " INTEGER,"
             + SoundModelContract.KEY_TYPE + " INTEGER,"
             + SoundModelContract.KEY_DATA + " BLOB,"
             + SoundModelContract.KEY_RECOGNITION_MODES + " INTEGER,"
@@ -122,10 +122,16 @@
     /**
      * Deletes the sound model and associated keyphrases.
      */
-    public boolean deleteKeyphraseSoundModel(int keyphraseId) {
+    public boolean deleteKeyphraseSoundModel(UUID modelUuid) {
+        if (modelUuid == null) {
+            Slog.w(TAG, "Model UUID must be specified for deletion");
+            return false;
+        }
+
         synchronized(this) {
             SQLiteDatabase db = getWritableDatabase();
-            String soundModelClause = SoundModelContract.KEY_KEYPHRASE_ID + "=" + keyphraseId;
+            String soundModelClause = SoundModelContract.KEY_MODEL_UUID + "="
+                    + modelUuid.toString();
 
             try {
                 return db.delete(SoundModelContract.TABLE, soundModelClause, null) != 0;
@@ -151,52 +157,56 @@
 
             try {
                 if (c.moveToFirst()) {
-                    int type = c.getInt(c.getColumnIndex(SoundModelContract.KEY_TYPE));
-                    if (type != SoundTrigger.SoundModel.TYPE_KEYPHRASE) {
-                        Slog.w(TAG, "No KeyphraseSoundModel available for the given keyphrase");
-                        return null;
-                    }
-
-                    String modelUuid = c.getString(
-                            c.getColumnIndex(SoundModelContract.KEY_MODEL_UUID));
-                    if (modelUuid == null) {
-                        Slog.w(TAG, "Ignoring sound model since it doesn't specify an ID");
-                        return null;
-                    }
-
-                    byte[] data = c.getBlob(c.getColumnIndex(SoundModelContract.KEY_DATA));
-                    int recognitionModes = c.getInt(
-                            c.getColumnIndex(SoundModelContract.KEY_RECOGNITION_MODES));
-                    int[] users = getArrayForCommaSeparatedString(
-                            c.getString(c.getColumnIndex(SoundModelContract.KEY_USERS)));
-                    String locale = c.getString(c.getColumnIndex(SoundModelContract.KEY_LOCALE));
-                    String text = c.getString(
-                            c.getColumnIndex(SoundModelContract.KEY_HINT_TEXT));
-
-                    // Only add keyphrases meant for the current user.
-                    if (users == null) {
-                        // No users present in the keyphrase.
-                        Slog.w(TAG, "Ignoring keyphrase since it doesn't specify users");
-                        return null;
-                    }
-                    boolean isAvailableForCurrentUser = false;
-                    int currentUser = mUserManager.getUserHandle();
-                    for (int user : users) {
-                        if (currentUser == user) {
-                            isAvailableForCurrentUser = true;
-                            break;
+                    do {
+                        int type = c.getInt(c.getColumnIndex(SoundModelContract.KEY_TYPE));
+                        if (type != SoundTrigger.SoundModel.TYPE_KEYPHRASE) {
+                            Slog.w(TAG, "Ignoring sound model since it's type is incorrect");
+                            continue;
                         }
-                    }
-                    if (!isAvailableForCurrentUser) {
-                        Slog.w(TAG, "Ignoring keyphrase since it's not for the current user");
-                        return null;
-                    }
 
-                    Keyphrase[] keyphrases = new Keyphrase[1];
-                    keyphrases[0] = new Keyphrase(
-                            keyphraseId, recognitionModes, locale, text, users);
-                    return new KeyphraseSoundModel(UUID.fromString(modelUuid),
-                            null /* FIXME use vendor UUID */, data, keyphrases);
+                        String modelUuid = c.getString(
+                                c.getColumnIndex(SoundModelContract.KEY_MODEL_UUID));
+                        if (modelUuid == null) {
+                            Slog.w(TAG, "Ignoring sound model since it doesn't specify an ID");
+                            continue;
+                        }
+
+                        byte[] data = c.getBlob(c.getColumnIndex(SoundModelContract.KEY_DATA));
+                        int recognitionModes = c.getInt(
+                                c.getColumnIndex(SoundModelContract.KEY_RECOGNITION_MODES));
+                        int[] users = getArrayForCommaSeparatedString(
+                                c.getString(c.getColumnIndex(SoundModelContract.KEY_USERS)));
+                        String locale = c.getString(
+                                c.getColumnIndex(SoundModelContract.KEY_LOCALE));
+                        String text = c.getString(
+                                c.getColumnIndex(SoundModelContract.KEY_HINT_TEXT));
+
+                        // Only add keyphrases meant for the current user.
+                        if (users == null) {
+                            // No users present in the keyphrase.
+                            Slog.w(TAG, "Ignoring keyphrase since it doesn't specify users");
+                            continue;
+                        }
+
+                        boolean isAvailableForCurrentUser = false;
+                        int currentUser = mUserManager.getUserHandle();
+                        for (int user : users) {
+                            if (currentUser == user) {
+                                isAvailableForCurrentUser = true;
+                                break;
+                            }
+                        }
+                        if (!isAvailableForCurrentUser) {
+                            Slog.w(TAG, "Ignoring keyphrase since it's not for the current user");
+                            continue;
+                        }
+
+                        Keyphrase[] keyphrases = new Keyphrase[1];
+                        keyphrases[0] = new Keyphrase(
+                                keyphraseId, recognitionModes, locale, text, users);
+                        return new KeyphraseSoundModel(UUID.fromString(modelUuid),
+                                null /* FIXME use vendor UUID */, data, keyphrases);
+                    } while (c.moveToNext());
                 }
                 Slog.w(TAG, "No SoundModel available for the given keyphrase");
             } finally {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java
index fd36bfc..994f758 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java
@@ -28,9 +28,12 @@
 import android.hardware.soundtrigger.SoundTrigger.SoundModelEvent;
 import android.hardware.soundtrigger.SoundTriggerModule;
 import android.os.RemoteException;
+import android.telephony.PhoneStateListener;
+import android.telephony.TelephonyManager;
 import android.util.Slog;
-import android.util.SparseArray;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.UUID;
 
@@ -53,26 +56,37 @@
     public static final int STATUS_ERROR = SoundTrigger.STATUS_ERROR;
     public static final int STATUS_OK = SoundTrigger.STATUS_OK;
 
-    private static final int INVALID_SOUND_MODEL_HANDLE = -1;
+    private static final int INVALID_VALUE = Integer.MIN_VALUE;
 
-    /** The {@link DspInfo} for the system, or null if none exists. */
+    /** The {@link ModuleProperties} for the system, or null if none exists. */
     final ModuleProperties moduleProperties;
 
     /** The properties for the DSP module */
     private final SoundTriggerModule mModule;
+    private final Object mLock = new Object();
+    private final TelephonyManager mTelephonyManager;
+    private final PhoneStateListener mPhoneStateListener;
 
-    // Use a RemoteCallbackList here?
-    private final SparseArray<IRecognitionStatusCallback> mActiveListeners;
-
-    private int mCurrentSoundModelHandle = INVALID_SOUND_MODEL_HANDLE;
+    // TODO: Since many layers currently only deal with one recognition
+    // we simplify things by assuming one listener here too.
+    private IRecognitionStatusCallback mActiveListener;
+    private int mKeyphraseId = INVALID_VALUE;
+    private int mCurrentSoundModelHandle = INVALID_VALUE;
     private UUID mCurrentSoundModelUuid = null;
     // FIXME: Ideally this should not be stored if allowMultipleTriggers happens at a lower layer.
     private RecognitionConfig mRecognitionConfig = null;
+    private boolean mRequested = false;
+    private boolean mCallActive = 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;
 
-    SoundTriggerHelper() {
+    SoundTriggerHelper(TelephonyManager telephonyManager) {
         ArrayList <ModuleProperties> modules = new ArrayList<>();
         int status = SoundTrigger.listModules(modules);
-        mActiveListeners = new SparseArray<>(1);
+        mTelephonyManager = telephonyManager;
+        mPhoneStateListener = new MyCallStateListener();
         if (status != SoundTrigger.STATUS_OK || modules.size() == 0) {
             Slog.w(TAG, "listModules status=" + status + ", # of modules=" + modules.size());
             moduleProperties = null;
@@ -85,17 +99,6 @@
     }
 
     /**
-     * @return True, if a recognition for the given {@link Keyphrase} is active.
-     */
-    synchronized boolean isKeyphraseActive(Keyphrase keyphrase) {
-        if (keyphrase == null) {
-            Slog.w(TAG, "isKeyphraseActive requires a non-null keyphrase");
-            return false;
-        }
-        return mActiveListeners.get(keyphrase.id) != null;
-    }
-
-    /**
      * Starts recognition for the given keyphraseId.
      *
      * @param keyphraseId The identifier of the keyphrase for which
@@ -104,85 +107,94 @@
      * @param listener The listener for the recognition events related to the given keyphrase.
      * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
      */
-    synchronized int startRecognition(int keyphraseId,
+    int startRecognition(int keyphraseId,
             KeyphraseSoundModel soundModel,
             IRecognitionStatusCallback listener,
             RecognitionConfig recognitionConfig) {
-        if (DBG) {
-            Slog.d(TAG, "startRecognition for keyphraseId=" + keyphraseId
-                    + " soundModel=" + soundModel + ", listener=" + listener
-                    + ", recognitionConfig=" + recognitionConfig);
-            Slog.d(TAG, "moduleProperties=" + moduleProperties);
-            Slog.d(TAG, "# of current listeners=" + mActiveListeners.size());
-            Slog.d(TAG, "current SoundModel handle=" + mCurrentSoundModelHandle);
-            Slog.d(TAG, "current SoundModel UUID="
-                    + (mCurrentSoundModelUuid == null ? null : mCurrentSoundModelUuid));
-        }
-        if (moduleProperties == null || mModule == null) {
-            Slog.w(TAG, "Attempting startRecognition without the capability");
+        if (soundModel == null || listener == null || recognitionConfig == null) {
             return STATUS_ERROR;
         }
 
-        if (mCurrentSoundModelHandle != INVALID_SOUND_MODEL_HANDLE
-                && !soundModel.uuid.equals(mCurrentSoundModelUuid)) {
-            Slog.w(TAG, "Unloading previous sound model");
-            int status = mModule.unloadSoundModel(mCurrentSoundModelHandle);
-            if (status != SoundTrigger.STATUS_OK) {
-                Slog.w(TAG, "unloadSoundModel call failed with " + status);
-                return status;
+        synchronized (mLock) {
+            if (DBG) {
+                Slog.d(TAG, "startRecognition for keyphraseId=" + keyphraseId
+                        + " soundModel=" + soundModel + ", listener=" + listener.asBinder()
+                        + ", recognitionConfig=" + recognitionConfig);
+                Slog.d(TAG, "moduleProperties=" + moduleProperties);
+                Slog.d(TAG, "current listener="
+                        + (mActiveListener == null ? "null" : mActiveListener.asBinder()));
+                Slog.d(TAG, "current SoundModel handle=" + mCurrentSoundModelHandle);
+                Slog.d(TAG, "current SoundModel UUID="
+                        + (mCurrentSoundModelUuid == null ? null : mCurrentSoundModelUuid));
             }
-            mCurrentSoundModelHandle = INVALID_SOUND_MODEL_HANDLE;
-            mCurrentSoundModelUuid = null;
-        }
 
-        // If the previous recognition was by a different listener,
-        // Notify them that it was stopped.
-        IRecognitionStatusCallback oldListener = mActiveListeners.get(keyphraseId);
-        if (oldListener != null && oldListener.asBinder() != listener.asBinder()) {
-            Slog.w(TAG, "Canceling previous recognition");
-            try {
-                oldListener.onError(STATUS_ERROR);
-            } catch (RemoteException e) {
-                Slog.w(TAG, "RemoteException in onDetectionStopped");
+            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);
             }
-            mActiveListeners.remove(keyphraseId);
-        }
 
-        // Load the sound model if the current one is null.
-        int soundModelHandle = mCurrentSoundModelHandle;
-        if (mCurrentSoundModelHandle == INVALID_SOUND_MODEL_HANDLE
-                || mCurrentSoundModelUuid == null) {
-            int[] handle = new int[] { INVALID_SOUND_MODEL_HANDLE };
-            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_SOUND_MODEL_HANDLE) {
-                Slog.w(TAG, "loadSoundModel call returned invalid sound model handle");
+            if (moduleProperties == null || mModule == null) {
+                Slog.w(TAG, "Attempting startRecognition without the capability");
                 return STATUS_ERROR;
             }
-            soundModelHandle = handle[0];
-        } else {
-            if (DBG) Slog.d(TAG, "Reusing previously loaded sound model");
-        }
 
-        // Start the recognition.
-        int status = mModule.startRecognition(soundModelHandle, recognitionConfig);
-        if (status != SoundTrigger.STATUS_OK) {
-            Slog.w(TAG, "startRecognition failed with " + status);
-            return status;
-        }
+            if (mCurrentSoundModelHandle != INVALID_VALUE
+                    && !soundModel.uuid.equals(mCurrentSoundModelUuid)) {
+                Slog.w(TAG, "Unloading previous sound model");
+                int status = mModule.unloadSoundModel(mCurrentSoundModelHandle);
+                if (status != SoundTrigger.STATUS_OK) {
+                    Slog.w(TAG, "unloadSoundModel call failed with " + status);
+                }
+                mCurrentSoundModelHandle = INVALID_VALUE;
+                mCurrentSoundModelUuid = null;
+                mStarted = false;
+            }
 
-        // Everything went well!
-        mCurrentSoundModelHandle = soundModelHandle;
-        mCurrentSoundModelUuid = soundModel.uuid;
-        mRecognitionConfig = recognitionConfig;
-        // Register the new listener. This replaces the old one.
-        // There can only be a maximum of one active listener for a keyphrase
-        // at any given time.
-        mActiveListeners.put(keyphraseId, listener);
-        return STATUS_OK;
+            // If the previous recognition was by a different listener,
+            // Notify them that it was stopped.
+            if (mActiveListener != null && mActiveListener.asBinder() != listener.asBinder()) {
+                Slog.w(TAG, "Canceling previous recognition");
+                try {
+                    mActiveListener.onError(STATUS_ERROR);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "RemoteException in onDetectionStopped");
+                }
+                mActiveListener = null;
+            }
+
+            // Load the sound model if the current one is null.
+            int soundModelHandle = mCurrentSoundModelHandle;
+            if (mCurrentSoundModelHandle == INVALID_VALUE
+                    || mCurrentSoundModelUuid == null) {
+                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;
+                }
+                soundModelHandle = handle[0];
+            } else {
+                if (DBG) Slog.d(TAG, "Reusing previously loaded sound model");
+            }
+
+            // Start the recognition.
+            mRequested = true;
+            mKeyphraseId = keyphraseId;
+            mCurrentSoundModelHandle = soundModelHandle;
+            mCurrentSoundModelUuid = soundModel.uuid;
+            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;
+
+            return updateRecognitionLocked(false /* don't notify for synchronous calls */);
+        }
     }
 
     /**
@@ -195,173 +207,325 @@
      *
      * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
      */
-    synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
-        if (DBG) {
-            Slog.d(TAG, "stopRecognition for keyphraseId=" + keyphraseId
-                    + ", listener=" + listener);
-            Slog.d(TAG, "# of current listeners = " + mActiveListeners.size());
-        }
-
-        if (moduleProperties == null || mModule == null) {
-            Slog.w(TAG, "Attempting stopRecognition without the capability");
-            return STATUS_ERROR;
-        }
-
-        IRecognitionStatusCallback currentListener = mActiveListeners.get(keyphraseId);
+    int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
         if (listener == null) {
-            Slog.w(TAG, "Attempting stopRecognition without a valid listener");
             return STATUS_ERROR;
-        } if (currentListener == null) {
-            // startRecognition hasn't been called or it failed.
-            Slog.w(TAG, "Attempting stopRecognition without a successful startRecognition");
-            return STATUS_ERROR;
-        } else if (currentListener.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;
-        } else {
+        }
+
+        synchronized (mLock) {
+            if (DBG) {
+                Slog.d(TAG, "stopRecognition for keyphraseId=" + keyphraseId
+                        + ", listener=" + listener.asBinder());
+                Slog.d(TAG, "current listener="
+                        + (mActiveListener == null ? "null" : mActiveListener.asBinder()));
+            }
+
+            if (moduleProperties == null || mModule == null) {
+                Slog.w(TAG, "Attempting stopRecognition without the capability");
+                return STATUS_ERROR;
+            }
+
+            if (mActiveListener == 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()) {
+                // 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;
+            }
+
             // Stop recognition if it's the current one, ignore otherwise.
-            int status = mModule.stopRecognition(mCurrentSoundModelHandle);
+            mRequested = false;
+            int status = updateRecognitionLocked(false /* don't notify for synchronous calls */);
             if (status != SoundTrigger.STATUS_OK) {
-                Slog.w(TAG, "stopRecognition call failed with " + status);
                 return status;
             }
+
             status = mModule.unloadSoundModel(mCurrentSoundModelHandle);
             if (status != SoundTrigger.STATUS_OK) {
                 Slog.w(TAG, "unloadSoundModel call failed with " + status);
-                return status;
             }
 
-            mCurrentSoundModelHandle = INVALID_SOUND_MODEL_HANDLE;
-            mCurrentSoundModelUuid = null;
-
-            mActiveListeners.remove(keyphraseId);
-            return STATUS_OK;
+            // Clear the internal state once the recognition has been stopped.
+            // Unload sound model call may fail in scenarios, and we'd still want
+            // to reload the sound model.
+            internalClearStateLocked();
+            return status;
         }
     }
 
-    synchronized void stopAllRecognitions() {
-        if (moduleProperties == null || mModule == null) {
-            return;
-        }
+    /**
+     * Stops all recognitions active currently and clears the internal state.
+     */
+    void stopAllRecognitions() {
+        synchronized (mLock) {
+            if (moduleProperties == null || mModule == null) {
+                return;
+            }
 
-        if (mCurrentSoundModelHandle == INVALID_SOUND_MODEL_HANDLE) {
-            return;
-        }
+            if (mCurrentSoundModelHandle == INVALID_VALUE) {
+                return;
+            }
 
-        int status = mModule.stopRecognition(mCurrentSoundModelHandle);
-        if (status != SoundTrigger.STATUS_OK) {
-            Slog.w(TAG, "stopRecognition call failed with " + status);
-        }
-        status = mModule.unloadSoundModel(mCurrentSoundModelHandle);
-        if (status != SoundTrigger.STATUS_OK) {
-            Slog.w(TAG, "unloadSoundModel call failed with " + status);
-        }
+            mRequested = false;
+            int status = updateRecognitionLocked(false /* don't notify for synchronous calls */);
+            status = mModule.unloadSoundModel(mCurrentSoundModelHandle);
+            if (status != SoundTrigger.STATUS_OK) {
+                Slog.w(TAG, "unloadSoundModel call failed with " + status);
+            }
 
-        mCurrentSoundModelHandle = INVALID_SOUND_MODEL_HANDLE;
-        mCurrentSoundModelUuid = null;
-
-        mActiveListeners.clear();
+            internalClearStateLocked();
+        }
     }
 
     //---- SoundTrigger.StatusListener methods
     @Override
     public void onRecognition(RecognitionEvent event) {
-        if (event == null) {
+        if (event == null || !(event instanceof KeyphraseRecognitionEvent)) {
             Slog.w(TAG, "Invalid recognition event!");
             return;
         }
 
         if (DBG) Slog.d(TAG, "onRecognition: " + event);
-        switch (event.status) {
-            // Fire aborts/failures to all listeners since it's not tied to a keyphrase.
-            case SoundTrigger.RECOGNITION_STATUS_ABORT: // fall-through
-            case SoundTrigger.RECOGNITION_STATUS_FAILURE:
-                try {
-                    synchronized (this) {
-                        for (int i = 0; i < mActiveListeners.size(); i++) {
-                            mActiveListeners.valueAt(i).onError(STATUS_ERROR);
-                        }
-                    }
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "RemoteException in onDetectionStopped");
-                }
-                break;
-            case SoundTrigger.RECOGNITION_STATUS_SUCCESS:
-                if (!(event instanceof KeyphraseRecognitionEvent)) {
-                    Slog.w(TAG, "Invalid recognition event!");
-                    return;
-                }
-
-                KeyphraseRecognitionExtra[] keyphraseExtras =
-                        ((KeyphraseRecognitionEvent) event).keyphraseExtras;
-                if (keyphraseExtras == null || keyphraseExtras.length == 0) {
-                    Slog.w(TAG, "Invalid keyphrase recognition event!");
-                    return;
-                }
-                // TODO: Handle more than one keyphrase extras.
-                int keyphraseId = keyphraseExtras[0].id;
-                try {
-                    synchronized(this) {
-                        // Check which keyphrase triggered, and fire the appropriate event.
-                        IRecognitionStatusCallback listener = mActiveListeners.get(keyphraseId);
-                        if (listener != null) {
-                            listener.onDetected((KeyphraseRecognitionEvent) event);
-                        } else {
-                            Slog.w(TAG, "received onRecognition event without any listener for it");
-                            return;
-                        }
-
-                        // FIXME: Remove this block if the lower layer supports multiple triggers.
-                        if (mRecognitionConfig != null
-                                && mRecognitionConfig.allowMultipleTriggers) {
-                            int status = mModule.startRecognition(
-                                    mCurrentSoundModelHandle, mRecognitionConfig);
-                            if (status != STATUS_OK) {
-                                Slog.w(TAG, "Error in restarting recognition after a trigger");
-                                listener.onError(status);
-                            }
-                        }
-                    }
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "RemoteException in onDetectionStopped");
-                }
-                break;
+        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:
+                    onRecognitionAbortLocked();
+                    break;
+                case SoundTrigger.RECOGNITION_STATUS_FAILURE:
+                    onRecognitionFailureLocked();
+                    break;
+                case SoundTrigger.RECOGNITION_STATUS_SUCCESS:
+                    onRecognitionSuccessLocked((KeyphraseRecognitionEvent) event);
+                    break;
+            }
         }
     }
 
+    @Override
     public void onSoundModelUpdate(SoundModelEvent event) {
         if (event == null) {
             Slog.w(TAG, "Invalid sound model event!");
             return;
         }
-
         if (DBG) Slog.d(TAG, "onSoundModelUpdate: " + event);
-
-        //TODO: implement sound model update
+        synchronized (mLock) {
+            onSoundModelUpdatedLocked(event);
+        }
     }
 
+    @Override
     public void onServiceStateChange(int state) {
         if (DBG) Slog.d(TAG, "onServiceStateChange, state: " + state);
-
-        //TODO: implement service state update
+        synchronized (mLock) {
+            onServiceStateChangedLocked(SoundTrigger.SERVICE_STATE_DISABLED == state);
+        }
     }
 
     @Override
     public void onServiceDied() {
-        synchronized (this) {
-            try {
-                for (int i = 0; i < mActiveListeners.size(); i++) {
-                    mActiveListeners.valueAt(i).onError(SoundTrigger.STATUS_DEAD_OBJECT);
-                }
-            } catch (RemoteException e) {
-                Slog.w(TAG, "RemoteException in onDetectionStopped");
+        Slog.e(TAG, "onServiceDied!!");
+        synchronized (mLock) {
+            onServiceDiedLocked();
+        }
+    }
+
+    class MyCallStateListener extends PhoneStateListener {
+        @Override
+        public void onCallStateChanged(int state, String arg1) {
+            if (DBG) Slog.d(TAG, "onCallStateChanged: " + state);
+            synchronized (mLock) {
+                onCallStateChangedLocked(TelephonyManager.CALL_STATE_IDLE != state);
             }
-            mCurrentSoundModelHandle = INVALID_SOUND_MODEL_HANDLE;
-            mCurrentSoundModelUuid = null;
-            // Remove all listeners.
-            mActiveListeners.clear();
+        }
+    }
+
+    private void onCallStateChangedLocked(boolean callActive) {
+        if (mCallActive == callActive) {
+            // We consider multiple call states as being active
+            // so we check if something really changed or not here.
+            return;
+        }
+        mCallActive = callActive;
+        updateRecognitionLocked(true /* notify */);
+    }
+
+    private void onSoundModelUpdatedLocked(SoundModelEvent event) {
+        // TODO: Handle sound model update here.
+    }
+
+    private void onServiceStateChangedLocked(boolean disabled) {
+        if (disabled == mServiceDisabled) {
+            return;
+        }
+        mServiceDisabled = disabled;
+        updateRecognitionLocked(true /* notify */);
+    }
+
+    private void onRecognitionAbortLocked() {
+        Slog.w(TAG, "Recognition aborted");
+        // No-op
+        // This is handled via service state changes instead.
+    }
+
+    private void onRecognitionFailureLocked() {
+        Slog.w(TAG, "Recognition failure");
+        try {
+            if (mActiveListener != null) {
+                mActiveListener.onError(STATUS_ERROR);
+            }
+        } catch (RemoteException e) {
+            Slog.w(TAG, "RemoteException in onError", e);
+        } finally {
+            internalClearStateLocked();
+        }
+    }
+
+    private void onRecognitionSuccessLocked(KeyphraseRecognitionEvent event) {
+        Slog.i(TAG, "Recognition success");
+        KeyphraseRecognitionExtra[] keyphraseExtras =
+                ((KeyphraseRecognitionEvent) event).keyphraseExtras;
+        if (keyphraseExtras == null || keyphraseExtras.length == 0) {
+            Slog.w(TAG, "Invalid keyphrase recognition event!");
+            return;
+        }
+        // TODO: Handle more than one keyphrase extras.
+        if (mKeyphraseId != keyphraseExtras[0].id) {
+            Slog.w(TAG, "received onRecognition event for a different keyphrase");
+            return;
+        }
+
+        try {
+            if (mActiveListener != null) {
+                mActiveListener.onDetected((KeyphraseRecognitionEvent) event);
+            }
+        } catch (RemoteException e) {
+            Slog.w(TAG, "RemoteException in onDetected", e);
+        }
+
+        mStarted = false;
+        mRequested = mRecognitionConfig.allowMultipleTriggers;
+        // TODO: Remove this block if the lower layer supports multiple triggers.
+        if (mRequested) {
+            updateRecognitionLocked(true /* notify */);
+        }
+    }
+
+    private void onServiceDiedLocked() {
+        try {
+            if (mActiveListener != null) {
+                mActiveListener.onError(SoundTrigger.STATUS_DEAD_OBJECT);
+            }
+        } catch (RemoteException e) {
+            Slog.w(TAG, "RemoteException in onError", e);
+        } finally {
+            internalClearStateLocked();
+        }
+    }
+
+    private int updateRecognitionLocked(boolean notify) {
+        if (mModule == null || moduleProperties == null
+                || mCurrentSoundModelHandle == INVALID_VALUE || mActiveListener == null) {
+            // Nothing to do here.
+            return STATUS_OK;
+        }
+
+        boolean start = mRequested && !mCallActive && !mServiceDisabled;
+        if (start == mStarted) {
+            // No-op.
+            return STATUS_OK;
+        }
+
+        // See if the recognition needs to be started.
+        if (start) {
+            // Start recognition.
+            int status = mModule.startRecognition(mCurrentSoundModelHandle, mRecognitionConfig);
+            if (status != SoundTrigger.STATUS_OK) {
+                Slog.w(TAG, "startRecognition failed with " + status);
+                // Notify of error if needed.
+                if (notify) {
+                    try {
+                        mActiveListener.onError(status);
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "RemoteException in onError", e);
+                    }
+                }
+            } else {
+                mStarted = true;
+                // Notify of resume if needed.
+                if (notify) {
+                    try {
+                        mActiveListener.onRecognitionResumed();
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "RemoteException in onRecognitionResumed", e);
+                    }
+                }
+            }
+            return status;
+        } else {
+            // Stop recognition.
+            int status = mModule.stopRecognition(mCurrentSoundModelHandle);
+            if (status != SoundTrigger.STATUS_OK) {
+                Slog.w(TAG, "stopRecognition call failed with " + status);
+                if (notify) {
+                    try {
+                        mActiveListener.onError(status);
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "RemoteException in onError", e);
+                    }
+                }
+            } else {
+                mStarted = false;
+                // Notify of pause if needed.
+                if (notify) {
+                    try {
+                        mActiveListener.onRecognitionPaused();
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "RemoteException in onRecognitionPaused", e);
+                    }
+                }
+            }
+            return status;
+        }
+    }
+
+    private void internalClearStateLocked() {
+        mStarted = false;
+        mRequested = false;
+
+        mKeyphraseId = INVALID_VALUE;
+        mCurrentSoundModelHandle = INVALID_VALUE;
+        mCurrentSoundModelUuid = null;
+        mRecognitionConfig = null;
+        mActiveListener = null;
+
+        // Unregister from call state changes.
+        mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
+    }
+
+    void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        synchronized (mLock) {
+            pw.print("  module properties=");
+            pw.println(moduleProperties == null ? "null" : moduleProperties);
+            pw.print("  keyphrase ID="); pw.println(mKeyphraseId);
+            pw.print("  sound model handle="); pw.println(mCurrentSoundModelHandle);
+            pw.print("  sound model UUID=");
+            pw.println(mCurrentSoundModelUuid == null ? "null" : mCurrentSoundModelUuid);
+            pw.print("  current listener=");
+            pw.println(mActiveListener == null ? "null" : mActiveListener.asBinder());
+
+            pw.print("  requested="); pw.println(mRequested);
+            pw.print("  started="); pw.println(mStarted);
+            pw.print("  call active="); pw.println(mCallActive);
+            pw.print("  service disabled="); pw.println(mServiceDisabled);
         }
     }
 }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 75d41aa..0d24793 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -38,6 +38,7 @@
 import android.provider.Settings;
 import android.service.voice.IVoiceInteractionService;
 import android.service.voice.IVoiceInteractionSession;
+import android.telephony.TelephonyManager;
 import android.util.Slog;
 
 import com.android.internal.app.IVoiceInteractionManagerService;
@@ -67,7 +68,8 @@
         mContext = context;
         mResolver = context.getContentResolver();
         mDbHelper = new DatabaseHelper(context);
-        mSoundTriggerHelper = new SoundTriggerHelper();
+        mSoundTriggerHelper = new SoundTriggerHelper(
+                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE));
     }
 
     @Override
@@ -301,19 +303,22 @@
             }
 
             final long caller = Binder.clearCallingIdentity();
+            boolean deleted = false;
             try {
-                if (mDbHelper.deleteKeyphraseSoundModel(keyphraseId)) {
+                KeyphraseSoundModel soundModel = mDbHelper.getKeyphraseSoundModel(keyphraseId);
+                if (soundModel != null) {
+                    deleted = mDbHelper.deleteKeyphraseSoundModel(soundModel.uuid);
+                }
+                return deleted ? SoundTriggerHelper.STATUS_OK : SoundTriggerHelper.STATUS_ERROR;
+            } finally {
+                if (deleted) {
                     synchronized (this) {
                         // Notify the voice interaction service of a change in sound models.
                         if (mImpl != null && mImpl.mService != null) {
                             mImpl.notifySoundModelsChangedLocked();
                         }
                     }
-                    return SoundTriggerHelper.STATUS_OK;
-                } else {
-                    return SoundTriggerHelper.STATUS_ERROR;
                 }
-            } finally {
                 Binder.restoreCallingIdentity(caller);
             }
         }
@@ -427,6 +432,7 @@
                 }
                 mImpl.dumpLocked(fd, pw, args);
             }
+            mSoundTriggerHelper.dump(fd, pw, args);
         }
 
         class SettingsObserver extends ContentObserver {
diff --git a/telecomm/java/android/telecomm/Call.java b/telecomm/java/android/telecomm/Call.java
index 4e9e604..3374d51 100644
--- a/telecomm/java/android/telecomm/Call.java
+++ b/telecomm/java/android/telecomm/Call.java
@@ -249,7 +249,7 @@
         /**
          * Invoked when the state of this {@code Call} has changed. See {@link #getState()}.
          *
-         * TODO(ihab): Provide previous state also?
+         * TODO: Provide previous state also?
          *
          * @param call The {@code Call} invoking this method.
          * @param state The new state of the {@code Call}.
@@ -453,7 +453,7 @@
     /**
      * Notifies this {@code Call} that the phone account user interface element was touched.
      *
-     * TODO(ihab): Figure out if and how we can generalize this
+     * TODO: Figure out if and how we can generalize this
      */
     public void phoneAccountClicked() {
         mInCallAdapter.phoneAccountClicked(mTelecommCallId);
diff --git a/telecomm/java/android/telecomm/Connection.java b/telecomm/java/android/telecomm/Connection.java
index d347aad..b323646 100644
--- a/telecomm/java/android/telecomm/Connection.java
+++ b/telecomm/java/android/telecomm/Connection.java
@@ -273,7 +273,7 @@
     }
 
     /**
-     * TODO(santoscordon): Needs documentation.
+     * TODO: Needs documentation.
      */
     public final void setParentConnection(Connection parentConnection) {
         Log.d(this, "parenting %s to %s", this, parentConnection);
@@ -458,7 +458,7 @@
     }
 
     /**
-     * TODO(santoscordon): Needs documentation.
+     * TODO: Needs documentation.
      */
     public final void setPostDialWait(String remaining) {
         for (Listener l : mListeners) {
@@ -654,7 +654,7 @@
     public void onSwapWithBackgroundCall() {}
 
     /**
-     * TODO(santoscordon): Needs documentation.
+     * TODO: Needs documentation.
      */
     public void onChildrenChanged(List<Connection> children) {}
 
diff --git a/telecomm/java/android/telecomm/ConnectionService.java b/telecomm/java/android/telecomm/ConnectionService.java
index fddc9b0..5e6bf87 100644
--- a/telecomm/java/android/telecomm/ConnectionService.java
+++ b/telecomm/java/android/telecomm/ConnectionService.java
@@ -647,7 +647,7 @@
             return;
         }
 
-        // TODO(santoscordon): Find existing conference call and invoke split(connection).
+        // TODO: Find existing conference call and invoke split(connection).
     }
 
     private void swapWithBackgroundCall(String callId) {
diff --git a/telecomm/java/android/telecomm/InCallAdapter.java b/telecomm/java/android/telecomm/InCallAdapter.java
index 279e47d..08eb03a 100644
--- a/telecomm/java/android/telecomm/InCallAdapter.java
+++ b/telecomm/java/android/telecomm/InCallAdapter.java
@@ -28,8 +28,6 @@
  * given call IDs to execute commands such as {@link #answerCall} for incoming calls or
  * {@link #disconnectCall} for active calls the user would like to end. Some commands are only
  * appropriate for calls in certain states; please consult each method for such limitations.
- * TODO(santoscordon): Needs more/better comments once the API is finalized.
- * TODO(santoscordon): Specify the adapter will stop functioning when there are no more calls.
  */
 public final class InCallAdapter {
     private final IInCallAdapter mAdapter;
@@ -56,8 +54,6 @@
 
     /**
      * Instructs Telecomm to reject the specified call.
-     * TODO(santoscordon): Add reject-with-text-message parameter when that feature
-     * is ported over.
      *
      * @param callId The identifier of the call to reject.
      * @param rejectWithMessage Whether to reject with a text message.
diff --git a/telecomm/java/android/telecomm/InCallService.java b/telecomm/java/android/telecomm/InCallService.java
index 83e2957..a88e1cc 100644
--- a/telecomm/java/android/telecomm/InCallService.java
+++ b/telecomm/java/android/telecomm/InCallService.java
@@ -35,7 +35,7 @@
  * This service is implemented by any app that wishes to provide the user-interface for managing
  * phone calls. Telecomm binds to this service while there exists a live (active or incoming) call,
  * and uses it to notify the in-call app of any live and and recently disconnected calls.
- * TODO(santoscordon): What happens if two or more apps on a given device implement this interface?
+ * TODO: What happens if two or more apps on a given device implement this interface?
  */
 public abstract class InCallService extends Service {
     private static final int MSG_SET_IN_CALL_ADAPTER = 1;
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
index 02610f8..77c0c32 100644
--- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java
@@ -20,7 +20,7 @@
 import android.os.Bundle;
 import android.service.voice.AlwaysOnHotwordDetector;
 import android.service.voice.AlwaysOnHotwordDetector.Callback;
-import android.service.voice.AlwaysOnHotwordDetector.TriggerAudio;
+import android.service.voice.AlwaysOnHotwordDetector.EventPayload;
 import android.service.voice.VoiceInteractionService;
 import android.util.Log;
 
@@ -37,7 +37,7 @@
         }
 
         @Override
-        public void onDetected(TriggerAudio triggerAudio) {
+        public void onDetected(EventPayload eventPayload) {
             Log.i(TAG, "onDetected");
         }
 
@@ -45,6 +45,16 @@
         public void onError() {
             Log.i(TAG, "onError");
         }
+
+        @Override
+        public void onRecognitionPaused() {
+            Log.i(TAG, "onRecognitionPaused");
+        }
+
+        @Override
+        public void onRecognitionResumed() {
+            Log.i(TAG, "onRecognitionResumed");
+        }
     };
 
     private AlwaysOnHotwordDetector mHotwordDetector;