Merge "Improve theme application transition." into qt-dev
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 74ceeb9..388161d 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -56,7 +56,8 @@
  * returned by {@link BluetoothAdapter#getBondedDevices()
  * BluetoothAdapter.getBondedDevices()}. You can then open a
  * {@link BluetoothSocket} for communication with the remote device, using
- * {@link #createRfcommSocketToServiceRecord(UUID)}.
+ * {@link #createRfcommSocketToServiceRecord(UUID)} over Bluetooth BR/EDR or using
+ * {@link #createL2capChannel(int)} over Bluetooth LE.
  *
  * <p class="note"><strong>Note:</strong>
  * Requires the {@link android.Manifest.permission#BLUETOOTH} permission.
diff --git a/core/java/android/bluetooth/BluetoothServerSocket.java b/core/java/android/bluetooth/BluetoothServerSocket.java
index 4e88625..c06b837 100644
--- a/core/java/android/bluetooth/BluetoothServerSocket.java
+++ b/core/java/android/bluetooth/BluetoothServerSocket.java
@@ -35,21 +35,28 @@
  * On the client side, use a single {@link BluetoothSocket} to both initiate
  * an outgoing connection and to manage the connection.
  *
- * <p>The most common type of Bluetooth socket is RFCOMM, which is the type
- * supported by the Android APIs. RFCOMM is a connection-oriented, streaming
- * transport over Bluetooth. It is also known as the Serial Port Profile (SPP).
+ * <p>For Bluetooth BR/EDR, the most common type of socket is RFCOMM, which is the type supported by
+ * the Android APIs. RFCOMM is a connection-oriented, streaming transport over Bluetooth BR/EDR. It
+ * is also known as the Serial Port Profile (SPP). To create a listening
+ * {@link BluetoothServerSocket} that's ready for incoming Bluetooth BR/EDR connections, use {@link
+ * BluetoothAdapter#listenUsingRfcommWithServiceRecord
+ * BluetoothAdapter.listenUsingRfcommWithServiceRecord()}.
  *
- * <p>To create a listening {@link BluetoothServerSocket} that's ready for
- * incoming connections, use
- * {@link BluetoothAdapter#listenUsingRfcommWithServiceRecord
- * BluetoothAdapter.listenUsingRfcommWithServiceRecord()}. Then call
- * {@link #accept()} to listen for incoming connection requests. This call
- * will block until a connection is established, at which point, it will return
- * a {@link BluetoothSocket} to manage the connection. Once the {@link
- * BluetoothSocket} is acquired, it's a good idea to call {@link #close()} on
- * the {@link BluetoothServerSocket} when it's no longer needed for accepting
- * connections. Closing the {@link BluetoothServerSocket} will <em>not</em>
- * close the returned {@link BluetoothSocket}.
+ * <p>For Bluetooth LE, the socket uses LE Connection-oriented Channel (CoC). LE CoC is a
+ * connection-oriented, streaming transport over Bluetooth LE and has a credit-based flow control.
+ * Correspondingly, use {@link BluetoothAdapter#listenUsingL2capChannel
+ * BluetoothAdapter.listenUsingL2capChannel()} to create a listening {@link BluetoothServerSocket}
+ * that's ready for incoming Bluetooth LE CoC connections. For LE CoC, you can use {@link #getPsm()}
+ * to get the protocol/service multiplexer (PSM) value that the peer needs to use to connect to your
+ * socket.
+ *
+ * <p> After the listening {@link BluetoothServerSocket} is created, call {@link #accept()} to
+ * listen for incoming connection requests. This call will block until a connection is established,
+ * at which point, it will return a {@link BluetoothSocket} to manage the connection. Once the
+ * {@link BluetoothSocket} is acquired, it's a good idea to call {@link #close()} on the {@link
+ * BluetoothServerSocket} when it's no longer needed for accepting
+ * connections. Closing the {@link BluetoothServerSocket} will <em>not</em> close the returned
+ * {@link BluetoothSocket}.
  *
  * <p>{@link BluetoothServerSocket} is thread
  * safe. In particular, {@link #close} will always immediately abort ongoing
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 24f42d4..4da0d2d 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7983,6 +7983,16 @@
                 "lock_screen_show_notifications";
 
         /**
+         * Indicates whether the lock screen should display silent notifications.
+         * <p>
+         * Type: int (0 for false, 1 for true)
+         *
+         * @hide
+         */
+        public static final String LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS =
+                "lock_screen_show_silent_notifications";
+
+        /**
          * List of TV inputs that are currently hidden. This is a string
          * containing the IDs of all hidden TV inputs. Each ID is encoded by
          * {@link android.net.Uri#encode(String)} and separated by ':'.
@@ -8838,6 +8848,7 @@
             LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
             LOCK_SCREEN_CUSTOM_CLOCK_FACE,
             LOCK_SCREEN_SHOW_NOTIFICATIONS,
+            LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS,
             ZEN_DURATION,
             SHOW_ZEN_UPGRADE_NOTIFICATION,
             SHOW_ZEN_SETTINGS_SUGGESTION,
@@ -9013,6 +9024,7 @@
             VALIDATORS.put(IN_CALL_NOTIFICATION_ENABLED, IN_CALL_NOTIFICATION_ENABLED_VALIDATOR);
             VALIDATORS.put(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, BOOLEAN_VALIDATOR);
             VALIDATORS.put(LOCK_SCREEN_SHOW_NOTIFICATIONS, BOOLEAN_VALIDATOR);
+            VALIDATORS.put(LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, BOOLEAN_VALIDATOR);
             VALIDATORS.put(ZEN_DURATION, ZEN_DURATION_VALIDATOR);
             VALIDATORS.put(SHOW_ZEN_UPGRADE_NOTIFICATION, BOOLEAN_VALIDATOR);
             VALIDATORS.put(SHOW_ZEN_SETTINGS_SUGGESTION, BOOLEAN_VALIDATOR);
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index a296d64..3a25e67 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -26,6 +26,7 @@
 #include <nativehelper/JNIHelp.h>
 #include "core_jni_helpers.h"
 
+#include <audiomanager/AudioManager.h>
 #include <media/AudioSystem.h>
 #include <media/AudioPolicy.h>
 #include <media/MicrophoneInfo.h>
@@ -353,7 +354,15 @@
 static jint
 android_media_AudioSystem_newAudioPlayerId(JNIEnv *env, jobject thiz)
 {
-    return AudioSystem::newAudioUniqueId(AUDIO_UNIQUE_ID_USE_PLAYER);
+    int id = AudioSystem::newAudioUniqueId(AUDIO_UNIQUE_ID_USE_CLIENT);
+    return id != AUDIO_UNIQUE_ID_ALLOCATE ? id : PLAYER_PIID_INVALID;
+}
+
+static jint
+android_media_AudioSystem_newAudioRecorderId(JNIEnv *env, jobject thiz)
+{
+    int id = AudioSystem::newAudioUniqueId(AUDIO_UNIQUE_ID_USE_CLIENT);
+    return id != AUDIO_UNIQUE_ID_ALLOCATE ? id : RECORD_RIID_INVALID;
 }
 
 static jint
@@ -470,9 +479,10 @@
 
     env->CallStaticVoidMethod(clazz,
                               gAudioPolicyEventHandlerMethods.postRecordConfigEventFromNative,
-                              event, (jint) clientInfo->uid, clientInfo->session,
-                              clientInfo->source, clientInfo->port_id, clientInfo->silenced,
-                              recParamArray, jClientEffects, jEffects, source);
+                              event, (jint) clientInfo->riid, (jint) clientInfo->uid,
+                              clientInfo->session, clientInfo->source, clientInfo->port_id,
+                              clientInfo->silenced, recParamArray, jClientEffects, jEffects,
+                              source);
     env->DeleteLocalRef(clazz);
     env->DeleteLocalRef(recParamArray);
     env->DeleteLocalRef(jClientEffects);
@@ -2246,6 +2256,7 @@
     {"isSourceActive",      "(I)Z",     (void *)android_media_AudioSystem_isSourceActive},
     {"newAudioSessionId",   "()I",      (void *)android_media_AudioSystem_newAudioSessionId},
     {"newAudioPlayerId",    "()I",      (void *)android_media_AudioSystem_newAudioPlayerId},
+    {"newAudioRecorderId",  "()I",      (void *)android_media_AudioSystem_newAudioRecorderId},
     {"setDeviceConnectionState", "(IILjava/lang/String;Ljava/lang/String;I)I", (void *)android_media_AudioSystem_setDeviceConnectionState},
     {"getDeviceConnectionState", "(ILjava/lang/String;)I",  (void *)android_media_AudioSystem_getDeviceConnectionState},
     {"handleDeviceConfigChange", "(ILjava/lang/String;Ljava/lang/String;I)I", (void *)android_media_AudioSystem_handleDeviceConfigChange},
@@ -2440,7 +2451,7 @@
                     "dynamicPolicyCallbackFromNative", "(ILjava/lang/String;I)V");
     gAudioPolicyEventHandlerMethods.postRecordConfigEventFromNative =
             GetStaticMethodIDOrDie(env, env->FindClass(kClassPathName),
-                    "recordingCallbackFromNative", "(IIIIIZ[I[Landroid/media/audiofx/AudioEffect$Descriptor;[Landroid/media/audiofx/AudioEffect$Descriptor;I)V");
+                    "recordingCallbackFromNative", "(IIIIIIZ[I[Landroid/media/audiofx/AudioEffect$Descriptor;[Landroid/media/audiofx/AudioEffect$Descriptor;I)V");
 
     jclass audioMixClass = FindClassOrDie(env, "android/media/audiopolicy/AudioMix");
     gAudioMixClass = MakeGlobalRefOrDie(env, audioMixClass);
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index f8e43437..bcc57d2 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -3766,9 +3766,24 @@
      * with frameworks/av/include/media/AudioPolicy.h
      */
     /** @hide */
-    public final static int RECORD_CONFIG_EVENT_START = 1;
+    public static final int RECORD_CONFIG_EVENT_NONE = -1;
     /** @hide */
-    public final static int RECORD_CONFIG_EVENT_STOP = 0;
+    public static final int RECORD_CONFIG_EVENT_START = 0;
+    /** @hide */
+    public static final int RECORD_CONFIG_EVENT_STOP = 1;
+    /** @hide */
+    public static final int RECORD_CONFIG_EVENT_UPDATE = 2;
+    /** @hide */
+    public static final int RECORD_CONFIG_EVENT_DEATH = 3;
+    /**
+     * keep in sync with frameworks/native/include/audiomanager/AudioManager.h
+     */
+    /** @hide */
+    public static final int RECORD_RIID_INVALID = -1;
+    /** @hide */
+    public static final int RECORDER_STATE_STARTED = 0;
+    /** @hide */
+    public static final int RECORDER_STATE_STOPPED = 1;
 
     /**
      * All operations on this list are sync'd on mRecordCallbackLock.
diff --git a/media/java/android/media/AudioRecordingConfiguration.java b/media/java/android/media/AudioRecordingConfiguration.java
index 82bcbc1..74e6618 100644
--- a/media/java/android/media/AudioRecordingConfiguration.java
+++ b/media/java/android/media/AudioRecordingConfiguration.java
@@ -157,7 +157,7 @@
         return new AudioRecordingConfiguration( /*anonymized uid*/ -1,
                 in.mClientSessionId, in.mClientSource, in.mClientFormat,
                 in.mDeviceFormat, in.mPatchHandle, "" /*empty package name*/,
-                in.mClientPortId, in.mClientSilenced, in.mDeviceSource, in.mClientEffects,
+                /*anonymized portId*/ -1, in.mClientSilenced, in.mDeviceSource, in.mClientEffects,
                 in.mDeviceEffects);
     }
 
@@ -270,11 +270,12 @@
     }
 
     /**
+     * @hide
      * Returns the system unique ID assigned for the AudioRecord object corresponding to this
      * AudioRecordingConfiguration client.
      * @return the port ID.
      */
-    int getClientPortId() {
+    public int getClientPortId() {
         return mClientPortId;
     }
 
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index d105fa3..987db8b 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -220,6 +220,11 @@
      */
     public static native int newAudioPlayerId();
 
+    /**
+     * Returns a new unused audio recorder ID
+     */
+    public static native int newAudioRecorderId();
+
 
     /*
      * Sets a group generic audio configuration parameters. The use of these parameters
@@ -347,6 +352,7 @@
         /**
          * Callback for recording activity notifications events
          * @param event
+         * @param riid recording identifier
          * @param uid uid of the client app performing the recording
          * @param session
          * @param source
@@ -361,7 +367,7 @@
          *          6: patch handle
          * @param packName package name of the client app performing the recording. NOT SUPPORTED
          */
-        void onRecordingConfigurationChanged(int event, int uid, int session, int source,
+        void onRecordingConfigurationChanged(int event, int riid, int uid, int session, int source,
                         int portId, boolean silenced, int[] recordingFormat,
                         AudioEffect.Descriptor[] clienteffects, AudioEffect.Descriptor[] effects,
                         int activeSource, String packName);
@@ -379,16 +385,23 @@
     /**
      * Callback from native for recording configuration updates.
      * @param event
+     * @param riid
+     * @param uid
      * @param session
      * @param source
+     * @param portId
+     * @param silenced
      * @param recordingFormat see
-     *     {@link AudioRecordingCallback#onRecordingConfigurationChanged(int, int, int, int, int,\
-     boolean, int[], AudioEffect.Descriptor[], AudioEffect.Descriptor[], int, String)}
+     *     {@link AudioRecordingCallback#onRecordingConfigurationChanged(int, int, int, int, int, \
+     int, boolean, int[], AudioEffect.Descriptor[], AudioEffect.Descriptor[], int, String)}
      *     for the description of the record format.
+     * @param cleintEffects
+     * @param effects
+     * @param activeSource
      */
     @UnsupportedAppUsage
-    private static void recordingCallbackFromNative(int event, int uid, int session, int source,
-                          int portId, boolean silenced, int[] recordingFormat,
+    private static void recordingCallbackFromNative(int event, int riid, int uid, int session,
+                          int source, int portId, boolean silenced, int[] recordingFormat,
                           AudioEffect.Descriptor[] clientEffects, AudioEffect.Descriptor[] effects,
                           int activeSource) {
         AudioRecordingCallback cb = null;
@@ -401,7 +414,7 @@
 
         if (cb != null) {
             // TODO receive package name from native
-            cb.onRecordingConfigurationChanged(event, uid, session, source, portId, silenced,
+            cb.onRecordingConfigurationChanged(event, riid, uid, session, source, portId, silenced,
                                         recordingFormat, clientEffects, effects, activeSource, "");
         }
     }
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 85de00a..81ddcdb4 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -60,6 +60,10 @@
 
     oneway void releasePlayer(in int piid);
 
+    int trackRecorder(in IBinder recorder);
+
+    oneway void recorderEvent(in int riid, in int event);
+
     // Java-only methods below.
 
     oneway void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
diff --git a/packages/SettingsLib/BarChartPreference/src/com/android/settingslib/widget/BarView.java b/packages/SettingsLib/BarChartPreference/src/com/android/settingslib/widget/BarView.java
index fccb719..93a34c0 100644
--- a/packages/SettingsLib/BarChartPreference/src/com/android/settingslib/widget/BarView.java
+++ b/packages/SettingsLib/BarChartPreference/src/com/android/settingslib/widget/BarView.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.content.res.TypedArray;
+import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.LayoutInflater;
@@ -73,7 +74,12 @@
         mIcon.setImageDrawable(barViewInfo.getIcon());
         mBarTitle.setText(barViewInfo.getTitle());
         mBarSummary.setText(barViewInfo.getSummary());
-        mIcon.setContentDescription(barViewInfo.getContentDescription());
+
+        final CharSequence barViewInfoContent = barViewInfo.getContentDescription();
+        if (!TextUtils.isEmpty(barViewInfoContent)
+                && !TextUtils.equals((barViewInfo.getTitle()), barViewInfoContent)) {
+            mIcon.setContentDescription(barViewInfo.getContentDescription());
+        }
     }
 
     @VisibleForTesting
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/Estimate.kt b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/Estimate.kt
index ae8e1e2..786139f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/Estimate.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/Estimate.kt
@@ -45,7 +45,7 @@
                     Settings.Global.getLong(
                             resolver, Settings.Global.BATTERY_ESTIMATES_LAST_UPDATE_TIME, -1))
             return if (Duration.between(lastUpdateTime,
-                            Instant.now()).compareTo(Duration.ofMinutes(2)) > 0) {
+                            Instant.now()).compareTo(Duration.ofMinutes(1)) > 0) {
                 null
             } else Estimate(
                     Settings.Global.getLong(resolver,
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
index 9dd5bb4..bd7b3d5 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
@@ -105,7 +105,8 @@
                 }
                 t.setEarlyWakeup();
                 t.apply();
-                mApplyHandler.sendEmptyMessage(toApplySeqNo);
+                Message.obtain(mApplyHandler, MSG_UPDATE_SEQUENCE_NUMBER, toApplySeqNo, 0)
+                        .sendToTarget();
             }
         });
 
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
index 135b351..07c2f10 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
@@ -289,12 +289,19 @@
         public void onPluginConnected(ClockPlugin plugin, Context pluginContext) {
             addClockPlugin(plugin);
             reload();
+            if (plugin == mCurrentClock) {
+                ClockManager.this.reload();
+            }
         }
 
         @Override
         public void onPluginDisconnected(ClockPlugin plugin) {
+            boolean isCurrentClock = plugin == mCurrentClock;
             removeClockPlugin(plugin);
             reload();
+            if (isCurrentClock) {
+                ClockManager.this.reload();
+            }
         }
 
         /**
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index 6c1d1f9..6832ee3 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -36,6 +36,7 @@
 import android.net.Uri;
 import android.os.Handler;
 import android.provider.Settings;
+import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.util.TypedValue;
@@ -319,6 +320,9 @@
         mUser = ActivityManager.getCurrentUser();
         getContext().getContentResolver().registerContentObserver(
                 Settings.System.getUriFor(SHOW_BATTERY_PERCENT), false, mSettingObserver, mUser);
+        getContext().getContentResolver().registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.BATTERY_ESTIMATES_LAST_UPDATE_TIME),
+                false, mSettingObserver);
         updateShowPercent();
         subscribeForTunerUpdates();
         mUserTracker.startTracking();
@@ -493,6 +497,11 @@
         public void onChange(boolean selfChange, Uri uri) {
             super.onChange(selfChange, uri);
             updateShowPercent();
+            if (TextUtils.equals(uri.getLastPathSegment(),
+                    Settings.Global.BATTERY_ESTIMATES_LAST_UPDATE_TIME)) {
+                // update the text for sure if the estimate in the cache was updated
+                updatePercentText();
+            }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index ed59f79..9490b14 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -306,7 +306,8 @@
             return false;
         }
         boolean exceedsPriorityThreshold;
-        if (NotificationUtils.useNewInterruptionModel(mContext)) {
+        if (NotificationUtils.useNewInterruptionModel(mContext)
+                && hideSilentNotificationsOnLockscreen()) {
             exceedsPriorityThreshold = getEntryManager().getNotificationData().isHighPriority(sbn);
         } else {
             exceedsPriorityThreshold =
@@ -315,6 +316,11 @@
         return mShowLockscreenNotifications && exceedsPriorityThreshold;
     }
 
+    private boolean hideSilentNotificationsOnLockscreen() {
+        return Settings.Secure.getInt(mContext.getContentResolver(),
+                Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 0) == 0;
+    }
+
     private void setShowLockscreenNotifications(boolean show) {
         mShowLockscreenNotifications = show;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index fde1455..9c7a1e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -73,7 +73,6 @@
     private boolean mTestmode = false;
     private boolean mHasReceivedBattery = false;
     private Estimate mEstimate;
-    private long mLastEstimateTimestamp = -1;
     private boolean mFetchingEstimate = false;
 
     @Inject
@@ -203,13 +202,6 @@
 
     @Override
     public void getEstimatedTimeRemainingString(EstimateFetchCompletion completion) {
-        if (mEstimate != null
-                && mLastEstimateTimestamp > System.currentTimeMillis() - UPDATE_GRANULARITY_MSEC) {
-            String percentage = generateTimeRemainingString();
-            completion.onBatteryRemainingEstimateRetrieved(percentage);
-            return;
-        }
-
         // Need to fetch or refresh the estimate, but it may involve binder calls so offload the
         // work
         synchronized (mFetchCallbacks) {
@@ -238,8 +230,10 @@
         mFetchingEstimate = true;
         Dependency.get(Dependency.BG_HANDLER).post(() -> {
             // Only fetch the estimate if they are enabled
-            mEstimate = mEstimates.isHybridNotificationEnabled() ? mEstimates.getEstimate() : null;
-            mLastEstimateTimestamp = System.currentTimeMillis();
+            mEstimate = null;
+            if (mEstimates.isHybridNotificationEnabled()) {
+                updateEstimate();
+            }
             mFetchingEstimate = false;
             Dependency.get(Dependency.MAIN_HANDLER).post(this::notifyEstimateFetchCallbacks);
         });
@@ -258,8 +252,15 @@
     }
 
     private void updateEstimate() {
-        mEstimate = mEstimates.getEstimate();
-        mLastEstimateTimestamp = System.currentTimeMillis();
+        // if the estimate has been cached we can just use that, otherwise get a new one and
+        // throw it in the cache.
+        mEstimate = Estimate.getCachedEstimateIfAvailable(mContext);
+        if (mEstimate == null) {
+            mEstimate = mEstimates.getEstimate();
+            if (mEstimate != null) {
+                Estimate.storeCachedEstimate(mContext, mEstimate);
+            }
+        }
     }
 
     private void updatePowerSave() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 490b2ea..49a263a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -17,10 +17,13 @@
 package com.android.systemui.statusbar;
 
 import static android.content.Intent.ACTION_USER_SWITCHED;
+import static android.provider.Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL;
 
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -35,6 +38,7 @@
 import android.os.Looper;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
@@ -152,7 +156,34 @@
         assertTrue(mLockscreenUserManager.isLockscreenPublicMode(mCurrentUserId));
     }
 
-    private class TestNotificationLockscreenUserManager extends NotificationLockscreenUserManagerImpl {
+    @Test
+    public void testShowSilentNotifications_settingSaysShow() {
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                NOTIFICATION_NEW_INTERRUPTION_MODEL, 1);
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 1);
+        when(mNotificationData.isHighPriority(any())).thenReturn(false);
+
+        assertTrue(mLockscreenUserManager.shouldShowOnKeyguard(mock(StatusBarNotification.class)));
+    }
+
+    @Test
+    public void testShowSilentNotifications_settingSaysHide() {
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                NOTIFICATION_NEW_INTERRUPTION_MODEL, 1);
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 0);
+        when(mNotificationData.isHighPriority(any())).thenReturn(false);
+
+        assertFalse(mLockscreenUserManager.shouldShowOnKeyguard(mock(StatusBarNotification.class)));
+    }
+
+    private class TestNotificationLockscreenUserManager
+            extends NotificationLockscreenUserManagerImpl {
         public TestNotificationLockscreenUserManager(Context context) {
             super(context);
         }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index d3af8f07..e38defd 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -409,16 +409,16 @@
     private final AudioSystem.ErrorCallback mAudioSystemCallback = new AudioSystem.ErrorCallback() {
         public void onError(int error) {
             switch (error) {
-            case AudioSystem.AUDIO_STATUS_SERVER_DIED:
-                mRecordMonitor.clear();
+                case AudioSystem.AUDIO_STATUS_SERVER_DIED:
+                    mRecordMonitor.onAudioServerDied();
 
-                sendMsg(mAudioHandler, MSG_AUDIO_SERVER_DIED,
-                        SENDMSG_NOOP, 0, 0, null, 0);
-                sendMsg(mAudioHandler, MSG_DISPATCH_AUDIO_SERVER_STATE,
-                        SENDMSG_QUEUE, 0, 0, null, 0);
-                break;
-            default:
-                break;
+                    sendMsg(mAudioHandler, MSG_AUDIO_SERVER_DIED,
+                            SENDMSG_NOOP, 0, 0, null, 0);
+                    sendMsg(mAudioHandler, MSG_DISPATCH_AUDIO_SERVER_STATE,
+                            SENDMSG_QUEUE, 0, 0, null, 0);
+                    break;
+                default:
+                    break;
             }
         }
     };
@@ -6943,6 +6943,23 @@
         return mRecordMonitor.getActiveRecordingConfigurations(isPrivileged);
     }
 
+    //======================
+    // Audio recording state notification from clients
+    //======================
+    /**
+     * Track a recorder provided by the client
+     */
+    public int trackRecorder(IBinder recorder) {
+        return mRecordMonitor.trackRecorder(recorder);
+    }
+
+    /**
+     * Receive an event from the client about a tracked recorder
+     */
+    public void recorderEvent(int riid, int event) {
+        mRecordMonitor.recorderEvent(riid, event);
+    }
+
     public void disableRingtoneSync(final int userId) {
         final int callingUserId = UserHandle.getCallingUserId();
         if (callingUserId != userId) {
diff --git a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
index 87b272b..69d1ea7 100644
--- a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
@@ -33,7 +33,6 @@
 import java.text.DateFormat;
 import java.util.ArrayList;
 import java.util.Date;
-import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 
@@ -50,50 +49,148 @@
     // playback configurations that do not contain uid/package name information.
     private boolean mHasPublicClients = false;
 
-    private HashMap<Integer, AudioRecordingConfiguration> mRecordConfigs =
-            new HashMap<Integer, AudioRecordingConfiguration>();
+    static final class RecordingState {
+        private final int mRiid;
+        private final RecorderDeathHandler mDeathHandler;
+        private boolean mIsActive;
+        private AudioRecordingConfiguration mConfig;
+
+        RecordingState(int riid, RecorderDeathHandler handler) {
+            mRiid = riid;
+            mDeathHandler = handler;
+        }
+
+        RecordingState(AudioRecordingConfiguration config) {
+            mRiid = AudioManager.RECORD_RIID_INVALID;
+            mDeathHandler = null;
+            mConfig = config;
+        }
+
+        int getRiid() {
+            return mRiid;
+        }
+
+        int getPortId() {
+            return mConfig != null ? mConfig.getClientPortId() : -1;
+        }
+
+        AudioRecordingConfiguration getConfig() {
+            return mConfig;
+        }
+
+        boolean hasDeathHandler() {
+            return mDeathHandler != null;
+        }
+
+        boolean isActiveConfiguration() {
+            return mIsActive && mConfig != null;
+        }
+
+        // returns true if status of an active recording has changed
+        boolean setActive(boolean active) {
+            if (mIsActive == active) return false;
+            mIsActive = active;
+            return mConfig != null;
+        }
+
+        // returns true if an active recording has been updated
+        boolean setConfig(AudioRecordingConfiguration config) {
+            if (config.equals(mConfig)) return false;
+            mConfig = config;
+            return mIsActive;
+        }
+
+        void dump(PrintWriter pw) {
+            pw.println("riid " + mRiid + "; active? " + mIsActive);
+            if (mConfig != null) {
+                mConfig.dump(pw);
+            } else {
+                pw.println("  no config");
+            }
+        }
+    }
+    private List<RecordingState> mRecordStates = new ArrayList<RecordingState>();
 
     private final PackageManager mPackMan;
 
     RecordingActivityMonitor(Context ctxt) {
         RecMonitorClient.sMonitor = this;
+        RecorderDeathHandler.sMonitor = this;
         mPackMan = ctxt.getPackageManager();
     }
 
     /**
      * Implementation of android.media.AudioSystem.AudioRecordingCallback
      */
-    public void onRecordingConfigurationChanged(int event, int uid, int session, int source,
-                                                int portId, boolean silenced, int[] recordingInfo,
+    public void onRecordingConfigurationChanged(int event, int riid, int uid, int session,
+                                                int source, int portId, boolean silenced,
+                                                int[] recordingInfo,
                                                 AudioEffect.Descriptor[] clientEffects,
                                                 AudioEffect.Descriptor[] effects,
                                                 int activeSource, String packName) {
+        final AudioRecordingConfiguration config = createRecordingConfiguration(
+                uid, session, source, recordingInfo,
+                portId, silenced, activeSource, clientEffects, effects);
         if (MediaRecorder.isSystemOnlyAudioSource(source)) {
-            // still want to log event, it just won't appear in recording configurations
-            sEventLogger.log(new RecordingEvent(event, uid, session, source, packName)
-                    .printLog(TAG));
+            // still want to log event, it just won't appear in recording configurations;
+            sEventLogger.log(new RecordingEvent(event, riid, config).printLog(TAG));
             return;
         }
-        String clientEffectName =  clientEffects.length == 0 ? "None" : clientEffects[0].name;
-        String effectName =  effects.length == 0 ? "None" : effects[0].name;
-
-        final List<AudioRecordingConfiguration> configsSystem =
-                updateSnapshot(event, uid, session, source, recordingInfo,
-                portId, silenced, activeSource, clientEffects, effects);
-        if (configsSystem != null){
-            dispatchCallbacks(configsSystem);
-        }
+        dispatchCallbacks(updateSnapshot(event, riid, config));
     }
+
+    /**
+     * Track a recorder provided by the client
+     */
+    public int trackRecorder(IBinder recorder) {
+        if (recorder == null) {
+            Log.e(TAG, "trackRecorder called with null token");
+            return AudioManager.RECORD_RIID_INVALID;
+        }
+        final int newRiid = AudioSystem.newAudioRecorderId();
+        RecorderDeathHandler handler = new RecorderDeathHandler(newRiid, recorder);
+        if (!handler.init()) {
+            // probably means that the AudioRecord has already died
+            return AudioManager.RECORD_RIID_INVALID;
+        }
+        synchronized (mRecordStates) {
+            mRecordStates.add(new RecordingState(newRiid, handler));
+        }
+        // a newly added record is inactive, no change in active configs is possible.
+        return newRiid;
+    }
+
+    /**
+     * Receive an event from the client about a tracked recorder
+     */
+    public void recorderEvent(int riid, int event) {
+        int configEvent = event == AudioManager.RECORDER_STATE_STARTED
+                ? AudioManager.RECORD_CONFIG_EVENT_START :
+                event == AudioManager.RECORDER_STATE_STOPPED
+                ? AudioManager.RECORD_CONFIG_EVENT_STOP : AudioManager.RECORD_CONFIG_EVENT_NONE;
+        if (riid == AudioManager.RECORD_RIID_INVALID
+                || configEvent == AudioManager.RECORD_CONFIG_EVENT_NONE) {
+            sEventLogger.log(new RecordingEvent(event, riid, null).printLog(TAG));
+            return;
+        }
+        dispatchCallbacks(updateSnapshot(configEvent, riid, null));
+    }
+
+    void unregisterRecorder(int riid) {
+        dispatchCallbacks(updateSnapshot(AudioManager.RECORD_CONFIG_EVENT_DEATH, riid, null));
+    }
+
     private void dispatchCallbacks(List<AudioRecordingConfiguration> configs) {
+        if (configs == null) { // null means "no changes"
+            return;
+        }
         synchronized (mClients) {
             // list of recording configurations for "public consumption". It is only computed if
             // there are non-system recording activity listeners.
             final List<AudioRecordingConfiguration> configsPublic = mHasPublicClients
                     ? anonymizeForPublicConsumption(configs) :
                       new ArrayList<AudioRecordingConfiguration>();
-            final Iterator<RecMonitorClient> clientIterator = mClients.iterator();
-            while (clientIterator.hasNext()) {
-                final RecMonitorClient rmc = clientIterator.next();
+            for (RecMonitorClient rmc : mClients) {
                 try {
                     if (rmc.mIsPrivileged) {
                         rmc.mDispatcherCb.dispatchRecordingConfigChange(configs);
@@ -108,12 +205,12 @@
     }
 
     protected void dump(PrintWriter pw) {
-        // players
+        // recorders
         pw.println("\nRecordActivityMonitor dump time: "
                 + DateFormat.getTimeInstance().format(new Date()));
-        synchronized(mRecordConfigs) {
-            for (AudioRecordingConfiguration conf : mRecordConfigs.values()) {
-                conf.dump(pw);
+        synchronized (mRecordStates) {
+            for (RecordingState state : mRecordStates) {
+                state.dump(pw);
             }
         }
         pw.println("\n");
@@ -121,7 +218,7 @@
         sEventLogger.dump(pw);
     }
 
-    private ArrayList<AudioRecordingConfiguration> anonymizeForPublicConsumption(
+    private static ArrayList<AudioRecordingConfiguration> anonymizeForPublicConsumption(
             List<AudioRecordingConfiguration> sysConfigs) {
         ArrayList<AudioRecordingConfiguration> publicConfigs =
                 new ArrayList<AudioRecordingConfiguration>();
@@ -136,11 +233,30 @@
         AudioSystem.setRecordingCallback(this);
     }
 
-    void clear() {
-        synchronized (mRecordConfigs) {
-            mRecordConfigs.clear();
+    void onAudioServerDied() {
+        // Remove all RecordingState entries that do not have a death handler (that means
+        // they are tracked by the Audio Server). If there were active entries among removed,
+        // dispatch active configuration changes.
+        List<AudioRecordingConfiguration> configs = null;
+        synchronized (mRecordStates) {
+            boolean configChanged = false;
+            for (Iterator<RecordingState> it = mRecordStates.iterator(); it.hasNext(); ) {
+                RecordingState state = it.next();
+                if (!state.hasDeathHandler()) {
+                    if (state.isActiveConfiguration()) {
+                        configChanged = true;
+                        sEventLogger.log(new RecordingEvent(
+                                        AudioManager.RECORD_CONFIG_EVENT_DEATH,
+                                        state.getRiid(), state.getConfig()));
+                    }
+                    it.remove();
+                }
+            }
+            if (configChanged) {
+                configs = getActiveRecordingConfigurations(true /*isPrivileged*/);
+            }
         }
-        dispatchCallbacks(new ArrayList<AudioRecordingConfiguration>());
+        dispatchCallbacks(configs);
     }
 
     void registerRecordingCallback(IRecordingConfigDispatcher rcdb, boolean isPrivileged) {
@@ -181,21 +297,25 @@
     }
 
     List<AudioRecordingConfiguration> getActiveRecordingConfigurations(boolean isPrivileged) {
-        synchronized(mRecordConfigs) {
-            if (isPrivileged) {
-                return new ArrayList<AudioRecordingConfiguration>(mRecordConfigs.values());
-            } else {
-                final List<AudioRecordingConfiguration> configsPublic =
-                        anonymizeForPublicConsumption(
-                            new ArrayList<AudioRecordingConfiguration>(mRecordConfigs.values()));
-                return configsPublic;
+        List<AudioRecordingConfiguration> configs = new ArrayList<AudioRecordingConfiguration>();
+        synchronized (mRecordStates) {
+            for (RecordingState state : mRecordStates) {
+                if (state.isActiveConfiguration()) {
+                    configs.add(state.getConfig());
+                }
             }
         }
+        // AudioRecordingConfiguration objects never get updated. If config changes,
+        // the reference to the config is set in RecordingState.
+        if (!isPrivileged) {
+            configs = anonymizeForPublicConsumption(configs);
+        }
+        return configs;
     }
 
     /**
-     * Update the internal "view" of the active recording sessions
-     * @param event
+     * Create a recording configuration from the provided parameters
+     * @param uid
      * @param session
      * @param source
      * @param recordingFormat see
@@ -207,82 +327,133 @@
      * @param activeSource
      * @param clientEffects
      * @param effects
+     * @return null a configuration object.
+     */
+    private AudioRecordingConfiguration createRecordingConfiguration(int uid,
+            int session, int source, int[] recordingInfo, int portId, boolean silenced,
+            int activeSource, AudioEffect.Descriptor[] clientEffects,
+            AudioEffect.Descriptor[] effects) {
+        final AudioFormat clientFormat = new AudioFormat.Builder()
+                .setEncoding(recordingInfo[0])
+                // FIXME this doesn't support index-based masks
+                .setChannelMask(recordingInfo[1])
+                .setSampleRate(recordingInfo[2])
+                .build();
+        final AudioFormat deviceFormat = new AudioFormat.Builder()
+                .setEncoding(recordingInfo[3])
+                // FIXME this doesn't support index-based masks
+                .setChannelMask(recordingInfo[4])
+                .setSampleRate(recordingInfo[5])
+                .build();
+        final int patchHandle = recordingInfo[6];
+        final String[] packages = mPackMan.getPackagesForUid(uid);
+        final String packageName;
+        if (packages != null && packages.length > 0) {
+            packageName = packages[0];
+        } else {
+            packageName = "";
+        }
+        return new AudioRecordingConfiguration(uid, session, source,
+                clientFormat, deviceFormat, patchHandle, packageName,
+                portId, silenced, activeSource, clientEffects, effects);
+    }
+
+    /**
+     * Update the internal "view" of the active recording sessions
+     * @param event RECORD_CONFIG_EVENT_...
+     * @param riid
+     * @param config
      * @return null if the list of active recording sessions has not been modified, a list
      *     with the current active configurations otherwise.
      */
-    private List<AudioRecordingConfiguration> updateSnapshot(int event, int uid, int session,
-            int source, int[] recordingInfo, int portId, boolean silenced, int activeSource,
-            AudioEffect.Descriptor[] clientEffects, AudioEffect.Descriptor[] effects) {
-        final boolean configChanged;
-        final ArrayList<AudioRecordingConfiguration> configs;
-        synchronized(mRecordConfigs) {
-            switch (event) {
-            case AudioManager.RECORD_CONFIG_EVENT_STOP:
-                // return failure if an unknown recording session stopped
-                configChanged = (mRecordConfigs.remove(new Integer(portId)) != null);
-                if (configChanged) {
-                    sEventLogger.log(new RecordingEvent(event, uid, session, source, null));
-                }
-                break;
-            case AudioManager.RECORD_CONFIG_EVENT_START:
-                final AudioFormat clientFormat = new AudioFormat.Builder()
-                        .setEncoding(recordingInfo[0])
-                        // FIXME this doesn't support index-based masks
-                        .setChannelMask(recordingInfo[1])
-                        .setSampleRate(recordingInfo[2])
-                        .build();
-                final AudioFormat deviceFormat = new AudioFormat.Builder()
-                        .setEncoding(recordingInfo[3])
-                        // FIXME this doesn't support index-based masks
-                        .setChannelMask(recordingInfo[4])
-                        .setSampleRate(recordingInfo[5])
-                        .build();
-                final int patchHandle = recordingInfo[6];
-                final Integer portIdKey = new Integer(portId);
-
-                final String[] packages = mPackMan.getPackagesForUid(uid);
-                final String packageName;
-                if (packages != null && packages.length > 0) {
-                    packageName = packages[0];
+    private List<AudioRecordingConfiguration> updateSnapshot(
+            int event, int riid, AudioRecordingConfiguration config) {
+        List<AudioRecordingConfiguration> configs = null;
+        synchronized (mRecordStates) {
+            int stateIndex = -1;
+            if (riid != AudioManager.RECORD_RIID_INVALID) {
+                stateIndex = findStateByRiid(riid);
+            } else if (config != null) {
+                stateIndex = findStateByPortId(config.getClientPortId());
+            }
+            if (stateIndex == -1) {
+                if (event == AudioManager.RECORD_CONFIG_EVENT_START && config != null) {
+                    // First time registration for a recorder tracked by AudioServer.
+                    mRecordStates.add(new RecordingState(config));
+                    stateIndex = mRecordStates.size() - 1;
                 } else {
-                    packageName = "";
-                }
-                final AudioRecordingConfiguration updatedConfig =
-                        new AudioRecordingConfiguration(uid, session, source,
-                                clientFormat, deviceFormat, patchHandle, packageName,
-                                portId, silenced, activeSource, clientEffects, effects);
-
-                if (mRecordConfigs.containsKey(portIdKey)) {
-                    if (updatedConfig.equals(mRecordConfigs.get(portIdKey))) {
-                        configChanged = false;
-                    } else {
-                        // config exists but has been modified
-                        mRecordConfigs.remove(portIdKey);
-                        mRecordConfigs.put(portIdKey, updatedConfig);
-                        configChanged = true;
+                    if (config == null) {
+                        // Records tracked by clients must be registered first via trackRecorder.
+                        Log.e(TAG, String.format(
+                                        "Unexpected event %d for riid %d", event, riid));
                     }
-                } else {
-                    mRecordConfigs.put(portIdKey, updatedConfig);
-                    configChanged = true;
+                    return configs;
                 }
-                if (configChanged) {
-                    sEventLogger.log(new RecordingEvent(event, uid, session, source, packageName));
-                }
-                break;
-            default:
-                Log.e(TAG, String.format("Unknown event %d for session %d, source %d",
-                        event, session, source));
-                configChanged = false;
+            }
+            final RecordingState state = mRecordStates.get(stateIndex);
+
+            boolean configChanged;
+            switch (event) {
+                case AudioManager.RECORD_CONFIG_EVENT_START:
+                    configChanged = state.setActive(true);
+                    if (config != null) { // ??? Can remove ???
+                        configChanged = state.setConfig(config) || configChanged;
+                    }
+                    break;
+                case AudioManager.RECORD_CONFIG_EVENT_UPDATE:
+                    // For this event config != null
+                    configChanged = state.setConfig(config);
+                    break;
+                case AudioManager.RECORD_CONFIG_EVENT_STOP:
+                    configChanged = state.setActive(false);
+                    if (!state.hasDeathHandler()) {
+                        // A recorder tracked by AudioServer has to be removed now so it
+                        // does not leak. It will be re-registered if recording starts again.
+                        mRecordStates.remove(stateIndex);
+                    }
+                    break;
+                case AudioManager.RECORD_CONFIG_EVENT_DEATH:
+                    configChanged = state.isActiveConfiguration();
+                    mRecordStates.remove(stateIndex);
+                    break;
+                default:
+                    Log.e(TAG, String.format("Unknown event %d for riid %d / portid %d",
+                                    event, riid, state.getPortId()));
+                    configChanged = false;
             }
             if (configChanged) {
-                configs = new ArrayList<AudioRecordingConfiguration>(mRecordConfigs.values());
-            } else {
-                configs = null;
+                sEventLogger.log(new RecordingEvent(event, riid, state.getConfig()));
+                configs = getActiveRecordingConfigurations(true /*isPrivileged*/);
             }
         }
         return configs;
     }
 
+    // riid is assumed to be valid
+    private int findStateByRiid(int riid) {
+        synchronized (mRecordStates) {
+            for (int i = 0; i < mRecordStates.size(); i++) {
+                if (mRecordStates.get(i).getRiid() == riid) {
+                    return i;
+                }
+            }
+        }
+        return -1;
+    }
+
+    private int findStateByPortId(int portId) {
+        // Lookup by portId is unambiguous only for recordings managed by the Audio Server.
+        synchronized (mRecordStates) {
+            for (int i = 0; i < mRecordStates.size(); i++) {
+                if (!mRecordStates.get(i).hasDeathHandler()
+                        && mRecordStates.get(i).getPortId() == portId) {
+                    return i;
+                }
+            }
+        }
+        return -1;
+    }
+
     /**
      * Inner class to track clients that want to be notified of recording updates
      */
@@ -319,28 +490,80 @@
         }
     }
 
+    private static final class RecorderDeathHandler implements IBinder.DeathRecipient {
+
+        // can afford to be static because only one RecordingActivityMonitor ever instantiated
+        static RecordingActivityMonitor sMonitor;
+
+        final int mRiid;
+        private final IBinder mRecorderToken;
+
+        RecorderDeathHandler(int riid, IBinder recorderToken) {
+            mRiid = riid;
+            mRecorderToken = recorderToken;
+        }
+
+        public void binderDied() {
+            sMonitor.unregisterRecorder(mRiid);
+        }
+
+        boolean init() {
+            try {
+                mRecorderToken.linkToDeath(this, 0);
+                return true;
+            } catch (RemoteException e) {
+                Log.w(TAG, "Could not link to recorder death", e);
+                return false;
+            }
+        }
+    }
+
     /**
      * Inner class for recording event logging
      */
     private static final class RecordingEvent extends AudioEventLogger.Event {
         private final int mRecEvent;
+        private final int mRIId;
         private final int mClientUid;
         private final int mSession;
         private final int mSource;
         private final String mPackName;
 
-        RecordingEvent(int event, int uid, int session, int source, String packName) {
+        RecordingEvent(int event, int riid, AudioRecordingConfiguration config) {
             mRecEvent = event;
-            mClientUid = uid;
-            mSession = session;
-            mSource = source;
-            mPackName = packName;
+            mRIId = riid;
+            if (config != null) {
+                mClientUid = config.getClientUid();
+                mSession = config.getClientAudioSessionId();
+                mSource = config.getClientAudioSource();
+                mPackName = config.getClientPackageName();
+            } else {
+                mClientUid = -1;
+                mSession = -1;
+                mSource = -1;
+                mPackName = null;
+            }
+        }
+
+        private static String recordEventToString(int recEvent) {
+            switch (recEvent) {
+                case AudioManager.RECORD_CONFIG_EVENT_START:
+                    return "start";
+                case AudioManager.RECORD_CONFIG_EVENT_UPDATE:
+                    return "update";
+                case AudioManager.RECORD_CONFIG_EVENT_STOP:
+                    return "stop";
+                case AudioManager.RECORD_CONFIG_EVENT_DEATH:
+                    return "death";
+                default:
+                    return "unknown (" + recEvent + ")";
+            }
         }
 
         @Override
         public String eventToString() {
-            return new StringBuilder("rec ").append(
-                        mRecEvent == AudioManager.RECORD_CONFIG_EVENT_START ? "start" : "stop ")
+            return new StringBuilder("rec ").append(recordEventToString(mRecEvent))
+                    .append(" riid:").append(mRIId)
                     .append(" uid:").append(mClientUid)
                     .append(" session:").append(mSession)
                     .append(" src:").append(MediaRecorder.toLogFriendlyAudioSource(mSource))
@@ -349,5 +572,5 @@
     }
 
     private static final AudioEventLogger sEventLogger = new AudioEventLogger(50,
-            "recording activity as reported through AudioSystem.AudioRecordingCallback");
+            "recording activity received by AudioService");
 }
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
index 1c18771..c54bfc0 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
@@ -400,10 +400,7 @@
      * @throws IOException if there was an issue with local database update.
      */
     private void setGenerationId(int userId, int generationId) throws IOException {
-        long updatedRows = mDatabase.setPlatformKeyGenerationId(userId, generationId);
-        if (updatedRows < 0) {
-            throw new IOException("Failed to set the platform key in the local DB.");
-        }
+        mDatabase.setPlatformKeyGenerationId(userId, generationId);
     }
 
     /**
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
index 3f5ac8e..c739650 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
@@ -322,19 +322,18 @@
     /**
      * Sets the {@code generationId} of the platform key for user {@code userId}.
      *
-     * @return The primary key ID of the relation.
+     * @return The number of updated rows.
      */
     public long setPlatformKeyGenerationId(int userId, int generationId) {
         SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
         ContentValues values = new ContentValues();
         values.put(UserMetadataEntry.COLUMN_NAME_USER_ID, userId);
         values.put(UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID, generationId);
-        long result = db.replace(
-                UserMetadataEntry.TABLE_NAME, /*nullColumnHack=*/ null, values);
-        if (result != -1) {
-            invalidateKeysWithOldGenerationId(userId, generationId);
-        }
-        return result;
+        String selection = UserMetadataEntry.COLUMN_NAME_USER_ID + " = ?";
+        String[] selectionArguments = new String[] {String.valueOf(userId)};
+
+        ensureUserMetadataEntryExists(userId);
+        return db.update(UserMetadataEntry.TABLE_NAME, values, selection, selectionArguments);
     }
 
     /**
@@ -377,16 +376,19 @@
     /**
      * Sets the {@code serialNumber} for the user {@code userId}.
      *
-     * @return The primary key of the inserted row, or -1 if failed.
+     * @return The number of updated rows.
      */
     public long setUserSerialNumber(int userId, long serialNumber) {
         SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
         ContentValues values = new ContentValues();
         values.put(UserMetadataEntry.COLUMN_NAME_USER_ID, userId);
         values.put(UserMetadataEntry.COLUMN_NAME_USER_SERIAL_NUMBER, serialNumber);
-        long result = db.replace(
-                UserMetadataEntry.TABLE_NAME, /*nullColumnHack=*/ null, values);
-        return result;
+        String selection = UserMetadataEntry.COLUMN_NAME_USER_ID + " = ?";
+        String[] selectionArguments = new String[] {String.valueOf(userId)};
+
+        ensureUserMetadataEntryExists(userId);
+        return db.update(UserMetadataEntry.TABLE_NAME, values, selection, selectionArguments);
+
     }
 
     /**
@@ -1326,6 +1328,18 @@
     }
 
     /**
+     * Creates an empty row in the user metadata table if such a row doesn't exist for
+     * the given userId, so db.update will succeed.
+     */
+    private void ensureUserMetadataEntryExists(int userId) {
+        SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
+        ContentValues values = new ContentValues();
+        values.put(UserMetadataEntry.COLUMN_NAME_USER_ID, userId);
+        db.insertWithOnConflict(UserMetadataEntry.TABLE_NAME, /*nullColumnHack=*/ null,
+                values, SQLiteDatabase.CONFLICT_IGNORE);
+    }
+
+    /**
      * Closes all open connections to the database.
      */
     public void close() {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 8f1709e..c5a2068 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -546,9 +546,7 @@
 
     private DevicePolicyConstants mConstants;
 
-    private static boolean ENABLE_LOCK_GUARD = Build.IS_ENG
-            || true // STOPSHIP Remove it.
-            || (SystemProperties.getInt("debug.dpm.lock_guard", 0) == 1);
+    private static final boolean ENABLE_LOCK_GUARD = true;
 
     interface Stats {
         int LOCK_GUARD_GUARD = 0;
@@ -1379,7 +1377,7 @@
             }
         }
 
-        void readFromXml(XmlPullParser parser)
+        void readFromXml(XmlPullParser parser, boolean shouldOverridePolicies)
                 throws XmlPullParserException, IOException {
             int outerDepth = parser.getDepth();
             int type;
@@ -1390,7 +1388,10 @@
                 }
                 String tag = parser.getName();
                 if (TAG_POLICIES.equals(tag)) {
-                    info.readPoliciesFromXml(parser);
+                    if (shouldOverridePolicies) {
+                        Log.d(LOG_TAG, "Overriding device admin policies from XML.");
+                        info.readPoliciesFromXml(parser);
+                    }
                 } else if (TAG_PASSWORD_QUALITY.equals(tag)) {
                     minimumPasswordMetrics.quality = Integer.parseInt(
                             parser.getAttributeValue(null, ATTR_VALUE));
@@ -1518,9 +1519,8 @@
                     }
                 } else if (TAG_PARENT_ADMIN.equals(tag)) {
                     Preconditions.checkState(!isParent);
-
                     parentAdmin = new ActiveAdmin(info, /* parent */ true);
-                    parentAdmin.readFromXml(parser);
+                    parentAdmin.readFromXml(parser, shouldOverridePolicies);
                 } else if (TAG_ORGANIZATION_COLOR.equals(tag)) {
                     organizationColor = Integer.parseInt(
                             parser.getAttributeValue(null, ATTR_VALUE));
@@ -3326,8 +3326,10 @@
                                     + userHandle);
                         }
                         if (dai != null) {
+                            boolean shouldOverwritePolicies =
+                                    shouldOverwritePoliciesFromXml(dai.getComponent(), userHandle);
                             ActiveAdmin ap = new ActiveAdmin(dai, /* parent */ false);
-                            ap.readFromXml(parser);
+                            ap.readFromXml(parser, shouldOverwritePolicies);
                             policy.mAdminMap.put(ap.info.getComponent(), ap);
                         }
                     } catch (RuntimeException e) {
@@ -3437,6 +3439,14 @@
         }
     }
 
+    private boolean shouldOverwritePoliciesFromXml(
+            ComponentName deviceAdminComponent, int userHandle) {
+        // http://b/123415062: If DA, overwrite with the stored policies that were agreed by the
+        // user to prevent apps from sneaking additional policies into updates.
+        return !isProfileOwner(deviceAdminComponent, userHandle)
+                && !isDeviceOwner(deviceAdminComponent, userHandle);
+    }
+
     private void updateLockTaskPackagesLocked(List<String> packages, int userId) {
         long ident = mInjector.binderClearCallingIdentity();
         try {
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
index 932a769..bac8414 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
@@ -305,6 +305,32 @@
     }
 
     @Test
+    public void setUserSerialNumbers_keepsPlatformKeyGenerationId() {
+        int userId = 42;
+        int generationId = 110;
+        Long serialNumber = 10L;
+
+        mRecoverableKeyStoreDb.setPlatformKeyGenerationId(userId, generationId);
+        mRecoverableKeyStoreDb.setUserSerialNumber(userId, serialNumber);
+
+        assertEquals(generationId, mRecoverableKeyStoreDb.getPlatformKeyGenerationId(userId));
+    }
+
+    @Test
+    public void setPlatformKeyGenerationId_keepsUserSerialNumber() {
+        int userId = 42;
+        int generationId = 110;
+        Long serialNumber = 10L;
+
+        mRecoverableKeyStoreDb.setPlatformKeyGenerationId(userId, generationId);
+        mRecoverableKeyStoreDb.setUserSerialNumber(userId, serialNumber);
+        mRecoverableKeyStoreDb.setPlatformKeyGenerationId(userId, generationId + 1);
+
+        assertEquals(serialNumber, mRecoverableKeyStoreDb.getUserSerialNumbers().get(userId));
+    }
+
+
+    @Test
     public void removeUserFromAllTables_removesData() throws Exception {
         int userId = 12;
         int generationId = 24;
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 26745a7..2d8a280 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1202,7 +1202,7 @@
      * Override the SPN Display Condition 2 integer bits (lsb). B2, B1 is the last two bits of the
      * spn display condition coding.
      *
-     * The default value -1 mean this field is not config.
+     * The default value -1 mean this field is not set.
      *
      * B1 = 0: display of registered PLMN name not required when registered PLMN is either HPLMN
      * or a PLMN in the service provider PLMN list (see EF_SPDI).
@@ -1241,7 +1241,7 @@
 
     /**
      * Override the PNN - a string array of comma-separated alpha long and short names:
-     * "alpha_long1, alpha_short1".
+     * "alpha_long1,alpha_short1".
      *
      * Reference: 3GPP TS 31.102 v15.2.0 Section 4.2.58 EF_PNN.
      * @hide
@@ -1259,6 +1259,8 @@
 
     /**
      * Allow ERI rules to select a carrier name display string when using 3gpp2 access technologies.
+     * If this bit is not set, the carrier name display string will be selected from the carrier
+     * display name resolver which doesn't apply the ERI rules.
      *
      * @hide
      */