Merge "Add runtime_native_boot namespace"
diff --git a/api/current.txt b/api/current.txt
index ab18d72..9dec22e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -24142,8 +24142,10 @@
     field public static final int INFO_OUTPUT_FORMAT_CHANGED = -2; // 0xfffffffe
     field public static final int INFO_TRY_AGAIN_LATER = -1; // 0xffffffff
     field public static final String PARAMETER_KEY_HDR10_PLUS_INFO = "hdr10-plus-info";
+    field public static final String PARAMETER_KEY_OFFSET_TIME = "time-offset-us";
     field public static final String PARAMETER_KEY_REQUEST_SYNC_FRAME = "request-sync";
     field public static final String PARAMETER_KEY_SUSPEND = "drop-input-frames";
+    field public static final String PARAMETER_KEY_SUSPEND_TIME = "drop-start-time-us";
     field public static final String PARAMETER_KEY_VIDEO_BITRATE = "video-bitrate";
     field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT = 1; // 0x1
     field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING = 2; // 0x2
@@ -24988,6 +24990,7 @@
     field public static final String KEY_COLOR_STANDARD = "color-standard";
     field public static final String KEY_COLOR_TRANSFER = "color-transfer";
     field public static final String KEY_COMPLEXITY = "complexity";
+    field public static final String KEY_CREATE_INPUT_SURFACE_SUSPENDED = "create-input-buffers-suspended";
     field public static final String KEY_DURATION = "durationUs";
     field public static final String KEY_FLAC_COMPRESSION_LEVEL = "flac-compression-level";
     field public static final String KEY_FRAME_RATE = "frame-rate";
diff --git a/api/system-current.txt b/api/system-current.txt
index 45d5f63..4cb5eb0 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -6309,8 +6309,6 @@
 
   public abstract class ContentCaptureService extends android.app.Service {
     ctor public ContentCaptureService();
-    method @NonNull public final java.util.Set<android.content.ComponentName> getContentCaptureDisabledActivities();
-    method @NonNull public final java.util.Set<java.lang.String> getContentCaptureDisabledPackages();
     method public void onActivitySnapshot(@NonNull android.view.contentcapture.ContentCaptureSessionId, @NonNull android.service.contentcapture.SnapshotData);
     method public void onConnected();
     method public void onContentCaptureEvent(@NonNull android.view.contentcapture.ContentCaptureSessionId, @NonNull android.view.contentcapture.ContentCaptureEvent);
@@ -6319,9 +6317,7 @@
     method public void onDestroyContentCaptureSession(@NonNull android.view.contentcapture.ContentCaptureSessionId);
     method public void onDisconnected();
     method public void onUserDataRemovalRequest(@NonNull android.view.contentcapture.UserDataRemovalRequest);
-    method public final void setActivityContentCaptureEnabled(@NonNull android.content.ComponentName, boolean);
     method public final void setContentCaptureWhitelist(@Nullable java.util.List<java.lang.String>, @Nullable java.util.List<android.content.ComponentName>);
-    method public final void setPackageContentCaptureEnabled(@NonNull String, boolean);
     field public static final String SERVICE_INTERFACE = "android.service.contentcapture.ContentCaptureService";
   }
 
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 5f47e06..63f9b59 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -2144,30 +2144,24 @@
     }
     optional HardwareType hardware_type = 1;
 
-    /* hardware_location allows vendors to differentiate between multiple instances of
+   /**
+    * hardware_location allows vendors to differentiate between multiple instances of
     * the same hardware_type.  The specific locations are vendor defined integers,
     * referring to board-specific numbering schemes.
     */
     optional int32 hardware_location = 2;
 
-    /* failure_code is specific to the HardwareType of the failed hardware.
-     * It should use the enum values defined below.
+    /**
+     * failure_code is specific to the HardwareType of the failed hardware.
+     * It should use one of the enum values defined below.
      */
-    enum MicrophoneFailureCode {
-        MICROPHONE_FAILURE_COMPLETE = 0;
-    }
-    enum CodecFailureCode {
-        CODEC_FAILURE_COMPLETE = 0;
-    }
-    enum SpeakerFailureCode {
-        SPEAKER_FAILURE_COMPLETE = 0;
-        SPEAKER_FAILURE_HIGH_Z = 1;
-        SPEAKER_FAILURE_SHORT = 2;
-    }
-    enum FingerprintFailureCode {
-        FINGERPRINT_FAILURE_COMPLETE = 0;
-        FINGERPRINT_SENSOR_BROKEN = 1;
-        FINGERPRINT_TOO_MANY_DEAD_PIXELS = 2;
+    enum HardwareErrorCode {
+        UNKNOWN = 0;
+        COMPLETE = 1;
+        SPEAKER_HIGH_Z = 2;
+        SPEAKER_SHORT = 3;
+        FINGERPRINT_SENSOR_BROKEN = 4;
+        FINGERPRINT_TOO_MANY_DEAD_PIXELS = 5;
     }
     optional int32 failure_code = 3;
 }
diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java
index 020de7f..cc2e59a 100644
--- a/core/java/android/service/contentcapture/ContentCaptureService.java
+++ b/core/java/android/service/contentcapture/ContentCaptureService.java
@@ -48,9 +48,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.Collections;
 import java.util.List;
-import java.util.Set;
 
 /**
  * A service used to capture the content of the screen to provide contextual data in other areas of
@@ -166,10 +164,6 @@
     /**
      * Explicitly limits content capture to the given packages and activities.
      *
-     * <p>When the whitelist is set, it overrides the values passed to
-     * {@link #setActivityContentCaptureEnabled(ComponentName, boolean)}
-     * and {@link #setPackageContentCaptureEnabled(String, boolean)}.
-     *
      * <p>To reset the whitelist, call it passing {@code null} to both arguments.
      *
      * <p>Useful when the service wants to restrict content capture to a category of apps, like
@@ -194,76 +188,6 @@
     }
 
     /**
-     * Defines whether content capture should be enabled for activities with such
-     * {@link android.content.ComponentName}.
-     *
-     * <p>Useful to blacklist a particular activity.
-     */
-    public final void setActivityContentCaptureEnabled(@NonNull ComponentName activity,
-            boolean enabled) {
-        final IContentCaptureServiceCallback callback = mCallback;
-        if (callback == null) {
-            Log.w(TAG, "setActivityContentCaptureEnabled(): no server callback");
-            return;
-        }
-        try {
-            callback.setActivityContentCaptureEnabled(activity, enabled);
-        } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Defines whether content capture should be enabled for activities of the app with such
-     * {@code packageName}.
-     *
-     * <p>Useful to blacklist any activity from a particular app.
-     */
-    public final void setPackageContentCaptureEnabled(@NonNull String packageName,
-            boolean enabled) {
-        final IContentCaptureServiceCallback callback = mCallback;
-        if (callback == null) {
-            Log.w(TAG, "setPackageContentCaptureEnabled(): no server callback");
-            return;
-        }
-        try {
-            callback.setPackageContentCaptureEnabled(packageName, enabled);
-        } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Gets the activities where content capture was disabled by
-     * {@link #setActivityContentCaptureEnabled(ComponentName, boolean)}.
-     */
-    @NonNull
-    public final Set<ComponentName> getContentCaptureDisabledActivities() {
-        final IContentCaptureServiceCallback callback = mCallback;
-        if (callback == null) {
-            Log.w(TAG, "getContentCaptureDisabledActivities(): no server callback");
-            return Collections.emptySet();
-        }
-        //TODO(b/122595322): implement (using SyncResultReceiver)
-        return null;
-    }
-
-    /**
-     * Gets the apps where content capture was disabled by
-     * {@link #setPackageContentCaptureEnabled(String, boolean)}.
-     */
-    @NonNull
-    public final Set<String> getContentCaptureDisabledPackages() {
-        final IContentCaptureServiceCallback callback = mCallback;
-        if (callback == null) {
-            Log.w(TAG, "getContentCaptureDisabledPackages(): no server callback");
-            return Collections.emptySet();
-        }
-        //TODO(b/122595322): implement (using SyncResultReceiver)
-        return null;
-    }
-
-    /**
      * Called when the Android system connects to service.
      *
      * <p>You should generally do initialization here rather than in {@link #onCreate}.
diff --git a/core/java/android/service/contentcapture/IContentCaptureServiceCallback.aidl b/core/java/android/service/contentcapture/IContentCaptureServiceCallback.aidl
index e84bd6f..2a729b6 100644
--- a/core/java/android/service/contentcapture/IContentCaptureServiceCallback.aidl
+++ b/core/java/android/service/contentcapture/IContentCaptureServiceCallback.aidl
@@ -28,8 +28,4 @@
  */
 oneway interface IContentCaptureServiceCallback {
     void setContentCaptureWhitelist(in List<String> packages, in List<ComponentName> activities);
-    void setActivityContentCaptureEnabled(in ComponentName activity, boolean enabled);
-    void setPackageContentCaptureEnabled(in String packageName, boolean enabled);
-    void getContentCaptureDisabledActivities(in IResultReceiver receiver);
-    void getContentCaptureDisabledPackages(in IResultReceiver receiver);
 }
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index ecbec65..cd5207c 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -492,7 +492,7 @@
         if (mBackgroundControl == null) {
             return;
         }
-        if ((mSurfaceFlags & PixelFormat.OPAQUE) != 0) {
+        if ((mSurfaceFlags & SurfaceControl.OPAQUE) != 0) {
             mBackgroundControl.show();
             mBackgroundControl.setLayer(Integer.MIN_VALUE);
         } else {
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
index c256d57..eef40e1 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -341,10 +341,10 @@
     // the proper ordering of these system-wide.
     // =======================================================================================
 
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     private static INotificationManager sService;
 
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     static private INotificationManager getService() {
         if (sService != null) {
             return sService;
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index e7a1c49..345058b 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -276,6 +276,7 @@
         "libmediametrics",
         "libmeminfo",
         "libaudioclient",
+        "libaudiopolicy",
         "libjpeg",
         "libusbhost",
         "libharfbuzz_ng",
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 080c5d5..18f0cae 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -25,6 +25,7 @@
 import android.annotation.WorkerThread;
 import android.content.res.ResourcesImpl;
 import android.hardware.HardwareBuffer;
+import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.StrictMode;
@@ -77,7 +78,7 @@
      */
     private boolean mRequestPremultiplied;
 
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769491)
     private byte[] mNinePatchChunk; // may be null
     @UnsupportedAppUsage
     private NinePatch.InsetStruct mNinePatchInsets; // may be null
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index d945635..b67aea2 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -142,8 +142,10 @@
 void SkiaRecordingCanvas::drawWebViewFunctor(int functor) {
     FunctorDrawable* functorDrawable;
     if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) {
+        // TODO(cblume) use VkFunctorDrawable instead of VkInteropFunctorDrawable here when the
+        // interop is disabled.
         functorDrawable =
-                mDisplayList->allocateDrawable<VkFunctorDrawable>(functor, asSkCanvas());
+                mDisplayList->allocateDrawable<VkInteropFunctorDrawable>(functor, asSkCanvas());
     } else {
         functorDrawable = mDisplayList->allocateDrawable<GLFunctorDrawable>(functor, asSkCanvas());
     }
diff --git a/media/Android.bp b/media/Android.bp
index 1aefebe..141d415c 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -102,7 +102,8 @@
     "--hide MissingPermission --hide BroadcastBehavior " +
     "--hide HiddenSuperclass --hide DeprecationMismatch --hide UnavailableSymbol " +
     "--hide SdkConstant --hide HiddenTypeParameter --hide Todo --hide Typo " +
-    "--hide HiddenTypedefConstant "
+    "--hide HiddenTypedefConstant --show-annotation android.annotation.SystemApi " +
+    " --show-annotation android.annotation.TestApi "
 
 droidstubs {
     name: "updatable-media-stubs",
@@ -110,8 +111,7 @@
         ":updatable-media-srcs-without-aidls",
         ":framework-media-annotation-srcs",
     ],
-    args: metalava_updatable_media_args + " --show-annotation android.annotation.SystemApi " +
-        " --show-annotation android.annotation.TestApi ",
+    args: metalava_updatable_media_args,
     // Ideally, sdk_version here should be "current_system", but "current - 1" is used
     // to avoid dependency cycle with framework.
     sdk_version: "28",
diff --git a/media/java/android/media/AudioRecordingConfiguration.java b/media/java/android/media/AudioRecordingConfiguration.java
index 1d763ce..0af47e8 100644
--- a/media/java/android/media/AudioRecordingConfiguration.java
+++ b/media/java/android/media/AudioRecordingConfiguration.java
@@ -402,7 +402,7 @@
                 && (mClientPortId == that.mClientPortId)
                 && (mClientSilenced == that.mClientSilenced)
                 && (mDeviceSource == that.mDeviceSource)
-                && (mClientEffects.equals(that.mClientEffects))
-                && (mDeviceEffects.equals(that.mDeviceEffects)));
+                && (Arrays.equals(mClientEffects, that.mClientEffects))
+                && (Arrays.equals(mDeviceEffects, that.mDeviceEffects)));
     }
 }
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 0c3d625..c6c2fdd 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -3373,6 +3373,8 @@
     /**
      * Change a video encoder's target bitrate on the fly. The value is an
      * Integer object containing the new bitrate in bps.
+     *
+     * @see #setParameters(Bundle)
      */
     public static final String PARAMETER_KEY_VIDEO_BITRATE = "video-bitrate";
 
@@ -3384,12 +3386,43 @@
      * input-side of the encoder in that case.
      * The value is an Integer object containing the value 1 to suspend
      * or the value 0 to resume.
+     *
+     * @see #setParameters(Bundle)
      */
     public static final String PARAMETER_KEY_SUSPEND = "drop-input-frames";
 
     /**
+     * When {@link #PARAMETER_KEY_SUSPEND} is present, the client can also
+     * optionally use this key to specify the timestamp (in micro-second)
+     * at which the suspend/resume operation takes effect.
+     *
+     * Note that the specified timestamp must be greater than or equal to the
+     * timestamp of any previously queued suspend/resume operations.
+     *
+     * The value is a long int, indicating the timestamp to suspend/resume.
+     *
+     * @see #setParameters(Bundle)
+     */
+    public static final String PARAMETER_KEY_SUSPEND_TIME = "drop-start-time-us";
+
+    /**
+     * Specify an offset (in micro-second) to be added on top of the timestamps
+     * onward. A typical use case is to apply an adjust to the timestamps after
+     * a period of pause by the user.
+     *
+     * This parameter can only be used on an encoder in "surface-input" mode.
+     *
+     * The value is a long int, indicating the timestamp offset to be applied.
+     *
+     * @see #setParameters(Bundle)
+     */
+    public static final String PARAMETER_KEY_OFFSET_TIME = "time-offset-us";
+
+    /**
      * Request that the encoder produce a sync frame "soon".
      * Provide an Integer with the value 0.
+     *
+     * @see #setParameters(Bundle)
      */
     public static final String PARAMETER_KEY_REQUEST_SYNC_FRAME = "request-sync";
 
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index c82b5f6..4ca0216 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -463,6 +463,63 @@
         = "repeat-previous-frame-after";
 
     /**
+     * Instruct the video encoder in "surface-input" mode to drop excessive
+     * frames from the source, so that the input frame rate to the encoder
+     * does not exceed the specified fps.
+     *
+     * The associated value is a float, representing the max frame rate to
+     * feed the encoder at.
+     *
+     * @hide
+     */
+    public static final String KEY_MAX_FPS_TO_ENCODER
+        = "max-fps-to-encoder";
+
+    /**
+     * Instruct the video encoder in "surface-input" mode to limit the gap of
+     * timestamp between any two adjacent frames fed to the encoder to the
+     * specified amount (in micro-second).
+     *
+     * The associated value is a long int. When positive, it represents the max
+     * timestamp gap between two adjacent frames fed to the encoder. When negative,
+     * the absolute value represents a fixed timestamp gap between any two adjacent
+     * frames fed to the encoder. Note that this will also apply even when the
+     * original timestamp goes backward in time. Under normal conditions, such frames
+     * would be dropped and not sent to the encoder.
+     *
+     * The output timestamp will be restored to the original timestamp and will
+     * not be affected.
+     *
+     * This is used in some special scenarios where input frames arrive sparingly
+     * but it's undesirable to allocate more bits to any single frame, or when it's
+     * important to ensure all frames are captured (rather than captured in the
+     * correct order).
+     *
+     * @hide
+     */
+    public static final String KEY_MAX_PTS_GAP_TO_ENCODER
+        = "max-pts-gap-to-encoder";
+
+    /**
+     * If specified when configuring a video encoder that's in "surface-input"
+     * mode, it will instruct the encoder to put the surface source in suspended
+     * state when it's connected. No video frames will be accepted until a resume
+     * operation (see {@link MediaCodec#PARAMETER_KEY_SUSPEND}), optionally with
+     * timestamp specified via {@link MediaCodec#PARAMETER_KEY_SUSPEND_TIME}, is
+     * received.
+     *
+     * The value is an integer, with 1 indicating to create with the surface
+     * source suspended, or 0 otherwise. The default value is 0.
+     *
+     * If this key is not set or set to 0, the surface source will accept buffers
+     * as soon as it's connected to the encoder (although they may not be encoded
+     * immediately). This key can be used when the client wants to prepare the
+     * encoder session in advance, but do not want to accept buffers immediately.
+     */
+    public static final String KEY_CREATE_INPUT_SURFACE_SUSPENDED
+        = "create-input-buffers-suspended";
+
+    /**
      * If specified when configuring a video decoder rendering to a surface,
      * causes the decoder to output "blank", i.e. black frames to the surface
      * when stopped to clear out any previously displayed contents.
diff --git a/media/jni/soundpool/SoundPool.cpp b/media/jni/soundpool/SoundPool.cpp
index 5f51320..e08dab4 100644
--- a/media/jni/soundpool/SoundPool.cpp
+++ b/media/jni/soundpool/SoundPool.cpp
@@ -26,7 +26,6 @@
 #include <media/AudioTrack.h>
 #include "SoundPool.h"
 #include "SoundPoolThread.h"
-#include <media/AudioPolicyHelper.h>
 #include <media/NdkMediaCodec.h>
 #include <media/NdkMediaExtractor.h>
 #include <media/NdkMediaFormat.h>
@@ -746,7 +745,8 @@
         // initialize track
         size_t afFrameCount;
         uint32_t afSampleRate;
-        audio_stream_type_t streamType = audio_attributes_to_stream_type(mSoundPool->attributes());
+        audio_stream_type_t streamType =
+                AudioSystem::attributesToStreamType(*mSoundPool->attributes());
         if (AudioSystem::getOutputFrameCount(&afFrameCount, streamType) != NO_ERROR) {
             afFrameCount = kDefaultFrameCount;
         }
diff --git a/native/webview/plat_support/draw_functor.cpp b/native/webview/plat_support/draw_functor.cpp
index e43a60c..6deb47f 100644
--- a/native/webview/plat_support/draw_functor.cpp
+++ b/native/webview/plat_support/draw_functor.cpp
@@ -177,6 +177,9 @@
         webview_functor_callbacks.vk.initialize = &initializeVk;
         webview_functor_callbacks.vk.draw = &drawVk;
         webview_functor_callbacks.vk.postDraw = &postDrawVk;
+        // TODO(boliu): Remove this once SkiaRecordingCanvas::drawWebViewFunctor
+        // no longer uses GL interop.
+        webview_functor_callbacks.gles.draw = &draw_gl;
         break;
     }
     callbacks_initialized = true;
diff --git a/packages/ExtServices/src/android/ext/services/notification/Assistant.java b/packages/ExtServices/src/android/ext/services/notification/Assistant.java
index b7d8492..b6b229c 100644
--- a/packages/ExtServices/src/android/ext/services/notification/Assistant.java
+++ b/packages/ExtServices/src/android/ext/services/notification/Assistant.java
@@ -105,6 +105,7 @@
     protected AssistantSettings.Factory mSettingsFactory = AssistantSettings.FACTORY;
     @VisibleForTesting
     protected AssistantSettings mSettings;
+    private SmsHelper mSmsHelper;
 
     public Assistant() {
     }
@@ -122,6 +123,18 @@
         mAgingHelper = new AgingHelper(getContext(),
                 mNotificationCategorizer,
                 new AgingCallback());
+        mSmsHelper = new SmsHelper(this);
+        mSmsHelper.initialize();
+    }
+
+    @Override
+    public void onDestroy() {
+        // This null check is only for the unit tests as ServiceTestCase.tearDown calls onDestroy
+        // without having first called onCreate.
+        if (mSmsHelper != null) {
+            mSmsHelper.destroy();
+        }
+        super.onDestroy();
     }
 
     private void loadFile() {
@@ -215,7 +228,7 @@
             return null;
         }
         NotificationEntry entry =
-                new NotificationEntry(mPackageManager, sbn, channel, SmsHelper.getInstance(this));
+                new NotificationEntry(mPackageManager, sbn, channel, mSmsHelper);
         SmartActionsHelper.SmartSuggestions suggestions = mSmartActionsHelper.suggest(entry);
         return createEnqueuedNotificationAdjustment(
                 entry, suggestions.actions, suggestions.replies);
@@ -262,7 +275,7 @@
             Ranking ranking = getRanking(sbn.getKey(), rankingMap);
             if (ranking != null && ranking.getChannel() != null) {
                 NotificationEntry entry = new NotificationEntry(mPackageManager,
-                        sbn, ranking.getChannel(), SmsHelper.getInstance(this));
+                        sbn, ranking.getChannel(), mSmsHelper);
                 String key = getKey(
                         sbn.getPackageName(), sbn.getUserId(), ranking.getChannel().getId());
                 ChannelImpressions ci = mkeyToImpressions.getOrDefault(key,
@@ -398,7 +411,6 @@
     @Override
     public void onListenerConnected() {
         if (DEBUG) Log.i(TAG, "CONNECTED");
-        SmsHelper.getInstance(this).initialize();
         try {
             mFile = new AtomicFile(new File(new File(
                     Environment.getDataUserCePackageDirectory(
@@ -415,7 +427,6 @@
 
     @Override
     public void onListenerDisconnected() {
-        SmsHelper.getInstance(this).destroy();
         if (mAgingHelper != null) {
             mAgingHelper.onDestroy();
         }
diff --git a/packages/ExtServices/src/android/ext/services/notification/SmsHelper.java b/packages/ExtServices/src/android/ext/services/notification/SmsHelper.java
index b077138..07be0b8 100644
--- a/packages/ExtServices/src/android/ext/services/notification/SmsHelper.java
+++ b/packages/ExtServices/src/android/ext/services/notification/SmsHelper.java
@@ -33,23 +33,11 @@
 public class SmsHelper {
     private static final String TAG = "SmsHelper";
 
-    private static SmsHelper sSmsHelper;
-    private static final Object sLock = new Object();
-
     private final Context mContext;
     private ComponentName mDefaultSmsApplication;
     private BroadcastReceiver mBroadcastReceiver;
 
-    static SmsHelper getInstance(Context context) {
-        synchronized (sLock) {
-            if (sSmsHelper == null) {
-                sSmsHelper = new SmsHelper(context);
-            }
-            return sSmsHelper;
-        }
-    }
-
-    private SmsHelper(Context context) {
+    SmsHelper(Context context) {
         mContext = context.getApplicationContext();
     }
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 1060211..81c00bc 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -23,8 +23,9 @@
     <dimen name="navigation_bar_size">@*android:dimen/navigation_bar_height</dimen>
     <!-- Minimum swipe distance to catch the swipe gestures to invoke assist or switch tasks. -->
     <dimen name="navigation_bar_min_swipe_distance">48dp</dimen>
-    <!-- The distance from a side of device of the navigation bar to start an edge swipe -->
-    <dimen name="navigation_bar_edge_swipe_threshold">48dp</dimen>
+    <!-- The default distance from a side of the device to start an edge swipe from -->
+    <dimen name="navigation_bar_default_edge_width">48dp</dimen>
+    <dimen name="navigation_bar_default_edge_height">500dp</dimen>
 
     <!-- thickness (height) of the dead zone at the top of the navigation bar,
          reducing false presses on navbar buttons; approx 2mm -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 56d9bf4c..5365dcf 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1829,6 +1829,9 @@
     <!-- SysUI Tuner: Button that leads to the navigation bar customization screen [CHAR LIMIT=60] -->
     <string name="nav_bar">Navigation bar</string>
 
+    <!-- Label for navigation edge panel for gestures [CHAR LIMIT=60] -->
+    <string name="nav_bar_edge_panel" translatable="false">Navigation bar Edge Panel</string>
+
     <!-- SysUI Tuner: Button that controls layout of navigation bar [CHAR LIMIT=60] -->
     <string name="nav_bar_layout">Layout</string>
 
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
index 2ce6965..d5bd2b2 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
@@ -21,12 +21,15 @@
 import android.content.IntentFilter;
 import android.net.ConnectivityManager;
 import android.net.wifi.WifiManager;
+import android.os.Handler;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.internal.telephony.IccCardConstants;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.settingslib.WirelessUtils;
@@ -206,6 +209,7 @@
     protected void updateCarrierText() {
         boolean allSimsMissing = true;
         boolean anySimReadyAndInService = false;
+        boolean missingSimsWithSubs = false;
         CharSequence displayText = null;
 
         List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getSubscriptionInfo(false);
@@ -252,6 +256,7 @@
                 // described above.
                 displayText = makeCarrierStringOnEmergencyCapable(
                         getMissingSimMessage(), subs.get(0).getCarrierName());
+                missingSimsWithSubs = true;
             } else {
                 // We don't have a SubscriptionInfo to get the emergency calls only from.
                 // Grab it from the old sticky broadcast if possible instead. We can use it
@@ -288,12 +293,14 @@
             displayText = getAirplaneModeMessage();
         }
 
+        Handler handler = Dependency.get(Dependency.MAIN_HANDLER);
+        final CarrierTextCallbackInfo info = new CarrierTextCallbackInfo(
+                displayText,
+                displayText.toString().split(mSeparator.toString()),
+                anySimReadyAndInService && !missingSimsWithSubs,
+                subsIds);
         if (mCarrierTextCallback != null) {
-            mCarrierTextCallback.updateCarrierInfo(new CarrierTextCallbackInfo(
-                    displayText,
-                    displayText.toString().split(mSeparator.toString()),
-                    anySimReadyAndInService,
-                    subsIds));
+            handler.post(() -> mCarrierTextCallback.updateCarrierInfo(info));
         }
 
     }
@@ -487,7 +494,8 @@
         public final boolean anySimReady;
         public final int[] subscriptionIds;
 
-        CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
+        @VisibleForTesting
+        public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
                 boolean anySimReady, int[] subscriptionIds) {
             this.carrierText = carrierText;
             this.listOfCarriers = listOfCarriers;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
index c0ed4b9..b865ce8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
@@ -54,6 +54,7 @@
 import com.android.settingslib.Utils;
 import com.android.settingslib.drawable.UserIconDrawable;
 import com.android.settingslib.graph.SignalDrawable;
+import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.R.dimen;
 import com.android.systemui.plugins.ActivityStarter;
@@ -134,6 +135,15 @@
         mDeviceProvisionedController = deviceProvisionedController;
     }
 
+    @VisibleForTesting
+    public QSFooterImpl(Context context, AttributeSet attrs) {
+        this(context, attrs,
+                Dependency.get(ActivityStarter.class),
+                Dependency.get(UserInfoController.class),
+                Dependency.get(NetworkController.class),
+                Dependency.get(DeviceProvisionedController.class));
+    }
+
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
@@ -476,32 +486,62 @@
                 mInfos[0].visible && mInfos[1].visible ? View.VISIBLE : View.GONE);
     }
 
+    @VisibleForTesting
+    protected int getSlotIndex(int subscriptionId) {
+        return SubscriptionManager.getSlotIndex(subscriptionId);
+    }
+
     @Override
     public void updateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) {
         if (info.anySimReady) {
             boolean[] slotSeen = new boolean[SIM_SLOTS];
-            for (int i = 0; i < SIM_SLOTS && i < info.listOfCarriers.length; i++) {
-                int slot = SubscriptionManager.getSlotIndex(info.subscriptionIds[i]);
-                mInfos[slot].visible = true;
-                slotSeen[slot] = true;
-                mCarrierTexts[slot].setText(info.listOfCarriers[i].toString().trim());
-                mCarrierGroups[slot].setVisibility(View.VISIBLE);
-            }
-            for (int i = 0; i < SIM_SLOTS; i++) {
-                if (!slotSeen[i]) {
+            if (info.listOfCarriers.length == info.subscriptionIds.length) {
+                for (int i = 0; i < SIM_SLOTS && i < info.listOfCarriers.length; i++) {
+                    int slot = getSlotIndex(info.subscriptionIds[i]);
+                    if (slot >= SIM_SLOTS) {
+                        Log.w(TAG, "updateInfoCarrier - slot: " + slot);
+                        continue;
+                    }
+                    if (slot == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
+                        Log.e(TAG,
+                                "Invalid SIM slot index for subscription: "
+                                        + info.subscriptionIds[i]);
+                        continue;
+                    }
+                    mInfos[slot].visible = true;
+                    slotSeen[slot] = true;
+                    mCarrierTexts[slot].setText(info.listOfCarriers[i].toString().trim());
+                    mCarrierGroups[slot].setVisibility(View.VISIBLE);
+                }
+                for (int i = 0; i < SIM_SLOTS; i++) {
+                    if (!slotSeen[i]) {
+                        mInfos[i].visible = false;
+                        mCarrierGroups[i].setVisibility(View.GONE);
+                    }
+                }
+            } else {
+                // If there are sims ready but there are not the same number of carrier names as
+                // subscription ids, just show the full text in the first slot
+                mInfos[0].visible = true;
+                mCarrierTexts[0].setText(info.carrierText);
+                mCarrierGroups[0].setVisibility(View.VISIBLE);
+                for (int i = 1; i < SIM_SLOTS; i++) {
                     mInfos[i].visible = false;
+                    mCarrierTexts[i].setText("");
                     mCarrierGroups[i].setVisibility(View.GONE);
                 }
             }
-            handleUpdateState();
         } else {
             mInfos[0].visible = false;
-            mInfos[1].visible = false;
             mCarrierTexts[0].setText(info.carrierText);
             mCarrierGroups[0].setVisibility(View.VISIBLE);
-            mCarrierGroups[1].setVisibility(View.GONE);
-            handleUpdateState();
+            for (int i = 1; i < SIM_SLOTS; i++) {
+                mInfos[i].visible = false;
+                mCarrierTexts[i].setText("");
+                mCarrierGroups[i].setVisibility(View.GONE);
+            }
         }
+        handleUpdateState();
     }
 
     @Override
@@ -510,9 +550,14 @@
             int qsType, boolean activityIn, boolean activityOut,
             String typeContentDescription,
             String description, boolean isWide, int subId, boolean roaming) {
-        int slotIndex = SubscriptionManager.getSlotIndex(subId);
+        int slotIndex = getSlotIndex(subId);
         if (slotIndex >= SIM_SLOTS) {
-            Log.e(TAG, "setMobileDataIndicators - slot: " + slotIndex);
+            Log.w(TAG, "setMobileDataIndicators - slot: " + slotIndex);
+            return;
+        }
+        if (slotIndex == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
+            Log.e(TAG, "Invalid SIM slot index for subscription: " + subId);
+            return;
         }
         mInfos[slotIndex].visible = statusIcon.visible;
         mInfos[slotIndex].mobileSignalIconId = statusIcon.icon;
@@ -539,7 +584,6 @@
         boolean roaming;
     }
 
-
     /**
      * TextView that changes its ellipsize value with its visibility.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarEdgePanel.java
new file mode 100644
index 0000000..dae4da7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarEdgePanel.java
@@ -0,0 +1,78 @@
+/*
+ * 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 android.annotation.NonNull;
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.systemui.R;
+
+public class NavigationBarEdgePanel extends View {
+    private static final String TAG = "NavigationBarEdgePanel";
+
+    public static NavigationBarEdgePanel create(@NonNull Context context, int width, int height,
+            int gravity) {
+        final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(width, height,
+                WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
+                WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
+                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                    | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                    | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
+                PixelFormat.TRANSLUCENT);
+        lp.gravity = gravity;
+        lp.setTitle(TAG + context.getDisplayId());
+        lp.accessibilityTitle = context.getString(R.string.nav_bar_edge_panel);
+        lp.windowAnimations = 0;
+        NavigationBarEdgePanel panel = new NavigationBarEdgePanel(context);
+        panel.setLayoutParams(lp);
+        return panel;
+    }
+
+    private NavigationBarEdgePanel(Context context) {
+        super(context);
+    }
+
+    public void setWindowFlag(int flags, boolean enable) {
+        WindowManager.LayoutParams lp = (WindowManager.LayoutParams) getLayoutParams();
+        if (lp == null || enable == ((lp.flags & flags) != 0)) {
+            return;
+        }
+        if (enable) {
+            lp.flags |= flags;
+        } else {
+            lp.flags &= ~flags;
+        }
+        updateLayout(lp);
+    }
+
+    public void setDimensions(int width, int height) {
+        final WindowManager.LayoutParams lp = (WindowManager.LayoutParams) getLayoutParams();
+        if (lp.width != width || lp.height != height) {
+            lp.width = width;
+            lp.height = height;
+            updateLayout(lp);
+        }
+    }
+
+    private void updateLayout(WindowManager.LayoutParams lp) {
+        WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
+        wm.updateViewLayout(this, lp);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 02683c1..651670c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -18,6 +18,8 @@
 
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_RIGHT;
 
 import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_QUICK_SCRUB;
 import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_SHOW_OVERVIEW_BUTTON;
@@ -35,6 +37,8 @@
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.annotation.DrawableRes;
+import android.annotation.IntDef;
+import android.annotation.SuppressLint;
 import android.app.StatusBarManager;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -51,6 +55,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.Display;
+import android.view.Gravity;
 import android.view.MotionEvent;
 import android.view.Surface;
 import android.view.View;
@@ -87,12 +92,21 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.function.Consumer;
 
 public class NavigationBarView extends FrameLayout implements PluginListener<NavGesture> {
     final static boolean DEBUG = false;
     final static String TAG = "StatusBar/NavBarView";
 
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({WINDOW_TARGET_BOTTOM, WINDOW_TARGET_LEFT, WINDOW_TARGET_RIGHT})
+    public @interface WindowTarget{}
+    public static final int WINDOW_TARGET_BOTTOM = 0;
+    public static final int WINDOW_TARGET_LEFT = 1;
+    public static final int WINDOW_TARGET_RIGHT = 2;
+
     // slippery nav bar when everything is disabled, e.g. during setup
     final static boolean SLIPPERY_WHEN_DISABLED = true;
 
@@ -109,6 +123,7 @@
     int mNavigationIconHints = 0;
 
     private @NavigationBarCompat.HitTarget int mDownHitTarget = HIT_TARGET_NONE;
+    private @WindowTarget int mWindowHitTarget = WINDOW_TARGET_BOTTOM;
     private Rect mHomeButtonBounds = new Rect();
     private Rect mBackButtonBounds = new Rect();
     private Rect mRecentsButtonBounds = new Rect();
@@ -160,6 +175,9 @@
     private NavigationAssistantAction mAssistantAction;
     private NavigationNotificationPanelAction mNotificationPanelAction;
 
+    private NavigationBarEdgePanel mLeftEdgePanel;
+    private NavigationBarEdgePanel mRightEdgePanel;
+
     /**
      * Helper that is responsible for showing the right toast when a disallowed activity operation
      * occurred. In pinned mode, we show instructions on how to break out of this mode, whilst in
@@ -222,6 +240,18 @@
         }
     };
 
+    private final OnTouchListener mEdgePanelTouchListener = new OnTouchListener() {
+        @SuppressLint("ClickableViewAccessibility")
+        @Override
+        public boolean onTouch(View v, MotionEvent event) {
+            if (event.getActionMasked() == ACTION_DOWN) {
+                mWindowHitTarget = v == mLeftEdgePanel ? WINDOW_TARGET_LEFT : WINDOW_TARGET_RIGHT;
+                mDownHitTarget = HIT_TARGET_NONE;
+            }
+            return mGestureHelper.onTouchEvent(event);
+        }
+    };
+
     private class H extends Handler {
         public void handleMessage(Message m) {
             switch (m.what) {
@@ -297,6 +327,16 @@
                 mColorAdaptionController.end();
             }
         }
+
+        @Override
+        public void onEdgeSensitivityChanged(int width, int height) {
+            if (mLeftEdgePanel != null) {
+                mLeftEdgePanel.setDimensions(width, height);
+            }
+            if (mRightEdgePanel != null) {
+                mRightEdgePanel.setDimensions(width, height);
+            }
+        }
     };
 
     public NavigationBarView(Context context, AttributeSet attrs) {
@@ -433,6 +473,7 @@
                 int x = (int) event.getX();
                 int y = (int) event.getY();
                 mDownHitTarget = HIT_TARGET_NONE;
+                mWindowHitTarget = WINDOW_TARGET_BOTTOM;
                 if (deadZoneConsumed) {
                     mDownHitTarget = HIT_TARGET_DEAD_ZONE;
                 } else if (getBackButton().isVisible() && mBackButtonBounds.contains(x, y)) {
@@ -483,6 +524,10 @@
         return mDownHitTarget;
     }
 
+    public @WindowTarget int getWindowTarget() {
+        return mWindowHitTarget;
+    }
+
     public void abortCurrentGesture() {
         getHomeButton().abortCurrentGesture();
     }
@@ -837,24 +882,32 @@
     }
 
     private void setSlippery(boolean slippery) {
-        boolean changed = false;
+        setWindowFlag(WindowManager.LayoutParams.FLAG_SLIPPERY, slippery);
+    }
+
+    public void setWindowTouchable(boolean flag) {
+        setWindowFlag(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, !flag);
+        if (mLeftEdgePanel != null) {
+            mLeftEdgePanel.setWindowFlag(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, !flag);
+        }
+        if (mRightEdgePanel != null) {
+            mRightEdgePanel.setWindowFlag(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, !flag);
+        }
+    }
+
+    private void setWindowFlag(int flags, boolean enable) {
         final ViewGroup navbarView = ((ViewGroup) getParent());
-        final WindowManager.LayoutParams lp = (WindowManager.LayoutParams) navbarView
-                .getLayoutParams();
-        if (lp == null) {
+        WindowManager.LayoutParams lp = (WindowManager.LayoutParams) navbarView.getLayoutParams();
+        if (lp == null || enable == ((lp.flags & flags) != 0)) {
             return;
         }
-        if (slippery && (lp.flags & WindowManager.LayoutParams.FLAG_SLIPPERY) == 0) {
-            lp.flags |= WindowManager.LayoutParams.FLAG_SLIPPERY;
-            changed = true;
-        } else if (!slippery && (lp.flags & WindowManager.LayoutParams.FLAG_SLIPPERY) != 0) {
-            lp.flags &= ~WindowManager.LayoutParams.FLAG_SLIPPERY;
-            changed = true;
+        if (enable) {
+            lp.flags |= flags;
+        } else {
+            lp.flags &= ~flags;
         }
-        if (changed) {
-            WindowManager wm = (WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE);
-            wm.updateViewLayout(navbarView, lp);
-        }
+        WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
+        wm.updateViewLayout(navbarView, lp);
     }
 
     public void setMenuVisibility(final boolean show) {
@@ -1016,6 +1069,17 @@
         } catch (RemoteException e) {
             Slog.e(TAG, "Failed to get nav bar position.", e);
         }
+
+        // For landscape, hide the panel that would interfere with navigation bar layout
+        if (mLeftEdgePanel != null && mRightEdgePanel != null) {
+            mLeftEdgePanel.setVisibility(VISIBLE);
+            mRightEdgePanel.setVisibility(VISIBLE);
+            if (navBarPos == NAV_BAR_LEFT) {
+                mLeftEdgePanel.setVisibility(GONE);
+            } else if (navBarPos == NAV_BAR_RIGHT) {
+                mRightEdgePanel.setVisibility(GONE);
+            }
+        }
         mGestureHelper.setBarState(isRtl, navBarPos);
     }
 
@@ -1142,6 +1206,21 @@
                 NavGesture.class, false /* Only one */);
         setUpSwipeUpOnboarding(isQuickStepSwipeUpEnabled());
         mColorAdaptionController.start();
+
+        if (mPrototypeController.isEnabled()) {
+            WindowManager wm = (WindowManager) getContext()
+                    .getSystemService(Context.WINDOW_SERVICE);
+            int width = mPrototypeController.getEdgeSensitivityWidth();
+            int height = mPrototypeController.getEdgeSensitivityHeight();
+            mLeftEdgePanel = NavigationBarEdgePanel.create(getContext(), width, height,
+                    Gravity.START | Gravity.BOTTOM);
+            mRightEdgePanel = NavigationBarEdgePanel.create(getContext(), width, height,
+                    Gravity.END | Gravity.BOTTOM);
+            mLeftEdgePanel.setOnTouchListener(mEdgePanelTouchListener);
+            mRightEdgePanel.setOnTouchListener(mEdgePanelTouchListener);
+            wm.addView(mLeftEdgePanel, mLeftEdgePanel.getLayoutParams());
+            wm.addView(mRightEdgePanel, mRightEdgePanel.getLayoutParams());
+        }
     }
 
     @Override
@@ -1157,6 +1236,17 @@
         for (int i = 0; i < mButtonDispatchers.size(); ++i) {
             mButtonDispatchers.valueAt(i).onDestroy();
         }
+
+        if (mPrototypeController.isEnabled()) {
+            WindowManager wm = (WindowManager) getContext()
+                    .getSystemService(Context.WINDOW_SERVICE);
+            if (mLeftEdgePanel != null) {
+                wm.removeView(mLeftEdgePanel);
+            }
+            if (mRightEdgePanel != null) {
+                wm.removeView(mRightEdgePanel);
+            }
+        }
     }
 
     private void setUpSwipeUpOnboarding(boolean connectedToOverviewProxy) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java
index b4feb25..8421e23 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.content.Context;
+import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Handler;
@@ -34,7 +35,12 @@
 public class NavigationPrototypeController extends ContentObserver {
     private static final String HIDE_BACK_BUTTON_SETTING = "quickstepcontroller_hideback";
     private static final String HIDE_HOME_BUTTON_SETTING = "quickstepcontroller_hidehome";
+    private static final String PROTOTYPE_ENABLED = "prototype_enabled";
 
+    private static final String EDGE_SENSITIVITY_HEIGHT_SETTING =
+            "quickstepcontroller_edge_height_sensitivity";
+    public static final String EDGE_SENSITIVITY_WIDTH_SETTING =
+            "quickstepcontroller_edge_width_sensitivity";
     private final String GESTURE_MATCH_SETTING = "quickstepcontroller_gesture_match_map";
     public static final String NAV_COLOR_ADAPT_ENABLE_SETTING = "navbar_color_adapt_enable";
 
@@ -79,6 +85,8 @@
         registerObserver(HIDE_HOME_BUTTON_SETTING);
         registerObserver(GESTURE_MATCH_SETTING);
         registerObserver(NAV_COLOR_ADAPT_ENABLE_SETTING);
+        registerObserver(EDGE_SENSITIVITY_WIDTH_SETTING);
+        registerObserver(EDGE_SENSITIVITY_HEIGHT_SETTING);
     }
 
     /**
@@ -106,10 +114,26 @@
             } else if (path.endsWith(NAV_COLOR_ADAPT_ENABLE_SETTING)) {
                 mListener.onColorAdaptChanged(
                         NavBarTintController.isEnabled(mContext));
+            } else if (path.endsWith(EDGE_SENSITIVITY_WIDTH_SETTING)
+                    || path.endsWith(EDGE_SENSITIVITY_HEIGHT_SETTING)) {
+                mListener.onEdgeSensitivityChanged(getEdgeSensitivityWidth(),
+                        getEdgeSensitivityHeight());
             }
         }
     }
 
+    public int getEdgeSensitivityWidth() {
+        return convertDpToPixel(getGlobalInt(EDGE_SENSITIVITY_WIDTH_SETTING, 0));
+    }
+
+    public int getEdgeSensitivityHeight() {
+        return convertDpToPixel(getGlobalInt(EDGE_SENSITIVITY_HEIGHT_SETTING, 0));
+    }
+
+    public boolean isEnabled() {
+        return getGlobalBool(PROTOTYPE_ENABLED, false);
+    }
+
     /**
      * Retrieve the action map to apply to the quick step controller
      * @return an action map
@@ -144,15 +168,24 @@
         return Settings.Global.getInt(mContext.getContentResolver(), name, defaultVal ? 1 : 0) == 1;
     }
 
+    private int getGlobalInt(String name, int defaultVal) {
+        return Settings.Global.getInt(mContext.getContentResolver(), name, defaultVal);
+    }
+
     private void registerObserver(String name) {
         mContext.getContentResolver()
                 .registerContentObserver(Settings.Global.getUriFor(name), false, this);
     }
 
+    private static int convertDpToPixel(float dp) {
+        return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
+    }
+
     public interface OnPrototypeChangedListener {
         void onGestureRemap(@GestureAction int[] actions);
         void onBackButtonVisibilityChanged(boolean visible);
         void onHomeButtonVisibilityChanged(boolean visible);
         void onColorAdaptChanged(boolean enabled);
+        void onEdgeSensitivityChanged(int width, int height);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java
index d5d283c..84f1cef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java
@@ -28,9 +28,12 @@
 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_HOME;
 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE;
 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_OVERVIEW;
+import static com.android.systemui.statusbar.phone.NavigationBarView.WINDOW_TARGET_BOTTOM;
+import static com.android.systemui.statusbar.phone.NavigationPrototypeController.EDGE_SENSITIVITY_WIDTH_SETTING;
 
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Matrix;
 import android.graphics.Rect;
@@ -42,12 +45,9 @@
 import android.view.InputDevice;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewGroup;
+import android.view.ViewConfiguration;
 import android.view.ViewPropertyAnimator;
 
-import android.view.WindowManager;
-import android.view.WindowManager.LayoutParams;
-
 import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
@@ -72,6 +72,7 @@
     /** Experiment to swipe home button left to execute a back key press */
     private static final String HIDE_BACK_BUTTON_PROP = "quickstepcontroller_hideback";
     private static final String ENABLE_CLICK_THROUGH_NAV_PROP = "quickstepcontroller_clickthrough";
+    private static final String GESTURE_REGION_THRESHOLD_SETTING = "gesture_region_threshold";
     private static final long BACK_BUTTON_FADE_IN_ALPHA = 150;
     private static final long CLICK_THROUGH_TAP_DELAY = 70;
     private static final long CLICK_THROUGH_TAP_RESET_DELAY = 100;
@@ -109,10 +110,10 @@
     private float mMaxDragLimit;
     private float mMinDragLimit;
     private float mDragDampeningFactor;
-    private float mEdgeSwipeThreshold;
     private boolean mClickThroughPressed;
     private float mClickThroughPressX;
     private float mClickThroughPressY;
+    private int mGestureRegionThreshold;
 
     private NavigationGestureAction mCurrentAction;
     private NavigationGestureAction[] mGestureActions = new NavigationGestureAction[MAX_GESTURES];
@@ -139,7 +140,7 @@
     };
 
     private final Runnable mClickThroughResetTap = () -> {
-        setWindowTouchable(true);
+        mNavigationBarView.setWindowTouchable(true);
         mClickThroughPressed = false;
     };
 
@@ -210,7 +211,8 @@
 
         // The same down event was just sent on intercept and therefore can be ignored here
         final boolean ignoreProxyDownEvent = event.getAction() == MotionEvent.ACTION_DOWN
-                && mOverviewEventSender.getProxy() != null;
+                && mOverviewEventSender.getProxy() != null
+                && mNavigationBarView.getWindowTarget() == WINDOW_TARGET_BOTTOM;
         return ignoreProxyDownEvent || handleTouchEvent(event);
     }
 
@@ -268,12 +270,15 @@
                 mNavigationBarView.transformMatrixToLocal(mTransformLocalMatrix);
                 mAllowGestureDetection = true;
                 mNotificationsVisibleOnDown = !mNavigationBarView.isNotificationsFullyCollapsed();
-                mEdgeSwipeThreshold = mContext.getResources()
-                        .getDimensionPixelSize(R.dimen.navigation_bar_edge_swipe_threshold);
+                final int defaultRegionThreshold = mContext.getResources()
+                        .getDimensionPixelOffset(R.dimen.navigation_bar_default_edge_width);
+                mGestureRegionThreshold = convertDpToPixel(getIntGlobalSetting(mContext,
+                        EDGE_SENSITIVITY_WIDTH_SETTING, defaultRegionThreshold));
                 break;
             }
             case MotionEvent.ACTION_MOVE: {
-                if (!mAllowGestureDetection) {
+                if (!mAllowGestureDetection
+                        || mNavigationBarView.getWindowTarget() != WINDOW_TARGET_BOTTOM) {
                     break;
                 }
                 int x = (int) event.getX();
@@ -330,18 +335,12 @@
                     } else if (exceededSwipeHorizontalTouchSlop) {
                         if (mDragHPositive ? (posH < touchDownH) : (posH > touchDownH)) {
                             // Swiping left (rtl) gesture
-                            int index = mGestureActions[ACTION_SWIPE_LEFT_FROM_EDGE_INDEX] != null
-                                        && isEdgeSwipeAlongNavBar(touchDownH, !mDragHPositive)
-                                    ? ACTION_SWIPE_LEFT_FROM_EDGE_INDEX : ACTION_SWIPE_LEFT_INDEX;
-                            tryToStartGesture(mGestureActions[index], true /* alignedWithNavBar */,
-                                    event);
+                            tryToStartGesture(mGestureActions[ACTION_SWIPE_LEFT_INDEX],
+                                    true /* alignedWithNavBar */, event);
                         } else {
                             // Swiping right (ltr) gesture
-                            int index = mGestureActions[ACTION_SWIPE_RIGHT_FROM_EDGE_INDEX] != null
-                                        && isEdgeSwipeAlongNavBar(touchDownH, mDragHPositive)
-                                    ? ACTION_SWIPE_RIGHT_FROM_EDGE_INDEX : ACTION_SWIPE_RIGHT_INDEX;
-                            tryToStartGesture(mGestureActions[index], true /* alignedWithNavBar */,
-                                    event);
+                            tryToStartGesture(mGestureActions[ACTION_SWIPE_RIGHT_INDEX],
+                                    true /* alignedWithNavBar */, event);
                         }
                     }
                 }
@@ -354,24 +353,34 @@
             case MotionEvent.ACTION_UP:
                 if (mCurrentAction != null) {
                     mCurrentAction.endGesture();
-                } else if (action == MotionEvent.ACTION_UP
-                        && getBoolGlobalSetting(mContext, ENABLE_CLICK_THROUGH_NAV_PROP)
-                        && !mClickThroughPressed) {
-                    // Enable click through functionality where no gesture has been detected and not
-                    // passed the drag slop so inject a touch event at the same location
-                    // after making the navigation bar window untouchable. After a some time, the
-                    // navigation bar will be able to take input events again
-                    float diffX = Math.abs(event.getX() - mTouchDownX);
-                    float diffY = Math.abs(event.getY() - mTouchDownY);
+                } else if (action == MotionEvent.ACTION_UP) {
+                    if (canTriggerEdgeSwipe(event)) {
+                        int index = mNavigationBarView.getWindowTarget() == NAV_BAR_LEFT
+                                ? ACTION_SWIPE_RIGHT_FROM_EDGE_INDEX
+                                : ACTION_SWIPE_LEFT_FROM_EDGE_INDEX;
+                        tryToStartGesture(mGestureActions[index], false /* alignedWithNavBar */,
+                                event);
+                        if (mCurrentAction != null) {
+                            mCurrentAction.endGesture();
+                        }
+                    } else if (getBoolGlobalSetting(mContext, ENABLE_CLICK_THROUGH_NAV_PROP)
+                            && !mClickThroughPressed) {
+                        // Enable click through functionality where no gesture has been detected and
+                        // not passed the drag slop so inject a touch event at the same location
+                        // after making the navigation bar window untouchable. After a some time,
+                        // the navigation bar will be able to take input events again
+                        float diffX = Math.abs(event.getX() - mTouchDownX);
+                        float diffY = Math.abs(event.getY() - mTouchDownY);
 
-                    if ((diffX <= NavigationBarCompat.getQuickStepDragSlopPx()
-                            && diffY <= NavigationBarCompat.getQuickStepDragSlopPx())) {
-                        setWindowTouchable(false);
-                        mClickThroughPressX = event.getRawX();
-                        mClickThroughPressY = event.getRawY();
-                        mClickThroughPressed = true;
-                        mNavigationBarView.postDelayed(mClickThroughSendTap,
-                                CLICK_THROUGH_TAP_DELAY);
+                        if ((diffX <= NavigationBarCompat.getQuickStepDragSlopPx()
+                                && diffY <= NavigationBarCompat.getQuickStepDragSlopPx())) {
+                            mNavigationBarView.setWindowTouchable(false);
+                            mClickThroughPressX = event.getRawX();
+                            mClickThroughPressY = event.getRawY();
+                            mClickThroughPressed = true;
+                            mNavigationBarView.postDelayed(mClickThroughSendTap,
+                                    CLICK_THROUGH_TAP_DELAY);
+                        }
                     }
                 }
 
@@ -403,30 +412,6 @@
         return mCurrentAction != null || deadZoneConsumed;
     }
 
-    private void setWindowTouchable(boolean flag) {
-        final WindowManager.LayoutParams lp = (WindowManager.LayoutParams)
-                ((ViewGroup) mNavigationBarView.getParent()).getLayoutParams();
-        if (flag) {
-            lp.flags &= ~LayoutParams.FLAG_NOT_TOUCHABLE;
-        } else {
-            lp.flags |= LayoutParams.FLAG_NOT_TOUCHABLE;
-        }
-        final WindowManager wm = (WindowManager) mNavigationBarView.getContext()
-                .getSystemService(Context.WINDOW_SERVICE);
-        wm.updateViewLayout((View) mNavigationBarView.getParent(), lp);
-    }
-
-    private boolean isEdgeSwipeAlongNavBar(int touchDown, boolean dragPositiveDirection) {
-        // Detect edge swipe from side of 0 -> threshold
-        if (dragPositiveDirection) {
-            return touchDown < mEdgeSwipeThreshold;
-        }
-        // Detect edge swipe from side of size -> (size - threshold)
-        final int largeSide = isNavBarVertical()
-                ? mNavigationBarView.getHeight() : mNavigationBarView.getWidth();
-        return touchDown > largeSide - mEdgeSwipeThreshold;
-    }
-
     private void handleDragHitTarget(int position, int touchDown) {
         // Drag the hit target if gesture action requires it
         if (mHitTarget != null && (mGestureVerticalDragsButton || mGestureHorizontalDragsButton)) {
@@ -448,6 +433,10 @@
     }
 
     private boolean shouldProxyEvents(int action) {
+        // Do not send events for side navigation bar panels
+        if (mNavigationBarView.getWindowTarget() != WINDOW_TARGET_BOTTOM) {
+            return false;
+        }
         final boolean actionValid = (mCurrentAction == null
                 || !mCurrentAction.disableProxyEvents());
         if (actionValid && !mIsInScreenPinning) {
@@ -619,6 +608,32 @@
         }
     }
 
+    /**
+     * To trigger an edge swipe, the user must start from the left or right edges of certain height
+     * from the bottom then past the drag slope towards the center of the screen, followed by either
+     * a timed trigger for fast swipes or distance if held on the screen longer.
+     * For time, user must swipe up quickly before the Tap Timeout (typically 100ms) and for
+     * distance, the user can drag back to cancel if the touch up has not past the threshold.
+     * @param event Touch up event
+     * @return whether or not edge swipe gesture occurs
+     */
+    private boolean canTriggerEdgeSwipe(MotionEvent event) {
+        if (mNavigationBarView.getWindowTarget() == WINDOW_TARGET_BOTTOM) {
+            return false;
+        }
+        int x = (int) event.getX();
+        int y = (int) event.getY();
+        int xDiff = Math.abs(x - mTouchDownX);
+        int yDiff = Math.abs(y - mTouchDownY);
+        final boolean exceededSwipeTouchSlop = xDiff > NavigationBarCompat.getQuickStepDragSlopPx()
+                && xDiff > yDiff;
+        if (exceededSwipeTouchSlop) {
+            long timeDiff = event.getEventTime() - event.getDownTime();
+            return xDiff > mGestureRegionThreshold || timeDiff < ViewConfiguration.getTapTimeout();
+        }
+        return false;
+    }
+
     private boolean canPerformAnyAction() {
         for (NavigationGestureAction action: mGestureActions) {
             if (action != null && action.isEnabled()) {
@@ -684,10 +699,18 @@
         return mNavBarPosition == NAV_BAR_LEFT || mNavBarPosition == NAV_BAR_RIGHT;
     }
 
+    private static int convertDpToPixel(float dp) {
+        return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
+    }
+
     static boolean getBoolGlobalSetting(Context context, String key) {
         return Settings.Global.getInt(context.getContentResolver(), key, 0) != 0;
     }
 
+    static int getIntGlobalSetting(Context context, String key, int defaultValue) {
+        return Settings.Global.getInt(context.getContentResolver(), key, defaultValue);
+    }
+
     public static boolean shouldhideBackButton(Context context) {
         return getBoolGlobalSetting(context, HIDE_BACK_BUTTON_PROP);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterImplTest.java
index 8503962..374a73c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterImplTest.java
@@ -16,27 +16,35 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.support.test.filters.SmallTest;
+import android.telephony.SubscriptionManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.LayoutInflater;
 import android.view.View;
 
+import com.android.keyguard.CarrierTextController.CarrierTextCallbackInfo;
 import com.android.systemui.R;
 import com.android.systemui.R.id;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.utils.leaks.LeakCheckedTest;
 
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
@@ -68,4 +76,117 @@
         // Verify Settings wasn't launched.
         verify(mActivityStarter, never()).startActivity(any(), anyBoolean());
     }
+
+    @Test // throws no Exception
+    public void testUpdateCarrierText_sameLengts() {
+        QSFooterImpl spiedFooter = Mockito.spy(mFooter);
+        when(spiedFooter.getSlotIndex(anyInt())).thenAnswer(
+                new Answer<Integer>() {
+                    @Override
+                    public Integer answer(InvocationOnMock invocationOnMock) throws Throwable {
+                        return invocationOnMock.getArgument(0);
+                    }
+                });
+
+        // listOfCarriers length 1, subscriptionIds length 1, anySims false
+        CarrierTextCallbackInfo c1 = new CarrierTextCallbackInfo(
+                "",
+                new CharSequence[]{""},
+                false,
+                new int[]{0});
+        spiedFooter.updateCarrierInfo(c1);
+
+        // listOfCarriers length 1, subscriptionIds length 1, anySims true
+        CarrierTextCallbackInfo c2 = new CarrierTextCallbackInfo(
+                "",
+                new CharSequence[]{""},
+                true,
+                new int[]{0});
+        spiedFooter.updateCarrierInfo(c2);
+
+        // listOfCarriers length 2, subscriptionIds length 2, anySims false
+        CarrierTextCallbackInfo c3 = new CarrierTextCallbackInfo(
+                "",
+                new CharSequence[]{"", ""},
+                false,
+                new int[]{0, 1});
+        spiedFooter.updateCarrierInfo(c3);
+
+        // listOfCarriers length 2, subscriptionIds length 2, anySims true
+        CarrierTextCallbackInfo c4 = new CarrierTextCallbackInfo(
+                "",
+                new CharSequence[]{"", ""},
+                true,
+                new int[]{0, 1});
+        spiedFooter.updateCarrierInfo(c4);
+    }
+
+    @Test // throws no Exception
+    public void testUpdateCarrierText_differentLength() {
+        QSFooterImpl spiedFooter = Mockito.spy(mFooter);
+        when(spiedFooter.getSlotIndex(anyInt())).thenAnswer(
+                new Answer<Integer>() {
+                    @Override
+                    public Integer answer(InvocationOnMock invocationOnMock) throws Throwable {
+                        return invocationOnMock.getArgument(0);
+                    }
+                });
+
+        // listOfCarriers length 2, subscriptionIds length 1, anySims false
+        CarrierTextCallbackInfo c1 = new CarrierTextCallbackInfo(
+                "",
+                new CharSequence[]{"", ""},
+                false,
+                new int[]{0});
+        spiedFooter.updateCarrierInfo(c1);
+
+        // listOfCarriers length 2, subscriptionIds length 1, anySims true
+        CarrierTextCallbackInfo c2 = new CarrierTextCallbackInfo(
+                "",
+                new CharSequence[]{"", ""},
+                true,
+                new int[]{0});
+        spiedFooter.updateCarrierInfo(c2);
+
+        // listOfCarriers length 1, subscriptionIds length 2, anySims false
+        CarrierTextCallbackInfo c3 = new CarrierTextCallbackInfo(
+                "",
+                new CharSequence[]{""},
+                false,
+                new int[]{0, 1});
+        spiedFooter.updateCarrierInfo(c3);
+
+        // listOfCarriers length 1, subscriptionIds length 2, anySims true
+        CarrierTextCallbackInfo c4 = new CarrierTextCallbackInfo(
+                "",
+                new CharSequence[]{""},
+                true,
+                new int[]{0, 1});
+        spiedFooter.updateCarrierInfo(c4);
+    }
+
+    @Test // throws no Exception
+    public void testUpdateCarrierText_invalidSim() {
+        QSFooterImpl spiedFooter = Mockito.spy(mFooter);
+        when(spiedFooter.getSlotIndex(anyInt())).thenReturn(
+                SubscriptionManager.INVALID_SIM_SLOT_INDEX);
+        CarrierTextCallbackInfo c4 = new CarrierTextCallbackInfo(
+                "",
+                new CharSequence[]{"", ""},
+                true,
+                new int[]{0, 1});
+        spiedFooter.updateCarrierInfo(c4);
+    }
+
+    @Test // throws no Exception
+    public void testSetMobileDataIndicators_invalidSim() {
+        QSFooterImpl spiedFooter = Mockito.spy(mFooter);
+        when(spiedFooter.getSlotIndex(anyInt())).thenReturn(
+                SubscriptionManager.INVALID_SIM_SLOT_INDEX);
+        spiedFooter.setMobileDataIndicators(
+                mock(NetworkController.IconState.class),
+                mock(NetworkController.IconState.class),
+                0, 0, true, true, "", "", true, 0, true);
+    }
+
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/QuickStepControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/QuickStepControllerTest.java
index 382dde9..dbf00a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/QuickStepControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/QuickStepControllerTest.java
@@ -23,6 +23,7 @@
 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_DEAD_ZONE;
 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_HOME;
 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE;
+import static com.android.systemui.statusbar.phone.NavigationBarView.WINDOW_TARGET_BOTTOM;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -63,7 +64,6 @@
 public class QuickStepControllerTest extends SysuiTestCase {
     private static final int NAVBAR_WIDTH = 1000;
     private static final int NAVBAR_HEIGHT = 300;
-    private static final int EDGE_THRESHOLD = 100;
 
     private QuickStepController mController;
     private NavigationBarView mNavigationBarView;
@@ -77,8 +77,6 @@
         MockitoAnnotations.initMocks(this);
         final ButtonDispatcher backButton = mock(ButtonDispatcher.class);
         mResources = mock(Resources.class);
-        doReturn(EDGE_THRESHOLD).when(mResources)
-                .getDimensionPixelSize(R.dimen.navigation_bar_edge_swipe_threshold);
 
         mProxyService = mock(OverviewProxyService.class);
         mProxy = mock(IOverviewProxy.Stub.class);
@@ -95,6 +93,7 @@
         doReturn(true).when(mNavigationBarView).isNotificationsFullyCollapsed();
         doReturn(true).when(mNavigationBarView).isQuickScrubEnabled();
         doReturn(HIT_TARGET_NONE).when(mNavigationBarView).getDownHitTarget();
+        doReturn(WINDOW_TARGET_BOTTOM).when(mNavigationBarView).getWindowTarget();
         doReturn(backButton).when(mNavigationBarView).getBackButton();
         doReturn(mResources).when(mNavigationBarView).getResources();
         doReturn(mContext).when(mNavigationBarView).getContext();
@@ -196,10 +195,8 @@
         NavigationGestureAction swipeDown = mockAction(true);
         NavigationGestureAction swipeLeft = mockAction(true);
         NavigationGestureAction swipeRight = mockAction(true);
-        NavigationGestureAction swipeLeftFromEdge = mockAction(true);
-        NavigationGestureAction swipeRightFromEdge = mockAction(true);
-        mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight, swipeLeftFromEdge,
-                swipeRightFromEdge);
+        mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight,
+                null /* leftEdgeSwipe */, null /* rightEdgeSwipe */);
 
         // Swipe Up
         assertGestureTriggersAction(swipeUp, 1, 100, 5, 1);
@@ -209,10 +206,6 @@
         assertGestureTriggersAction(swipeLeft, NAVBAR_WIDTH / 2, 1, 5, 1);
         // Swipe Right
         assertGestureTriggersAction(swipeRight, NAVBAR_WIDTH / 2, 1, NAVBAR_WIDTH, 5);
-        // Swipe Left from Edge
-        assertGestureTriggersAction(swipeLeftFromEdge, NAVBAR_WIDTH, 1, 5, 1);
-        // Swipe Right from Edge
-        assertGestureTriggersAction(swipeRightFromEdge, 0, 1, NAVBAR_WIDTH, 5);
     }
 
     @Test
@@ -224,10 +217,8 @@
         NavigationGestureAction swipeDown = mockAction(true);
         NavigationGestureAction swipeLeft = mockAction(true);
         NavigationGestureAction swipeRight = mockAction(true);
-        NavigationGestureAction swipeLeftFromEdge = mockAction(true);
-        NavigationGestureAction swipeRightFromEdge = mockAction(true);
-        mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight, swipeLeftFromEdge,
-                swipeRightFromEdge);
+        mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight,
+                null /* leftEdgeSwipe */, null /* rightEdgeSwipe */);
 
         // In landscape
         mController.setBarState(false /* isRTL */, NAV_BAR_RIGHT);
@@ -240,10 +231,6 @@
         assertGestureTriggersAction(swipeUp, 100, 1, 5, 1);
         // Swipe Right
         assertGestureTriggersAction(swipeDown, 1, 1, 100, 5);
-        // Swipe Up from Edge
-        assertGestureTriggersAction(swipeRightFromEdge, 1, NAVBAR_WIDTH, 5, 0);
-        // Swipe Down from Edge
-        assertGestureTriggersAction(swipeLeftFromEdge, 0, 1, 0, NAVBAR_WIDTH);
     }
 
     @Test
@@ -256,10 +243,8 @@
         NavigationGestureAction swipeDown = mockAction(true);
         NavigationGestureAction swipeLeft = mockAction(true);
         NavigationGestureAction swipeRight = mockAction(true);
-        NavigationGestureAction swipeLeftFromEdge = mockAction(true);
-        NavigationGestureAction swipeRightFromEdge = mockAction(true);
-        mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight, swipeLeftFromEdge,
-                swipeRightFromEdge);
+        mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight,
+                null /* leftEdgeSwipe */, null /* rightEdgeSwipe */);
 
         // Swipe Up
         assertGestureTriggersAction(swipeLeft, 1, NAVBAR_WIDTH / 2, 5, 1);
@@ -269,10 +254,6 @@
         assertGestureTriggersAction(swipeDown, 100, 1, 5, 1);
         // Swipe Right
         assertGestureTriggersAction(swipeUp, 1, 1, 100, 5);
-        // Swipe Up from Edge
-        assertGestureTriggersAction(swipeLeftFromEdge, 1, NAVBAR_WIDTH, 5, 0);
-        // Swipe Down from Edge
-        assertGestureTriggersAction(swipeRightFromEdge, 0, 1, 0, NAVBAR_WIDTH);
     }
 
     @Test
@@ -286,10 +267,8 @@
         NavigationGestureAction swipeDown = mockAction(true);
         NavigationGestureAction swipeLeft = mockAction(true);
         NavigationGestureAction swipeRight = mockAction(true);
-        NavigationGestureAction swipeLeftFromEdge = mockAction(true);
-        NavigationGestureAction swipeRightFromEdge = mockAction(true);
-        mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight, swipeLeftFromEdge,
-                swipeRightFromEdge);
+        mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight,
+                null /* leftEdgeSwipe */, null /* rightEdgeSwipe */);
 
         // Swipe Up in RTL
         assertGestureTriggersAction(swipeUp, 1, 100, 5, 1);
@@ -299,10 +278,6 @@
         assertGestureTriggersAction(swipeRight, NAVBAR_WIDTH / 2, 1, 5, 1);
         // Swipe Right in RTL
         assertGestureTriggersAction(swipeLeft, NAVBAR_WIDTH / 2, 1, NAVBAR_WIDTH, 0);
-        // Swipe Left from Edge
-        assertGestureTriggersAction(swipeRightFromEdge, NAVBAR_WIDTH, 1, 5, 1);
-        // Swipe Right from Edge
-        assertGestureTriggersAction(swipeLeftFromEdge, 0, 1, NAVBAR_WIDTH, 5);
     }
 
     @Test
@@ -316,10 +291,8 @@
         NavigationGestureAction swipeDown = mockAction(true);
         NavigationGestureAction swipeLeft = mockAction(true);
         NavigationGestureAction swipeRight = mockAction(true);
-        NavigationGestureAction swipeLeftFromEdge = mockAction(true);
-        NavigationGestureAction swipeRightFromEdge = mockAction(true);
-        mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight, swipeLeftFromEdge,
-                swipeRightFromEdge);
+        mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight,
+                null /* leftEdgeSwipe */, null /* rightEdgeSwipe */);
 
         // Swipe Up
         assertGestureTriggersAction(swipeLeft, 1, NAVBAR_WIDTH / 2, 5, 1);
@@ -329,10 +302,6 @@
         assertGestureTriggersAction(swipeUp, 100, 1, 5, 1);
         // Swipe Right
         assertGestureTriggersAction(swipeDown, 1, 1, 100, 5);
-        // Swipe Up from Edge
-        assertGestureTriggersAction(swipeLeftFromEdge, 1, NAVBAR_WIDTH, 5, 0);
-        // Swipe Down from Edge
-        assertGestureTriggersAction(swipeRightFromEdge, 0, 1, 0, NAVBAR_WIDTH);
     }
 
     @Test
@@ -346,10 +315,8 @@
         NavigationGestureAction swipeDown = mockAction(true);
         NavigationGestureAction swipeLeft = mockAction(true);
         NavigationGestureAction swipeRight = mockAction(true);
-        NavigationGestureAction swipeLeftFromEdge = mockAction(true);
-        NavigationGestureAction swipeRightFromEdge = mockAction(true);
-        mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight, swipeLeftFromEdge,
-                swipeRightFromEdge);
+        mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight,
+                null /* leftEdgeSwipe */, null /* rightEdgeSwipe */);
 
         // Swipe Up
         assertGestureTriggersAction(swipeRight, 1, NAVBAR_WIDTH / 2, 5, 1);
@@ -359,10 +326,6 @@
         assertGestureTriggersAction(swipeDown, 100, 1, 5, 1);
         // Swipe Right
         assertGestureTriggersAction(swipeUp, 1, 1, 100, 5);
-        // Swipe Up from Edge
-        assertGestureTriggersAction(swipeRightFromEdge, 1, NAVBAR_WIDTH, 5, 0);
-        // Swipe Down from Edge
-        assertGestureTriggersAction(swipeLeftFromEdge, 0, 1, 0, NAVBAR_WIDTH);
     }
 
     @Test
@@ -602,25 +565,6 @@
         assertGestureDragsHitTargetAllDirections(buttonView, true /* isRTL */, NAV_BAR_LEFT);
     }
 
-    @Test
-    public void testNoEdgeActionsTriggerNormalActions() {
-        doReturn(NAVBAR_WIDTH).when(mNavigationBarView).getWidth();
-        doReturn(NAVBAR_HEIGHT).when(mNavigationBarView).getHeight();
-
-        NavigationGestureAction swipeUp = mockAction(true);
-        NavigationGestureAction swipeDown = mockAction(true);
-        NavigationGestureAction swipeLeft = mockAction(true);
-        NavigationGestureAction swipeRight = mockAction(true);
-        mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight,
-                null /* swipeLeftFromEdgeAction */,
-                null /* swipeLeftFromEdgeAction */);
-
-        // Swipe Left from Edge
-        assertGestureTriggersAction(swipeLeft, NAVBAR_WIDTH, 1, 5, 1);
-        // Swipe Right from Edge
-        assertGestureTriggersAction(swipeRight, 0, 1, NAVBAR_WIDTH, 5);
-    }
-
     private void assertGestureDragsHitTargetAllDirections(View buttonView, boolean isRTL,
             int navPos) {
         mController.setBarState(isRTL, navPos);
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
index c8a27f1..7102b82 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
@@ -416,36 +416,5 @@
             // TODO(b/122595322): implement
             // TODO(b/119613670): log metrics
         }
-
-        @Override
-        public void setActivityContentCaptureEnabled(ComponentName activity, boolean enabled) {
-            if (mMaster.verbose) {
-                Log.v(TAG, "setActivityContentCaptureEnabled(activity=" + activity + ", enabled="
-                        + enabled + ")");
-            }
-            // TODO(b/122595322): implement
-            // TODO(b/119613670): log metrics
-        }
-
-        @Override
-        public void setPackageContentCaptureEnabled(String packageName, boolean enabled) {
-            if (mMaster.verbose) {
-                Log.v(TAG,
-                        "setPackageContentCaptureEnabled(packageName=" + packageName + ", enabled="
-                                + enabled + ")");
-            }
-            // TODO(b/122595322): implement
-            // TODO(b/119613670): log metrics
-        }
-
-        @Override
-        public void getContentCaptureDisabledActivities(IResultReceiver receiver) {
-            // TODO(b/122595322): implement
-        }
-
-        @Override
-        public void getContentCaptureDisabledPackages(IResultReceiver receiver) {
-            // TODO(b/122595322): implement
-        }
     }
 }
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index deaa931..a3bae52 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -15,6 +15,9 @@
  */
 package com.android.server.audio;
 
+import static com.android.server.audio.AudioService.CONNECTION_STATE_CONNECTED;
+import static com.android.server.audio.AudioService.CONNECTION_STATE_DISCONNECTED;
+
 import android.annotation.NonNull;
 import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothDevice;
@@ -43,7 +46,7 @@
 /** @hide */
 /*package*/ final class AudioDeviceBroker {
 
-    private static final String TAG = "AudioDeviceBroker";
+    private static final String TAG = "AS.AudioDeviceBroker";
 
     private static final long BROKER_WAKELOCK_TIMEOUT_MS = 5000; //5s
 
@@ -62,27 +65,27 @@
     private int mForcedUseForCommExt;
 
     // Manages all connected devices, only ever accessed on the message loop
-    //### or make it synchronized
     private final AudioDeviceInventory mDeviceInventory;
     // Manages notifications to BT service
     private final BtHelper mBtHelper;
 
 
     //-------------------------------------------------------------------
+    // we use a different lock than mDeviceStateLock so as not to create
+    // lock contention between enqueueing a message and handling them
     private static final Object sLastDeviceConnectionMsgTimeLock = new Object();
+    @GuardedBy("sLastDeviceConnectionMsgTimeLock")
     private static long sLastDeviceConnectMsgTime = 0;
 
-    private final Object mBluetoothA2dpEnabledLock = new Object();
+    // General lock to be taken whenever the state of the audio devices is to be checked or changed
+    private final Object mDeviceStateLock = new Object();
+
     // Request to override default use of A2DP for media.
-    @GuardedBy("mBluetoothA2dpEnabledLock")
+    @GuardedBy("mDeviceStateLock")
     private boolean mBluetoothA2dpEnabled;
 
-    // lock always taken synchronized on mConnectedDevices
-    /*package*/  final Object mA2dpAvrcpLock = new Object();
-    // lock always taken synchronized on mConnectedDevices
-    /*package*/  final Object mHearingAidLock = new Object();
-
     // lock always taken when accessing AudioService.mSetModeDeathHandlers
+    // TODO do not "share" the lock between AudioService and BtHelpr, see b/123769055
     /*package*/ final Object mSetModeLock = new Object();
 
     //-------------------------------------------------------------------
@@ -109,13 +112,17 @@
     // All post* methods are asynchronous
 
     /*package*/ void onSystemReady() {
-        mBtHelper.onSystemReady();
+        synchronized (mDeviceStateLock) {
+            mBtHelper.onSystemReady();
+        }
     }
 
     /*package*/ void onAudioServerDied() {
         // Restore forced usage for communications and record
-        onSetForceUse(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, "onAudioServerDied");
-        onSetForceUse(AudioSystem.FOR_RECORD, mForcedUseForComm, "onAudioServerDied");
+        synchronized (mDeviceStateLock) {
+            onSetForceUse(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, "onAudioServerDied");
+            onSetForceUse(AudioSystem.FOR_RECORD, mForcedUseForComm, "onAudioServerDied");
+        }
         // restore devices
         sendMsgNoDelay(MSG_RESTORE_DEVICES, SENDMSG_REPLACE);
     }
@@ -130,7 +137,9 @@
     }
 
     /*package*/ void disconnectAllBluetoothProfiles() {
-        mBtHelper.disconnectAllBluetoothProfiles();
+        synchronized (mDeviceStateLock) {
+            mBtHelper.disconnectAllBluetoothProfiles();
+        }
     }
 
     /**
@@ -140,11 +149,13 @@
      * @param intent
      */
     /*package*/ void receiveBtEvent(@NonNull Intent intent) {
-        mBtHelper.receiveBtEvent(intent);
+        synchronized (mDeviceStateLock) {
+            mBtHelper.receiveBtEvent(intent);
+        }
     }
 
     /*package*/ void setBluetoothA2dpOn_Async(boolean on, String source) {
-        synchronized (mBluetoothA2dpEnabledLock) {
+        synchronized (mDeviceStateLock) {
             if (mBluetoothA2dpEnabled == on) {
                 return;
             }
@@ -158,28 +169,34 @@
     }
 
     /*package*/ void setSpeakerphoneOn(boolean on, String eventSource) {
-        if (on) {
-            if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
-                setForceUse_Async(AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE, eventSource);
+        synchronized (mDeviceStateLock) {
+            if (on) {
+                if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
+                    setForceUse_Async(AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE, eventSource);
+                }
+                mForcedUseForComm = AudioSystem.FORCE_SPEAKER;
+            } else if (mForcedUseForComm == AudioSystem.FORCE_SPEAKER) {
+                mForcedUseForComm = AudioSystem.FORCE_NONE;
             }
-            mForcedUseForComm = AudioSystem.FORCE_SPEAKER;
-        } else if (mForcedUseForComm == AudioSystem.FORCE_SPEAKER) {
-            mForcedUseForComm = AudioSystem.FORCE_NONE;
-        }
 
-        mForcedUseForCommExt = mForcedUseForComm;
-        setForceUse_Async(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource);
+            mForcedUseForCommExt = mForcedUseForComm;
+            setForceUse_Async(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource);
+        }
     }
 
     /*package*/ boolean isSpeakerphoneOn() {
-        return (mForcedUseForCommExt == AudioSystem.FORCE_SPEAKER);
+        synchronized (mDeviceStateLock) {
+            return (mForcedUseForCommExt == AudioSystem.FORCE_SPEAKER);
+        }
     }
 
     /*package*/ void setWiredDeviceConnectionState(int type,
             @AudioService.ConnectionState int state, String address, String name,
             String caller) {
         //TODO move logging here just like in setBluetooth* methods
-        mDeviceInventory.setWiredDeviceConnectionState(type, state, address, name, caller);
+        synchronized (mDeviceStateLock) {
+            mDeviceInventory.setWiredDeviceConnectionState(type, state, address, name, caller);
+        }
     }
 
     /*package*/ int setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
@@ -192,22 +209,27 @@
                         + " addr=" + device.getAddress()
                         + " prof=" + profile + " supprNoisy=" + suppressNoisyIntent
                         + " vol=" + a2dpVolume)).printLog(TAG));
-        if (mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE,
-                new BtHelper.BluetoothA2dpDeviceInfo(device))) {
-            AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
-                    "A2DP connection state ignored"));
-            return 0;
+        synchronized (mDeviceStateLock) {
+            if (mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE,
+                    new BtHelper.BluetoothA2dpDeviceInfo(device))) {
+                AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+                        "A2DP connection state ignored"));
+                return 0;
+            }
+            return mDeviceInventory.setBluetoothA2dpDeviceConnectionState(
+                    device, state, profile, suppressNoisyIntent,
+                    AudioSystem.DEVICE_NONE, a2dpVolume);
         }
-        return mDeviceInventory.setBluetoothA2dpDeviceConnectionState(
-                device, state, profile, suppressNoisyIntent, AudioSystem.DEVICE_NONE, a2dpVolume);
     }
 
     /*package*/ int handleBluetoothA2dpActiveDeviceChange(
             @NonNull BluetoothDevice device,
             @AudioService.BtProfileConnectionState int state, int profile,
             boolean suppressNoisyIntent, int a2dpVolume) {
-        return mDeviceInventory.handleBluetoothA2dpActiveDeviceChange(device, state, profile,
-                suppressNoisyIntent, a2dpVolume);
+        synchronized (mDeviceStateLock) {
+            return mDeviceInventory.handleBluetoothA2dpActiveDeviceChange(device, state, profile,
+                    suppressNoisyIntent, a2dpVolume);
+        }
     }
 
     /*package*/ int setBluetoothHearingAidDeviceConnectionState(
@@ -218,57 +240,69 @@
                         + " addr=" + device.getAddress()
                         + " supprNoisy=" + suppressNoisyIntent
                         + " src=" + eventSource)).printLog(TAG));
-        return mDeviceInventory.setBluetoothHearingAidDeviceConnectionState(
-                device, state, suppressNoisyIntent, musicDevice);
+        synchronized (mDeviceStateLock) {
+            return mDeviceInventory.setBluetoothHearingAidDeviceConnectionState(
+                    device, state, suppressNoisyIntent, musicDevice);
+        }
     }
 
     // never called by system components
     /*package*/ void setBluetoothScoOnByApp(boolean on) {
-        mForcedUseForCommExt = on ? AudioSystem.FORCE_BT_SCO : AudioSystem.FORCE_NONE;
+        synchronized (mDeviceStateLock) {
+            mForcedUseForCommExt = on ? AudioSystem.FORCE_BT_SCO : AudioSystem.FORCE_NONE;
+        }
     }
 
     /*package*/ boolean isBluetoothScoOnForApp() {
-        return mForcedUseForCommExt == AudioSystem.FORCE_BT_SCO;
+        synchronized (mDeviceStateLock) {
+            return mForcedUseForCommExt == AudioSystem.FORCE_BT_SCO;
+        }
     }
 
     /*package*/ void setBluetoothScoOn(boolean on, String eventSource) {
         //Log.i(TAG, "setBluetoothScoOnInt: " + on + " " + eventSource);
-        if (on) {
-            // do not accept SCO ON if SCO audio is not connected
-            if (!mBtHelper.isBluetoothScoOn()) {
-                mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO;
-                return;
+        synchronized (mDeviceStateLock) {
+            if (on) {
+                // do not accept SCO ON if SCO audio is not connected
+                if (!mBtHelper.isBluetoothScoOn()) {
+                    mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO;
+                    return;
+                }
+                mForcedUseForComm = AudioSystem.FORCE_BT_SCO;
+            } else if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
+                mForcedUseForComm = AudioSystem.FORCE_NONE;
             }
-            mForcedUseForComm = AudioSystem.FORCE_BT_SCO;
-        } else if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
-            mForcedUseForComm = AudioSystem.FORCE_NONE;
+            mForcedUseForCommExt = mForcedUseForComm;
+            AudioSystem.setParameters("BT_SCO=" + (on ? "on" : "off"));
+            sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE,
+                    AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource);
+            sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE,
+                    AudioSystem.FOR_RECORD, mForcedUseForComm, eventSource);
+            // Un-mute ringtone stream volume
+            mAudioService.setUpdateRingerModeServiceInt();
         }
-        mForcedUseForCommExt = mForcedUseForComm;
-        AudioSystem.setParameters("BT_SCO=" + (on ? "on" : "off"));
-        sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE,
-                AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource);
-        sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE,
-                AudioSystem.FOR_RECORD, mForcedUseForComm, eventSource);
-        // Un-mute ringtone stream volume
-        mAudioService.setUpdateRingerModeServiceInt();
     }
 
     /*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) {
-        return mDeviceInventory.startWatchingRoutes(observer);
+        synchronized (mDeviceStateLock) {
+            return mDeviceInventory.startWatchingRoutes(observer);
+        }
     }
 
     /*package*/ AudioRoutesInfo getCurAudioRoutes() {
-        return mDeviceInventory.getCurAudioRoutes();
+        synchronized (mDeviceStateLock) {
+            return mDeviceInventory.getCurAudioRoutes();
+        }
     }
 
     /*package*/ boolean isAvrcpAbsoluteVolumeSupported() {
-        synchronized (mA2dpAvrcpLock) {
+        synchronized (mDeviceStateLock) {
             return mBtHelper.isAvrcpAbsoluteVolumeSupported();
         }
     }
 
     /*package*/ boolean isBluetoothA2dpOn() {
-        synchronized (mBluetoothA2dpEnabledLock) {
+        synchronized (mDeviceStateLock) {
             return mBluetoothA2dpEnabled;
         }
     }
@@ -355,14 +389,12 @@
         sendMsgNoDelay(MSG_BROADCAST_AUDIO_BECOMING_NOISY, SENDMSG_REPLACE);
     }
 
-    //###TODO unify with handleSetA2dpSinkConnectionState
     /*package*/ void postA2dpSinkConnection(int state,
             @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo, int delay) {
         sendILMsg(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE, SENDMSG_QUEUE,
                 state, btDeviceInfo, delay);
     }
 
-    //###TODO unify with handleSetA2dpSourceConnectionState
     /*package*/ void postA2dpSourceConnection(int state,
             @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo, int delay) {
         sendILMsg(MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE, SENDMSG_QUEUE,
@@ -395,7 +427,7 @@
                 .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
                 .append(Binder.getCallingPid()).append(" src:").append(source).toString();
 
-        synchronized (mBluetoothA2dpEnabledLock) {
+        synchronized (mDeviceStateLock) {
             mBluetoothA2dpEnabled = on;
             mBrokerHandler.removeMessages(MSG_IIL_SET_FORCE_BT_A2DP_USE);
             onSetForceUse(
@@ -407,25 +439,38 @@
 
     /*package*/ boolean handleDeviceConnection(boolean connect, int device, String address,
                                                        String deviceName) {
-        return mDeviceInventory.handleDeviceConnection(connect, device, address, deviceName);
+        synchronized (mDeviceStateLock) {
+            return mDeviceInventory.handleDeviceConnection(connect, device, address, deviceName);
+        }
     }
 
     /*package*/ void handleDisconnectA2dp() {
-        mDeviceInventory.disconnectA2dp();
+        synchronized (mDeviceStateLock) {
+            mDeviceInventory.disconnectA2dp();
+        }
     }
     /*package*/ void handleDisconnectA2dpSink() {
-        mDeviceInventory.disconnectA2dpSink();
+        synchronized (mDeviceStateLock) {
+            mDeviceInventory.disconnectA2dpSink();
+        }
+    }
+
+    /*package*/ void handleDisconnectHearingAid() {
+        synchronized (mDeviceStateLock) {
+            mDeviceInventory.disconnectHearingAid();
+        }
     }
 
     /*package*/ void handleSetA2dpSinkConnectionState(@BluetoothProfile.BtProfileState int state,
                 @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) {
-        final int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0;
-        //### DOESN'T HONOR SYNC ON DEVICES -> make a synchronized version?
-        // might be ok here because called on BT thread? + sync happening in
-        //  checkSendBecomingNoisyIntent
-        final int delay = mDeviceInventory.checkSendBecomingNoisyIntent(
-                AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, intState,
-                AudioSystem.DEVICE_NONE);
+        final int intState = (state == BluetoothA2dp.STATE_CONNECTED)
+                ? CONNECTION_STATE_CONNECTED : CONNECTION_STATE_DISCONNECTED;
+        final int delay;
+        synchronized (mDeviceStateLock) {
+            delay = mDeviceInventory.checkSendBecomingNoisyIntent(
+                    AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, intState,
+                    AudioSystem.DEVICE_NONE);
+        }
         final String addr = btDeviceInfo == null ? "null" : btDeviceInfo.getBtDevice().getAddress();
 
         if (AudioService.DEBUG_DEVICES) {
@@ -437,10 +482,6 @@
                 state, btDeviceInfo, delay);
     }
 
-    /*package*/ void handleDisconnectHearingAid() {
-        mDeviceInventory.disconnectHearingAid();
-    }
-
     /*package*/ void handleSetA2dpSourceConnectionState(@BluetoothProfile.BtProfileState int state,
             @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) {
         final int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0;
@@ -468,8 +509,6 @@
         sendLMsgNoDelay(MSG_L_A2DP_ACTIVE_DEVICE_CHANGE, SENDMSG_QUEUE, btDeviceInfo);
     }
 
-    //###
-    // must be called synchronized on mConnectedDevices
     /*package*/ boolean hasScheduledA2dpDockTimeout() {
         return mBrokerHandler.hasMessages(MSG_IL_BTA2DP_DOCK_TIMEOUT);
     }
@@ -486,19 +525,19 @@
     }
 
     /*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) {
-        synchronized (mA2dpAvrcpLock) {
+        synchronized (mDeviceStateLock) {
             mBtHelper.setAvrcpAbsoluteVolumeSupported(supported);
         }
     }
 
     /*package*/ boolean getBluetoothA2dpEnabled() {
-        synchronized (mBluetoothA2dpEnabledLock) {
+        synchronized (mDeviceStateLock) {
             return mBluetoothA2dpEnabled;
         }
     }
 
     /*package*/ int getA2dpCodec(@NonNull BluetoothDevice device) {
-        synchronized (mA2dpAvrcpLock) {
+        synchronized (mDeviceStateLock) {
             return mBtHelper.getA2dpCodec(device);
         }
     }
@@ -579,71 +618,97 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_RESTORE_DEVICES:
-                    mDeviceInventory.onRestoreDevices();
-                    synchronized (mBluetoothA2dpEnabledLock) {
+                    synchronized (mDeviceStateLock) {
+                        mDeviceInventory.onRestoreDevices();
                         mBtHelper.onAudioServerDiedRestoreA2dp();
                     }
                     break;
                 case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
-                    mDeviceInventory.onSetWiredDeviceConnectionState(
-                            (AudioDeviceInventory.WiredDeviceConnectionState) msg.obj);
+                    synchronized (mDeviceStateLock) {
+                        mDeviceInventory.onSetWiredDeviceConnectionState(
+                                (AudioDeviceInventory.WiredDeviceConnectionState) msg.obj);
+                    }
                     break;
                 case MSG_I_BROADCAST_BT_CONNECTION_STATE:
-                    mBtHelper.onBroadcastScoConnectionState(msg.arg1);
+                    synchronized (mDeviceStateLock) {
+                        mBtHelper.onBroadcastScoConnectionState(msg.arg1);
+                    }
                     break;
-                case MSG_IIL_SET_FORCE_USE: // intented fall-through
+                case MSG_IIL_SET_FORCE_USE: // intended fall-through
                 case MSG_IIL_SET_FORCE_BT_A2DP_USE:
                     onSetForceUse(msg.arg1, msg.arg2, (String) msg.obj);
                     break;
                 case MSG_REPORT_NEW_ROUTES:
-                    mDeviceInventory.onReportNewRoutes();
+                    synchronized (mDeviceStateLock) {
+                        mDeviceInventory.onReportNewRoutes();
+                    }
                     break;
                 case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE:
-                    mDeviceInventory.onSetA2dpSinkConnectionState(
-                            (BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1);
+                    synchronized (mDeviceStateLock) {
+                        mDeviceInventory.onSetA2dpSinkConnectionState(
+                                (BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1);
+                    }
                     break;
                 case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE:
-                    mDeviceInventory.onSetA2dpSourceConnectionState(
-                            (BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1);
+                    synchronized (mDeviceStateLock) {
+                        mDeviceInventory.onSetA2dpSourceConnectionState(
+                                (BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1);
+                    }
                     break;
                 case MSG_IL_SET_HEARING_AID_CONNECTION_STATE:
-                    mDeviceInventory.onSetHearingAidConnectionState(
-                            (BluetoothDevice) msg.obj, msg.arg1);
+                    synchronized (mDeviceStateLock) {
+                        mDeviceInventory.onSetHearingAidConnectionState(
+                                (BluetoothDevice) msg.obj, msg.arg1);
+                    }
                     break;
                 case MSG_BT_HEADSET_CNCT_FAILED:
-                    mBtHelper.resetBluetoothSco();
+                    synchronized (mDeviceStateLock) {
+                        mBtHelper.resetBluetoothSco();
+                    }
                     break;
                 case MSG_IL_BTA2DP_DOCK_TIMEOUT:
                     // msg.obj  == address of BTA2DP device
-                    mDeviceInventory.onMakeA2dpDeviceUnavailableNow((String) msg.obj, msg.arg1);
+                    synchronized (mDeviceStateLock) {
+                        mDeviceInventory.onMakeA2dpDeviceUnavailableNow((String) msg.obj, msg.arg1);
+                    }
                     break;
                 case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
                     final int a2dpCodec;
                     final BluetoothDevice btDevice = (BluetoothDevice) msg.obj;
-                    synchronized (mA2dpAvrcpLock) {
+                    synchronized (mDeviceStateLock) {
                         a2dpCodec = mBtHelper.getA2dpCodec(btDevice);
+                        mDeviceInventory.onBluetoothA2dpDeviceConfigChange(
+                                new BtHelper.BluetoothA2dpDeviceInfo(btDevice, -1, a2dpCodec));
                     }
-                    mDeviceInventory.onBluetoothA2dpDeviceConfigChange(
-                            new BtHelper.BluetoothA2dpDeviceInfo(btDevice, -1, a2dpCodec));
                     break;
                 case MSG_BROADCAST_AUDIO_BECOMING_NOISY:
                     onSendBecomingNoisyIntent();
                     break;
                 case MSG_II_SET_HEARING_AID_VOLUME:
-                    mBtHelper.setHearingAidVolume(msg.arg1, msg.arg2);
+                    synchronized (mDeviceStateLock) {
+                        mBtHelper.setHearingAidVolume(msg.arg1, msg.arg2);
+                    }
                     break;
                 case MSG_I_SET_AVRCP_ABSOLUTE_VOLUME:
-                    mBtHelper.setAvrcpAbsoluteVolumeIndex(msg.arg1);
+                    synchronized (mDeviceStateLock) {
+                        mBtHelper.setAvrcpAbsoluteVolumeIndex(msg.arg1);
+                    }
                     break;
                 case MSG_I_DISCONNECT_BT_SCO:
-                    mBtHelper.disconnectBluetoothSco(msg.arg1);
+                    synchronized (mDeviceStateLock) {
+                        mBtHelper.disconnectBluetoothSco(msg.arg1);
+                    }
                     break;
                 case MSG_TOGGLE_HDMI:
-                    mDeviceInventory.onToggleHdmi();
+                    synchronized (mDeviceStateLock) {
+                        mDeviceInventory.onToggleHdmi();
+                    }
                     break;
                 case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE:
-                    mDeviceInventory.onBluetoothA2dpActiveDeviceChange(
-                            (BtHelper.BluetoothA2dpDeviceInfo) msg.obj);
+                    synchronized (mDeviceStateLock) {
+                        mDeviceInventory.onBluetoothA2dpActiveDeviceChange(
+                                (BtHelper.BluetoothA2dpDeviceInfo) msg.obj);
+                    }
                     break;
                 default:
                     Log.wtf(TAG, "Invalid message " + msg.what);
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 97649a7..11fdc8f 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -162,10 +162,7 @@
                 "A2DP sink connected: device addr=" + address + " state=" + state
                         + " vol=" + a2dpVolume));
 
-        final int a2dpCodec;
-        synchronized (mDeviceBroker.mA2dpAvrcpLock) {
-            a2dpCodec = btInfo.getCodec();
-        }
+        final int a2dpCodec = btInfo.getCodec();
 
         synchronized (mConnectedDevices) {
             final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
@@ -508,22 +505,20 @@
 
     /*package*/ void disconnectA2dp() {
         synchronized (mConnectedDevices) {
-            synchronized (mDeviceBroker.mA2dpAvrcpLock) {
-                final ArraySet<String> toRemove = new ArraySet<>();
-                // Disconnect ALL DEVICE_OUT_BLUETOOTH_A2DP devices
-                mConnectedDevices.values().forEach(deviceInfo -> {
-                    if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
-                        toRemove.add(deviceInfo.mDeviceAddress);
-                    }
-                });
-                if (toRemove.size() > 0) {
-                    final int delay = checkSendBecomingNoisyIntentInt(
-                            AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
-                            0, AudioSystem.DEVICE_NONE);
-                    toRemove.stream().forEach(deviceAddress ->
-                            makeA2dpDeviceUnavailableLater(deviceAddress, delay)
-                    );
+            final ArraySet<String> toRemove = new ArraySet<>();
+            // Disconnect ALL DEVICE_OUT_BLUETOOTH_A2DP devices
+            mConnectedDevices.values().forEach(deviceInfo -> {
+                if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
+                    toRemove.add(deviceInfo.mDeviceAddress);
                 }
+            });
+            if (toRemove.size() > 0) {
+                final int delay = checkSendBecomingNoisyIntentInt(
+                        AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+                        AudioService.CONNECTION_STATE_DISCONNECTED, AudioSystem.DEVICE_NONE);
+                toRemove.stream().forEach(deviceAddress ->
+                        makeA2dpDeviceUnavailableLater(deviceAddress, delay)
+                );
             }
         }
     }
@@ -543,22 +538,20 @@
 
     /*package*/ void disconnectHearingAid() {
         synchronized (mConnectedDevices) {
-            synchronized (mDeviceBroker.mHearingAidLock) {
-                final ArraySet<String> toRemove = new ArraySet<>();
-                // Disconnect ALL DEVICE_OUT_HEARING_AID devices
-                mConnectedDevices.values().forEach(deviceInfo -> {
-                    if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) {
-                        toRemove.add(deviceInfo.mDeviceAddress);
-                    }
-                });
-                if (toRemove.size() > 0) {
-                    final int delay = checkSendBecomingNoisyIntentInt(
-                            AudioSystem.DEVICE_OUT_HEARING_AID, 0, AudioSystem.DEVICE_NONE);
-                    toRemove.stream().forEach(deviceAddress ->
-                            // TODO delay not used?
-                            makeHearingAidDeviceUnavailable(deviceAddress /*, delay*/)
-                    );
+            final ArraySet<String> toRemove = new ArraySet<>();
+            // Disconnect ALL DEVICE_OUT_HEARING_AID devices
+            mConnectedDevices.values().forEach(deviceInfo -> {
+                if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) {
+                    toRemove.add(deviceInfo.mDeviceAddress);
                 }
+            });
+            if (toRemove.size() > 0) {
+                final int delay = checkSendBecomingNoisyIntentInt(
+                        AudioSystem.DEVICE_OUT_HEARING_AID, 0, AudioSystem.DEVICE_NONE);
+                toRemove.stream().forEach(deviceAddress ->
+                        // TODO delay not used?
+                        makeHearingAidDeviceUnavailable(deviceAddress /*, delay*/)
+                );
             }
         }
     }
@@ -566,7 +559,8 @@
     // must be called before removing the device from mConnectedDevices
     // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying
     // from AudioSystem
-    /*package*/ int checkSendBecomingNoisyIntent(int device, int state, int musicDevice) {
+    /*package*/ int checkSendBecomingNoisyIntent(int device,
+            @AudioService.ConnectionState int state, int musicDevice) {
         synchronized (mConnectedDevices) {
             return checkSendBecomingNoisyIntentInt(device, state, musicDevice);
         }
@@ -840,8 +834,9 @@
     // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying
     // from AudioSystem
     @GuardedBy("mConnectedDevices")
-    private int checkSendBecomingNoisyIntentInt(int device, int state, int musicDevice) {
-        if (state != 0) {
+    private int checkSendBecomingNoisyIntentInt(int device,
+            @AudioService.ConnectionState int state, int musicDevice) {
+        if (state != AudioService.CONNECTION_STATE_DISCONNECTED) {
             return 0;
         }
         if ((device & mBecomingNoisyIntentDevices) == 0) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 9457fe3..251cb61 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -786,7 +786,6 @@
                 mPrescaleAbsoluteVolume[i] = preScale[i];
             }
         }
-
     }
 
     public void systemReady() {
@@ -3821,7 +3820,6 @@
 
     private static void sendMsg(Handler handler, int msg,
             int existingMsgPolicy, int arg1, int arg2, Object obj, int delay) {
-
         if (existingMsgPolicy == SENDMSG_REPLACE) {
             handler.removeMessages(msg);
         } else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) {
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index bf325013..95df21e 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -16,6 +16,7 @@
 package com.android.server.audio;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
@@ -35,8 +36,6 @@
 import android.provider.Settings;
 import android.util.Log;
 
-import com.android.internal.annotations.GuardedBy;
-
 import java.util.ArrayList;
 import java.util.List;
 import java.util.NoSuchElementException;
@@ -57,21 +56,40 @@
     }
 
     // List of clients having issued a SCO start request
-    private final ArrayList<ScoClient> mScoClients = new ArrayList<ScoClient>();
+    private final @NonNull ArrayList<ScoClient> mScoClients = new ArrayList<ScoClient>();
 
     // BluetoothHeadset API to control SCO connection
-    private BluetoothHeadset mBluetoothHeadset;
+    private @Nullable BluetoothHeadset mBluetoothHeadset;
 
     // Bluetooth headset device
-    private BluetoothDevice mBluetoothHeadsetDevice;
+    private @Nullable BluetoothDevice mBluetoothHeadsetDevice;
+
+    private @Nullable BluetoothHearingAid mHearingAid;
+
+    // Reference to BluetoothA2dp to query for AbsoluteVolume.
+    private @Nullable BluetoothA2dp mA2dp;
+
+    // If absolute volume is supported in AVRCP device
+    private boolean mAvrcpAbsVolSupported = false;
+
+    // Current connection state indicated by bluetooth headset
+    private int mScoConnectionState;
 
     // Indicate if SCO audio connection is currently active and if the initiator is
     // audio service (internal) or bluetooth headset (external)
     private int mScoAudioState;
+
+    // Indicates the mode used for SCO audio connection. The mode is virtual call if the request
+    // originated from an app targeting an API version before JB MR2 and raw audio after that.
+    private int mScoAudioMode;
+
     // SCO audio state is not active
     private static final int SCO_STATE_INACTIVE = 0;
     // SCO audio activation request waiting for headset service to connect
     private static final int SCO_STATE_ACTIVATE_REQ = 1;
+    // SCO audio state is active due to an action in BT handsfree (either voice recognition or
+    // in call audio)
+    private static final int SCO_STATE_ACTIVE_EXTERNAL = 2;
     // SCO audio state is active or starting due to a request from AudioManager API
     private static final int SCO_STATE_ACTIVE_INTERNAL = 3;
     // SCO audio deactivation request waiting for headset service to connect
@@ -79,39 +97,19 @@
     // SCO audio deactivation in progress, waiting for Bluetooth audio intent
     private static final int SCO_STATE_DEACTIVATING = 5;
 
-    // SCO audio state is active due to an action in BT handsfree (either voice recognition or
-    // in call audio)
-    private static final int SCO_STATE_ACTIVE_EXTERNAL = 2;
-
-    // Indicates the mode used for SCO audio connection. The mode is virtual call if the request
-    // originated from an app targeting an API version before JB MR2 and raw audio after that.
-    private int mScoAudioMode;
     // SCO audio mode is undefined
-    /*package*/   static final int SCO_MODE_UNDEFINED = -1;
+    /*package*/  static final int SCO_MODE_UNDEFINED = -1;
     // SCO audio mode is virtual voice call (BluetoothHeadset.startScoUsingVirtualVoiceCall())
     /*package*/  static final int SCO_MODE_VIRTUAL_CALL = 0;
     // SCO audio mode is raw audio (BluetoothHeadset.connectAudio())
     private  static final int SCO_MODE_RAW = 1;
     // SCO audio mode is Voice Recognition (BluetoothHeadset.startVoiceRecognition())
     private  static final int SCO_MODE_VR = 2;
-
+    // max valid SCO audio mode values
     private static final int SCO_MODE_MAX = 2;
 
-    // Current connection state indicated by bluetooth headset
-    private int mScoConnectionState;
-
     private static final int BT_HEARING_AID_GAIN_MIN = -128;
 
-    @GuardedBy("mDeviceBroker.mHearingAidLock")
-    private BluetoothHearingAid mHearingAid;
-
-    // Reference to BluetoothA2dp to query for AbsoluteVolume.
-    @GuardedBy("mDeviceBroker.mA2dpAvrcpLock")
-    private BluetoothA2dp mA2dp;
-    // If absolute volume is supported in AVRCP device
-    @GuardedBy("mDeviceBroker.mA2dpAvrcpLock")
-    private boolean mAvrcpAbsVolSupported = false;
-
     //----------------------------------------------------------------------
     /*package*/ static class BluetoothA2dpDeviceInfo {
         private final @NonNull BluetoothDevice mBtDevice;
@@ -144,7 +142,7 @@
     //----------------------------------------------------------------------
     // Interface for AudioDeviceBroker
 
-    /*package*/ void onSystemReady() {
+    /*package*/ synchronized void onSystemReady() {
         mScoConnectionState = android.media.AudioManager.SCO_AUDIO_STATE_ERROR;
         resetBluetoothSco();
         getBluetoothHeadset();
@@ -165,45 +163,39 @@
         }
     }
 
-    @GuardedBy("mBluetoothA2dpEnabledLock")
-    /*package*/ void onAudioServerDiedRestoreA2dp() {
+    /*package*/ synchronized void onAudioServerDiedRestoreA2dp() {
         final int forMed = mDeviceBroker.getBluetoothA2dpEnabled()
                 ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP;
         mDeviceBroker.setForceUse_Async(AudioSystem.FOR_MEDIA, forMed, "onAudioServerDied()");
     }
 
-    @GuardedBy("mA2dpAvrcpLock")
-    /*package*/ boolean isAvrcpAbsoluteVolumeSupported() {
+    /*package*/ synchronized boolean isAvrcpAbsoluteVolumeSupported() {
         return (mA2dp != null && mAvrcpAbsVolSupported);
     }
 
-    @GuardedBy("mA2dpAvrcpLock")
-    /*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) {
+    /*package*/ synchronized void setAvrcpAbsoluteVolumeSupported(boolean supported) {
         mAvrcpAbsVolSupported = supported;
     }
 
-    /*package*/ void setAvrcpAbsoluteVolumeIndex(int index) {
-        synchronized (mDeviceBroker.mA2dpAvrcpLock) {
-            if (mA2dp == null) {
-                if (AudioService.DEBUG_VOL) {
-                    Log.d(TAG, "setAvrcpAbsoluteVolumeIndex: bailing due to null mA2dp");
-                    return;
-                }
-            }
-            if (!mAvrcpAbsVolSupported) {
+    /*package*/ synchronized void setAvrcpAbsoluteVolumeIndex(int index) {
+        if (mA2dp == null) {
+            if (AudioService.DEBUG_VOL) {
+                Log.d(TAG, "setAvrcpAbsoluteVolumeIndex: bailing due to null mA2dp");
                 return;
             }
-            if (AudioService.DEBUG_VOL) {
-                Log.i(TAG, "setAvrcpAbsoluteVolumeIndex index=" + index);
-            }
-            AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent(
-                    AudioServiceEvents.VolumeEvent.VOL_SET_AVRCP_VOL, index / 10));
-            mA2dp.setAvrcpAbsoluteVolume(index / 10);
         }
+        if (!mAvrcpAbsVolSupported) {
+            return;
+        }
+        if (AudioService.DEBUG_VOL) {
+            Log.i(TAG, "setAvrcpAbsoluteVolumeIndex index=" + index);
+        }
+        AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent(
+                AudioServiceEvents.VolumeEvent.VOL_SET_AVRCP_VOL, index / 10));
+        mA2dp.setAvrcpAbsoluteVolume(index / 10);
     }
 
-    @GuardedBy("mA2dpAvrcpLock")
-    /*package*/ int getA2dpCodec(@NonNull BluetoothDevice device) {
+    /*package*/ synchronized int getA2dpCodec(@NonNull BluetoothDevice device) {
         if (mA2dp == null) {
             return AudioSystem.AUDIO_FORMAT_DEFAULT;
         }
@@ -218,7 +210,7 @@
         return mapBluetoothCodecToAudioFormat(btCodecConfig.getCodecType());
     }
 
-    /*package*/ void receiveBtEvent(Intent intent) {
+    /*package*/ synchronized void receiveBtEvent(Intent intent) {
         final String action = intent.getAction();
         if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) {
             BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
@@ -226,53 +218,51 @@
         } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
             boolean broadcast = false;
             int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR;
-            synchronized (mScoClients) {
-                int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
-                // broadcast intent if the connection was initated by AudioService
-                if (!mScoClients.isEmpty()
-                        && (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL
-                                || mScoAudioState == SCO_STATE_ACTIVATE_REQ
-                                || mScoAudioState == SCO_STATE_DEACTIVATE_REQ
-                                || mScoAudioState == SCO_STATE_DEACTIVATING)) {
-                    broadcast = true;
-                }
-                switch (btState) {
-                    case BluetoothHeadset.STATE_AUDIO_CONNECTED:
-                        scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED;
-                        if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL
-                                && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
-                            mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+            int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
+            // broadcast intent if the connection was initated by AudioService
+            if (!mScoClients.isEmpty()
+                    && (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL
+                    || mScoAudioState == SCO_STATE_ACTIVATE_REQ
+                    || mScoAudioState == SCO_STATE_DEACTIVATE_REQ
+                    || mScoAudioState == SCO_STATE_DEACTIVATING)) {
+                broadcast = true;
+            }
+            switch (btState) {
+                case BluetoothHeadset.STATE_AUDIO_CONNECTED:
+                    scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED;
+                    if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL
+                            && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
+                        mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+                    }
+                    mDeviceBroker.setBluetoothScoOn(true, "BtHelper.receiveBtEvent");
+                    break;
+                case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
+                    mDeviceBroker.setBluetoothScoOn(false, "BtHelper.receiveBtEvent");
+                    scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
+                    // startBluetoothSco called after stopBluetoothSco
+                    if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) {
+                        if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null
+                                && connectBluetoothScoAudioHelper(mBluetoothHeadset,
+                                mBluetoothHeadsetDevice, mScoAudioMode)) {
+                            mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+                            broadcast = false;
+                            break;
                         }
-                        mDeviceBroker.setBluetoothScoOn(true, "BtHelper.receiveBtEvent");
-                        break;
-                    case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
-                        mDeviceBroker.setBluetoothScoOn(false, "BtHelper.receiveBtEvent");
-                        scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
-                        // startBluetoothSco called after stopBluetoothSco
-                        if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) {
-                            if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null
-                                    && connectBluetoothScoAudioHelper(mBluetoothHeadset,
-                                            mBluetoothHeadsetDevice, mScoAudioMode)) {
-                                mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
-                                broadcast = false;
-                                break;
-                            }
-                        }
-                        // Tear down SCO if disconnected from external
-                        clearAllScoClients(0, mScoAudioState == SCO_STATE_ACTIVE_INTERNAL);
-                        mScoAudioState = SCO_STATE_INACTIVE;
-                        break;
-                    case BluetoothHeadset.STATE_AUDIO_CONNECTING:
-                        if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL
-                                && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
-                            mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
-                        }
-                        break;
-                    default:
-                        // do not broadcast CONNECTING or invalid state
-                        broadcast = false;
-                        break;
-                }
+                    }
+                    // Tear down SCO if disconnected from external
+                    clearAllScoClients(0, mScoAudioState == SCO_STATE_ACTIVE_INTERNAL);
+                    mScoAudioState = SCO_STATE_INACTIVE;
+                    break;
+                case BluetoothHeadset.STATE_AUDIO_CONNECTING:
+                    if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL
+                            && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
+                        mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+                    }
+                    break;
+                default:
+                    // do not broadcast CONNECTING or invalid state
+                    broadcast = false;
+                    break;
             }
             if (broadcast) {
                 broadcastScoConnectionState(scoAudioState);
@@ -289,15 +279,13 @@
      *
      * @return false if SCO isn't connected
      */
-    /*package*/ boolean isBluetoothScoOn() {
-        synchronized (mScoClients) {
-            if ((mBluetoothHeadset != null)
-                    && (mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
-                            != BluetoothHeadset.STATE_AUDIO_CONNECTED)) {
-                Log.w(TAG, "isBluetoothScoOn(true) returning false because "
-                        + mBluetoothHeadsetDevice + " is not in audio connected mode");
-                return false;
-            }
+    /*package*/ synchronized boolean isBluetoothScoOn() {
+        if ((mBluetoothHeadset != null)
+                && (mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
+                != BluetoothHeadset.STATE_AUDIO_CONNECTED)) {
+            Log.w(TAG, "isBluetoothScoOn(true) returning false because "
+                    + mBluetoothHeadsetDevice + " is not in audio connected mode");
+            return false;
         }
         return true;
     }
@@ -308,17 +296,15 @@
      *
      * @param exceptPid pid whose SCO connections through {@link AudioManager} should be kept
      */
-    /*package*/ void disconnectBluetoothSco(int exceptPid) {
-        synchronized (mScoClients) {
-            checkScoAudioState();
-            if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL) {
-                return;
-            }
-            clearAllScoClients(exceptPid, true);
+    /*package*/ synchronized void disconnectBluetoothSco(int exceptPid) {
+        checkScoAudioState();
+        if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL) {
+            return;
         }
+        clearAllScoClients(exceptPid, true);
     }
 
-    /*package*/ void startBluetoothScoForClient(IBinder cb, int scoAudioMode,
+    /*package*/ synchronized void startBluetoothScoForClient(IBinder cb, int scoAudioMode,
                 @NonNull String eventSource) {
         ScoClient client = getScoClient(cb, true);
         // The calling identity must be cleared before calling ScoClient.incCount().
@@ -337,7 +323,8 @@
         Binder.restoreCallingIdentity(ident);
     }
 
-    /*package*/ void stopBluetoothScoForClient(IBinder cb, @NonNull String eventSource) {
+    /*package*/ synchronized void stopBluetoothScoForClient(IBinder cb,
+            @NonNull String eventSource) {
         ScoClient client = getScoClient(cb, false);
         // The calling identity must be cleared before calling ScoClient.decCount().
         // decCount() calls requestScoState() which in turn can call BluetoothHeadset APIs
@@ -352,36 +339,29 @@
     }
 
 
-    /*package*/ void setHearingAidVolume(int index, int streamType) {
-        synchronized (mDeviceBroker.mHearingAidLock) {
-            if (mHearingAid == null) {
-                if (AudioService.DEBUG_VOL) {
-                    Log.i(TAG, "setHearingAidVolume: null mHearingAid");
-                }
-                return;
-            }
-            //hearing aid expect volume value in range -128dB to 0dB
-            int gainDB = (int) AudioSystem.getStreamVolumeDB(streamType, index / 10,
-                    AudioSystem.DEVICE_OUT_HEARING_AID);
-            if (gainDB < BT_HEARING_AID_GAIN_MIN) {
-                gainDB = BT_HEARING_AID_GAIN_MIN;
-            }
+    /*package*/ synchronized void setHearingAidVolume(int index, int streamType) {
+        if (mHearingAid == null) {
             if (AudioService.DEBUG_VOL) {
-                Log.i(TAG, "setHearingAidVolume: calling mHearingAid.setVolume idx="
-                        + index + " gain=" + gainDB);
+                Log.i(TAG, "setHearingAidVolume: null mHearingAid");
             }
-            AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent(
-                    AudioServiceEvents.VolumeEvent.VOL_SET_HEARING_AID_VOL, index, gainDB));
-            mHearingAid.setVolume(gainDB);
+            return;
         }
+        //hearing aid expect volume value in range -128dB to 0dB
+        int gainDB = (int) AudioSystem.getStreamVolumeDB(streamType, index / 10,
+                AudioSystem.DEVICE_OUT_HEARING_AID);
+        if (gainDB < BT_HEARING_AID_GAIN_MIN) {
+            gainDB = BT_HEARING_AID_GAIN_MIN;
+        }
+        if (AudioService.DEBUG_VOL) {
+            Log.i(TAG, "setHearingAidVolume: calling mHearingAid.setVolume idx="
+                    + index + " gain=" + gainDB);
+        }
+        AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent(
+                AudioServiceEvents.VolumeEvent.VOL_SET_HEARING_AID_VOL, index, gainDB));
+        mHearingAid.setVolume(gainDB);
     }
 
-    //----------------------------------------------------------------------
-    private void broadcastScoConnectionState(int state) {
-        mDeviceBroker.broadcastScoConnectionState(state);
-    }
-
-    /*package*/ void onBroadcastScoConnectionState(int state) {
+    /*package*/ synchronized void onBroadcastScoConnectionState(int state) {
         if (state == mScoConnectionState) {
             return;
         }
@@ -393,6 +373,26 @@
         mScoConnectionState = state;
     }
 
+    /*package*/ synchronized void disconnectAllBluetoothProfiles() {
+        mDeviceBroker.handleDisconnectA2dp();
+        mDeviceBroker.handleDisconnectA2dpSink();
+        disconnectHeadset();
+        mDeviceBroker.handleDisconnectHearingAid();
+    }
+
+    /*package*/ synchronized void resetBluetoothSco() {
+        clearAllScoClients(0, false);
+        mScoAudioState = SCO_STATE_INACTIVE;
+        broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+        AudioSystem.setParameters("A2dpSuspended=false");
+        mDeviceBroker.setBluetoothScoOn(false, "resetBluetoothSco");
+    }
+
+    //----------------------------------------------------------------------
+    private void broadcastScoConnectionState(int state) {
+        mDeviceBroker.broadcastScoConnectionState(state);
+    }
+
     private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive) {
         if (btDevice == null) {
             return true;
@@ -437,25 +437,23 @@
     }
 
     private void setBtScoActiveDevice(BluetoothDevice btDevice) {
-        synchronized (mScoClients) {
-            Log.i(TAG, "setBtScoActiveDevice: " + mBluetoothHeadsetDevice + " -> " + btDevice);
-            final BluetoothDevice previousActiveDevice = mBluetoothHeadsetDevice;
-            if (Objects.equals(btDevice, previousActiveDevice)) {
-                return;
-            }
-            if (!handleBtScoActiveDeviceChange(previousActiveDevice, false)) {
-                Log.w(TAG, "setBtScoActiveDevice() failed to remove previous device "
-                        + previousActiveDevice);
-            }
-            if (!handleBtScoActiveDeviceChange(btDevice, true)) {
-                Log.e(TAG, "setBtScoActiveDevice() failed to add new device " + btDevice);
-                // set mBluetoothHeadsetDevice to null when failing to add new device
-                btDevice = null;
-            }
-            mBluetoothHeadsetDevice = btDevice;
-            if (mBluetoothHeadsetDevice == null) {
-                resetBluetoothSco();
-            }
+        Log.i(TAG, "setBtScoActiveDevice: " + mBluetoothHeadsetDevice + " -> " + btDevice);
+        final BluetoothDevice previousActiveDevice = mBluetoothHeadsetDevice;
+        if (Objects.equals(btDevice, previousActiveDevice)) {
+            return;
+        }
+        if (!handleBtScoActiveDeviceChange(previousActiveDevice, false)) {
+            Log.w(TAG, "setBtScoActiveDevice() failed to remove previous device "
+                    + previousActiveDevice);
+        }
+        if (!handleBtScoActiveDeviceChange(btDevice, true)) {
+            Log.e(TAG, "setBtScoActiveDevice() failed to add new device " + btDevice);
+            // set mBluetoothHeadsetDevice to null when failing to add new device
+            btDevice = null;
+        }
+        mBluetoothHeadsetDevice = btDevice;
+        if (mBluetoothHeadsetDevice == null) {
+            resetBluetoothSco();
         }
     }
 
@@ -466,7 +464,7 @@
                     List<BluetoothDevice> deviceList;
                     switch(profile) {
                         case BluetoothProfile.A2DP:
-                            synchronized (mDeviceBroker.mA2dpAvrcpLock) {
+                            synchronized (BtHelper.this) {
                                 mA2dp = (BluetoothA2dp) proxy;
                                 deviceList = mA2dp.getConnectedDevices();
                                 if (deviceList.size() > 0) {
@@ -495,7 +493,7 @@
                             break;
 
                         case BluetoothProfile.HEADSET:
-                            synchronized (mScoClients) {
+                            synchronized (BtHelper.this) {
                                 // Discard timeout message
                                 mDeviceBroker.handleCancelFailureToConnectToBtHeadsetService();
                                 mBluetoothHeadset = (BluetoothHeadset) proxy;
@@ -536,17 +534,19 @@
                             break;
 
                         case BluetoothProfile.HEARING_AID:
-                            mHearingAid = (BluetoothHearingAid) proxy;
-                            deviceList = mHearingAid.getConnectedDevices();
-                            if (deviceList.size() > 0) {
-                                btDevice = deviceList.get(0);
-                                final @BluetoothProfile.BtProfileState int state =
-                                        mHearingAid.getConnectionState(btDevice);
-                                mDeviceBroker.setBluetoothHearingAidDeviceConnectionState(
-                                        btDevice, state,
-                                        /*suppressNoisyIntent*/ false,
-                                        /*musicDevice*/ android.media.AudioSystem.DEVICE_NONE,
-                                        /*eventSource*/ "mBluetoothProfileServiceListener");
+                            synchronized (BtHelper.this) {
+                                mHearingAid = (BluetoothHearingAid) proxy;
+                                deviceList = mHearingAid.getConnectedDevices();
+                                if (deviceList.size() > 0) {
+                                    btDevice = deviceList.get(0);
+                                    final @BluetoothProfile.BtProfileState int state =
+                                            mHearingAid.getConnectionState(btDevice);
+                                    mDeviceBroker.setBluetoothHearingAidDeviceConnectionState(
+                                            btDevice, state,
+                                            /*suppressNoisyIntent*/ false,
+                                            /*musicDevice*/ android.media.AudioSystem.DEVICE_NONE,
+                                            /*eventSource*/ "mBluetoothProfileServiceListener");
+                                }
                             }
                             break;
 
@@ -579,18 +579,9 @@
                 }
             };
 
-    void disconnectAllBluetoothProfiles() {
-        mDeviceBroker.handleDisconnectA2dp();
-        mDeviceBroker.handleDisconnectA2dpSink();
-        disconnectHeadset();
-        mDeviceBroker.handleDisconnectHearingAid();
-    }
-
     private void disconnectHeadset() {
-        synchronized (mScoClients) {
-            setBtScoActiveDevice(null);
-            mBluetoothHeadset = null;
-        }
+        setBtScoActiveDevice(null);
+        mBluetoothHeadset = null;
     }
 
     //----------------------------------------------------------------------
@@ -605,8 +596,12 @@
             mStartcount = 0;
         }
 
+        @Override
         public void binderDied() {
-            synchronized (mScoClients) {
+            // this is the only place the implementation of ScoClient needs to be synchronized
+            // on the instance, as all other methods are directly or indirectly called from
+            // package-private methods, which are synchronized
+            synchronized (BtHelper.this) {
                 Log.w(TAG, "SCO client died");
                 int index = mScoClients.indexOf(this);
                 if (index < 0) {
@@ -618,77 +613,69 @@
             }
         }
 
-        public void incCount(int scoAudioMode) {
-            synchronized (mScoClients) {
-                requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode);
-                if (mStartcount == 0) {
-                    try {
-                        mCb.linkToDeath(this, 0);
-                    } catch (RemoteException e) {
-                        // client has already died!
-                        Log.w(TAG, "ScoClient  incCount() could not link to "
-                                + mCb + " binder death");
-                    }
-                }
-                mStartcount++;
-            }
-        }
-
-        public void decCount() {
-            synchronized (mScoClients) {
-                if (mStartcount == 0) {
-                    Log.w(TAG, "ScoClient.decCount() already 0");
-                } else {
-                    mStartcount--;
-                    if (mStartcount == 0) {
-                        try {
-                            mCb.unlinkToDeath(this, 0);
-                        } catch (NoSuchElementException e) {
-                            Log.w(TAG, "decCount() going to 0 but not registered to binder");
-                        }
-                    }
-                    requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0);
+        void incCount(int scoAudioMode) {
+            requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode);
+            if (mStartcount == 0) {
+                try {
+                    mCb.linkToDeath(this, 0);
+                } catch (RemoteException e) {
+                    // client has already died!
+                    Log.w(TAG, "ScoClient  incCount() could not link to "
+                            + mCb + " binder death");
                 }
             }
+            mStartcount++;
         }
 
-        public void clearCount(boolean stopSco) {
-            synchronized (mScoClients) {
-                if (mStartcount != 0) {
+        void decCount() {
+            if (mStartcount == 0) {
+                Log.w(TAG, "ScoClient.decCount() already 0");
+            } else {
+                mStartcount--;
+                if (mStartcount == 0) {
                     try {
                         mCb.unlinkToDeath(this, 0);
                     } catch (NoSuchElementException e) {
-                        Log.w(TAG, "clearCount() mStartcount: "
-                                + mStartcount + " != 0 but not registered to binder");
+                        Log.w(TAG, "decCount() going to 0 but not registered to binder");
                     }
                 }
-                mStartcount = 0;
-                if (stopSco) {
-                    requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0);
-                }
+                requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0);
             }
         }
 
-        public int getCount() {
+        void clearCount(boolean stopSco) {
+            if (mStartcount != 0) {
+                try {
+                    mCb.unlinkToDeath(this, 0);
+                } catch (NoSuchElementException e) {
+                    Log.w(TAG, "clearCount() mStartcount: "
+                            + mStartcount + " != 0 but not registered to binder");
+                }
+            }
+            mStartcount = 0;
+            if (stopSco) {
+                requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0);
+            }
+        }
+
+        int getCount() {
             return mStartcount;
         }
 
-        public IBinder getBinder() {
+        IBinder getBinder() {
             return mCb;
         }
 
-        public int getPid() {
+        int getPid() {
             return mCreatorPid;
         }
 
-        public int totalCount() {
-            synchronized (mScoClients) {
-                int count = 0;
-                for (ScoClient mScoClient : mScoClients) {
-                    count += mScoClient.getCount();
-                }
-                return count;
+        private int totalCount() {
+            int count = 0;
+            for (ScoClient mScoClient : mScoClients) {
+                count += mScoClient.getCount();
             }
+            return count;
         }
 
         private void requestScoState(int state, int scoAudioMode) {
@@ -705,6 +692,7 @@
                 broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING);
                 // Accept SCO audio activation only in NORMAL audio mode or if the mode is
                 // currently controlled by the same client process.
+                // TODO do not sync that way, see b/123769055
                 synchronized (mDeviceBroker.mSetModeLock) {
                     int modeOwnerPid =  mDeviceBroker.getSetModeDeathHandlers().isEmpty()
                             ? 0 : mDeviceBroker.getSetModeDeathHandlers().get(0).getPid();
@@ -857,60 +845,43 @@
         }
     }
 
-    /*package*/ void resetBluetoothSco() {
-        synchronized (mScoClients) {
-            clearAllScoClients(0, false);
-            mScoAudioState = SCO_STATE_INACTIVE;
-            broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
-        }
-        AudioSystem.setParameters("A2dpSuspended=false");
-        mDeviceBroker.setBluetoothScoOn(false, "resetBluetoothSco");
-    }
-
-
     private void checkScoAudioState() {
-        synchronized (mScoClients) {
-            if (mBluetoothHeadset != null
-                    && mBluetoothHeadsetDevice != null
-                    && mScoAudioState == SCO_STATE_INACTIVE
-                    && mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
-                            != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
-                mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
-            }
+        if (mBluetoothHeadset != null
+                && mBluetoothHeadsetDevice != null
+                && mScoAudioState == SCO_STATE_INACTIVE
+                && mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
+                != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
+            mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
         }
     }
 
 
     private ScoClient getScoClient(IBinder cb, boolean create) {
-        synchronized (mScoClients) {
-            for (ScoClient existingClient : mScoClients) {
-                if (existingClient.getBinder() == cb) {
-                    return existingClient;
-                }
+        for (ScoClient existingClient : mScoClients) {
+            if (existingClient.getBinder() == cb) {
+                return existingClient;
             }
-            if (create) {
-                ScoClient newClient = new ScoClient(cb);
-                mScoClients.add(newClient);
-                return newClient;
-            }
-            return null;
         }
+        if (create) {
+            ScoClient newClient = new ScoClient(cb);
+            mScoClients.add(newClient);
+            return newClient;
+        }
+        return null;
     }
 
     private void clearAllScoClients(int exceptPid, boolean stopSco) {
-        synchronized (mScoClients) {
-            ScoClient savedClient = null;
-            for (ScoClient cl : mScoClients) {
-                if (cl.getPid() != exceptPid) {
-                    cl.clearCount(stopSco);
-                } else {
-                    savedClient = cl;
-                }
+        ScoClient savedClient = null;
+        for (ScoClient cl : mScoClients) {
+            if (cl.getPid() != exceptPid) {
+                cl.clearCount(stopSco);
+            } else {
+                savedClient = cl;
             }
-            mScoClients.clear();
-            if (savedClient != null) {
-                mScoClients.add(savedClient);
-            }
+        }
+        mScoClients.clear();
+        if (savedClient != null) {
+            mScoClients.add(savedClient);
         }
     }
 
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index cefe583..19d10ec 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -509,7 +509,7 @@
         private static final float DEFAULT_CONN_CONGESTION_DELAY_FRAC = 0.5f;
         private static final float DEFAULT_CONN_PREFETCH_RELAX_FRAC = 0.5f;
         private static final boolean DEFAULT_USE_HEARTBEATS = false;
-        private static final boolean DEFAULT_TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true;
+        private static final boolean DEFAULT_TIME_CONTROLLER_SKIP_NOT_READY_JOBS = false;
         private static final long DEFAULT_QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS =
                 10 * 60 * 1000L; // 10 minutes
         private static final long DEFAULT_QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS =
diff --git a/services/core/java/com/android/server/pm/permission/TEST_MAPPING b/services/core/java/com/android/server/pm/permission/TEST_MAPPING
index 09bacd6..0892b32 100644
--- a/services/core/java/com/android/server/pm/permission/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/permission/TEST_MAPPING
@@ -18,6 +18,14 @@
                     "include-filter": "android.permission.cts.SplitPermissionTest"
                 }
             ]
+        },
+        {
+            "name": "CtsStatsdHostTestCases",
+            "options": [
+                {
+                    "include-filter": "android.cts.statsd.atom.UidAtomTests#testDangerousPermissionState"
+                }
+            ]
         }
     ]
 }
\ No newline at end of file
diff --git a/services/core/java/com/android/server/role/TEST_MAPPING b/services/core/java/com/android/server/role/TEST_MAPPING
new file mode 100644
index 0000000..9efd292
--- /dev/null
+++ b/services/core/java/com/android/server/role/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+    "presubmit": [
+        {
+            "name": "CtsStatsdHostTestCases",
+            "options": [
+                {
+                    "include-filter": "android.cts.statsd.atom.UidAtomTests#testRoleHolder"
+                }
+            ]
+        },
+        {
+            "name": "CtsRoleTestCases"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 146a8ed..3bb6608 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -4433,7 +4433,8 @@
                         if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "Losing focus: " + lastFocus);
                         lastFocus.reportFocusChangedSerialized(false, mInTouchMode);
                     }
-                } break;
+                    break;
+                }
 
                 case REPORT_LOSING_FOCUS: {
                     final DisplayContent displayContent = (DisplayContent) msg.obj;
@@ -4450,7 +4451,8 @@
                                 losers.get(i));
                         losers.get(i).reportFocusChangedSerialized(false, mInTouchMode);
                     }
-                } break;
+                    break;
+                }
 
                 case WINDOW_FREEZE_TIMEOUT: {
                     final DisplayContent displayContent = (DisplayContent) msg.obj;
@@ -4614,12 +4616,13 @@
                     break;
                 }
 
-                case NOTIFY_ACTIVITY_DRAWN:
+                case NOTIFY_ACTIVITY_DRAWN: {
                     try {
                         mActivityTaskManager.notifyActivityDrawn((IBinder) msg.obj);
                     } catch (RemoteException e) {
                     }
                     break;
+                }
                 case ALL_WINDOWS_DRAWN: {
                     Runnable callback;
                     synchronized (mGlobalLock) {
@@ -4656,8 +4659,8 @@
                             }
                         }
                     }
+                    break;
                 }
-                break;
                 case CHECK_IF_BOOT_ANIMATION_FINISHED: {
                     final boolean bootAnimationComplete;
                     synchronized (mGlobalLock) {
@@ -4667,15 +4670,15 @@
                     if (bootAnimationComplete) {
                         performEnableScreen();
                     }
+                    break;
                 }
-                break;
                 case RESET_ANR_MESSAGE: {
                     synchronized (mGlobalLock) {
                         mLastANRState = null;
                     }
                     mAtmInternal.clearSavedANRState();
+                    break;
                 }
-                break;
                 case WALLPAPER_DRAW_PENDING_TIMEOUT: {
                     synchronized (mGlobalLock) {
                         final WallpaperController wallpaperController =
@@ -4685,16 +4688,16 @@
                             mWindowPlacerLocked.performSurfacePlacement();
                         }
                     }
+                    break;
                 }
-                break;
                 case UPDATE_DOCKED_STACK_DIVIDER: {
                     synchronized (mGlobalLock) {
                         final DisplayContent displayContent = getDefaultDisplayContentLocked();
                         displayContent.getDockedDividerController().reevaluateVisibility(false);
                         displayContent.adjustForImeIfNeeded();
                     }
+                    break;
                 }
-                break;
                 case WINDOW_REPLACEMENT_TIMEOUT: {
                     synchronized (mGlobalLock) {
                         for (int i = mWindowReplacementTimeouts.size() - 1; i >= 0; i--) {
@@ -4703,8 +4706,8 @@
                         }
                         mWindowReplacementTimeouts.clear();
                     }
+                    break;
                 }
-                break;
                 case WINDOW_HIDE_TIMEOUT: {
                     final WindowState window = (WindowState) msg.obj;
                     synchronized (mGlobalLock) {
@@ -4724,44 +4727,44 @@
                         window.setDisplayLayoutNeeded();
                         mWindowPlacerLocked.performSurfacePlacement();
                     }
+                    break;
                 }
-                break;
                 case RESTORE_POINTER_ICON: {
                     synchronized (mGlobalLock) {
                         restorePointerIconLocked((DisplayContent)msg.obj, msg.arg1, msg.arg2);
                     }
+                    break;
                 }
-                break;
                 case SEAMLESS_ROTATION_TIMEOUT: {
                     final DisplayContent displayContent = (DisplayContent) msg.obj;
                     synchronized (mGlobalLock) {
                         displayContent.onSeamlessRotationTimeout();
                     }
+                    break;
                 }
-                break;
                 case SET_HAS_OVERLAY_UI: {
                     mAmInternal.setHasOverlayUi(msg.arg1, msg.arg2 == 1);
+                    break;
                 }
-                break;
                 case SET_RUNNING_REMOTE_ANIMATION: {
                     mAmInternal.setRunningRemoteAnimation(msg.arg1, msg.arg2 == 1);
+                    break;
                 }
-                break;
                 case ANIMATION_FAILSAFE: {
                     synchronized (mGlobalLock) {
                         if (mRecentsAnimationController != null) {
                             mRecentsAnimationController.scheduleFailsafe();
                         }
                     }
+                    break;
                 }
-                break;
                 case RECOMPUTE_FOCUS: {
                     synchronized (mGlobalLock) {
                         updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL,
                                 true /* updateInputWindows */);
                     }
+                    break;
                 }
-                break;
             }
             if (DEBUG_WINDOW_TRACE) {
                 Slog.v(TAG_WM, "handleMessage: exit");
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index c50e6a7..c5b9cf1 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -1099,9 +1099,13 @@
          */
         public static final int DISABLED_BY_WRONG_PASSWORD = 13;
         /**
+         * This network is disabled because service is not subscribed
+         */
+        public static final int DISABLED_AUTHENTICATION_NO_SUBSCRIPTION = 14;
+        /**
          * This Maximum disable reason value
          */
-        public static final int NETWORK_SELECTION_DISABLED_MAX = 14;
+        public static final int NETWORK_SELECTION_DISABLED_MAX = 15;
 
         /**
          * Quality network selection disable reason String (for debug purpose)
@@ -1120,7 +1124,8 @@
                 "NETWORK_SELECTION_DISABLED_NO_INTERNET_PERMANENT",
                 "NETWORK_SELECTION_DISABLED_BY_WIFI_MANAGER",
                 "NETWORK_SELECTION_DISABLED_BY_USER_SWITCH",
-                "NETWORK_SELECTION_DISABLED_BY_WRONG_PASSWORD"
+                "NETWORK_SELECTION_DISABLED_BY_WRONG_PASSWORD",
+                "NETWORK_SELECTION_DISABLED_AUTHENTICATION_NO_SUBSCRIPTION"
         };
 
         /**