Merge "Privatize remaining protected members in NEM"
diff --git a/api/current.txt b/api/current.txt
index 7e0c33c..72dd139 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -42662,6 +42662,7 @@
     method public static java.lang.String capabilitiesToString(int);
     method public android.telecom.PhoneAccountHandle getAccountHandle();
     method public int getCallCapabilities();
+    method public int getCallDirection();
     method public android.telecom.CallIdentification getCallIdentification();
     method public int getCallProperties();
     method public java.lang.String getCallerDisplayName();
@@ -42698,6 +42699,9 @@
     field public static final int CAPABILITY_SUPPORT_DEFLECT = 16777216; // 0x1000000
     field public static final int CAPABILITY_SUPPORT_HOLD = 2; // 0x2
     field public static final int CAPABILITY_SWAP_CONFERENCE = 8; // 0x8
+    field public static final int DIRECTION_INCOMING = 0; // 0x0
+    field public static final int DIRECTION_OUTGOING = 1; // 0x1
+    field public static final int DIRECTION_UNKNOWN = -1; // 0xffffffff
     field public static final int PROPERTY_CONFERENCE = 1; // 0x1
     field public static final int PROPERTY_EMERGENCY_CALLBACK_MODE = 4; // 0x4
     field public static final int PROPERTY_ENTERPRISE_CALL = 32; // 0x20
diff --git a/core/java/android/app/role/RoleManager.java b/core/java/android/app/role/RoleManager.java
index 2d630a6..d73e73f 100644
--- a/core/java/android/app/role/RoleManager.java
+++ b/core/java/android/app/role/RoleManager.java
@@ -67,6 +67,56 @@
     private static final String LOG_TAG = RoleManager.class.getSimpleName();
 
     /**
+     * The name of the proxy calling role.
+     * <p>
+     * A proxy calling app implements the {@link android.telecom.CallRedirectionService} API and
+     * provides a means to re-write the phone number for an outgoing call to place the call through
+     * a proxy calling service.
+     * <p>
+     * A single app may fill this role at any one time.
+     * @hide
+     */
+    public static final String ROLE_PROXY_CALLING_APP = "android.app.role.PROXY_CALLING_APP";
+
+    /**
+     * The name of the call screening and caller id role.
+     * <p>
+     * A call screening and caller id app implements the
+     * {@link android.telecom.CallScreeningService} API.
+     * <p>
+     * A single app may fill this role at any one time.
+     * @hide
+     */
+    public static final String ROLE_CALL_SCREENING_APP = "android.app.role.CALL_SCREENING_APP";
+
+    /**
+     * The name of the call companion app role.
+     * <p>
+     * A call companion app provides no user interface for calls, but will be bound to by Telecom
+     * when there are active calls on the device.  Companion apps for wearable devices are an
+     * acceptable use-case.  A call companion app implements the
+     * {@link android.telecom.InCallService} API.
+     * <p>
+     * Multiple apps app may fill this role at any one time.
+     * @hide
+     */
+    public static final String ROLE_CALL_COMPANION_APP = "android.app.role.CALL_COMPANION_APP";
+
+    /**
+     * The name of the car mode dialer app role.
+     * <p>
+     * Similar to the {@link #ROLE_DIALER} role, this role determines which app is responsible for
+     * showing the user interface for ongoing calls on the device.  This app filling this role is
+     * only used when the device is in car mode (see
+     * {@link android.app.UiModeManager#ACTION_ENTER_CAR_MODE} for more information).  An app
+     * filling this role must implement the {@link android.telecom.InCallService} API.
+     * <p>
+     * A single app may fill this role at any one time.
+     * @hide
+     */
+    public static final String ROLE_CAR_MODE_DIALER_APP = "android.app.role.CAR_MODE_DIALER_APP";
+
+    /**
      * The name of the dialer role.
      */
     public static final String ROLE_DIALER = "android.app.role.DIALER";
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 2b266b7..c984651 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1621,7 +1621,7 @@
             }
 
             final AttributeSet attrs = parser;
-            return parseApkLite(apkPath, parser, attrs, signingDetails);
+            return parseApkLite(apkPath, parser, attrs, signingDetails, flags);
 
         } catch (XmlPullParserException | IOException | RuntimeException e) {
             Slog.w(TAG, "Failed to parse " + apkPath, e);
@@ -1708,7 +1708,7 @@
     }
 
     private static ApkLite parseApkLite(String codePath, XmlPullParser parser, AttributeSet attrs,
-            SigningDetails signingDetails)
+            SigningDetails signingDetails, int flags)
             throws IOException, XmlPullParserException, PackageParserException {
         final Pair<String, String> packageSplit = parsePackageSplitNames(parser, attrs);
 
@@ -1716,11 +1716,12 @@
         int versionCode = 0;
         int versionCodeMajor = 0;
         int revisionCode = 0;
+        int targetSdkVersion = 0;
         boolean coreApp = false;
         boolean debuggable = false;
         boolean multiArch = false;
         boolean use32bitAbi = false;
-        boolean extractNativeLibs = true;
+        Boolean extractNativeLibsProvided = null;
         boolean isolatedSplits = false;
         boolean isFeatureSplit = false;
         boolean isSplitRequired = false;
@@ -1785,7 +1786,8 @@
                         use32bitAbi = attrs.getAttributeBooleanValue(i, false);
                     }
                     if ("extractNativeLibs".equals(attr)) {
-                        extractNativeLibs = attrs.getAttributeBooleanValue(i, true);
+                        extractNativeLibsProvided = Boolean.valueOf(
+                                attrs.getAttributeBooleanValue(i, true));
                     }
                     if ("preferCodeIntegrity".equals(attr)) {
                         preferCodeIntegrity = attrs.getAttributeBooleanValue(i, false);
@@ -1803,9 +1805,51 @@
                             PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
                             "<uses-split> tag requires 'android:name' attribute");
                 }
+            } else if (TAG_USES_SDK.equals(parser.getName())) {
+                final String[] errorMsg = new String[1];
+                Pair<Integer, Integer> versions = deriveSdkVersions(new AbstractVersionsAccessor() {
+                    @Override public String getMinSdkVersionCode() {
+                        return getAttributeAsString("minSdkVersion");
+                    }
+
+                    @Override public int getMinSdkVersion() {
+                        return getAttributeAsInt("minSdkVersion");
+                    }
+
+                    @Override public String getTargetSdkVersionCode() {
+                        return getAttributeAsString("targetSdkVersion");
+                    }
+
+                    @Override public int getTargetSdkVersion() {
+                        return getAttributeAsInt("targetSdkVersion");
+                    }
+
+                    private String getAttributeAsString(String name) {
+                        return attrs.getAttributeValue(ANDROID_RESOURCES, name);
+                    }
+
+                    private int getAttributeAsInt(String name) {
+                        try {
+                            return attrs.getAttributeIntValue(ANDROID_RESOURCES, name, -1);
+                        } catch (NumberFormatException e) {
+                            return -1;
+                        }
+                    }
+                }, flags, errorMsg);
+
+                if (versions == null) {
+                    throw new PackageParserException(
+                            PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, errorMsg[0]);
+                }
+
+                targetSdkVersion = versions.second;
             }
         }
 
+        final boolean extractNativeLibsDefault = targetSdkVersion < Build.VERSION_CODES.Q;
+        final boolean extractNativeLibs = (extractNativeLibsProvided != null)
+                ? extractNativeLibsProvided : extractNativeLibsDefault;
+
         if (preferCodeIntegrity && extractNativeLibs) {
             throw new PackageParserException(
                     PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
@@ -2215,65 +2259,60 @@
 
             } else if (tagName.equals(TAG_USES_SDK)) {
                 if (SDK_VERSION > 0) {
-                    sa = res.obtainAttributes(parser,
-                            com.android.internal.R.styleable.AndroidManifestUsesSdk);
+                    sa = res.obtainAttributes(parser, R.styleable.AndroidManifestUsesSdk);
+                    final TypedArray saFinal = sa;
+                    Pair<Integer, Integer> versions = deriveSdkVersions(
+                            new AbstractVersionsAccessor() {
+                                @Override public String getMinSdkVersionCode() {
+                                    return getAttributeAsString(
+                                            R.styleable.AndroidManifestUsesSdk_minSdkVersion);
+                                }
 
-                    int minVers = 1;
-                    String minCode = null;
-                    int targetVers = 0;
-                    String targetCode = null;
+                                @Override public int getMinSdkVersion() {
+                                    return getAttributeAsInt(
+                                            R.styleable.AndroidManifestUsesSdk_minSdkVersion);
+                                }
 
-                    TypedValue val = sa.peekValue(
-                            com.android.internal.R.styleable.AndroidManifestUsesSdk_minSdkVersion);
-                    if (val != null) {
-                        if (val.type == TypedValue.TYPE_STRING && val.string != null) {
-                            minCode = val.string.toString();
-                        } else {
-                            // If it's not a string, it's an integer.
-                            minVers = val.data;
-                        }
+                                @Override public String getTargetSdkVersionCode() {
+                                    return getAttributeAsString(
+                                            R.styleable.AndroidManifestUsesSdk_targetSdkVersion);
+                                }
+
+                                @Override public int getTargetSdkVersion() {
+                                    return getAttributeAsInt(
+                                            R.styleable.AndroidManifestUsesSdk_targetSdkVersion);
+                                }
+
+                                private String getAttributeAsString(int index) {
+                                    TypedValue val = saFinal.peekValue(index);
+                                    if (val != null && val.type == TypedValue.TYPE_STRING
+                                            && val.string != null) {
+                                        return val.string.toString();
+                                    }
+                                    return null;
+                                }
+
+                                private int getAttributeAsInt(int index) {
+                                    TypedValue val = saFinal.peekValue(index);
+                                    if (val != null && val.type != TypedValue.TYPE_STRING) {
+                                        // If it's not a string, it's an integer.
+                                        return val.data;
+                                    }
+                                    return -1;
+                                }
+                            }, flags, outError);
+
+                    if (versions == null) {
+                        mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK;
+                        return null;
                     }
 
-                    val = sa.peekValue(
-                            com.android.internal.R.styleable.AndroidManifestUsesSdk_targetSdkVersion);
-                    if (val != null) {
-                        if (val.type == TypedValue.TYPE_STRING && val.string != null) {
-                            targetCode = val.string.toString();
-                            if (minCode == null) {
-                                minCode = targetCode;
-                            }
-                        } else {
-                            // If it's not a string, it's an integer.
-                            targetVers = val.data;
-                        }
-                    } else {
-                        targetVers = minVers;
-                        targetCode = minCode;
-                    }
+                    pkg.applicationInfo.minSdkVersion = versions.first;
+                    pkg.applicationInfo.targetSdkVersion = versions.second;
 
                     sa.recycle();
-
-                    final int minSdkVersion = PackageParser.computeMinSdkVersion(minVers, minCode,
-                            SDK_VERSION, SDK_CODENAMES, outError);
-                    if (minSdkVersion < 0) {
-                        mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK;
-                        return null;
-                    }
-
-                    boolean defaultToCurrentDevBranch = (flags & PARSE_FORCE_SDK) != 0;
-                    final int targetSdkVersion = PackageParser.computeTargetSdkVersion(targetVers,
-                            targetCode, SDK_CODENAMES, outError, defaultToCurrentDevBranch);
-                    if (targetSdkVersion < 0) {
-                        mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK;
-                        return null;
-                    }
-
-                    pkg.applicationInfo.minSdkVersion = minSdkVersion;
-                    pkg.applicationInfo.targetSdkVersion = targetSdkVersion;
                 }
-
                 XmlUtils.skipCurrentTag(parser);
-
             } else if (tagName.equals(TAG_SUPPORT_SCREENS)) {
                 sa = res.obtainAttributes(parser,
                         com.android.internal.R.styleable.AndroidManifestSupportsScreens);
@@ -2675,6 +2714,67 @@
         return -1;
     }
 
+    private interface AbstractVersionsAccessor {
+        /** Returns minimum SDK version code string, or null if absent. */
+        String getMinSdkVersionCode();
+
+        /** Returns minimum SDK version code, or -1 if absent. */
+        int getMinSdkVersion();
+
+        /** Returns target SDK version code string, or null if absent. */
+        String getTargetSdkVersionCode();
+
+        /** Returns target SDK version code, or -1 if absent. */
+        int getTargetSdkVersion();
+    }
+
+    private static @Nullable Pair<Integer, Integer> deriveSdkVersions(
+            @NonNull AbstractVersionsAccessor accessor, int flags, String[] outError) {
+        int minVers = 1;
+        String minCode = null;
+        int targetVers = 0;
+        String targetCode = null;
+
+        String code = accessor.getMinSdkVersionCode();
+        int version = accessor.getMinSdkVersion();
+        // Check integer first since code is almost never a null string (e.g. "28").
+        if (version >= 0) {
+            minVers = version;
+        } else if (code != null) {
+            minCode = code;
+        }
+
+        code = accessor.getTargetSdkVersionCode();
+        version = accessor.getTargetSdkVersion();
+        // Check integer first since code is almost never a null string (e.g. "28").
+        if (version >= 0) {
+            targetVers = version;
+        } else if (code != null) {
+            targetCode = code;
+            if (minCode == null) {
+                minCode = targetCode;
+            }
+        } else {
+            targetVers = minVers;
+            targetCode = minCode;
+        }
+
+        final int minSdkVersion = computeMinSdkVersion(minVers, minCode,
+                SDK_VERSION, SDK_CODENAMES, outError);
+        if (minSdkVersion < 0) {
+            return null;
+        }
+
+        boolean defaultToCurrentDevBranch = (flags & PARSE_FORCE_SDK) != 0;
+        final int targetSdkVersion = computeTargetSdkVersion(targetVers,
+                targetCode, SDK_CODENAMES, outError, defaultToCurrentDevBranch);
+        if (targetSdkVersion < 0) {
+            return null;
+        }
+
+        return Pair.create(minSdkVersion, targetSdkVersion);
+    }
+
     /**
      * Computes the minSdkVersion to use at runtime. If the package is not
      * compatible with this platform, populates {@code outError[0]} with an
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index 9801406..68d6d85 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -16,6 +16,8 @@
 
 package android.os;
 
+import android.annotation.NonNull;
+
 import com.android.internal.os.Zygote;
 
 import dalvik.annotation.optimization.FastNative;
@@ -313,7 +315,7 @@
      * @param sectionName The name of the code section to appear in the trace.  This may be at
      * most 127 Unicode code units long.
      */
-    public static void beginSection(String sectionName) {
+    public static void beginSection(@NonNull String sectionName) {
         if (isTagEnabled(TRACE_TAG_APP)) {
             if (sectionName.length() > MAX_SECTION_NAME_LEN) {
                 throw new IllegalArgumentException("sectionName is too long");
@@ -345,7 +347,7 @@
      * @param methodName The method name to appear in the trace.
      * @param cookie Unique identifier for distinguishing simultaneous events
      */
-    public static void beginAsyncSection(String methodName, int cookie) {
+    public static void beginAsyncSection(@NonNull String methodName, int cookie) {
         asyncTraceBegin(TRACE_TAG_APP, methodName, cookie);
     }
 
@@ -357,7 +359,7 @@
      * @param methodName The method name to appear in the trace.
      * @param cookie Unique identifier for distinguishing simultaneous events
      */
-    public static void endAsyncSection(String methodName, int cookie) {
+    public static void endAsyncSection(@NonNull String methodName, int cookie) {
         asyncTraceEnd(TRACE_TAG_APP, methodName, cookie);
     }
 
@@ -367,7 +369,7 @@
      * @param counterName The counter name to appear in the trace.
      * @param counterValue The counter value.
      */
-    public static void setCounter(String counterName, long counterValue) {
+    public static void setCounter(@NonNull String counterName, long counterValue) {
         if (isTagEnabled(TRACE_TAG_APP)) {
             nativeTraceCounter(TRACE_TAG_APP, counterName, counterValue);
         }
diff --git a/core/java/com/android/internal/policy/ScreenDecorationsUtils.java b/core/java/com/android/internal/policy/ScreenDecorationsUtils.java
index 100c6ee..adf7692 100644
--- a/core/java/com/android/internal/policy/ScreenDecorationsUtils.java
+++ b/core/java/com/android/internal/policy/ScreenDecorationsUtils.java
@@ -16,10 +16,10 @@
 
 package com.android.internal.policy;
 
-import com.android.internal.R;
-
 import android.content.res.Resources;
 
+import com.android.internal.R;
+
 /**
  * Utility functions for screen decorations used by both window manager and System UI.
  */
@@ -31,15 +31,19 @@
      * scaling, this means that we don't have to reload them on config changes.
      */
     public static float getWindowCornerRadius(Resources resources) {
+        if (!supportsRoundedCornersOnWindows(resources)) {
+            return 0f;
+        }
+
         // Radius that should be used in case top or bottom aren't defined.
         float defaultRadius = resources.getDimension(R.dimen.rounded_corner_radius);
 
         float topRadius = resources.getDimension(R.dimen.rounded_corner_radius_top);
-        if (topRadius == 0) {
+        if (topRadius == 0f) {
             topRadius = defaultRadius;
         }
         float bottomRadius = resources.getDimension(R.dimen.rounded_corner_radius_bottom);
-        if (bottomRadius == 0) {
+        if (bottomRadius == 0f) {
             bottomRadius = defaultRadius;
         }
 
@@ -47,4 +51,11 @@
         // completely cover the display.
         return Math.min(topRadius, bottomRadius);
     }
+
+    /**
+     * If live rounded corners are supported on windows.
+     */
+    public static boolean supportsRoundedCornersOnWindows(Resources resources) {
+        return resources.getBoolean(R.bool.config_supportsRoundedCornersOnWindows);
+    }
 }
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 1c98c66..140225e 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3648,4 +3648,8 @@
          set in AndroidManifest.
          {@see android.view.Display#FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS} -->
     <string name="config_secondaryHomeComponent" translatable="false">com.android.launcher3/com.android.launcher3.SecondaryDisplayLauncher</string>
+
+    <!-- If device supports corner radius on windows.
+         This should be turned off on low-end devices to improve animation performance. -->
+    <bool name="config_supportsRoundedCornersOnWindows">true</bool>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 01fbf80..f8c9037 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3515,6 +3515,7 @@
   <java-symbol type="dimen" name="rounded_corner_radius" />
   <java-symbol type="dimen" name="rounded_corner_radius_top" />
   <java-symbol type="dimen" name="rounded_corner_radius_bottom" />
+  <java-symbol type="bool" name="config_supportsRoundedCornersOnWindows" />
 
   <java-symbol type="string" name="config_defaultModuleMetadataProvider" />
 
diff --git a/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml b/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml
index 22b8d2f..4b65b6a 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml
@@ -24,6 +24,7 @@
     android:clipToPadding="false"
     android:gravity="center"
     android:orientation="horizontal"
+    android:clickable="true"
     android:paddingStart="@dimen/status_bar_padding_start"
     android:paddingEnd="@dimen/status_bar_padding_end" >
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index f3bdbae..078947c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -76,4 +76,10 @@
      */
     float getWindowCornerRadius() = 10;
 
+    /**
+     * If device supports live rounded corners on windows.
+     * This might be turned off for performance reasons
+     */
+    boolean supportsRoundedCornersOnWindows() = 11;
+
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 27d624a..1c8a672 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -14,13 +14,13 @@
 
 import com.android.systemui.Dependency;
 import com.android.systemui.plugins.ClockPlugin;
-import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.policy.ExtensionController;
+import com.android.systemui.statusbar.policy.ExtensionController.Extension;
 
-import java.util.Objects;
 import java.util.TimeZone;
+import java.util.function.Consumer;
 
 /**
  * Switch to show plugin clock when plugin is connected, otherwise it will show default clock.
@@ -47,43 +47,15 @@
      * or not to show it below the alternate clock.
      */
     private View mKeyguardStatusArea;
+    /**
+     * Used to select between plugin or default implementations of ClockPlugin interface.
+     */
+    private Extension<ClockPlugin> mClockExtension;
+    /**
+     * Consumer that accepts the a new ClockPlugin implementation when the Extension reloads.
+     */
+    private final Consumer<ClockPlugin> mClockPluginConsumer = plugin -> setClockPlugin(plugin);
 
-    private final PluginListener<ClockPlugin> mClockPluginListener =
-            new PluginListener<ClockPlugin>() {
-                @Override
-                public void onPluginConnected(ClockPlugin plugin, Context pluginContext) {
-                    disconnectPlugin();
-                    View smallClockView = plugin.getView();
-                    if (smallClockView != null) {
-                        // For now, assume that the most recently connected plugin is the
-                        // selected clock face. In the future, the user should be able to
-                        // pick a clock face from the available plugins.
-                        mSmallClockFrame.addView(smallClockView, -1,
-                                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
-                                        ViewGroup.LayoutParams.WRAP_CONTENT));
-                        initPluginParams();
-                        mClockView.setVisibility(View.GONE);
-                    }
-                    View bigClockView = plugin.getBigClockView();
-                    if (bigClockView != null && mBigClockContainer != null) {
-                        mBigClockContainer.addView(bigClockView);
-                        mBigClockContainer.setVisibility(View.VISIBLE);
-                    }
-                    if (!plugin.shouldShowStatusArea()) {
-                        mKeyguardStatusArea.setVisibility(View.GONE);
-                    }
-                    mClockPlugin = plugin;
-                }
-
-                @Override
-                public void onPluginDisconnected(ClockPlugin plugin) {
-                    if (Objects.equals(plugin, mClockPlugin)) {
-                        disconnectPlugin();
-                        mClockView.setVisibility(View.VISIBLE);
-                        mKeyguardStatusArea.setVisibility(View.VISIBLE);
-                    }
-                }
-            };
     private final StatusBarStateController.StateListener mStateListener =
             new StatusBarStateController.StateListener() {
                 @Override
@@ -122,18 +94,61 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        Dependency.get(PluginManager.class).addPluginListener(mClockPluginListener,
-                ClockPlugin.class);
+        mClockExtension = Dependency.get(ExtensionController.class).newExtension(ClockPlugin.class)
+                .withPlugin(ClockPlugin.class)
+                .withCallback(mClockPluginConsumer)
+                .build();
         Dependency.get(StatusBarStateController.class).addCallback(mStateListener);
     }
 
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
-        Dependency.get(PluginManager.class).removePluginListener(mClockPluginListener);
+        mClockExtension.destroy();
         Dependency.get(StatusBarStateController.class).removeCallback(mStateListener);
     }
 
+    private void setClockPlugin(ClockPlugin plugin) {
+        // Disconnect from existing plugin.
+        if (mClockPlugin != null) {
+            View smallClockView = mClockPlugin.getView();
+            if (smallClockView != null && smallClockView.getParent() == mSmallClockFrame) {
+                mSmallClockFrame.removeView(smallClockView);
+            }
+            if (mBigClockContainer != null) {
+                mBigClockContainer.removeAllViews();
+                mBigClockContainer.setVisibility(View.GONE);
+            }
+            mClockPlugin = null;
+        }
+        if (plugin == null) {
+            mClockView.setVisibility(View.VISIBLE);
+            mKeyguardStatusArea.setVisibility(View.VISIBLE);
+            return;
+        }
+        // Attach small and big clock views to hierarchy.
+        View smallClockView = plugin.getView();
+        if (smallClockView != null) {
+            mSmallClockFrame.addView(smallClockView, -1,
+                    new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                            ViewGroup.LayoutParams.WRAP_CONTENT));
+            mClockView.setVisibility(View.GONE);
+        }
+        View bigClockView = plugin.getBigClockView();
+        if (bigClockView != null && mBigClockContainer != null) {
+            mBigClockContainer.addView(bigClockView);
+            mBigClockContainer.setVisibility(View.VISIBLE);
+        }
+        // Hide default clock.
+        if (!plugin.shouldShowStatusArea()) {
+            mKeyguardStatusArea.setVisibility(View.GONE);
+        }
+        // Initialize plugin parameters.
+        mClockPlugin = plugin;
+        mClockPlugin.setStyle(getPaint().getStyle());
+        mClockPlugin.setTextColor(getCurrentTextColor());
+    }
+
     /**
      * Set container for big clock face appearing behind NSSL and KeyguardStatusView.
      */
@@ -232,33 +247,9 @@
         }
     }
 
-    /**
-     * When plugin changes, set all kept parameters into newer plugin.
-     */
-    private void initPluginParams() {
-        if (mClockPlugin != null) {
-            mClockPlugin.setStyle(getPaint().getStyle());
-            mClockPlugin.setTextColor(getCurrentTextColor());
-        }
-    }
-
-    private void disconnectPlugin() {
-        if (mClockPlugin != null) {
-            View smallClockView = mClockPlugin.getView();
-            if (smallClockView != null) {
-                mSmallClockFrame.removeView(smallClockView);
-            }
-            if (mBigClockContainer != null) {
-                mBigClockContainer.removeAllViews();
-                mBigClockContainer.setVisibility(View.GONE);
-            }
-            mClockPlugin = null;
-        }
-    }
-
     @VisibleForTesting (otherwise = VisibleForTesting.NONE)
-    PluginListener getClockPluginListener() {
-        return mClockPluginListener;
+    Consumer<ClockPlugin> getClockPluginConsumer() {
+        return mClockPluginConsumer;
     }
 
     @VisibleForTesting (otherwise = VisibleForTesting.NONE)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 323cf1f..f14495b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -1843,6 +1843,13 @@
      */
     private void handleHide() {
         Trace.beginSection("KeyguardViewMediator#handleHide");
+
+        // It's possible that the device was unlocked in a dream state. It's time to wake up.
+        if (mAodShowing) {
+            PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+            pm.wakeUp(SystemClock.uptimeMillis(), "com.android.systemui:BOUNCER_DOZING");
+        }
+
         synchronized (KeyguardViewMediator.this) {
             if (DEBUG) Log.d(TAG, "handleHide");
 
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt
index 01ee5ca..4388200 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt
@@ -52,7 +52,8 @@
 
                         @Suppress("DEPRECATION")
                         override fun onClick(dialog: DialogInterface?, which: Int) {
-                            Dependency.get(ActivityStarter::class.java).startActivity(intent, false)
+                            Dependency.get(ActivityStarter::class.java)
+                                    .postStartActivityDismissingKeyguard(intent, 0)
                         }
                     })
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
index e030e40..e63f88a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
@@ -284,10 +284,17 @@
     public void updateEverything() {
         post(() -> {
             updateVisibilities();
+            updateClickabilities();
             setClickable(false);
         });
     }
 
+    private void updateClickabilities() {
+        mMultiUserSwitch.setClickable(mMultiUserSwitch.getVisibility() == View.VISIBLE);
+        mEdit.setClickable(mEdit.getVisibility() == View.VISIBLE);
+        mSettingsButton.setClickable(mSettingsButton.getVisibility() == View.VISIBLE);
+    }
+
     private void updateVisibilities() {
         mSettingsContainer.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE);
         mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 40c6039..28285e14 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -699,7 +699,7 @@
     }
 
     public void updateEverything() {
-        post(() -> setClickable(false));
+        post(() -> setClickable(!mExpanded));
     }
 
     public void setQSPanel(final QSPanel qsPanel) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 81757d0..c474faf 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -101,6 +101,7 @@
     private float mBackButtonAlpha;
     private MotionEvent mStatusBarGestureDownEvent;
     private float mWindowCornerRadius;
+    private boolean mSupportsRoundedCornersOnWindows;
 
     private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() {
 
@@ -244,6 +245,18 @@
             }
         }
 
+        public boolean supportsRoundedCornersOnWindows() {
+            if (!verifyCaller("supportsRoundedCornersOnWindows")) {
+                return false;
+            }
+            long token = Binder.clearCallingIdentity();
+            try {
+                return mSupportsRoundedCornersOnWindows;
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
         private boolean verifyCaller(String reason) {
             final int callerId = Binder.getCallingUserHandle().getIdentifier();
             if (callerId != mCurrentBoundedUserId) {
@@ -353,6 +366,8 @@
         mInteractionFlags = Prefs.getInt(mContext, Prefs.Key.QUICK_STEP_INTERACTION_FLAGS,
                 getDefaultInteractionFlags());
         mWindowCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(mContext.getResources());
+        mSupportsRoundedCornersOnWindows = ScreenDecorationsUtils
+                .supportsRoundedCornersOnWindows(mContext.getResources());
 
         // Listen for the package update changes.
         if (mDeviceProvisionedController.getCurrentUser() == UserHandle.USER_SYSTEM) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index d873b0c..75adf50 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -956,7 +956,7 @@
             handled = true;
         }
         handled |= super.onTouchEvent(event);
-        return mDozing ? handled : true;
+        return !mDozing || mPulsing || handled;
     }
 
     private boolean handleQsTouch(MotionEvent event) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index e25c829..4bece48 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -364,7 +364,7 @@
             mExpansionFraction = fraction;
 
             final boolean keyguardOrUnlocked = mState == ScrimState.UNLOCKED
-                    || mState == ScrimState.KEYGUARD;
+                    || mState == ScrimState.KEYGUARD || mState == ScrimState.PULSING;
             if (!keyguardOrUnlocked || !mExpansionAffectsAlpha) {
                 return;
             }
@@ -409,7 +409,7 @@
             behindFraction = (float) Math.pow(behindFraction, 0.8f);
             mCurrentBehindAlpha = behindFraction * GRADIENT_SCRIM_ALPHA_BUSY;
             mCurrentInFrontAlpha = 0;
-        } else if (mState == ScrimState.KEYGUARD) {
+        } else if (mState == ScrimState.KEYGUARD || mState == ScrimState.PULSING) {
             // Either darken of make the scrim transparent when you
             // pull down the shade
             float interpolatedFract = getInterpolatedFraction();
@@ -562,8 +562,8 @@
         if (alpha == 0f) {
             scrim.setClickable(false);
         } else {
-            // Eat touch events (unless dozing or pulsing).
-            scrim.setClickable(mState != ScrimState.AOD && mState != ScrimState.PULSING);
+            // Eat touch events (unless dozing).
+            scrim.setClickable(mState != ScrimState.AOD);
         }
         updateScrim(scrim, alpha);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 6d78f72..6f877ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -3573,10 +3573,7 @@
             mVisualStabilityManager.setScreenOn(false);
             updateVisibleToUser();
 
-            // We need to disable touch events because these might
-            // collapse the panel after we expanded it, and thus we would end up with a blank
-            // Keyguard.
-            mNotificationPanel.setTouchAndAnimationDisabled(true);
+            updateNotificationPanelTouchState();
             mStatusBarWindow.cancelCurrentTouch();
             if (mLaunchCameraOnFinishedGoingToSleep) {
                 mLaunchCameraOnFinishedGoingToSleep = false;
@@ -3599,13 +3596,22 @@
             mDeviceInteractive = true;
             mAmbientPulseManager.releaseAllImmediately();
             mVisualStabilityManager.setScreenOn(true);
-            mNotificationPanel.setTouchAndAnimationDisabled(false);
+            updateNotificationPanelTouchState();
             updateVisibleToUser();
             updateIsKeyguard();
             mDozeServiceHost.stopDozing();
         }
     };
 
+    /**
+     * We need to disable touch events because these might
+     * collapse the panel after we expanded it, and thus we would end up with a blank
+     * Keyguard.
+     */
+    private void updateNotificationPanelTouchState() {
+        mNotificationPanel.setTouchAndAnimationDisabled(!mDeviceInteractive && !mPulsing);
+    }
+
     final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
         @Override
         public void onScreenTurningOn() {
@@ -3871,17 +3877,15 @@
                 @Override
                 public void onPulseStarted() {
                     callback.onPulseStarted();
-                    if (mAmbientPulseManager.hasNotifications()) {
-                        // Only pulse the stack scroller if there's actually something to show.
-                        // Otherwise just show the always-on screen.
-                        setPulsing(true);
-                    }
+                    updateNotificationPanelTouchState();
+                    setPulsing(true);
                 }
 
                 @Override
                 public void onPulseFinished() {
                     mPulsing = false;
                     callback.onPulseFinished();
+                    updateNotificationPanelTouchState();
                     setPulsing(false);
                 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 0f8970f..bb23608 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -187,7 +187,7 @@
             mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
         } else if (bouncerNeedsScrimming()) {
             mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
-        } else if (mShowing && !mDozing) {
+        } else if (mShowing) {
             if (!isWakeAndUnlocking() && !mStatusBar.isInLaunchTransition()) {
                 mBouncer.setExpansion(expansion);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index 53e461d..8b25c34 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -339,7 +339,7 @@
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         NotificationStackScrollLayout stackScrollLayout = getStackScrollLayout();
-        if (mService.isDozing() && !stackScrollLayout.hasPulsingNotifications()) {
+        if (mService.isDozing() && !mService.isPulsing()) {
             // Capture all touch events in always-on.
             return true;
         }
@@ -347,8 +347,7 @@
         if (mNotificationPanel.isFullyExpanded()
                 && stackScrollLayout.getVisibility() == View.VISIBLE
                 && mStatusBarStateController.getState() == StatusBarState.KEYGUARD
-                && !mService.isBouncerShowing()
-                && !mService.isDozing()) {
+                && !mService.isBouncerShowing()) {
             intercept = mDragDownHelper.onInterceptTouchEvent(ev);
         }
         if (!intercept) {
@@ -369,7 +368,7 @@
         boolean handled = false;
         if (mService.isDozing()) {
             mDoubleTapHelper.onTouchEvent(ev);
-            handled = true;
+            handled = !mService.isPulsing();
         }
         if ((mStatusBarStateController.getState() == StatusBarState.KEYGUARD && !handled)
                 || mDragDownHelper.isDraggingDown()) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index 1844df5..8e02f57 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -21,7 +21,6 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -42,19 +41,18 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.ClockPlugin;
-import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.StatusBarStateController;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.function.Consumer;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 // Need to run on the main thread because KeyguardSliceView$Row init checks for
@@ -62,7 +60,6 @@
 // the keyguard_clcok_switch layout is inflated.
 @RunWithLooper(setAsMainLooper = true)
 public class KeyguardClockSwitchTest extends SysuiTestCase {
-    private PluginManager mPluginManager;
     private FrameLayout mClockContainer;
     private StatusBarStateController.StateListener mStateListener;
 
@@ -73,7 +70,6 @@
 
     @Before
     public void setUp() {
-        mPluginManager = mDependency.injectMockDependency(PluginManager.class);
         LayoutInflater layoutInflater = LayoutInflater.from(getContext());
         mKeyguardClockSwitch =
                 (KeyguardClockSwitch) layoutInflater.inflate(R.layout.keyguard_clock_switch, null);
@@ -84,29 +80,12 @@
     }
 
     @Test
-    public void onAttachToWindow_addPluginListener() {
-        mKeyguardClockSwitch.onAttachedToWindow();
-
-        ArgumentCaptor<PluginListener> listener = ArgumentCaptor.forClass(PluginListener.class);
-        verify(mPluginManager).addPluginListener(listener.capture(), eq(ClockPlugin.class));
-    }
-
-    @Test
-    public void onDetachToWindow_removePluginListener() {
-        mKeyguardClockSwitch.onDetachedFromWindow();
-
-        ArgumentCaptor<PluginListener> listener = ArgumentCaptor.forClass(PluginListener.class);
-        verify(mPluginManager).removePluginListener(listener.capture());
-    }
-
-    @Test
     public void onPluginConnected_showPluginClock() {
         ClockPlugin plugin = mock(ClockPlugin.class);
         TextClock pluginView = new TextClock(getContext());
         when(plugin.getView()).thenReturn(pluginView);
-        PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
 
-        listener.onPluginConnected(plugin, null);
+        mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
 
         verify(mClockView).setVisibility(GONE);
         assertThat(plugin.getView().getParent()).isEqualTo(mClockContainer);
@@ -122,9 +101,8 @@
         ClockPlugin plugin = mock(ClockPlugin.class);
         TextClock pluginView = new TextClock(getContext());
         when(plugin.getBigClockView()).thenReturn(pluginView);
-        PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
         // WHEN the plugin is connected
-        listener.onPluginConnected(plugin, null);
+        mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
         // THEN the big clock container is visible and it is the parent of the
         // big clock view.
         assertThat(bigClockContainer.getVisibility()).isEqualTo(VISIBLE);
@@ -134,8 +112,7 @@
     @Test
     public void onPluginConnected_nullView() {
         ClockPlugin plugin = mock(ClockPlugin.class);
-        PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
-        listener.onPluginConnected(plugin, null);
+        mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
         verify(mClockView, never()).setVisibility(GONE);
     }
 
@@ -144,12 +121,11 @@
         // GIVEN a plugin has already connected
         ClockPlugin plugin1 = mock(ClockPlugin.class);
         when(plugin1.getView()).thenReturn(new TextClock(getContext()));
-        PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
-        listener.onPluginConnected(plugin1, null);
+        mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin1);
         // WHEN a second plugin is connected
         ClockPlugin plugin2 = mock(ClockPlugin.class);
         when(plugin2.getView()).thenReturn(new TextClock(getContext()));
-        listener.onPluginConnected(plugin2, null);
+        mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin2);
         // THEN only the view from the second plugin should be a child of KeyguardClockSwitch.
         assertThat(plugin2.getView().getParent()).isEqualTo(mClockContainer);
         assertThat(plugin1.getView().getParent()).isNull();
@@ -161,10 +137,9 @@
         TextClock pluginView = new TextClock(getContext());
         when(plugin.getView()).thenReturn(pluginView);
         mClockView.setVisibility(GONE);
-        PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
 
-        listener.onPluginConnected(plugin, null);
-        listener.onPluginDisconnected(plugin);
+        mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
+        mKeyguardClockSwitch.getClockPluginConsumer().accept(null);
 
         verify(mClockView).setVisibility(VISIBLE);
         assertThat(plugin.getView().getParent()).isNull();
@@ -180,10 +155,9 @@
         ClockPlugin plugin = mock(ClockPlugin.class);
         TextClock pluginView = new TextClock(getContext());
         when(plugin.getBigClockView()).thenReturn(pluginView);
-        PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
-        listener.onPluginConnected(plugin, null);
-        // WHEN the plugin is disconnected
-        listener.onPluginDisconnected(plugin);
+        // WHEN the plugin is connected and then disconnected
+        mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
+        mKeyguardClockSwitch.getClockPluginConsumer().accept(null);
         // THEN the big lock container is GONE and the big clock view doesn't have
         // a parent.
         assertThat(bigClockContainer.getVisibility()).isEqualTo(GONE);
@@ -193,41 +167,23 @@
     @Test
     public void onPluginDisconnected_nullView() {
         ClockPlugin plugin = mock(ClockPlugin.class);
-        PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
-        listener.onPluginConnected(plugin, null);
-        listener.onPluginDisconnected(plugin);
+        mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
+        mKeyguardClockSwitch.getClockPluginConsumer().accept(null);
         verify(mClockView, never()).setVisibility(GONE);
     }
 
     @Test
-    public void onPluginDisconnected_firstOfTwoDisconnected() {
-        // GIVEN two plugins are connected
-        ClockPlugin plugin1 = mock(ClockPlugin.class);
-        when(plugin1.getView()).thenReturn(new TextClock(getContext()));
-        PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
-        listener.onPluginConnected(plugin1, null);
-        ClockPlugin plugin2 = mock(ClockPlugin.class);
-        when(plugin2.getView()).thenReturn(new TextClock(getContext()));
-        listener.onPluginConnected(plugin2, null);
-        // WHEN the first plugin is disconnected
-        listener.onPluginDisconnected(plugin1);
-        // THEN the view from the second plugin is still a child of KeyguardClockSwitch.
-        assertThat(plugin2.getView().getParent()).isEqualTo(mClockContainer);
-        assertThat(plugin1.getView().getParent()).isNull();
-    }
-
-    @Test
     public void onPluginDisconnected_secondOfTwoDisconnected() {
         // GIVEN two plugins are connected
         ClockPlugin plugin1 = mock(ClockPlugin.class);
         when(plugin1.getView()).thenReturn(new TextClock(getContext()));
-        PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
-        listener.onPluginConnected(plugin1, null);
+        Consumer<ClockPlugin> consumer = mKeyguardClockSwitch.getClockPluginConsumer();
+        consumer.accept(plugin1);
         ClockPlugin plugin2 = mock(ClockPlugin.class);
         when(plugin2.getView()).thenReturn(new TextClock(getContext()));
-        listener.onPluginConnected(plugin2, null);
+        consumer.accept(plugin2);
         // WHEN the second plugin is disconnected
-        listener.onPluginDisconnected(plugin2);
+        consumer.accept(null);
         // THEN the default clock should be shown.
         verify(mClockView).setVisibility(VISIBLE);
         assertThat(plugin1.getView().getParent()).isNull();
@@ -246,8 +202,7 @@
         ClockPlugin plugin = mock(ClockPlugin.class);
         TextClock pluginView = new TextClock(getContext());
         when(plugin.getView()).thenReturn(pluginView);
-        PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
-        listener.onPluginConnected(plugin, null);
+        mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
 
         mKeyguardClockSwitch.setTextColor(Color.WHITE);
 
@@ -271,8 +226,7 @@
         TextClock pluginView = new TextClock(getContext());
         when(plugin.getView()).thenReturn(pluginView);
         Style style = mock(Style.class);
-        PluginListener listener = mKeyguardClockSwitch.getClockPluginListener();
-        listener.onPluginConnected(plugin, null);
+        mKeyguardClockSwitch.getClockPluginConsumer().accept(plugin);
 
         mKeyguardClockSwitch.setStyle(style);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 146c5d6..cc5f50a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -54,7 +54,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.function.Consumer;
 
@@ -578,7 +578,7 @@
     @Test
     public void testEatsTouchEvent() {
         HashSet<ScrimState> eatsTouches =
-                new HashSet<>(Arrays.asList(ScrimState.AOD, ScrimState.PULSING));
+                new HashSet<>(Collections.singletonList(ScrimState.AOD));
         for (ScrimState state : ScrimState.values()) {
             if (state == ScrimState.UNINITIALIZED) {
                 continue;
diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java
index 3cdf09e..466fb4e 100644
--- a/services/core/java/com/android/server/AppOpsService.java
+++ b/services/core/java/com/android/server/AppOpsService.java
@@ -3391,6 +3391,8 @@
         pw.println("    Limit output to data associated with the given app op mode.");
         pw.println("  --package [PACKAGE]");
         pw.println("    Limit output to data associated with the given package name.");
+        pw.println("  --watchers");
+        pw.println("    Only output the watcher sections.");
     }
 
     private void dumpTimesLocked(PrintWriter pw, String firstPrefix, String prefix, long[] times,
@@ -3429,6 +3431,7 @@
         String dumpPackage = null;
         int dumpUid = -1;
         int dumpMode = -1;
+        boolean dumpWatchers = false;
 
         if (args != null) {
             for (int i=0; i<args.length; i++) {
@@ -3476,6 +3479,8 @@
                     if (dumpMode < 0) {
                         return;
                     }
+                } else if ("--watchers".equals(arg)) {
+                    dumpWatchers = true;
                 } else if (arg.length() > 0 && arg.charAt(0) == '-'){
                     pw.println("Unknown option: " + arg);
                     return;
@@ -3496,7 +3501,8 @@
             final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
             final Date date = new Date();
             boolean needSep = false;
-            if (dumpOp < 0 && dumpMode < 0 && dumpPackage == null && mProfileOwners != null) {
+            if (dumpOp < 0 && dumpMode < 0 && dumpPackage == null && mProfileOwners != null
+                    && !dumpWatchers) {
                 pw.println("  Profile owners:");
                 for (int poi = 0; poi < mProfileOwners.size(); poi++) {
                     pw.print("    User #");
@@ -3517,7 +3523,7 @@
                     ArraySet<ModeCallback> callbacks = mOpModeWatchers.valueAt(i);
                     for (int j=0; j<callbacks.size(); j++) {
                         final ModeCallback cb = callbacks.valueAt(j);
-                        if (dumpPackage != null && cb.mWatchingUid >= 0
+                        if (dumpPackage != null
                                 && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
                             continue;
                         }
@@ -3561,7 +3567,7 @@
                 boolean printedHeader = false;
                 for (int i=0; i<mModeWatchers.size(); i++) {
                     final ModeCallback cb = mModeWatchers.valueAt(i);
-                    if (dumpPackage != null && cb.mWatchingUid >= 0
+                    if (dumpPackage != null
                             && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
                         continue;
                     }
@@ -3587,7 +3593,7 @@
                     if (dumpOp >= 0 && activeWatchers.indexOfKey(dumpOp) < 0) {
                         continue;
                     }
-                    if (dumpPackage != null && cb.mWatchingUid >= 0
+                    if (dumpPackage != null
                             && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
                         continue;
                     }
@@ -3627,7 +3633,7 @@
                     if (dumpOp >= 0 && notedWatchers.indexOfKey(dumpOp) < 0) {
                         continue;
                     }
-                    if (dumpPackage != null && cb.mWatchingUid >= 0
+                    if (dumpPackage != null
                             && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
                         continue;
                     }
@@ -3655,7 +3661,7 @@
                     pw.println(cb);
                 }
             }
-            if (mClients.size() > 0 && dumpMode < 0) {
+            if (mClients.size() > 0 && dumpMode < 0 && !dumpWatchers) {
                 needSep = true;
                 boolean printedHeader = false;
                 for (int i=0; i<mClients.size(); i++) {
@@ -3692,7 +3698,7 @@
                 }
             }
             if (mAudioRestrictions.size() > 0 && dumpOp < 0 && dumpPackage != null
-                    && dumpMode < 0) {
+                    && dumpMode < 0 && !dumpWatchers) {
                 boolean printedHeader = false;
                 for (int o=0; o<mAudioRestrictions.size(); o++) {
                     final String op = AppOpsManager.opToName(mAudioRestrictions.keyAt(o));
@@ -3725,6 +3731,9 @@
                 final SparseIntArray opModes = uidState.opModes;
                 final ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
 
+                if (dumpWatchers) {
+                    continue;
+                }
                 if (dumpOp >= 0 || dumpPackage != null || dumpMode >= 0) {
                     boolean hasOp = dumpOp < 0 || (uidState.opModes != null
                             && uidState.opModes.indexOfKey(dumpOp) >= 0);
@@ -3880,18 +3889,34 @@
             for (int i = 0; i < userRestrictionCount; i++) {
                 IBinder token = mOpUserRestrictions.keyAt(i);
                 ClientRestrictionState restrictionState = mOpUserRestrictions.valueAt(i);
-                pw.println("  User restrictions for token " + token + ":");
+                boolean printedTokenHeader = false;
+
+                if (dumpMode >= 0 || dumpWatchers) {
+                    continue;
+                }
 
                 final int restrictionCount = restrictionState.perUserRestrictions != null
                         ? restrictionState.perUserRestrictions.size() : 0;
-                if (restrictionCount > 0) {
-                    pw.println("      Restricted ops:");
+                if (restrictionCount > 0 && dumpPackage == null) {
+                    boolean printedOpsHeader = false;
                     for (int j = 0; j < restrictionCount; j++) {
                         int userId = restrictionState.perUserRestrictions.keyAt(j);
                         boolean[] restrictedOps = restrictionState.perUserRestrictions.valueAt(j);
                         if (restrictedOps == null) {
                             continue;
                         }
+                        if (dumpOp >= 0 && (dumpOp >= restrictedOps.length
+                                || !restrictedOps[dumpOp])) {
+                            continue;
+                        }
+                        if (!printedTokenHeader) {
+                            pw.println("  User restrictions for token " + token + ":");
+                            printedTokenHeader = true;
+                        }
+                        if (!printedOpsHeader) {
+                            pw.println("      Restricted ops:");
+                            printedOpsHeader = true;
+                        }
                         StringBuilder restrictedOpsValue = new StringBuilder();
                         restrictedOpsValue.append("[");
                         final int restrictedOpCount = restrictedOps.length;
@@ -3911,11 +3936,37 @@
 
                 final int excludedPackageCount = restrictionState.perUserExcludedPackages != null
                         ? restrictionState.perUserExcludedPackages.size() : 0;
-                if (excludedPackageCount > 0) {
-                    pw.println("      Excluded packages:");
+                if (excludedPackageCount > 0 && dumpOp < 0) {
+                    boolean printedPackagesHeader = false;
                     for (int j = 0; j < excludedPackageCount; j++) {
                         int userId = restrictionState.perUserExcludedPackages.keyAt(j);
                         String[] packageNames = restrictionState.perUserExcludedPackages.valueAt(j);
+                        if (packageNames == null) {
+                            continue;
+                        }
+                        boolean hasPackage;
+                        if (dumpPackage != null) {
+                            hasPackage = false;
+                            for (String pkg : packageNames) {
+                                if (dumpPackage.equals(pkg)) {
+                                    hasPackage = true;
+                                    break;
+                                }
+                            }
+                        } else {
+                            hasPackage = true;
+                        }
+                        if (!hasPackage) {
+                            continue;
+                        }
+                        if (!printedTokenHeader) {
+                            pw.println("  User restrictions for token " + token + ":");
+                            printedTokenHeader = true;
+                        }
+                        if (!printedPackagesHeader) {
+                            pw.println("      Excluded packages:");
+                            printedPackagesHeader = true;
+                        }
                         pw.print("        "); pw.print("user: "); pw.print(userId);
                                 pw.print(" packages: "); pw.println(Arrays.toString(packageNames));
                     }
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 5d4c5c3..faca750 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -534,12 +534,6 @@
                 return;
             }
             verifyIncomingUid(uid);
-            if (mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
-                    > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
-                Slog.e(TAG, "Ignoring incoming vibration as process with uid = "
-                        + uid + " is background");
-                return;
-            }
             if (!verifyVibrationEffect(effect)) {
                 return;
             }
@@ -577,6 +571,13 @@
                 }
 
                 Vibration vib = new Vibration(token, effect, usageHint, uid, opPkg, reason);
+                if (mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
+                        > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
+                        && vib.isHapticFeedback()) {
+                    Slog.e(TAG, "Ignoring incoming vibration as process with uid = "
+                            + uid + " is background");
+                    return;
+                }
                 linkVibration(vib);
                 long ident = Binder.clearCallingIdentity();
                 try {
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index ea01a0b..1b787b8 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -279,9 +279,9 @@
      * <p>True means enabling muting logic.
      * <p>False means never mute device.
      */
-    // TODO(OEM): set to true to disable muting.
+    // TODO(OEM): Change property to ro and set to true to disable muting.
     static final String PROPERTY_SYSTEM_AUDIO_MODE_MUTING_ENABLE =
-            "ro.hdmi.property_system_audio_mode_muting_enable";
+            "persist.sys.hdmi.property_system_audio_mode_muting_enable";
 
     // Set to false to allow playback device to go to suspend mode even
     // when it's an active source. True by default.
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java b/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java
index 11faa56..2da698b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java
@@ -252,6 +252,10 @@
         return (HdmiCecLocalDevicePlayback) mSource;
     }
 
+    protected final HdmiCecLocalDeviceSource source() {
+        return (HdmiCecLocalDeviceSource) mSource;
+    }
+
     protected final HdmiCecLocalDeviceTv tv() {
         return (HdmiCecLocalDeviceTv) mSource;
     }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 7860122..c338e21 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -27,10 +27,12 @@
 import android.view.InputDevice;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
 import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Iterator;
@@ -51,6 +53,11 @@
     // Within the timer, a received <User Control Pressed> will start "Press and Hold" behavior.
     // When it expires, we can assume <User Control Release> is received.
     private static final int FOLLOWER_SAFETY_TIMEOUT = 550;
+    /**
+     * Return value of {@link #getLocalPortFromPhysicalAddress(int)}
+     */
+    private static final int TARGET_NOT_UNDER_LOCAL_DEVICE = -1;
+    private static final int TARGET_SAME_PHYSICAL_ADDRESS = 0;
 
     protected final HdmiControlService mService;
     protected final int mDeviceType;
@@ -434,10 +441,14 @@
         return true;
     }
 
+    // Audio System device with no Playback device type
+    // needs to refactor this function if it's also a switch
     protected boolean handleRoutingChange(HdmiCecMessage message) {
         return false;
     }
 
+    // Audio System device with no Playback device type
+    // needs to refactor this function if it's also a switch
     protected boolean handleRoutingInformation(HdmiCecMessage message) {
         return false;
     }
@@ -1033,4 +1044,45 @@
         pw.println("mActiveSource: " + mActiveSource);
         pw.println(String.format("mActiveRoutingPath: 0x%04x", mActiveRoutingPath));
     }
+
+    /**
+     * Method to parse target physical address to the port number on the current device.
+     *
+     * <p>This check assumes target address is valid.
+     * @param targetPhysicalAddress is the physical address of the target device
+     * @return
+     * <p>If the target device is under the current device, return the port number of current device
+     * that the target device is connected to.
+     *
+     * <p>If the target device has the same physical address as the current device, return
+     * {@link #TARGET_SAME_PHYSICAL_ADDRESS}.
+     *
+     * <p>If the target device is not under the current device, return
+     * {@link #TARGET_NOT_UNDER_LOCAL_DEVICE}.
+     */
+    protected int getLocalPortFromPhysicalAddress(int targetPhysicalAddress) {
+        int myPhysicalAddress = mService.getPhysicalAddress();
+        if (myPhysicalAddress == targetPhysicalAddress) {
+            return TARGET_SAME_PHYSICAL_ADDRESS;
+        }
+        int finalMask = 0xF000;
+        int mask;
+        int port = 0;
+        for (mask = 0x0F00; mask > 0x000F;  mask >>= 4) {
+            if ((myPhysicalAddress & mask) == 0)  {
+                port = mask & targetPhysicalAddress;
+                break;
+            } else {
+                finalMask |= mask;
+            }
+        }
+        if (finalMask != 0xFFFF && (finalMask & targetPhysicalAddress) == myPhysicalAddress) {
+            while (mask != 0x000F) {
+                mask >>= 4;
+                port >>= 4;
+            }
+            return port;
+        }
+        return TARGET_NOT_UNDER_LOCAL_DEVICE;
+    }
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index 7358eaa..d8a2d89 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -36,7 +36,7 @@
  * Represent a logical device of type {@link HdmiDeviceInfo#DEVICE_AUDIO_SYSTEM} residing in Android
  * system.
  */
-public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice {
+public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource {
 
     private static final String TAG = "HdmiCecLocalDeviceAudioSystem";
 
@@ -143,43 +143,17 @@
             int systemAudioOnPowerOnProp, boolean lastSystemAudioControlStatus) {
         if ((systemAudioOnPowerOnProp == ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON)
                 || ((systemAudioOnPowerOnProp == USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON)
-                        && lastSystemAudioControlStatus)) {
+                && lastSystemAudioControlStatus)) {
             addAndStartAction(new SystemAudioInitiationActionFromAvr(this));
         }
     }
 
-    @ServiceThreadOnly
-    protected boolean handleActiveSource(HdmiCecMessage message) {
-        assertRunOnServiceThread();
-        int logicalAddress = message.getSource();
-        int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
-        ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress);
-        if (!mActiveSource.equals(activeSource)) {
-            setActiveSource(activeSource);
-        }
-        return true;
-    }
-
-    @Override
-    @ServiceThreadOnly
-    protected boolean handleSetStreamPath(HdmiCecMessage message) {
-        assertRunOnServiceThread();
-        int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
-        // If current device is the target path, playback device should handle it.
-        // If the path is under the current device, should switch
-        int port = getLocalPortFromPhysicalAddress(physicalAddress);
-        if (port > 0) {
-            routeToPort(port);
-        }
-        return true;
-    }
-
     @Override
     @ServiceThreadOnly
     protected int getPreferredAddress() {
         assertRunOnServiceThread();
         return SystemProperties.getInt(
-                Constants.PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM, Constants.ADDR_UNREGISTERED);
+            Constants.PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM, Constants.ADDR_UNREGISTERED);
     }
 
     @Override
@@ -323,7 +297,7 @@
         for (int i = 0; i < params.length; i++) {
             byte val = params[i];
             audioFormatCodes[i] =
-                    val >= 1 && val <= Constants.AUDIO_CODEC_MAX ? val : Constants.AUDIO_CODEC_NONE;
+                val >= 1 && val <= Constants.AUDIO_CODEC_MAX ? val : Constants.AUDIO_CODEC_NONE;
         }
         return audioFormatCodes;
     }
@@ -333,7 +307,23 @@
     protected boolean handleSystemAudioModeRequest(HdmiCecMessage message) {
         assertRunOnServiceThread();
         boolean systemAudioStatusOn = message.getParams().length != 0;
-        if (!setSystemAudioMode(systemAudioStatusOn)) {
+        // Check if the request comes from a non-TV device.
+        // Need to check if TV supports System Audio Control
+        // if non-TV device tries to turn on the feature
+        if (message.getSource() != Constants.ADDR_TV) {
+            if (systemAudioStatusOn) {
+                handleSystemAudioModeOnFromNonTvDevice(message);
+                return true;
+            }
+        } else {
+            // If TV request the feature on
+            // cache TV supporting System Audio Control
+            // until Audio System loses its physical address.
+            setTvSystemAudioModeSupport(true);
+        }
+        // If TV or Audio System does not support the feature,
+        // will send abort command.
+        if (!checkSupportAndSetSystemAudioMode(systemAudioStatusOn)) {
             mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
             return true;
         }
@@ -348,7 +338,8 @@
     @ServiceThreadOnly
     protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
         assertRunOnServiceThread();
-        if (!setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message))) {
+        if (!checkSupportAndSetSystemAudioMode(
+                HdmiUtils.parseCommandParamSystemAudioStatus(message))) {
             mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
         }
         return true;
@@ -358,7 +349,8 @@
     @ServiceThreadOnly
     protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
         assertRunOnServiceThread();
-        if (!setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message))) {
+        if (!checkSupportAndSetSystemAudioMode(
+                HdmiUtils.parseCommandParamSystemAudioStatus(message))) {
             mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
         }
         return true;
@@ -390,7 +382,7 @@
     private void notifyArcStatusToAudioService(boolean enabled) {
         // Note that we don't set any name to ARC.
         mService.getAudioManager()
-                .setWiredDeviceConnectionState(AudioSystem.DEVICE_IN_HDMI, enabled ? 1 : 0, "", "");
+            .setWiredDeviceConnectionState(AudioSystem.DEVICE_IN_HDMI, enabled ? 1 : 0, "", "");
     }
 
     private void reportAudioStatus(HdmiCecMessage message) {
@@ -406,7 +398,16 @@
                         mAddress, message.getSource(), scaledVolume, mute));
     }
 
-    protected boolean setSystemAudioMode(boolean newSystemAudioMode) {
+    /**
+     * Method to check if device support System Audio Control. If so, wake up device if necessary.
+     *
+     * <p> then call {@link #setSystemAudioMode(boolean)} to turn on or off System Audio Mode
+     * @param newSystemAudioMode turning feature on or off. True is on. False is off.
+     * @return true or false.
+     *
+     * <p>False when device does not support the feature. Otherwise returns true.
+     */
+    protected boolean checkSupportAndSetSystemAudioMode(boolean newSystemAudioMode) {
         if (!isSystemAudioControlFeatureEnabled()) {
             HdmiLogger.debug(
                     "Cannot turn "
@@ -422,6 +423,17 @@
         if (newSystemAudioMode && mService.isPowerStandbyOrTransient()) {
             mService.wakeUp();
         }
+        setSystemAudioMode(newSystemAudioMode);
+        return true;
+    }
+
+    /**
+     * Real work to turn on or off System Audio Mode.
+     *
+     * Use {@link #checkSupportAndSetSystemAudioMode(boolean)}
+     * if trying to turn on or off the feature.
+     */
+    private void setSystemAudioMode(boolean newSystemAudioMode) {
         int targetPhysicalAddress = getActiveSource().physicalAddress;
         int port = getLocalPortFromPhysicalAddress(targetPhysicalAddress);
         if (newSystemAudioMode && port >= 0) {
@@ -449,48 +461,6 @@
                 mService.announceSystemAudioModeChange(newSystemAudioMode);
             }
         }
-        return true;
-    }
-
-    /**
-     * Method to parse target physical address to the port number on the current device.
-     *
-     * <p>This check assumes target address is valid.
-     * @param targetPhysicalAddress is the physical address of the target device
-     * @return
-     * <p>If the target device is under the current device, return the port number of current device
-     * that the target device is connected to.
-     *
-     * <p>If the target device has the same physical address as the current device, return
-     * {@link #TARGET_SAME_PHYSICAL_ADDRESS}.
-     *
-     * <p>If the target device is not under the current device, return
-     * {@link #TARGET_NOT_UNDER_LOCAL_DEVICE}.
-     */
-    protected int getLocalPortFromPhysicalAddress(int targetPhysicalAddress) {
-        int myPhysicalAddress = mService.getPhysicalAddress();
-        if (myPhysicalAddress == targetPhysicalAddress) {
-            return TARGET_SAME_PHYSICAL_ADDRESS;
-        }
-        int finalMask = 0xF000;
-        int mask;
-        int port = 0;
-        for (mask = 0x0F00; mask > 0x000F;  mask >>= 4) {
-            if ((myPhysicalAddress & mask) == 0)  {
-                port = mask & targetPhysicalAddress;
-                break;
-            } else {
-                finalMask |= mask;
-            }
-        }
-        if (finalMask != 0xFFFF && (finalMask & targetPhysicalAddress) == myPhysicalAddress) {
-            while (mask != 0x000F) {
-                mask >>= 4;
-                port >>= 4;
-            }
-            return port;
-        }
-        return TARGET_NOT_UNDER_LOCAL_DEVICE;
     }
 
     protected void switchToAudioInput() {
@@ -534,7 +504,7 @@
             return;
         }
 
-        if (setSystemAudioMode(false)) {
+        if (checkSupportAndSetSystemAudioMode(false)) {
             // send <Set System Audio Mode> [“Off”]
             mService.sendCecCommand(
                     HdmiCecMessageBuilder.buildSetSystemAudioMode(
@@ -557,6 +527,7 @@
      * <p>The result of the query may be cached until Audio device type is put in standby or loses
      * its physical address.
      */
+    // TODO(amyjojo): making mTvSystemAudioModeSupport null originally and fix the logic.
     void queryTvSystemAudioModeSupport(TvSystemAudioModeSupportedCallback callback) {
         if (!mTvSystemAudioModeSupport) {
             addAndStartAction(new DetectTvSystemAudioModeSupportAction(this, callback));
@@ -565,6 +536,37 @@
         }
     }
 
+    /**
+     * Handler of System Audio Mode Request on from non TV device
+     */
+    void handleSystemAudioModeOnFromNonTvDevice(HdmiCecMessage message) {
+        if (!isSystemAudioControlFeatureEnabled()) {
+            HdmiLogger.debug(
+                    "Cannot turn on" + "system audio mode "
+                            + "because the System Audio Control feature is disabled.");
+            mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
+            return;
+        }
+        // Wake up device if it is still on standby
+        if (mService.isPowerStandbyOrTransient()) {
+            mService.wakeUp();
+        }
+        // Check if TV supports System Audio Control.
+        // Handle broadcasting setSystemAudioMode on or aborting message on callback.
+        queryTvSystemAudioModeSupport(new TvSystemAudioModeSupportedCallback() {
+            public void onResult(boolean supported) {
+                if (supported) {
+                    setSystemAudioMode(true);
+                    mService.sendCecCommand(
+                            HdmiCecMessageBuilder.buildSetSystemAudioMode(
+                                    mAddress, Constants.ADDR_BROADCAST, true));
+                } else {
+                    mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
+                }
+            }
+        });
+    }
+
     void setTvSystemAudioModeSupport(boolean supported) {
         mTvSystemAudioModeSupport = supported;
     }
@@ -588,14 +590,4 @@
         assertRunOnServiceThread();
         mAutoDeviceOff = autoDeviceOff;
     }
-
-    private void routeToPort(int portId) {
-        // TODO(AMYJOJO): route to specific input of the port
-        mLocalActivePath = portId;
-    }
-
-    @VisibleForTesting
-    protected int getLocalActivePath() {
-        return mLocalActivePath;
-    }
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index d45b00b..be7588a 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -26,6 +26,7 @@
 import android.provider.Settings.Global;
 import android.util.Slog;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.LocalePicker;
 import com.android.internal.app.LocalePicker.LocaleInfo;
 import com.android.internal.util.IndentingPrintWriter;
@@ -35,12 +36,10 @@
 import java.util.List;
 import java.util.Locale;
 
-import java.util.List;
-
 /**
  * Represent a logical device of type Playback residing in Android system.
  */
-final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice {
+final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
     private static final String TAG = "HdmiCecLocalDevicePlayback";
 
     private static final boolean WAKE_ON_HOTPLUG =
@@ -62,6 +61,11 @@
     // If true, turn off TV upon standby. False by default.
     private boolean mAutoTvOff;
 
+    // Local active port number used for Routing Control.
+    // Default 0 means HOME is the current active path. Temp solution only.
+    // TODO(amyjojo): adding system constants for input ports to TIF mapping.
+    private int mLocalActivePath = 0;
+
     HdmiCecLocalDevicePlayback(HdmiControlService service) {
         super(service, HdmiDeviceInfo.DEVICE_PLAYBACK);
 
@@ -100,25 +104,6 @@
     }
 
     @ServiceThreadOnly
-    void oneTouchPlay(IHdmiControlCallback callback) {
-        assertRunOnServiceThread();
-        List<OneTouchPlayAction> actions = getActions(OneTouchPlayAction.class);
-        if (!actions.isEmpty()) {
-            Slog.i(TAG, "oneTouchPlay already in progress");
-            actions.get(0).addCallback(callback);
-            return;
-        }
-        OneTouchPlayAction action = OneTouchPlayAction.create(this, Constants.ADDR_TV,
-                callback);
-        if (action == null) {
-            Slog.w(TAG, "Cannot initiate oneTouchPlay");
-            invokeCallback(callback, HdmiControlManager.RESULT_EXCEPTION);
-            return;
-        }
-        addAndStartAction(action);
-    }
-
-    @ServiceThreadOnly
     void queryDisplayStatus(IHdmiControlCallback callback) {
         assertRunOnServiceThread();
         List<DevicePowerStatusAction> actions = getActions(DevicePowerStatusAction.class);
@@ -227,21 +212,6 @@
         return !getWakeLock().isHeld();
     }
 
-    @Override
-    @ServiceThreadOnly
-    protected boolean handleActiveSource(HdmiCecMessage message) {
-        assertRunOnServiceThread();
-        int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
-        mayResetActiveSource(physicalAddress);
-        return true;  // Broadcast message.
-    }
-
-    private void mayResetActiveSource(int physicalAddress) {
-        if (physicalAddress != mService.getPhysicalAddress()) {
-            setActiveSource(false);
-        }
-    }
-
     @ServiceThreadOnly
     protected boolean handleUserControlPressed(HdmiCecMessage message) {
         assertRunOnServiceThread();
@@ -254,10 +224,21 @@
     protected boolean handleSetStreamPath(HdmiCecMessage message) {
         assertRunOnServiceThread();
         int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
-        maySetActiveSource(physicalAddress);
-        maySendActiveSource(message.getSource());
-        wakeUpIfActiveSource();
-        return true;  // Broadcast message.
+        // If current device is the target path, set to Active Source.
+        // If the path is under the current device, should switch
+        int port = getLocalPortFromPhysicalAddress(physicalAddress);
+        if (port == 0) {
+            setActiveSource(true);
+            maySendActiveSource(message.getSource());
+            wakeUpIfActiveSource();
+        } else if (port > 0) {
+            // Wake up the device if the power is in standby mode for routing
+            if (mService.isPowerStandbyOrTransient()) {
+                mService.wakeUp();
+            }
+            routeToPort(port);
+        }
+        return true;
     }
 
     // Samsung model we tested sends <Routing Change> and <Request Active Source>
@@ -306,14 +287,6 @@
         }
     }
 
-    @Override
-    @ServiceThreadOnly
-    protected boolean handleRequestActiveSource(HdmiCecMessage message) {
-        assertRunOnServiceThread();
-        maySendActiveSource(message.getSource());
-        return true;  // Broadcast message.
-    }
-
     @ServiceThreadOnly
     protected boolean handleSetMenuLanguage(HdmiCecMessage message) {
         assertRunOnServiceThread();
@@ -383,6 +356,16 @@
         checkIfPendingActionsCleared();
     }
 
+    private void routeToPort(int portId) {
+        // TODO(AMYJOJO): route to specific input of the port
+        mLocalActivePath = portId;
+    }
+
+    @VisibleForTesting
+    protected int getLocalActivePath() {
+        return mLocalActivePath;
+    }
+
     @Override
     protected void dump(final IndentingPrintWriter pw) {
         super.dump(pw);
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
new file mode 100644
index 0000000..f9180b7
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2018 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.server.hdmi;
+
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.IHdmiControlCallback;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
+
+import java.util.List;
+
+/**
+ * Represent a logical source device residing in Android system.
+ */
+abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice {
+
+    private static final String TAG = "HdmiCecLocalDeviceSource";
+
+    private boolean mIsActiveSource = false;
+
+    protected HdmiCecLocalDeviceSource(HdmiControlService service, int deviceType) {
+        super(service, deviceType);
+    }
+
+    @Override
+    @ServiceThreadOnly
+    void onHotplug(int portId, boolean connected) {
+        assertRunOnServiceThread();
+        mCecMessageCache.flushAll();
+        // We'll not clear mIsActiveSource on the hotplug event to pass CETC 11.2.2-2 ~ 3.
+        if (mService.isPowerStandbyOrTransient()) {
+            mService.wakeUp();
+        }
+    }
+
+    @ServiceThreadOnly
+    void oneTouchPlay(IHdmiControlCallback callback) {
+        assertRunOnServiceThread();
+        List<OneTouchPlayAction> actions = getActions(OneTouchPlayAction.class);
+        if (!actions.isEmpty()) {
+            Slog.i(TAG, "oneTouchPlay already in progress");
+            actions.get(0).addCallback(callback);
+            return;
+        }
+        OneTouchPlayAction action = OneTouchPlayAction.create(this, Constants.ADDR_TV,
+                callback);
+        if (action == null) {
+            Slog.w(TAG, "Cannot initiate oneTouchPlay");
+            invokeCallback(callback, HdmiControlManager.RESULT_EXCEPTION);
+            return;
+        }
+        addAndStartAction(action);
+    }
+
+    @ServiceThreadOnly
+    private void invokeCallback(IHdmiControlCallback callback, int result) {
+        assertRunOnServiceThread();
+        try {
+            callback.onComplete(result);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Invoking callback failed:" + e);
+        }
+    }
+
+    @ServiceThreadOnly
+    protected boolean handleActiveSource(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+        int logicalAddress = message.getSource();
+        int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
+        ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress);
+        if (physicalAddress != mService.getPhysicalAddress()
+                || !mActiveSource.equals(activeSource)) {
+            setActiveSource(activeSource);
+            setActiveSource(false);
+        }
+        return true;
+    }
+
+    @Override
+    @ServiceThreadOnly
+    protected boolean handleRequestActiveSource(HdmiCecMessage message) {
+        assertRunOnServiceThread();
+        if (mIsActiveSource) {
+            mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(
+                    mAddress, mService.getPhysicalAddress()));
+        }
+        return true;
+    }
+
+    @ServiceThreadOnly
+    void setActiveSource(boolean on) {
+        assertRunOnServiceThread();
+        mIsActiveSource = on;
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index e3a4084..10f6f92 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -1830,9 +1830,13 @@
     @ServiceThreadOnly
     private void oneTouchPlay(final IHdmiControlCallback callback) {
         assertRunOnServiceThread();
-        HdmiCecLocalDevicePlayback source = playback();
+        HdmiCecLocalDeviceSource source = playback();
         if (source == null) {
-            Slog.w(TAG, "Local playback device not available");
+            source = audioSystem();
+        }
+
+        if (source == null) {
+            Slog.w(TAG, "Local source device not available");
             invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
             return;
         }
diff --git a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
index 5c66316..c7ba7cc 100644
--- a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
+++ b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
@@ -16,8 +16,8 @@
 package com.android.server.hdmi;
 
 import android.hardware.hdmi.HdmiControlManager;
-import android.hardware.hdmi.IHdmiControlCallback;
 import android.hardware.hdmi.HdmiPlaybackClient.OneTouchPlayCallback;
+import android.hardware.hdmi.IHdmiControlCallback;
 import android.os.RemoteException;
 import android.util.Slog;
 
@@ -55,7 +55,7 @@
     private int mPowerStatusCounter = 0;
 
     // Factory method. Ensures arguments are valid.
-    static OneTouchPlayAction create(HdmiCecLocalDevicePlayback source,
+    static OneTouchPlayAction create(HdmiCecLocalDeviceSource source,
             int targetAddress, IHdmiControlCallback callback) {
         if (source == null || callback == null) {
             Slog.e(TAG, "Wrong arguments");
@@ -84,8 +84,8 @@
 
     private void broadcastActiveSource() {
         sendCommand(HdmiCecMessageBuilder.buildActiveSource(getSourceAddress(), getSourcePath()));
-        // Because only playback device can create this action, it's safe to cast.
-        playback().setActiveSource(true);
+        // Because only source device can create this action, it's safe to cast.
+        source().setActiveSource(true);
     }
 
     private void queryDevicePowerStatus() {
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java b/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java
index 2fdcb51..a6e6965 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java
@@ -16,6 +16,7 @@
 package com.android.server.hdmi;
 
 import android.hardware.tv.cec.V1_0.SendMessageResult;
+
 import com.android.internal.annotations.VisibleForTesting;
 
 /**
@@ -91,7 +92,7 @@
                             mSendRequestActiveSourceRetryCount++;
                             sendRequestActiveSource();
                         } else {
-                            audioSystem().setSystemAudioMode(false);
+                            audioSystem().checkSupportAndSetSystemAudioMode(false);
                             finish();
                         }
                     }
@@ -106,7 +107,7 @@
                             mSendSetSystemAudioModeRetryCount++;
                             sendSetSystemAudioMode(on, dest);
                         } else {
-                            audioSystem().setSystemAudioMode(false);
+                            audioSystem().checkSupportAndSetSystemAudioMode(false);
                             finish();
                         }
                     }
@@ -115,7 +116,7 @@
 
     private void handleActiveSourceTimeout() {
         HdmiLogger.debug("Cannot get active source.");
-        audioSystem().setSystemAudioMode(false);
+        audioSystem().checkSupportAndSetSystemAudioMode(false);
         finish();
     }
 
@@ -123,12 +124,12 @@
         audioSystem().queryTvSystemAudioModeSupport(
                 supported -> {
                     if (supported) {
-                        if (audioSystem().setSystemAudioMode(true)) {
+                        if (audioSystem().checkSupportAndSetSystemAudioMode(true)) {
                             sendSetSystemAudioMode(true, Constants.ADDR_BROADCAST);
                         }
                         finish();
                     } else {
-                        audioSystem().setSystemAudioMode(false);
+                        audioSystem().checkSupportAndSetSystemAudioMode(false);
                         finish();
                     }
                 });
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 2329356..48ee9dc 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.apex.ApexInfo;
+import android.apex.ApexInfoList;
 import android.apex.IApexService;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionInfo;
@@ -127,28 +128,55 @@
         return false;
     }
 
-    void commitSession(@NonNull PackageInstallerSession sessionInfo) {
-        updateStoredSession(sessionInfo);
+    private static boolean submitSessionToApexService(int sessionId, ApexInfoList apexInfoList) {
+        final IApexService apex = IApexService.Stub.asInterface(
+                ServiceManager.getService("apexservice"));
+        boolean success;
+        try {
+            success = apex.submitStagedSession(sessionId, apexInfoList);
+        } catch (RemoteException re) {
+            Slog.e(TAG, "Unable to contact apexservice", re);
+            return false;
+        }
+        return success;
+    }
 
-        mBgHandler.post(() -> {
-            sessionInfo.setStagedSessionReady();
+    void preRebootVerification(@NonNull PackageInstallerSession session) {
+        boolean success = true;
+        if ((session.params.installFlags & PackageManager.INSTALL_APEX) != 0) {
 
-            SessionInfo session = sessionInfo.generateInfo(false);
-            // For APEXes, we validate the signature here before we write the package to the
-            // staging directory. For APKs, the signature verification will be done by the package
-            // manager at the point at which it applies the staged install.
-            //
-            // TODO: Decide whether we want to fail fast by detecting signature mismatches right
-            // away.
-            if ((sessionInfo.params.installFlags & PackageManager.INSTALL_APEX) != 0) {
-                if (!validateApexSignatureLocked(session.resolvedBaseCodePath,
-                        session.appPackageName)) {
-                    sessionInfo.setStagedSessionFailed(SessionInfo.VERIFICATION_FAILED);
+            final ApexInfoList apexInfoList = new ApexInfoList();
+
+            if (!submitSessionToApexService(session.sessionId, apexInfoList)) {
+                success = false;
+            } else {
+                // For APEXes, we validate the signature here before we mark the session as ready,
+                // so we fail the session early if there is a signature mismatch. For APKs, the
+                // signature verification will be done by the package manager at the point at which
+                // it applies the staged install.
+                //
+                // TODO: Decide whether we want to fail fast by detecting signature mismatches right
+                // away.
+                for (ApexInfo apexPackage : apexInfoList.apexInfos) {
+                    if (!validateApexSignatureLocked(apexPackage.packagePath,
+                            apexPackage.packageName)) {
+                        success = false;
+                        break;
+                    }
                 }
             }
+        }
+        if (success) {
+            session.setStagedSessionReady();
+        } else {
+            session.setStagedSessionFailed(SessionInfo.VERIFICATION_FAILED);
+        }
+        mPm.sendSessionUpdatedBroadcast(session.generateInfo(false), session.userId);
+    }
 
-            mPm.sendSessionUpdatedBroadcast(sessionInfo.generateInfo(false), sessionInfo.userId);
-        });
+    void commitSession(@NonNull PackageInstallerSession session) {
+        updateStoredSession(session);
+        mBgHandler.post(() -> preRebootVerification(session));
     }
 
     void createSession(@NonNull PackageInstallerSession sessionInfo) {
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 9ecafb3..c8c5e8f 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -107,6 +107,7 @@
         "android.hardware.gnss@1.0",
         "android.hardware.gnss@1.1",
         "android.hardware.gnss@2.0",
+        "android.hardware.input.classifier@1.0",
         "android.hardware.ir@1.0",
         "android.hardware.light@2.0",
         "android.hardware.power@1.0",
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index bdede33..7049b21 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -17,6 +17,7 @@
 
 import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
 import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
+import static com.android.server.hdmi.Constants.ADDR_TUNER_1;
 import static com.android.server.hdmi.Constants.ADDR_TV;
 import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
 import static com.android.server.hdmi.HdmiControlService.STANDBY_SCREEN_OFF;
@@ -64,70 +65,71 @@
     @Before
     public void setUp() {
         mHdmiControlService =
-                new HdmiControlService(InstrumentationRegistry.getTargetContext()) {
-                    @Override
-                    AudioManager getAudioManager() {
-                        return new AudioManager() {
-                            @Override
-                            public int getStreamVolume(int streamType) {
-                                switch (streamType) {
-                                    case STREAM_MUSIC:
-                                        return mMusicVolume;
-                                    default:
-                                        return 0;
-                                }
+            new HdmiControlService(InstrumentationRegistry.getTargetContext()) {
+                @Override
+                AudioManager getAudioManager() {
+                    return new AudioManager() {
+                        @Override
+                        public int getStreamVolume(int streamType) {
+                            switch (streamType) {
+                                case STREAM_MUSIC:
+                                    return mMusicVolume;
+                                default:
+                                    return 0;
                             }
+                        }
 
-                            @Override
-                            public boolean isStreamMute(int streamType) {
-                                switch (streamType) {
-                                    case STREAM_MUSIC:
-                                        return mMusicMute;
-                                    default:
-                                        return false;
-                                }
+                        @Override
+                        public boolean isStreamMute(int streamType) {
+                            switch (streamType) {
+                                case STREAM_MUSIC:
+                                    return mMusicMute;
+                                default:
+                                    return false;
                             }
+                        }
 
-                            @Override
-                            public int getStreamMaxVolume(int streamType) {
-                                switch (streamType) {
-                                    case STREAM_MUSIC:
-                                        return mMusicMaxVolume;
-                                    default:
-                                        return 100;
-                                }
+                        @Override
+                        public int getStreamMaxVolume(int streamType) {
+                            switch (streamType) {
+                                case STREAM_MUSIC:
+                                    return mMusicMaxVolume;
+                                default:
+                                    return 100;
                             }
+                        }
 
-                            @Override
-                            public void adjustStreamVolume(
-                                    int streamType, int direction, int flags) {
-                                switch (streamType) {
-                                    case STREAM_MUSIC:
-                                        if (direction == AudioManager.ADJUST_UNMUTE) {
-                                            mMusicMute = false;
-                                        } else if (direction == AudioManager.ADJUST_MUTE) {
-                                            mMusicMute = true;
-                                        }
-                                    default:
-                                }
+                        @Override
+                        public void adjustStreamVolume(
+                                int streamType, int direction, int flags) {
+                            switch (streamType) {
+                                case STREAM_MUSIC:
+                                    if (direction == AudioManager.ADJUST_UNMUTE) {
+                                        mMusicMute = false;
+                                    } else if (direction == AudioManager.ADJUST_MUTE) {
+                                        mMusicMute = true;
+                                    }
+                                    break;
+                                default:
                             }
+                        }
 
-                            @Override
-                            public void setWiredDeviceConnectionState(
-                                    int type, int state, String address, String name) {
-                                // Do nothing.
-                            }
-                        };
-                    }
+                        @Override
+                        public void setWiredDeviceConnectionState(
+                                int type, int state, String address, String name) {
+                            // Do nothing.
+                        }
+                    };
+                }
 
-                    @Override
-                    void wakeUp() {}
+                @Override
+                void wakeUp() {}
 
-                    @Override
-                    boolean isControlEnabled() {
-                        return true;
-                    }
-                };
+                @Override
+                boolean isControlEnabled() {
+                    return true;
+                }
+            };
 
         mMyLooper = mTestLooper.getLooper();
         mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(mHdmiControlService);
@@ -135,7 +137,7 @@
         mHdmiControlService.setIoLooper(mMyLooper);
         mNativeWrapper = new FakeNativeWrapper();
         mHdmiCecController =
-                HdmiCecController.createWithNativeWrapper(mHdmiControlService, mNativeWrapper);
+            HdmiCecController.createWithNativeWrapper(mHdmiControlService, mNativeWrapper);
         mHdmiControlService.setCecController(mHdmiCecController);
         mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
         mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
@@ -170,11 +172,12 @@
     @Test
     public void handleGiveSystemAudioModeStatus_originalOff() throws Exception {
         HdmiCecMessage expectedMessage =
-                HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false);
+                HdmiCecMessageBuilder.buildReportSystemAudioMode(
+                        ADDR_AUDIO_SYSTEM, ADDR_TV, false);
         HdmiCecMessage messageGive =
                 HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus(ADDR_TV, ADDR_AUDIO_SYSTEM);
         assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive))
-                .isTrue();
+            .isTrue();
         mTestLooper.dispatchAll();
         assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
     }
@@ -190,9 +193,9 @@
 
         mHdmiCecLocalDeviceAudioSystem.setSystemAudioControlFeatureEnabled(false);
         assertThat(
-                        mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor(
-                                MESSAGE_REQUEST_SAD_LCPM))
-                .isTrue();
+            mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor(
+                MESSAGE_REQUEST_SAD_LCPM))
+            .isTrue();
         mTestLooper.dispatchAll();
         assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
     }
@@ -206,11 +209,11 @@
                         Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR,
                         Constants.ABORT_NOT_IN_CORRECT_MODE);
 
-        mHdmiCecLocalDeviceAudioSystem.setSystemAudioMode(false);
+        mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(false);
         assertThat(
-                        mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor(
-                                MESSAGE_REQUEST_SAD_LCPM))
-                .isEqualTo(true);
+            mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor(
+                MESSAGE_REQUEST_SAD_LCPM))
+            .isEqualTo(true);
         mTestLooper.dispatchAll();
         assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
     }
@@ -224,11 +227,11 @@
                         Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR,
                         Constants.ABORT_UNABLE_TO_DETERMINE);
 
-        mHdmiCecLocalDeviceAudioSystem.setSystemAudioMode(true);
+        mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(true);
         assertThat(
-                        mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor(
-                                MESSAGE_REQUEST_SAD_LCPM))
-                .isEqualTo(true);
+            mHdmiCecLocalDeviceAudioSystem.handleRequestShortAudioDescriptor(
+                MESSAGE_REQUEST_SAD_LCPM))
+            .isEqualTo(true);
         mTestLooper.dispatchAll();
         assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
     }
@@ -244,17 +247,17 @@
         HdmiCecMessage expectedMessage =
                 HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false);
         assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive))
-                .isTrue();
+            .isTrue();
         mTestLooper.dispatchAll();
         assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
         // Check if correctly turned on
         mNativeWrapper.clearResultMessages();
         expectedMessage =
-                HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, true);
+            HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, true);
         assertThat(mHdmiCecLocalDeviceAudioSystem.handleSetSystemAudioMode(messageSet)).isTrue();
         mTestLooper.dispatchAll();
         assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive))
-                .isTrue();
+            .isTrue();
         mTestLooper.dispatchAll();
         assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
         assertThat(mMusicMute).isFalse();
@@ -273,15 +276,15 @@
                 HdmiCecMessageBuilder.buildSetSystemAudioMode(
                         ADDR_AUDIO_SYSTEM, ADDR_BROADCAST, false);
         assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(messageRequestOff))
-                .isTrue();
+            .isTrue();
         mTestLooper.dispatchAll();
         assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
 
         mNativeWrapper.clearResultMessages();
         expectedMessage =
-                HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false);
+            HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false);
         assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive))
-                .isTrue();
+            .isTrue();
         mTestLooper.dispatchAll();
         assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
         assertThat(mMusicMute).isTrue();
@@ -292,7 +295,7 @@
         mHdmiCecLocalDeviceAudioSystem.setAutoDeviceOff(false);
         mHdmiCecLocalDeviceAudioSystem.setAutoTvOff(false);
         // Set system audio control on first
-        mHdmiCecLocalDeviceAudioSystem.setSystemAudioMode(true);
+        mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(true);
         // Check if standby correctly turns off the feature
         mHdmiCecLocalDeviceAudioSystem.onStandby(false, STANDBY_SCREEN_OFF);
         mTestLooper.dispatchAll();
@@ -309,9 +312,9 @@
         mHdmiCecLocalDeviceAudioSystem.systemAudioControlOnPowerOn(
                 Constants.ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, true);
         assertThat(
-                        mHdmiCecLocalDeviceAudioSystem.getActions(
-                                SystemAudioInitiationActionFromAvr.class))
-                .isNotEmpty();
+            mHdmiCecLocalDeviceAudioSystem.getActions(
+                SystemAudioInitiationActionFromAvr.class))
+            .isNotEmpty();
     }
 
     @Test
@@ -320,9 +323,9 @@
         mHdmiCecLocalDeviceAudioSystem.systemAudioControlOnPowerOn(
                 Constants.NEVER_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, false);
         assertThat(
-                        mHdmiCecLocalDeviceAudioSystem.getActions(
-                                SystemAudioInitiationActionFromAvr.class))
-                .isEmpty();
+            mHdmiCecLocalDeviceAudioSystem.getActions(
+                SystemAudioInitiationActionFromAvr.class))
+            .isEmpty();
     }
 
     @Test
@@ -331,9 +334,9 @@
         mHdmiCecLocalDeviceAudioSystem.systemAudioControlOnPowerOn(
                 Constants.USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, false);
         assertThat(
-                        mHdmiCecLocalDeviceAudioSystem.getActions(
-                                SystemAudioInitiationActionFromAvr.class))
-                .isEmpty();
+            mHdmiCecLocalDeviceAudioSystem.getActions(
+                SystemAudioInitiationActionFromAvr.class))
+            .isEmpty();
     }
 
     @Test
@@ -342,9 +345,9 @@
         mHdmiCecLocalDeviceAudioSystem.systemAudioControlOnPowerOn(
                 Constants.USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, true);
         assertThat(
-                        mHdmiCecLocalDeviceAudioSystem.getActions(
-                                SystemAudioInitiationActionFromAvr.class))
-                .isNotEmpty();
+            mHdmiCecLocalDeviceAudioSystem.getActions(
+                SystemAudioInitiationActionFromAvr.class))
+            .isNotEmpty();
     }
 
     @Test
@@ -354,12 +357,12 @@
         assertThat(mHdmiCecLocalDeviceAudioSystem.handleActiveSource(message)).isTrue();
         mTestLooper.dispatchAll();
         assertThat(mHdmiCecLocalDeviceAudioSystem.getActiveSource().equals(expectedActiveSource))
-                .isTrue();
+            .isTrue();
     }
 
     @Test
     public void terminateSystemAudioMode_systemAudioModeOff() throws Exception {
-        mHdmiCecLocalDeviceAudioSystem.setSystemAudioMode(false);
+        mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(false);
         assertThat(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()).isFalse();
         mMusicMute = false;
         HdmiCecMessage message =
@@ -373,7 +376,7 @@
 
     @Test
     public void terminateSystemAudioMode_systemAudioModeOn() throws Exception {
-        mHdmiCecLocalDeviceAudioSystem.setSystemAudioMode(true);
+        mHdmiCecLocalDeviceAudioSystem.checkSupportAndSetSystemAudioMode(true);
         assertThat(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()).isTrue();
         mMusicMute = false;
         HdmiCecMessage expectedMessage =
@@ -458,7 +461,7 @@
         assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message)).isTrue();
         mTestLooper.dispatchAll();
         assertThat(mHdmiCecLocalDeviceAudioSystem.getActions(ArcInitiationActionFromAvr.class))
-                .isNotEmpty();
+            .isNotEmpty();
     }
 
     @Test
@@ -473,7 +476,7 @@
         assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcTermination(message)).isTrue();
         mTestLooper.dispatchAll();
         assertThat(mHdmiCecLocalDeviceAudioSystem.getActions(ArcTerminationActionFromAvr.class))
-                .isNotEmpty();
+            .isNotEmpty();
     }
 
     @Test
@@ -521,12 +524,37 @@
         assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
     }
 
-    @Test
-    public void handleSetStreamPath_underCurrentDevice() {
-        assertThat(mHdmiCecLocalDeviceAudioSystem.getLocalActivePath()).isEqualTo(0);
+    public void handleSystemAudioModeRequest_fromNonTV_tVNotSupport() {
         HdmiCecMessage message =
-                HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x2100);
-        assertThat(mHdmiCecLocalDeviceAudioSystem.handleSetStreamPath(message)).isTrue();
-        assertThat(mHdmiCecLocalDeviceAudioSystem.getLocalActivePath()).isEqualTo(1);
+                HdmiCecMessageBuilder.buildSystemAudioModeRequest(
+                        ADDR_TUNER_1, ADDR_AUDIO_SYSTEM,
+                        mAvrPhysicalAddress, true);
+        HdmiCecMessage expectedMessage =
+                HdmiCecMessageBuilder.buildFeatureAbortCommand(
+                        ADDR_AUDIO_SYSTEM,
+                        ADDR_TUNER_1,
+                        Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST,
+                        Constants.ABORT_REFUSED);
+
+        assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(message)).isTrue();
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
+    }
+
+    @Test
+    public void handleSystemAudioModeRequest_fromNonTV_tVSupport() {
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildSystemAudioModeRequest(
+                        ADDR_TUNER_1, ADDR_AUDIO_SYSTEM,
+                        mAvrPhysicalAddress, true);
+        HdmiCecMessage expectedMessage =
+                HdmiCecMessageBuilder.buildSetSystemAudioMode(
+                        ADDR_AUDIO_SYSTEM, Constants.ADDR_BROADCAST, true);
+        mHdmiCecLocalDeviceAudioSystem.setTvSystemAudioModeSupport(true);
+
+
+        assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(message)).isTrue();
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getOnlyResultMessage()).isEqualTo(expectedMessage);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
new file mode 100644
index 0000000..76f638c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2018 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.server.hdmi;
+
+import static com.android.server.hdmi.Constants.ADDR_TV;
+import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Looper;
+import android.os.test.TestLooper;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.ArrayList;
+
+@SmallTest
+@RunWith(JUnit4.class)
+/** Tests for {@link HdmiCecLocalDevicePlayback} class. */
+public class HdmiCecLocalDevicePlaybackTest {
+
+    private HdmiControlService mHdmiControlService;
+    private HdmiCecController mHdmiCecController;
+    private HdmiCecLocalDevicePlayback mHdmiCecLocalDevicePlayback;
+    private FakeNativeWrapper mNativeWrapper;
+    private Looper mMyLooper;
+    private TestLooper mTestLooper = new TestLooper();
+    private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
+    private int mPlaybackPhysicalAddress;
+
+    @Before
+    public void setUp() {
+        mHdmiControlService =
+            new HdmiControlService(InstrumentationRegistry.getTargetContext()) {
+                @Override
+                void wakeUp() {
+                }
+
+                @Override
+                boolean isControlEnabled() {
+                    return true;
+                }
+            };
+
+        mMyLooper = mTestLooper.getLooper();
+        mHdmiCecLocalDevicePlayback = new HdmiCecLocalDevicePlayback(mHdmiControlService);
+        mHdmiCecLocalDevicePlayback.init();
+        mHdmiControlService.setIoLooper(mMyLooper);
+        mNativeWrapper = new FakeNativeWrapper();
+        mHdmiCecController =
+            HdmiCecController.createWithNativeWrapper(mHdmiControlService, mNativeWrapper);
+        mHdmiControlService.setCecController(mHdmiCecController);
+        mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
+        mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
+        mLocalDevices.add(mHdmiCecLocalDevicePlayback);
+        mHdmiControlService.initPortInfo();
+        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mTestLooper.dispatchAll();
+        mNativeWrapper.clearResultMessages();
+        mPlaybackPhysicalAddress = 0x2000;
+        mNativeWrapper.setPhysicalAddress(mPlaybackPhysicalAddress);
+    }
+
+    @Test
+    public void handleSetStreamPath_underCurrentDevice() {
+        assertThat(mHdmiCecLocalDevicePlayback.getLocalActivePath()).isEqualTo(0);
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x2100);
+        assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message)).isTrue();
+        assertThat(mHdmiCecLocalDevicePlayback.getLocalActivePath()).isEqualTo(1);
+    }
+}
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 2820836..dcaa499 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -239,6 +239,30 @@
             "android.telecom.event.HANDOVER_FAILED";
 
     public static class Details {
+        /** @hide */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(
+                prefix = { "DIRECTION_" },
+                value = {DIRECTION_UNKNOWN, DIRECTION_INCOMING, DIRECTION_OUTGOING})
+        public @interface CallDirection {}
+
+        /**
+         * Indicates that the call is neither and incoming nor an outgoing call.  This can be the
+         * case for calls reported directly by a {@link ConnectionService} in special cases such as
+         * call handovers.
+         */
+        public static final int DIRECTION_UNKNOWN = -1;
+
+        /**
+         * Indicates that the call is an incoming call.
+         */
+        public static final int DIRECTION_INCOMING = 0;
+
+        /**
+         * Indicates that the call is an outgoing call.
+         */
+        public static final int DIRECTION_OUTGOING = 1;
+
 
         /** Call can currently be put on hold or unheld. */
         public static final int CAPABILITY_HOLD = 0x00000001;
@@ -519,6 +543,7 @@
         private final Bundle mIntentExtras;
         private final long mCreationTimeMillis;
         private final CallIdentification mCallIdentification;
+        private final @CallDirection int mCallDirection;
 
         /**
          * Whether the supplied capabilities  supports the specified capability.
@@ -838,6 +863,14 @@
             return mCallIdentification;
         }
 
+        /**
+         * Indicates whether the call is an incoming or outgoing call.
+         * @return The call's direction.
+         */
+        public @CallDirection int getCallDirection() {
+            return mCallDirection;
+        }
+
         @Override
         public boolean equals(Object o) {
             if (o instanceof Details) {
@@ -859,7 +892,8 @@
                         areBundlesEqual(mExtras, d.mExtras) &&
                         areBundlesEqual(mIntentExtras, d.mIntentExtras) &&
                         Objects.equals(mCreationTimeMillis, d.mCreationTimeMillis) &&
-                        Objects.equals(mCallIdentification, d.mCallIdentification);
+                        Objects.equals(mCallIdentification, d.mCallIdentification) &&
+                        Objects.equals(mCallDirection, d.mCallDirection);
             }
             return false;
         }
@@ -881,7 +915,8 @@
                             mExtras,
                             mIntentExtras,
                             mCreationTimeMillis,
-                            mCallIdentification);
+                            mCallIdentification,
+                            mCallDirection);
         }
 
         /** {@hide} */
@@ -902,7 +937,8 @@
                 Bundle extras,
                 Bundle intentExtras,
                 long creationTimeMillis,
-                CallIdentification callIdentification) {
+                CallIdentification callIdentification,
+                int callDirection) {
             mTelecomCallId = telecomCallId;
             mHandle = handle;
             mHandlePresentation = handlePresentation;
@@ -920,6 +956,7 @@
             mIntentExtras = intentExtras;
             mCreationTimeMillis = creationTimeMillis;
             mCallIdentification = callIdentification;
+            mCallDirection = callDirection;
         }
 
         /** {@hide} */
@@ -941,7 +978,8 @@
                     parcelableCall.getExtras(),
                     parcelableCall.getIntentExtras(),
                     parcelableCall.getCreationTimeMillis(),
-                    parcelableCall.getCallIdentification());
+                    parcelableCall.getCallIdentification(),
+                    parcelableCall.getCallDirection());
         }
 
         @Override
diff --git a/telecomm/java/android/telecom/CallIdentification.java b/telecomm/java/android/telecom/CallIdentification.java
index 97af06c..87834fd 100644
--- a/telecomm/java/android/telecom/CallIdentification.java
+++ b/telecomm/java/android/telecom/CallIdentification.java
@@ -250,8 +250,8 @@
         mDetails = details;
         mPhoto = photo;
         mNuisanceConfidence = nuisanceConfidence;
-        mCallScreeningAppName = callScreeningPackageName;
-        mCallScreeningPackageName = callScreeningAppName;
+        mCallScreeningAppName = callScreeningAppName;
+        mCallScreeningPackageName = callScreeningPackageName;
     }
 
     private String mName;
@@ -430,4 +430,22 @@
         return Objects.hash(mName, mDescription, mDetails, mPhoto, mNuisanceConfidence,
                 mCallScreeningAppName, mCallScreeningPackageName);
     }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("[CallId mName=");
+        sb.append(Log.pii(mName));
+        sb.append(", mDesc=");
+        sb.append(mDescription);
+        sb.append(", mDet=");
+        sb.append(mDetails);
+        sb.append(", conf=");
+        sb.append(mNuisanceConfidence);
+        sb.append(", appName=");
+        sb.append(mCallScreeningAppName);
+        sb.append(", pkgName=");
+        sb.append(mCallScreeningPackageName);
+        return sb.toString();
+    }
 }
diff --git a/telecomm/java/android/telecom/CallScreeningService.java b/telecomm/java/android/telecom/CallScreeningService.java
index be96b3c..826ad82 100644
--- a/telecomm/java/android/telecom/CallScreeningService.java
+++ b/telecomm/java/android/telecom/CallScreeningService.java
@@ -21,6 +21,7 @@
 import android.app.Service;
 import android.content.ComponentName;
 import android.content.Intent;
+import android.net.Uri;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -33,8 +34,9 @@
 
 /**
  * This service can be implemented by the default dialer (see
- * {@link TelecomManager#getDefaultDialerPackage()}) to allow or disallow incoming calls before
- * they are shown to a user.
+ * {@link TelecomManager#getDefaultDialerPackage()}) or a third party app to allow or disallow
+ * incoming calls before they are shown to a user.  This service can also provide
+ * {@link CallIdentification} information for calls.
  * <p>
  * Below is an example manifest registration for a {@code CallScreeningService}.
  * <pre>
@@ -56,6 +58,34 @@
  *     information about a {@link Call.Details call} which will be shown to the user in the
  *     Dialer app.</li>
  * </ol>
+ * <p>
+ * <h2>Becoming the {@link CallScreeningService}</h2>
+ * Telecom will bind to a single app chosen by the user which implements the
+ * {@link CallScreeningService} API when there are new incoming and outgoing calls.
+ * <p>
+ * The code snippet below illustrates how your app can request that it fills the call screening
+ * role.
+ * <pre>
+ * {@code
+ * private static final int REQUEST_ID = 1;
+ *
+ * public void requestRole() {
+ *     RoleManager roleManager = (RoleManager) getSystemService(ROLE_SERVICE);
+ *     Intent intent = roleManager.createRequestRoleIntent("android.app.role.CALL_SCREENING_APP");
+ *     startActivityForResult(intent, REQUEST_ID);
+ * }
+ *
+ * &#64;Override
+ * public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ *     if (requestCode == REQUEST_ID) {
+ *         if (resultCode == android.app.Activity.RESULT_OK) {
+ *             // Your app is now the call screening app
+ *         } else {
+ *             // Your app is not the call screening app
+ *         }
+ *     }
+ * }
+ * </pre>
  */
 public abstract class CallScreeningService extends Service {
     /**
@@ -222,30 +252,46 @@
     }
 
     /**
-     * Called when a new incoming call is added.
-     * {@link CallScreeningService#respondToCall(Call.Details, CallScreeningService.CallResponse)}
-     * should be called to allow or disallow the call.
+     * Called when a new incoming or outgoing call is added which is not in the user's contact list.
+     * <p>
+     * A {@link CallScreeningService} must indicate whether an incoming call is allowed or not by
+     * calling
+     * {@link CallScreeningService#respondToCall(Call.Details, CallScreeningService.CallResponse)}.
+     * Your app can tell if a call is an incoming call by checking to see if
+     * {@link Call.Details#getCallDirection()} is {@link Call.Details#DIRECTION_INCOMING}.
+     * <p>
+     * For incoming or outgoing calls, the {@link CallScreeningService} can call
+     * {@link #provideCallIdentification(Call.Details, CallIdentification)} in order to provide
+     * {@link CallIdentification} for the call.
      * <p>
      * Note: The {@link Call.Details} instance provided to a call screening service will only have
      * the following properties set.  The rest of the {@link Call.Details} properties will be set to
      * their default value or {@code null}.
      * <ul>
-     *     <li>{@link Call.Details#getState()}</li>
+     *     <li>{@link Call.Details#getCallDirection()}</li>
      *     <li>{@link Call.Details#getConnectTimeMillis()}</li>
      *     <li>{@link Call.Details#getCreationTimeMillis()}</li>
      *     <li>{@link Call.Details#getHandle()}</li>
      *     <li>{@link Call.Details#getHandlePresentation()}</li>
      * </ul>
+     * <p>
+     * Only calls where the {@link Call.Details#getHandle() handle} {@link Uri#getScheme() scheme}
+     * is {@link PhoneAccount#SCHEME_TEL} are passed for call
+     * screening.  Further, only calls which are not in the user's contacts are passed for
+     * screening.  For outgoing calls, no post-dial digits are passed.
      *
-     * @param callDetails Information about a new incoming call, see {@link Call.Details}.
+     * @param callDetails Information about a new call, see {@link Call.Details}.
      */
     public abstract void onScreenCall(@NonNull Call.Details callDetails);
 
     /**
-     * Responds to the given call, either allowing it or disallowing it.
+     * Responds to the given incoming call, either allowing it or disallowing it.
      * <p>
      * The {@link CallScreeningService} calls this method to inform the system whether the call
      * should be silently blocked or not.
+     * <p>
+     * Calls to this method are ignored unless the {@link Call.Details#getCallDirection()} is
+     * {@link Call.Details#DIRECTION_INCOMING}.
      *
      * @param callDetails The call to allow.
      *                    <p>
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
index 1aeeca7..f5f0af7 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -40,11 +40,30 @@
 import java.util.List;
 
 /**
- * This service is implemented by any app that wishes to provide the user-interface for managing
- * phone calls. Telecom binds to this service while there exists a live (active or incoming) call,
- * and uses it to notify the in-call app of any live and recently disconnected calls. An app must
- * first be set as the default phone app (See {@link TelecomManager#getDefaultDialerPackage()})
- * before the telecom service will bind to its {@code InCallService} implementation.
+ * This service is implemented by an app that wishes to provide functionality for managing
+ * phone calls.
+ * <p>
+ * There are three types of apps which Telecom can bind to when there exists a live (active or
+ * incoming) call:
+ * <ol>
+ *     <li>Default Dialer/Phone app - the default dialer/phone app is one which provides the
+ *     in-call user interface while the device is in a call.  A device is bundled with a system
+ *     provided default dialer/phone app.  The user may choose a single app to take over this role
+ *     from the system app.</li>
+ *     <li>Default Car-mode Dialer/Phone app - the default car-mode dialer/phone app is one which
+ *     provides the in-call user interface while the device is in a call and the device is in car
+ *     mode.  The user may choose a single app to fill this role.</li>
+ *     <li>Call Companion app - a call companion app is one which provides no user interface itself,
+ *     but exposes call information to another display surface, such as a wearable device.  The
+ *     user may choose multiple apps to fill this role.</li>
+ * </ol>
+ * <p>
+ * Apps which wish to fulfill one of the above roles use the {@link android.app.role.RoleManager}
+ * to request that they fill the desired role.
+ *
+ * <h2>Becoming the Default Phone App</h2>
+ * An app filling the role of the default phone app provides a user interface while the device is in
+ * a call, and the device is not in car mode.
  * <p>
  * Below is an example manifest registration for an {@code InCallService}. The meta-data
  * {@link TelecomManager#METADATA_IN_CALL_SERVICE_UI} indicates that this particular
@@ -82,12 +101,34 @@
  * }
  * </pre>
  * <p>
- * When a user installs your application and runs it for the first time, you should prompt the user
- * to see if they would like your application to be the new default phone app.  See the
- * {@link TelecomManager#ACTION_CHANGE_DEFAULT_DIALER} intent documentation for more information on
- * how to do this.
+ * When a user installs your application and runs it for the first time, you should use the
+ * {@link android.app.role.RoleManager} to prompt the user to see if they would like your app to
+ * be the new default phone app.
+ * <p id="requestRole">
+ * The code below shows how your app can request to become the default phone/dialer app:
+ * <pre>
+ * {@code
+ * private static final int REQUEST_ID = 1;
+ *
+ * public void requestRole() {
+ *     RoleManager roleManager = (RoleManager) getSystemService(ROLE_SERVICE);
+ *     Intent intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_DIALER);
+ *     startActivityForResult(intent, REQUEST_ID);
+ * }
+ *
+ * &#64;Override
+ * public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ *     if (requestCode == REQUEST_ID) {
+ *         if (resultCode == android.app.Activity.RESULT_OK) {
+ *             // Your app is now the default dialer app
+ *         } else {
+ *             // Your app is not the default dialer app
+ *         }
+ *     }
+ * }
+ * </pre>
  * <p id="incomingCallNotification">
- * <h2>Showing the Incoming Call Notification</h2>
+ * <h3>Showing the Incoming Call Notification</h3>
  * When your app receives a new incoming call via {@link InCallService#onCallAdded(Call)}, it is
  * responsible for displaying an incoming call UI for the incoming call.  It should do this using
  * {@link android.app.NotificationManager} APIs to post a new incoming call notification.
@@ -121,7 +162,7 @@
  * heads-up notification if the user is actively using the phone.  When the user is not using the
  * phone, your full-screen incoming call UI is used instead.
  * For example:
- * <pre><code>
+ * <pre><code>{@code
  * // Create an intent which triggers your fullscreen incoming call user interface.
  * Intent intent = new Intent(Intent.ACTION_MAIN, null);
  * intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -151,7 +192,49 @@
  * NotificationManager notificationManager = mContext.getSystemService(
  *     NotificationManager.class);
  * notificationManager.notify(YOUR_CHANNEL_ID, YOUR_TAG, YOUR_ID, builder.build());
- * </code></pre>
+ * }</pre>
+ * <p>
+ * <h2>Becoming the Default Car-mode Phone App</h2>
+ * An app filling the role of the default car-mode dialer/phone app provides a user interface while
+ * the device is in a call, and in car mode.  See
+ * {@link android.app.UiModeManager#ACTION_ENTER_CAR_MODE} for more information about car mode.
+ * When the device is in car mode, Telecom binds to the default car-mode dialer/phone app instead
+ * of the usual dialer/phone app.
+ * <p>
+ * Similar to the requirements for becoming the default dialer/phone app, your app must declare a
+ * manifest entry for its {@link InCallService} implementation.  Your manifest entry should ensure
+ * the following conditions are met:
+ * <ul>
+ *     <li>Do NOT declare the {@link TelecomManager#METADATA_IN_CALL_SERVICE_UI} metadata.</li>
+ *     <li>Set the {@link TelecomManager#METADATA_IN_CALL_SERVICE_CAR_MODE_UI} metadata to
+ *     {@code true}<li>
+ *     <li>Your app must request the permission
+ *     {@link android.Manifest.permission.CALL_COMPANION_APP}.</li>
+ * </ul>
+ * <p>
+ * Your app should request to fill the role {@code android.app.role.CAR_MODE_DIALER_APP} in order to
+ * become the default (see <a href="#requestRole">above</a> for how to request your app fills this
+ * role).
+ *
+ * <h2>Becoming a Call Companion App</h2>
+ * An app which fills the companion app role does not directly provide a user interface while the
+ * device is in a call.  Instead, it is typically used to relay information about calls to another
+ * display surface, such as a wearable device.
+ * <p>
+ * Similar to the requirements for becoming the default dialer/phone app, your app must declare a
+ * manifest entry for its {@link InCallService} implementation.  Your manifest entry should
+ * ensure the following conditions are met:
+ * <ul>
+ *     <li>Do NOT declare the {@link TelecomManager#METADATA_IN_CALL_SERVICE_UI} metadata.</li>
+ *     <li>Do NOT declare the {@link TelecomManager#METADATA_IN_CALL_SERVICE_CAR_MODE_UI}
+ *     metadata.</li>
+ *     <li>Your app must request the permission
+ *     {@link android.Manifest.permission.CALL_COMPANION_APP}.</li>
+ * </ul>
+ * <p>
+ * Your app should request to fill the role {@code android.app.role.CALL_COMPANION_APP} in order to
+ * become a call companion app (see <a href="#requestRole">above</a> for how to request your app
+ * fills this role).
  */
 public abstract class InCallService extends Service {
 
diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java
index 911786e..f7dec83 100644
--- a/telecomm/java/android/telecom/ParcelableCall.java
+++ b/telecomm/java/android/telecom/ParcelableCall.java
@@ -24,6 +24,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.RemoteException;
+import android.telecom.Call.Details.CallDirection;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -64,6 +65,7 @@
     private final Bundle mExtras;
     private final long mCreationTimeMillis;
     private final CallIdentification mCallIdentification;
+    private final int mCallDirection;
 
     public ParcelableCall(
             String id,
@@ -92,7 +94,8 @@
             Bundle intentExtras,
             Bundle extras,
             long creationTimeMillis,
-            CallIdentification callIdentification) {
+            CallIdentification callIdentification,
+            int callDirection) {
         mId = id;
         mState = state;
         mDisconnectCause = disconnectCause;
@@ -120,6 +123,7 @@
         mExtras = extras;
         mCreationTimeMillis = creationTimeMillis;
         mCallIdentification = callIdentification;
+        mCallDirection = callDirection;
     }
 
     /** The unique ID of the call. */
@@ -318,6 +322,13 @@
         return mCallIdentification;
     }
 
+    /**
+     * Indicates whether the call is an incoming or outgoing call.
+     */
+    public @CallDirection int getCallDirection() {
+        return mCallDirection;
+    }
+
     /** Responsible for creating ParcelableCall objects for deserialized Parcels. */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public static final Parcelable.Creator<ParcelableCall> CREATOR =
@@ -356,6 +367,7 @@
             ParcelableRttCall rttCall = source.readParcelable(classLoader);
             long creationTimeMillis = source.readLong();
             CallIdentification callIdentification = source.readParcelable(classLoader);
+            int callDirection = source.readInt();
             return new ParcelableCall(
                     id,
                     state,
@@ -383,7 +395,8 @@
                     intentExtras,
                     extras,
                     creationTimeMillis,
-                    callIdentification);
+                    callIdentification,
+                    callDirection);
         }
 
         @Override
@@ -429,6 +442,7 @@
         destination.writeParcelable(mRttCall, 0);
         destination.writeLong(mCreationTimeMillis);
         destination.writeParcelable(mCallIdentification, 0);
+        destination.writeInt(mCallDirection);
     }
 
     @Override