Merge "Remove call to getComponent(StatusBar) from PowerUI."
diff --git a/apex/statsd/.clang-format b/apex/statsd/.clang-format
new file mode 100644
index 0000000..cead3a0
--- /dev/null
+++ b/apex/statsd/.clang-format
@@ -0,0 +1,17 @@
+BasedOnStyle: Google
+AllowShortIfStatementsOnASingleLine: true
+AllowShortFunctionsOnASingleLine: false
+AllowShortLoopsOnASingleLine: true
+BinPackArguments: true
+BinPackParameters: true
+ColumnLimit: 100
+CommentPragmas: NOLINT:.*
+ContinuationIndentWidth: 8
+DerivePointerAlignment: false
+IndentWidth: 4
+PointerAlignment: Left
+TabWidth: 4
+AccessModifierOffset: -4
+IncludeCategories:
+  - Regex:    '^"Log\.h"'
+    Priority:    -1
diff --git a/api/current.txt b/api/current.txt
index 54d33a2..6d2221e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -9490,6 +9490,7 @@
     method @Nullable public android.database.Cursor query(@NonNull android.net.Uri, @Nullable String[], @Nullable String, @Nullable String[], @Nullable String, @Nullable android.os.CancellationSignal);
     method @Nullable public android.database.Cursor query(@NonNull android.net.Uri, @Nullable String[], @Nullable android.os.Bundle, @Nullable android.os.CancellationSignal);
     method public boolean refresh(android.net.Uri, @Nullable android.os.Bundle, @Nullable android.os.CancellationSignal);
+    method @NonNull public final android.content.Context requireContext();
     method public final void restoreCallingIdentity(@NonNull android.content.ContentProvider.CallingIdentity);
     method protected final void setPathPermissions(@Nullable android.content.pm.PathPermission[]);
     method protected final void setReadPermission(@Nullable String);
@@ -30343,6 +30344,8 @@
     method public int describeContents();
     method public android.net.wifi.hotspot2.pps.Credential getCredential();
     method public android.net.wifi.hotspot2.pps.HomeSp getHomeSp();
+    method public long getSubscriptionExpirationTimeInMillis();
+    method public boolean isOsuProvisioned();
     method public void setCredential(android.net.wifi.hotspot2.pps.Credential);
     method public void setHomeSp(android.net.wifi.hotspot2.pps.HomeSp);
     method public void writeToParcel(android.os.Parcel, int);
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 02b6b3e..7de8793 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -834,6 +834,23 @@
     }
 
     /**
+     * Retrieves a Non-Nullable Context this provider is running in, this is intended to be called
+     * after {@link #onCreate}. When called before context was created, an IllegalStateException
+     * will be thrown.
+     * <p>
+     * Note A provider must be declared in the manifest and created automatically by the system,
+     * and context is only available after {@link #onCreate} is called.
+     */
+    @NonNull
+    public final Context requireContext() {
+        final Context ctx = getContext();
+        if (ctx == null) {
+            throw new IllegalStateException("Cannot find context from the provider.");
+        }
+        return ctx;
+    }
+
+    /**
      * Set the calling package, returning the current value (or {@code null})
      * which can be used later to restore the previous state.
      */
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index 91f3a20..79bec92 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -42,20 +42,23 @@
  */
 interface ILocationManager
 {
-    Location getLastLocation(in LocationRequest request, String packageName);
+    Location getLastLocation(in LocationRequest request, String packageName, String featureId);
     boolean getCurrentLocation(in LocationRequest request,
             in ICancellationSignal cancellationSignal, in ILocationListener listener,
-            String packageName, String listenerIdentifier);
+            String packageName, String featureId, String listenerIdentifier);
 
     void requestLocationUpdates(in LocationRequest request, in ILocationListener listener,
-            in PendingIntent intent, String packageName, String listenerIdentifier);
+            in PendingIntent intent, String packageName, String featureId,
+            String listenerIdentifier);
     void removeUpdates(in ILocationListener listener, in PendingIntent intent, String packageName);
 
     void requestGeofence(in LocationRequest request, in Geofence geofence,
-            in PendingIntent intent, String packageName, String listenerIdentifier);
+            in PendingIntent intent, String packageName, String featureId,
+            String listenerIdentifier);
     void removeGeofence(in Geofence fence, in PendingIntent intent, String packageName);
 
-    boolean registerGnssStatusCallback(IGnssStatusListener callback, String packageName);
+    boolean registerGnssStatusCallback(IGnssStatusListener callback, String packageName,
+            String featureId);
     void unregisterGnssStatusCallback(IGnssStatusListener callback);
 
     boolean geocoderIsPresent();
@@ -69,14 +72,14 @@
     boolean sendNiResponse(int notifId, int userResponse);
 
     boolean addGnssMeasurementsListener(in IGnssMeasurementsListener listener,
-             String packageName, String listenerIdentifier);
+             String packageName, String featureId, String listenerIdentifier);
     void injectGnssMeasurementCorrections(in GnssMeasurementCorrections corrections,
             in String packageName);
     long getGnssCapabilities(in String packageName);
     void removeGnssMeasurementsListener(in IGnssMeasurementsListener listener);
 
     boolean addGnssNavigationMessageListener(in IGnssNavigationMessageListener listener,
-             String packageName, String listenerIdentifier);
+             String packageName, String featureId, String listenerIdentifier);
     void removeGnssNavigationMessageListener(in IGnssNavigationMessageListener listener);
 
     int getGnssYearOfHardware();
@@ -84,7 +87,7 @@
 
     int getGnssBatchSize(String packageName);
     boolean addGnssBatchingCallback(in IBatchedLocationCallback callback, String packageName,
-             String listenerIdentifier);
+             String featureId, String listenerIdentifier);
     void removeGnssBatchingCallback();
     boolean startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName);
     void flushGnssBatch(String packageName);
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 9e17e95..010f92f 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -515,7 +515,8 @@
     @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
     public Location getLastLocation() {
         try {
-            return mService.getLastLocation(null, mContext.getPackageName());
+            return mService.getLastLocation(null, mContext.getPackageName(),
+                    mContext.getFeatureId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -547,7 +548,8 @@
                 provider, 0, 0, true);
 
         try {
-            return mService.getLastLocation(request, mContext.getPackageName());
+            return mService.getLastLocation(request, mContext.getPackageName(),
+                    mContext.getFeatureId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -641,7 +643,7 @@
 
         try {
             if (mService.getCurrentLocation(currentLocationRequest, remoteCancellationSignal,
-                    listenerTransport, mContext.getPackageName(),
+                    listenerTransport, mContext.getPackageName(), mContext.getFeatureId(),
                     getListenerIdentifier(consumer))) {
                 listenerTransport.register(mContext.getSystemService(AlarmManager.class),
                         remoteCancellationSignal);
@@ -1085,7 +1087,8 @@
             boolean registered = false;
             try {
                 mService.requestLocationUpdates(locationRequest, transport, null,
-                        mContext.getPackageName(), getListenerIdentifier(listener));
+                        mContext.getPackageName(), mContext.getFeatureId(),
+                        getListenerIdentifier(listener));
                 registered = true;
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
@@ -1130,7 +1133,8 @@
 
         try {
             mService.requestLocationUpdates(locationRequest, null, pendingIntent,
-                    mContext.getPackageName(), getListenerIdentifier(pendingIntent));
+                    mContext.getPackageName(), mContext.getFeatureId(),
+                    getListenerIdentifier(pendingIntent));
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1594,7 +1598,7 @@
         LocationRequest request = new LocationRequest().setExpireIn(expiration);
         try {
             mService.requestGeofence(request, fence, intent, mContext.getPackageName(),
-                    getListenerIdentifier(intent));
+                    mContext.getFeatureId(), getListenerIdentifier(intent));
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1672,7 +1676,7 @@
 
         try {
             mService.requestGeofence(request, fence, intent, mContext.getPackageName(),
-                    getListenerIdentifier(intent));
+                    mContext.getFeatureId(), getListenerIdentifier(intent));
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -2748,7 +2752,7 @@
 
             mListenerTransport = new GnssStatusListener();
             return mService.registerGnssStatusCallback(mListenerTransport,
-                    mContext.getPackageName());
+                    mContext.getPackageName(), mContext.getFeatureId());
         }
 
         @Override
@@ -2808,7 +2812,8 @@
 
             mListenerTransport = new GnssMeasurementsListener();
             return mService.addGnssMeasurementsListener(mListenerTransport,
-                    mContext.getPackageName(), "gnss measurement callback");
+                    mContext.getPackageName(), mContext.getFeatureId(),
+                    "gnss measurement callback");
         }
 
         @Override
@@ -2844,7 +2849,8 @@
 
             mListenerTransport = new GnssNavigationMessageListener();
             return mService.addGnssNavigationMessageListener(mListenerTransport,
-                    mContext.getPackageName(), "gnss navigation callback");
+                    mContext.getPackageName(), mContext.getFeatureId(),
+                    "gnss navigation callback");
         }
 
         @Override
@@ -2880,7 +2886,7 @@
 
             mListenerTransport = new BatchedLocationCallback();
             return mService.addGnssBatchingCallback(mListenerTransport, mContext.getPackageName(),
-                     "batched location callback");
+                     mContext.getFeatureId(), "batched location callback");
         }
 
         @Override
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 7906fa3..7d107dd 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -89,6 +89,10 @@
  * of audio/video files and streams. An example on how to use the methods in
  * this class can be found in {@link android.widget.VideoView}.
  *
+ * <p>MediaPlayer is not thread-safe. Creation of and all access to player instances
+ * should be on the same thread. If registering <a href="#Callbacks">callbacks</a>,
+ * the thread must have a Looper.
+ *
  * <p>Topics covered here are:
  * <ol>
  * <li><a href="#StateDiagram">State Diagram</a>
@@ -305,7 +309,7 @@
  *         </li>
  *     <li>When the playback reaches the end of stream, the playback completes.
  *         <ul>
- *         <li>If the looping mode was being set to <var>true</var>with
+ *         <li>If the looping mode was being set to <var>true</var> with
  *         {@link #setLooping(boolean)}, the MediaPlayer object shall remain in
  *         the <em>Started</em> state.</li>
  *         <li>If the looping mode was set to <var>false
@@ -554,13 +558,13 @@
  * possible runtime errors during playback or streaming. Registration for
  * these events is done by properly setting the appropriate listeners (via calls
  * to
- * {@link #setOnPreparedListener(OnPreparedListener)}setOnPreparedListener,
- * {@link #setOnVideoSizeChangedListener(OnVideoSizeChangedListener)}setOnVideoSizeChangedListener,
- * {@link #setOnSeekCompleteListener(OnSeekCompleteListener)}setOnSeekCompleteListener,
- * {@link #setOnCompletionListener(OnCompletionListener)}setOnCompletionListener,
- * {@link #setOnBufferingUpdateListener(OnBufferingUpdateListener)}setOnBufferingUpdateListener,
- * {@link #setOnInfoListener(OnInfoListener)}setOnInfoListener,
- * {@link #setOnErrorListener(OnErrorListener)}setOnErrorListener, etc).
+ * {@link #setOnPreparedListener(OnPreparedListener) setOnPreparedListener},
+ * {@link #setOnVideoSizeChangedListener(OnVideoSizeChangedListener) setOnVideoSizeChangedListener},
+ * {@link #setOnSeekCompleteListener(OnSeekCompleteListener) setOnSeekCompleteListener},
+ * {@link #setOnCompletionListener(OnCompletionListener) setOnCompletionListener},
+ * {@link #setOnBufferingUpdateListener(OnBufferingUpdateListener) setOnBufferingUpdateListener},
+ * {@link #setOnInfoListener(OnInfoListener) setOnInfoListener},
+ * {@link #setOnErrorListener(OnErrorListener) setOnErrorListener}, etc).
  * In order to receive the respective callback
  * associated with these listeners, applications are required to create
  * MediaPlayer objects on a thread with its own Looper running (main UI
diff --git a/media/jni/soundpool/Android.bp b/media/jni/soundpool/Android.bp
index 35b7b01..e1945dd 100644
--- a/media/jni/soundpool/Android.bp
+++ b/media/jni/soundpool/Android.bp
@@ -3,11 +3,16 @@
 
     srcs: [
         "android_media_SoundPool.cpp",
+        "Sound.cpp",
+        "SoundDecoder.cpp",
+        "SoundManager.cpp",
         "SoundPool.cpp",
-        "SoundPoolThread.cpp",
+        "Stream.cpp",
+        "StreamManager.cpp",
     ],
 
     shared_libs: [
+        "libaudioutils",
         "liblog",
         "libcutils",
         "libutils",
diff --git a/media/jni/soundpool/Sound.cpp b/media/jni/soundpool/Sound.cpp
new file mode 100644
index 0000000..0bbc3e4
--- /dev/null
+++ b/media/jni/soundpool/Sound.cpp
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "SoundPool::Sound"
+#include <utils/Log.h>
+
+#include "Sound.h"
+
+#include <media/NdkMediaCodec.h>
+#include <media/NdkMediaExtractor.h>
+#include <media/NdkMediaFormat.h>
+
+namespace android::soundpool {
+
+constexpr uint32_t kMaxSampleRate = 192000;
+constexpr size_t   kDefaultHeapSize = 1024 * 1024; // 1MB (compatible with low mem devices)
+
+Sound::Sound(int32_t soundID, int fd, int64_t offset, int64_t length)
+    : mSoundID(soundID)
+    , mFd(dup(fd))
+    , mOffset(offset)
+    , mLength(length)
+{
+    ALOGV("%s(soundID=%d, fd=%d, offset=%lld, length=%lld)",
+            __func__, soundID, fd, (long long)offset, (long long)length);
+    ALOGW_IF(mFd == -1, "Unable to dup descriptor %d", fd);
+}
+
+Sound::~Sound()
+{
+    ALOGV("%s(soundID=%d, fd=%d)", __func__, mSoundID, mFd.get());
+}
+
+static status_t decode(int fd, int64_t offset, int64_t length,
+        uint32_t *rate, int32_t *channelCount, audio_format_t *audioFormat,
+        audio_channel_mask_t *channelMask, sp<MemoryHeapBase> heap,
+        size_t *sizeInBytes) {
+    ALOGV("%s(fd=%d, offset=%lld, length=%lld, ...)",
+            __func__, fd, (long long)offset, (long long)length);
+    std::unique_ptr<AMediaExtractor, decltype(&AMediaExtractor_delete)> ex{
+            AMediaExtractor_new(), &AMediaExtractor_delete};
+    status_t err = AMediaExtractor_setDataSourceFd(ex.get(), fd, offset, length);
+
+    if (err != AMEDIA_OK) {
+        return err;
+    }
+
+    *audioFormat = AUDIO_FORMAT_PCM_16_BIT;  // default format for audio codecs.
+    const size_t numTracks = AMediaExtractor_getTrackCount(ex.get());
+    for (size_t i = 0; i < numTracks; i++) {
+        std::unique_ptr<AMediaFormat, decltype(&AMediaFormat_delete)> format{
+                AMediaExtractor_getTrackFormat(ex.get(), i), &AMediaFormat_delete};
+        const char *mime;
+        if (!AMediaFormat_getString(format.get(),  AMEDIAFORMAT_KEY_MIME, &mime)) {
+            return UNKNOWN_ERROR;
+        }
+        if (strncmp(mime, "audio/", 6) == 0) {
+            std::unique_ptr<AMediaCodec, decltype(&AMediaCodec_delete)> codec{
+                    AMediaCodec_createDecoderByType(mime), &AMediaCodec_delete};
+            if (codec == nullptr
+                    || AMediaCodec_configure(codec.get(), format.get(),
+                            nullptr /* window */, nullptr /* drm */, 0 /* flags */) != AMEDIA_OK
+                    || AMediaCodec_start(codec.get()) != AMEDIA_OK
+                    || AMediaExtractor_selectTrack(ex.get(), i) != AMEDIA_OK) {
+                return UNKNOWN_ERROR;
+            }
+
+            bool sawInputEOS = false;
+            bool sawOutputEOS = false;
+            uint8_t* writePos = static_cast<uint8_t*>(heap->getBase());
+            size_t available = heap->getSize();
+            size_t written = 0;
+            format.reset(AMediaCodec_getOutputFormat(codec.get())); // update format.
+
+            while (!sawOutputEOS) {
+                if (!sawInputEOS) {
+                    ssize_t bufidx = AMediaCodec_dequeueInputBuffer(codec.get(), 5000);
+                    ALOGV("%s: input buffer %zd", __func__, bufidx);
+                    if (bufidx >= 0) {
+                        size_t bufsize;
+                        uint8_t * const buf = AMediaCodec_getInputBuffer(
+                                codec.get(), bufidx, &bufsize);
+                        if (buf == nullptr) {
+                            ALOGE("%s: AMediaCodec_getInputBuffer returned nullptr, short decode",
+                                    __func__);
+                            break;
+                        }
+                        int sampleSize = AMediaExtractor_readSampleData(ex.get(), buf, bufsize);
+                        ALOGV("%s: read %d", __func__, sampleSize);
+                        if (sampleSize < 0) {
+                            sampleSize = 0;
+                            sawInputEOS = true;
+                            ALOGV("%s: EOS", __func__);
+                        }
+                        const int64_t presentationTimeUs = AMediaExtractor_getSampleTime(ex.get());
+
+                        const media_status_t mstatus = AMediaCodec_queueInputBuffer(
+                                codec.get(), bufidx,
+                                0 /* offset */, sampleSize, presentationTimeUs,
+                                sawInputEOS ? AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM : 0);
+                        if (mstatus != AMEDIA_OK) {
+                            // AMEDIA_ERROR_UNKNOWN == { -ERANGE -EINVAL -EACCES }
+                            ALOGE("%s: AMediaCodec_queueInputBuffer returned status %d,"
+                                    "short decode",
+                                    __func__, (int)mstatus);
+                            break;
+                        }
+                        (void)AMediaExtractor_advance(ex.get());
+                    }
+                }
+
+                AMediaCodecBufferInfo info;
+                const int status = AMediaCodec_dequeueOutputBuffer(codec.get(), &info, 1);
+                ALOGV("%s: dequeueoutput returned: %d", __func__, status);
+                if (status >= 0) {
+                    if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
+                        ALOGV("%s: output EOS", __func__);
+                        sawOutputEOS = true;
+                    }
+                    ALOGV("%s: got decoded buffer size %d", __func__, info.size);
+
+                    const uint8_t * const buf = AMediaCodec_getOutputBuffer(
+                            codec.get(), status, nullptr /* out_size */);
+                    if (buf == nullptr) {
+                        ALOGE("%s: AMediaCodec_getOutputBuffer returned nullptr, short decode",
+                                __func__);
+                        break;
+                    }
+                    const size_t dataSize = std::min((size_t)info.size, available);
+                    memcpy(writePos, buf + info.offset, dataSize);
+                    writePos += dataSize;
+                    written += dataSize;
+                    available -= dataSize;
+                    const media_status_t mstatus = AMediaCodec_releaseOutputBuffer(
+                            codec.get(), status, false /* render */);
+                    if (mstatus != AMEDIA_OK) {
+                        // AMEDIA_ERROR_UNKNOWN == { -ERANGE -EINVAL -EACCES }
+                        ALOGE("%s: AMediaCodec_releaseOutputBuffer"
+                                " returned status %d, short decode",
+                                __func__, (int)mstatus);
+                        break;
+                    }
+                    if (available == 0) {
+                        // there might be more data, but there's no space for it
+                        sawOutputEOS = true;
+                    }
+                } else if (status == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
+                    ALOGV("%s: output buffers changed", __func__);
+                } else if (status == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
+                    format.reset(AMediaCodec_getOutputFormat(codec.get())); // update format
+                    ALOGV("%s: format changed to: %s",
+                           __func__, AMediaFormat_toString(format.get()));
+                } else if (status == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
+                    ALOGV("%s: no output buffer right now", __func__);
+                } else if (status <= AMEDIA_ERROR_BASE) {
+                    ALOGE("%s: decode error: %d", __func__, status);
+                    break;
+                } else {
+                    ALOGV("%s: unexpected info code: %d", __func__, status);
+                }
+            }
+
+            (void)AMediaCodec_stop(codec.get());
+            if (!AMediaFormat_getInt32(
+                    format.get(), AMEDIAFORMAT_KEY_SAMPLE_RATE, (int32_t*) rate) ||
+                !AMediaFormat_getInt32(
+                    format.get(), AMEDIAFORMAT_KEY_CHANNEL_COUNT, channelCount)) {
+                return UNKNOWN_ERROR;
+            }
+            if (!AMediaFormat_getInt32(format.get(), AMEDIAFORMAT_KEY_CHANNEL_MASK,
+                    (int32_t*) channelMask)) {
+                *channelMask = AUDIO_CHANNEL_NONE;
+            }
+            *sizeInBytes = written;
+            return OK;
+        }
+    }
+    return UNKNOWN_ERROR;
+}
+
+status_t Sound::doLoad()
+{
+    ALOGV("%s()", __func__);
+    status_t status = NO_INIT;
+    if (mFd.get() != -1) {
+        mHeap = new MemoryHeapBase(kDefaultHeapSize);
+
+        ALOGV("%s: start decode", __func__);
+        uint32_t sampleRate;
+        int32_t channelCount;
+        audio_format_t format;
+        audio_channel_mask_t channelMask;
+        status_t status = decode(mFd.get(), mOffset, mLength, &sampleRate, &channelCount, &format,
+                        &channelMask, mHeap, &mSizeInBytes);
+        ALOGV("%s: close(%d)", __func__, mFd.get());
+        mFd.reset();  // close
+
+        if (status != NO_ERROR) {
+            ALOGE("%s: unable to load sound", __func__);
+        } else if (sampleRate > kMaxSampleRate) {
+            ALOGE("%s: sample rate (%u) out of range", __func__, sampleRate);
+            status = BAD_VALUE;
+        } else if (channelCount < 1 || channelCount > FCC_8) {
+            ALOGE("%s: sample channel count (%d) out of range", __func__, channelCount);
+            status = BAD_VALUE;
+        } else {
+            // Correctly loaded, proper parameters
+            ALOGV("%s: pointer = %p, sizeInBytes = %zu, sampleRate = %u, channelCount = %d",
+                  __func__, mHeap->getBase(), mSizeInBytes, sampleRate, channelCount);
+            mData = new MemoryBase(mHeap, 0, mSizeInBytes);
+            mSampleRate = sampleRate;
+            mChannelCount = channelCount;
+            mFormat = format;
+            mChannelMask = channelMask;
+            mState = READY;  // this should be last, as it is an atomic sync point
+            return NO_ERROR;
+        }
+    } else {
+        ALOGE("%s: uninitialized fd, dup failed", __func__);
+    }
+    // ERROR handling
+    mHeap.clear();
+    mState = DECODE_ERROR; // this should be last, as it is an atomic sync point
+    return status;
+}
+
+} // namespace android::soundpool
diff --git a/media/jni/soundpool/Sound.h b/media/jni/soundpool/Sound.h
new file mode 100644
index 0000000..efe940a
--- /dev/null
+++ b/media/jni/soundpool/Sound.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#pragma once
+
+#include <android-base/unique_fd.h>
+#include <binder/MemoryBase.h>
+#include <binder/MemoryHeapBase.h>
+#include <system/audio.h>
+
+namespace android::soundpool {
+
+class SoundDecoder;
+
+/**
+ * Sound is a resource used by SoundPool, referenced by soundID.
+ *
+ * After loading, it is effectively const so no locking required.
+ * However, in order to guarantee that all the values have been
+ * written properly and read properly, we use the mState as an atomic synchronization
+ * point.  So if getState() shows READY, then all the other getters may
+ * be safely read.
+ *
+ * Technical details:
+ * We access the mState atomic value through memory_order_seq_cst
+ *
+ * https://en.cppreference.com/w/cpp/atomic/memory_order
+ *
+ * which provides memory barriers.  So if the last value written by the SoundDecoder
+ * is mState, then the compiler ensures no other prior writes by SoundDecoder will be
+ * reordered afterwards, and memory barrier is placed (as necessary) to ensure the
+ * cache is visible to other processors.
+ *
+ * Likewise, if the first value read by SoundPool is mState,
+ * the compiler ensures no reads for that thread will be reordered before mState is read,
+ * and a memory barrier is placed (as necessary) to ensure that the cache is properly
+ * updated with other processor's writes before reading.
+ *
+ * See https://developer.android.com/training/articles/smp for discussions about
+ * the variant load-acquire, store-release semantics.
+ */
+class Sound {
+    friend SoundDecoder;  // calls doLoad().
+
+public:
+    enum sound_state : int32_t { LOADING, READY, DECODE_ERROR };
+    // A sound starts in the LOADING state and transitions only once
+    // to either READY or DECODE_ERROR when doLoad() is called.
+
+    Sound(int soundID, int fd, int64_t offset, int64_t length);
+    ~Sound();
+
+    int32_t getSoundID() const { return mSoundID; }
+    int32_t getChannelCount() const { return mChannelCount; }
+    uint32_t getSampleRate() const { return mSampleRate; }
+    audio_format_t getFormat() const { return mFormat; }
+    audio_channel_mask_t getChannelMask() const { return mChannelMask; }
+    size_t getSizeInBytes() const { return mSizeInBytes; }
+    sound_state getState() const { return mState; }
+    uint8_t* getData() const { return static_cast<uint8_t*>(mData->unsecurePointer()); }
+    sp<IMemory> getIMemory() const { return mData; }
+
+private:
+    status_t doLoad();  // only SoundDecoder accesses this.
+
+    size_t               mSizeInBytes = 0;
+    const int32_t        mSoundID;
+    uint32_t             mSampleRate = 0;
+    std::atomic<sound_state> mState = LOADING; // used as synchronization point
+    int32_t              mChannelCount = 0;
+    audio_format_t       mFormat = AUDIO_FORMAT_INVALID;
+    audio_channel_mask_t mChannelMask = AUDIO_CHANNEL_NONE;
+    base::unique_fd      mFd;     // initialized in constructor, reset to -1 after loading
+    const int64_t        mOffset; // int64_t to match java long, see off64_t
+    const int64_t        mLength; // int64_t to match java long, see off64_t
+    sp<IMemory>          mData;
+    sp<MemoryHeapBase>   mHeap;
+};
+
+} // namespace android::soundpool
diff --git a/media/jni/soundpool/SoundDecoder.cpp b/media/jni/soundpool/SoundDecoder.cpp
new file mode 100644
index 0000000..12200ef
--- /dev/null
+++ b/media/jni/soundpool/SoundDecoder.cpp
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "SoundPool::SoundDecoder"
+#include "utils/Log.h"
+
+#include "SoundDecoder.h"
+
+namespace android::soundpool {
+
+// Maximum Samples that can be background decoded before we block the caller.
+static constexpr size_t kMaxQueueSize = 128;
+
+// The amount of time we wait for a new Sound decode request
+// before the SoundDecoder thread closes.
+static constexpr int32_t kWaitTimeBeforeCloseMs = 1000;
+
+SoundDecoder::SoundDecoder(SoundManager* soundManager, size_t threads)
+    : mSoundManager(soundManager)
+{
+    ALOGV("%s(%p, %zu)", __func__, soundManager, threads);
+    // ThreadPool is created, but we don't launch any threads.
+    mThreadPool = std::make_unique<ThreadPool>(
+            std::min(threads, (size_t)std::thread::hardware_concurrency()),
+            "SoundDecoder_");
+}
+
+SoundDecoder::~SoundDecoder()
+{
+    ALOGV("%s()", __func__);
+    quit();
+}
+
+void SoundDecoder::quit()
+{
+    ALOGV("%s()", __func__);
+    {
+        std::lock_guard lock(mLock);
+        mQuit = true;
+        mQueueSpaceAvailable.notify_all(); // notify all load waiters
+        mQueueDataAvailable.notify_all();  // notify all worker threads
+    }
+    mThreadPool->quit();
+}
+
+void SoundDecoder::run(int32_t id __unused /* ALOGV only */)
+{
+    ALOGV("%s(%d): entering", __func__, id);
+    std::unique_lock lock(mLock);
+    while (!mQuit) {
+        if (mSoundIDs.size() == 0) {
+            ALOGV("%s(%d): waiting", __func__, id);
+            mQueueDataAvailable.wait_for(
+                    lock, std::chrono::duration<int32_t, std::milli>(kWaitTimeBeforeCloseMs));
+            if (mSoundIDs.size() == 0) {
+                break; // no new sound, exit this thread.
+            }
+            continue;
+        }
+        const int32_t soundID = mSoundIDs.front();
+        mSoundIDs.pop_front();
+        mQueueSpaceAvailable.notify_one();
+        ALOGV("%s(%d): processing soundID: %d  size: %zu", __func__, id, soundID, mSoundIDs.size());
+        lock.unlock();
+        std::shared_ptr<Sound> sound = mSoundManager->findSound(soundID);
+        status_t status = NO_INIT;
+        if (sound.get() != nullptr) {
+            status = sound->doLoad();
+        }
+        ALOGV("%s(%d): notifying loaded soundID:%d  status:%d", __func__, id, soundID, status);
+        mSoundManager->notify(SoundPoolEvent(SoundPoolEvent::SOUND_LOADED, soundID, status));
+        lock.lock();
+    }
+    ALOGV("%s(%d): exiting", __func__, id);
+}
+
+void SoundDecoder::loadSound(int32_t soundID)
+{
+    ALOGV("%s(%d)", __func__, soundID);
+    size_t pendingSounds;
+    {
+        std::unique_lock lock(mLock);
+        while (mSoundIDs.size() == kMaxQueueSize) {
+            if (mQuit) return;
+            ALOGV("%s: waiting soundID: %d size: %zu", __func__, soundID, mSoundIDs.size());
+            mQueueSpaceAvailable.wait(lock);
+        }
+        if (mQuit) return;
+        mSoundIDs.push_back(soundID);
+        mQueueDataAvailable.notify_one();
+        ALOGV("%s: adding soundID: %d  size: %zu", __func__, soundID, mSoundIDs.size());
+        pendingSounds = mSoundIDs.size();
+    }
+    // Launch threads as needed.  The "as needed" is weakly consistent as we release mLock.
+    if (pendingSounds > mThreadPool->getActiveThreadCount()) {
+        const int32_t id __unused = mThreadPool->launch([this](int32_t id) { run(id); });
+        ALOGV_IF(id != 0, "%s: launched thread %d", __func__, id);
+    }
+}
+
+} // end namespace android::soundpool
diff --git a/media/jni/soundpool/SoundDecoder.h b/media/jni/soundpool/SoundDecoder.h
new file mode 100644
index 0000000..1288943
--- /dev/null
+++ b/media/jni/soundpool/SoundDecoder.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#pragma once
+
+#include "SoundPool.h"
+
+#include <deque>
+#include <mutex>
+
+namespace android::soundpool {
+
+/**
+ * SoundDecoder handles background decoding tasks.
+ */
+class SoundDecoder {
+public:
+    SoundDecoder(SoundManager* soundManager, size_t threads);
+    ~SoundDecoder();
+    void loadSound(int32_t soundID);
+    void quit();
+
+private:
+    void run(int32_t id);                       // The decode thread function.
+
+    SoundManager* const     mSoundManager;      // set in constructor, has own lock
+    std::unique_ptr<ThreadPool> mThreadPool;    // set in constructor, has own lock
+
+    std::mutex              mLock;
+    std::condition_variable mQueueSpaceAvailable;
+    std::condition_variable mQueueDataAvailable;
+
+    std::deque<int32_t>     mSoundIDs;            // GUARDED_BY(mLock);
+    bool                    mQuit = false;        // GUARDED_BY(mLock);
+};
+
+} // end namespace android::soundpool
+
diff --git a/media/jni/soundpool/SoundManager.cpp b/media/jni/soundpool/SoundManager.cpp
new file mode 100644
index 0000000..3c625bf
--- /dev/null
+++ b/media/jni/soundpool/SoundManager.cpp
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "SoundPool::SoundManager"
+#include <utils/Log.h>
+
+#include "SoundManager.h"
+
+#include <thread>
+
+#include "SoundDecoder.h"
+
+namespace android::soundpool {
+
+static const size_t kDecoderThreads = std::thread::hardware_concurrency() >= 4 ? 2 : 1;
+
+SoundManager::SoundManager()
+    : mDecoder{std::make_unique<SoundDecoder>(this, kDecoderThreads)}
+{
+    ALOGV("%s()", __func__);
+}
+
+SoundManager::~SoundManager()
+{
+    ALOGV("%s()", __func__);
+    mDecoder->quit();
+
+    std::lock_guard lock(mSoundManagerLock);
+    mSounds.clear();
+}
+
+int32_t SoundManager::load(int fd, int64_t offset, int64_t length, int32_t priority __unused)
+{
+    ALOGV("%s(fd=%d, offset=%lld, length=%lld, priority=%d)",
+            __func__, fd, (long long)offset, (long long)length, priority);
+    int32_t soundID;
+    {
+        std::lock_guard lock(mSoundManagerLock);
+        // mNextSoundID is always positive and does not "integer overflow"
+        do {
+            mNextSoundID = mNextSoundID == INT32_MAX ? 1 : mNextSoundID + 1;
+        } while (findSound_l(mNextSoundID) != nullptr);
+        soundID = mNextSoundID;
+        auto sound = std::make_shared<Sound>(soundID, fd, offset, length);
+        mSounds.emplace(soundID, sound);
+    }
+    // mDecoder->loadSound() must be called outside of mSoundManagerLock.
+    // mDecoder->loadSound() may block on mDecoder message queue space;
+    // the message queue emptying may block on SoundManager::findSound().
+    //
+    // It is theoretically possible that sound loads might decode out-of-order.
+    mDecoder->loadSound(soundID);
+    return soundID;
+}
+
+bool SoundManager::unload(int32_t soundID)
+{
+    ALOGV("%s(soundID=%d)", __func__, soundID);
+    std::lock_guard lock(mSoundManagerLock);
+    return mSounds.erase(soundID) > 0; // erase() returns number of sounds removed.
+}
+
+std::shared_ptr<Sound> SoundManager::findSound(int32_t soundID) const
+{
+    std::lock_guard lock(mSoundManagerLock);
+    return findSound_l(soundID);
+}
+
+std::shared_ptr<Sound> SoundManager::findSound_l(int32_t soundID) const
+{
+    auto it = mSounds.find(soundID);
+    return it != mSounds.end() ? it->second : nullptr;
+}
+
+void SoundManager::setCallback(SoundPool *soundPool, SoundPoolCallback* callback, void* user)
+{
+    mCallbackHandler.setCallback(soundPool, callback, user);
+}
+
+void SoundManager::notify(SoundPoolEvent event)
+{
+    mCallbackHandler.notify(event);
+}
+
+void* SoundManager::getUserData() const
+{
+    return mCallbackHandler.getUserData();
+}
+
+} // namespace android::soundpool
diff --git a/media/jni/soundpool/SoundManager.h b/media/jni/soundpool/SoundManager.h
new file mode 100644
index 0000000..9201e78
--- /dev/null
+++ b/media/jni/soundpool/SoundManager.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#pragma once
+
+#include "Sound.h"
+
+#include <mutex>
+#include <unordered_map>
+
+namespace android {
+
+class SoundPool;
+
+// for queued events
+class SoundPoolEvent {
+public:
+    explicit SoundPoolEvent(int msg, int arg1 = 0, int arg2 = 0) :
+        mMsg(msg), mArg1(arg1), mArg2(arg2) {}
+    const int mMsg;   // MessageType
+    const int mArg1;  // soundID
+    const int mArg2;  // status
+    enum MessageType { INVALID, SOUND_LOADED };
+};
+
+// callback function prototype
+typedef void SoundPoolCallback(SoundPoolEvent event, SoundPool* soundPool, void* user);
+
+} // namespace android
+
+namespace android::soundpool {
+
+// This class manages Sounds for the SoundPool.
+class SoundManager {
+public:
+    SoundManager();
+    ~SoundManager();
+
+    // Matches corresponding SoundPool API functions
+    int32_t load(int fd, int64_t offset, int64_t length, int32_t priority);
+    bool unload(int32_t soundID);
+    void setCallback(SoundPool* soundPool, SoundPoolCallback* callback, void* user);
+    void* getUserData() const;
+
+    // SoundPool and SoundDecoder access
+    std::shared_ptr<Sound> findSound(int32_t soundID) const;
+
+    // from the SoundDecoder
+    void notify(SoundPoolEvent event);
+
+private:
+
+    // CallbackHandler is used to manage notifications back to the app when a sound
+    // has been loaded.  It uses a recursive lock to allow setting the callback
+    // during the callback.
+    class CallbackHandler {
+    public:
+        void setCallback(SoundPool *soundPool, SoundPoolCallback* callback, void* userData)
+        {
+            std::lock_guard<std::recursive_mutex> lock(mCallbackLock);
+            mSoundPool = soundPool;
+            mCallback = callback;
+            mUserData = userData;
+        }
+        void notify(SoundPoolEvent event) const
+        {
+            std::lock_guard<std::recursive_mutex> lock(mCallbackLock);
+            if (mCallback != nullptr) {
+                mCallback(event, mSoundPool, mUserData);
+                // Note: mCallback may call setCallback().
+                // so mCallback, mUserData may have changed.
+            }
+        }
+        void* getUserData() const
+        {
+            std::lock_guard<std::recursive_mutex> lock(mCallbackLock);
+            return mUserData;
+        }
+    private:
+        mutable std::recursive_mutex  mCallbackLock; // allow mCallback to setCallback().
+        SoundPool*          mSoundPool = nullptr; // GUARDED_BY(mCallbackLock)
+        SoundPoolCallback*  mCallback = nullptr;  // GUARDED_BY(mCallbackLock)
+        void*               mUserData = nullptr;  // GUARDED_BY(mCallbackLock)
+    };
+
+    std::shared_ptr<Sound> findSound_l(int32_t soundID) const;
+
+    // The following variables are initialized in constructor and can be accessed anytime.
+    CallbackHandler         mCallbackHandler;              // has its own lock
+    const std::unique_ptr<SoundDecoder> mDecoder;          // has its own lock
+
+    mutable std::mutex      mSoundManagerLock;
+    std::unordered_map<int, std::shared_ptr<Sound>> mSounds; // GUARDED_BY(mSoundManagerLock)
+    int32_t                 mNextSoundID = 0;    // GUARDED_BY(mSoundManagerLock)
+};
+
+} // namespace android::soundpool
diff --git a/media/jni/soundpool/SoundPool.cpp b/media/jni/soundpool/SoundPool.cpp
index 102bbf0..ac44843 100644
--- a/media/jni/soundpool/SoundPool.cpp
+++ b/media/jni/soundpool/SoundPool.cpp
@@ -16,1124 +16,230 @@
 
 //#define LOG_NDEBUG 0
 #define LOG_TAG "SoundPool"
-
-#include <chrono>
-#include <inttypes.h>
-#include <thread>
 #include <utils/Log.h>
 
-#define USE_SHARED_MEM_BUFFER
+#include <algorithm>
+#include <thread>
 
-#include <media/AudioTrack.h>
 #include "SoundPool.h"
-#include "SoundPoolThread.h"
-#include <media/NdkMediaCodec.h>
-#include <media/NdkMediaExtractor.h>
-#include <media/NdkMediaFormat.h>
 
 namespace android
 {
 
-int kDefaultBufferCount = 4;
-uint32_t kMaxSampleRate = 48000;
-uint32_t kDefaultSampleRate = 44100;
-uint32_t kDefaultFrameCount = 1200;
-size_t kDefaultHeapSize = 1024 * 1024; // 1MB
+// kManagerThreads = 1 historically.
+// Not really necessary to have more than one, but it does speed things up by about
+// 25% having 2 threads instead of 1 when playing many sounds.  Having many threads
+// could starve other AudioFlinger clients with SoundPool activity. It may also cause
+// issues with app loading, e.g. Camera.
+static const size_t kStreamManagerThreads = std::thread::hardware_concurrency() >= 4 ? 2 : 1;
 
+// kUseApiLock = true prior to R.
+// Set to true to prevent multiple users access internal to the SoundPool API.
+// Set to false to make the SoundPool methods weakly consistent.  When set to false,
+// only AutoPause and AutoResume are locked, which are the only two methods that
+// require API level locking for consistency.
+static constexpr bool kUseApiLock = false;
 
-SoundPool::SoundPool(int maxChannels, const audio_attributes_t* pAttributes)
+namespace {
+// Check input arguments to SoundPool - return "true" to reject request.
+
+bool checkVolume(float *leftVolume, float *rightVolume)
 {
-    ALOGV("SoundPool constructor: maxChannels=%d, attr.usage=%d, attr.flags=0x%x, attr.tags=%s",
-            maxChannels, pAttributes->usage, pAttributes->flags, pAttributes->tags);
-
-    // check limits
-    mMaxChannels = maxChannels;
-    if (mMaxChannels < 1) {
-        mMaxChannels = 1;
+    if (*leftVolume != std::clamp(*leftVolume, 0.f, 1.f) ||
+            *rightVolume != std::clamp(*rightVolume, 0.f, 1.f)) {
+        ALOGI("volume l=%f r=%f out of (0.f, 1.f) bounds, using 1.f", *leftVolume, *rightVolume);
+        // for backward compatibility use 1.f.
+        *leftVolume = *rightVolume = 1.f;
     }
-    else if (mMaxChannels > 32) {
-        mMaxChannels = 32;
+    return false;
+}
+
+bool checkRate(float *rate)
+{
+    if (*rate != std::clamp(*rate, 0.125f, 8.f)) {
+        ALOGI("rate %f out of (0.125f, 8.f) bounds, clamping", *rate);
+        // for backward compatibility just clamp
+        *rate = std::clamp(*rate, 0.125f, 8.f);
     }
-    ALOGW_IF(maxChannels != mMaxChannels, "App requested %d channels", maxChannels);
+    return false;
+}
 
-    mQuit = false;
-    mMuted = false;
-    mDecodeThread = 0;
-    memcpy(&mAttributes, pAttributes, sizeof(audio_attributes_t));
-    mAllocated = 0;
-    mNextSampleID = 0;
-    mNextChannelID = 0;
-
-    mCallback = 0;
-    mUserData = 0;
-
-    mChannelPool = new SoundChannel[mMaxChannels];
-    for (int i = 0; i < mMaxChannels; ++i) {
-        mChannelPool[i].init(this);
-        mChannels.push_back(&mChannelPool[i]);
+bool checkPriority(int32_t *priority)
+{
+    if (*priority < 0) {
+        ALOGI("negative priority %d, should be >= 0.", *priority);
+        // for backward compatibility, ignore.
     }
+    return false;
+}
 
-    // start decode thread
-    startThreads();
+bool checkLoop(int32_t *loop)
+{
+    if (*loop < -1) {
+        ALOGI("loop %d, should be >= -1", *loop);
+        *loop = -1;
+    }
+    return false;
+}
+
+} // namespace
+
+SoundPool::SoundPool(int32_t maxStreams, const audio_attributes_t* attributes)
+    : mStreamManager(maxStreams, kStreamManagerThreads, attributes)
+{
+    ALOGV("%s(maxStreams=%d, attr={ content_type=%d, usage=%d, flags=0x%x, tags=%s })",
+            __func__, maxStreams,
+            attributes->content_type, attributes->usage, attributes->flags, attributes->tags);
 }
 
 SoundPool::~SoundPool()
 {
-    ALOGV("SoundPool destructor");
-    mDecodeThread->quit();
-    quit();
-
-    Mutex::Autolock lock(&mLock);
-
-    mChannels.clear();
-    if (mChannelPool)
-        delete [] mChannelPool;
-    // clean up samples
-    ALOGV("clear samples");
-    mSamples.clear();
-
-    if (mDecodeThread)
-        delete mDecodeThread;
+    ALOGV("%s()", __func__);
 }
 
-void SoundPool::addToRestartList(SoundChannel* channel)
+int32_t SoundPool::load(int fd, int64_t offset, int64_t length, int32_t priority)
 {
-    Mutex::Autolock lock(&mRestartLock);
-    if (!mQuit) {
-        mRestart.push_back(channel);
-        mCondition.signal();
-    }
+    ALOGV("%s(fd=%d, offset=%lld, length=%lld, priority=%d)",
+            __func__, fd, (long long)offset, (long long)length, priority);
+    auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+    return mSoundManager.load(fd, offset, length, priority);
 }
 
-void SoundPool::addToStopList(SoundChannel* channel)
+bool SoundPool::unload(int32_t soundID)
 {
-    Mutex::Autolock lock(&mRestartLock);
-    if (!mQuit) {
-        mStop.push_back(channel);
-        mCondition.signal();
-    }
+    ALOGV("%s(%d)", __func__, soundID);
+    auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+    return mSoundManager.unload(soundID);
 }
 
-int SoundPool::beginThread(void* arg)
+int32_t SoundPool::play(int32_t soundID, float leftVolume, float rightVolume,
+        int32_t priority, int32_t loop, float rate)
 {
-    SoundPool* p = (SoundPool*)arg;
-    return p->run();
-}
+    ALOGV("%s(soundID=%d, leftVolume=%f, rightVolume=%f, priority=%d, loop=%d, rate=%f)",
+            __func__, soundID, leftVolume, rightVolume, priority, loop, rate);
 
-int SoundPool::run()
-{
-    mRestartLock.lock();
-    while (!mQuit) {
-        mCondition.wait(mRestartLock);
-        ALOGV("awake");
-        if (mQuit) break;
+    // New for R: check arguments to ensure track can be created.
+    // If SoundPool defers the creation of the AudioTrack to the StreamManager thread,
+    // the failure to create may not be visible to the caller, so this precheck is needed.
+    if (checkVolume(&leftVolume, &rightVolume)
+            || checkPriority(&priority)
+            || checkLoop(&loop)
+            || checkRate(&rate)) return 0;
 
-        while (!mStop.empty()) {
-            SoundChannel* channel;
-            ALOGV("Getting channel from stop list");
-            List<SoundChannel* >::iterator iter = mStop.begin();
-            channel = *iter;
-            mStop.erase(iter);
-            mRestartLock.unlock();
-            if (channel != 0) {
-                Mutex::Autolock lock(&mLock);
-                channel->stop();
-            }
-            mRestartLock.lock();
-            if (mQuit) break;
-        }
-
-        while (!mRestart.empty()) {
-            SoundChannel* channel;
-            ALOGV("Getting channel from list");
-            List<SoundChannel*>::iterator iter = mRestart.begin();
-            channel = *iter;
-            mRestart.erase(iter);
-            mRestartLock.unlock();
-            if (channel != 0) {
-                Mutex::Autolock lock(&mLock);
-                channel->nextEvent();
-            }
-            mRestartLock.lock();
-            if (mQuit) break;
-        }
-    }
-
-    mStop.clear();
-    mRestart.clear();
-    mCondition.signal();
-    mRestartLock.unlock();
-    ALOGV("goodbye");
-    return 0;
-}
-
-void SoundPool::quit()
-{
-    mRestartLock.lock();
-    mQuit = true;
-    mCondition.signal();
-    mCondition.wait(mRestartLock);
-    ALOGV("return from quit");
-    mRestartLock.unlock();
-}
-
-bool SoundPool::startThreads()
-{
-    createThreadEtc(beginThread, this, "SoundPool");
-    if (mDecodeThread == NULL)
-        mDecodeThread = new SoundPoolThread(this);
-    return mDecodeThread != NULL;
-}
-
-sp<Sample> SoundPool::findSample(int sampleID)
-{
-    Mutex::Autolock lock(&mLock);
-    return findSample_l(sampleID);
-}
-
-sp<Sample> SoundPool::findSample_l(int sampleID)
-{
-    return mSamples.valueFor(sampleID);
-}
-
-SoundChannel* SoundPool::findChannel(int channelID)
-{
-    for (int i = 0; i < mMaxChannels; ++i) {
-        if (mChannelPool[i].channelID() == channelID) {
-            return &mChannelPool[i];
-        }
-    }
-    return NULL;
-}
-
-SoundChannel* SoundPool::findNextChannel(int channelID)
-{
-    for (int i = 0; i < mMaxChannels; ++i) {
-        if (mChannelPool[i].nextChannelID() == channelID) {
-            return &mChannelPool[i];
-        }
-    }
-    return NULL;
-}
-
-int SoundPool::load(int fd, int64_t offset, int64_t length, int priority __unused)
-{
-    ALOGV("load: fd=%d, offset=%" PRId64 ", length=%" PRId64 ", priority=%d",
-            fd, offset, length, priority);
-    int sampleID;
-    {
-        Mutex::Autolock lock(&mLock);
-        sampleID = ++mNextSampleID;
-        sp<Sample> sample = new Sample(sampleID, fd, offset, length);
-        mSamples.add(sampleID, sample);
-        sample->startLoad();
-    }
-    // mDecodeThread->loadSample() must be called outside of mLock.
-    // mDecodeThread->loadSample() may block on mDecodeThread message queue space;
-    // the message queue emptying may block on SoundPool::findSample().
-    //
-    // It theoretically possible that sample loads might decode out-of-order.
-    mDecodeThread->loadSample(sampleID);
-    return sampleID;
-}
-
-bool SoundPool::unload(int sampleID)
-{
-    ALOGV("unload: sampleID=%d", sampleID);
-    Mutex::Autolock lock(&mLock);
-    return mSamples.removeItem(sampleID) >= 0; // removeItem() returns index or BAD_VALUE
-}
-
-int SoundPool::play(int sampleID, float leftVolume, float rightVolume,
-        int priority, int loop, float rate)
-{
-    ALOGV("play sampleID=%d, leftVolume=%f, rightVolume=%f, priority=%d, loop=%d, rate=%f",
-            sampleID, leftVolume, rightVolume, priority, loop, rate);
-    SoundChannel* channel;
-    int channelID;
-
-    Mutex::Autolock lock(&mLock);
-
-    if (mQuit) {
-        return 0;
-    }
-    // is sample ready?
-    sp<Sample> sample(findSample_l(sampleID));
-    if ((sample == 0) || (sample->state() != Sample::READY)) {
-        ALOGW("  sample %d not READY", sampleID);
+    auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+    const std::shared_ptr<soundpool::Sound> sound = mSoundManager.findSound(soundID);
+    if (sound == nullptr || sound->getState() != soundpool::Sound::READY) {
+        ALOGW("%s soundID %d not READY", __func__, soundID);
         return 0;
     }
 
-    dump();
-
-    // allocate a channel
-    channel = allocateChannel_l(priority, sampleID);
-
-    // no channel allocated - return 0
-    if (!channel) {
-        ALOGV("No channel allocated");
-        return 0;
-    }
-
-    channelID = ++mNextChannelID;
-
-    ALOGV("play channel %p state = %d", channel, channel->state());
-    channel->play(sample, channelID, leftVolume, rightVolume, priority, loop, rate);
-    return channelID;
-}
-
-SoundChannel* SoundPool::allocateChannel_l(int priority, int sampleID)
-{
-    List<SoundChannel*>::iterator iter;
-    SoundChannel* channel = NULL;
-
-    // check if channel for given sampleID still available
-    if (!mChannels.empty()) {
-        for (iter = mChannels.begin(); iter != mChannels.end(); ++iter) {
-            if (sampleID == (*iter)->getPrevSampleID() && (*iter)->state() == SoundChannel::IDLE) {
-                channel = *iter;
-                mChannels.erase(iter);
-                ALOGV("Allocated recycled channel for same sampleID");
-                break;
-            }
-        }
-    }
-
-    // allocate any channel
-    if (!channel && !mChannels.empty()) {
-        iter = mChannels.begin();
-        if (priority >= (*iter)->priority()) {
-            channel = *iter;
-            mChannels.erase(iter);
-            ALOGV("Allocated active channel");
-        }
-    }
-
-    // update priority and put it back in the list
-    if (channel) {
-        channel->setPriority(priority);
-        for (iter = mChannels.begin(); iter != mChannels.end(); ++iter) {
-            if (priority < (*iter)->priority()) {
-                break;
-            }
-        }
-        mChannels.insert(iter, channel);
-    }
-    return channel;
-}
-
-// move a channel from its current position to the front of the list
-void SoundPool::moveToFront_l(SoundChannel* channel)
-{
-    for (List<SoundChannel*>::iterator iter = mChannels.begin(); iter != mChannels.end(); ++iter) {
-        if (*iter == channel) {
-            mChannels.erase(iter);
-            mChannels.push_front(channel);
-            break;
-        }
-    }
-}
-
-void SoundPool::pause(int channelID)
-{
-    ALOGV("pause(%d)", channelID);
-    Mutex::Autolock lock(&mLock);
-    SoundChannel* channel = findChannel(channelID);
-    if (channel) {
-        channel->pause();
-    }
+    const int32_t streamID = mStreamManager.queueForPlay(
+            sound, soundID, leftVolume, rightVolume, priority, loop, rate);
+    ALOGV("%s returned %d", __func__, streamID);
+    return streamID;
 }
 
 void SoundPool::autoPause()
 {
-    ALOGV("autoPause()");
-    Mutex::Autolock lock(&mLock);
-    for (int i = 0; i < mMaxChannels; ++i) {
-        SoundChannel* channel = &mChannelPool[i];
-        channel->autoPause();
-    }
-}
-
-void SoundPool::resume(int channelID)
-{
-    ALOGV("resume(%d)", channelID);
-    Mutex::Autolock lock(&mLock);
-    SoundChannel* channel = findChannel(channelID);
-    if (channel) {
-        channel->resume();
-    }
-}
-
-void SoundPool::mute(bool muting)
-{
-    ALOGV("mute(%d)", muting);
-    Mutex::Autolock lock(&mLock);
-    mMuted = muting;
-    if (!mChannels.empty()) {
-            for (List<SoundChannel*>::iterator iter = mChannels.begin();
-                    iter != mChannels.end(); ++iter) {
-                (*iter)->mute(muting);
-            }
-        }
+    ALOGV("%s()", __func__);
+    auto apiLock = std::make_unique<std::lock_guard<std::mutex>>(mApiLock);
+    mStreamManager.forEach([](soundpool::Stream *stream) { stream->autoPause(); });
 }
 
 void SoundPool::autoResume()
 {
-    ALOGV("autoResume()");
-    Mutex::Autolock lock(&mLock);
-    for (int i = 0; i < mMaxChannels; ++i) {
-        SoundChannel* channel = &mChannelPool[i];
-        channel->autoResume();
+    ALOGV("%s()", __func__);
+    auto apiLock = std::make_unique<std::lock_guard<std::mutex>>(mApiLock);
+    mStreamManager.forEach([](soundpool::Stream *stream) { stream->autoResume(); });
+}
+
+void SoundPool::mute(bool muting)
+{
+    ALOGV("%s(%d)", __func__, muting);
+    auto apiLock = std::make_unique<std::lock_guard<std::mutex>>(mApiLock);
+    mStreamManager.forEach([=](soundpool::Stream *stream) { stream->mute(muting); });
+}
+
+void SoundPool::pause(int32_t streamID)
+{
+    ALOGV("%s(%d)", __func__, streamID);
+    auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+    if (soundpool::Stream* stream = mStreamManager.findStream(streamID)) {
+        stream->pause(streamID);
     }
 }
 
-void SoundPool::stop(int channelID)
+void SoundPool::resume(int32_t streamID)
 {
-    ALOGV("stop(%d)", channelID);
-    Mutex::Autolock lock(&mLock);
-    SoundChannel* channel = findChannel(channelID);
-    if (channel) {
-        channel->stop();
-    } else {
-        channel = findNextChannel(channelID);
-        if (channel)
-            channel->clearNextEvent();
+    ALOGV("%s(%d)", __func__, streamID);
+    auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+    if (soundpool::Stream* stream = mStreamManager.findStream(streamID)) {
+        stream->resume(streamID);
     }
 }
 
-void SoundPool::setVolume(int channelID, float leftVolume, float rightVolume)
+void SoundPool::stop(int32_t streamID)
 {
-    Mutex::Autolock lock(&mLock);
-    SoundChannel* channel = findChannel(channelID);
-    if (channel) {
-        channel->setVolume(leftVolume, rightVolume);
+    ALOGV("%s(%d)", __func__, streamID);
+    auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+    soundpool::Stream* stream = mStreamManager.findStream(streamID);
+    if (stream != nullptr && stream->requestStop(streamID)) {
+        mStreamManager.moveToRestartQueue(stream);
     }
 }
 
-void SoundPool::setPriority(int channelID, int priority)
+void SoundPool::setVolume(int32_t streamID, float leftVolume, float rightVolume)
 {
-    ALOGV("setPriority(%d, %d)", channelID, priority);
-    Mutex::Autolock lock(&mLock);
-    SoundChannel* channel = findChannel(channelID);
-    if (channel) {
-        channel->setPriority(priority);
+    ALOGV("%s(%d, %f %f)", __func__, streamID, leftVolume, rightVolume);
+    if (checkVolume(&leftVolume, &rightVolume)) return;
+    auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+    if (soundpool::Stream* stream = mStreamManager.findStream(streamID)) {
+        stream->setVolume(streamID, leftVolume, rightVolume);
     }
 }
 
-void SoundPool::setLoop(int channelID, int loop)
+void SoundPool::setPriority(int32_t streamID, int32_t priority)
 {
-    ALOGV("setLoop(%d, %d)", channelID, loop);
-    Mutex::Autolock lock(&mLock);
-    SoundChannel* channel = findChannel(channelID);
-    if (channel) {
-        channel->setLoop(loop);
+    ALOGV("%s(%d, %d)", __func__, streamID, priority);
+    if (checkPriority(&priority)) return;
+    auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+    if (soundpool::Stream* stream = mStreamManager.findStream(streamID)) {
+        stream->setPriority(streamID, priority);
     }
 }
 
-void SoundPool::setRate(int channelID, float rate)
+void SoundPool::setLoop(int32_t streamID, int32_t loop)
 {
-    ALOGV("setRate(%d, %f)", channelID, rate);
-    Mutex::Autolock lock(&mLock);
-    SoundChannel* channel = findChannel(channelID);
-    if (channel) {
-        channel->setRate(rate);
+    ALOGV("%s(%d, %d)", __func__, streamID, loop);
+    if (checkLoop(&loop)) return;
+    auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+    if (soundpool::Stream* stream = mStreamManager.findStream(streamID)) {
+        stream->setLoop(streamID, loop);
     }
 }
 
-// call with lock held
-void SoundPool::done_l(SoundChannel* channel)
+void SoundPool::setRate(int32_t streamID, float rate)
 {
-    ALOGV("done_l(%d)", channel->channelID());
-    // if "stolen", play next event
-    if (channel->nextChannelID() != 0) {
-        ALOGV("add to restart list");
-        addToRestartList(channel);
-    }
-
-    // return to idle state
-    else {
-        ALOGV("move to front");
-        moveToFront_l(channel);
+    ALOGV("%s(%d, %f)", __func__, streamID, rate);
+    if (checkRate(&rate)) return;
+    auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+    if (soundpool::Stream* stream = mStreamManager.findStream(streamID)) {
+        stream->setRate(streamID, rate);
     }
 }
 
 void SoundPool::setCallback(SoundPoolCallback* callback, void* user)
 {
-    Mutex::Autolock lock(&mCallbackLock);
-    mCallback = callback;
-    mUserData = user;
+    ALOGV("%s(%p, %p)", __func__, callback, user);
+    auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+    mSoundManager.setCallback(this, callback, user);
 }
 
-void SoundPool::notify(SoundPoolEvent event)
+void* SoundPool::getUserData() const
 {
-    Mutex::Autolock lock(&mCallbackLock);
-    if (mCallback != NULL) {
-        mCallback(event, this, mUserData);
-    }
-}
-
-void SoundPool::dump()
-{
-    for (int i = 0; i < mMaxChannels; ++i) {
-        mChannelPool[i].dump();
-    }
-}
-
-
-Sample::Sample(int sampleID, int fd, int64_t offset, int64_t length)
-{
-    init();
-    mSampleID = sampleID;
-    mFd = dup(fd);
-    mOffset = offset;
-    mLength = length;
-    ALOGV("create sampleID=%d, fd=%d, offset=%" PRId64 " length=%" PRId64,
-        mSampleID, mFd, mLength, mOffset);
-}
-
-void Sample::init()
-{
-    mSize = 0;
-    mRefCount = 0;
-    mSampleID = 0;
-    mState = UNLOADED;
-    mFd = -1;
-    mOffset = 0;
-    mLength = 0;
-}
-
-Sample::~Sample()
-{
-    ALOGV("Sample::destructor sampleID=%d, fd=%d", mSampleID, mFd);
-    if (mFd > 0) {
-        ALOGV("close(%d)", mFd);
-        ::close(mFd);
-    }
-}
-
-static status_t decode(int fd, int64_t offset, int64_t length,
-        uint32_t *rate, int *numChannels, audio_format_t *audioFormat,
-        audio_channel_mask_t *channelMask, sp<MemoryHeapBase> heap,
-        size_t *memsize) {
-
-    ALOGV("fd %d, offset %" PRId64 ", size %" PRId64, fd, offset, length);
-    AMediaExtractor *ex = AMediaExtractor_new();
-    status_t err = AMediaExtractor_setDataSourceFd(ex, fd, offset, length);
-
-    if (err != AMEDIA_OK) {
-        AMediaExtractor_delete(ex);
-        return err;
-    }
-
-    *audioFormat = AUDIO_FORMAT_PCM_16_BIT;
-
-    size_t numTracks = AMediaExtractor_getTrackCount(ex);
-    for (size_t i = 0; i < numTracks; i++) {
-        AMediaFormat *format = AMediaExtractor_getTrackFormat(ex, i);
-        const char *mime;
-        if (!AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mime)) {
-            AMediaExtractor_delete(ex);
-            AMediaFormat_delete(format);
-            return UNKNOWN_ERROR;
-        }
-        if (strncmp(mime, "audio/", 6) == 0) {
-
-            AMediaCodec *codec = AMediaCodec_createDecoderByType(mime);
-            if (codec == NULL
-                    || AMediaCodec_configure(codec, format,
-                            NULL /* window */, NULL /* drm */, 0 /* flags */) != AMEDIA_OK
-                    || AMediaCodec_start(codec) != AMEDIA_OK
-                    || AMediaExtractor_selectTrack(ex, i) != AMEDIA_OK) {
-                AMediaExtractor_delete(ex);
-                AMediaCodec_delete(codec);
-                AMediaFormat_delete(format);
-                return UNKNOWN_ERROR;
-            }
-
-            bool sawInputEOS = false;
-            bool sawOutputEOS = false;
-            uint8_t* writePos = static_cast<uint8_t*>(heap->getBase());
-            size_t available = heap->getSize();
-            size_t written = 0;
-
-            AMediaFormat_delete(format);
-            format = AMediaCodec_getOutputFormat(codec);
-
-            while (!sawOutputEOS) {
-                if (!sawInputEOS) {
-                    ssize_t bufidx = AMediaCodec_dequeueInputBuffer(codec, 5000);
-                    ALOGV("input buffer %zd", bufidx);
-                    if (bufidx >= 0) {
-                        size_t bufsize;
-                        uint8_t *buf = AMediaCodec_getInputBuffer(codec, bufidx, &bufsize);
-                        if (buf == nullptr) {
-                            ALOGE("AMediaCodec_getInputBuffer returned nullptr, short decode");
-                            break;
-                        }
-                        int sampleSize = AMediaExtractor_readSampleData(ex, buf, bufsize);
-                        ALOGV("read %d", sampleSize);
-                        if (sampleSize < 0) {
-                            sampleSize = 0;
-                            sawInputEOS = true;
-                            ALOGV("EOS");
-                        }
-                        int64_t presentationTimeUs = AMediaExtractor_getSampleTime(ex);
-
-                        media_status_t mstatus = AMediaCodec_queueInputBuffer(codec, bufidx,
-                                0 /* offset */, sampleSize, presentationTimeUs,
-                                sawInputEOS ? AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM : 0);
-                        if (mstatus != AMEDIA_OK) {
-                            // AMEDIA_ERROR_UNKNOWN == { -ERANGE -EINVAL -EACCES }
-                            ALOGE("AMediaCodec_queueInputBuffer returned status %d, short decode",
-                                    (int)mstatus);
-                            break;
-                        }
-                        (void)AMediaExtractor_advance(ex);
-                    }
-                }
-
-                AMediaCodecBufferInfo info;
-                int status = AMediaCodec_dequeueOutputBuffer(codec, &info, 1);
-                ALOGV("dequeueoutput returned: %d", status);
-                if (status >= 0) {
-                    if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
-                        ALOGV("output EOS");
-                        sawOutputEOS = true;
-                    }
-                    ALOGV("got decoded buffer size %d", info.size);
-
-                    uint8_t *buf = AMediaCodec_getOutputBuffer(codec, status, NULL /* out_size */);
-                    if (buf == nullptr) {
-                        ALOGE("AMediaCodec_getOutputBuffer returned nullptr, short decode");
-                        break;
-                    }
-                    size_t dataSize = info.size;
-                    if (dataSize > available) {
-                        dataSize = available;
-                    }
-                    memcpy(writePos, buf + info.offset, dataSize);
-                    writePos += dataSize;
-                    written += dataSize;
-                    available -= dataSize;
-                    media_status_t mstatus = AMediaCodec_releaseOutputBuffer(
-                            codec, status, false /* render */);
-                    if (mstatus != AMEDIA_OK) {
-                        // AMEDIA_ERROR_UNKNOWN == { -ERANGE -EINVAL -EACCES }
-                        ALOGE("AMediaCodec_releaseOutputBuffer returned status %d, short decode",
-                                (int)mstatus);
-                        break;
-                    }
-                    if (available == 0) {
-                        // there might be more data, but there's no space for it
-                        sawOutputEOS = true;
-                    }
-                } else if (status == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
-                    ALOGV("output buffers changed");
-                } else if (status == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
-                    AMediaFormat_delete(format);
-                    format = AMediaCodec_getOutputFormat(codec);
-                    ALOGV("format changed to: %s", AMediaFormat_toString(format));
-                } else if (status == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
-                    ALOGV("no output buffer right now");
-                } else if (status <= AMEDIA_ERROR_BASE) {
-                    ALOGE("decode error: %d", status);
-                    break;
-                } else {
-                    ALOGV("unexpected info code: %d", status);
-                }
-            }
-
-            (void)AMediaCodec_stop(codec);
-            (void)AMediaCodec_delete(codec);
-            (void)AMediaExtractor_delete(ex);
-            if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_SAMPLE_RATE, (int32_t*) rate) ||
-                    !AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_CHANNEL_COUNT, numChannels)) {
-                (void)AMediaFormat_delete(format);
-                return UNKNOWN_ERROR;
-            }
-            if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_CHANNEL_MASK,
-                    (int32_t*) channelMask)) {
-                *channelMask = AUDIO_CHANNEL_NONE;
-            }
-            (void)AMediaFormat_delete(format);
-            *memsize = written;
-            return OK;
-        }
-        (void)AMediaFormat_delete(format);
-    }
-    (void)AMediaExtractor_delete(ex);
-    return UNKNOWN_ERROR;
-}
-
-status_t Sample::doLoad()
-{
-    uint32_t sampleRate;
-    int numChannels;
-    audio_format_t format;
-    audio_channel_mask_t channelMask;
-    status_t status;
-    mHeap = new MemoryHeapBase(kDefaultHeapSize);
-
-    ALOGV("Start decode");
-    status = decode(mFd, mOffset, mLength, &sampleRate, &numChannels, &format,
-                    &channelMask, mHeap, &mSize);
-    ALOGV("close(%d)", mFd);
-    ::close(mFd);
-    mFd = -1;
-    if (status != NO_ERROR) {
-        ALOGE("Unable to load sample");
-        goto error;
-    }
-    ALOGV("pointer = %p, size = %zu, sampleRate = %u, numChannels = %d",
-          mHeap->getBase(), mSize, sampleRate, numChannels);
-
-    if (sampleRate > kMaxSampleRate) {
-       ALOGE("Sample rate (%u) out of range", sampleRate);
-       status = BAD_VALUE;
-       goto error;
-    }
-
-    if ((numChannels < 1) || (numChannels > FCC_8)) {
-        ALOGE("Sample channel count (%d) out of range", numChannels);
-        status = BAD_VALUE;
-        goto error;
-    }
-
-    mData = new MemoryBase(mHeap, 0, mSize);
-    mSampleRate = sampleRate;
-    mNumChannels = numChannels;
-    mFormat = format;
-    mChannelMask = channelMask;
-    mState = READY;
-    return NO_ERROR;
-
-error:
-    mHeap.clear();
-    return status;
-}
-
-
-void SoundChannel::init(SoundPool* soundPool)
-{
-    mSoundPool = soundPool;
-    mPrevSampleID = -1;
-}
-
-// call with sound pool lock held
-void SoundChannel::play(const sp<Sample>& sample, int nextChannelID, float leftVolume,
-        float rightVolume, int priority, int loop, float rate)
-{
-    sp<AudioTrack> oldTrack;
-    sp<AudioTrack> newTrack;
-    status_t status = NO_ERROR;
-
-    { // scope for the lock
-        Mutex::Autolock lock(&mLock);
-
-        ALOGV("SoundChannel::play %p: sampleID=%d, channelID=%d, leftVolume=%f, rightVolume=%f,"
-                " priority=%d, loop=%d, rate=%f",
-                this, sample->sampleID(), nextChannelID, leftVolume, rightVolume,
-                priority, loop, rate);
-
-        // if not idle, this voice is being stolen
-        if (mState != IDLE) {
-            ALOGV("channel %d stolen - event queued for channel %d", channelID(), nextChannelID);
-            mNextEvent.set(sample, nextChannelID, leftVolume, rightVolume, priority, loop, rate);
-            stop_l();
-            return;
-        }
-
-        // initialize track
-        size_t afFrameCount;
-        uint32_t afSampleRate;
-        audio_stream_type_t streamType =
-                AudioSystem::attributesToStreamType(*mSoundPool->attributes());
-        if (AudioSystem::getOutputFrameCount(&afFrameCount, streamType) != NO_ERROR) {
-            afFrameCount = kDefaultFrameCount;
-        }
-        if (AudioSystem::getOutputSamplingRate(&afSampleRate, streamType) != NO_ERROR) {
-            afSampleRate = kDefaultSampleRate;
-        }
-        int numChannels = sample->numChannels();
-        uint32_t sampleRate = uint32_t(float(sample->sampleRate()) * rate + 0.5);
-        size_t frameCount = 0;
-
-        if (loop) {
-            const audio_format_t format = sample->format();
-            const size_t frameSize = audio_is_linear_pcm(format)
-                    ? numChannels * audio_bytes_per_sample(format) : 1;
-            frameCount = sample->size() / frameSize;
-        }
-
-#ifndef USE_SHARED_MEM_BUFFER
-        uint32_t totalFrames = (kDefaultBufferCount * afFrameCount * sampleRate) / afSampleRate;
-        // Ensure minimum audio buffer size in case of short looped sample
-        if(frameCount < totalFrames) {
-            frameCount = totalFrames;
-        }
-#endif
-
-        // check if the existing track has the same sample id.
-        if (mAudioTrack != 0 && mPrevSampleID == sample->sampleID()) {
-            // the sample rate may fail to change if the audio track is a fast track.
-            if (mAudioTrack->setSampleRate(sampleRate) == NO_ERROR) {
-                newTrack = mAudioTrack;
-                ALOGV("reusing track %p for sample %d", mAudioTrack.get(), sample->sampleID());
-            }
-        }
-        if (newTrack == 0) {
-            // mToggle toggles each time a track is started on a given channel.
-            // The toggle is concatenated with the SoundChannel address and passed to AudioTrack
-            // as callback user data. This enables the detection of callbacks received from the old
-            // audio track while the new one is being started and avoids processing them with
-            // wrong audio audio buffer size  (mAudioBufferSize)
-            unsigned long toggle = mToggle ^ 1;
-            void *userData = (void *)((unsigned long)this | toggle);
-            audio_channel_mask_t sampleChannelMask = sample->channelMask();
-            // When sample contains a not none channel mask, use it as is.
-            // Otherwise, use channel count to calculate channel mask.
-            audio_channel_mask_t channelMask = sampleChannelMask != AUDIO_CHANNEL_NONE
-                    ? sampleChannelMask : audio_channel_out_mask_from_count(numChannels);
-
-            // do not create a new audio track if current track is compatible with sample parameters
-    #ifdef USE_SHARED_MEM_BUFFER
-            newTrack = new AudioTrack(streamType, sampleRate, sample->format(),
-                    channelMask, sample->getIMemory(), AUDIO_OUTPUT_FLAG_FAST, callback, userData,
-                    0 /*default notification frames*/, AUDIO_SESSION_ALLOCATE,
-                    AudioTrack::TRANSFER_DEFAULT,
-                    NULL /*offloadInfo*/, -1 /*uid*/, -1 /*pid*/, mSoundPool->attributes());
-    #else
-            uint32_t bufferFrames = (totalFrames + (kDefaultBufferCount - 1)) / kDefaultBufferCount;
-            newTrack = new AudioTrack(streamType, sampleRate, sample->format(),
-                    channelMask, frameCount, AUDIO_OUTPUT_FLAG_FAST, callback, userData,
-                    bufferFrames, AUDIO_SESSION_ALLOCATE, AudioTrack::TRANSFER_DEFAULT,
-                    NULL /*offloadInfo*/, -1 /*uid*/, -1 /*pid*/, mSoundPool->attributes());
-    #endif
-            oldTrack = mAudioTrack;
-            status = newTrack->initCheck();
-            if (status != NO_ERROR) {
-                ALOGE("Error creating AudioTrack");
-                // newTrack goes out of scope, so reference count drops to zero
-                goto exit;
-            }
-            // From now on, AudioTrack callbacks received with previous toggle value will be ignored.
-            mToggle = toggle;
-            mAudioTrack = newTrack;
-            ALOGV("using new track %p for sample %d", newTrack.get(), sample->sampleID());
-        }
-        if (mMuted) {
-            newTrack->setVolume(0.0f, 0.0f);
-        } else {
-            newTrack->setVolume(leftVolume, rightVolume);
-        }
-        newTrack->setLoop(0, frameCount, loop);
-        mPos = 0;
-        mSample = sample;
-        mChannelID = nextChannelID;
-        mPriority = priority;
-        mLoop = loop;
-        mLeftVolume = leftVolume;
-        mRightVolume = rightVolume;
-        mNumChannels = numChannels;
-        mRate = rate;
-        clearNextEvent();
-        mState = PLAYING;
-        mAudioTrack->start();
-        mAudioBufferSize = newTrack->frameCount()*newTrack->frameSize();
-    }
-
-exit:
-    ALOGV("delete oldTrack %p", oldTrack.get());
-    if (status != NO_ERROR) {
-        mAudioTrack.clear();
-    }
-}
-
-void SoundChannel::nextEvent()
-{
-    sp<Sample> sample;
-    int nextChannelID;
-    float leftVolume;
-    float rightVolume;
-    int priority;
-    int loop;
-    float rate;
-
-    // check for valid event
-    {
-        Mutex::Autolock lock(&mLock);
-        nextChannelID = mNextEvent.channelID();
-        if (nextChannelID  == 0) {
-            ALOGV("stolen channel has no event");
-            return;
-        }
-
-        sample = mNextEvent.sample();
-        leftVolume = mNextEvent.leftVolume();
-        rightVolume = mNextEvent.rightVolume();
-        priority = mNextEvent.priority();
-        loop = mNextEvent.loop();
-        rate = mNextEvent.rate();
-    }
-
-    ALOGV("Starting stolen channel %d -> %d", channelID(), nextChannelID);
-    play(sample, nextChannelID, leftVolume, rightVolume, priority, loop, rate);
-}
-
-void SoundChannel::callback(int event, void* user, void *info)
-{
-    SoundChannel* channel = static_cast<SoundChannel*>((void *)((unsigned long)user & ~1));
-
-    channel->process(event, info, (unsigned long)user & 1);
-}
-
-void SoundChannel::process(int event, void *info, unsigned long toggle)
-{
-    //ALOGV("process(%d)", mChannelID);
-
-    Mutex::Autolock lock(&mLock);
-
-    AudioTrack::Buffer* b = NULL;
-    if (event == AudioTrack::EVENT_MORE_DATA) {
-       b = static_cast<AudioTrack::Buffer *>(info);
-    }
-
-    if (mToggle != toggle) {
-        ALOGV("process wrong toggle %p channel %d", this, mChannelID);
-        if (b != NULL) {
-            b->size = 0;
-        }
-        return;
-    }
-
-    sp<Sample> sample = mSample;
-
-//    ALOGV("SoundChannel::process event %d", event);
-
-    if (event == AudioTrack::EVENT_MORE_DATA) {
-
-        // check for stop state
-        if (b->size == 0) return;
-
-        if (mState == IDLE) {
-            b->size = 0;
-            return;
-        }
-
-        if (sample != 0) {
-            // fill buffer
-            uint8_t* q = (uint8_t*) b->i8;
-            size_t count = 0;
-
-            if (mPos < (int)sample->size()) {
-                uint8_t* p = sample->data() + mPos;
-                count = sample->size() - mPos;
-                if (count > b->size) {
-                    count = b->size;
-                }
-                memcpy(q, p, count);
-//              ALOGV("fill: q=%p, p=%p, mPos=%u, b->size=%u, count=%d", q, p, mPos, b->size,
-//                      count);
-            } else if (mPos < mAudioBufferSize) {
-                count = mAudioBufferSize - mPos;
-                if (count > b->size) {
-                    count = b->size;
-                }
-                memset(q, 0, count);
-//              ALOGV("fill extra: q=%p, mPos=%u, b->size=%u, count=%d", q, mPos, b->size, count);
-            }
-
-            mPos += count;
-            b->size = count;
-            //ALOGV("buffer=%p, [0]=%d", b->i16, b->i16[0]);
-        }
-    } else if (event == AudioTrack::EVENT_UNDERRUN || event == AudioTrack::EVENT_BUFFER_END) {
-        ALOGV("process %p channel %d event %s",
-              this, mChannelID, (event == AudioTrack::EVENT_UNDERRUN) ? "UNDERRUN" :
-                      "BUFFER_END");
-        // Only BUFFER_END should happen as we use static tracks.
-        setVolume_l(0.f, 0.f);  // set volume to 0 to indicate no need to ramp volume down.
-        mSoundPool->addToStopList(this);
-    } else if (event == AudioTrack::EVENT_LOOP_END) {
-        ALOGV("End loop %p channel %d", this, mChannelID);
-    } else if (event == AudioTrack::EVENT_NEW_IAUDIOTRACK) {
-        ALOGV("process %p channel %d NEW_IAUDIOTRACK", this, mChannelID);
-    } else {
-        ALOGW("SoundChannel::process unexpected event %d", event);
-    }
-}
-
-
-// call with lock held
-bool SoundChannel::doStop_l()
-{
-    if (mState != IDLE) {
-        ALOGV("stop");
-        if (mLeftVolume != 0.f || mRightVolume != 0.f) {
-            setVolume_l(0.f, 0.f);
-            if (mSoundPool->attributes()->usage != AUDIO_USAGE_GAME) {
-                // Since we're forcibly halting the previously playing content,
-                // we sleep here to ensure the volume is ramped down before we stop the track.
-                // Ideally the sleep time is the mixer period, or an approximation thereof
-                // (Fast vs Normal tracks are different).
-                ALOGV("sleeping: ChannelID:%d  SampleID:%d", mChannelID, mSample->sampleID());
-                std::this_thread::sleep_for(std::chrono::milliseconds(20));
-            }
-        }
-        mAudioTrack->stop();
-        mPrevSampleID = mSample->sampleID();
-        mSample.clear();
-        mState = IDLE;
-        mPriority = IDLE_PRIORITY;
-        return true;
-    }
-    return false;
-}
-
-// call with lock held and sound pool lock held
-void SoundChannel::stop_l()
-{
-    if (doStop_l()) {
-        mSoundPool->done_l(this);
-    }
-}
-
-// call with sound pool lock held
-void SoundChannel::stop()
-{
-    bool stopped;
-    {
-        Mutex::Autolock lock(&mLock);
-        stopped = doStop_l();
-    }
-
-    if (stopped) {
-        mSoundPool->done_l(this);
-    }
-}
-
-//FIXME: Pause is a little broken right now
-void SoundChannel::pause()
-{
-    Mutex::Autolock lock(&mLock);
-    if (mState == PLAYING) {
-        ALOGV("pause track");
-        mState = PAUSED;
-        mAudioTrack->pause();
-    }
-}
-
-void SoundChannel::autoPause()
-{
-    Mutex::Autolock lock(&mLock);
-    if (mState == PLAYING) {
-        ALOGV("pause track");
-        mState = PAUSED;
-        mAutoPaused = true;
-        mAudioTrack->pause();
-    }
-}
-
-void SoundChannel::resume()
-{
-    Mutex::Autolock lock(&mLock);
-    if (mState == PAUSED) {
-        ALOGV("resume track");
-        mState = PLAYING;
-        mAutoPaused = false;
-        mAudioTrack->start();
-    }
-}
-
-void SoundChannel::autoResume()
-{
-    Mutex::Autolock lock(&mLock);
-    if (mAutoPaused && (mState == PAUSED)) {
-        ALOGV("resume track");
-        mState = PLAYING;
-        mAutoPaused = false;
-        mAudioTrack->start();
-    }
-}
-
-void SoundChannel::setRate(float rate)
-{
-    Mutex::Autolock lock(&mLock);
-    if (mAudioTrack != NULL && mSample != 0) {
-        uint32_t sampleRate = uint32_t(float(mSample->sampleRate()) * rate + 0.5);
-        mAudioTrack->setSampleRate(sampleRate);
-        mRate = rate;
-    }
-}
-
-// call with lock held
-void SoundChannel::setVolume_l(float leftVolume, float rightVolume)
-{
-    mLeftVolume = leftVolume;
-    mRightVolume = rightVolume;
-    if (mAudioTrack != NULL && !mMuted)
-        mAudioTrack->setVolume(leftVolume, rightVolume);
-}
-
-void SoundChannel::setVolume(float leftVolume, float rightVolume)
-{
-    Mutex::Autolock lock(&mLock);
-    setVolume_l(leftVolume, rightVolume);
-}
-
-void SoundChannel::mute(bool muting)
-{
-    Mutex::Autolock lock(&mLock);
-    mMuted = muting;
-    if (mAudioTrack != NULL) {
-        if (mMuted) {
-            mAudioTrack->setVolume(0.0f, 0.0f);
-        } else {
-            mAudioTrack->setVolume(mLeftVolume, mRightVolume);
-        }
-    }
-}
-
-void SoundChannel::setLoop(int loop)
-{
-    Mutex::Autolock lock(&mLock);
-    if (mAudioTrack != NULL && mSample != 0) {
-        uint32_t loopEnd = mSample->size()/mNumChannels/
-            ((mSample->format() == AUDIO_FORMAT_PCM_16_BIT) ? sizeof(int16_t) : sizeof(uint8_t));
-        mAudioTrack->setLoop(0, loopEnd, loop);
-        mLoop = loop;
-    }
-}
-
-SoundChannel::~SoundChannel()
-{
-    ALOGV("SoundChannel destructor %p", this);
-    {
-        Mutex::Autolock lock(&mLock);
-        clearNextEvent();
-        doStop_l();
-    }
-    // do not call AudioTrack destructor with mLock held as it will wait for the AudioTrack
-    // callback thread to exit which may need to execute process() and acquire the mLock.
-    mAudioTrack.clear();
-}
-
-void SoundChannel::dump()
-{
-    ALOGV("mState = %d mChannelID=%d, mNumChannels=%d, mPos = %d, mPriority=%d, mLoop=%d",
-            mState, mChannelID, mNumChannels, mPos, mPriority, mLoop);
-}
-
-void SoundEvent::set(const sp<Sample>& sample, int channelID, float leftVolume,
-            float rightVolume, int priority, int loop, float rate)
-{
-    mSample = sample;
-    mChannelID = channelID;
-    mLeftVolume = leftVolume;
-    mRightVolume = rightVolume;
-    mPriority = priority;
-    mLoop = loop;
-    mRate =rate;
+    ALOGV("%s()", __func__);
+    auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+    return mSoundManager.getUserData();
 }
 
 } // end namespace android
diff --git a/media/jni/soundpool/SoundPool.h b/media/jni/soundpool/SoundPool.h
index 01e4faa..d5b16ef 100644
--- a/media/jni/soundpool/SoundPool.h
+++ b/media/jni/soundpool/SoundPool.h
@@ -14,227 +14,59 @@
  * limitations under the License.
  */
 
-#ifndef SOUNDPOOL_H_
-#define SOUNDPOOL_H_
+#pragma once
 
-#include <utils/threads.h>
-#include <utils/List.h>
-#include <utils/Vector.h>
-#include <utils/KeyedVector.h>
-#include <media/AudioTrack.h>
-#include <binder/MemoryHeapBase.h>
-#include <binder/MemoryBase.h>
+#include "SoundManager.h"
+#include "StreamManager.h"
 
 namespace android {
 
-static const int IDLE_PRIORITY = -1;
-
-// forward declarations
-class SoundEvent;
-class SoundPoolThread;
-class SoundPool;
-
-// for queued events
-class SoundPoolEvent {
-public:
-    explicit SoundPoolEvent(int msg, int arg1=0, int arg2=0) :
-        mMsg(msg), mArg1(arg1), mArg2(arg2) {}
-    int         mMsg;
-    int         mArg1;
-    int         mArg2;
-    enum MessageType { INVALID, SAMPLE_LOADED };
-};
-
-// callback function prototype
-typedef void SoundPoolCallback(SoundPoolEvent event, SoundPool* soundPool, void* user);
-
-// tracks samples used by application
-class Sample  : public RefBase {
-public:
-    enum sample_state { UNLOADED, LOADING, READY, UNLOADING };
-    Sample(int sampleID, int fd, int64_t offset, int64_t length);
-    ~Sample();
-    int sampleID() { return mSampleID; }
-    int numChannels() { return mNumChannels; }
-    int sampleRate() { return mSampleRate; }
-    audio_format_t format() { return mFormat; }
-    audio_channel_mask_t channelMask() { return mChannelMask; }
-    size_t size() { return mSize; }
-    int state() { return mState; }
-    uint8_t* data() { return static_cast<uint8_t*>(mData->unsecurePointer()); }
-    status_t doLoad();
-    void startLoad() { mState = LOADING; }
-    sp<IMemory> getIMemory() { return mData; }
-
-private:
-    void init();
-
-    size_t               mSize;
-    volatile int32_t     mRefCount;
-    uint16_t             mSampleID;
-    uint16_t             mSampleRate;
-    uint8_t              mState;
-    uint8_t              mNumChannels;
-    audio_format_t       mFormat;
-    audio_channel_mask_t mChannelMask;
-    int                  mFd;
-    int64_t              mOffset;
-    int64_t              mLength;
-    sp<IMemory>          mData;
-    sp<MemoryHeapBase>   mHeap;
-};
-
-// stores pending events for stolen channels
-class SoundEvent
-{
-public:
-    SoundEvent() : mChannelID(0), mLeftVolume(0), mRightVolume(0),
-            mPriority(IDLE_PRIORITY), mLoop(0), mRate(0) {}
-    void set(const sp<Sample>& sample, int channelID, float leftVolume,
-            float rightVolume, int priority, int loop, float rate);
-    sp<Sample>      sample() { return mSample; }
-    int             channelID() { return mChannelID; }
-    float           leftVolume() { return mLeftVolume; }
-    float           rightVolume() { return mRightVolume; }
-    int             priority() { return mPriority; }
-    int             loop() { return mLoop; }
-    float           rate() { return mRate; }
-    void            clear() { mChannelID = 0; mSample.clear(); }
-
-protected:
-    sp<Sample>      mSample;
-    int             mChannelID;
-    float           mLeftVolume;
-    float           mRightVolume;
-    int             mPriority;
-    int             mLoop;
-    float           mRate;
-};
-
-// for channels aka AudioTracks
-class SoundChannel : public SoundEvent {
-public:
-    enum state { IDLE, RESUMING, STOPPING, PAUSED, PLAYING };
-    SoundChannel() : mState(IDLE), mNumChannels(1),
-            mPos(0), mToggle(0), mAutoPaused(false), mMuted(false) {}
-    ~SoundChannel();
-    void init(SoundPool* soundPool);
-    void play(const sp<Sample>& sample, int channelID, float leftVolume, float rightVolume,
-            int priority, int loop, float rate);
-    void setVolume_l(float leftVolume, float rightVolume);
-    void setVolume(float leftVolume, float rightVolume);
-    void mute(bool muting);
-    void stop_l();
-    void stop();
-    void pause();
-    void autoPause();
-    void resume();
-    void autoResume();
-    void setRate(float rate);
-    int state() { return mState; }
-    void setPriority(int priority) { mPriority = priority; }
-    void setLoop(int loop);
-    int numChannels() { return mNumChannels; }
-    void clearNextEvent() { mNextEvent.clear(); }
-    void nextEvent();
-    int nextChannelID() { return mNextEvent.channelID(); }
-    void dump();
-    int getPrevSampleID(void) { return mPrevSampleID; }
-
-private:
-    static void callback(int event, void* user, void *info);
-    void process(int event, void *info, unsigned long toggle);
-    bool doStop_l();
-
-    SoundPool*          mSoundPool;
-    sp<AudioTrack>      mAudioTrack;
-    SoundEvent          mNextEvent;
-    Mutex               mLock;
-    int                 mState;
-    int                 mNumChannels;
-    int                 mPos;
-    int                 mAudioBufferSize;
-    unsigned long       mToggle;
-    bool                mAutoPaused;
-    int                 mPrevSampleID;
-    bool                mMuted;
-};
-
-// application object for managing a pool of sounds
+/**
+ * Native class for Java SoundPool, manages a pool of sounds.
+ *
+ * See the Android SoundPool Java documentation for description of valid values.
+ * https://developer.android.com/reference/android/media/SoundPool
+ */
 class SoundPool {
-    friend class SoundPoolThread;
-    friend class SoundChannel;
 public:
-    SoundPool(int maxChannels, const audio_attributes_t* pAttributes);
+    SoundPool(int32_t maxStreams, const audio_attributes_t* attributes);
     ~SoundPool();
-    int load(int fd, int64_t offset, int64_t length, int priority);
-    bool unload(int sampleID);
-    int play(int sampleID, float leftVolume, float rightVolume, int priority,
-            int loop, float rate);
-    void pause(int channelID);
-    void mute(bool muting);
+
+    // SoundPool Java API support
+    int32_t load(int fd, int64_t offset, int64_t length, int32_t priority);
+    bool unload(int32_t soundID);
+    int32_t play(int32_t soundID, float leftVolume, float rightVolume, int32_t priority,
+            int32_t loop, float rate);
+    void pause(int32_t streamID);
     void autoPause();
-    void resume(int channelID);
+    void resume(int32_t streamID);
     void autoResume();
-    void stop(int channelID);
-    void setVolume(int channelID, float leftVolume, float rightVolume);
-    void setPriority(int channelID, int priority);
-    void setLoop(int channelID, int loop);
-    void setRate(int channelID, float rate);
-    const audio_attributes_t* attributes() { return &mAttributes; }
-
-    // called from SoundPoolThread
-    void sampleLoaded(int sampleID);
-    sp<Sample> findSample(int sampleID);
-
-    // called from AudioTrack thread
-    void done_l(SoundChannel* channel);
-
-    // callback function
+    void stop(int32_t streamID);
+    void setVolume(int32_t streamID, float leftVolume, float rightVolume);
+    void setPriority(int32_t streamID, int32_t priority);
+    void setLoop(int32_t streamID, int32_t loop);
+    void setRate(int32_t streamID, float rate);
     void setCallback(SoundPoolCallback* callback, void* user);
-    void* getUserData() { return mUserData; }
+    void* getUserData() const;
+
+    // not exposed in the public Java API, used for internal playerSetVolume() muting.
+    void mute(bool muting);
 
 private:
-    SoundPool() {} // no default constructor
-    bool startThreads();
-    sp<Sample> findSample_l(int sampleID);
-    SoundChannel* findChannel (int channelID);
-    SoundChannel* findNextChannel (int channelID);
-    SoundChannel* allocateChannel_l(int priority, int sampleID);
-    void moveToFront_l(SoundChannel* channel);
-    void notify(SoundPoolEvent event);
-    void dump();
 
-    // restart thread
-    void addToRestartList(SoundChannel* channel);
-    void addToStopList(SoundChannel* channel);
-    static int beginThread(void* arg);
-    int run();
-    void quit();
+    // Constructor initialized variables
+    // Can access without lock as they are internally locked,
+    // though care needs to be taken that the final result composed of
+    // individually consistent actions are consistent.
+    soundpool::SoundManager  mSoundManager;
+    soundpool::StreamManager mStreamManager;
 
-    Mutex                   mLock;
-    Mutex                   mRestartLock;
-    Condition               mCondition;
-    SoundPoolThread*        mDecodeThread;
-    SoundChannel*           mChannelPool;
-    List<SoundChannel*>     mChannels;
-    List<SoundChannel*>     mRestart;
-    List<SoundChannel*>     mStop;
-    DefaultKeyedVector< int, sp<Sample> >   mSamples;
-    int                     mMaxChannels;
-    audio_attributes_t      mAttributes;
-    int                     mAllocated;
-    int                     mNextSampleID;
-    int                     mNextChannelID;
-    bool                    mQuit;
-    bool                    mMuted;
-
-    // callback
-    Mutex                   mCallbackLock;
-    SoundPoolCallback*      mCallback;
-    void*                   mUserData;
+    // mApiLock serializes SoundPool application calls (configurable by kUseApiLock).
+    // It only locks at the SoundPool layer and not below.  At this level,
+    // mApiLock is only required for autoPause() and autoResume() to prevent zippering
+    // of the individual pauses and resumes, and mute() for self-interaction with itself.
+    // It is optional for all other apis.
+    mutable std::mutex        mApiLock;
 };
 
 } // end namespace android
-
-#endif /*SOUNDPOOL_H_*/
diff --git a/media/jni/soundpool/SoundPoolThread.cpp b/media/jni/soundpool/SoundPoolThread.cpp
deleted file mode 100644
index ba3b482..0000000
--- a/media/jni/soundpool/SoundPoolThread.cpp
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2007 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.
- */
-
-//#define LOG_NDEBUG 0
-#define LOG_TAG "SoundPoolThread"
-#include "utils/Log.h"
-
-#include "SoundPoolThread.h"
-
-namespace android {
-
-void SoundPoolThread::write(SoundPoolMsg msg) {
-    Mutex::Autolock lock(&mLock);
-    while (mMsgQueue.size() >= maxMessages) {
-        mCondition.wait(mLock);
-    }
-
-    // if thread is quitting, don't add to queue
-    if (mRunning) {
-        mMsgQueue.push(msg);
-        mCondition.signal();
-    }
-}
-
-const SoundPoolMsg SoundPoolThread::read() {
-    Mutex::Autolock lock(&mLock);
-    while (mMsgQueue.size() == 0) {
-        mCondition.wait(mLock);
-    }
-    SoundPoolMsg msg = mMsgQueue[0];
-    mMsgQueue.removeAt(0);
-    mCondition.signal();
-    return msg;
-}
-
-void SoundPoolThread::quit() {
-    Mutex::Autolock lock(&mLock);
-    if (mRunning) {
-        mRunning = false;
-        mMsgQueue.clear();
-        mMsgQueue.push(SoundPoolMsg(SoundPoolMsg::KILL, 0));
-        mCondition.signal();
-        mCondition.wait(mLock);
-    }
-    ALOGV("return from quit");
-}
-
-SoundPoolThread::SoundPoolThread(SoundPool* soundPool) :
-    mSoundPool(soundPool)
-{
-    mMsgQueue.setCapacity(maxMessages);
-    if (createThreadEtc(beginThread, this, "SoundPoolThread")) {
-        mRunning = true;
-    }
-}
-
-SoundPoolThread::~SoundPoolThread()
-{
-    quit();
-}
-
-int SoundPoolThread::beginThread(void* arg) {
-    ALOGV("beginThread");
-    SoundPoolThread* soundPoolThread = (SoundPoolThread*)arg;
-    return soundPoolThread->run();
-}
-
-int SoundPoolThread::run() {
-    ALOGV("run");
-    for (;;) {
-        SoundPoolMsg msg = read();
-        ALOGV("Got message m=%d, mData=%d", msg.mMessageType, msg.mData);
-        switch (msg.mMessageType) {
-        case SoundPoolMsg::KILL:
-            ALOGV("goodbye");
-            return NO_ERROR;
-        case SoundPoolMsg::LOAD_SAMPLE:
-            doLoadSample(msg.mData);
-            break;
-        default:
-            ALOGW("run: Unrecognized message %d\n",
-                    msg.mMessageType);
-            break;
-        }
-    }
-}
-
-void SoundPoolThread::loadSample(int sampleID) {
-    write(SoundPoolMsg(SoundPoolMsg::LOAD_SAMPLE, sampleID));
-}
-
-void SoundPoolThread::doLoadSample(int sampleID) {
-    sp <Sample> sample = mSoundPool->findSample(sampleID);
-    status_t status = -1;
-    if (sample != 0) {
-        status = sample->doLoad();
-    }
-    mSoundPool->notify(SoundPoolEvent(SoundPoolEvent::SAMPLE_LOADED, sampleID, status));
-}
-
-} // end namespace android
diff --git a/media/jni/soundpool/SoundPoolThread.h b/media/jni/soundpool/SoundPoolThread.h
deleted file mode 100644
index 7b3e1dd..0000000
--- a/media/jni/soundpool/SoundPoolThread.h
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2007 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.
- */
-
-#ifndef SOUNDPOOLTHREAD_H_
-#define SOUNDPOOLTHREAD_H_
-
-#include <utils/threads.h>
-#include <utils/Vector.h>
-#include <media/AudioTrack.h>
-
-#include "SoundPool.h"
-
-namespace android {
-
-class SoundPoolMsg {
-public:
-    enum MessageType { INVALID, KILL, LOAD_SAMPLE };
-    SoundPoolMsg() : mMessageType(INVALID), mData(0) {}
-    SoundPoolMsg(MessageType MessageType, int data) :
-        mMessageType(MessageType), mData(data) {}
-    uint16_t         mMessageType;
-    uint16_t         mData;
-};
-
-/*
- * This class handles background requests from the SoundPool
- */
-class SoundPoolThread {
-public:
-    explicit SoundPoolThread(SoundPool* SoundPool);
-    ~SoundPoolThread();
-    void loadSample(int sampleID);
-    void quit();
-    void write(SoundPoolMsg msg);
-
-private:
-    static const size_t maxMessages = 128;
-
-    static int beginThread(void* arg);
-    int run();
-    void doLoadSample(int sampleID);
-    const SoundPoolMsg read();
-
-    Mutex                   mLock;
-    Condition               mCondition;
-    Vector<SoundPoolMsg>    mMsgQueue;
-    SoundPool*              mSoundPool;
-    bool                    mRunning;
-};
-
-} // end namespace android
-
-#endif /*SOUNDPOOLTHREAD_H_*/
diff --git a/media/jni/soundpool/Stream.cpp b/media/jni/soundpool/Stream.cpp
new file mode 100644
index 0000000..e7d4d90
--- /dev/null
+++ b/media/jni/soundpool/Stream.cpp
@@ -0,0 +1,448 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "SoundPool::Stream"
+#include <utils/Log.h>
+
+#include "Stream.h"
+
+#include "StreamManager.h"
+
+namespace android::soundpool {
+
+Stream::~Stream()
+{
+    ALOGV("%s(%p)", __func__, this);
+}
+
+void Stream::autoPause()
+{
+    std::lock_guard lock(mLock);
+    if (mState == PLAYING) {
+        ALOGV("%s: track streamID: %d", __func__, (int)mStreamID);
+        mState = PAUSED;
+        mAutoPaused = true;
+        if (mAudioTrack != nullptr) {
+            mAudioTrack->pause();
+        }
+    }
+}
+
+void Stream::autoResume()
+{
+    std::lock_guard lock(mLock);
+    if (mAutoPaused) {
+        if (mState == PAUSED) {
+            ALOGV("%s: track streamID: %d", __func__, (int)mStreamID);
+            mState = PLAYING;
+            if (mAudioTrack != nullptr) {
+                mAudioTrack->start();
+            }
+        }
+        mAutoPaused = false; // New for R: always reset autopause (consistent with API spec).
+    }
+}
+
+void Stream::mute(bool muting)
+{
+    std::lock_guard lock(mLock);
+    mMuted = muting;
+    if (mAudioTrack != nullptr) {
+        if (mMuted) {
+            mAudioTrack->setVolume(0.0f, 0.0f);
+        } else {
+            mAudioTrack->setVolume(mLeftVolume, mRightVolume);
+        }
+    }
+}
+
+void Stream::pause(int32_t streamID)
+{
+    std::lock_guard lock(mLock);
+    if (streamID == mStreamID) {
+        if (mState == PLAYING) {
+            ALOGV("%s: track streamID: %d", __func__, streamID);
+            mState = PAUSED;
+            if (mAudioTrack != nullptr) {
+                mAudioTrack->pause();
+            }
+        }
+    }
+}
+
+void Stream::resume(int32_t streamID)
+{
+    std::lock_guard lock(mLock);
+    if (streamID == mStreamID) {
+         if (mState == PAUSED) {
+            ALOGV("%s: track streamID: %d", __func__, streamID);
+            mState = PLAYING;
+            if (mAudioTrack != nullptr) {
+                mAudioTrack->start();
+            }
+            mAutoPaused = false; // TODO: is this right? (ambiguous per spec), move outside?
+        }
+    }
+}
+
+void Stream::setRate(int32_t streamID, float rate)
+{
+    std::lock_guard lock(mLock);
+    if (streamID == mStreamID) {
+        mRate = rate;
+        if (mAudioTrack != nullptr && mSound != nullptr) {
+            const uint32_t sampleRate = uint32_t(float(mSound->getSampleRate()) * rate + 0.5);
+            mAudioTrack->setSampleRate(sampleRate);
+        }
+    }
+}
+
+void Stream::setVolume_l(float leftVolume, float rightVolume)
+{
+    mLeftVolume = leftVolume;
+    mRightVolume = rightVolume;
+    if (mAudioTrack != nullptr && !mMuted) {
+        mAudioTrack->setVolume(leftVolume, rightVolume);
+    }
+}
+
+void Stream::setVolume(int32_t streamID, float leftVolume, float rightVolume)
+{
+    std::lock_guard lock(mLock);
+    if (streamID == mStreamID) {
+        setVolume_l(leftVolume, rightVolume);
+    }
+}
+
+void Stream::setPriority(int32_t streamID, int32_t priority)
+{
+    std::lock_guard lock(mLock);
+    if (streamID == mStreamID) {
+        mPriority = priority;
+    }
+}
+
+void Stream::setLoop(int32_t streamID, int32_t loop)
+{
+    std::lock_guard lock(mLock);
+    if (streamID == mStreamID) {
+        if (mAudioTrack != nullptr && mSound != nullptr) {
+            const uint32_t loopEnd = mSound->getSizeInBytes() / mSound->getChannelCount() /
+                (mSound->getFormat() == AUDIO_FORMAT_PCM_16_BIT
+                        ? sizeof(int16_t) : sizeof(uint8_t));
+            mAudioTrack->setLoop(0, loopEnd, loop);
+        }
+        mLoop = loop;
+    }
+}
+
+void Stream::setPlay(
+        int32_t streamID, const std::shared_ptr<Sound> &sound, int32_t soundID,
+        float leftVolume, float rightVolume, int32_t priority, int32_t loop, float rate)
+{
+    std::lock_guard lock(mLock);
+    // We must be idle, or we must be repurposing a pending Stream.
+    LOG_ALWAYS_FATAL_IF(mState != IDLE && mAudioTrack != nullptr, "State %d must be IDLE", mState);
+    mSound = sound;
+    mSoundID = soundID;
+    mLeftVolume = leftVolume;
+    mRightVolume = rightVolume;
+    mPriority = priority;
+    mLoop = loop;
+    mRate = rate;
+    mState = PLAYING;
+    mAutoPaused = false;   // New for R (consistent with Java API spec).
+    mStreamID = streamID;  // prefer this to be the last, as it is an atomic sync point
+}
+
+void Stream::setStopTimeNs(int64_t stopTimeNs)
+{
+    std::lock_guard lock(mLock);
+    mStopTimeNs = stopTimeNs;
+}
+
+bool Stream::requestStop(int32_t streamID)
+{
+    std::lock_guard lock(mLock);
+    if (streamID == mStreamID) {
+        if (mAudioTrack != nullptr) {
+            if (mState == PLAYING && !mMuted && (mLeftVolume != 0.f || mRightVolume != 0.f)) {
+                setVolume_l(0.f, 0.f);
+                mStopTimeNs = systemTime() + kStopWaitTimeNs;
+            } else {
+                mStopTimeNs = systemTime();
+            }
+            return true; // must be queued on the restart list.
+        }
+        stop_l();
+    }
+    return false;
+}
+
+void Stream::stop()
+{
+    std::lock_guard lock(mLock);
+    stop_l();
+}
+
+void Stream::stop_l()
+{
+    if (mState != IDLE) {
+        if (mAudioTrack != nullptr) {
+            mAudioTrack->stop();
+        }
+        mSound.reset();
+        mState = IDLE;
+    }
+}
+
+void Stream::clearAudioTrack()
+{
+    // This will invoke the destructor which waits for the AudioTrack thread to join,
+    // and is currently the only safe way to ensure there are no callbacks afterwards.
+    mAudioTrack.clear();
+}
+
+Stream* Stream::getPairStream() const
+{
+   return mStreamManager->getPairStream(this);
+}
+
+Stream* Stream::playPairStream() {
+    Stream* pairStream = getPairStream();
+    LOG_ALWAYS_FATAL_IF(pairStream == nullptr, "No pair stream!");
+    sp<AudioTrack> releaseTracks[2];
+    {
+        // TODO: Do we really want to force a simultaneous synchronization between
+        // the stream and its pair?
+
+        // note locking order - the paired stream is obtained before the queued stream.
+        // we can invert the locking order, but it is slightly more optimal to do it this way.
+        std::lock_guard lockp(pairStream->mLock);
+        if (pairStream->mSound == nullptr) {
+            return nullptr; // no pair sound
+        }
+        {
+            std::lock_guard lock(mLock);
+            LOG_ALWAYS_FATAL_IF(mState != IDLE, "State: %d must be IDLE", mState);
+            // TODO: do we want a specific set() here?
+            pairStream->mAudioTrack = mAudioTrack;
+            pairStream->mSoundID = mSoundID; // optimization to reuse AudioTrack.
+            pairStream->mToggle = mToggle;
+            pairStream->mAutoPaused = mAutoPaused; // save autopause state
+            pairStream->mMuted = mMuted;
+            mAudioTrack.clear();  // the pair owns the audiotrack.
+            mSound.reset();
+            mSoundID = 0;
+        }
+        // TODO: do we need a specific play_l() anymore?
+        const int pairState = pairStream->mState;
+        pairStream->play_l(pairStream->mSound, pairStream->mStreamID,
+                pairStream->mLeftVolume, pairStream->mRightVolume, pairStream->mPriority,
+                pairStream->mLoop, pairStream->mRate, releaseTracks);
+        if (pairStream->mState == IDLE) {
+            return nullptr; // AudioTrack error
+        }
+        if (pairState == PAUSED) {  // reestablish pause
+            pairStream->mState = PAUSED;
+            pairStream->mAudioTrack->pause();
+        }
+    }
+    // release tracks outside of Stream lock
+    return pairStream;
+}
+
+void Stream::play_l(const std::shared_ptr<Sound>& sound, int32_t nextStreamID,
+        float leftVolume, float rightVolume, int32_t priority, int32_t loop, float rate,
+        sp<AudioTrack> releaseTracks[2])
+{
+    // These tracks are released without the lock.
+    sp<AudioTrack> &oldTrack = releaseTracks[0];
+    sp<AudioTrack> &newTrack = releaseTracks[1];
+    status_t status = NO_ERROR;
+
+    {
+        ALOGV("%s(%p)(soundID=%d, streamID=%d, leftVolume=%f, rightVolume=%f,"
+                " priority=%d, loop=%d, rate=%f)",
+                __func__, this, sound->getSoundID(), nextStreamID, leftVolume, rightVolume,
+                priority, loop, rate);
+
+        // initialize track
+        const audio_stream_type_t streamType =
+                AudioSystem::attributesToStreamType(*mStreamManager->getAttributes());
+        const int32_t channelCount = sound->getChannelCount();
+        const uint32_t sampleRate = uint32_t(float(sound->getSampleRate()) * rate + 0.5);
+        size_t frameCount = 0;
+
+        if (loop) {
+            const audio_format_t format = sound->getFormat();
+            const size_t frameSize = audio_is_linear_pcm(format)
+                    ? channelCount * audio_bytes_per_sample(format) : 1;
+            frameCount = sound->getSizeInBytes() / frameSize;
+        }
+
+        // check if the existing track has the same sound id.
+        if (mAudioTrack != nullptr && mSoundID == sound->getSoundID()) {
+            // the sample rate may fail to change if the audio track is a fast track.
+            if (mAudioTrack->setSampleRate(sampleRate) == NO_ERROR) {
+                newTrack = mAudioTrack;
+                ALOGV("%s: reusing track %p for sound %d",
+                        __func__, mAudioTrack.get(), sound->getSoundID());
+            }
+        }
+        if (newTrack == 0) {
+            // mToggle toggles each time a track is started on a given stream.
+            // The toggle is concatenated with the Stream address and passed to AudioTrack
+            // as callback user data. This enables the detection of callbacks received from the old
+            // audio track while the new one is being started and avoids processing them with
+            // wrong audio audio buffer size  (mAudioBufferSize)
+            auto toggle = mToggle ^ 1;
+            void* userData = (void*)((uintptr_t)this | toggle);
+            audio_channel_mask_t soundChannelMask = sound->getChannelMask();
+            // When sound contains a valid channel mask, use it as is.
+            // Otherwise, use stream count to calculate channel mask.
+            audio_channel_mask_t channelMask = soundChannelMask != AUDIO_CHANNEL_NONE
+                    ? soundChannelMask : audio_channel_out_mask_from_count(channelCount);
+
+            // do not create a new audio track if current track is compatible with sound parameters
+
+            newTrack = new AudioTrack(streamType, sampleRate, sound->getFormat(),
+                    channelMask, sound->getIMemory(), AUDIO_OUTPUT_FLAG_FAST,
+                    staticCallback, userData,
+                    0 /*default notification frames*/, AUDIO_SESSION_ALLOCATE,
+                    AudioTrack::TRANSFER_DEFAULT,
+                    nullptr /*offloadInfo*/, -1 /*uid*/, -1 /*pid*/,
+                    mStreamManager->getAttributes());
+
+            oldTrack = mAudioTrack;
+            status = newTrack->initCheck();
+            if (status != NO_ERROR) {
+                ALOGE("%s: error creating AudioTrack", __func__);
+                // newTrack goes out of scope, so reference count drops to zero
+                goto exit;
+            }
+            // From now on, AudioTrack callbacks received with previous toggle value will be ignored.
+            mToggle = toggle;
+            mAudioTrack = newTrack;
+            ALOGV("%s: using new track %p for sound %d",
+                    __func__, newTrack.get(), sound->getSoundID());
+        }
+        if (mMuted) {
+            newTrack->setVolume(0.0f, 0.0f);
+        } else {
+            newTrack->setVolume(leftVolume, rightVolume);
+        }
+        newTrack->setLoop(0, frameCount, loop);
+        mAudioTrack->start();
+        mSound = sound;
+        mSoundID = sound->getSoundID();
+        mPriority = priority;
+        mLoop = loop;
+        mLeftVolume = leftVolume;
+        mRightVolume = rightVolume;
+        mRate = rate;
+        mState = PLAYING;
+        mStopTimeNs = 0;
+        mStreamID = nextStreamID;  // prefer this to be the last, as it is an atomic sync point
+    }
+
+exit:
+    ALOGV("%s: delete oldTrack %p", __func__, oldTrack.get());
+    if (status != NO_ERROR) {
+        // TODO: should we consider keeping the soundID if the old track is OK?
+        // Do not attempt to restart this track (should we remove the stream id?)
+        mState = IDLE;
+        mSoundID = 0;
+        mSound.reset();
+        mAudioTrack.clear();  // actual release from releaseTracks[]
+    }
+}
+
+/* static */
+void Stream::staticCallback(int event, void* user, void* info)
+{
+    const uintptr_t userAsInt = (uintptr_t)user;
+    Stream* stream = reinterpret_cast<Stream*>(userAsInt & ~1);
+    stream->callback(event, info, userAsInt & 1, 0 /* tries */);
+}
+
+void Stream::callback(int event, void* info, int toggle, int tries)
+{
+    ALOGV("%s streamID %d", __func__, (int)mStreamID);
+    int32_t activeStreamIDToRestart = 0;
+    {
+        std::unique_lock lock(mLock);
+
+        if (mAudioTrack == nullptr) {
+            // The AudioTrack is either with this stream or its pair.
+            // if this swaps a few times, the toggle is bound to be wrong, so we fail then.
+            //
+            // TODO: Modify AudioTrack callbacks to avoid the hacky toggle and retry
+            // logic here.
+            if (tries < 3) {
+                lock.unlock();
+                getPairStream()->callback(event, info, toggle, tries + 1);
+            } else {
+                ALOGW("%s streamID %d cannot find track", __func__, (int)mStreamID);
+            }
+            return;
+        }
+        if (mToggle != toggle) {
+            ALOGD("%s streamID %d wrong toggle", __func__, (int)mStreamID);
+            return;
+        }
+        switch (event) {
+        case AudioTrack::EVENT_MORE_DATA:
+            ALOGW("%s streamID %d Invalid EVENT_MORE_DATA for static track",
+                    __func__, (int)mStreamID);
+            break;
+        case AudioTrack::EVENT_UNDERRUN:
+            ALOGW("%s streamID %d Invalid EVENT_UNDERRUN for static track",
+                    __func__, (int)mStreamID);
+            break;
+        case AudioTrack::EVENT_BUFFER_END:
+            ALOGV("%s streamID %d EVENT_BUFFER_END", __func__, (int)mStreamID);
+            if (mState != IDLE) {
+                activeStreamIDToRestart = mStreamID;
+                mStopTimeNs = systemTime();
+            }
+            break;
+        case AudioTrack::EVENT_LOOP_END:
+            ALOGV("%s streamID %d EVENT_LOOP_END", __func__, (int)mStreamID);
+            break;
+        case AudioTrack::EVENT_NEW_IAUDIOTRACK:
+            ALOGV("%s streamID %d NEW_IAUDIOTRACK", __func__, (int)mStreamID);
+            break;
+        default:
+            ALOGW("%s streamID %d Invalid event %d", __func__, (int)mStreamID, event);
+            break;
+        }
+    } // lock ends here.  This is on the callback thread, no need to be precise.
+    if (activeStreamIDToRestart > 0) {
+        // Restart only if a particular streamID is still current and active.
+        ALOGV("%s: moveToRestartQueue %d", __func__, activeStreamIDToRestart);
+        mStreamManager->moveToRestartQueue(this, activeStreamIDToRestart);
+    }
+}
+
+void Stream::dump() const
+{
+    ALOGV("mPairStream=%p, mState=%d, mStreamID=%d, mSoundID=%d, mPriority=%d, mLoop=%d",
+            getPairStream(), mState, (int)mStreamID, mSoundID, mPriority, mLoop);
+}
+
+} // namespace android::soundpool
diff --git a/media/jni/soundpool/Stream.h b/media/jni/soundpool/Stream.h
new file mode 100644
index 0000000..82d2690
--- /dev/null
+++ b/media/jni/soundpool/Stream.h
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#pragma once
+
+#include "Sound.h"
+
+#include <audio_utils/clock.h>
+#include <media/AudioTrack.h>
+
+namespace android::soundpool {
+
+// This is the amount of time to wait after stop is called when stealing an
+// AudioTrack to allow the sound to ramp down.  If this is 0, glitches
+// may occur when stealing an AudioTrack.
+inline constexpr int64_t kStopWaitTimeNs = 20 * NANOS_PER_MILLISECOND;
+
+inline constexpr size_t kCacheLineSize = 64; /* std::hardware_constructive_interference_size */
+
+class StreamManager; // forward decl
+
+/**
+ * A Stream is associated with a StreamID exposed to the app to play a Sound.
+ *
+ * The Stream uses monitor locking strategy on mLock.
+ * https://en.wikipedia.org/wiki/Monitor_(synchronization)
+ *
+ * where public methods are guarded by a lock (as needed)
+ *
+ * For Java equivalent APIs, see
+ * https://developer.android.com/reference/android/media/SoundPool
+ *
+ * Streams are paired by the StreamManager, so one stream in the pair may be "stopping"
+ * while the other stream of the pair has been prepared to run
+ * (and the streamID returned to the app) pending its pair to be stopped.
+ * The pair of a Stream may be obtained by calling getPairStream(),
+ * where this->getPairStream()->getPairStream() == this; (pair is a commutative relationship).
+ *
+ * playPairStream() and getPairPriority() access the paired stream.
+ * See also StreamManager.h for details of physical layout implications of paired streams.
+ */
+class alignas(kCacheLineSize) Stream {
+public:
+    enum state { IDLE, PAUSED, PLAYING };
+    // The PAUSED, PLAYING state directly corresponds to the AudioTrack state of an active Stream.
+    //
+    // The IDLE state indicates an inactive Stream.   An IDLE Stream may have a non-nullptr
+    // AudioTrack, which may be recycled for use if the SoundID matches the next Stream playback.
+    //
+    // PAUSED -> PLAYING through resume()  (see also autoResume())
+    // PLAYING -> PAUSED through pause()   (see also autoPause())
+    //
+    // IDLE is the initial state of a Stream and also when a stream becomes inactive.
+    // {PAUSED, PLAYING} -> IDLE through stop() (or if the Sound finishes playing)
+    // IDLE -> PLAYING through play().  (there is no way to start a Stream in paused mode).
+
+    ~Stream();
+    void setStreamManager(StreamManager* streamManager) { // non-nullptr
+        mStreamManager = streamManager; // set in StreamManager constructor, not changed
+    }
+
+    // The following methods are monitor locked by mLock.
+    //
+    // For methods taking a streamID:
+    // if the streamID matches the Stream's mStreamID, then method proceeds
+    // else the command is ignored with no effect.
+
+    // returns true if the stream needs to be explicitly stopped.
+    bool requestStop(int32_t streamID);
+    void stop();                    // explicit stop(), typically called from the worker thread.
+    void clearAudioTrack();
+    void pause(int32_t streamID);
+    void autoPause();               // see the Java SoundPool.autoPause documentation for details.
+    void resume(int32_t streamID);
+    void autoResume();
+    void mute(bool muting);
+    void dump() const;
+
+    // returns the pair stream if successful, nullptr otherwise
+    Stream* playPairStream();
+
+    // These parameters are explicitly checked in the SoundPool class
+    // so never deviate from the Java API specified values.
+    void setVolume(int32_t streamID, float leftVolume, float rightVolume);
+    void setRate(int32_t streamID, float rate);
+    void setPriority(int32_t streamID, int priority);
+    void setLoop(int32_t streamID, int loop);
+    void setPlay(int32_t streamID, const std::shared_ptr<Sound> &sound, int32_t soundID,
+           float leftVolume, float rightVolume, int32_t priority, int32_t loop, float rate);
+    void setStopTimeNs(int64_t stopTimeNs); // systemTime() clock monotonic.
+
+    // The following getters are not locked and have weak consistency.
+    // These are considered advisory only - being stale is of nuisance.
+    int32_t getPriority() const { return mPriority; }
+    int32_t getPairPriority() const { return getPairStream()->getPriority(); }
+    int64_t getStopTimeNs() const { return mStopTimeNs; }
+
+    int32_t getStreamID() const { return mStreamID; }  // Can change with setPlay()
+    int32_t getSoundID() const { return mSoundID; }    // Can change with play_l()
+    bool hasSound() const { return mSound.get() != nullptr; }
+
+    Stream* getPairStream() const;  // this never changes.  See top of header.
+
+private:
+    void play_l(const std::shared_ptr<Sound>& sound, int streamID,
+            float leftVolume, float rightVolume, int priority, int loop, float rate,
+            sp<AudioTrack> releaseTracks[2]);
+    void stop_l();
+    void setVolume_l(float leftVolume, float rightVolume);
+
+    // For use with AudioTrack callback.
+    static void staticCallback(int event, void* user, void* info);
+    void callback(int event, void* info, int toggle, int tries);
+
+    // StreamManager should be set on construction and not changed.
+    // release mLock before calling into StreamManager
+    StreamManager*     mStreamManager = nullptr;
+
+    mutable std::mutex  mLock;
+    std::atomic_int32_t mStreamID = 0;          // Note: valid streamIDs are always positive.
+    int                 mState = IDLE;
+    std::shared_ptr<Sound> mSound;              // Non-null if playing.
+    int32_t             mSoundID = 0;           // The sound ID associated with the AudioTrack.
+    float               mLeftVolume = 0.f;
+    float               mRightVolume = 0.f;
+    int32_t             mPriority = INT32_MIN;
+    int32_t             mLoop = 0;
+    float               mRate = 0.f;
+    bool                mAutoPaused = false;
+    bool                mMuted = false;
+
+    sp<AudioTrack>      mAudioTrack;
+    int                 mToggle = 0;
+    int64_t             mStopTimeNs = 0;        // if nonzero, time to wait for stop.
+};
+
+} // namespace android::soundpool
diff --git a/media/jni/soundpool/StreamManager.cpp b/media/jni/soundpool/StreamManager.cpp
new file mode 100644
index 0000000..8928c47
--- /dev/null
+++ b/media/jni/soundpool/StreamManager.cpp
@@ -0,0 +1,407 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "SoundPool::StreamManager"
+#include <utils/Log.h>
+
+#include "StreamManager.h"
+
+#include <audio_utils/clock.h>
+#include <audio_utils/roundup.h>
+
+namespace android::soundpool {
+
+// kMaxStreams is number that should be less than the current AudioTrack max per UID of 40.
+// It is the maximum number of AudioTrack resources allowed in the SoundPool.
+// We suggest a value at least 4 or greater to allow CTS tests to pass.
+static constexpr int32_t kMaxStreams = 32;
+
+// kStealActiveStream_OldestFirst = false historically (Q and earlier)
+// Changing to true could break app expectations but could change behavior beneficially.
+// In R, we change this to true, as it is the correct way per SoundPool documentation.
+static constexpr bool kStealActiveStream_OldestFirst = true;
+
+// kPlayOnCallingThread = true prior to R.
+// Changing to false means calls to play() are almost instantaneous instead of taking around
+// ~10ms to launch the AudioTrack. It is perhaps 100x faster.
+static constexpr bool kPlayOnCallingThread = false;
+
+// Amount of time for a StreamManager thread to wait before closing.
+static constexpr int64_t kWaitTimeBeforeCloseNs = 9 * NANOS_PER_SECOND;
+
+////////////
+
+StreamMap::StreamMap(int32_t streams) {
+    ALOGV("%s(%d)", __func__, streams);
+    if (streams > kMaxStreams) {
+        ALOGW("%s: requested %d streams, clamping to %d", __func__, streams, kMaxStreams);
+        streams = kMaxStreams;
+    } else if (streams < 1) {
+        ALOGW("%s: requested %d streams, clamping to 1", __func__, streams);
+        streams = 1;
+    }
+    mStreamPoolSize = streams * 2;
+    mStreamPool.reset(new Stream[mStreamPoolSize]);
+    // we use a perfect hash table with 2x size to map StreamIDs to Stream pointers.
+    mPerfectHash = std::make_unique<PerfectHash<int32_t, Stream *>>(roundup(mStreamPoolSize * 2));
+}
+
+Stream* StreamMap::findStream(int32_t streamID) const
+{
+    Stream *stream = lookupStreamFromId(streamID);
+    return stream != nullptr && stream->getStreamID() == streamID ? stream : nullptr;
+}
+
+size_t StreamMap::streamPosition(const Stream* stream) const
+{
+    ptrdiff_t index = stream - mStreamPool.get();
+    LOG_ALWAYS_FATAL_IF(index < 0 || index >= mStreamPoolSize,
+            "%s: stream position out of range: %td", __func__, index);
+    return (size_t)index;
+}
+
+Stream* StreamMap::lookupStreamFromId(int32_t streamID) const
+{
+    return streamID > 0 ? mPerfectHash->getValue(streamID).load() : nullptr;
+}
+
+int32_t StreamMap::getNextIdForStream(Stream* stream) const {
+    // even though it is const, it mutates the internal hash table.
+    const int32_t id = mPerfectHash->generateKey(
+        stream,
+        [] (Stream *stream) {
+            return stream == nullptr ? 0 : stream->getStreamID();
+        }, /* getKforV() */
+        stream->getStreamID() /* oldID */);
+    return id;
+}
+
+////////////
+
+StreamManager::StreamManager(
+        int32_t streams, size_t threads, const audio_attributes_t* attributes)
+    : StreamMap(streams)
+    , mAttributes(*attributes)
+{
+    ALOGV("%s(%d, %zu, ...)", __func__, streams, threads);
+    forEach([this](Stream *stream) {
+        stream->setStreamManager(this);
+        if ((streamPosition(stream) & 1) == 0) { // put the first stream of pair as available.
+            mAvailableStreams.insert(stream);
+        }
+    });
+
+    mThreadPool = std::make_unique<ThreadPool>(
+            std::min(threads, (size_t)std::thread::hardware_concurrency()),
+            "SoundPool_");
+}
+
+StreamManager::~StreamManager()
+{
+    ALOGV("%s", __func__);
+    {
+        std::unique_lock lock(mStreamManagerLock);
+        mQuit = true;
+        mStreamManagerCondition.notify_all();
+    }
+    mThreadPool->quit();
+
+    // call stop on the stream pool
+    forEach([](Stream *stream) { stream->stop(); });
+
+    // This invokes the destructor on the AudioTracks -
+    // we do it here to ensure that AudioTrack callbacks will not occur
+    // afterwards.
+    forEach([](Stream *stream) { stream->clearAudioTrack(); });
+}
+
+
+int32_t StreamManager::queueForPlay(const std::shared_ptr<Sound> &sound,
+        int32_t soundID, float leftVolume, float rightVolume,
+        int32_t priority, int32_t loop, float rate)
+{
+    ALOGV("%s(sound=%p, soundID=%d, leftVolume=%f, rightVolume=%f, priority=%d, loop=%d, rate=%f)",
+            __func__, sound.get(), soundID, leftVolume, rightVolume, priority, loop, rate);
+    bool launchThread = false;
+    int32_t streamID = 0;
+
+    { // for lock
+        std::unique_lock lock(mStreamManagerLock);
+        Stream *newStream = nullptr;
+        bool fromAvailableQueue = false;
+        ALOGV("%s: mStreamManagerLock lock acquired", __func__);
+
+        sanityCheckQueue_l();
+        // find an available stream, prefer one that has matching sound id.
+        if (mAvailableStreams.size() > 0) {
+            newStream = *mAvailableStreams.begin();
+            for (auto stream : mAvailableStreams) {
+                if (stream->getSoundID() == soundID) {
+                    newStream = stream;
+                    break;
+                }
+            }
+            if (newStream != nullptr) {
+                newStream->setStopTimeNs(systemTime());
+            }
+            fromAvailableQueue = true;
+        }
+
+        // also look in the streams restarting (if the paired stream doesn't have a pending play)
+        if (newStream == nullptr || newStream->getSoundID() != soundID) {
+            for (auto [unused , stream] : mRestartStreams) {
+                if (!stream->getPairStream()->hasSound()) {
+                    if (stream->getSoundID() == soundID) {
+                        newStream = stream;
+                        break;
+                    } else if (newStream == nullptr) {
+                        newStream = stream;
+                    }
+                }
+            }
+        }
+
+        // no available streams, look for one to steal from the active list
+        if (newStream == nullptr) {
+            for (auto stream : mActiveStreams) {
+                if (stream->getPriority() <= priority) {
+                    if (newStream == nullptr
+                            || newStream->getPriority() > stream->getPriority()) {
+                        newStream = stream;
+                    }
+                }
+            }
+            if (newStream != nullptr) { // we need to mute as it is still playing.
+                (void)newStream->requestStop(newStream->getStreamID());
+            }
+        }
+
+        // none found, look for a stream that is restarting, evict one.
+        if (newStream == nullptr) {
+            for (auto [unused, stream] : mRestartStreams) {
+                if (stream->getPairPriority() <= priority) {
+                    newStream = stream;
+                    break;
+                }
+            }
+        }
+
+        // DO NOT LOOK into mProcessingStreams as those are held by the StreamManager threads.
+
+        if (newStream == nullptr) {
+            ALOGD("%s: unable to find stream, returning 0", __func__);
+            return 0; // unable to find available stream
+        }
+
+        Stream *pairStream = newStream->getPairStream();
+        streamID = getNextIdForStream(pairStream);
+        pairStream->setPlay(
+                streamID, sound, soundID, leftVolume, rightVolume, priority, loop, rate);
+        if (fromAvailableQueue && kPlayOnCallingThread) {
+            removeFromQueues_l(newStream);
+            mProcessingStreams.emplace(newStream);
+            lock.unlock();
+            if (Stream* nextStream = newStream->playPairStream()) {
+                lock.lock();
+                ALOGV("%s: starting streamID:%d", __func__, nextStream->getStreamID());
+                addToActiveQueue_l(nextStream);
+            } else {
+                lock.lock();
+                mAvailableStreams.insert(newStream);
+                streamID = 0;
+            }
+            mProcessingStreams.erase(newStream);
+        } else {
+            launchThread = moveToRestartQueue_l(newStream) && needMoreThreads_l();
+        }
+        sanityCheckQueue_l();
+        ALOGV("%s: mStreamManagerLock released", __func__);
+    } // lock
+
+    if (launchThread) {
+        const int32_t id __unused = mThreadPool->launch([this](int32_t id) { run(id); });
+        ALOGV_IF(id != 0, "%s: launched thread %d", __func__, id);
+    }
+    ALOGV("%s: returning %d", __func__, streamID);
+    return streamID;
+}
+
+void StreamManager::moveToRestartQueue(
+        Stream* stream, int32_t activeStreamIDToMatch)
+{
+    ALOGV("%s(stream(ID)=%d, activeStreamIDToMatch=%d)",
+            __func__, stream->getStreamID(), activeStreamIDToMatch);
+    bool restart;
+    {
+        std::lock_guard lock(mStreamManagerLock);
+        sanityCheckQueue_l();
+        if (mProcessingStreams.count(stream) > 0 ||
+                mProcessingStreams.count(stream->getPairStream()) > 0) {
+            ALOGD("%s: attempting to restart processing stream(%d)",
+                    __func__, stream->getStreamID());
+            restart = false;
+        } else {
+            moveToRestartQueue_l(stream, activeStreamIDToMatch);
+            restart = needMoreThreads_l();
+        }
+        sanityCheckQueue_l();
+    }
+    if (restart) {
+        const int32_t id __unused = mThreadPool->launch([this](int32_t id) { run(id); });
+        ALOGV_IF(id != 0, "%s: launched thread %d", __func__, id);
+    }
+}
+
+bool StreamManager::moveToRestartQueue_l(
+        Stream* stream, int32_t activeStreamIDToMatch)
+{
+    ALOGV("%s(stream(ID)=%d, activeStreamIDToMatch=%d)",
+            __func__, stream->getStreamID(), activeStreamIDToMatch);
+    if (activeStreamIDToMatch > 0 && stream->getStreamID() != activeStreamIDToMatch) {
+        return false;
+    }
+    const ssize_t found = removeFromQueues_l(stream, activeStreamIDToMatch);
+    if (found < 0) return false;
+
+    LOG_ALWAYS_FATAL_IF(found > 1, "stream on %zd > 1 stream lists", found);
+
+    addToRestartQueue_l(stream);
+    mStreamManagerCondition.notify_one();
+    return true;
+}
+
+ssize_t StreamManager::removeFromQueues_l(
+        Stream* stream, int32_t activeStreamIDToMatch) {
+    size_t found = 0;
+    for (auto it = mActiveStreams.begin(); it != mActiveStreams.end(); ++it) {
+        if (*it == stream) {
+            mActiveStreams.erase(it); // we erase the iterator and break (otherwise it not safe).
+            ++found;
+            break;
+        }
+    }
+    // activeStreamIDToMatch is nonzero indicates we proceed only if found.
+    if (found == 0 && activeStreamIDToMatch > 0) {
+        return -1;  // special code: not present on active streams, ignore restart request
+    }
+
+    for (auto it = mRestartStreams.begin(); it != mRestartStreams.end(); ++it) {
+        if (it->second == stream) {
+            mRestartStreams.erase(it);
+            ++found;
+            break;
+        }
+    }
+    found += mAvailableStreams.erase(stream);
+
+    // streams on mProcessingStreams are undergoing processing by the StreamManager thread
+    // and do not participate in normal stream migration.
+    return found;
+}
+
+void StreamManager::addToRestartQueue_l(Stream *stream) {
+    mRestartStreams.emplace(stream->getStopTimeNs(), stream);
+}
+
+void StreamManager::addToActiveQueue_l(Stream *stream) {
+    if (kStealActiveStream_OldestFirst) {
+        mActiveStreams.push_back(stream);  // oldest to newest
+    } else {
+        mActiveStreams.push_front(stream); // newest to oldest
+    }
+}
+
+void StreamManager::run(int32_t id)
+{
+    ALOGV("%s(%d) entering", __func__, id);
+    int64_t waitTimeNs = kWaitTimeBeforeCloseNs;
+    std::unique_lock lock(mStreamManagerLock);
+    while (!mQuit) {
+        mStreamManagerCondition.wait_for(
+                lock, std::chrono::duration<int64_t, std::nano>(waitTimeNs));
+        ALOGV("%s(%d) awake", __func__, id);
+
+        sanityCheckQueue_l();
+
+        if (mQuit || (mRestartStreams.empty() && waitTimeNs == kWaitTimeBeforeCloseNs)) {
+            break;  // end the thread
+        }
+
+        waitTimeNs = kWaitTimeBeforeCloseNs;
+        while (!mQuit && !mRestartStreams.empty()) {
+            const nsecs_t nowNs = systemTime();
+            auto it = mRestartStreams.begin();
+            Stream* const stream = it->second;
+            const int64_t diffNs = stream->getStopTimeNs() - nowNs;
+            if (diffNs > 0) {
+                waitTimeNs = std::min(waitTimeNs, diffNs);
+                break;
+            }
+            mRestartStreams.erase(it);
+            mProcessingStreams.emplace(stream);
+            lock.unlock();
+            stream->stop();
+            ALOGV("%s(%d) stopping streamID:%d", __func__, id, stream->getStreamID());
+            if (Stream* nextStream = stream->playPairStream()) {
+                ALOGV("%s(%d) starting streamID:%d", __func__, id, nextStream->getStreamID());
+                lock.lock();
+                if (nextStream->getStopTimeNs() > 0) {
+                    // the next stream was stopped before we can move it to the active queue.
+                    ALOGV("%s(%d) stopping started streamID:%d",
+                            __func__, id, nextStream->getStreamID());
+                    moveToRestartQueue_l(nextStream);
+                } else {
+                    addToActiveQueue_l(nextStream);
+                }
+            } else {
+                lock.lock();
+                mAvailableStreams.insert(stream);
+            }
+            mProcessingStreams.erase(stream);
+            sanityCheckQueue_l();
+        }
+    }
+    ALOGV("%s(%d) exiting", __func__, id);
+}
+
+void StreamManager::dump() const
+{
+    forEach([](const Stream *stream) { stream->dump(); });
+}
+
+void StreamManager::sanityCheckQueue_l() const
+{
+    // We want to preserve the invariant that each stream pair is exactly on one of the queues.
+    const size_t availableStreams = mAvailableStreams.size();
+    const size_t restartStreams = mRestartStreams.size();
+    const size_t activeStreams = mActiveStreams.size();
+    const size_t processingStreams = mProcessingStreams.size();
+    const size_t managedStreams = availableStreams + restartStreams + activeStreams
+                + processingStreams;
+    const size_t totalStreams = getStreamMapSize() >> 1;
+    LOG_ALWAYS_FATAL_IF(managedStreams != totalStreams,
+            "%s: mAvailableStreams:%zu + mRestartStreams:%zu + "
+            "mActiveStreams:%zu + mProcessingStreams:%zu = %zu != total streams %zu",
+            __func__, availableStreams, restartStreams, activeStreams, processingStreams,
+            managedStreams, totalStreams);
+    ALOGV("%s: mAvailableStreams:%zu + mRestartStreams:%zu + "
+            "mActiveStreams:%zu + mProcessingStreams:%zu = %zu (total streams: %zu)",
+            __func__, availableStreams, restartStreams, activeStreams, processingStreams,
+            managedStreams, totalStreams);
+}
+
+} // namespace android::soundpool
diff --git a/media/jni/soundpool/StreamManager.h b/media/jni/soundpool/StreamManager.h
new file mode 100644
index 0000000..8c98ac9
--- /dev/null
+++ b/media/jni/soundpool/StreamManager.h
@@ -0,0 +1,467 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#pragma once
+
+#include "Stream.h"
+
+#include <condition_variable>
+#include <future>
+#include <list>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <unordered_set>
+#include <vector>
+
+#include <utils/AndroidThreads.h>
+
+namespace android::soundpool {
+
+// TODO: Move helper classes to a utility file, with separate test.
+
+/**
+ * JavaThread is used like std::thread but for threads that may call the JVM.
+ *
+ * std::thread does not easily attach to the JVM.  We need JVM capable threads
+ * from createThreadEtc() since android binder call optimization may attempt to
+ * call back into Java if the SoundPool runs in system server.
+ *
+ *
+ * No locking is required - the member variables are inherently thread-safe.
+ */
+class JavaThread {
+public:
+    JavaThread(std::function<void()> f, const char *name)
+        : mF{std::move(f)} {
+        createThreadEtc(staticFunction, this, name);
+    }
+
+    JavaThread(JavaThread &&) = delete; // uses "this" ptr, not moveable.
+
+    void join() const {
+        mFuture.wait();
+    }
+
+    bool isClosed() const {
+        return mIsClosed;
+    }
+
+private:
+    static int staticFunction(void *data) {
+        JavaThread *jt = static_cast<JavaThread *>(data);
+        jt->mF();
+        jt->mIsClosed = true;
+        jt->mPromise.set_value();
+        return 0;
+    }
+
+    // No locking is provided as these variables are initialized in the constructor
+    // and the members referenced are thread-safe objects.
+    // (mFuture.wait() can block multiple threads.)
+    // Note the order of member variables is reversed for destructor.
+    const std::function<void()> mF;
+    // Used in join() to block until the thread completes.
+    // See https://en.cppreference.com/w/cpp/thread/promise for the void specialization of
+    // promise.
+    std::promise<void>          mPromise;
+    std::future<void>           mFuture{mPromise.get_future()};
+    std::atomic_bool            mIsClosed = false;
+};
+
+/**
+ * The ThreadPool manages thread lifetimes of SoundPool worker threads.
+ *
+ * TODO: the (eventual) goal of ThreadPool is to transparently and cooperatively
+ * maximize CPU utilization while avoiding starvation of other applications.
+ * Some possibilities:
+ *
+ * We should create worker threads when we have SoundPool work and the system is idle.
+ * CPU cycles are "use-it-or-lose-it" when the system is idle.
+ *
+ * We should adjust the priority of worker threads so that the second (and subsequent) worker
+ * threads have lower priority (should we try to promote priority also?).
+ *
+ * We should throttle the spawning of new worker threads, spacing over time, to avoid
+ * creating too many new threads all at once, on initialization.
+ */
+class ThreadPool {
+public:
+    ThreadPool(size_t maxThreadCount, std::string name)
+        : mMaxThreadCount(maxThreadCount)
+        , mName{std::move(name)} { }
+
+    ~ThreadPool() { quit(); }
+
+    size_t getActiveThreadCount() const { return mActiveThreadCount; }
+    size_t getMaxThreadCount() const { return mMaxThreadCount; }
+
+    void quit() {
+        std::list<std::unique_ptr<JavaThread>> threads;
+        {
+            std::lock_guard lock(mThreadLock);
+            if (mQuit) return;  // already joined.
+            mQuit = true;
+            threads = std::move(mThreads);
+            mThreads.clear();
+        }
+        // mQuit set under lock, no more threads will be created.
+        for (auto &thread : threads) {
+            thread->join();
+            thread.reset();
+        }
+        LOG_ALWAYS_FATAL_IF(mActiveThreadCount != 0,
+                "Invalid Active Threads: %zu", (size_t)mActiveThreadCount);
+    }
+
+    // returns a non-zero id if successful, the id is to help logging messages.
+    int32_t launch(std::function<void(int32_t /* id */)> f) {
+        std::list<std::unique_ptr<JavaThread>> threadsToRelease; // release outside of lock.
+        std::lock_guard lock(mThreadLock);
+        if (mQuit) return 0;  // ignore if we have quit
+
+        // clean up threads.
+        for (auto it = mThreads.begin(); it != mThreads.end(); ) {
+            if ((*it)->isClosed()) {
+                threadsToRelease.emplace_back(std::move(*it));
+               it = mThreads.erase(it);
+            } else {
+               ++it;
+            }
+        }
+
+        const size_t threadCount = mThreads.size();
+        if (threadCount < mMaxThreadCount) {
+            // if the id wraps, we don't care about collisions.  it's just for logging.
+            mNextThreadId = mNextThreadId == INT32_MAX ? 1 : ++mNextThreadId;
+            const int32_t id = mNextThreadId;
+            mThreads.emplace_back(std::make_unique<JavaThread>(
+                    [this, id, mf = std::move(f)] { mf(id); --mActiveThreadCount; },
+                    (mName + std::to_string(id)).c_str()));
+            ++mActiveThreadCount;
+            return id;
+        }
+        return 0;
+    }
+
+    // TODO: launch only if load average is low.
+    // This gets the load average
+    // See also std::thread::hardware_concurrency() for the concurrent capability.
+    static double getLoadAvg() {
+        double loadAvg[1];
+        if (getloadavg(loadAvg, std::size(loadAvg)) > 0) {
+            return loadAvg[0];
+        }
+        return -1.;
+    }
+
+private:
+    const size_t            mMaxThreadCount;
+    const std::string       mName;
+
+    std::atomic_size_t      mActiveThreadCount = 0;
+
+    std::mutex              mThreadLock;
+    bool                    mQuit = false;           // GUARDED_BY(mThreadLock)
+    int32_t                 mNextThreadId = 0;       // GUARDED_BY(mThreadLock)
+    std::list<std::unique_ptr<JavaThread>> mThreads; // GUARDED_BY(mThreadLock)
+};
+
+/**
+ * A Perfect HashTable for IDs (key) to pointers (value).
+ *
+ * There are no collisions.  Why? because we generate the IDs for you to look up :-).
+ *
+ * The goal of this hash table is to map an integer ID handle > 0 to a pointer.
+ * We give these IDs in monotonic order (though we may skip if it were to cause a collision).
+ *
+ * The size of the hashtable must be large enough to accommodate the max number of keys.
+ * We suggest 2x.
+ *
+ * Readers are lockless
+ * Single writer could be lockless, but we allow multiple writers through an internal lock.
+ *
+ * For the Key type K, valid keys generated are > 0 (signed or unsigned)
+ * For the Value type V, values are pointers - nullptr means empty.
+ */
+template <typename K, typename V>
+class PerfectHash {
+public:
+    PerfectHash(size_t hashCapacity)
+        : mHashCapacity(hashCapacity)
+        , mK2V{new std::atomic<V>[hashCapacity]()} {
+    }
+
+    // Generate a key for a value V.
+    // There is a testing function getKforV() which checks what the value reports as its key.
+    //
+    // Calls back into getKforV under lock.
+    //
+    // We expect that the hashCapacity is 2x the number of stored keys in order
+    // to have one or two tries to find an empty slot
+    K generateKey(V value, std::function<K(V)> getKforV, K oldKey = 0) {
+        std::lock_guard lock(mHashLock);
+        // try to remove the old key.
+        if (oldKey > 0) {  // key valid
+            const V v = getValue(oldKey);
+            if (v != nullptr) {  // value still valid
+                const K atPosition = getKforV(v);
+                if (atPosition < 0 ||            // invalid value
+                        atPosition == oldKey ||  // value's key still valid and matches old key
+                        ((atPosition ^ oldKey) & (mHashCapacity - 1)) != 0) { // stale key entry
+                    getValue(oldKey) = nullptr;  // invalidate
+                }
+            } // else if value is invalid, no need to invalidate.
+        }
+        // check if we are invalidating only.
+        if (value == nullptr) return 0;
+        // now insert the new value and return the key.
+        size_t tries = 0;
+        for (; tries < mHashCapacity; ++tries) {
+            mNextKey = mNextKey == std::numeric_limits<K>::max() ? 1 : mNextKey + 1;
+            const V v = getValue(mNextKey);
+            //ALOGD("tries: %zu, key:%d value:%p", tries, (int)mNextKey, v);
+            if (v == nullptr) break; // empty
+            const K atPosition = getKforV(v);
+            //ALOGD("tries: %zu  key atPosition:%d", tries, (int)atPosition);
+            if (atPosition < 0 || // invalid value
+                    ((atPosition ^ mNextKey) & (mHashCapacity - 1)) != 0) { // stale key entry
+                break;
+           }
+        }
+        LOG_ALWAYS_FATAL_IF(tries == mHashCapacity, "hash table overflow!");
+        //ALOGD("%s: found after %zu tries", __func__, tries);
+        getValue(mNextKey) = value;
+        return mNextKey;
+    }
+
+    std::atomic<V> &getValue(K key) { return mK2V[key & (mHashCapacity - 1)]; }
+    const std::atomic_int32_t &getValue(K key) const { return mK2V[key & (mHashCapacity - 1)]; }
+
+private:
+    mutable std::mutex          mHashLock;
+    const size_t                mHashCapacity; // size of mK2V no lock needed.
+    std::unique_ptr<std::atomic<V>[]> mK2V;    // no lock needed for read access.
+    K                           mNextKey{};    // GUARDED_BY(mHashLock)
+};
+
+/**
+ * StreamMap contains the all the valid streams available to SoundPool.
+ *
+ * There is no Lock required for this class because the streams are
+ * allocated in the constructor, the lookup is lockless, and the Streams
+ * returned are locked internally.
+ *
+ * The lookup uses a perfect hash.
+ * It is possible to use a lockless hash table or to use a stripe-locked concurrent
+ * hashmap for essentially lock-free lookup.
+ *
+ * This follows Map-Reduce parallelism model.
+ * https://en.wikipedia.org/wiki/MapReduce
+ *
+ * Conceivably the forEach could be parallelized using std::for_each with a
+ * std::execution::par policy.
+ *
+ * https://en.cppreference.com/w/cpp/algorithm/for_each
+ */
+class StreamMap {
+public:
+    explicit StreamMap(int32_t streams);
+
+    // Returns the stream associated with streamID or nullptr if not found.
+    // This need not be locked.
+    // The stream ID will never migrate to another Stream, but it may change
+    // underneath you.  The Stream operations that take a streamID will confirm
+    // that the streamID matches under the Stream lock before executing otherwise
+    // it ignores the command as stale.
+    Stream* findStream(int32_t streamID) const;
+
+    // Iterates through the stream pool applying the function f.
+    // Since this enumerates over every single stream, it is unlocked.
+    //
+    // See related: https://en.cppreference.com/w/cpp/algorithm/for_each
+    void forEach(std::function<void(const Stream *)>f) const {
+        for (size_t i = 0; i < mStreamPoolSize; ++i) {
+            f(&mStreamPool[i]);
+        }
+    }
+
+    void forEach(std::function<void(Stream *)>f) {
+        for (size_t i = 0; i < mStreamPoolSize; ++i) {
+            f(&mStreamPool[i]);
+        }
+    }
+
+    // Returns the pair stream for a given Stream.
+    // This need not be locked as it is a property of the pointer address.
+    Stream* getPairStream(const Stream* stream) const {
+        const size_t index = streamPosition(stream);
+        return &mStreamPool[index ^ 1];
+    }
+
+    // find the position of the stream in mStreamPool array.
+    size_t streamPosition(const Stream* stream) const; // no lock needed
+
+    size_t getStreamMapSize() const {
+        return mStreamPoolSize;
+    }
+
+    // find the next valid ID for a stream and store in hash table.
+    int32_t getNextIdForStream(Stream* stream) const;
+
+private:
+
+    // use the hash table to attempt to find the stream.
+    // nullptr is returned if the lookup fails.
+    Stream* lookupStreamFromId(int32_t streamID) const;
+
+    // The stream pool is initialized in the constructor, effectively const.
+    // no locking required for access.
+    //
+    // The constructor parameter "streams" results in streams pairs of streams.
+    // We have twice as many streams because we wish to return a streamID "handle"
+    // back to the app immediately, while we may be stopping the other stream in the
+    // pair to get its AudioTrack :-).
+    //
+    // Of the stream pair, only one of the streams may have an AudioTrack.
+    // The fixed association of a stream pair allows callbacks from the AudioTrack
+    // to be associated properly to either one or the other of the stream pair.
+    //
+    // TODO: The stream pair arrangement can be removed if we have better AudioTrack
+    // callback handling (being able to remove and change the callback after construction).
+    //
+    // Streams may be accessed anytime off of the stream pool
+    // as there is internal locking on each stream.
+    std::unique_ptr<Stream[]>   mStreamPool;        // no lock needed for access.
+    size_t                      mStreamPoolSize;    // no lock needed for access.
+
+    // In order to find the Stream from a StreamID, we could do a linear lookup in mStreamPool.
+    // As an alternative, one could use stripe-locked or lock-free concurrent hashtables.
+    //
+    // When considering linear search vs hashmap, verify the typical use-case size.
+    // Linear search is faster than std::unordered_map (circa 2018) for less than 40 elements.
+    // [ Skarupke, M. (2018), "You Can Do Better than std::unordered_map: New and Recent
+    // Improvements to Hash Table Performance." C++Now 2018. cppnow.org, see
+    // https://www.youtube.com/watch?v=M2fKMP47slQ ]
+    //
+    // Here, we use a PerfectHash of Id to Stream *, since we can control the
+    // StreamID returned to the user.  This allows O(1) read access to mStreamPool lock-free.
+    //
+    // We prefer that the next stream ID is monotonic for aesthetic reasons
+    // (if we didn't care about monotonicity, a simple method is to apply a generation count
+    // to each stream in the unused upper bits of its index in mStreamPool for the id).
+    //
+    std::unique_ptr<PerfectHash<int32_t, Stream *>> mPerfectHash;
+};
+
+/**
+ * StreamManager is used to manage the streams (accessed by StreamID from Java).
+ *
+ * Locking order (proceeds from application to component).
+ *  SoundPool mApiLock (if needed) -> StreamManager mStreamManagerLock
+ *                                 -> pair Stream mLock -> queued Stream mLock
+ */
+class StreamManager : public StreamMap {
+public:
+    // Note: the SoundPool pointer is only used for stream initialization.
+    // It is not stored in StreamManager.
+    StreamManager(int32_t streams, size_t threads, const audio_attributes_t* attributes);
+    ~StreamManager();
+
+    // Returns positive streamID on success, 0 on failure.  This is locked.
+    int32_t queueForPlay(const std::shared_ptr<Sound> &sound,
+            int32_t soundID, float leftVolume, float rightVolume,
+            int32_t priority, int32_t loop, float rate);
+
+    ///////////////////////////////////////////////////////////////////////
+    // Called from soundpool::Stream
+
+    const audio_attributes_t* getAttributes() const { return &mAttributes; }
+
+    // Moves the stream to the restart queue (called upon BUFFER_END of the static track)
+    // this is locked internally.
+    // If activeStreamIDToMatch is nonzero, it will only move to the restart queue
+    // if the streamIDToMatch is found on the active queue.
+    void moveToRestartQueue(Stream* stream, int32_t activeStreamIDToMatch = 0);
+
+private:
+
+    void run(int32_t id);                        // worker thread, takes lock internally.
+    void dump() const;                           // no lock needed
+
+    // returns true if more worker threads are needed.
+    bool needMoreThreads_l() {
+        return mRestartStreams.size() > 0 &&
+                (mThreadPool->getActiveThreadCount() == 0
+                || std::distance(mRestartStreams.begin(),
+                        mRestartStreams.upper_bound(systemTime()))
+                        > (ptrdiff_t)mThreadPool->getActiveThreadCount());
+    }
+
+    // returns true if the stream was added.
+    bool moveToRestartQueue_l(Stream* stream, int32_t activeStreamIDToMatch = 0);
+    // returns number of queues the stream was removed from (should be 0 or 1);
+    // a special code of -1 is returned if activeStreamIDToMatch is > 0 and
+    // the stream wasn't found on the active queue.
+    ssize_t removeFromQueues_l(Stream* stream, int32_t activeStreamIDToMatch = 0);
+    void addToRestartQueue_l(Stream *stream);
+    void addToActiveQueue_l(Stream *stream);
+    void sanityCheckQueue_l() const;
+
+    const audio_attributes_t mAttributes;
+    std::unique_ptr<ThreadPool> mThreadPool;                  // locked internally
+
+    // mStreamManagerLock is used to lock access for transitions between the
+    // 4 stream queues by the Manager Thread or by the user initiated play().
+    // A stream pair has exactly one stream on exactly one of the queues.
+    std::mutex                  mStreamManagerLock;
+    std::condition_variable     mStreamManagerCondition;
+
+    bool                        mQuit = false;      // GUARDED_BY(mStreamManagerLock)
+
+    // There are constructor arg "streams" pairs of streams, only one of each
+    // pair on the 4 stream queues below.  The other stream in the pair serves as
+    // placeholder to accumulate user changes, pending actual availability of the
+    // AudioTrack, as it may be in use, requiring stop-then-restart.
+    //
+    // The 4 queues are implemented in the appropriate STL container based on perceived
+    // optimality.
+
+    // 1) mRestartStreams: Streams awaiting stop.
+    // The paired stream may be active (but with no AudioTrack), and will be restarted
+    // with an active AudioTrack when the current stream is stopped.
+    std::multimap<int64_t /* stopTimeNs */, Stream*>
+                                mRestartStreams;    // GUARDED_BY(mStreamManagerLock)
+
+    // 2) mActiveStreams: Streams that are active.
+    // The paired stream will be inactive.
+    // This is in order of specified by kStealActiveStream_OldestFirst
+    std::list<Stream*>          mActiveStreams;     // GUARDED_BY(mStreamManagerLock)
+
+    // 3) mAvailableStreams: Streams that are inactive.
+    // The paired stream will also be inactive.
+    // No particular order.
+    std::unordered_set<Stream*> mAvailableStreams;  // GUARDED_BY(mStreamManagerLock)
+
+    // 4) mProcessingStreams: Streams that are being processed by the ManagerThreads
+    // When on this queue, the stream and its pair are not available for stealing.
+    // Each ManagerThread will have at most one stream on the mProcessingStreams queue.
+    // The paired stream may be active or restarting.
+    // No particular order.
+    std::unordered_set<Stream*> mProcessingStreams; // GUARDED_BY(mStreamManagerLock)
+};
+
+} // namespace android::soundpool
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 16073ae..52aaf4f 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -32,6 +32,7 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.os.PowerManager;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.GestureDetector;
@@ -109,6 +110,8 @@
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
 import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
 import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.phone.DozeScrimController;
+import com.android.systemui.statusbar.phone.DozeServiceHost;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.LightBarController;
@@ -301,6 +304,9 @@
             ScrimController scrimController,
             Lazy<LockscreenWallpaper> lockscreenWallpaperLazy,
             Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
+            DozeServiceHost dozeServiceHost,
+            PowerManager powerManager,
+            DozeScrimController dozeScrimController,
 
             /* Car Settings injected components. */
             NavigationBarViewFactory navigationBarViewFactory) {
@@ -364,7 +370,10 @@
                 dozeParameters,
                 scrimController,
                 lockscreenWallpaperLazy,
-                biometricUnlockControllerLazy);
+                biometricUnlockControllerLazy,
+                dozeServiceHost,
+                powerManager,
+                dozeScrimController);
         mScrimController = scrimController;
         mNavigationBarViewFactory = navigationBarViewFactory;
     }
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
index 3014452..3b48259 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
@@ -18,6 +18,7 @@
 
 import static android.content.DialogInterface.BUTTON_NEGATIVE;
 import static android.content.DialogInterface.BUTTON_POSITIVE;
+import static android.os.UserManager.DISALLOW_ADD_USER;
 
 import android.app.ActivityManager;
 import android.app.AlertDialog;
@@ -145,7 +146,8 @@
         userRecords.add(createStartGuestUserRecord());
 
         // Add add user record if the foreground user can add users
-        if (mCarUserManagerHelper.canForegroundUserAddUsers()) {
+        UserHandle fgUserHandle = UserHandle.of(ActivityManager.getCurrentUser());
+        if (!mUserManager.hasUserRestriction(DISALLOW_ADD_USER, fgUserHandle)) {
             userRecords.add(createAddUserRecord());
         }
 
@@ -285,7 +287,7 @@
         }
 
         private void handleAddUserClicked() {
-            if (mCarUserManagerHelper.isUserLimitReached()) {
+            if (!mUserManager.canAddMoreUsers()) {
                 mAddUserView.setEnabled(true);
                 showMaxUserLimitReachedDialog();
             } else {
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/NotificationPersonExtractorPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/NotificationPersonExtractorPlugin.java
new file mode 100644
index 0000000..802a8da
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/NotificationPersonExtractorPlugin.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.plugins;
+
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.graphics.drawable.Drawable;
+import android.service.notification.StatusBarNotification;
+
+import com.android.systemui.plugins.annotations.DependsOn;
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+/** Custom logic that can extract a PeopleHub "person" from a notification. */
+@ProvidesInterface(
+        action = NotificationPersonExtractorPlugin.ACTION,
+        version = NotificationPersonExtractorPlugin.VERSION)
+@DependsOn(target = NotificationPersonExtractorPlugin.PersonData.class)
+public interface NotificationPersonExtractorPlugin extends Plugin {
+
+    String ACTION = "com.android.systemui.action.PEOPLE_HUB_PERSON_EXTRACTOR";
+    int VERSION = 0;
+
+    /**
+     * Attempts to extract a person from a notification. Returns {@code null} if one is not found.
+     */
+    @Nullable PersonData extractPerson(StatusBarNotification sbn);
+
+    /**
+     * Attempts to extract a person id from a notification. Returns {@code null} if one is not
+     * found.
+     *
+     * This method can be overridden in order to provide a faster implementation.
+     */
+    @Nullable
+    default String extractPersonKey(StatusBarNotification sbn) {
+        return extractPerson(sbn).key;
+    }
+
+    /** A person to be surfaced in PeopleHub. */
+    @ProvidesInterface(version = PersonData.VERSION)
+    final class PersonData {
+
+        public static final int VERSION = 0;
+
+        public final String key;
+        public final CharSequence name;
+        public final Drawable avatar;
+        public final PendingIntent clickIntent;
+
+        public PersonData(String key, CharSequence name, Drawable avatar,
+                PendingIntent clickIntent) {
+            this.key = key;
+            this.name = name;
+            this.avatar = avatar;
+            this.clickIntent = clickIntent;
+        }
+    }
+}
diff --git a/packages/SystemUI/res/layout/people_strip.xml b/packages/SystemUI/res/layout/people_strip.xml
index b314db8..f0ac08b 100644
--- a/packages/SystemUI/res/layout/people_strip.xml
+++ b/packages/SystemUI/res/layout/people_strip.xml
@@ -33,10 +33,9 @@
     <LinearLayout
         android:id="@+id/people_list"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center"
-        android:paddingTop="8dp"
-        android:paddingBottom="8dp"
+        android:layout_height="match_parent"
+        android:paddingTop="12dp"
+        android:paddingBottom="12dp"
         android:gravity="center"
         android:orientation="horizontal">
 
@@ -49,7 +48,7 @@
         <LinearLayout
             android:layout_width="70dp"
             android:layout_height="match_parent"
-            android:gravity="center"
+            android:gravity="center_horizontal"
             android:orientation="vertical"
             android:visibility="invisible">
 
@@ -87,7 +86,7 @@
         <LinearLayout
             android:layout_width="70dp"
             android:layout_height="match_parent"
-            android:gravity="center"
+            android:gravity="center_horizontal"
             android:orientation="vertical"
             android:visibility="invisible">
 
@@ -125,7 +124,7 @@
         <LinearLayout
             android:layout_width="70dp"
             android:layout_height="match_parent"
-            android:gravity="center"
+            android:gravity="center_horizontal"
             android:orientation="vertical"
             android:visibility="invisible">
 
@@ -163,7 +162,7 @@
         <LinearLayout
             android:layout_width="70dp"
             android:layout_height="match_parent"
-            android:gravity="center"
+            android:gravity="center_horizontal"
             android:orientation="vertical"
             android:visibility="invisible">
 
@@ -201,7 +200,7 @@
         <LinearLayout
             android:layout_width="70dp"
             android:layout_height="match_parent"
-            android:gravity="center"
+            android:gravity="center_horizontal"
             android:orientation="vertical"
             android:visibility="invisible">
 
diff --git a/packages/SystemUI/src/com/android/systemui/LatencyTester.java b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
index ddbabee..30a60ab 100644
--- a/packages/SystemUI/src/com/android/systemui/LatencyTester.java
+++ b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui;
 
+import static android.os.PowerManager.WAKE_REASON_UNKNOWN;
+
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -28,21 +30,32 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
-import com.android.systemui.statusbar.phone.StatusBar;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
 
 /**
  * Class that only runs on debuggable builds that listens to broadcasts that simulate actions in the
  * system that are used for testing the latency.
  */
+@Singleton
 public class LatencyTester extends SystemUI {
 
-    private static final String ACTION_FINGERPRINT_WAKE =
+    private static final String
+            ACTION_FINGERPRINT_WAKE =
             "com.android.systemui.latency.ACTION_FINGERPRINT_WAKE";
-    private static final String ACTION_TURN_ON_SCREEN =
+    private static final String
+            ACTION_TURN_ON_SCREEN =
             "com.android.systemui.latency.ACTION_TURN_ON_SCREEN";
+    private final BiometricUnlockController mBiometricUnlockController;
+    private final PowerManager mPowerManager;
 
-    public LatencyTester(Context context) {
+    @Inject
+    public LatencyTester(Context context, BiometricUnlockController biometricUnlockController,
+            PowerManager powerManager) {
         super(context);
+        mBiometricUnlockController = biometricUnlockController;
+        mPowerManager = powerManager;
     }
 
     @Override
@@ -68,19 +81,17 @@
     }
 
     private void fakeTurnOnScreen() {
-        PowerManager powerManager = mContext.getSystemService(PowerManager.class);
         if (LatencyTracker.isEnabled(mContext)) {
             LatencyTracker.getInstance(mContext).onActionStart(
                     LatencyTracker.ACTION_TURN_ON_SCREEN);
         }
-        powerManager.wakeUp(SystemClock.uptimeMillis(), "android.policy:LATENCY_TESTS");
+        mPowerManager.wakeUp(
+                SystemClock.uptimeMillis(), WAKE_REASON_UNKNOWN, "android.policy:LATENCY_TESTS");
     }
 
     private void fakeWakeAndUnlock() {
-        BiometricUnlockController biometricUnlockController = getComponent(StatusBar.class)
-                .getBiometricUnlockController();
-        biometricUnlockController.onBiometricAcquired(BiometricSourceType.FINGERPRINT);
-        biometricUnlockController.onBiometricAuthenticated(
+        mBiometricUnlockController.onBiometricAcquired(BiometricSourceType.FINGERPRINT);
+        mBiometricUnlockController.onBiometricAuthenticated(
                 KeyguardUpdateMonitor.getCurrentUser(), BiometricSourceType.FINGERPRINT);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
index 6674c12..9032c6f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
@@ -20,6 +20,7 @@
 import com.android.systemui.appops.AppOpsController;
 import com.android.systemui.appops.AppOpsControllerImpl;
 import com.android.systemui.classifier.FalsingManagerProxy;
+import com.android.systemui.doze.DozeHost;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.FalsingManager;
@@ -33,6 +34,7 @@
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.phone.DarkIconDispatcherImpl;
+import com.android.systemui.statusbar.phone.DozeServiceHost;
 import com.android.systemui.statusbar.phone.ManagedProfileController;
 import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -241,5 +243,10 @@
     /**
      */
     @Binds
-    public abstract FalsingManager provideFalsingmanager(FalsingManagerProxy falsingManagerImpl);
+    public abstract FalsingManager provideFalsingManager(FalsingManagerProxy falsingManagerImpl);
+
+    /**
+     */
+    @Binds
+    public abstract DozeHost provideDozeHost(DozeServiceHost dozeServiceHost);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
index 49cd414..71c2e38 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.dagger;
 
+import com.android.systemui.LatencyTester;
 import com.android.systemui.SystemUI;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.pip.PipUI;
@@ -48,6 +49,12 @@
     @ClassKey(KeyguardViewMediator.class)
     public abstract SystemUI bindKeyguardViewMediator(KeyguardViewMediator sysui);
 
+    /** Inject into LatencyTests. */
+    @Binds
+    @IntoMap
+    @ClassKey(LatencyTester.class)
+    public abstract SystemUI bindLatencyTester(LatencyTester sysui);
+
     /** Inject into PipUI. */
     @Binds
     @IntoMap
@@ -66,6 +73,7 @@
     @ClassKey(Recents.class)
     public abstract SystemUI bindRecents(Recents sysui);
 
+
     /** Inject into VolumeUI. */
     @Binds
     @IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 6e464f4..b4dc538 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -138,6 +138,14 @@
         mNotificationEntryListeners.add(listener);
     }
 
+    /**
+     * Removes a {@link NotificationEntryListener} previously registered via
+     * {@link #addNotificationEntryListener(NotificationEntryListener)}.
+     */
+    public void removeNotificationEntryListener(NotificationEntryListener listener) {
+        mNotificationEntryListeners.remove(listener);
+    }
+
     /** Sets the {@link NotificationRemoveInterceptor}. */
     public void setNotificationRemoveInterceptor(NotificationRemoveInterceptor interceptor) {
         mRemoveInterceptor = interceptor;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java
index 7e398bb..7d0ce5c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java
@@ -37,6 +37,7 @@
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
 import com.android.systemui.statusbar.notification.logging.NotifEvent;
 import com.android.systemui.statusbar.notification.logging.NotifLog;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
@@ -76,12 +77,16 @@
     private final Ranking mTmpRanking = new Ranking();
     private final boolean mUsePeopleFiltering;
     private final NotifLog mNotifLog;
+    private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
 
     @Inject
-    public NotificationData(NotificationSectionsFeatureManager sectionsFeatureManager,
-            NotifLog notifLog) {
+    public NotificationData(
+            NotificationSectionsFeatureManager sectionsFeatureManager,
+            NotifLog notifLog,
+            PeopleNotificationIdentifier peopleNotificationIdentifier) {
         mUsePeopleFiltering = sectionsFeatureManager.isFilteringEnabled();
         mNotifLog = notifLog;
+        mPeopleNotificationIdentifier = peopleNotificationIdentifier;
     }
 
     public void setHeadsUpManager(HeadsUpManager headsUpManager) {
@@ -460,8 +465,7 @@
     }
 
     private boolean isPeopleNotification(NotificationEntry e) {
-        return e.getSbn().getNotification().getNotificationStyle()
-                == Notification.MessagingStyle.class;
+        return mPeopleNotificationIdentifier.isPeopleNotification(e.getSbn());
     }
 
     public void dump(PrintWriter pw, String indent) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubModule.kt
index 8c067b7..4570989 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubModule.kt
@@ -24,14 +24,24 @@
 
     @Binds
     abstract fun peopleHubSectionFooterViewController(
-        viewAdapter: PeopleHubSectionFooterViewAdapterImpl
+        impl: PeopleHubSectionFooterViewAdapterImpl
     ): PeopleHubSectionFooterViewAdapter
 
     @Binds
-    abstract fun peopleHubDataSource(s: PeopleHubDataSourceImpl): DataSource<PeopleHubModel>
+    abstract fun peopleHubDataSource(impl: PeopleHubDataSourceImpl): DataSource<PeopleHubModel>
 
     @Binds
     abstract fun peopleHubViewModelFactoryDataSource(
-        dataSource: PeopleHubViewModelFactoryDataSourceImpl
+        impl: PeopleHubViewModelFactoryDataSourceImpl
     ): DataSource<PeopleHubViewModelFactory>
+
+    @Binds
+    abstract fun peopleNotificationIdentifier(
+        impl: PeopleNotificationIdentifierImpl
+    ): PeopleNotificationIdentifier
+
+    @Binds
+    abstract fun notificationPersonExtractor(
+        pluginImpl: NotificationPersonExtractorPluginBoundary
+    ): NotificationPersonExtractor
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
index c8a3c1a..fe257d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
@@ -17,11 +17,14 @@
 package com.android.systemui.statusbar.notification.people
 
 import android.app.Notification
+import android.content.Context
 import android.graphics.Canvas
 import android.graphics.ColorFilter
 import android.graphics.PixelFormat
 import android.graphics.drawable.BitmapDrawable
 import android.graphics.drawable.Drawable
+import android.os.UserHandle
+import android.service.notification.StatusBarNotification
 import android.util.TypedValue
 import android.view.View
 import android.view.ViewGroup
@@ -30,59 +33,112 @@
 import com.android.internal.widget.MessagingGroup
 import com.android.launcher3.icons.BaseIconFactory
 import com.android.systemui.R
+import com.android.systemui.plugins.NotificationPersonExtractorPlugin
 import com.android.systemui.statusbar.notification.NotificationEntryListener
 import com.android.systemui.statusbar.notification.NotificationEntryManager
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.policy.ExtensionController
 import java.util.ArrayDeque
 import javax.inject.Inject
 import javax.inject.Singleton
 
 private const val MAX_STORED_INACTIVE_PEOPLE = 10
 
-@Singleton
-class PeopleHubDataSourceImpl @Inject constructor(
-    notificationEntryManager: NotificationEntryManager,
-    private val peopleHubManager: PeopleHubManager
-) : DataSource<PeopleHubModel> {
+interface NotificationPersonExtractor {
+    fun extractPerson(sbn: StatusBarNotification): PersonModel?
+    fun extractPersonKey(sbn: StatusBarNotification): String?
+}
 
-    private var dataListener: DataListener<PeopleHubModel>? = null
+@Singleton
+class NotificationPersonExtractorPluginBoundary @Inject constructor(
+    extensionController: ExtensionController,
+    private val context: Context
+) : NotificationPersonExtractor {
+
+    private var plugin: NotificationPersonExtractorPlugin? = null
 
     init {
-        notificationEntryManager.addNotificationEntryListener(object : NotificationEntryListener {
-            override fun onEntryInflated(entry: NotificationEntry, inflatedFlags: Int) =
-                    addVisibleEntry(entry)
+        plugin = extensionController
+                .newExtension(NotificationPersonExtractorPlugin::class.java)
+                .withPlugin(NotificationPersonExtractorPlugin::class.java)
+                .withCallback { extractor ->
+                    plugin = extractor
+                }
+                .build()
+                .get()
+    }
 
-            override fun onEntryReinflated(entry: NotificationEntry) = addVisibleEntry(entry)
+    override fun extractPerson(sbn: StatusBarNotification) =
+            plugin?.extractPerson(sbn)?.let { data ->
+                val badged = addBadgeToDrawable(data.avatar, context, sbn.packageName, sbn.user)
+                PersonModel(data.key, data.name, badged, data.clickIntent)
+            }
 
-            override fun onPostEntryUpdated(entry: NotificationEntry) = addVisibleEntry(entry)
+    override fun extractPersonKey(sbn: StatusBarNotification) = plugin?.extractPersonKey(sbn)
+}
 
-            override fun onEntryRemoved(
-                entry: NotificationEntry,
-                visibility: NotificationVisibility?,
-                removedByUser: Boolean
-            ) = removeVisibleEntry(entry)
-        })
+@Singleton
+class PeopleHubDataSourceImpl @Inject constructor(
+    private val notificationEntryManager: NotificationEntryManager,
+    private val peopleHubManager: PeopleHubManager,
+    private val extractor: NotificationPersonExtractor
+) : DataSource<PeopleHubModel> {
+
+    private val dataListeners = mutableListOf<DataListener<PeopleHubModel>>()
+
+    private val notificationEntryListener = object : NotificationEntryListener {
+        override fun onEntryInflated(entry: NotificationEntry, inflatedFlags: Int) =
+                addVisibleEntry(entry)
+
+        override fun onEntryReinflated(entry: NotificationEntry) = addVisibleEntry(entry)
+
+        override fun onPostEntryUpdated(entry: NotificationEntry) = addVisibleEntry(entry)
+
+        override fun onEntryRemoved(
+            entry: NotificationEntry,
+            visibility: NotificationVisibility?,
+            removedByUser: Boolean
+        ) = removeVisibleEntry(entry)
     }
 
     private fun removeVisibleEntry(entry: NotificationEntry) {
-        if (entry.extractPersonKey()?.let(peopleHubManager::removeActivePerson) == true) {
+        val key = extractor.extractPersonKey(entry.sbn) ?: entry.extractPersonKey()
+        if (key?.let(peopleHubManager::removeActivePerson) == true) {
             updateUi()
         }
     }
 
     private fun addVisibleEntry(entry: NotificationEntry) {
-        if (entry.extractPerson()?.let(peopleHubManager::addActivePerson) == true) {
+        val personModel = extractor.extractPerson(entry.sbn) ?: entry.extractPerson()
+        if (personModel?.let(peopleHubManager::addActivePerson) == true) {
             updateUi()
         }
     }
 
-    override fun setListener(listener: DataListener<PeopleHubModel>) {
-        this.dataListener = listener
-        updateUi()
+    override fun registerListener(listener: DataListener<PeopleHubModel>): Subscription {
+        val registerWithNotificationEntryManager = dataListeners.isEmpty()
+        dataListeners.add(listener)
+        if (registerWithNotificationEntryManager) {
+            notificationEntryManager.addNotificationEntryListener(notificationEntryListener)
+        } else {
+            listener.onDataChanged(peopleHubManager.getPeopleHubModel())
+        }
+        return object : Subscription {
+            override fun unsubscribe() {
+                dataListeners.remove(listener)
+                if (dataListeners.isEmpty()) {
+                    notificationEntryManager
+                            .removeNotificationEntryListener(notificationEntryListener)
+                }
+            }
+        }
     }
 
     private fun updateUi() {
-        dataListener?.onDataChanged(peopleHubManager.getPeopleHubModel())
+        val model = peopleHubManager.getPeopleHubModel()
+        for (listener in dataListeners) {
+            listener.onDataChanged(model)
+        }
     }
 }
 
@@ -124,19 +180,26 @@
     if (!isMessagingNotification()) {
         return null
     }
-
     val clickIntent = sbn.notification.contentIntent
+            ?: return null
     val extras = sbn.notification.extras
     val name = extras.getString(Notification.EXTRA_CONVERSATION_TITLE)
             ?: extras.getString(Notification.EXTRA_TITLE)
             ?: return null
     val drawable = extractAvatarFromRow(this) ?: return null
+    val badgedAvatar = addBadgeToDrawable(drawable, row.context, sbn.packageName, sbn.user)
+    return PersonModel(key, name, badgedAvatar, clickIntent)
+}
 
-    val context = row.context
+private fun addBadgeToDrawable(
+    drawable: Drawable,
+    context: Context,
+    packageName: String,
+    user: UserHandle
+): Drawable {
     val pm = context.packageManager
-    val appInfo = pm.getApplicationInfoAsUser(sbn.packageName, 0, sbn.user)
-
-    val badgedAvatar = object : Drawable() {
+    val appInfo = pm.getApplicationInfoAsUser(packageName, 0, user)
+    return object : Drawable() {
         override fun draw(canvas: Canvas) {
             val iconBounds = getBounds()
             val factory = object : BaseIconFactory(
@@ -146,7 +209,7 @@
                     true) {}
             val badge = factory.createBadgedIconBitmap(
                     appInfo.loadIcon(pm),
-                    sbn.user,
+                    user,
                     true,
                     appInfo.isInstantApp,
                     null)
@@ -156,7 +219,7 @@
                         colorFilter = drawable.colorFilter
                         val badgeWidth = TypedValue.applyDimension(
                                 TypedValue.COMPLEX_UNIT_DIP,
-                                16f,
+                                15f,
                                 context.resources.displayMetrics
                         ).toInt()
                         setBounds(
@@ -181,8 +244,6 @@
         @PixelFormat.Opacity
         override fun getOpacity(): Int = PixelFormat.OPAQUE
     }
-
-    return PersonModel(key, name, badgedAvatar, clickIntent)
 }
 
 private fun extractAvatarFromRow(entry: NotificationEntry): Drawable? =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubViewController.kt
index 8d1253b..5c35408 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubViewController.kt
@@ -60,8 +60,9 @@
     private val dataSource: DataSource<@JvmSuppressWildcards PeopleHubViewModelFactory>
 ) : PeopleHubSectionFooterViewAdapter {
 
-    override fun bindView(viewBoundary: PeopleHubSectionFooterViewBoundary) =
-            dataSource.setListener(PeopleHubDataListenerImpl(viewBoundary))
+    override fun bindView(viewBoundary: PeopleHubSectionFooterViewBoundary) {
+        dataSource.registerListener(PeopleHubDataListenerImpl(viewBoundary))
+    }
 }
 
 private class PeopleHubDataListenerImpl(
@@ -92,8 +93,8 @@
     private val dataSource: DataSource<@JvmSuppressWildcards PeopleHubModel>
 ) : DataSource<PeopleHubViewModelFactory> {
 
-    override fun setListener(listener: DataListener<PeopleHubViewModelFactory>) =
-            dataSource.setListener(PeopleHubModelListenerImpl(activityStarter, listener))
+    override fun registerListener(listener: DataListener<PeopleHubViewModelFactory>) =
+            dataSource.registerListener(PeopleHubModelListenerImpl(activityStarter, listener))
 }
 
 private class PeopleHubModelListenerImpl(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
new file mode 100644
index 0000000..bfd4070
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.people
+
+import android.app.Notification
+import android.service.notification.StatusBarNotification
+import javax.inject.Inject
+import javax.inject.Singleton
+
+interface PeopleNotificationIdentifier {
+    fun isPeopleNotification(sbn: StatusBarNotification): Boolean
+}
+
+@Singleton
+class PeopleNotificationIdentifierImpl @Inject constructor(
+    private val personExtractor: NotificationPersonExtractor
+) : PeopleNotificationIdentifier {
+
+    override fun isPeopleNotification(sbn: StatusBarNotification) =
+            sbn.notification.notificationStyle == Notification.MessagingStyle::class.java ||
+                    personExtractor.extractPersonKey(sbn) != null
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/ViewPipeline.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/ViewPipeline.kt
index 33e3bb8..3ca3792 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/ViewPipeline.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/ViewPipeline.kt
@@ -28,10 +28,17 @@
 
 /** Boundary between a View and data pipeline, as seen by the View. */
 interface DataSource<out T> {
-    fun setListener(listener: DataListener<T>)
+    fun registerListener(listener: DataListener<T>): Subscription
+}
+
+/** Represents a registration with a [DataSource]. */
+interface Subscription {
+    /** Removes the previously registered [DataListener] from the [DataSource] */
+    fun unsubscribe()
 }
 
 /** Transform all data coming out of this [DataSource] using the given [mapper]. */
 fun <S, T> DataSource<S>.map(mapper: (S) -> T): DataSource<T> = object : DataSource<T> {
-    override fun setListener(listener: DataListener<T>) = setListener(listener.contraMap(mapper))
+    override fun registerListener(listener: DataListener<T>) =
+            registerListener(listener.contraMap(mapper))
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
index bd87d77..54d4066 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
@@ -131,6 +131,7 @@
         }
         mInitialized = true;
         reinflateViews(layoutInflater);
+        mPeopleHubViewAdapter.bindView(mPeopleHubViewBoundary);
         mConfigurationController.addCallback(mConfigurationListener);
     }
 
@@ -172,10 +173,6 @@
         if (oldPeopleHubPos != -1) {
             mParent.addView(mPeopleHubView, oldPeopleHubPos);
         }
-
-        if (!mInitialized) {
-            mPeopleHubViewAdapter.bindView(mPeopleHubViewBoundary);
-        }
     }
 
     /** Listener for when the "clear all" buttton is clciked on the gentle notification header. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index b9516cf..bd3d848 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -53,10 +53,10 @@
  */
 public class BiometricUnlockController extends KeyguardUpdateMonitorCallback {
 
-    private static final String TAG = "BiometricUnlockController";
+    private static final String TAG = "BiometricUnlockCtrl";
     private static final boolean DEBUG_BIO_WAKELOCK = KeyguardConstants.DEBUG_BIOMETRIC_WAKELOCK;
     private static final long BIOMETRIC_WAKELOCK_TIMEOUT_MS = 15 * 1000;
-    private static final String BIOMETRIC_WAKE_LOCK_NAME = "wake-and-unlock wakelock";
+    private static final String BIOMETRIC_WAKE_LOCK_NAME = "wake-and-unlock:wakelock";
 
     @IntDef(prefix = { "MODE_" }, value = {
             MODE_NONE,
@@ -132,7 +132,7 @@
     private final KeyguardBypassController mKeyguardBypassController;
     private PowerManager.WakeLock mWakeLock;
     private final KeyguardUpdateMonitor mUpdateMonitor;
-    private final DozeParameters mDozeParameters;
+    private DozeParameters mDozeParameters;
     private final KeyguardStateController mKeyguardStateController;
     private final StatusBarWindowController mStatusBarWindowController;
     private final Context mContext;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 50d33a7..bc48235 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -24,7 +24,6 @@
 import android.provider.Settings;
 import android.util.MathUtils;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.MainResources;
 import com.android.systemui.doze.AlwaysOnDisplayPolicy;
@@ -188,12 +187,7 @@
             return;
         }
         mControlScreenOffAnimation = controlScreenOffAnimation;
-        getPowerManager().setDozeAfterScreenOff(!controlScreenOffAnimation);
-    }
-
-    @VisibleForTesting
-    protected PowerManager getPowerManager() {
-        return mPowerManager;
+        mPowerManager.setDozeAfterScreenOff(!controlScreenOffAnimation);
     }
 
     private boolean getBoolean(String propName, int resId) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
index fe3c04e..1ecc489 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -29,10 +29,12 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 
 import javax.inject.Inject;
+import javax.inject.Singleton;
 
 /**
  * Controller which handles all the doze animations of the scrims.
  */
+@Singleton
 public class DozeScrimController implements StateListener {
     private static final String TAG = "DozeScrimController";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
new file mode 100644
index 0000000..2854355
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -0,0 +1,465 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
+import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
+
+import android.annotation.NonNull;
+import android.os.Bundle;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.doze.DozeEvent;
+import com.android.systemui.doze.DozeHost;
+import com.android.systemui.doze.DozeLog;
+import com.android.systemui.doze.DozeReceiver;
+import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.statusbar.PulseExpansionHandler;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+
+import java.util.ArrayList;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import dagger.Lazy;
+
+/**
+ * Implementation of DozeHost for SystemUI.
+ */
+@Singleton
+public final class DozeServiceHost implements DozeHost {
+    private static final String TAG = "DozeServiceHost";
+    private final ArrayList<Callback> mCallbacks = new ArrayList<>();
+    private final DozeLog mDozeLog;
+    private final PowerManager mPowerManager;
+    private boolean mAnimateWakeup;
+    private boolean mAnimateScreenOff;
+    private boolean mIgnoreTouchWhilePulsing;
+    private Runnable mPendingScreenOffCallback;
+    @VisibleForTesting
+    boolean mWakeLockScreenPerformsAuth = SystemProperties.getBoolean(
+            "persist.sysui.wake_performs_auth", true);
+    private boolean mDozingRequested;
+    private boolean mDozing;
+    private boolean mPulsing;
+    private WakefulnessLifecycle mWakefulnessLifecycle;
+    private final SysuiStatusBarStateController mStatusBarStateController;
+    private final DeviceProvisionedController mDeviceProvisionedController;
+    private final HeadsUpManagerPhone mHeadsUpManagerPhone;
+    private final BatteryController mBatteryController;
+    private final ScrimController mScrimController;
+    private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
+    private BiometricUnlockController mBiometricUnlockController;
+    private final KeyguardViewMediator mKeyguardViewMediator;
+    private final AssistManager mAssistManager;
+    private final DozeScrimController mDozeScrimController;
+    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private final VisualStabilityManager mVisualStabilityManager;
+    private final PulseExpansionHandler mPulseExpansionHandler;
+    private final StatusBarWindowController mStatusBarWindowController;
+    private final NotificationWakeUpCoordinator mNotificationWakeUpCoordinator;
+    private NotificationIconAreaController mNotificationIconAreaController;
+    private StatusBarWindowViewController mStatusBarWindowViewController;
+    private StatusBarWindowView mStatusBarWindow;
+    private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    private NotificationPanelView mNotificationPanel;
+    private View mAmbientIndicationContainer;
+    private StatusBar mStatusBar;
+
+    @Inject
+    public DozeServiceHost(DozeLog dozeLog, PowerManager powerManager,
+            WakefulnessLifecycle wakefulnessLifecycle,
+            SysuiStatusBarStateController statusBarStateController,
+            DeviceProvisionedController deviceProvisionedController,
+            HeadsUpManagerPhone headsUpManagerPhone, BatteryController batteryController,
+            ScrimController scrimController,
+            Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
+            KeyguardViewMediator keyguardViewMediator,
+            AssistManager assistManager,
+            DozeScrimController dozeScrimController, KeyguardUpdateMonitor keyguardUpdateMonitor,
+            VisualStabilityManager visualStabilityManager,
+            PulseExpansionHandler pulseExpansionHandler,
+            StatusBarWindowController statusBarWindowController,
+            NotificationWakeUpCoordinator notificationWakeUpCoordinator) {
+        super();
+        mDozeLog = dozeLog;
+        mPowerManager = powerManager;
+        mWakefulnessLifecycle = wakefulnessLifecycle;
+        mStatusBarStateController = statusBarStateController;
+        mDeviceProvisionedController = deviceProvisionedController;
+        mHeadsUpManagerPhone = headsUpManagerPhone;
+        mBatteryController = batteryController;
+        mScrimController = scrimController;
+        mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
+        mKeyguardViewMediator = keyguardViewMediator;
+        mAssistManager = assistManager;
+        mDozeScrimController = dozeScrimController;
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mVisualStabilityManager = visualStabilityManager;
+        mPulseExpansionHandler = pulseExpansionHandler;
+        mStatusBarWindowController = statusBarWindowController;
+        mNotificationWakeUpCoordinator = notificationWakeUpCoordinator;
+    }
+
+    // TODO: we should try to not pass status bar in here if we can avoid it.
+
+    /**
+     * Initialize instance with objects only available later during execution.
+     */
+    public void initialize(StatusBar statusBar,
+            NotificationIconAreaController notificationIconAreaController,
+            StatusBarWindowViewController statusBarWindowViewController,
+            StatusBarWindowView statusBarWindow,
+            StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+            NotificationPanelView notificationPanel, View ambientIndicationContainer) {
+        mStatusBar = statusBar;
+        mNotificationIconAreaController = notificationIconAreaController;
+        mStatusBarWindowViewController = statusBarWindowViewController;
+        mStatusBarWindow = statusBarWindow;
+        mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+        mNotificationPanel = notificationPanel;
+        mAmbientIndicationContainer = ambientIndicationContainer;
+        mBiometricUnlockController = mBiometricUnlockControllerLazy.get();
+    }
+
+    @Override
+    public String toString() {
+        return "PSB.DozeServiceHost[mCallbacks=" + mCallbacks.size() + "]";
+    }
+
+    void firePowerSaveChanged(boolean active) {
+        for (Callback callback : mCallbacks) {
+            callback.onPowerSaveChanged(active);
+        }
+    }
+
+    void fireNotificationPulse(NotificationEntry entry) {
+        Runnable pulseSuppressedListener = () -> {
+            entry.setPulseSuppressed(true);
+            mNotificationIconAreaController.updateAodNotificationIcons();
+        };
+        for (Callback callback : mCallbacks) {
+            callback.onNotificationAlerted(pulseSuppressedListener);
+        }
+    }
+
+    boolean getDozingRequested() {
+        return mDozingRequested;
+    }
+
+    boolean isPulsing() {
+        return mPulsing;
+    }
+
+
+    @Override
+    public void addCallback(@NonNull Callback callback) {
+        mCallbacks.add(callback);
+    }
+
+    @Override
+    public void removeCallback(@NonNull Callback callback) {
+        mCallbacks.remove(callback);
+    }
+
+    @Override
+    public void startDozing() {
+        if (!mDozingRequested) {
+            mDozingRequested = true;
+            mDozeLog.traceDozing(mDozing);
+            updateDozing();
+            mStatusBar.updateIsKeyguard();
+        }
+    }
+
+    void updateDozing() {
+        // When in wake-and-unlock while pulsing, keep dozing state until fully unlocked.
+        boolean
+                dozing =
+                mDozingRequested && mStatusBarStateController.getState() == StatusBarState.KEYGUARD
+                        || mBiometricUnlockController.getMode()
+                        == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
+        // When in wake-and-unlock we may not have received a change to StatusBarState
+        // but we still should not be dozing, manually set to false.
+        if (mBiometricUnlockController.getMode()
+                == BiometricUnlockController.MODE_WAKE_AND_UNLOCK) {
+            dozing = false;
+        }
+
+
+        mStatusBarStateController.setIsDozing(dozing);
+    }
+
+    @Override
+    public void pulseWhileDozing(@NonNull PulseCallback callback, int reason) {
+        if (reason == DozeEvent.PULSE_REASON_SENSOR_LONG_PRESS) {
+            mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
+                                 "com.android.systemui:LONG_PRESS");
+            mAssistManager.startAssist(new Bundle());
+            return;
+        }
+
+        if (reason == DozeEvent.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) {
+            mScrimController.setWakeLockScreenSensorActive(true);
+        }
+
+        if (reason == DozeEvent.PULSE_REASON_DOCKING && mStatusBarWindow != null) {
+            mStatusBarWindowViewController.suppressWakeUpGesture(true);
+        }
+
+        boolean passiveAuthInterrupt = reason == DozeEvent.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN
+                        && mWakeLockScreenPerformsAuth;
+        // Set the state to pulsing, so ScrimController will know what to do once we ask it to
+        // execute the transition. The pulse callback will then be invoked when the scrims
+        // are black, indicating that StatusBar is ready to present the rest of the UI.
+        mPulsing = true;
+        mDozeScrimController.pulse(new PulseCallback() {
+            @Override
+            public void onPulseStarted() {
+                callback.onPulseStarted();
+                mStatusBar.updateNotificationPanelTouchState();
+                setPulsing(true);
+            }
+
+            @Override
+            public void onPulseFinished() {
+                mPulsing = false;
+                callback.onPulseFinished();
+                mStatusBar.updateNotificationPanelTouchState();
+                mScrimController.setWakeLockScreenSensorActive(false);
+                if (mStatusBarWindow != null) {
+                    mStatusBarWindowViewController.suppressWakeUpGesture(false);
+                }
+                setPulsing(false);
+            }
+
+            private void setPulsing(boolean pulsing) {
+                mStatusBarStateController.setPulsing(pulsing);
+                mStatusBarKeyguardViewManager.setPulsing(pulsing);
+                mKeyguardViewMediator.setPulsing(pulsing);
+                mNotificationPanel.setPulsing(pulsing);
+                mVisualStabilityManager.setPulsing(pulsing);
+                mStatusBarWindowViewController.setPulsing(pulsing);
+                mIgnoreTouchWhilePulsing = false;
+                if (mKeyguardUpdateMonitor != null && passiveAuthInterrupt) {
+                    mKeyguardUpdateMonitor.onAuthInterruptDetected(pulsing /* active */);
+                }
+                mStatusBar.updateScrimController();
+                mPulseExpansionHandler.setPulsing(pulsing);
+                mNotificationWakeUpCoordinator.setPulsing(pulsing);
+            }
+        }, reason);
+        // DozeScrimController is in pulse state, now let's ask ScrimController to start
+        // pulsing and draw the black frame, if necessary.
+        mStatusBar.updateScrimController();
+    }
+
+    @Override
+    public void stopDozing() {
+        if (mDozingRequested) {
+            mDozingRequested = false;
+            mDozeLog.traceDozing(mDozing);
+            updateDozing();
+        }
+    }
+
+    @Override
+    public void onIgnoreTouchWhilePulsing(boolean ignore) {
+        if (ignore != mIgnoreTouchWhilePulsing) {
+            mDozeLog.tracePulseTouchDisabledByProx(ignore);
+        }
+        mIgnoreTouchWhilePulsing = ignore;
+        if (mDozing && ignore) {
+            mStatusBarWindowViewController.cancelCurrentTouch();
+        }
+    }
+
+    @Override
+    public void dozeTimeTick() {
+        mNotificationPanel.dozeTimeTick();
+        if (mAmbientIndicationContainer instanceof DozeReceiver) {
+            ((DozeReceiver) mAmbientIndicationContainer).dozeTimeTick();
+        }
+    }
+
+    @Override
+    public boolean isPowerSaveActive() {
+        return mBatteryController.isAodPowerSave();
+    }
+
+    @Override
+    public boolean isPulsingBlocked() {
+        return mBiometricUnlockController.getMode()
+                == BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
+    }
+
+    @Override
+    public boolean isProvisioned() {
+        return mDeviceProvisionedController.isDeviceProvisioned()
+                && mDeviceProvisionedController.isCurrentUserSetup();
+    }
+
+    @Override
+    public boolean isBlockingDoze() {
+        if (mBiometricUnlockController.hasPendingAuthentication()) {
+            Log.i(StatusBar.TAG, "Blocking AOD because fingerprint has authenticated");
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void extendPulse(int reason) {
+        if (reason == DozeEvent.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) {
+            mScrimController.setWakeLockScreenSensorActive(true);
+        }
+        if (mDozeScrimController.isPulsing() && mHeadsUpManagerPhone.hasNotifications()) {
+            mHeadsUpManagerPhone.extendHeadsUp();
+        } else {
+            mDozeScrimController.extendPulse();
+        }
+    }
+
+    @Override
+    public void stopPulsing() {
+        if (mDozeScrimController.isPulsing()) {
+            mDozeScrimController.pulseOutNow();
+        }
+    }
+
+    @Override
+    public void setAnimateWakeup(boolean animateWakeup) {
+        if (mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_AWAKE
+                || mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_WAKING) {
+            // Too late to change the wakeup animation.
+            return;
+        }
+        mAnimateWakeup = animateWakeup;
+    }
+
+    @Override
+    public void setAnimateScreenOff(boolean animateScreenOff) {
+        mAnimateScreenOff = animateScreenOff;
+    }
+
+    @Override
+    public void onSlpiTap(float screenX, float screenY) {
+        if (screenX > 0 && screenY > 0 && mAmbientIndicationContainer != null
+                && mAmbientIndicationContainer.getVisibility() == View.VISIBLE) {
+            int[] locationOnScreen = new int[2];
+            mAmbientIndicationContainer.getLocationOnScreen(locationOnScreen);
+            float viewX = screenX - locationOnScreen[0];
+            float viewY = screenY - locationOnScreen[1];
+            if (0 <= viewX && viewX <= mAmbientIndicationContainer.getWidth()
+                    && 0 <= viewY && viewY <= mAmbientIndicationContainer.getHeight()) {
+
+                // Dispatch a tap
+                long now = SystemClock.elapsedRealtime();
+                MotionEvent ev = MotionEvent.obtain(
+                        now, now, MotionEvent.ACTION_DOWN, screenX, screenY, 0);
+                mAmbientIndicationContainer.dispatchTouchEvent(ev);
+                ev.recycle();
+                ev = MotionEvent.obtain(
+                        now, now, MotionEvent.ACTION_UP, screenX, screenY, 0);
+                mAmbientIndicationContainer.dispatchTouchEvent(ev);
+                ev.recycle();
+            }
+        }
+    }
+
+    @Override
+    public void setDozeScreenBrightness(int value) {
+        mStatusBarWindowController.setDozeScreenBrightness(value);
+    }
+
+    @Override
+    public void setAodDimmingScrim(float scrimOpacity) {
+        mScrimController.setAodFrontScrimAlpha(scrimOpacity);
+    }
+
+
+
+    @Override
+    public void prepareForGentleSleep(Runnable onDisplayOffCallback) {
+        if (mPendingScreenOffCallback != null) {
+            Log.w(TAG, "Overlapping onDisplayOffCallback. Ignoring previous one.");
+        }
+        mPendingScreenOffCallback = onDisplayOffCallback;
+        mStatusBar.updateScrimController();
+    }
+
+    @Override
+    public void cancelGentleSleep() {
+        mPendingScreenOffCallback = null;
+        if (mScrimController.getState() == ScrimState.OFF) {
+            mStatusBar.updateScrimController();
+        }
+    }
+
+    /**
+     * When the dozing host is waiting for scrims to fade out to change the display state.
+     */
+    boolean hasPendingScreenOffCallback() {
+        return mPendingScreenOffCallback != null;
+    }
+
+    /**
+     * Executes an nullifies the pending display state callback.
+     *
+     * @see #hasPendingScreenOffCallback()
+     * @see #prepareForGentleSleep(Runnable)
+     */
+    void executePendingScreenOffCallback() {
+        if (mPendingScreenOffCallback == null) {
+            return;
+        }
+        mPendingScreenOffCallback.run();
+        mPendingScreenOffCallback = null;
+    }
+
+    boolean shouldAnimateWakeup() {
+        return mAnimateWakeup;
+    }
+
+    boolean shouldAnimateScreenOff() {
+        return mAnimateScreenOff;
+    }
+
+    public void setDozing(boolean dozing) {
+        mDozing = dozing;
+    }
+
+    boolean getIgnoreTouchWhilePulsing() {
+        return mIgnoreTouchWhilePulsing;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 6064fbe..35039a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -60,11 +60,13 @@
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
+import javax.inject.Singleton;
 
 /**
  * Controls both the scrim behind the notifications and in front of the notifications (when a
  * security method gets shown).
  */
+@Singleton
 public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnColorsChangedListener,
         Dumpable {
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 4e0af64..b8adfea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -50,7 +50,6 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
@@ -157,10 +156,8 @@
 import com.android.systemui.charging.WirelessChargingAnimation;
 import com.android.systemui.classifier.FalsingLog;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.doze.DozeEvent;
 import com.android.systemui.doze.DozeHost;
 import com.android.systemui.doze.DozeLog;
-import com.android.systemui.doze.DozeReceiver;
 import com.android.systemui.fragments.ExtensionFragmentListener;
 import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.keyguard.KeyguardSliceProvider;
@@ -347,7 +344,7 @@
     /**
      * The {@link StatusBarState} of the status bar.
      */
-    protected int mState;
+    protected int mState; // TODO: remove this. Just use StatusBarStateController
     protected boolean mBouncerShowing;
 
     private PhoneStatusBarPolicy mIconPolicy;
@@ -373,7 +370,7 @@
     protected StatusBarWindowController mStatusBarWindowController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @VisibleForTesting
-    DozeServiceHost mDozeServiceHost = new DozeServiceHost();
+    DozeServiceHost mDozeServiceHost;
     private boolean mWakeUpComingFromTouch;
     private PointF mWakeUpTouchLocation;
 
@@ -493,7 +490,6 @@
     private final UiOffloadThread mUiOffloadThread;
 
     protected boolean mDozing;
-    private boolean mDozingRequested;
 
     private final NotificationMediaManager mMediaManager;
     private final NotificationLockscreenUserManager mLockscreenUserManager;
@@ -612,7 +608,6 @@
     private ActivityLaunchAnimator mActivityLaunchAnimator;
     protected StatusBarNotificationPresenter mPresenter;
     private NotificationActivityStarter mNotificationActivityStarter;
-    private boolean mPulsing;
     private final BubbleController mBubbleController;
     private final BubbleController.BubbleExpandListener mBubbleExpandListener;
 
@@ -693,7 +688,10 @@
             DozeParameters dozeParameters,
             ScrimController scrimController,
             Lazy<LockscreenWallpaper> lockscreenWallpaperLazy,
-            Lazy<BiometricUnlockController> biometricUnlockControllerLazy) {
+            Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
+            DozeServiceHost dozeServiceHost,
+            PowerManager powerManager,
+            DozeScrimController dozeScrimController) {
         super(context);
         mFeatureFlags = featureFlags;
         mLightBarController = lightBarController;
@@ -750,9 +748,12 @@
         mStatusBarWindowController = statusBarWindowController;
         mStatusBarWindowViewControllerBuilder = statusBarWindowViewControllerBuilder;
         mNotifLog = notifLog;
+        mDozeServiceHost = dozeServiceHost;
+        mPowerManager = powerManager;
         mDozeParameters = dozeParameters;
         mScrimController = scrimController;
         mLockscreenWallpaperLazy = lockscreenWallpaperLazy;
+        mDozeScrimController = dozeScrimController;
         mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
 
         mBubbleExpandListener =
@@ -805,7 +806,6 @@
         mAccessibilityManager = (AccessibilityManager)
                 mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
 
-        mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
         mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController);
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
@@ -905,6 +905,9 @@
         startKeyguard();
 
         mKeyguardUpdateMonitor.registerCallback(mUpdateCallback);
+        mDozeServiceHost.initialize(this, mNotificationIconAreaController,
+                mStatusBarWindowViewController, mStatusBarWindow, mStatusBarKeyguardViewManager,
+                mNotificationPanel, mAmbientIndicationContainer);
         putComponent(DozeHost.class, mDozeServiceHost);
 
         mScreenPinningRequest = new ScreenPinningRequest(mContext);
@@ -1069,7 +1072,6 @@
 
         mNotificationPanel.initDependencies(this, mGroupManager, mNotificationShelf,
                 mHeadsUpManager, mNotificationIconAreaController, mScrimController);
-        mDozeScrimController = new DozeScrimController(mDozeParameters, mDozeLog);
 
         BackDropView backdrop = mStatusBarWindow.findViewById(R.id.backdrop);
         mMediaManager.setup(backdrop, backdrop.findViewById(R.id.backdrop_front),
@@ -1730,7 +1732,7 @@
         if (isDozing() && isHeadsUp) {
             entry.setPulseSuppressed(false);
             mDozeServiceHost.fireNotificationPulse(entry);
-            if (mPulsing) {
+            if (mDozeServiceHost.isPulsing()) {
                 mDozeScrimController.cancelPendingPulseTimeout();
             }
         }
@@ -1762,7 +1764,7 @@
     }
 
     public boolean isPulsing() {
-        return mPulsing;
+        return mDozeServiceHost.isPulsing();
     }
 
     public boolean hideStatusBarIconsWhenExpanded() {
@@ -2825,7 +2827,7 @@
         if (mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_ASLEEP
                 && mKeyguardStateController.canDismissLockScreen()
                 && !mStatusBarStateController.leaveOpenOnKeyguardHide()
-                && isPulsing()) {
+                && mDozeServiceHost.isPulsing()) {
             // Reuse the biometric wake-and-unlock transition if we dismiss keyguard from a pulse.
             // TODO: Factor this transition out of BiometricUnlockController.
             mBiometricUnlockController.startWakeAndUnlock(
@@ -3156,7 +3158,7 @@
         return mState == StatusBarState.FULLSCREEN_USER_SWITCHER;
     }
 
-    private boolean updateIsKeyguard() {
+    boolean updateIsKeyguard() {
         boolean wakeAndUnlocking = mBiometricUnlockController.getMode()
                 == BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
 
@@ -3164,8 +3166,8 @@
         // there's no surface we can show to the user. Note that the device goes fully interactive
         // late in the transition, so we also allow the device to start dozing once the screen has
         // turned off fully.
-        boolean keyguardForDozing = mDozingRequested &&
-                (!mDeviceInteractive || isGoingToSleep() && (isScreenFullyOff() || mIsKeyguard));
+        boolean keyguardForDozing = mDozeServiceHost.getDozingRequested()
+                && (!mDeviceInteractive || isGoingToSleep() && (isScreenFullyOff() || mIsKeyguard));
         boolean shouldBeKeyguard = (mStatusBarStateController.isKeyguardRequested()
                 || keyguardForDozing) && !wakeAndUnlocking;
         if (keyguardForDozing) {
@@ -3581,7 +3583,7 @@
     public void onStateChanged(int newState) {
         mState = newState;
         updateReportRejectedTouchVisibility();
-        updateDozing();
+        mDozeServiceHost.updateDozing();
         updateTheme();
         mNavigationBarController.touchAutoDim(mDisplayId);
         Trace.beginSection("StatusBar#updateKeyguardState");
@@ -3619,36 +3621,23 @@
     public void onDozingChanged(boolean isDozing) {
         Trace.beginSection("StatusBar#updateDozing");
         mDozing = isDozing;
+        mDozeServiceHost.setDozing(mDozing);
 
         // Collapse the notification panel if open
-        boolean dozingAnimated = mDozingRequested && mDozeParameters.shouldControlScreenOff();
+        boolean dozingAnimated = mDozeServiceHost.getDozingRequested()
+                && mDozeParameters.shouldControlScreenOff();
         mNotificationPanel.resetViews(dozingAnimated);
 
         updateQsExpansionEnabled();
         mKeyguardViewMediator.setDozing(mDozing);
 
         mEntryManager.updateNotifications("onDozingChanged");
-        updateDozingState();
+        mDozeServiceHost.updateDozing();
         updateScrimController();
         updateReportRejectedTouchVisibility();
         Trace.endSection();
     }
 
-    private void updateDozing() {
-        // When in wake-and-unlock while pulsing, keep dozing state until fully unlocked.
-        boolean dozing = mDozingRequested && mState == StatusBarState.KEYGUARD
-                || mBiometricUnlockController.getMode()
-                == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
-        // When in wake-and-unlock we may not have received a change to mState
-        // but we still should not be dozing, manually set to false.
-        if (mBiometricUnlockController.getMode() ==
-                BiometricUnlockController.MODE_WAKE_AND_UNLOCK) {
-            dozing = false;
-        }
-
-        mStatusBarStateController.setIsDozing(dozing);
-    }
-
     private void updateKeyguardState() {
         mKeyguardStateController.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(),
                 mStatusBarKeyguardViewManager.isOccluded());
@@ -3873,10 +3862,11 @@
      * collapse the panel after we expanded it, and thus we would end up with a blank
      * Keyguard.
      */
-    private void updateNotificationPanelTouchState() {
+    void updateNotificationPanelTouchState() {
         boolean goingToSleepWithoutAnimation = isGoingToSleep()
                 && !mDozeParameters.shouldControlScreenOff();
-        boolean disabled = (!mDeviceInteractive && !mPulsing) || goingToSleepWithoutAnimation;
+        boolean disabled = (!mDeviceInteractive && !mDozeServiceHost.isPulsing())
+                || goingToSleepWithoutAnimation;
         mNotificationPanel.setTouchAndAnimationDisabled(disabled);
         mNotificationIconAreaController.setAnimationsEnabled(!disabled);
     }
@@ -4026,7 +4016,7 @@
     }
 
     public void notifyBiometricAuthModeChanged() {
-        updateDozing();
+        mDozeServiceHost.updateDozing();
         updateScrimController();
         mStatusBarWindowViewController.onBiometricAuthModeChanged(
                 mBiometricUnlockController.isWakeAndUnlock(),
@@ -4062,7 +4052,7 @@
             mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback);
         } else if (mBrightnessMirrorVisible) {
             mScrimController.transitionTo(ScrimState.BRIGHTNESS_MIRROR);
-        } else if (isPulsing()) {
+        } else if (mDozeServiceHost.isPulsing()) {
             mScrimController.transitionTo(ScrimState.PULSING,
                     mDozeScrimController.getScrimCallback());
         } else if (mDozeServiceHost.hasPendingScreenOffCallback()) {
@@ -4092,295 +4082,8 @@
         return mStatusBarKeyguardViewManager.isShowing();
     }
 
-    @VisibleForTesting
-    final class DozeServiceHost implements DozeHost {
-        private final ArrayList<Callback> mCallbacks = new ArrayList<>();
-        private boolean mAnimateWakeup;
-        private boolean mAnimateScreenOff;
-        private boolean mIgnoreTouchWhilePulsing;
-        private Runnable mPendingScreenOffCallback;
-        @VisibleForTesting
-        boolean mWakeLockScreenPerformsAuth = SystemProperties.getBoolean(
-                "persist.sysui.wake_performs_auth", true);
-
-        @Override
-        public String toString() {
-            return "PSB.DozeServiceHost[mCallbacks=" + mCallbacks.size() + "]";
-        }
-
-        public void firePowerSaveChanged(boolean active) {
-            for (Callback callback : mCallbacks) {
-                callback.onPowerSaveChanged(active);
-            }
-        }
-
-        public void fireNotificationPulse(NotificationEntry entry) {
-            Runnable pulseSupressedListener = () -> {
-                entry.setPulseSuppressed(true);
-                mNotificationIconAreaController.updateAodNotificationIcons();
-            };
-            for (Callback callback : mCallbacks) {
-                callback.onNotificationAlerted(pulseSupressedListener);
-            }
-        }
-
-        @Override
-        public void addCallback(@NonNull Callback callback) {
-            mCallbacks.add(callback);
-        }
-
-        @Override
-        public void removeCallback(@NonNull Callback callback) {
-            mCallbacks.remove(callback);
-        }
-
-        @Override
-        public void startDozing() {
-            if (!mDozingRequested) {
-                mDozingRequested = true;
-                mDozeLog.traceDozing(mDozing);
-                updateDozing();
-                updateIsKeyguard();
-            }
-        }
-
-        @Override
-        public void pulseWhileDozing(@NonNull PulseCallback callback, int reason) {
-            if (reason == DozeEvent.PULSE_REASON_SENSOR_LONG_PRESS) {
-                mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
-                        "com.android.systemui:LONG_PRESS");
-                startAssist(new Bundle());
-                return;
-            }
-
-            if (reason == DozeEvent.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) {
-                mScrimController.setWakeLockScreenSensorActive(true);
-            }
-
-            if (reason == DozeEvent.PULSE_REASON_DOCKING && mStatusBarWindow != null) {
-                mStatusBarWindowViewController.suppressWakeUpGesture(true);
-            }
-
-            boolean passiveAuthInterrupt = reason == DozeEvent.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN
-                            && mWakeLockScreenPerformsAuth;
-            // Set the state to pulsing, so ScrimController will know what to do once we ask it to
-            // execute the transition. The pulse callback will then be invoked when the scrims
-            // are black, indicating that StatusBar is ready to present the rest of the UI.
-            mPulsing = true;
-            mDozeScrimController.pulse(new PulseCallback() {
-                @Override
-                public void onPulseStarted() {
-                    callback.onPulseStarted();
-                    updateNotificationPanelTouchState();
-                    setPulsing(true);
-                }
-
-                @Override
-                public void onPulseFinished() {
-                    mPulsing = false;
-                    callback.onPulseFinished();
-                    updateNotificationPanelTouchState();
-                    mScrimController.setWakeLockScreenSensorActive(false);
-                    if (mStatusBarWindow != null) {
-                        mStatusBarWindowViewController.suppressWakeUpGesture(false);
-                    }
-                    setPulsing(false);
-                }
-
-                private void setPulsing(boolean pulsing) {
-                    mStatusBarStateController.setPulsing(pulsing);
-                    mStatusBarKeyguardViewManager.setPulsing(pulsing);
-                    mKeyguardViewMediator.setPulsing(pulsing);
-                    mNotificationPanel.setPulsing(pulsing);
-                    mVisualStabilityManager.setPulsing(pulsing);
-                    mStatusBarWindowViewController.setPulsing(pulsing);
-                    mIgnoreTouchWhilePulsing = false;
-                    if (mKeyguardUpdateMonitor != null && passiveAuthInterrupt) {
-                        mKeyguardUpdateMonitor.onAuthInterruptDetected(pulsing /* active */);
-                    }
-                    updateScrimController();
-                    mPulseExpansionHandler.setPulsing(pulsing);
-                    mWakeUpCoordinator.setPulsing(pulsing);
-                }
-            }, reason);
-            // DozeScrimController is in pulse state, now let's ask ScrimController to start
-            // pulsing and draw the black frame, if necessary.
-            updateScrimController();
-        }
-
-        @Override
-        public void stopDozing() {
-            if (mDozingRequested) {
-                mDozingRequested = false;
-                mDozeLog.traceDozing(mDozing);
-                updateDozing();
-            }
-        }
-
-        @Override
-        public void onIgnoreTouchWhilePulsing(boolean ignore) {
-            if (ignore != mIgnoreTouchWhilePulsing) {
-                mDozeLog.tracePulseTouchDisabledByProx(ignore);
-            }
-            mIgnoreTouchWhilePulsing = ignore;
-            if (isDozing() && ignore) {
-                mStatusBarWindowViewController.cancelCurrentTouch();
-            }
-        }
-
-        @Override
-        public void dozeTimeTick() {
-            mNotificationPanel.dozeTimeTick();
-            if (mAmbientIndicationContainer instanceof DozeReceiver) {
-                ((DozeReceiver) mAmbientIndicationContainer).dozeTimeTick();
-            }
-        }
-
-        @Override
-        public boolean isPowerSaveActive() {
-            return mBatteryController.isAodPowerSave();
-        }
-
-        @Override
-        public boolean isPulsingBlocked() {
-            return mBiometricUnlockController.getMode()
-                    == BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
-        }
-
-        @Override
-        public boolean isProvisioned() {
-            return mDeviceProvisionedController.isDeviceProvisioned()
-                    && mDeviceProvisionedController.isCurrentUserSetup();
-        }
-
-        @Override
-        public boolean isBlockingDoze() {
-            if (mBiometricUnlockController.hasPendingAuthentication()) {
-                Log.i(TAG, "Blocking AOD because fingerprint has authenticated");
-                return true;
-            }
-            return false;
-        }
-
-        @Override
-        public void extendPulse(int reason) {
-            if (reason == DozeEvent.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) {
-                mScrimController.setWakeLockScreenSensorActive(true);
-            }
-            if (mDozeScrimController.isPulsing() && mHeadsUpManager.hasNotifications()) {
-                mHeadsUpManager.extendHeadsUp();
-            } else {
-                mDozeScrimController.extendPulse();
-            }
-        }
-
-        @Override
-        public void stopPulsing() {
-            if (mDozeScrimController.isPulsing()) {
-                mDozeScrimController.pulseOutNow();
-            }
-        }
-
-        @Override
-        public void setAnimateWakeup(boolean animateWakeup) {
-            if (mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_AWAKE
-                    || mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_WAKING) {
-                // Too late to change the wakeup animation.
-                return;
-            }
-            mAnimateWakeup = animateWakeup;
-        }
-
-        @Override
-        public void setAnimateScreenOff(boolean animateScreenOff) {
-            mAnimateScreenOff = animateScreenOff;
-        }
-
-        @Override
-        public void onSlpiTap(float screenX, float screenY) {
-            if (screenX > 0 && screenY > 0 && mAmbientIndicationContainer != null
-                && mAmbientIndicationContainer.getVisibility() == View.VISIBLE) {
-                mAmbientIndicationContainer.getLocationOnScreen(mTmpInt2);
-                float viewX = screenX - mTmpInt2[0];
-                float viewY = screenY - mTmpInt2[1];
-                if (0 <= viewX && viewX <= mAmbientIndicationContainer.getWidth()
-                        && 0 <= viewY && viewY <= mAmbientIndicationContainer.getHeight()) {
-                    dispatchTap(mAmbientIndicationContainer, viewX, viewY);
-                }
-            }
-        }
-
-        @Override
-        public void setDozeScreenBrightness(int value) {
-            mStatusBarWindowController.setDozeScreenBrightness(value);
-        }
-
-        @Override
-        public void setAodDimmingScrim(float scrimOpacity) {
-            mScrimController.setAodFrontScrimAlpha(scrimOpacity);
-        }
-
-        @Override
-        public void prepareForGentleSleep(Runnable onDisplayOffCallback) {
-            if (mPendingScreenOffCallback != null) {
-                Log.w(TAG, "Overlapping onDisplayOffCallback. Ignoring previous one.");
-            }
-            mPendingScreenOffCallback = onDisplayOffCallback;
-            updateScrimController();
-        }
-
-        @Override
-        public void cancelGentleSleep() {
-            mPendingScreenOffCallback = null;
-            if (mScrimController.getState() == ScrimState.OFF) {
-                updateScrimController();
-            }
-        }
-
-        /**
-         * When the dozing host is waiting for scrims to fade out to change the display state.
-         */
-        boolean hasPendingScreenOffCallback() {
-            return mPendingScreenOffCallback != null;
-        }
-
-        /**
-         * Executes an nullifies the pending display state callback.
-         *
-         * @see #hasPendingScreenOffCallback()
-         * @see #prepareForGentleSleep(Runnable)
-         */
-        void executePendingScreenOffCallback() {
-            if (mPendingScreenOffCallback == null) {
-                return;
-            }
-            mPendingScreenOffCallback.run();
-            mPendingScreenOffCallback = null;
-        }
-
-        private void dispatchTap(View view, float x, float y) {
-            long now = SystemClock.elapsedRealtime();
-            dispatchTouchEvent(view, x, y, now, MotionEvent.ACTION_DOWN);
-            dispatchTouchEvent(view, x, y, now, MotionEvent.ACTION_UP);
-        }
-
-        private void dispatchTouchEvent(View view, float x, float y, long now, int action) {
-            MotionEvent ev = MotionEvent.obtain(now, now, action, x, y, 0 /* meta */);
-            view.dispatchTouchEvent(ev);
-            ev.recycle();
-        }
-
-        private boolean shouldAnimateWakeup() {
-            return mAnimateWakeup;
-        }
-
-        public boolean shouldAnimateScreenOff() {
-            return mAnimateScreenOff;
-        }
-    }
-
     public boolean shouldIgnoreTouch() {
-        return isDozing() && mDozeServiceHost.mIgnoreTouchWhilePulsing;
+        return isDozing() && mDozeServiceHost.getIgnoreTouchWhilePulsing();
     }
 
     // Begin Extra BaseStatusBar methods.
@@ -4407,7 +4110,7 @@
     private boolean mVisibleToUser;
 
     protected DevicePolicyManager mDevicePolicyManager;
-    protected PowerManager mPowerManager;
+    private final PowerManager mPowerManager;
     protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
 
     protected KeyguardManager mKeyguardManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionController.java
index cade5dc..3ce6239 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionController.java
@@ -57,7 +57,7 @@
         ExtensionBuilder<T> withCallback(Consumer<T> callback);
         ExtensionBuilder<T> withUiMode(int mode, Supplier<T> def);
         ExtensionBuilder<T> withFeature(String feature, Supplier<T> def);
-        Extension build();
+        Extension<T> build();
     }
 
     public interface PluginConverter<T, P> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java
index fd030d1..eeef726 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java
@@ -135,7 +135,7 @@
         }
 
         @Override
-        public ExtensionController.Extension build() {
+        public ExtensionController.Extension<T> build() {
             // Sort items in ascending order
             Collections.sort(mExtension.mProducers, Comparator.comparingInt(Item::sortOrder));
             mExtension.notifyChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 7b4a7d2..502a9bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -679,8 +679,8 @@
                         }
                     };
 
-            InputConnection ic = InputConnectionCompat.createWrapper(
-                    inputConnection, outAttrs, callback);
+            InputConnection ic = inputConnection == null ? null :
+                    InputConnectionCompat.createWrapper(inputConnection, outAttrs, callback);
 
             Context userContext = null;
             try {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index ebdf851..bde7ef9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -82,6 +82,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationRowBinder;
 import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
 import com.android.systemui.statusbar.notification.logging.NotifLog;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationFlag;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -148,8 +149,12 @@
         private final CountDownLatch mCountDownLatch;
 
         TestableNotificationEntryManager() {
-            super(new NotificationData(mock(NotificationSectionsFeatureManager.class),
-                    mock(NotifLog.class)), mock(NotifLog.class));
+            super(
+                    new NotificationData(
+                            mock(NotificationSectionsFeatureManager.class),
+                            mock(NotifLog.class),
+                            mock(PeopleNotificationIdentifier.class)),
+                    mock(NotifLog.class));
             mCountDownLatch = new CountDownLatch(1);
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java
index 9202c51..2435bb9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java
@@ -46,6 +46,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationData;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.logging.NotifLog;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
@@ -78,8 +79,10 @@
 
     // TODO: Remove this once EntryManager no longer needs to be mocked
     private NotificationData mNotificationData =
-            new NotificationData(new NotificationSectionsFeatureManager(
-                    new DeviceConfigProxyFake(), mContext), mock(NotifLog.class));
+            new NotificationData(
+                    new NotificationSectionsFeatureManager(new DeviceConfigProxyFake(), mContext),
+                    mock(NotifLog.class),
+                    mock(PeopleNotificationIdentifier.class));
 
     private int mNextNotifId = 0;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java
index 640984b..dba0174 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java
@@ -81,6 +81,7 @@
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
 import com.android.systemui.statusbar.notification.collection.NotificationData.KeyguardEnvironment;
 import com.android.systemui.statusbar.notification.logging.NotifLog;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.ShadeController;
@@ -639,7 +640,10 @@
 
     public static class TestableNotificationData extends NotificationData {
         public TestableNotificationData(NotificationSectionsFeatureManager sectionsFeatureManager) {
-            super(sectionsFeatureManager, mock(NotifLog.class));
+            super(
+                    sectionsFeatureManager,
+                    mock(NotifLog.class),
+                    mock(PeopleNotificationIdentifier.class));
         }
 
         public static final String OVERRIDE_RANK = "r";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
new file mode 100644
index 0000000..b05172c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import static org.junit.Assert.assertFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.PowerManager;
+import android.testing.AndroidTestingRunner;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.doze.DozeEvent;
+import com.android.systemui.doze.DozeHost;
+import com.android.systemui.doze.DozeLog;
+import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.statusbar.PulseExpansionHandler;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.StatusBarStateControllerImpl;
+import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+
+import dagger.Lazy;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DozeServiceHostTest extends SysuiTestCase {
+
+    private DozeServiceHost mDozeServiceHost;
+
+    @Mock private HeadsUpManagerPhone mHeadsUpManager;
+    @Mock private ScrimController mScrimController;
+    @Mock private DozeScrimController mDozeScrimController;
+    @Mock private Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
+    @Mock private VisualStabilityManager mVisualStabilityManager;
+    @Mock private KeyguardViewMediator mKeyguardViewMediator;
+    @Mock private StatusBarStateControllerImpl mStatusBarStateController;
+    @Mock private BatteryController mBatteryController;
+    @Mock private DeviceProvisionedController mDeviceProvisionedController;
+    @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    @Mock private AssistManager mAssistManager;
+    @Mock private DozeLog mDozeLog;
+    @Mock private PulseExpansionHandler mPulseExpansionHandler;
+    @Mock private NotificationWakeUpCoordinator mNotificationWakeUpCoordinator;
+    @Mock private StatusBarWindowController mStatusBarWindowController;
+    @Mock private PowerManager mPowerManager;
+    @Mock private WakefulnessLifecycle mWakefullnessLifecycle;
+    @Mock private StatusBar mStatusBar;
+    @Mock private NotificationIconAreaController mNotificationIconAreaController;
+    @Mock private StatusBarWindowViewController mStatusBarWindowViewController;
+    @Mock private StatusBarWindowView mStatusBarWindow;
+    @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    @Mock private NotificationPanelView mNotificationPanel;
+    @Mock private View mAmbientIndicationContainer;
+    @Mock private BiometricUnlockController mBiometricUnlockController;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        when(mBiometricUnlockControllerLazy.get()).thenReturn(mBiometricUnlockController);
+        mDozeServiceHost = new DozeServiceHost(mDozeLog, mPowerManager, mWakefullnessLifecycle,
+                mStatusBarStateController, mDeviceProvisionedController, mHeadsUpManager,
+                mBatteryController, mScrimController, mBiometricUnlockControllerLazy,
+                mKeyguardViewMediator, mAssistManager, mDozeScrimController, mKeyguardUpdateMonitor,
+                mVisualStabilityManager, mPulseExpansionHandler, mStatusBarWindowController,
+                mNotificationWakeUpCoordinator);
+
+        mDozeServiceHost.initialize(mStatusBar, mNotificationIconAreaController,
+                mStatusBarWindowViewController, mStatusBarWindow, mStatusBarKeyguardViewManager,
+                mNotificationPanel, mAmbientIndicationContainer);
+    }
+
+    @Test
+    public void testStartStopDozing() {
+        when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
+        when(mStatusBarStateController.isKeyguardRequested()).thenReturn(true);
+
+        assertFalse(mDozeServiceHost.getDozingRequested());
+
+        mDozeServiceHost.startDozing();
+        verify(mStatusBarStateController).setIsDozing(eq(true));
+        verify(mStatusBar).updateIsKeyguard();
+
+        mDozeServiceHost.stopDozing();
+        verify(mStatusBarStateController).setIsDozing(eq(false));
+    }
+
+
+    @Test
+    public void testPulseWhileDozing_updatesScrimController() {
+        mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
+        mStatusBar.showKeyguardImpl();
+
+        // Keep track of callback to be able to stop the pulse
+//        DozeHost.PulseCallback[] pulseCallback = new DozeHost.PulseCallback[1];
+//        doAnswer(invocation -> {
+//            pulseCallback[0] = invocation.getArgument(0);
+//            return null;
+//        }).when(mDozeScrimController).pulse(any(), anyInt());
+
+        // Starting a pulse should change the scrim controller to the pulsing state
+        mDozeServiceHost.pulseWhileDozing(new DozeHost.PulseCallback() {
+            @Override
+            public void onPulseStarted() {
+            }
+
+            @Override
+            public void onPulseFinished() {
+            }
+        }, DozeEvent.PULSE_REASON_NOTIFICATION);
+
+        ArgumentCaptor<DozeHost.PulseCallback> pulseCallbackArgumentCaptor =
+                ArgumentCaptor.forClass(DozeHost.PulseCallback.class);
+
+        verify(mDozeScrimController).pulse(
+                pulseCallbackArgumentCaptor.capture(), eq(DozeEvent.PULSE_REASON_NOTIFICATION));
+        verify(mStatusBar).updateScrimController();
+        reset(mStatusBar);
+
+        pulseCallbackArgumentCaptor.getValue().onPulseFinished();
+        assertFalse(mDozeScrimController.isPulsing());
+        verify(mStatusBar).updateScrimController();
+    }
+
+
+    @Test
+    public void testPulseWhileDozingWithDockingReason_suppressWakeUpGesture() {
+        // Keep track of callback to be able to stop the pulse
+        final DozeHost.PulseCallback[] pulseCallback = new DozeHost.PulseCallback[1];
+        doAnswer(invocation -> {
+            pulseCallback[0] = invocation.getArgument(0);
+            return null;
+        }).when(mDozeScrimController).pulse(any(), anyInt());
+
+        // Starting a pulse while docking should suppress wakeup gesture
+        mDozeServiceHost.pulseWhileDozing(mock(DozeHost.PulseCallback.class),
+                DozeEvent.PULSE_REASON_DOCKING);
+        verify(mStatusBarWindowViewController).suppressWakeUpGesture(eq(true));
+
+        // Ending a pulse should restore wakeup gesture
+        pulseCallback[0].onPulseFinished();
+        verify(mStatusBarWindowViewController).suppressWakeUpGesture(eq(false));
+    }
+
+    @Test
+    public void testPulseWhileDozing_notifyAuthInterrupt() {
+        HashSet<Integer> reasonsWantingAuth = new HashSet<>(
+                Collections.singletonList(DozeEvent.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN));
+        HashSet<Integer> reasonsSkippingAuth = new HashSet<>(
+                Arrays.asList(DozeEvent.PULSE_REASON_INTENT,
+                        DozeEvent.PULSE_REASON_NOTIFICATION,
+                        DozeEvent.PULSE_REASON_SENSOR_SIGMOTION,
+                        DozeEvent.REASON_SENSOR_PICKUP,
+                        DozeEvent.REASON_SENSOR_DOUBLE_TAP,
+                        DozeEvent.PULSE_REASON_SENSOR_LONG_PRESS,
+                        DozeEvent.PULSE_REASON_DOCKING,
+                        DozeEvent.REASON_SENSOR_WAKE_UP,
+                        DozeEvent.REASON_SENSOR_TAP));
+        HashSet<Integer> reasonsThatDontPulse = new HashSet<>(
+                Arrays.asList(DozeEvent.REASON_SENSOR_PICKUP,
+                        DozeEvent.REASON_SENSOR_DOUBLE_TAP,
+                        DozeEvent.REASON_SENSOR_TAP));
+
+        doAnswer(invocation -> {
+            DozeHost.PulseCallback callback = invocation.getArgument(0);
+            callback.onPulseStarted();
+            return null;
+        }).when(mDozeScrimController).pulse(any(), anyInt());
+
+        mDozeServiceHost.mWakeLockScreenPerformsAuth = true;
+        for (int i = 0; i < DozeEvent.TOTAL_REASONS; i++) {
+            reset(mKeyguardUpdateMonitor);
+            mDozeServiceHost.pulseWhileDozing(mock(DozeHost.PulseCallback.class), i);
+            if (reasonsWantingAuth.contains(i)) {
+                verify(mKeyguardUpdateMonitor).onAuthInterruptDetected(eq(true));
+            } else if (reasonsSkippingAuth.contains(i) || reasonsThatDontPulse.contains(i)) {
+                verify(mKeyguardUpdateMonitor, never()).onAuthInterruptDetected(eq(true));
+            } else {
+                throw new AssertionError("Reason " + i + " isn't specified as wanting or skipping"
+                        + " passive auth. Please consider how this pulse reason should behave.");
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index cff6635..4853f20 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -57,6 +57,7 @@
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.collection.NotificationData;
 import com.android.systemui.statusbar.notification.logging.NotifLog;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -236,8 +237,11 @@
                     mock(PluginManager.class),
                     mock(ShadeController.class),
                     mock(NotificationLockscreenUserManager.class),
-                    new NotificationEntryManager(new NotificationData(mock(
-                            NotificationSectionsFeatureManager.class), mock(NotifLog.class)),
+                    new NotificationEntryManager(
+                            new NotificationData(
+                                    mock(NotificationSectionsFeatureManager.class),
+                                    mock(NotifLog.class),
+                                    mock(PeopleNotificationIdentifier.class)),
                             mock(NotifLog.class)),
                     mock(KeyguardStateController.class),
                     statusBarStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index f5e92e4..66c01ca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -85,8 +85,6 @@
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.doze.DozeEvent;
-import com.android.systemui.doze.DozeHost;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.ScreenLifecycle;
@@ -145,9 +143,6 @@
 import java.io.ByteArrayOutputStream;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
 
 import dagger.Lazy;
 
@@ -231,6 +226,7 @@
     @Mock private DozeParameters mDozeParameters;
     @Mock private Lazy<LockscreenWallpaper> mLockscreenWallpaperLazy;
     @Mock private LockscreenWallpaper mLockscreenWallpaper;
+    @Mock private DozeServiceHost mDozeServiceHost;
     @Mock private LinearLayout mLockIconContainer;
     @Mock private ViewMediatorCallback mKeyguardVieMediatorCallback;
 
@@ -365,7 +361,10 @@
                 mDozeParameters,
                 mScrimController,
                 mLockscreenWallpaperLazy,
-                mBiometricUnlockControllerLazy);
+                mBiometricUnlockControllerLazy,
+                mDozeServiceHost,
+                mPowerManager,
+                mDozeScrimController);
 
         when(mStatusBarWindowView.findViewById(R.id.lock_icon_container)).thenReturn(
                 mLockIconContainer);
@@ -388,7 +387,6 @@
         mStatusBar.mNotificationIconAreaController = mNotificationIconAreaController;
         mStatusBar.mPresenter = mNotificationPresenter;
         mStatusBar.mKeyguardIndicationController = mKeyguardIndicationController;
-        mStatusBar.mPowerManager = mPowerManager;
         mStatusBar.mBarService = mBarService;
         mStatusBar.mStackScroller = mStackScroller;
         mStatusBar.mStatusBarWindowViewController = mStatusBarWindowViewController;
@@ -757,83 +755,18 @@
         mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
         mStatusBar.showKeyguardImpl();
 
-        // Keep track of callback to be able to stop the pulse
-        DozeHost.PulseCallback[] pulseCallback = new DozeHost.PulseCallback[1];
-        doAnswer(invocation -> {
-            pulseCallback[0] = invocation.getArgument(0);
-            return null;
-        }).when(mDozeScrimController).pulse(any(), anyInt());
-
         // Starting a pulse should change the scrim controller to the pulsing state
-        mStatusBar.mDozeServiceHost.pulseWhileDozing(mock(DozeHost.PulseCallback.class),
-                DozeEvent.PULSE_REASON_NOTIFICATION);
+        when(mDozeServiceHost.isPulsing()).thenReturn(true);
+        mStatusBar.updateScrimController();
         verify(mScrimController).transitionTo(eq(ScrimState.PULSING), any());
 
         // Ending a pulse should take it back to keyguard state
-        pulseCallback[0].onPulseFinished();
+        when(mDozeServiceHost.isPulsing()).thenReturn(false);
+        mStatusBar.updateScrimController();
         verify(mScrimController).transitionTo(eq(ScrimState.KEYGUARD));
     }
 
     @Test
-    public void testPulseWhileDozing_notifyAuthInterrupt() {
-        HashSet<Integer> reasonsWantingAuth = new HashSet<>(
-                Collections.singletonList(DozeEvent.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN));
-        HashSet<Integer> reasonsSkippingAuth = new HashSet<>(
-                Arrays.asList(DozeEvent.PULSE_REASON_INTENT,
-                        DozeEvent.PULSE_REASON_NOTIFICATION,
-                        DozeEvent.PULSE_REASON_SENSOR_SIGMOTION,
-                        DozeEvent.REASON_SENSOR_PICKUP,
-                        DozeEvent.REASON_SENSOR_DOUBLE_TAP,
-                        DozeEvent.PULSE_REASON_SENSOR_LONG_PRESS,
-                        DozeEvent.PULSE_REASON_DOCKING,
-                        DozeEvent.REASON_SENSOR_WAKE_UP,
-                        DozeEvent.REASON_SENSOR_TAP));
-        HashSet<Integer> reasonsThatDontPulse = new HashSet<>(
-                Arrays.asList(DozeEvent.REASON_SENSOR_PICKUP,
-                        DozeEvent.REASON_SENSOR_DOUBLE_TAP,
-                        DozeEvent.REASON_SENSOR_TAP));
-
-        doAnswer(invocation -> {
-            DozeHost.PulseCallback callback = invocation.getArgument(0);
-            callback.onPulseStarted();
-            return null;
-        }).when(mDozeScrimController).pulse(any(), anyInt());
-
-        mStatusBar.mDozeServiceHost.mWakeLockScreenPerformsAuth = true;
-        for (int i = 0; i < DozeEvent.TOTAL_REASONS; i++) {
-            reset(mKeyguardUpdateMonitor);
-            mStatusBar.mDozeServiceHost.pulseWhileDozing(mock(DozeHost.PulseCallback.class), i);
-            if (reasonsWantingAuth.contains(i)) {
-                verify(mKeyguardUpdateMonitor).onAuthInterruptDetected(eq(true));
-            } else if (reasonsSkippingAuth.contains(i) || reasonsThatDontPulse.contains(i)) {
-                verify(mKeyguardUpdateMonitor, never()).onAuthInterruptDetected(eq(true));
-            } else {
-                throw new AssertionError("Reason " + i + " isn't specified as wanting or skipping"
-                        + " passive auth. Please consider how this pulse reason should behave.");
-            }
-        }
-    }
-
-    @Test
-    public void testPulseWhileDozingWithDockingReason_suppressWakeUpGesture() {
-        // Keep track of callback to be able to stop the pulse
-        final DozeHost.PulseCallback[] pulseCallback = new DozeHost.PulseCallback[1];
-        doAnswer(invocation -> {
-            pulseCallback[0] = invocation.getArgument(0);
-            return null;
-        }).when(mDozeScrimController).pulse(any(), anyInt());
-
-        // Starting a pulse while docking should suppress wakeup gesture
-        mStatusBar.mDozeServiceHost.pulseWhileDozing(mock(DozeHost.PulseCallback.class),
-                DozeEvent.PULSE_REASON_DOCKING);
-        verify(mStatusBarWindowViewController).suppressWakeUpGesture(eq(true));
-
-        // Ending a pulse should restore wakeup gesture
-        pulseCallback[0].onPulseFinished();
-        verify(mStatusBarWindowViewController).suppressWakeUpGesture(eq(false));
-    }
-
-    @Test
     public void testSetState_changesIsFullScreenUserSwitcherState() {
         mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
         assertFalse(mStatusBar.isFullScreenUserSwitcherState());
@@ -859,27 +792,17 @@
     }
 
     @Test
-    public void testStartStopDozing() {
-        mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
-        when(mStatusBarStateController.isKeyguardRequested()).thenReturn(true);
-
-        mStatusBar.mDozeServiceHost.startDozing();
-        verify(mStatusBarStateController).setIsDozing(eq(true));
-
-        mStatusBar.mDozeServiceHost.stopDozing();
-        verify(mStatusBarStateController).setIsDozing(eq(false));
-    }
-
-    @Test
     public void testOnStartedWakingUp_isNotDozing() {
         mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
         when(mStatusBarStateController.isKeyguardRequested()).thenReturn(true);
-        mStatusBar.mDozeServiceHost.startDozing();
-        verify(mStatusBarStateController).setIsDozing(eq(true));
+        when(mDozeServiceHost.getDozingRequested()).thenReturn(true);
+        mStatusBar.updateIsKeyguard();
+        // TODO: mNotificationPanelView.expand(false) gets called twice. Should be once.
+        verify(mNotificationPanelView, times(2)).expand(eq(false));
         clearInvocations(mNotificationPanelView);
 
         mStatusBar.mWakefulnessObserver.onStartedWakingUp();
-        verify(mStatusBarStateController).setIsDozing(eq(false));
+        verify(mDozeServiceHost).stopDozing();
         verify(mNotificationPanelView).expand(eq(false));
     }
 
@@ -887,7 +810,8 @@
     public void testOnStartedWakingUp_doesNotDismissBouncer_whenPulsing() {
         mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
         when(mStatusBarStateController.isKeyguardRequested()).thenReturn(true);
-        mStatusBar.mDozeServiceHost.startDozing();
+        when(mDozeServiceHost.getDozingRequested()).thenReturn(true);
+        mStatusBar.updateIsKeyguard();
         clearInvocations(mNotificationPanelView);
 
         mStatusBar.setBouncerShowing(true);
diff --git a/services/core/java/com/android/server/GnssManagerService.java b/services/core/java/com/android/server/GnssManagerService.java
index 44a8234..274e2f1 100644
--- a/services/core/java/com/android/server/GnssManagerService.java
+++ b/services/core/java/com/android/server/GnssManagerService.java
@@ -18,6 +18,7 @@
 
 import android.Manifest;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.content.Context;
@@ -300,7 +301,7 @@
      * @return true if callback is successfully added, false otherwise
      */
     public boolean addGnssBatchingCallback(IBatchedLocationCallback callback, String packageName,
-            @NonNull String listenerIdentity) {
+            @Nullable String featureId, @NonNull String listenerIdentity) {
         mContext.enforceCallingPermission(
                 android.Manifest.permission.LOCATION_HARDWARE,
                 "Location Hardware permission not granted to access hardware batching");
@@ -319,7 +320,7 @@
 
         CallerIdentity callerIdentity =
                 new CallerIdentity(Binder.getCallingUid(), Binder.getCallingPid(), packageName,
-                        listenerIdentity);
+                        featureId, listenerIdentity);
         synchronized (mGnssBatchingLock) {
             mGnssBatchingCallback = callback;
             mGnssBatchingDeathCallback =
@@ -497,6 +498,7 @@
     private <TListener extends IInterface> boolean addGnssDataListenerLocked(
             TListener listener,
             String packageName,
+            @Nullable String featureId,
             @NonNull String listenerIdentifier,
             RemoteListenerHelper<TListener> gnssDataProvider,
             ArrayMap<IBinder,
@@ -517,7 +519,7 @@
 
         CallerIdentity callerIdentity =
                 new CallerIdentity(Binder.getCallingUid(), Binder.getCallingPid(), packageName,
-                        listenerIdentifier);
+                        featureId, listenerIdentifier);
         LinkedListener<TListener> linkedListener =
                 new LocationManagerServiceUtils.LinkedListener<>(
                         listener, listenerIdentifier, callerIdentity, binderDeathCallback);
@@ -605,11 +607,13 @@
      * @param packageName name of requesting package
      * @return true if listener is successfully registered, false otherwise
      */
-    public boolean registerGnssStatusCallback(IGnssStatusListener listener, String packageName) {
+    public boolean registerGnssStatusCallback(IGnssStatusListener listener, String packageName,
+            @Nullable String featureId) {
         synchronized (mGnssStatusListeners) {
             return addGnssDataListenerLocked(
                     listener,
                     packageName,
+                    featureId,
                     "Gnss status",
                     mGnssStatusProvider,
                     mGnssStatusListeners,
@@ -636,12 +640,13 @@
      * @return true if listener is successfully added, false otherwise
      */
     public boolean addGnssMeasurementsListener(
-            IGnssMeasurementsListener listener, String packageName,
+            IGnssMeasurementsListener listener, String packageName, @Nullable String featureId,
             @NonNull String listenerIdentifier) {
         synchronized (mGnssMeasurementsListeners) {
             return addGnssDataListenerLocked(
                     listener,
                     packageName,
+                    featureId,
                     listenerIdentifier,
                     mGnssMeasurementsProvider,
                     mGnssMeasurementsListeners,
@@ -695,11 +700,12 @@
      */
     public boolean addGnssNavigationMessageListener(
             IGnssNavigationMessageListener listener, String packageName,
-            @NonNull String listenerIdentifier) {
+            @Nullable String featureId, @NonNull String listenerIdentifier) {
         synchronized (mGnssNavigationMessageListeners) {
             return addGnssDataListenerLocked(
                     listener,
                     packageName,
+                    featureId,
                     listenerIdentifier,
                     mGnssNavigationMessageProvider,
                     mGnssNavigationMessageListeners,
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index ad3f502..0a63bf8 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -1233,9 +1233,9 @@
         PowerManager.WakeLock mWakeLock;
 
         private Receiver(ILocationListener listener, PendingIntent intent, int pid, int uid,
-                String packageName, WorkSource workSource, boolean hideFromAppOps,
-                @NonNull String listenerIdentifier) {
-            super(new CallerIdentity(uid, pid, packageName, listenerIdentifier),
+                String packageName, @Nullable String featureId, WorkSource workSource,
+                boolean hideFromAppOps, @NonNull String listenerIdentifier) {
+            super(new CallerIdentity(uid, pid, packageName, featureId, listenerIdentifier),
                     "LocationListener");
             mListener = listener;
             mPendingIntent = intent;
@@ -1360,7 +1360,8 @@
             if (!currentlyMonitoring) {
                 if (allowMonitoring) {
                     return mAppOps.startOpNoThrow(op, mCallerIdentity.mUid,
-                            mCallerIdentity.mPackageName) == AppOpsManager.MODE_ALLOWED;
+                            mCallerIdentity.mPackageName, false, mCallerIdentity.mFeatureId, null)
+                            == AppOpsManager.MODE_ALLOWED;
                 }
             } else {
                 if (!allowMonitoring
@@ -1545,11 +1546,11 @@
 
     @Override
     public boolean addGnssBatchingCallback(IBatchedLocationCallback callback, String packageName,
-            String listenerIdentifier) {
+            String featureId, String listenerIdentifier) {
         Preconditions.checkNotNull(listenerIdentifier);
 
         return mGnssManagerService == null ? false : mGnssManagerService.addGnssBatchingCallback(
-                callback, packageName, listenerIdentifier);
+                callback, packageName, featureId, listenerIdentifier);
     }
 
     @Override
@@ -1712,10 +1713,10 @@
     }
 
     private boolean reportLocationAccessNoThrow(int pid, int uid, String packageName,
-            int allowedResolutionLevel, @Nullable String message) {
+            @Nullable String featureId, int allowedResolutionLevel, @Nullable String message) {
         int op = resolutionLevelToOp(allowedResolutionLevel);
         if (op >= 0) {
-            if (mAppOps.noteOpNoThrow(op, uid, packageName, null, message)
+            if (mAppOps.noteOpNoThrow(op, uid, packageName, featureId, message)
                     != AppOpsManager.MODE_ALLOWED) {
                 return false;
             }
@@ -2149,12 +2150,12 @@
 
     @GuardedBy("mLock")
     private Receiver getReceiverLocked(ILocationListener listener, int pid, int uid,
-            String packageName, WorkSource workSource, boolean hideFromAppOps,
-            @NonNull String listenerIdentifier) {
+            String packageName, @Nullable String featureId, WorkSource workSource,
+            boolean hideFromAppOps, @NonNull String listenerIdentifier) {
         IBinder binder = listener.asBinder();
         Receiver receiver = mReceivers.get(binder);
         if (receiver == null) {
-            receiver = new Receiver(listener, null, pid, uid, packageName, workSource,
+            receiver = new Receiver(listener, null, pid, uid, packageName, featureId, workSource,
                     hideFromAppOps, listenerIdentifier);
             if (!receiver.linkToListenerDeathNotificationLocked(
                     receiver.getListener().asBinder())) {
@@ -2167,10 +2168,11 @@
 
     @GuardedBy("mLock")
     private Receiver getReceiverLocked(PendingIntent intent, int pid, int uid, String packageName,
-            WorkSource workSource, boolean hideFromAppOps, @NonNull String listenerIdentifier) {
+            @Nullable String featureId, WorkSource workSource, boolean hideFromAppOps,
+            @NonNull String listenerIdentifier) {
         Receiver receiver = mReceivers.get(intent);
         if (receiver == null) {
-            receiver = new Receiver(null, intent, pid, uid, packageName, workSource,
+            receiver = new Receiver(null, intent, pid, uid, packageName, featureId, workSource,
                     hideFromAppOps, listenerIdentifier);
             mReceivers.put(intent, receiver);
         }
@@ -2233,7 +2235,8 @@
 
     @Override
     public void requestLocationUpdates(LocationRequest request, ILocationListener listener,
-            PendingIntent intent, String packageName, String listenerIdentifier) {
+            PendingIntent intent, String packageName, String featureId,
+            String listenerIdentifier) {
         Preconditions.checkNotNull(listenerIdentifier);
 
         synchronized (mLock) {
@@ -2289,11 +2292,11 @@
 
                 Receiver receiver;
                 if (intent != null) {
-                    receiver = getReceiverLocked(intent, pid, uid, packageName, workSource,
-                            hideFromAppOps, listenerIdentifier);
+                    receiver = getReceiverLocked(intent, pid, uid, packageName, featureId,
+                            workSource, hideFromAppOps, listenerIdentifier);
                 } else {
-                    receiver = getReceiverLocked(listener, pid, uid, packageName, workSource,
-                            hideFromAppOps, listenerIdentifier);
+                    receiver = getReceiverLocked(listener, pid, uid, packageName, featureId,
+                            workSource, hideFromAppOps, listenerIdentifier);
                 }
                 requestLocationUpdatesLocked(sanitizedRequest, receiver, uid, packageName);
             } finally {
@@ -2362,9 +2365,10 @@
         synchronized (mLock) {
             Receiver receiver;
             if (intent != null) {
-                receiver = getReceiverLocked(intent, pid, uid, packageName, null, false, "");
+                receiver = getReceiverLocked(intent, pid, uid, packageName, null, null, false, "");
             } else {
-                receiver = getReceiverLocked(listener, pid, uid, packageName, null, false, "");
+                receiver = getReceiverLocked(listener, pid, uid, packageName, null, null, false,
+                        "");
             }
 
             long identity = Binder.clearCallingIdentity();
@@ -2408,7 +2412,7 @@
     }
 
     @Override
-    public Location getLastLocation(LocationRequest r, String packageName) {
+    public Location getLastLocation(LocationRequest r, String packageName, String featureId) {
         synchronized (mLock) {
             LocationRequest request = r != null ? r : DEFAULT_LOCATION_REQUEST;
             int allowedResolutionLevel = getCallerAllowedResolutionLevel();
@@ -2483,8 +2487,8 @@
                 }
                 // Don't report location access if there is no last location to deliver.
                 if (lastLocation != null) {
-                    if (!reportLocationAccessNoThrow(pid, uid, packageName, allowedResolutionLevel,
-                            null)) {
+                    if (!reportLocationAccessNoThrow(pid, uid, packageName, featureId,
+                            allowedResolutionLevel, null)) {
                         if (D) {
                             Log.d(TAG, "not returning last loc for no op app: " + packageName);
                         }
@@ -2501,9 +2505,9 @@
     @Override
     public boolean getCurrentLocation(LocationRequest locationRequest,
             ICancellationSignal remoteCancellationSignal, ILocationListener listener,
-            String packageName, String listenerIdentifier) {
+            String packageName, String featureId, String listenerIdentifier) {
         // side effect of validating locationRequest and packageName
-        Location lastLocation = getLastLocation(locationRequest, packageName);
+        Location lastLocation = getLastLocation(locationRequest, packageName, featureId);
         if (lastLocation != null) {
             long locationAgeMs = TimeUnit.NANOSECONDS.toMillis(
                     SystemClock.elapsedRealtimeNanos() - lastLocation.getElapsedRealtimeNanos());
@@ -2538,7 +2542,8 @@
             }
         }
 
-        requestLocationUpdates(locationRequest, listener, null, packageName, listenerIdentifier);
+        requestLocationUpdates(locationRequest, listener, null, packageName, featureId,
+                listenerIdentifier);
         CancellationSignal cancellationSignal = CancellationSignal.fromTransport(
                 remoteCancellationSignal);
         if (cancellationSignal != null) {
@@ -2588,7 +2593,7 @@
 
     @Override
     public void requestGeofence(LocationRequest request, Geofence geofence, PendingIntent intent,
-            String packageName, String listenerIdentifier) {
+            String packageName, String featureId, String listenerIdentifier) {
         Preconditions.checkNotNull(listenerIdentifier);
 
         if (request == null) request = DEFAULT_LOCATION_REQUEST;
@@ -2636,7 +2641,7 @@
             }
 
             mGeofenceManager.addFence(sanitizedRequest, geofence, intent, allowedResolutionLevel,
-                    uid, packageName, listenerIdentifier);
+                    uid, packageName, featureId, listenerIdentifier);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -2672,9 +2677,10 @@
     }
 
     @Override
-    public boolean registerGnssStatusCallback(IGnssStatusListener listener, String packageName) {
+    public boolean registerGnssStatusCallback(IGnssStatusListener listener, String packageName,
+            String featureId) {
         return mGnssManagerService == null ? false : mGnssManagerService.registerGnssStatusCallback(
-                listener, packageName);
+                listener, packageName, featureId);
     }
 
     @Override
@@ -2684,12 +2690,12 @@
 
     @Override
     public boolean addGnssMeasurementsListener(IGnssMeasurementsListener listener,
-            String packageName, String listenerIdentifier) {
+            String packageName, String featureId, String listenerIdentifier) {
         Preconditions.checkNotNull(listenerIdentifier);
 
         return mGnssManagerService == null ? false
-                : mGnssManagerService.addGnssMeasurementsListener(listener, packageName,
-                        listenerIdentifier);
+                : mGnssManagerService.addGnssMeasurementsListener(listener, packageName, featureId,
+                       listenerIdentifier);
     }
 
     @Override
@@ -2717,12 +2723,12 @@
 
     @Override
     public boolean addGnssNavigationMessageListener(IGnssNavigationMessageListener listener,
-            String packageName, String listenerIdentifier) {
+            String packageName, String featureId, String listenerIdentifier) {
         Preconditions.checkNotNull(listenerIdentifier);
 
         return mGnssManagerService == null ? false
                 : mGnssManagerService.addGnssNavigationMessageListener(listener, packageName,
-                        listenerIdentifier);
+                        featureId, listenerIdentifier);
     }
 
     @Override
@@ -3028,6 +3034,7 @@
                             receiver.mCallerIdentity.mPid,
                             receiver.mCallerIdentity.mUid,
                             receiver.mCallerIdentity.mPackageName,
+                            receiver.mCallerIdentity.mFeatureId,
                             receiver.mAllowedResolutionLevel,
                             "Location sent to " + receiver.mCallerIdentity.mListenerIdentifier)) {
                         if (D) {
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 619c21e..44c81fc 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -988,18 +988,6 @@
         }
     }
 
-    private String getErrorString(int modality, int error, int vendorCode) {
-        for (AuthenticatorWrapper authenticator : mAuthenticators) {
-            if (authenticator.modality == modality) {
-                // TODO(b/141025588): Refactor IBiometricServiceReceiver.aidl#onError(...) to not
-                // ask for a String error message, but derive it from the error code instead.
-                return "";
-            }
-        }
-        Slog.w(TAG, "Unable to get error string for modality: " + modality);
-        return null;
-    }
-
     private void logDialogDismissed(int reason) {
         if (reason == BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED) {
             // Explicit auth, authentication confirmed.
diff --git a/services/core/java/com/android/server/location/CallerIdentity.java b/services/core/java/com/android/server/location/CallerIdentity.java
index 61e5d1f..75ba5b8 100644
--- a/services/core/java/com/android/server/location/CallerIdentity.java
+++ b/services/core/java/com/android/server/location/CallerIdentity.java
@@ -17,6 +17,7 @@
 package com.android.server.location;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 
 /**
  * Represents the calling process's uid, pid, and package name.
@@ -25,13 +26,15 @@
     public final int mUid;
     public final int mPid;
     public final String mPackageName;
+    public final @Nullable String mFeatureId;
     public final @NonNull String mListenerIdentifier;
 
-    public CallerIdentity(int uid, int pid, String packageName,
+    public CallerIdentity(int uid, int pid, String packageName, @Nullable String featureId,
             @NonNull String listenerIdentifier) {
         mUid = uid;
         mPid = pid;
         mPackageName = packageName;
+        mFeatureId = featureId;
         mListenerIdentifier = listenerIdentifier;
     }
 }
diff --git a/services/core/java/com/android/server/location/GeofenceManager.java b/services/core/java/com/android/server/location/GeofenceManager.java
index 4f10a71..17a2169 100644
--- a/services/core/java/com/android/server/location/GeofenceManager.java
+++ b/services/core/java/com/android/server/location/GeofenceManager.java
@@ -17,6 +17,7 @@
 package com.android.server.location;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
 import android.content.ContentResolver;
@@ -152,7 +153,7 @@
     }
 
     public void addFence(LocationRequest request, Geofence geofence, PendingIntent intent,
-            int allowedResolutionLevel, int uid, String packageName,
+            int allowedResolutionLevel, int uid, String packageName, @Nullable String featureId,
             @NonNull String listenerIdentifier) {
         if (D) {
             Slog.d(TAG, "addFence: request=" + request + ", geofence=" + geofence
@@ -160,8 +161,8 @@
         }
 
         GeofenceState state = new GeofenceState(geofence,
-                request.getExpireAt(), allowedResolutionLevel, uid, packageName, listenerIdentifier,
-                intent);
+                request.getExpireAt(), allowedResolutionLevel, uid, packageName, featureId,
+                listenerIdentifier, intent);
         synchronized (mLock) {
             // first make sure it doesn't already exist
             for (int i = mFences.size() - 1; i >= 0; i--) {
@@ -304,7 +305,7 @@
                 int op = LocationManagerService.resolutionLevelToOp(state.mAllowedResolutionLevel);
                 if (op >= 0) {
                     if (mAppOps.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, state.mUid,
-                            state.mPackageName, null, state.mListenerIdentifier)
+                            state.mPackageName, state.mFeatureId, state.mListenerIdentifier)
                             != AppOpsManager.MODE_ALLOWED) {
                         if (D) {
                             Slog.d(TAG, "skipping geofence processing for no op app: "
diff --git a/services/core/java/com/android/server/location/GeofenceState.java b/services/core/java/com/android/server/location/GeofenceState.java
index fe0719d..a91a1dc 100644
--- a/services/core/java/com/android/server/location/GeofenceState.java
+++ b/services/core/java/com/android/server/location/GeofenceState.java
@@ -18,6 +18,7 @@
 package com.android.server.location;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.PendingIntent;
 import android.location.Geofence;
 import android.location.Location;
@@ -39,6 +40,7 @@
     public final int mAllowedResolutionLevel;
     public final int mUid;
     public final String mPackageName;
+    public final @Nullable String mFeatureId;
     public final @NonNull String mListenerIdentifier;
     public final PendingIntent mIntent;
 
@@ -46,7 +48,8 @@
     double mDistanceToCenter;  // current distance to center of fence
 
     public GeofenceState(Geofence fence, long expireAt, int allowedResolutionLevel, int uid,
-            String packageName, @NonNull String listenerIdentifier, PendingIntent intent) {
+            String packageName, @Nullable String featureId, @NonNull String listenerIdentifier,
+            PendingIntent intent) {
         mState = STATE_UNKNOWN;
         mDistanceToCenter = Double.MAX_VALUE;
 
@@ -55,6 +58,7 @@
         mAllowedResolutionLevel = allowedResolutionLevel;
         mUid = uid;
         mPackageName = packageName;
+        mFeatureId = featureId;
         mListenerIdentifier = listenerIdentifier;
         mIntent = intent;
 
diff --git a/services/core/java/com/android/server/location/RemoteListenerHelper.java b/services/core/java/com/android/server/location/RemoteListenerHelper.java
index 25b544f..60ce1f4 100644
--- a/services/core/java/com/android/server/location/RemoteListenerHelper.java
+++ b/services/core/java/com/android/server/location/RemoteListenerHelper.java
@@ -182,7 +182,7 @@
         }
 
         return mAppOps.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, callerIdentity.mUid,
-                callerIdentity.mPackageName, null,
+                callerIdentity.mPackageName, callerIdentity.mFeatureId,
                 "Location sent to " + callerIdentity.mListenerIdentifier)
                 == AppOpsManager.MODE_ALLOWED;
     }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 00c0566..ed2bb3d5 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -259,7 +259,7 @@
         // Don't hold mSessions lock when calling restoreSession, since it might trigger an APK
         // atomic install which needs to query sessions, which requires lock on mSessions.
         for (PackageInstallerSession session : stagedSessionsToRestore) {
-            if (mPm.isDeviceUpgrading()) {
+            if (mPm.isDeviceUpgrading() && !session.isStagedAndInTerminalState()) {
                 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
                         "Build fingerprint has changed");
             }
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 23df1c5..5783932 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -146,17 +146,16 @@
         }
 
         // During system reboot, add a DEVICE_SHUTDOWN event to the end of event list, the timestamp
-        // is last time UsageStatsDatabase is persisted to disk.
+        // is last time UsageStatsDatabase is persisted to disk or the last event's time whichever
+        // is higher (because the file system timestamp is round down to integral seconds).
         // Also add a DEVICE_STARTUP event with current system timestamp.
         final IntervalStats currentDailyStats = mCurrentStats[INTERVAL_DAILY];
         if (currentDailyStats != null) {
-            // File system timestamp only has precision of 1 second, add 1000ms to make up
-            // for the loss of round up.
-            final Event shutdownEvent =
-                    new Event(DEVICE_SHUTDOWN, currentDailyStats.lastTimeSaved + 1000);
+            final Event shutdownEvent = new Event(DEVICE_SHUTDOWN,
+                    Math.max(currentDailyStats.lastTimeSaved, currentDailyStats.endTime));
             shutdownEvent.mPackage = Event.DEVICE_EVENT_PACKAGE_NAME;
             currentDailyStats.addEvent(shutdownEvent);
-            final Event startupEvent = new Event(DEVICE_STARTUP, currentTimeMillis);
+            final Event startupEvent = new Event(DEVICE_STARTUP, System.currentTimeMillis());
             startupEvent.mPackage = Event.DEVICE_EVENT_PACKAGE_NAME;
             currentDailyStats.addEvent(startupEvent);
         }
diff --git a/startop/apps/test/Android.bp b/startop/apps/test/Android.bp
index 2ff26b8..5de7fd2 100644
--- a/startop/apps/test/Android.bp
+++ b/startop/apps/test/Android.bp
@@ -18,7 +18,8 @@
     name: "startop_test_app",
     srcs: [
         "src/ComplexLayoutInflationActivity.java",
-        "src/CPUIntensive.java",
+        "src/CPUIntensiveBenchmarkActivity.java",
+        "src/CPUIntensiveBenchmarks.java",
         "src/EmptyActivity.java",
         "src/FrameLayoutInflationActivity.java",
         "src/LayoutInflationActivity.java",
diff --git a/startop/apps/test/AndroidManifest.xml b/startop/apps/test/AndroidManifest.xml
index ebe2584..b08072e 100644
--- a/startop/apps/test/AndroidManifest.xml
+++ b/startop/apps/test/AndroidManifest.xml
@@ -37,6 +37,18 @@
         </activity>
 
         <activity
+            android:label="CPU Intensive Benchmark Test"
+            android:name=".CPUIntensiveBenchmarkActivity"
+            android:exported="true" >
+
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity
             android:label="Empty Activity Layout Test"
             android:name=".EmptyActivity"
             android:exported="true" >
diff --git a/startop/apps/test/src/CPUIntensive.java b/startop/apps/test/src/CPUIntensive.java
deleted file mode 100644
index a411e8c..0000000
--- a/startop/apps/test/src/CPUIntensive.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2019 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.
- */
-
-/**
- *  A threaded CPU intensive class for use in benchmarks.
- */
-
-package com.android.startop.test;
-
-final class CPUIntensive {
-    public static final int THREAD_COUNT = 8;
-    public static final int ARRAY_SIZE = 30000;
-    public static int[][] array = new int[THREAD_COUNT][ARRAY_SIZE];
-
-    static class WorkerThread extends Thread {
-        int mThreadNumber;
-        WorkerThread(int number) {
-            mThreadNumber = number;
-        }
-        public void run() {
-            final int arrayLength = array[mThreadNumber].length;
-            for (int i = 0; i < arrayLength; ++i) {
-                array[mThreadNumber][i] = i * i;
-            }
-            for (int i = 0; i < arrayLength; ++i) {
-                for (int j = 0; j < arrayLength; ++j) {
-                    int swap = array[mThreadNumber][j];
-                    array[mThreadNumber][j] = array[mThreadNumber][(j + i) % arrayLength];
-                    array[mThreadNumber][(j + i) % arrayLength] = swap;
-                }
-            }
-        }
-    };
-
-    public static void doSomeWork(int threadCount) {
-        WorkerThread[] threads = new WorkerThread[threadCount];
-        // Create the threads.
-        for (int i = 0; i < threadCount; ++i) {
-            threads[i] = new WorkerThread(i);
-        }
-        // Start the threads.
-        for (int i = 0; i < threadCount; ++i) {
-            threads[i].start();
-        }
-        // Join the threads.
-        for (int i = 0; i < threadCount; ++i) {
-            try {
-                threads[i].join();
-            } catch (Exception ex) {
-            }
-        }
-    }
-}
-
diff --git a/startop/apps/test/src/CPUIntensiveBenchmarkActivity.java b/startop/apps/test/src/CPUIntensiveBenchmarkActivity.java
new file mode 100644
index 0000000..2ec5308
--- /dev/null
+++ b/startop/apps/test/src/CPUIntensiveBenchmarkActivity.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.startop.test;
+
+import android.os.Bundle;
+
+public class CPUIntensiveBenchmarkActivity extends SystemServerBenchmarkActivity {
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.system_server_benchmark_page);
+
+        mBenchmarkList = findViewById(R.id.benchmark_list);
+
+        CPUIntensiveBenchmarks.initializeBenchmarks(this, this);
+    }
+}
diff --git a/startop/apps/test/src/CPUIntensiveBenchmarks.java b/startop/apps/test/src/CPUIntensiveBenchmarks.java
new file mode 100644
index 0000000..19d0b63
--- /dev/null
+++ b/startop/apps/test/src/CPUIntensiveBenchmarks.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+/**
+ *  A threaded CPU intensive class for use in benchmarks.
+ */
+
+package com.android.startop.test;
+
+import android.app.Activity;
+
+public class CPUIntensiveBenchmarks {
+    public static final int ARRAY_SIZE = 30000;
+    public static int[][] mArray;
+
+    static class WorkerThread extends Thread {
+        int mThreadNumber;
+        WorkerThread(int number) {
+            mThreadNumber = number;
+        }
+        public void run() {
+            final int arrayLength = mArray[mThreadNumber].length;
+            for (int i = 0; i < arrayLength; ++i) {
+                mArray[mThreadNumber][i] = i * i;
+            }
+            for (int i = 0; i < arrayLength; ++i) {
+                for (int j = 0; j < arrayLength; ++j) {
+                    int swap = mArray[mThreadNumber][j];
+                    mArray[mThreadNumber][j] = mArray[mThreadNumber][(j + i) % arrayLength];
+                    mArray[mThreadNumber][(j + i) % arrayLength] = swap;
+                }
+            }
+        }
+    };
+
+    static void doSomeWork(int threadCount) {
+        mArray = new int[threadCount][ARRAY_SIZE];
+        WorkerThread[] threads = new WorkerThread[threadCount];
+        // Create the threads.
+        for (int i = 0; i < threadCount; ++i) {
+            threads[i] = new WorkerThread(i);
+        }
+        // Start the threads.
+        for (int i = 0; i < threadCount; ++i) {
+            threads[i].start();
+        }
+        // Join the threads.
+        for (int i = 0; i < threadCount; ++i) {
+            try {
+                threads[i].join();
+            } catch (Exception ex) {
+            }
+        }
+    }
+
+    // Time limit to run benchmarks in seconds
+    public static final int TIME_LIMIT = 5;
+
+    static void initializeBenchmarks(Activity parent, BenchmarkRunner benchmarks) {
+        benchmarks.addBenchmark("Use 1 thread", () -> {
+            doSomeWork(1);
+        });
+        benchmarks.addBenchmark("Use 2 threads", () -> {
+            doSomeWork(2);
+        });
+        benchmarks.addBenchmark("Use 4 threads", () -> {
+            doSomeWork(4);
+        });
+        benchmarks.addBenchmark("Use 8 threads", () -> {
+            doSomeWork(8);
+        });
+        benchmarks.addBenchmark("Use 16 threads", () -> {
+            doSomeWork(16);
+        });
+    }
+}
diff --git a/startop/apps/test/src/SystemServerBenchmarkActivity.java b/startop/apps/test/src/SystemServerBenchmarkActivity.java
index 75ea69b..6be8df3 100644
--- a/startop/apps/test/src/SystemServerBenchmarkActivity.java
+++ b/startop/apps/test/src/SystemServerBenchmarkActivity.java
@@ -17,28 +17,20 @@
 package com.android.startop.test;
 
 import android.app.Activity;
-import android.app.ActivityManager;
-import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.os.AsyncTask;
 import android.os.Bundle;
-import android.view.ViewGroup;
 import android.widget.Button;
 import android.widget.GridLayout;
 import android.widget.TextView;
 
 public class SystemServerBenchmarkActivity extends Activity implements BenchmarkRunner {
-    private GridLayout benchmarkList;
+    protected GridLayout mBenchmarkList;
 
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.system_server_benchmark_page);
 
-        benchmarkList = findViewById(R.id.benchmark_list);
+        mBenchmarkList = findViewById(R.id.benchmark_list);
 
         SystemServerBenchmarks.initializeBenchmarks(this, this);
     }
@@ -49,7 +41,7 @@
      * @param name A short name that shows up in the UI or benchmark results
      */
     public void addBenchmark(CharSequence name, Runnable thunk) {
-        Context context = benchmarkList.getContext();
+        Context context = mBenchmarkList.getContext();
         Button button = new Button(context);
         TextView mean = new TextView(context);
         TextView stdev = new TextView(context);
@@ -68,8 +60,8 @@
             });
         });
 
-        benchmarkList.addView(button);
-        benchmarkList.addView(mean);
-        benchmarkList.addView(stdev);
+        mBenchmarkList.addView(button);
+        mBenchmarkList.addView(mean);
+        mBenchmarkList.addView(stdev);
     }
 }
diff --git a/startop/apps/test/src/SystemServerBenchmarks.java b/startop/apps/test/src/SystemServerBenchmarks.java
index 5918503..25b43f4 100644
--- a/startop/apps/test/src/SystemServerBenchmarks.java
+++ b/startop/apps/test/src/SystemServerBenchmarks.java
@@ -60,22 +60,6 @@
         benchmarks.addBenchmark("Empty", () -> {
         });
 
-        benchmarks.addBenchmark("CPU Intensive (1 thread)", () -> {
-            CPUIntensive.doSomeWork(1);
-        });
-
-        benchmarks.addBenchmark("CPU Intensive (2 thread)", () -> {
-            CPUIntensive.doSomeWork(2);
-        });
-
-        benchmarks.addBenchmark("CPU Intensive (4 thread)", () -> {
-            CPUIntensive.doSomeWork(4);
-        });
-
-        benchmarks.addBenchmark("CPU Intensive (8 thread)", () -> {
-            CPUIntensive.doSomeWork(8);
-        });
-
         PackageManager pm = parent.getPackageManager();
         benchmarks.addBenchmark("getInstalledApplications", () -> {
             pm.getInstalledApplications(PackageManager.MATCH_SYSTEM_ONLY);
diff --git a/wifi/java/android/net/wifi/hotspot2/ConfigParser.java b/wifi/java/android/net/wifi/hotspot2/ConfigParser.java
index e8e8731..bb01365 100644
--- a/wifi/java/android/net/wifi/hotspot2/ConfigParser.java
+++ b/wifi/java/android/net/wifi/hotspot2/ConfigParser.java
@@ -182,6 +182,12 @@
             throw new IOException("Passpoint profile missing credential");
         }
 
+        // Don't allow the installer to make changes to the update identifier. This is an
+        // indicator of an R2 (or newer) network.
+        if (config.getUpdateIdentifier() != Integer.MIN_VALUE) {
+            config.setUpdateIdentifier(Integer.MIN_VALUE);
+        }
+
         // Parse CA (Certificate Authority) certificate.
         byte[] caCertData = mimeParts.get(TYPE_CA_CERT);
         if (caCertData != null) {
diff --git a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
index 557b7c9..e9aa076 100644
--- a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
+++ b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
@@ -65,6 +65,7 @@
      * Configurations under HomeSp subtree.
      */
     private HomeSp mHomeSp = null;
+
     /**
      * Set the Home SP (Service Provider) information.
      *
@@ -248,7 +249,10 @@
         mSubscriptionExpirationTimeInMillis = subscriptionExpirationTimeInMillis;
     }
     /**
-     * @hide
+     *  Utility method to get the time this subscription will expire. It is in the format of number
+     *  of milliseconds since January 1, 1970, 00:00:00 GMT.
+     *
+     *  @return The time this subscription will expire, or Long.MIN_VALUE to indicate unset value
      */
     public long getSubscriptionExpirationTimeInMillis() {
         return mSubscriptionExpirationTimeInMillis;
@@ -521,6 +525,8 @@
                 .append("\n");
         builder.append("UsageLimitDataLimit: ").append(mUsageLimitDataLimit).append("\n");
         builder.append("UsageLimitTimeLimit: ").append(mUsageLimitTimeLimitInMinutes).append("\n");
+        builder.append("Provisioned by a subscription server: ")
+                .append(isOsuProvisioned() ? "Yes" : "No").append("\n");
         if (mHomeSp != null) {
             builder.append("HomeSP Begin ---\n");
             builder.append(mHomeSp);
@@ -728,4 +734,14 @@
         }
         return true;
     }
+
+    /**
+     * Indicates if the Passpoint Configuration was provisioned by a subscription (OSU) server,
+     * which means that it's an R2 (or R3) profile.
+     *
+     * @return true if the Passpoint Configuration was provisioned by a subscription server.
+     */
+    public boolean isOsuProvisioned() {
+        return getUpdateIdentifier() != Integer.MIN_VALUE;
+    }
 }
diff --git a/wifi/tests/assets/hsr1/HSR1ProfileWithUpdateIdentifier.base64 b/wifi/tests/assets/hsr1/HSR1ProfileWithUpdateIdentifier.base64
new file mode 100644
index 0000000..f4d2abb
--- /dev/null
+++ b/wifi/tests/assets/hsr1/HSR1ProfileWithUpdateIdentifier.base64
@@ -0,0 +1,88 @@
+TUlNRS1WZXJzaW9uOiAxLjAKQ29udGVudC1UeXBlOiBtdWx0aXBhcnQvbWl4ZWQ7IGJvdW5kYXJ5
+PXtib3VuZGFyeX07IGNoYXJzZXQ9VVRGLTgKQ29udGVudC1UcmFuc2Zlci1FbmNvZGluZzogYmFz
+ZTY0CgotLXtib3VuZGFyeX0KQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi94LXBhc3Nwb2ludC1w
+cm9maWxlOyBjaGFyc2V0PVVURi04CkNvbnRlbnQtVHJhbnNmZXItRW5jb2Rpbmc6IGJhc2U2NAoK
+UEUxbmJYUlVjbVZsSUhodGJHNXpQU0p6ZVc1amJXdzZaRzFrWkdZeExqSWlQZ29nSUR4V1pYSkVW
+RVErTVM0eVBDOVdaWEpFVkVRKwpDaUFnUEU1dlpHVStDaUFnSUNBOFRtOWtaVTVoYldVK1VHVnlV
+SEp2ZG1sa1pYSlRkV0p6WTNKcGNIUnBiMjQ4TDA1dlpHVk9ZVzFsClBnb2dJQ0FnUEZKVVVISnZj
+R1Z5ZEdsbGN6NEtJQ0FnSUNBZ1BGUjVjR1UrQ2lBZ0lDQWdJQ0FnUEVSRVJrNWhiV1UrZFhKdU9u
+ZG0KWVRwdGJ6cG9iM1J6Y0c5ME1tUnZkREF0Y0dWeWNISnZkbWxrWlhKemRXSnpZM0pwY0hScGIy
+NDZNUzR3UEM5RVJFWk9ZVzFsUGdvZwpJQ0FnSUNBOEwxUjVjR1UrQ2lBZ0lDQThMMUpVVUhKdmNH
+VnlkR2xsY3o0S0lDQWdJRHhPYjJSbFBnb2dJQ0FnSUNBOFRtOWtaVTVoCmJXVStWWEJrWVhSbFNX
+UmxiblJwWm1sbGNqd3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lEeFdZV3gxWlQ0eE1qTTBQQzlXWVd4
+MVpUNEsKSUNBZ0lEd3ZUbTlrWlQ0S0lDQWdJRHhPYjJSbFBnb2dJQ0FnSUNBOFRtOWtaVTVoYldV
+K2FUQXdNVHd2VG05a1pVNWhiV1UrQ2lBZwpJQ0FnSUR4T2IyUmxQZ29nSUNBZ0lDQWdJRHhPYjJS
+bFRtRnRaVDVJYjIxbFUxQThMMDV2WkdWT1lXMWxQZ29nSUNBZ0lDQWdJRHhPCmIyUmxQZ29nSUNB
+Z0lDQWdJQ0FnUEU1dlpHVk9ZVzFsUGtaeWFXVnVaR3g1VG1GdFpUd3ZUbTlrWlU1aGJXVStDaUFn
+SUNBZ0lDQWcKSUNBOFZtRnNkV1UrUTJWdWRIVnllU0JJYjNWelpUd3ZWbUZzZFdVK0NpQWdJQ0Fn
+SUNBZ1BDOU9iMlJsUGdvZ0lDQWdJQ0FnSUR4TwpiMlJsUGdvZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZP
+WVcxbFBrWlJSRTQ4TDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZ1BGWmhiSFZsClBtMXBOaTVq
+Ynk1MWF6d3ZWbUZzZFdVK0NpQWdJQ0FnSUNBZ1BDOU9iMlJsUGdvZ0lDQWdJQ0FnSUR4T2IyUmxQ
+Z29nSUNBZ0lDQWcKSUNBZ1BFNXZaR1ZPWVcxbFBsSnZZVzFwYm1kRGIyNXpiM0owYVhWdFQwazhM
+MDV2WkdWT1lXMWxQZ29nSUNBZ0lDQWdJQ0FnUEZaaApiSFZsUGpFeE1qSXpNeXcwTkRVMU5qWThM
+MVpoYkhWbFBnb2dJQ0FnSUNBZ0lEd3ZUbTlrWlQ0S0lDQWdJQ0FnUEM5T2IyUmxQZ29nCklDQWdJ
+Q0E4VG05a1pUNEtJQ0FnSUNBZ0lDQThUbTlrWlU1aGJXVStRM0psWkdWdWRHbGhiRHd2VG05a1pV
+NWhiV1UrQ2lBZ0lDQWcKSUNBZ1BFNXZaR1UrQ2lBZ0lDQWdJQ0FnSUNBOFRtOWtaVTVoYldVK1Vt
+VmhiRzA4TDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZwpQRlpoYkhWbFBuTm9ZV3RsYmk1emRH
+bHljbVZrTG1OdmJUd3ZWbUZzZFdVK0NpQWdJQ0FnSUNBZ1BDOU9iMlJsUGdvZ0lDQWdJQ0FnCklE
+eE9iMlJsUGdvZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbFBsVnpaWEp1WVcxbFVHRnpjM2R2Y21R
+OEwwNXZaR1ZPWVcxbFBnb2cKSUNBZ0lDQWdJQ0FnUEU1dlpHVStDaUFnSUNBZ0lDQWdJQ0FnSUR4
+T2IyUmxUbUZ0WlQ1VmMyVnlibUZ0WlR3dlRtOWtaVTVoYldVKwpDaUFnSUNBZ0lDQWdJQ0FnSUR4
+V1lXeDFaVDVxWVcxbGN6d3ZWbUZzZFdVK0NpQWdJQ0FnSUNBZ0lDQThMMDV2WkdVK0NpQWdJQ0Fn
+CklDQWdJQ0E4VG05a1pUNEtJQ0FnSUNBZ0lDQWdJQ0FnUEU1dlpHVk9ZVzFsUGxCaGMzTjNiM0pr
+UEM5T2IyUmxUbUZ0WlQ0S0lDQWcKSUNBZ0lDQWdJQ0FnUEZaaGJIVmxQbGx0T1hWYVJFRjNUbmM5
+UFR3dlZtRnNkV1UrQ2lBZ0lDQWdJQ0FnSUNBOEwwNXZaR1UrQ2lBZwpJQ0FnSUNBZ0lDQThUbTlr
+WlQ0S0lDQWdJQ0FnSUNBZ0lDQWdQRTV2WkdWT1lXMWxQa1ZCVUUxbGRHaHZaRHd2VG05a1pVNWhi
+V1UrCkNpQWdJQ0FnSUNBZ0lDQWdJRHhPYjJSbFBnb2dJQ0FnSUNBZ0lDQWdJQ0FnSUR4T2IyUmxU
+bUZ0WlQ1RlFWQlVlWEJsUEM5T2IyUmwKVG1GdFpUNEtJQ0FnSUNBZ0lDQWdJQ0FnSUNBOFZtRnNk
+V1UrTWpFOEwxWmhiSFZsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThMMDV2WkdVKwpDaUFnSUNBZ0lDQWdJ
+Q0FnSUR4T2IyUmxQZ29nSUNBZ0lDQWdJQ0FnSUNBZ0lEeE9iMlJsVG1GdFpUNUpibTVsY2sxbGRH
+aHZaRHd2ClRtOWtaVTVoYldVK0NpQWdJQ0FnSUNBZ0lDQWdJQ0FnUEZaaGJIVmxQazFUTFVOSVFW
+QXRWakk4TDFaaGJIVmxQZ29nSUNBZ0lDQWcKSUNBZ0lDQThMMDV2WkdVK0NpQWdJQ0FnSUNBZ0lD
+QThMMDV2WkdVK0NpQWdJQ0FnSUNBZ1BDOU9iMlJsUGdvZ0lDQWdJQ0FnSUR4TwpiMlJsUGdvZ0lD
+QWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbFBrUnBaMmwwWVd4RFpYSjBhV1pwWTJGMFpUd3ZUbTlrWlU1
+aGJXVStDaUFnCklDQWdJQ0FnSUNBOFRtOWtaVDRLSUNBZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcx
+bFBrTmxjblJwWm1sallYUmxWSGx3WlR3dlRtOWsKWlU1aGJXVStDaUFnSUNBZ0lDQWdJQ0FnSUR4
+V1lXeDFaVDU0TlRBNWRqTThMMVpoYkhWbFBnb2dJQ0FnSUNBZ0lDQWdQQzlPYjJSbApQZ29nSUNB
+Z0lDQWdJQ0FnUEU1dlpHVStDaUFnSUNBZ0lDQWdJQ0FnSUR4T2IyUmxUbUZ0WlQ1RFpYSjBVMGhC
+TWpVMlJtbHVaMlZ5CmNISnBiblE4TDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThWbUZz
+ZFdVK01XWXhaakZtTVdZeFpqRm1NV1l4WmpGbU1XWXgKWmpGbU1XWXhaakZtTVdZeFpqRm1NV1l4
+WmpGbU1XWXhaakZtTVdZeFpqRm1NV1l4WmpGbU1XWXhaand2Vm1Gc2RXVStDaUFnSUNBZwpJQ0Fn
+SUNBOEwwNXZaR1UrQ2lBZ0lDQWdJQ0FnUEM5T2IyUmxQZ29nSUNBZ0lDQWdJRHhPYjJSbFBnb2dJ
+Q0FnSUNBZ0lDQWdQRTV2ClpHVk9ZVzFsUGxOSlRUd3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lDQWdJ
+Q0E4VG05a1pUNEtJQ0FnSUNBZ0lDQWdJQ0FnUEU1dlpHVk8KWVcxbFBrbE5VMGs4TDA1dlpHVk9Z
+VzFsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThWbUZzZFdVK2FXMXphVHd2Vm1Gc2RXVStDaUFnSUNBZwpJ
+Q0FnSUNBOEwwNXZaR1UrQ2lBZ0lDQWdJQ0FnSUNBOFRtOWtaVDRLSUNBZ0lDQWdJQ0FnSUNBZ1BF
+NXZaR1ZPWVcxbFBrVkJVRlI1CmNHVThMMDV2WkdWT1lXMWxQZ29nSUNBZ0lDQWdJQ0FnSUNBOFZt
+RnNkV1UrTWpROEwxWmhiSFZsUGdvZ0lDQWdJQ0FnSUNBZ1BDOU8KYjJSbFBnb2dJQ0FnSUNBZ0lE
+d3ZUbTlrWlQ0S0lDQWdJQ0FnUEM5T2IyUmxQZ29nSUNBZ1BDOU9iMlJsUGdvZ0lEd3ZUbTlrWlQ0
+SwpQQzlOWjIxMFZISmxaVDRLCgotLXtib3VuZGFyeX0KQ29udGVudC1UeXBlOiBhcHBsaWNhdGlv
+bi94LXg1MDktY2EtY2VydApDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiYXNlNjQKCkxTMHRM
+UzFDUlVkSlRpQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENrMUpTVVJMUkVORFFXaERaMEYzU1VKQlow
+bEtRVWxNYkVaa2QzcE0KVm5WeVRVRXdSME5UY1VkVFNXSXpSRkZGUWtOM1ZVRk5Ra2w0UlVSQlQw
+Sm5UbFlLUWtGTlZFSXdWa0pWUTBKRVVWUkZkMGhvWTA1TgpWRmwzVFZSRmVVMVVSVEZOUkVVeFYy
+aGpUazFxV1hkTlZFRTFUVlJGTVUxRVJURlhha0ZUVFZKQmR3cEVaMWxFVmxGUlJFVjNaRVpSClZr
+Rm5VVEJGZUUxSlNVSkpha0ZPUW1kcmNXaHJhVWM1ZHpCQ1FWRkZSa0ZCVDBOQlVUaEJUVWxKUWtO
+blMwTkJVVVZCQ25wdVFWQlYKZWpJMlRYTmhaVFIzY3pRelkzcFNOREV2U2pKUmRISlRTVnBWUzIx
+V1ZYTldkVzFFWWxsSWNsQk9kbFJZUzFOTldFRmpaWGRQVWtSUgpXVmdLVW5GMlNIWndiamhEYzJO
+Q01TdHZSMWhhZGtoM2VHbzBlbFl3VjB0dlN6SjZaVmhyWVhVemRtTjViRE5JU1V0MWNFcG1jVEpV
+ClJVRkRaV1pXYW1vd2RBcEtWeXRZTXpWUVIxZHdPUzlJTlhwSlZVNVdUbFpxVXpkVmJYTTRORWwy
+UzJoU1FqZzFNVEpRUWpsVmVVaGgKWjFoWlZsZzFSMWR3UVdOV2NIbG1jbXhTQ2taSk9WRmthR2dy
+VUdKck1IVjVhM1JrWW1ZdlEyUm1aMGhQYjJWaWNsUjBkMUpzYWswdwpiMFIwV0NzeVEzWTJhakIz
+UWtzM2FFUTRjRkIyWmpFcmRYa0tSM3BqZW1sblFWVXZORXQzTjJWYWNYbGtaamxDS3pWU2RYQlNL
+MGxhCmFYQllOREY0UldsSmNrdFNkM0ZwTlRFM1YxZDZXR05xWVVjeVkwNWlaalExTVFwNGNFZzFV
+RzVXTTJreGRIRXdOR3BOUjFGVmVrWjMKU1VSQlVVRkNielJIUVUxSU5IZElVVmxFVmxJd1QwSkNX
+VVZHU1hkWU5IWnpPRUpwUW1OVFkyOWtDalZ1YjFwSVVrMDRSVFFyYVUxRgpTVWRCTVZWa1NYZFJO
+MDFFYlVGR1NYZFlOSFp6T0VKcFFtTlRZMjlrTlc1dldraFNUVGhGTkN0cGIxSmhhMFpFUVZNS1RW
+SkJkMFJuCldVUldVVkZFUlhka1JsRldRV2RSTUVWNFoyZHJRV2QxVlZZelJFMTBWelp6ZDBSQldV
+UldVakJVUWtGVmQwRjNSVUl2ZWtGTVFtZE8KVmdwSVVUaEZRa0ZOUTBGUldYZEVVVmxLUzI5YVNX
+aDJZMDVCVVVWTVFsRkJSR2RuUlVKQlJtWlJjVTlVUVRkU2RqZExLMngxVVRkdwpibUZ6TkVKWmQw
+aEZDamxIUlZBdmRXOW9kalpMVDNrd1ZFZFJSbUp5VWxScVJtOU1WazVDT1VKYU1YbHRUVVJhTUM5
+VVNYZEpWV00zCmQyazNZVGgwTlcxRmNWbElNVFV6ZDFjS1lWZHZiMmxUYW5sTVRHaDFTVFJ6VG5K
+T1EwOTBhWE5rUW5FeWNqSk5SbGgwTm1nd2JVRlIKV1U5UWRqaFNPRXMzTDJablUzaEhSbkY2YUhs
+T2JXMVdUQW94Y1VKS2JHUjRNelJUY0hkelZFRk1VVlpRWWpSb1IzZEtlbHBtY2pGUQpZM0JGVVhn
+MmVFMXVWR3c0ZUVWWFdrVXpUWE01T1hWaFZYaGlVWEZKZDFKMUNreG5RVTlyVGtOdFdUSnRPRGxX
+YUhwaFNFb3hkVlk0Ck5VRmtUUzkwUkN0WmMyMXNibTVxZERsTVVrTmxhbUpDYVhCcVNVZHFUMWh5
+WnpGS1VDdHNlRllLYlhWTk5IWklLMUF2Yld4dGVITlEKVUhvd1pEWTFZaXRGUjIxS1duQnZUR3RQ
+TDNSa1RrNTJRMWw2YWtwd1ZFVlhjRVZ6VHpaT1RXaExXVzg5Q2kwdExTMHRSVTVFSUVORgpVbFJK
+UmtsRFFWUkZMUzB0TFMwSwotLXtib3VuZGFyeX0tLQo=
\ No newline at end of file
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/ConfigParserTest.java b/wifi/tests/src/android/net/wifi/hotspot2/ConfigParserTest.java
index d9a1d9af..c815d75 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/ConfigParserTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/ConfigParserTest.java
@@ -54,6 +54,8 @@
             "assets/hsr1/HSR1ProfileWithInvalidContentType.base64";
     private static final String PASSPOINT_INSTALLATION_FILE_WITHOUT_PROFILE =
             "assets/hsr1/HSR1ProfileWithoutProfile.base64";
+    private static final String PASSPOINT_INSTALLATION_FILE_WITH_UPDATE_ID =
+            "assets/hsr1/HSR1ProfileWithUpdateIdentifier.base64";
 
     /**
      * Read the content of the given resource file into a String.
@@ -201,4 +203,22 @@
         assertNull(ConfigParser.parsePasspointConfig(
                 "application/x-wifi-config", configStr.getBytes()));
     }
+
+    /**
+     * Verify a valid installation file is parsed successfully with the matching contents, and that
+     * Update identifier is cleared.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseConfigFileWithUpdateIdentifier() throws Exception {
+        String configStr = loadResourceFile(PASSPOINT_INSTALLATION_FILE_WITH_UPDATE_ID);
+        PasspointConfiguration expectedConfig = generateConfigurationFromProfile();
+        PasspointConfiguration actualConfig =
+                ConfigParser.parsePasspointConfig(
+                        "application/x-wifi-config", configStr.getBytes());
+        // Expected configuration does not contain an update identifier
+        assertTrue(actualConfig.equals(expectedConfig));
+    }
+
 }
\ No newline at end of file
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
index c3b074e..f501b16 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
@@ -183,7 +183,7 @@
         PasspointConfiguration config = PasspointTestUtils.createConfig();
 
         assertTrue(config.validate());
-        assertTrue(config.validateForR2());
+        assertFalse(config.isOsuProvisioned());
     }
 
     /**
@@ -241,7 +241,6 @@
         config.setPolicy(null);
 
         assertTrue(config.validate());
-        assertTrue(config.validateForR2());
     }
 
     /**
@@ -271,7 +270,6 @@
         config.setAaaServerTrustedNames(null);
 
         assertTrue(config.validate());
-        assertTrue(config.validateForR2());
     }
 
     /**
@@ -348,4 +346,17 @@
         PasspointConfiguration copyConfig = new PasspointConfiguration(sourceConfig);
         assertTrue(copyConfig.equals(sourceConfig));
     }
+
+    /**
+     * Verify that a configuration containing all fields is valid for R2.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateFullR2Config() throws Exception {
+        PasspointConfiguration config = PasspointTestUtils.createR2Config();
+        assertTrue(config.validate());
+        assertTrue(config.validateForR2());
+        assertTrue(config.isOsuProvisioned());
+    }
 }
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/PasspointTestUtils.java b/wifi/tests/src/android/net/wifi/hotspot2/PasspointTestUtils.java
index adf74eb..8d55acb 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/PasspointTestUtils.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/PasspointTestUtils.java
@@ -132,7 +132,6 @@
      */
     public static PasspointConfiguration createConfig() {
         PasspointConfiguration config = new PasspointConfiguration();
-        config.setUpdateIdentifier(1234);
         config.setHomeSp(createHomeSp());
         config.setAaaServerTrustedNames(
                 new String[] {"trusted.fqdn.com", "another-trusted.fqdn.com"});
@@ -145,7 +144,6 @@
         trustRootCertList.put("trustRoot.cert2.com",
                 new byte[CERTIFICATE_FINGERPRINT_BYTES]);
         config.setTrustRootCertList(trustRootCertList);
-        config.setUpdateIdentifier(1);
         config.setCredentialPriority(120);
         config.setSubscriptionCreationTimeInMillis(231200);
         config.setSubscriptionExpirationTimeInMillis(2134232);
@@ -160,4 +158,15 @@
         config.setServiceFriendlyNames(friendlyNames);
         return config;
     }
+
+    /**
+     * Helper function for creating an R2 {@link PasspointConfiguration} for testing.
+     *
+     * @return {@link PasspointConfiguration}
+     */
+    public static PasspointConfiguration createR2Config() {
+        PasspointConfiguration config = createConfig();
+        config.setUpdateIdentifier(1234);
+        return config;
+    }
 }