Merge "Add way to use sysui as a broadcast relay for slices" into pi-dev
diff --git a/config/hiddenapi-light-greylist.txt b/config/hiddenapi-light-greylist.txt
index 2dcd03a..0213622 100644
--- a/config/hiddenapi-light-greylist.txt
+++ b/config/hiddenapi-light-greylist.txt
@@ -2865,8 +2865,10 @@
 Landroid/widget/Switch;->mTrackDrawable:Landroid/graphics/drawable/Drawable;
 Landroid/widget/TabHost$IntentContentStrategy;->getContentView()Landroid/view/View;
 Landroid/widget/TabHost$IntentContentStrategy;->tabClosed()V
+Landroid/widget/TabHost$TabSpec;->mIndicatorStrategy:Landroid/widget/TabHost$IndicatorStrategy;
 Landroid/widget/TabHost;->mTabSpecs:Ljava/util/List;
 Landroid/widget/TabHost$TabSpec;->mContentStrategy:Landroid/widget/TabHost$ContentStrategy;
+Landroid/widget/TabWidget;->mDrawBottomStrips:Z
 Landroid/widget/TabWidget;->mSelectedTab:I
 Landroid/widget/TabWidget;->setTabSelectionListener(Landroid/widget/TabWidget$OnTabSelectionChanged;)V
 Landroid/widget/TextView;->assumeLayout()V
@@ -3451,6 +3453,7 @@
 Ljava/lang/reflect/Executable;->artMethod:J
 Ljava/lang/reflect/Parameter;-><init>(Ljava/lang/String;ILjava/lang/reflect/Executable;I)V
 Ljava/lang/reflect/Proxy;->invoke(Ljava/lang/reflect/Proxy;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;
+Ljava/lang/ref/Reference;->getReferent()Ljava/lang/Object;
 Ljava/lang/ref/ReferenceQueue;->add(Ljava/lang/ref/Reference;)V
 Ljava/lang/ref/Reference;->referent:Ljava/lang/Object;
 Ljava/lang/Runtime;->loadLibrary(Ljava/lang/String;Ljava/lang/ClassLoader;)V
@@ -3458,7 +3461,9 @@
 Ljava/lang/Runtime;->mLibPaths:[Ljava/lang/String;
 Ljava/lang/Runtime;->nativeLoad(Ljava/lang/String;Ljava/lang/ClassLoader;)Ljava/lang/String;
 Ljava/lang/Short;->value:S
+Ljava/lang/String;->getCharsNoCheck(II[CI)V
 Ljava/lang/String;-><init>(II[C)V
+Ljava/lang/System;->arraycopy([CI[CII)V
 Ljava/lang/System;->arraycopy([II[III)V
 Ljava/lang/System;-><init>()V
 Ljava/lang/Thread;->contextClassLoader:Ljava/lang/ClassLoader;
@@ -3490,7 +3495,21 @@
 Ljava/lang/Void;-><init>()V
 Ljava/net/Authenticator;->theAuthenticator:Ljava/net/Authenticator;
 Ljava/net/DatagramSocket;->impl:Ljava/net/DatagramSocketImpl;
+Ljava/net/HttpCookie;->assignors:Ljava/util/Map;
+Ljava/net/HttpCookie;->comment:Ljava/lang/String;
+Ljava/net/HttpCookie;->commentURL:Ljava/lang/String;
+Ljava/net/HttpCookie;->domain:Ljava/lang/String;
+Ljava/net/HttpCookie;->header:Ljava/lang/String;
 Ljava/net/HttpCookie;->httpOnly:Z
+Ljava/net/HttpCookie;->maxAge:J
+Ljava/net/HttpCookie;->name:Ljava/lang/String;
+Ljava/net/HttpCookie;->path:Ljava/lang/String;
+Ljava/net/HttpCookie;->portlist:Ljava/lang/String;
+Ljava/net/HttpCookie;->secure:Z
+Ljava/net/HttpCookie;->toDiscard:Z
+Ljava/net/HttpCookie;->tspecials:Ljava/lang/String;
+Ljava/net/HttpCookie;->value:Ljava/lang/String;
+Ljava/net/HttpCookie;->version:I
 Ljava/net/HttpCookie;->whenCreated:J
 Ljava/net/Inet4Address;-><init>()V
 Ljava/net/Inet6Address;->holder6:Ljava/net/Inet6Address$Inet6AddressHolder;
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index db9d923..97c9fa5 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -377,6 +377,11 @@
     public abstract boolean isRecentsComponentHomeActivity(int userId);
 
     /**
+     * Cancels any currently running recents animation.
+     */
+    public abstract void cancelRecentsAnimation(boolean restoreHomeStackPosition);
+
+    /**
      * Whether an UID is active or idle.
      */
     public abstract boolean isUidActive(int uid);
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 1084b42..0e44833 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -2831,7 +2831,7 @@
         synchronized (mLock) {
             if (mArtManager == null) {
                 try {
-                    mArtManager = new ArtManager(mPM.getArtManager());
+                    mArtManager = new ArtManager(mContext, mPM.getArtManager());
                 } catch (RemoteException e) {
                     throw e.rethrowFromSystemServer();
                 }
diff --git a/core/java/android/bluetooth/BluetoothCodecStatus.java b/core/java/android/bluetooth/BluetoothCodecStatus.java
index 7ae4cb7..3a05e70 100644
--- a/core/java/android/bluetooth/BluetoothCodecStatus.java
+++ b/core/java/android/bluetooth/BluetoothCodecStatus.java
@@ -57,13 +57,35 @@
         if (o instanceof BluetoothCodecStatus) {
             BluetoothCodecStatus other = (BluetoothCodecStatus) o;
             return (Objects.equals(other.mCodecConfig, mCodecConfig)
-                    && Objects.equals(other.mCodecsLocalCapabilities, mCodecsLocalCapabilities)
-                    && Objects.equals(other.mCodecsSelectableCapabilities,
+                    && sameCapabilities(other.mCodecsLocalCapabilities, mCodecsLocalCapabilities)
+                    && sameCapabilities(other.mCodecsSelectableCapabilities,
                     mCodecsSelectableCapabilities));
         }
         return false;
     }
 
+    /**
+     * Checks whether two arrays of capabilities contain same capabilities.
+     * The order of the capabilities in each array is ignored.
+     *
+     * @param c1 the first array of capabilities to compare
+     * @param c2 the second array of capabilities to compare
+     * @return true if both arrays contain same capabilities
+     */
+    private static boolean sameCapabilities(BluetoothCodecConfig[] c1,
+                                            BluetoothCodecConfig[] c2) {
+        if (c1 == null) {
+            return (c2 == null);
+        }
+        if (c2 == null) {
+            return false;
+        }
+        if (c1.length != c2.length) {
+            return false;
+        }
+        return Arrays.asList(c1).containsAll(Arrays.asList(c2));
+    }
+
     @Override
     public int hashCode() {
         return Objects.hash(mCodecConfig, mCodecsLocalCapabilities,
diff --git a/core/java/android/content/pm/dex/ArtManager.java b/core/java/android/content/pm/dex/ArtManager.java
index 4129398..b0970f4 100644
--- a/core/java/android/content/pm/dex/ArtManager.java
+++ b/core/java/android/content/pm/dex/ArtManager.java
@@ -16,12 +16,16 @@
 
 package android.content.pm.dex;
 
+import static android.Manifest.permission.PACKAGE_USAGE_STATS;
+import static android.Manifest.permission.READ_RUNTIME_PROFILES;
+
 import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
+import android.content.Context;
 import android.os.Environment;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
@@ -62,13 +66,14 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface ProfileType {}
 
-
-    private IArtManager mArtManager;
+    private final Context mContext;
+    private final IArtManager mArtManager;
 
     /**
      * @hide
      */
-    public ArtManager(@NonNull IArtManager manager) {
+    public ArtManager(@NonNull Context context, @NonNull IArtManager manager) {
+        mContext = context;
         mArtManager = manager;
     }
 
@@ -99,7 +104,7 @@
      * @param callback the callback which should be used for the result
      * @param executor the executor which should be used to post the result
      */
-    @RequiresPermission(android.Manifest.permission.READ_RUNTIME_PROFILES)
+    @RequiresPermission(allOf = { READ_RUNTIME_PROFILES, PACKAGE_USAGE_STATS })
     public void snapshotRuntimeProfile(@ProfileType int profileType, @Nullable String packageName,
             @Nullable String codePath, @NonNull @CallbackExecutor Executor executor,
             @NonNull SnapshotRuntimeProfileCallback callback) {
@@ -108,9 +113,10 @@
         SnapshotRuntimeProfileCallbackDelegate delegate =
                 new SnapshotRuntimeProfileCallbackDelegate(callback, executor);
         try {
-            mArtManager.snapshotRuntimeProfile(profileType, packageName, codePath, delegate);
+            mArtManager.snapshotRuntimeProfile(profileType, packageName, codePath, delegate,
+                    mContext.getOpPackageName());
         } catch (RemoteException e) {
-            e.rethrowAsRuntimeException();
+            throw e.rethrowAsRuntimeException();
         }
     }
 
@@ -122,14 +128,13 @@
      * @param profileType can be either {@link ArtManager#PROFILE_APPS}
      *                    or {@link ArtManager#PROFILE_BOOT_IMAGE}
      */
-    @RequiresPermission(android.Manifest.permission.READ_RUNTIME_PROFILES)
+    @RequiresPermission(allOf = { READ_RUNTIME_PROFILES, PACKAGE_USAGE_STATS })
     public boolean isRuntimeProfilingEnabled(@ProfileType int profileType) {
         try {
-            return mArtManager.isRuntimeProfilingEnabled(profileType);
+            return mArtManager.isRuntimeProfilingEnabled(profileType, mContext.getOpPackageName());
         } catch (RemoteException e) {
-            e.rethrowAsRuntimeException();
+            throw e.rethrowAsRuntimeException();
         }
-        return false;
     }
 
     /**
diff --git a/core/java/android/content/pm/dex/IArtManager.aidl b/core/java/android/content/pm/dex/IArtManager.aidl
index 6abfdba..7f0de7e 100644
--- a/core/java/android/content/pm/dex/IArtManager.aidl
+++ b/core/java/android/content/pm/dex/IArtManager.aidl
@@ -44,8 +44,8 @@
      * {@link ArtManager#isRuntimeProfilingEnabled(int)} does not return true for the given
      * {@code profileType}.
      */
-    oneway void snapshotRuntimeProfile(int profileType, in String packageName,
-        in String codePath, in ISnapshotRuntimeProfileCallback callback);
+    void snapshotRuntimeProfile(int profileType, in String packageName,
+        in String codePath, in ISnapshotRuntimeProfileCallback callback, String callingPackage);
 
      /**
        * Returns true if runtime profiles are enabled for the given type, false otherwise.
@@ -54,5 +54,5 @@
        *
        * @param profileType
        */
-    boolean isRuntimeProfilingEnabled(int profileType);
+    boolean isRuntimeProfilingEnabled(int profileType, String callingPackage);
 }
diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java
index 435bcb0..90d407c 100644
--- a/core/java/android/hardware/radio/ProgramSelector.java
+++ b/core/java/android/hardware/radio/ProgramSelector.java
@@ -498,7 +498,7 @@
     @Override
     public int hashCode() {
         // secondaryIds and vendorIds are ignored for equality/hashing
-        return Objects.hash(mProgramType, mPrimaryId);
+        return mPrimaryId.hashCode();
     }
 
     @Override
@@ -507,7 +507,8 @@
         if (!(obj instanceof ProgramSelector)) return false;
         ProgramSelector other = (ProgramSelector) obj;
         // secondaryIds and vendorIds are ignored for equality/hashing
-        return other.getProgramType() == mProgramType && mPrimaryId.equals(other.getPrimaryId());
+        // programType can be inferred from primaryId, thus not checked
+        return mPrimaryId.equals(other.getPrimaryId());
     }
 
     private ProgramSelector(Parcel in) {
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index f525b1f..300a78b 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -50,6 +50,7 @@
     private String mIfaceName;
     private ArrayList<LinkAddress> mLinkAddresses = new ArrayList<LinkAddress>();
     private ArrayList<InetAddress> mDnses = new ArrayList<InetAddress>();
+    private ArrayList<InetAddress> mValidatedPrivateDnses = new ArrayList<InetAddress>();
     private boolean mUsePrivateDns;
     private String mPrivateDnsServerName;
     private String mDomains;
@@ -167,6 +168,9 @@
             mIfaceName = source.getInterfaceName();
             for (LinkAddress l : source.getLinkAddresses()) mLinkAddresses.add(l);
             for (InetAddress i : source.getDnsServers()) mDnses.add(i);
+            for (InetAddress i : source.getValidatedPrivateDnsServers()) {
+                mValidatedPrivateDnses.add(i);
+            }
             mUsePrivateDns = source.mUsePrivateDns;
             mPrivateDnsServerName = source.mPrivateDnsServerName;
             mDomains = source.getDomains();
@@ -374,7 +378,7 @@
      * Replaces the DNS servers in this {@code LinkProperties} with
      * the given {@link Collection} of {@link InetAddress} objects.
      *
-     * @param addresses The {@link Collection} of DNS servers to set in this object.
+     * @param dnsServers The {@link Collection} of DNS servers to set in this object.
      * @hide
      */
     public void setDnsServers(Collection<InetAddress> dnsServers) {
@@ -448,6 +452,65 @@
     }
 
     /**
+     * Adds the given {@link InetAddress} to the list of validated private DNS servers,
+     * if not present. This is distinct from the server name in that these are actually
+     * resolved addresses.
+     *
+     * @param dnsServer The {@link InetAddress} to add to the list of validated private DNS servers.
+     * @return true if the DNS server was added, false if it was already present.
+     * @hide
+     */
+    public boolean addValidatedPrivateDnsServer(InetAddress dnsServer) {
+        if (dnsServer != null && !mValidatedPrivateDnses.contains(dnsServer)) {
+            mValidatedPrivateDnses.add(dnsServer);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Removes the given {@link InetAddress} from the list of validated private DNS servers.
+     *
+     * @param dnsServer The {@link InetAddress} to remove from the list of validated private DNS
+     *        servers.
+     * @return true if the DNS server was removed, false if it did not exist.
+     * @hide
+     */
+    public boolean removeValidatedPrivateDnsServer(InetAddress dnsServer) {
+        if (dnsServer != null) {
+            return mValidatedPrivateDnses.remove(dnsServer);
+        }
+        return false;
+    }
+
+    /**
+     * Replaces the validated private DNS servers in this {@code LinkProperties} with
+     * the given {@link Collection} of {@link InetAddress} objects.
+     *
+     * @param dnsServers The {@link Collection} of validated private DNS servers to set in this
+     *        object.
+     * @hide
+     */
+    public void setValidatedPrivateDnsServers(Collection<InetAddress> dnsServers) {
+        mValidatedPrivateDnses.clear();
+        for (InetAddress dnsServer: dnsServers) {
+            addValidatedPrivateDnsServer(dnsServer);
+        }
+    }
+
+    /**
+     * Returns all the {@link InetAddress} for validated private DNS servers on this link.
+     * These are resolved from the private DNS server name.
+     *
+     * @return An umodifiable {@link List} of {@link InetAddress} for validated private
+     *         DNS servers on this link.
+     * @hide
+     */
+    public List<InetAddress> getValidatedPrivateDnsServers() {
+        return Collections.unmodifiableList(mValidatedPrivateDnses);
+    }
+
+    /**
      * Sets the DNS domain search path used on this link.
      *
      * @param domains A {@link String} listing in priority order the comma separated
@@ -715,6 +778,15 @@
             privateDnsServerName = "PrivateDnsServerName: " + mPrivateDnsServerName + " ";
         }
 
+        String validatedPrivateDns = "";
+        if (!mValidatedPrivateDnses.isEmpty()) {
+            validatedPrivateDns = "ValidatedPrivateDnsAddresses: [";
+            for (InetAddress addr : mValidatedPrivateDnses) {
+                validatedPrivateDns += addr.getHostAddress() + ",";
+            }
+            validatedPrivateDns += "] ";
+        }
+
         String domainName = "Domains: " + mDomains;
 
         String mtu = " MTU: " + mMtu;
@@ -959,7 +1031,7 @@
             if (mDomains.equals(targetDomains) == false) return false;
         }
         return (mDnses.size() == targetDnses.size()) ?
-                    mDnses.containsAll(targetDnses) : false;
+                mDnses.containsAll(targetDnses) : false;
     }
 
     /**
@@ -977,6 +1049,20 @@
     }
 
     /**
+     * Compares this {@code LinkProperties} validated private DNS addresses against
+     * the target
+     *
+     * @param target LinkProperties to compare.
+     * @return {@code true} if both are identical, {@code false} otherwise.
+     * @hide
+     */
+    public boolean isIdenticalValidatedPrivateDnses(LinkProperties target) {
+        Collection<InetAddress> targetDnses = target.getValidatedPrivateDnsServers();
+        return (mValidatedPrivateDnses.size() == targetDnses.size())
+                ? mValidatedPrivateDnses.containsAll(targetDnses) : false;
+    }
+
+    /**
      * Compares this {@code LinkProperties} Routes against the target
      *
      * @param target LinkProperties to compare.
@@ -986,7 +1072,7 @@
     public boolean isIdenticalRoutes(LinkProperties target) {
         Collection<RouteInfo> targetRoutes = target.getRoutes();
         return (mRoutes.size() == targetRoutes.size()) ?
-                    mRoutes.containsAll(targetRoutes) : false;
+                mRoutes.containsAll(targetRoutes) : false;
     }
 
     /**
@@ -998,7 +1084,7 @@
      */
     public boolean isIdenticalHttpProxy(LinkProperties target) {
         return getHttpProxy() == null ? target.getHttpProxy() == null :
-                    getHttpProxy().equals(target.getHttpProxy());
+                getHttpProxy().equals(target.getHttpProxy());
     }
 
     /**
@@ -1074,6 +1160,7 @@
                 && isIdenticalAddresses(target)
                 && isIdenticalDnses(target)
                 && isIdenticalPrivateDns(target)
+                && isIdenticalValidatedPrivateDnses(target)
                 && isIdenticalRoutes(target)
                 && isIdenticalHttpProxy(target)
                 && isIdenticalStackedLinks(target)
@@ -1121,6 +1208,19 @@
     }
 
     /**
+     * Compares the validated private DNS addresses in this LinkProperties with another
+     * LinkProperties.
+     *
+     * @param target a LinkProperties with the new list of validated private dns addresses
+     * @return the differences between the DNS addresses.
+     * @hide
+     */
+    public CompareResult<InetAddress> compareValidatedPrivateDnses(LinkProperties target) {
+        return new CompareResult<>(mValidatedPrivateDnses,
+                target != null ? target.getValidatedPrivateDnsServers() : null);
+    }
+
+    /**
      * Compares all routes in this LinkProperties with another LinkProperties,
      * examining both the the base link and all stacked links.
      *
@@ -1168,6 +1268,7 @@
         return ((null == mIfaceName) ? 0 : mIfaceName.hashCode()
                 + mLinkAddresses.size() * 31
                 + mDnses.size() * 37
+                + mValidatedPrivateDnses.size() * 61
                 + ((null == mDomains) ? 0 : mDomains.hashCode())
                 + mRoutes.size() * 41
                 + ((null == mHttpProxy) ? 0 : mHttpProxy.hashCode())
@@ -1184,12 +1285,16 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeString(getInterfaceName());
         dest.writeInt(mLinkAddresses.size());
-        for(LinkAddress linkAddress : mLinkAddresses) {
+        for (LinkAddress linkAddress : mLinkAddresses) {
             dest.writeParcelable(linkAddress, flags);
         }
 
         dest.writeInt(mDnses.size());
-        for(InetAddress d : mDnses) {
+        for (InetAddress d : mDnses) {
+            dest.writeByteArray(d.getAddress());
+        }
+        dest.writeInt(mValidatedPrivateDnses.size());
+        for (InetAddress d : mValidatedPrivateDnses) {
             dest.writeByteArray(d.getAddress());
         }
         dest.writeBoolean(mUsePrivateDns);
@@ -1198,7 +1303,7 @@
         dest.writeInt(mMtu);
         dest.writeString(mTcpBufferSizes);
         dest.writeInt(mRoutes.size());
-        for(RouteInfo route : mRoutes) {
+        for (RouteInfo route : mRoutes) {
             dest.writeParcelable(route, flags);
         }
 
@@ -1225,26 +1330,33 @@
                     netProp.setInterfaceName(iface);
                 }
                 int addressCount = in.readInt();
-                for (int i=0; i<addressCount; i++) {
-                    netProp.addLinkAddress((LinkAddress)in.readParcelable(null));
+                for (int i = 0; i < addressCount; i++) {
+                    netProp.addLinkAddress((LinkAddress) in.readParcelable(null));
                 }
                 addressCount = in.readInt();
-                for (int i=0; i<addressCount; i++) {
+                for (int i = 0; i < addressCount; i++) {
                     try {
                         netProp.addDnsServer(InetAddress.getByAddress(in.createByteArray()));
                     } catch (UnknownHostException e) { }
                 }
+                addressCount = in.readInt();
+                for (int i = 0; i < addressCount; i++) {
+                    try {
+                        netProp.addValidatedPrivateDnsServer(
+                                InetAddress.getByAddress(in.createByteArray()));
+                    } catch (UnknownHostException e) { }
+                }
                 netProp.setUsePrivateDns(in.readBoolean());
                 netProp.setPrivateDnsServerName(in.readString());
                 netProp.setDomains(in.readString());
                 netProp.setMtu(in.readInt());
                 netProp.setTcpBufferSizes(in.readString());
                 addressCount = in.readInt();
-                for (int i=0; i<addressCount; i++) {
-                    netProp.addRoute((RouteInfo)in.readParcelable(null));
+                for (int i = 0; i < addressCount; i++) {
+                    netProp.addRoute((RouteInfo) in.readParcelable(null));
                 }
                 if (in.readByte() == 1) {
-                    netProp.setHttpProxy((ProxyInfo)in.readParcelable(null));
+                    netProp.setHttpProxy((ProxyInfo) in.readParcelable(null));
                 }
                 ArrayList<LinkProperties> stackedLinks = new ArrayList<LinkProperties>();
                 in.readList(stackedLinks, LinkProperties.class.getClassLoader());
@@ -1259,16 +1371,16 @@
             }
         };
 
-        /**
-         * Check the valid MTU range based on IPv4 or IPv6.
-         * @hide
-         */
-        public static boolean isValidMtu(int mtu, boolean ipv6) {
-            if (ipv6) {
-                if ((mtu >= MIN_MTU_V6 && mtu <= MAX_MTU)) return true;
-            } else {
-                if ((mtu >= MIN_MTU && mtu <= MAX_MTU)) return true;
-            }
-            return false;
+    /**
+     * Check the valid MTU range based on IPv4 or IPv6.
+     * @hide
+     */
+    public static boolean isValidMtu(int mtu, boolean ipv6) {
+        if (ipv6) {
+            if (mtu >= MIN_MTU_V6 && mtu <= MAX_MTU) return true;
+        } else {
+            if (mtu >= MIN_MTU && mtu <= MAX_MTU) return true;
         }
+        return false;
+    }
 }
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 59380fd..3eaecf9 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -811,6 +811,10 @@
 
             /**
              * Detect reflective usage of APIs that are not part of the public Android SDK.
+             *
+             * <p>Note that any non-SDK APIs that this processes accesses before this detection is
+             * enabled may not be detected. To ensure that all such API accesses are detected,
+             * you should apply this policy as early as possible after process creation.
              */
             public Builder detectNonSdkApiUsage() {
                 return enable(DETECT_VM_NON_SDK_API_USAGE);
@@ -1885,8 +1889,10 @@
 
             if ((sVmPolicy.mask & DETECT_VM_NON_SDK_API_USAGE) != 0) {
                 VMRuntime.setNonSdkApiUsageConsumer(sNonSdkApiUsageConsumer);
+                VMRuntime.setDedupeHiddenApiWarnings(false);
             } else {
                 VMRuntime.setNonSdkApiUsageConsumer(null);
+                VMRuntime.setDedupeHiddenApiWarnings(true);
             }
         }
     }
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 60537a4..6c18b45 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -496,9 +496,9 @@
  *
  * <p>Apps that use standard Android widgets support autofill out-of-the-box and need to do
  * very little to improve their user experience (annotating autofillable views and providing
- * autofill hints). However, some apps do their own rendering and the rendered content may
- * contain semantic structure that needs to be surfaced to the autofill framework. The platform
- * exposes APIs to achieve this, however it could take some time until these apps implement
+ * autofill hints). However, some apps (typically browsers) do their own rendering and the rendered
+ * content may contain semantic structure that needs to be surfaced to the autofill framework. The
+ * platform exposes APIs to achieve this, however it could take some time until these apps implement
  * autofill support.
  *
  * <p>To enable autofill for such apps the platform provides a compatibility mode in which the
@@ -521,15 +521,33 @@
  *     &lt;meta-data android:name="android.autofill" android:resource="@xml/autofillservice" /&gt;
  * &lt;/service&gt;</pre>
  *
- * <P>In the XML file you can specify one or more packages for which to enable compatibility
+ * <p>In the XML file you can specify one or more packages for which to enable compatibility
  * mode. Below is a sample meta-data declaration:
  *
  * <pre> &lt;autofill-service xmlns:android="http://schemas.android.com/apk/res/android"&gt;
  *     &lt;compatibility-package android:name="foo.bar.baz" android:maxLongVersionCode="1000000000"/&gt;
  * &lt;/autofill-service&gt;</pre>
  *
- * <p>When using compatibility mode, the {@link SaveInfo.Builder#setFlags(int) SaveInfo flags}
- * automatically include {@link SaveInfo#FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE}.
+ * <p>Notice that compatibility mode has limitations such as:
+ * <ul>
+ * <li>No manual autofill requests. Hence, the {@link FillRequest}
+ * {@link FillRequest#getFlags() flags} never have the {@link FillRequest#FLAG_MANUAL_REQUEST} flag.
+ * <li>The value of password fields are most likely masked&mdash;for example, {@code ****} instead
+ * of {@code 1234}. Hence, you must be careful when using these values to avoid updating the user
+ * data with invalid input. For example, when you parse the {@link FillRequest} and detect a
+ * password field, you could check if its
+ * {@link android.app.assist.AssistStructure.ViewNode#getInputType()
+ * input type} has password flags and if so, don't add it to the {@link SaveInfo} object.
+ * <li>The autofill context is not always {@link AutofillManager#commit() committed} when an HTML
+ * form is submitted. Hence, you must use other mechanisms to trigger save, such as setting the
+ * {@link SaveInfo#FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} flag on {@link SaveInfo.Builder#setFlags(int)}
+ * or using {@link SaveInfo.Builder#setTriggerId(AutofillId)}.
+ * <li>Browsers often provide their own autofill management system. When both the browser and
+ * the platform render an autofill dialog at the same time, the result can be confusing to the user.
+ * Such browsers typically offer an option for users to disable autofill, so your service should
+ * also allow users to disable compatiblity mode for specific apps. That way, it is up to the user
+ * to decide which autofill mechanism&mdash;the browser's or the platform's&mdash;should be used.
+ * </ul>
  */
 public abstract class AutofillService extends Service {
     private static final String TAG = "AutofillService";
diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecConfigTest.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecConfigTest.java
new file mode 100644
index 0000000..59b4665
--- /dev/null
+++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecConfigTest.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright 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 android.bluetooth;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit test cases for {@link BluetoothCodecConfig}.
+ * <p>
+ * To run this test, use:
+ * runtest --path core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecConfigTest.java
+ */
+public class BluetoothCodecConfigTest extends TestCase {
+    private static final int[] kCodecTypeArray = new int[] {
+        BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+        BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+        BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
+        BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
+        BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+        BluetoothCodecConfig.SOURCE_CODEC_TYPE_MAX,
+        BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID,
+    };
+    private static final int[] kCodecPriorityArray = new int[] {
+        BluetoothCodecConfig.CODEC_PRIORITY_DISABLED,
+        BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+        BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST,
+    };
+    private static final int[] kSampleRateArray = new int[] {
+        BluetoothCodecConfig.SAMPLE_RATE_NONE,
+        BluetoothCodecConfig.SAMPLE_RATE_44100,
+        BluetoothCodecConfig.SAMPLE_RATE_48000,
+        BluetoothCodecConfig.SAMPLE_RATE_88200,
+        BluetoothCodecConfig.SAMPLE_RATE_96000,
+        BluetoothCodecConfig.SAMPLE_RATE_176400,
+        BluetoothCodecConfig.SAMPLE_RATE_192000,
+    };
+    private static final int[] kBitsPerSampleArray = new int[] {
+        BluetoothCodecConfig.BITS_PER_SAMPLE_NONE,
+        BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+        BluetoothCodecConfig.BITS_PER_SAMPLE_24,
+        BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+    };
+    private static final int[] kChannelModeArray = new int[] {
+        BluetoothCodecConfig.CHANNEL_MODE_NONE,
+        BluetoothCodecConfig.CHANNEL_MODE_MONO,
+        BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+    };
+    private static final long[] kCodecSpecific1Array = new long[] { 1000, 1001, 1002, 1003, };
+    private static final long[] kCodecSpecific2Array = new long[] { 2000, 2001, 2002, 2003, };
+    private static final long[] kCodecSpecific3Array = new long[] { 3000, 3001, 3002, 3003, };
+    private static final long[] kCodecSpecific4Array = new long[] { 4000, 4001, 4002, 4003, };
+
+    private static final int kTotalConfigs = kCodecTypeArray.length * kCodecPriorityArray.length *
+        kSampleRateArray.length * kBitsPerSampleArray.length * kChannelModeArray.length *
+        kCodecSpecific1Array.length * kCodecSpecific2Array.length * kCodecSpecific3Array.length *
+        kCodecSpecific4Array.length;
+
+    private int selectCodecType(int configId) {
+        int left = kCodecTypeArray.length;
+        int right = kTotalConfigs / left;
+        int index = configId / right;
+        index = index % kCodecTypeArray.length;
+        return kCodecTypeArray[index];
+    }
+
+    private int selectCodecPriority(int configId) {
+        int left = kCodecTypeArray.length * kCodecPriorityArray.length;
+        int right = kTotalConfigs / left;
+        int index = configId / right;
+        index = index % kCodecPriorityArray.length;
+        return kCodecPriorityArray[index];
+    }
+
+    private int selectSampleRate(int configId) {
+        int left = kCodecTypeArray.length * kCodecPriorityArray.length * kSampleRateArray.length;
+        int right = kTotalConfigs / left;
+        int index = configId / right;
+        index = index % kSampleRateArray.length;
+        return kSampleRateArray[index];
+    }
+
+    private int selectBitsPerSample(int configId) {
+        int left = kCodecTypeArray.length * kCodecPriorityArray.length * kSampleRateArray.length *
+            kBitsPerSampleArray.length;
+        int right = kTotalConfigs / left;
+        int index = configId / right;
+        index = index % kBitsPerSampleArray.length;
+        return kBitsPerSampleArray[index];
+    }
+
+    private int selectChannelMode(int configId) {
+        int left = kCodecTypeArray.length * kCodecPriorityArray.length * kSampleRateArray.length *
+            kBitsPerSampleArray.length * kChannelModeArray.length;
+        int right = kTotalConfigs / left;
+        int index = configId / right;
+        index = index % kChannelModeArray.length;
+        return kChannelModeArray[index];
+    }
+
+    private long selectCodecSpecific1(int configId) {
+        int left = kCodecTypeArray.length * kCodecPriorityArray.length * kSampleRateArray.length *
+            kBitsPerSampleArray.length * kChannelModeArray.length * kCodecSpecific1Array.length;
+        int right = kTotalConfigs / left;
+        int index = configId / right;
+        index = index % kCodecSpecific1Array.length;
+        return kCodecSpecific1Array[index];
+    }
+
+    private long selectCodecSpecific2(int configId) {
+        int left = kCodecTypeArray.length * kCodecPriorityArray.length * kSampleRateArray.length *
+            kBitsPerSampleArray.length * kChannelModeArray.length * kCodecSpecific1Array.length *
+            kCodecSpecific2Array.length;
+        int right = kTotalConfigs / left;
+        int index = configId / right;
+        index = index % kCodecSpecific2Array.length;
+        return kCodecSpecific2Array[index];
+    }
+
+    private long selectCodecSpecific3(int configId) {
+        int left = kCodecTypeArray.length * kCodecPriorityArray.length * kSampleRateArray.length *
+            kBitsPerSampleArray.length * kChannelModeArray.length * kCodecSpecific1Array.length *
+            kCodecSpecific2Array.length * kCodecSpecific3Array.length;
+        int right = kTotalConfigs / left;
+        int index = configId / right;
+        index = index % kCodecSpecific3Array.length;
+        return kCodecSpecific3Array[index];
+    }
+
+    private long selectCodecSpecific4(int configId) {
+        int left = kCodecTypeArray.length * kCodecPriorityArray.length * kSampleRateArray.length *
+            kBitsPerSampleArray.length * kChannelModeArray.length * kCodecSpecific1Array.length *
+            kCodecSpecific2Array.length * kCodecSpecific3Array.length *
+            kCodecSpecific4Array.length;
+        int right = kTotalConfigs / left;
+        int index = configId / right;
+        index = index % kCodecSpecific4Array.length;
+        return kCodecSpecific4Array[index];
+    }
+
+    @SmallTest
+    public void testBluetoothCodecConfig_valid_get_methods() {
+
+        for (int config_id = 0; config_id < kTotalConfigs; config_id++) {
+            int codec_type = selectCodecType(config_id);
+            int codec_priority = selectCodecPriority(config_id);
+            int sample_rate = selectSampleRate(config_id);
+            int bits_per_sample = selectBitsPerSample(config_id);
+            int channel_mode = selectChannelMode(config_id);
+            long codec_specific1 = selectCodecSpecific1(config_id);
+            long codec_specific2 = selectCodecSpecific2(config_id);
+            long codec_specific3 = selectCodecSpecific3(config_id);
+            long codec_specific4 = selectCodecSpecific4(config_id);
+
+            BluetoothCodecConfig bcc = new BluetoothCodecConfig(codec_type, codec_priority,
+                                                                sample_rate, bits_per_sample,
+                                                                channel_mode, codec_specific1,
+                                                                codec_specific2, codec_specific3,
+                                                                codec_specific4);
+            if (sample_rate == BluetoothCodecConfig.SAMPLE_RATE_NONE) {
+                assertFalse(bcc.isValid());
+            } else if (bits_per_sample == BluetoothCodecConfig.BITS_PER_SAMPLE_NONE) {
+                assertFalse(bcc.isValid());
+            } else if (channel_mode == BluetoothCodecConfig.CHANNEL_MODE_NONE) {
+                assertFalse(bcc.isValid());
+            } else {
+                assertTrue(bcc.isValid());
+            }
+
+            if (codec_type == BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC) {
+                assertTrue(bcc.isMandatoryCodec());
+            } else {
+                assertFalse(bcc.isMandatoryCodec());
+            }
+
+            if (codec_type == BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC) {
+                assertEquals("SBC", bcc.getCodecName());
+            }
+            if (codec_type == BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC) {
+                assertEquals("AAC", bcc.getCodecName());
+            }
+            if (codec_type == BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX) {
+                assertEquals("aptX", bcc.getCodecName());
+            }
+            if (codec_type == BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD) {
+                assertEquals("aptX HD", bcc.getCodecName());
+            }
+            if (codec_type == BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC) {
+                assertEquals("LDAC", bcc.getCodecName());
+            }
+            if (codec_type == BluetoothCodecConfig.SOURCE_CODEC_TYPE_MAX) {
+                assertEquals("UNKNOWN CODEC(" + BluetoothCodecConfig.SOURCE_CODEC_TYPE_MAX + ")",
+                             bcc.getCodecName());
+            }
+            if (codec_type == BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID) {
+                assertEquals("INVALID CODEC", bcc.getCodecName());
+            }
+
+            assertEquals(codec_type, bcc.getCodecType());
+            assertEquals(codec_priority, bcc.getCodecPriority());
+            assertEquals(sample_rate, bcc.getSampleRate());
+            assertEquals(bits_per_sample, bcc.getBitsPerSample());
+            assertEquals(channel_mode, bcc.getChannelMode());
+            assertEquals(codec_specific1, bcc.getCodecSpecific1());
+            assertEquals(codec_specific2, bcc.getCodecSpecific2());
+            assertEquals(codec_specific3, bcc.getCodecSpecific3());
+            assertEquals(codec_specific4, bcc.getCodecSpecific4());
+        }
+    }
+
+    @SmallTest
+    public void testBluetoothCodecConfig_equals() {
+        BluetoothCodecConfig bcc1 =
+            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                                     BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                     BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                     BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                     BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                     1000, 2000, 3000, 4000);
+
+        BluetoothCodecConfig bcc2_same =
+            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                                     BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                     BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                     BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                     BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                     1000, 2000, 3000, 4000);
+        assertTrue(bcc1.equals(bcc2_same));
+
+        BluetoothCodecConfig bcc3_codec_type =
+            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                                     BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                     BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                     BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                     BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                     1000, 2000, 3000, 4000);
+        assertFalse(bcc1.equals(bcc3_codec_type));
+
+        BluetoothCodecConfig bcc4_codec_priority =
+            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                                     BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST,
+                                     BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                     BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                     BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                     1000, 2000, 3000, 4000);
+        assertFalse(bcc1.equals(bcc4_codec_priority));
+
+        BluetoothCodecConfig bcc5_sample_rate =
+            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                                     BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                     BluetoothCodecConfig.SAMPLE_RATE_48000,
+                                     BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                     BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                     1000, 2000, 3000, 4000);
+        assertFalse(bcc1.equals(bcc5_sample_rate));
+
+        BluetoothCodecConfig bcc6_bits_per_sample =
+            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                                     BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                     BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                     BluetoothCodecConfig.BITS_PER_SAMPLE_24,
+                                     BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                     1000, 2000, 3000, 4000);
+        assertFalse(bcc1.equals(bcc6_bits_per_sample));
+
+        BluetoothCodecConfig bcc7_channel_mode =
+            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                                     BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                     BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                     BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                     BluetoothCodecConfig.CHANNEL_MODE_MONO,
+                                     1000, 2000, 3000, 4000);
+        assertFalse(bcc1.equals(bcc7_channel_mode));
+
+        BluetoothCodecConfig bcc8_codec_specific1 =
+            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                                     BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                     BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                     BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                     BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                     1001, 2000, 3000, 4000);
+        assertFalse(bcc1.equals(bcc8_codec_specific1));
+
+        BluetoothCodecConfig bcc9_codec_specific2 =
+            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                                     BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                     BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                     BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                     BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                     1000, 2002, 3000, 4000);
+        assertFalse(bcc1.equals(bcc9_codec_specific2));
+
+        BluetoothCodecConfig bcc10_codec_specific3 =
+            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                                     BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                     BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                     BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                     BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                     1000, 2000, 3003, 4000);
+        assertFalse(bcc1.equals(bcc10_codec_specific3));
+
+        BluetoothCodecConfig bcc11_codec_specific4 =
+            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                                     BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                     BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                     BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                     BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                     1000, 2000, 3000, 4004);
+        assertFalse(bcc1.equals(bcc11_codec_specific4));
+    }
+}
diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecStatusTest.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecStatusTest.java
new file mode 100644
index 0000000..83bf2ed
--- /dev/null
+++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecStatusTest.java
@@ -0,0 +1,468 @@
+/*
+ * Copyright 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 android.bluetooth;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit test cases for {@link BluetoothCodecStatus}.
+ * <p>
+ * To run this test, use:
+ * runtest --path core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecStatusTest.java
+ */
+public class BluetoothCodecStatusTest extends TestCase {
+
+    // Codec configs: A and B are same; C is different
+    private static final BluetoothCodecConfig config_A =
+        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig config_B =
+        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig config_C =
+        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                 1000, 2000, 3000, 4000);
+
+    // Local capabilities: A and B are same; C is different
+    private static final BluetoothCodecConfig local_capability1_A =
+        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100 |
+                                 BluetoothCodecConfig.SAMPLE_RATE_48000,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO |
+                                 BluetoothCodecConfig.CHANNEL_MODE_MONO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig local_capability1_B =
+        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100 |
+                                 BluetoothCodecConfig.SAMPLE_RATE_48000,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO |
+                                 BluetoothCodecConfig.CHANNEL_MODE_MONO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig local_capability1_C =
+        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100 |
+                                 BluetoothCodecConfig.SAMPLE_RATE_48000,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                 1000, 2000, 3000, 4000);
+
+
+    private static final BluetoothCodecConfig local_capability2_A =
+        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100 |
+                                 BluetoothCodecConfig.SAMPLE_RATE_48000,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO |
+                                 BluetoothCodecConfig.CHANNEL_MODE_MONO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig local_capability2_B =
+        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100 |
+                                 BluetoothCodecConfig.SAMPLE_RATE_48000,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO |
+                                 BluetoothCodecConfig.CHANNEL_MODE_MONO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig local_capability2_C =
+        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100 |
+                                 BluetoothCodecConfig.SAMPLE_RATE_48000,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig local_capability3_A =
+        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100 |
+                                 BluetoothCodecConfig.SAMPLE_RATE_48000,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO |
+                                 BluetoothCodecConfig.CHANNEL_MODE_MONO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig local_capability3_B =
+        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100 |
+                                 BluetoothCodecConfig.SAMPLE_RATE_48000,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO |
+                                 BluetoothCodecConfig.CHANNEL_MODE_MONO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig local_capability3_C =
+        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100 |
+                                 BluetoothCodecConfig.SAMPLE_RATE_48000,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig local_capability4_A =
+        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100 |
+                                 BluetoothCodecConfig.SAMPLE_RATE_48000,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_24,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO |
+                                 BluetoothCodecConfig.CHANNEL_MODE_MONO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig local_capability4_B =
+        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100 |
+                                 BluetoothCodecConfig.SAMPLE_RATE_48000,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_24,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO |
+                                 BluetoothCodecConfig.CHANNEL_MODE_MONO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig local_capability4_C =
+        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100 |
+                                 BluetoothCodecConfig.SAMPLE_RATE_48000,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_24,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig local_capability5_A =
+        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100 |
+                                 BluetoothCodecConfig.SAMPLE_RATE_48000 |
+                                 BluetoothCodecConfig.SAMPLE_RATE_88200 |
+                                 BluetoothCodecConfig.SAMPLE_RATE_96000,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16 |
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_24 |
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO |
+                                 BluetoothCodecConfig.CHANNEL_MODE_MONO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig local_capability5_B =
+        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100 |
+                                 BluetoothCodecConfig.SAMPLE_RATE_48000 |
+                                 BluetoothCodecConfig.SAMPLE_RATE_88200 |
+                                 BluetoothCodecConfig.SAMPLE_RATE_96000,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16 |
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_24 |
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO |
+                                 BluetoothCodecConfig.CHANNEL_MODE_MONO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig local_capability5_C =
+        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100 |
+                                 BluetoothCodecConfig.SAMPLE_RATE_48000 |
+                                 BluetoothCodecConfig.SAMPLE_RATE_88200 |
+                                 BluetoothCodecConfig.SAMPLE_RATE_96000,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16 |
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_24 |
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                 1000, 2000, 3000, 4000);
+
+
+    // Selectable capabilities: A and B are same; C is different
+    private static final BluetoothCodecConfig selectable_capability1_A =
+        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO |
+                                 BluetoothCodecConfig.CHANNEL_MODE_MONO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig selectable_capability1_B =
+        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO |
+                                 BluetoothCodecConfig.CHANNEL_MODE_MONO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig selectable_capability1_C =
+        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig selectable_capability2_A =
+        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO |
+                                 BluetoothCodecConfig.CHANNEL_MODE_MONO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig selectable_capability2_B =
+        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO |
+                                 BluetoothCodecConfig.CHANNEL_MODE_MONO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig selectable_capability2_C =
+        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig selectable_capability3_A =
+        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO |
+                                 BluetoothCodecConfig.CHANNEL_MODE_MONO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig selectable_capability3_B =
+        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO |
+                                 BluetoothCodecConfig.CHANNEL_MODE_MONO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig selectable_capability3_C =
+        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig selectable_capability4_A =
+        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_24,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO |
+                                 BluetoothCodecConfig.CHANNEL_MODE_MONO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig selectable_capability4_B =
+        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_24,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO |
+                                 BluetoothCodecConfig.CHANNEL_MODE_MONO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig selectable_capability4_C =
+        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_24,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig selectable_capability5_A =
+        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100 |
+                                 BluetoothCodecConfig.SAMPLE_RATE_48000 |
+                                 BluetoothCodecConfig.SAMPLE_RATE_88200 |
+                                 BluetoothCodecConfig.SAMPLE_RATE_96000,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16 |
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_24 |
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO |
+                                 BluetoothCodecConfig.CHANNEL_MODE_MONO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig selectable_capability5_B =
+        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100 |
+                                 BluetoothCodecConfig.SAMPLE_RATE_48000 |
+                                 BluetoothCodecConfig.SAMPLE_RATE_88200 |
+                                 BluetoothCodecConfig.SAMPLE_RATE_96000,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16 |
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_24 |
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO |
+                                 BluetoothCodecConfig.CHANNEL_MODE_MONO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig selectable_capability5_C =
+        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100 |
+                                 BluetoothCodecConfig.SAMPLE_RATE_48000 |
+                                 BluetoothCodecConfig.SAMPLE_RATE_88200 |
+                                 BluetoothCodecConfig.SAMPLE_RATE_96000,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16 |
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_24 |
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig[] local_capability_A = {
+        local_capability1_A,
+        local_capability2_A,
+        local_capability3_A,
+        local_capability4_A,
+        local_capability5_A,
+    };
+
+    private static final BluetoothCodecConfig[] local_capability_B = {
+        local_capability1_B,
+        local_capability2_B,
+        local_capability3_B,
+        local_capability4_B,
+        local_capability5_B,
+    };
+
+    private static final BluetoothCodecConfig[] local_capability_B_reordered = {
+        local_capability5_B,
+        local_capability4_B,
+        local_capability2_B,
+        local_capability3_B,
+        local_capability1_B,
+    };
+
+    private static final BluetoothCodecConfig[] local_capability_C = {
+        local_capability1_C,
+        local_capability2_C,
+        local_capability3_C,
+        local_capability4_C,
+        local_capability5_C,
+    };
+
+    private static final BluetoothCodecConfig[] selectable_capability_A = {
+        selectable_capability1_A,
+        selectable_capability2_A,
+        selectable_capability3_A,
+        selectable_capability4_A,
+        selectable_capability5_A,
+    };
+
+    private static final BluetoothCodecConfig[] selectable_capability_B = {
+        selectable_capability1_B,
+        selectable_capability2_B,
+        selectable_capability3_B,
+        selectable_capability4_B,
+        selectable_capability5_B,
+    };
+
+    private static final BluetoothCodecConfig[] selectable_capability_B_reordered = {
+        selectable_capability5_B,
+        selectable_capability4_B,
+        selectable_capability2_B,
+        selectable_capability3_B,
+        selectable_capability1_B,
+    };
+
+    private static final BluetoothCodecConfig[] selectable_capability_C = {
+        selectable_capability1_C,
+        selectable_capability2_C,
+        selectable_capability3_C,
+        selectable_capability4_C,
+        selectable_capability5_C,
+    };
+
+    private static final BluetoothCodecStatus bcs_A =
+        new BluetoothCodecStatus(config_A, local_capability_A, selectable_capability_A);
+    private static final BluetoothCodecStatus bcs_B =
+        new BluetoothCodecStatus(config_B, local_capability_B, selectable_capability_B);
+    private static final BluetoothCodecStatus bcs_B_reordered =
+        new BluetoothCodecStatus(config_B, local_capability_B_reordered,
+                                 selectable_capability_B_reordered);
+    private static final BluetoothCodecStatus bcs_C =
+        new BluetoothCodecStatus(config_C, local_capability_C, selectable_capability_C);
+
+    @SmallTest
+    public void testBluetoothCodecStatus_get_methods() {
+
+        assertTrue(Objects.equals(bcs_A.getCodecConfig(), config_A));
+        assertTrue(Objects.equals(bcs_A.getCodecConfig(), config_B));
+        assertFalse(Objects.equals(bcs_A.getCodecConfig(), config_C));
+
+        assertTrue(Arrays.equals(bcs_A.getCodecsLocalCapabilities(), local_capability_A));
+        assertTrue(Arrays.equals(bcs_A.getCodecsLocalCapabilities(), local_capability_B));
+        assertFalse(Arrays.equals(bcs_A.getCodecsLocalCapabilities(), local_capability_C));
+
+        assertTrue(Arrays.equals(bcs_A.getCodecsSelectableCapabilities(),
+                                 selectable_capability_A));
+        assertTrue(Arrays.equals(bcs_A.getCodecsSelectableCapabilities(),
+                                  selectable_capability_B));
+        assertFalse(Arrays.equals(bcs_A.getCodecsSelectableCapabilities(),
+                                  selectable_capability_C));
+    }
+
+    @SmallTest
+    public void testBluetoothCodecStatus_equals() {
+        assertTrue(bcs_A.equals(bcs_B));
+        assertTrue(bcs_B.equals(bcs_A));
+        assertTrue(bcs_A.equals(bcs_B_reordered));
+        assertTrue(bcs_B_reordered.equals(bcs_A));
+        assertFalse(bcs_A.equals(bcs_C));
+        assertFalse(bcs_C.equals(bcs_A));
+    }
+}
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 7888436..c486e68 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -1222,7 +1222,7 @@
             TAG_F_NUMBER, TAG_DIGITAL_ZOOM_RATIO, TAG_EXPOSURE_TIME, TAG_SUBJECT_DISTANCE,
             TAG_GPS_TIMESTAMP));
     // Mappings from tag number to IFD type for pointer tags.
-    private static final HashMap sExifPointerTagMap = new HashMap();
+    private static final HashMap<Integer, Integer> sExifPointerTagMap = new HashMap();
 
     // See JPEG File Interchange Format Version 1.02.
     // The following values are defined for handling JPEG streams. In this implementation, we are
@@ -1299,6 +1299,7 @@
     private final boolean mIsInputStream;
     private int mMimeType;
     private final HashMap[] mAttributes = new HashMap[EXIF_TAGS.length];
+    private Set<Integer> mAttributesOffsets = new HashSet<>(EXIF_TAGS.length);
     private ByteOrder mExifByteOrder = ByteOrder.BIG_ENDIAN;
     private boolean mHasThumbnail;
     // The following values used for indicating a thumbnail position.
@@ -2957,8 +2958,9 @@
         }
         // See TIFF 6.0 Section 2: TIFF Structure, Figure 1.
         short numberOfDirectoryEntry = dataInputStream.readShort();
-        if (dataInputStream.mPosition + 12 * numberOfDirectoryEntry > dataInputStream.mLength) {
-            // Return if the size of entries is too big.
+        if (dataInputStream.mPosition + 12 * numberOfDirectoryEntry > dataInputStream.mLength
+                || numberOfDirectoryEntry <= 0) {
+            // Return if the size of entries is either too big or negative.
             return;
         }
 
@@ -3049,7 +3051,7 @@
             }
 
             // Recursively parse IFD when a IFD pointer tag appears.
-            Object nextIfdType = sExifPointerTagMap.get(tagNumber);
+            Integer nextIfdType = sExifPointerTagMap.get(tagNumber);
             if (DEBUG) {
                 Log.d(TAG, "nextIfdType: " + nextIfdType + " byteCount: " + byteCount);
             }
@@ -3083,9 +3085,20 @@
                 if (DEBUG) {
                     Log.d(TAG, String.format("Offset: %d, tagName: %s", offset, tag.name));
                 }
+
+                // Check if the next IFD offset
+                // 1. Exists within the boundaries of the input stream
+                // 2. Does not point to a previously read IFD.
                 if (offset > 0L && offset < dataInputStream.mLength) {
-                    dataInputStream.seek(offset);
-                    readImageFileDirectory(dataInputStream, (int) nextIfdType);
+                    if (!mAttributesOffsets.contains((int) offset)) {
+                        // Save offset of current IFD to prevent reading an IFD that is already read
+                        mAttributesOffsets.add(dataInputStream.mPosition);
+                        dataInputStream.seek(offset);
+                        readImageFileDirectory(dataInputStream, nextIfdType);
+                    } else {
+                        Log.w(TAG, "Skip jump into the IFD since it has already been read: "
+                                + "IfdType " + nextIfdType + " (at " + offset + ")");
+                    }
                 } else {
                     Log.w(TAG, "Skip jump into the IFD since its offset is invalid: " + offset);
                 }
@@ -3127,16 +3140,27 @@
             if (DEBUG) {
                 Log.d(TAG, String.format("nextIfdOffset: %d", nextIfdOffset));
             }
-            // The next IFD offset needs to be bigger than 8
-            // since the first IFD offset is at least 8.
-            if (nextIfdOffset > 8 && nextIfdOffset < dataInputStream.mLength) {
-                dataInputStream.seek(nextIfdOffset);
-                if (mAttributes[IFD_TYPE_THUMBNAIL].isEmpty()) {
+            // Check if the next IFD offset
+            // 1. Exists within the boundaries of the input stream
+            // 2. Does not point to a previously read IFD.
+            if (nextIfdOffset > 0L && nextIfdOffset < dataInputStream.mLength) {
+                if (!mAttributesOffsets.contains(nextIfdOffset)) {
+                    // Save offset of current IFD to prevent reading an IFD that is already read.
+                    mAttributesOffsets.add(dataInputStream.mPosition);
+                    dataInputStream.seek(nextIfdOffset);
                     // Do not overwrite thumbnail IFD data if it alreay exists.
-                    readImageFileDirectory(dataInputStream, IFD_TYPE_THUMBNAIL);
-                } else if (mAttributes[IFD_TYPE_PREVIEW].isEmpty()) {
-                    readImageFileDirectory(dataInputStream, IFD_TYPE_PREVIEW);
+                    if (mAttributes[IFD_TYPE_THUMBNAIL].isEmpty()) {
+                        readImageFileDirectory(dataInputStream, IFD_TYPE_THUMBNAIL);
+                    } else if (mAttributes[IFD_TYPE_PREVIEW].isEmpty()) {
+                        readImageFileDirectory(dataInputStream, IFD_TYPE_PREVIEW);
+                    }
+                } else {
+                    Log.w(TAG, "Stop reading file since re-reading an IFD may cause an "
+                            + "infinite loop: " + nextIfdOffset);
                 }
+            } else {
+                Log.w(TAG, "Stop reading file since a wrong offset may cause an infinite loop: "
+                        + nextIfdOffset);
             }
         }
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/CustomDialogPreference.java b/packages/SettingsLib/src/com/android/settingslib/CustomDialogPreference.java
index 9554e81..95a8f1d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/CustomDialogPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/CustomDialogPreference.java
@@ -28,6 +28,7 @@
 public class CustomDialogPreference extends DialogPreference {
 
     private CustomPreferenceDialogFragment mFragment;
+    private DialogInterface.OnShowListener mOnShowListener;
 
     public CustomDialogPreference(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
@@ -54,6 +55,10 @@
         return mFragment != null ? mFragment.getDialog() : null;
     }
 
+    public void setOnShowListener(DialogInterface.OnShowListener listner) {
+        mOnShowListener = listner;
+    }
+
     protected void onPrepareDialogBuilder(AlertDialog.Builder builder,
             DialogInterface.OnClickListener listener) {
     }
@@ -71,6 +76,10 @@
         mFragment = fragment;
     }
 
+    private DialogInterface.OnShowListener getOnShowListener() {
+        return mOnShowListener;
+    }
+
     public static class CustomPreferenceDialogFragment extends PreferenceDialogFragment {
 
         public static CustomPreferenceDialogFragment newInstance(String key) {
@@ -104,6 +113,13 @@
         }
 
         @Override
+        public Dialog onCreateDialog(Bundle savedInstanceState) {
+            final Dialog dialog = super.onCreateDialog(savedInstanceState);
+            dialog.setOnShowListener(getCustomizablePreference().getOnShowListener());
+            return dialog;
+        }
+
+        @Override
         public void onClick(DialogInterface dialog, int which) {
             super.onClick(dialog, which);
             getCustomizablePreference().onClick(dialog, which);
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index 547cd9a..fe0b35b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -103,10 +103,9 @@
     public void handleBroadcast(Intent intent) {
         String action = intent.getAction();
         if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
-            state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
-                    WifiManager.WIFI_STATE_UNKNOWN);
-            enabled = state == WifiManager.WIFI_STATE_ENABLED;
+            updateWifiState();
         } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+            updateWifiState();
             final NetworkInfo networkInfo =
                     intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
             connected = networkInfo != null && networkInfo.isConnected();
@@ -128,6 +127,11 @@
         }
     }
 
+    private void updateWifiState() {
+        state = mWifiManager.getWifiState();
+        enabled = state == WifiManager.WIFI_STATE_ENABLED;
+    }
+
     private void updateRssi(int newRssi) {
         rssi = newRssi;
         level = WifiManager.calculateSignalLevel(rssi, WifiManager.RSSI_LEVELS);
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index b220091..a1e5b0e 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -138,7 +138,7 @@
 
     <!-- Vertical translation of the shelf during animation that happens after the
     notification panel collapses -->
-    <dimen name="shelf_appear_translation">9dp</dimen>
+    <dimen name="shelf_appear_translation">42dp</dimen>
 
     <!-- The amount the content shifts upwards when transforming into the icon -->
     <dimen name="notification_icon_transform_content_shift">32dp</dimen>
@@ -497,9 +497,6 @@
          device. -->
     <dimen name="unlock_move_distance">75dp</dimen>
 
-    <!-- Distance after which the scrim starts fading in when dragging down the quick settings -->
-    <dimen name="notification_scrim_wait_distance">100dp</dimen>
-
     <!-- Move distance for the unlock hint animation on the lockscreen -->
     <dimen name="hint_move_distance">75dp</dimen>
 
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index d45c427..458e133d7 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -24,6 +24,8 @@
     <item type="id" name="scale_y_animator_tag"/>
     <item type="id" name="top_inset_animator_tag"/>
     <item type="id" name="height_animator_tag"/>
+    <item type="id" name="x_animator_tag"/>
+    <item type="id" name="y_animator_tag"/>
     <item type="id" name="shadow_alpha_animator_tag"/>
     <item type="id" name="translation_x_animator_end_value_tag"/>
     <item type="id" name="translation_y_animator_end_value_tag"/>
@@ -34,6 +36,8 @@
     <item type="id" name="top_inset_animator_end_value_tag"/>
     <item type="id" name="height_animator_end_value_tag"/>
     <item type="id" name="shadow_alpha_animator_end_value_tag"/>
+    <item type="id" name="x_animator_tag_end_value"/>
+    <item type="id" name="y_animator_tag_end_value"/>
     <item type="id" name="translation_x_animator_start_value_tag"/>
     <item type="id" name="translation_y_animator_start_value_tag"/>
     <item type="id" name="translation_z_animator_start_value_tag"/>
@@ -43,6 +47,8 @@
     <item type="id" name="top_inset_animator_start_value_tag"/>
     <item type="id" name="height_animator_start_value_tag"/>
     <item type="id" name="shadow_alpha_animator_start_value_tag"/>
+    <item type="id" name="x_animator_tag_start_value"/>
+    <item type="id" name="y_animator_tag_start_value"/>
     <item type="id" name="doze_saved_filter_tag"/>
     <item type="id" name="qs_icon_tag"/>
     <item type="id" name="qs_slash_tag"/>
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index 03a76da..b7ff984 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -114,6 +114,8 @@
                     mHost.dozeTimeTick();
                     // The first frame may arrive when the display isn't ready yet.
                     mHandler.postDelayed(mWakeLock.wrap(mHost::dozeTimeTick), 100);
+                    // The the delayed frame may arrive when the display isn't ready yet either.
+                    mHandler.postDelayed(mWakeLock.wrap(mHost::dozeTimeTick), 1000);
                 }
                 scheduleTimeTick();
                 break;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
index 6c7eda7..5ae43c6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
@@ -65,10 +65,9 @@
     void setKeyguardShowing(boolean keyguardShowing);
 
     /**
-     * Returns the {@link View} that should expand the quick settings when clicked.
+     * Sets the {@link android.view.View.OnClickListener to be used on elements that expend QS.
      */
-    @Nullable
-    View getExpandView();
+    void setExpandClickListener(View.OnClickListener onClickListener);
 
     default void disable(int state1, int state2, boolean animate) {}
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
index abe819b..fd9ddb0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
@@ -17,7 +17,6 @@
 package com.android.systemui.qs;
 
 import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 
 import android.content.Context;
 import android.content.Intent;
@@ -27,6 +26,7 @@
 import android.graphics.PorterDuff.Mode;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.RippleDrawable;
+import android.os.Bundle;
 import android.os.UserManager;
 import android.support.annotation.Nullable;
 import android.support.annotation.VisibleForTesting;
@@ -34,6 +34,7 @@
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.View.OnClickListener;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
@@ -96,6 +97,7 @@
     private ImageView mMobileRoaming;
     private final int mColorForeground;
     private final CellSignalState mInfo = new CellSignalState();
+    private OnClickListener mExpandClickListener;
 
     public QSFooterImpl(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -140,6 +142,7 @@
         mActivityStarter = Dependency.get(ActivityStarter.class);
         addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight,
                 oldBottom) -> updateAnimator(right - left));
+        setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
     }
 
     private void updateAnimator(int width) {
@@ -205,6 +208,11 @@
     }
 
     @Override
+    public void setExpandClickListener(OnClickListener onClickListener) {
+        mExpandClickListener = onClickListener;
+    }
+
+    @Override
     public void setExpanded(boolean expanded) {
         if (mExpanded == expanded) return;
         mExpanded = expanded;
@@ -238,8 +246,20 @@
     }
 
     @Override
-    public View getExpandView() {
-        return findViewById(R.id.expand_indicator);
+    public boolean performAccessibilityAction(int action, Bundle arguments) {
+        if (action == AccessibilityNodeInfo.ACTION_EXPAND) {
+            if (mExpandClickListener != null) {
+                mExpandClickListener.onClick(null);
+                return true;
+            }
+        }
+        return super.performAccessibilityAction(action, arguments);
+    }
+
+    @Override
+    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfo(info);
+        info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index b82e355..cbd1ca1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -240,11 +240,6 @@
     @Override
     public void setHeaderClickable(boolean clickable) {
         if (DEBUG) Log.d(TAG, "setHeaderClickable " + clickable);
-
-        View expandView = mFooter.getExpandView();
-        if (expandView != null) {
-            expandView.setClickable(clickable);
-        }
     }
 
     @Override
@@ -369,11 +364,7 @@
 
     @Override
     public void setExpandClickListener(OnClickListener onClickListener) {
-        View expandView = mFooter.getExpandView();
-
-        if (expandView != null) {
-            expandView.setOnClickListener(onClickListener);
-        }
+        mFooter.setExpandClickListener(onClickListener);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFooter.java
index 24b5a34..2ea21c6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFooter.java
@@ -114,11 +114,9 @@
         }
     }
 
-    @Nullable
     @Override
-    public View getExpandView() {
+    public void setExpandClickListener(OnClickListener onClickListener) {
         // No view that should expand/collapse the quick settings.
-        return null;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 6364f5b..44136c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -58,7 +58,7 @@
             = SystemProperties.getBoolean("debug.icon_scroll_animations", true);
     private static final int TAG_CONTINUOUS_CLIPPING = R.id.continuous_clipping_tag;
     private static final String TAG = "NotificationShelf";
-    private static final long SHELF_IN_TRANSLATION_DURATION = 220;
+    private static final long SHELF_IN_TRANSLATION_DURATION = 200;
 
     private ViewInvertHelper mViewInvertHelper;
     private boolean mDark;
@@ -157,14 +157,18 @@
 
     public void fadeInTranslating() {
         float translation = mShelfIcons.getTranslationY();
-        mShelfIcons.setTranslationY(translation + mShelfAppearTranslation);
+        mShelfIcons.setTranslationY(translation - mShelfAppearTranslation);
         mShelfIcons.setAlpha(0);
         mShelfIcons.animate()
-                .alpha(1)
-                .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
+                .setInterpolator(Interpolators.DECELERATE_QUINT)
                 .translationY(translation)
                 .setDuration(SHELF_IN_TRANSLATION_DURATION)
                 .start();
+        mShelfIcons.animate()
+                .alpha(1)
+                .setInterpolator(Interpolators.LINEAR)
+                .setDuration(SHELF_IN_TRANSLATION_DURATION)
+                .start();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StackScrollerDecorView.java
index 14a6c42..b2eb18e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StackScrollerDecorView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StackScrollerDecorView.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar;
 
+import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.content.Context;
 import android.util.AttributeSet;
@@ -35,6 +36,7 @@
     private boolean mIsVisible;
     private boolean mIsSecondaryVisible;
     private boolean mAnimating;
+    private boolean mSecondaryAnimating;
     private int mDuration = 260;
 
     public StackScrollerDecorView(Context context, AttributeSet attrs) {
@@ -61,13 +63,26 @@
     }
 
     public void performVisibilityAnimation(boolean nowVisible) {
-        animateText(mContent, nowVisible, null /* onFinishedRunnable */);
-        mIsVisible = nowVisible;
+        performVisibilityAnimation(nowVisible, null /* onFinishedRunnable */);
     }
 
     public void performVisibilityAnimation(boolean nowVisible, Runnable onFinishedRunnable) {
-        animateText(mContent, nowVisible, onFinishedRunnable);
-        mIsVisible = nowVisible;
+        boolean oldVisible = isVisible();
+        animateText(mContent, nowVisible, oldVisible, new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    mAnimating = true;
+                }
+
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mAnimating = false;
+                    mIsVisible = nowVisible;
+                    if (onFinishedRunnable != null) {
+                        onFinishedRunnable.run();
+                    }
+                }
+            });
     }
 
     public void performSecondaryVisibilityAnimation(boolean nowVisible) {
@@ -76,16 +91,43 @@
 
     public void performSecondaryVisibilityAnimation(boolean nowVisible,
             Runnable onFinishedRunnable) {
-        animateText(mSecondaryView, nowVisible, onFinishedRunnable);
-        mIsSecondaryVisible = nowVisible;
+        boolean oldVisible = isSecondaryVisible();
+        animateText(mSecondaryView, nowVisible, oldVisible, new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    mSecondaryAnimating = true;
+                }
+
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mSecondaryAnimating = false;
+                    mIsSecondaryVisible = nowVisible;
+                    if (onFinishedRunnable != null) {
+                        onFinishedRunnable.run();
+                    }
+                }
+            });
     }
 
+    /**
+     * Check whether the secondary view is visible or not.<p/>
+     *
+     * @see #isVisible()
+     */
     public boolean isSecondaryVisible() {
-        return mSecondaryView != null && (mIsSecondaryVisible || mAnimating);
+        return mSecondaryView != null && (mIsSecondaryVisible ^ mSecondaryAnimating);
     }
 
+    /**
+     * Check whether the whole view is visible or not.<p/>
+     * The view is considered visible if it matches one of following:
+     * <ul>
+     *   <li> It's visible and there is no ongoing animation. </li>
+     *   <li> It's not visible but is animating, thus being eventually visible. </li>
+     * </ul>
+     */
     public boolean isVisible() {
-        return mIsVisible || mAnimating;
+        return mIsVisible ^ mAnimating;
     }
 
     void setDuration(int duration) {
@@ -95,15 +137,18 @@
     /**
      * Animate the text to a new visibility.
      *
-     * @param nowVisible should it now be visible
-     * @param onFinishedRunnable A runnable which should be run when the animation is
-     *        finished.
+     * @param view Target view, maybe content view or dissmiss view
+     * @param nowVisible Should it now be visible
+     * @param oldVisible Is it visible currently
+     * @param listener A listener that doing flag settings or other actions
      */
-    private void animateText(View view, boolean nowVisible, final Runnable onFinishedRunnable) {
+    private void animateText(View view, boolean nowVisible, boolean oldVisible,
+        AnimatorListenerAdapter listener) {
         if (view == null) {
             return;
         }
-        if (nowVisible != mIsVisible) {
+
+        if (nowVisible != oldVisible) {
             // Animate text
             float endValue = nowVisible ? 1.0f : 0.0f;
             Interpolator interpolator;
@@ -112,24 +157,11 @@
             } else {
                 interpolator = Interpolators.ALPHA_OUT;
             }
-            mAnimating = true;
             view.animate()
                     .alpha(endValue)
                     .setInterpolator(interpolator)
                     .setDuration(mDuration)
-                    .withEndAction(new Runnable() {
-                        @Override
-                        public void run() {
-                            mAnimating = false;
-                            if (onFinishedRunnable != null) {
-                                onFinishedRunnable.run();
-                            }
-                        }
-                    });
-        } else {
-            if (onFinishedRunnable != null) {
-                onFinishedRunnable.run();
-            }
+                    .setListener(listener);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarFacetButtonController.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarFacetButtonController.java
index e7c8c94..8160f90 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarFacetButtonController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarFacetButtonController.java
@@ -75,7 +75,8 @@
         int displayId = getDisplayId();
         for (ActivityManager.StackInfo stackInfo :stackInfoList) {
             // if the display id is known and does not match the stack we skip
-            if (displayId != -1 && displayId != stackInfo.displayId) {
+            if (displayId != -1 && displayId != stackInfo.displayId ||
+                    stackInfo.topActivity == null) {
                 continue;
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/hvac/HvacController.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/hvac/HvacController.java
index 7d283d9..81d6191 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/hvac/HvacController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/hvac/HvacController.java
@@ -28,8 +28,10 @@
 import android.os.IBinder;
 import android.util.Log;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 
@@ -46,7 +48,7 @@
     private Handler mHandler;
     private Car mCar;
     private CarHvacManager mHvacManager;
-    private HashMap<HvacKey, TemperatureView> mTempComponents = new HashMap<>();
+    private HashMap<HvacKey, List<TemperatureView>> mTempComponents = new HashMap<>();
 
     public HvacController(Context context) {
         mContext = context;
@@ -114,18 +116,24 @@
      * @param temperatureView
      */
     public void addHvacTextView(TemperatureView temperatureView) {
-        mTempComponents.put(
-                new HvacKey(temperatureView.getPropertyId(), temperatureView.getAreaId()),
-                temperatureView);
+
+        HvacKey hvacKey = new HvacKey(temperatureView.getPropertyId(), temperatureView.getAreaId());
+        if (!mTempComponents.containsKey(hvacKey)) {
+            mTempComponents.put(hvacKey, new ArrayList<>());
+        }
+        mTempComponents.get(hvacKey).add(temperatureView);
         initComponent(temperatureView);
     }
 
     private void initComponents() {
-        Iterator<Map.Entry<HvacKey, TemperatureView>> iterator =
+        Iterator<Map.Entry<HvacKey, List<TemperatureView>>> iterator =
                 mTempComponents.entrySet().iterator();
         while (iterator.hasNext()) {
-            Map.Entry<HvacKey, TemperatureView> next = iterator.next();
-            initComponent(next.getValue());
+            Map.Entry<HvacKey, List<TemperatureView>> next = iterator.next();
+            List<TemperatureView> temperatureViews = next.getValue();
+            for (TemperatureView view : temperatureViews) {
+                initComponent(view);
+            }
         }
     }
 
@@ -155,11 +163,13 @@
             try {
                 int areaId = val.getAreaId();
                 int propertyId = val.getPropertyId();
-                TemperatureView temperatureView = mTempComponents.get(
+                List<TemperatureView> temperatureViews = mTempComponents.get(
                         new HvacKey(propertyId, areaId));
-                if (temperatureView != null) {
+                if (temperatureViews != null && !temperatureViews.isEmpty()) {
                     float value = (float) val.getValue();
-                    temperatureView.setTemp(value);
+                    for (TemperatureView tempView : temperatureViews) {
+                        tempView.setTemp(value);
+                    }
                 } // else the data is not of interest
             } catch (Exception e) {
                 // catch all so we don't take down the sysui if a new data type is
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java
index d7b211f..75b41ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java
@@ -20,25 +20,30 @@
 import android.util.Property;
 import android.view.View;
 
-import com.android.systemui.statusbar.stack.AnimationProperties;
+import com.android.systemui.R;
 
 import java.util.function.BiConsumer;
-import java.util.function.Consumer;
 import java.util.function.Function;
 
 /**
  * An animatable property of a view. Used with {@link PropertyAnimator}
  */
-public interface AnimatableProperty {
-    int getAnimationStartTag();
+public abstract class AnimatableProperty {
 
-    int getAnimationEndTag();
+    public static final AnimatableProperty X = AnimatableProperty.from(View.X,
+            R.id.x_animator_tag, R.id.x_animator_tag_start_value, R.id.x_animator_tag_end_value);
+    public static final AnimatableProperty Y = AnimatableProperty.from(View.Y,
+            R.id.y_animator_tag, R.id.y_animator_tag_start_value, R.id.y_animator_tag_end_value);
 
-    int getAnimatorTag();
+    public abstract int getAnimationStartTag();
 
-    Property getProperty();
+    public abstract int getAnimationEndTag();
 
-    static <T extends View> AnimatableProperty from(String name, BiConsumer<T, Float> setter,
+    public abstract int getAnimatorTag();
+
+    public abstract Property getProperty();
+
+    public static <T extends View> AnimatableProperty from(String name, BiConsumer<T, Float> setter,
             Function<T, Float> getter, int animatorTag, int startValueTag, int endValueTag) {
         Property<T, Float> property = new FloatProperty<T>(name) {
 
@@ -74,4 +79,29 @@
             }
         };
     }
+
+    public static <T extends View> AnimatableProperty from(Property<T, Float> property,
+            int animatorTag, int startValueTag, int endValueTag) {
+        return new AnimatableProperty() {
+            @Override
+            public int getAnimationStartTag() {
+                return startValueTag;
+            }
+
+            @Override
+            public int getAnimationEndTag() {
+                return endValueTag;
+            }
+
+            @Override
+            public int getAnimatorTag() {
+                return animatorTag;
+            }
+
+            @Override
+            public Property getProperty() {
+                return property;
+            }
+        };
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
index 92dcc9e..2efcd16 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
@@ -24,6 +24,7 @@
 import android.view.View;
 import android.view.animation.Interpolator;
 
+import com.android.keyguard.KeyguardStatusView;
 import com.android.systemui.Interpolators;
 import com.android.systemui.statusbar.stack.AnimationFilter;
 import com.android.systemui.statusbar.stack.AnimationProperties;
@@ -115,4 +116,7 @@
         view.setTag(animationEndTag, newEndValue);
     }
 
+    public static <T extends View> boolean isAnimating(T view, AnimatableProperty property) {
+        return  view.getTag(property.getAnimatorTag()) != null;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index d9a55c5..042e4ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -20,8 +20,8 @@
 
 import android.content.res.Resources;
 import android.util.MathUtils;
-import com.android.keyguard.KeyguardStatusView;
 
+import com.android.keyguard.KeyguardStatusView;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 
@@ -47,12 +47,6 @@
     private int mClockNotificationsMargin;
 
     /**
-     * Current height of {@link NotificationPanelView}, considering how much the
-     * user collapsed it.
-     */
-    private float mExpandedHeight;
-
-    /**
      * Height of the parent view - display size in px.
      */
     private int mHeight;
@@ -84,9 +78,9 @@
     private int mContainerTopPadding;
 
     /**
-     * @see NotificationPanelView#getMaxPanelHeight()
+     * @see NotificationPanelView#getExpandedFraction()
      */
-    private float mMaxPanelHeight;
+    private float mPanelExpansion;
 
     /**
      * Burn-in prevention x translation.
@@ -140,13 +134,13 @@
     }
 
     public void setup(int minTopMargin, int maxShadeBottom, int notificationStackHeight,
-            float expandedHeight, float maxPanelHeight, int parentHeight, int keyguardStatusHeight,
-            float dark, boolean secure, boolean pulsing, int bouncerTop) {
+            float panelExpansion, int parentHeight,
+            int keyguardStatusHeight, float dark, boolean secure, boolean pulsing,
+            int bouncerTop) {
         mMinTopMargin = minTopMargin + mContainerTopPadding;
         mMaxShadeBottom = maxShadeBottom;
         mNotificationStackHeight = notificationStackHeight;
-        mExpandedHeight = expandedHeight;
-        mMaxPanelHeight = maxPanelHeight;
+        mPanelExpansion = panelExpansion;
         mHeight = parentHeight;
         mKeyguardStatusHeight = keyguardStatusHeight;
         mDarkAmount = dark;
@@ -171,16 +165,12 @@
         return mHeight / 2 - mKeyguardStatusHeight - mClockNotificationsMargin;
     }
 
-    public int getExpandedClockBottom() {
-        return getExpandedClockPosition() + mKeyguardStatusHeight;
-    }
-
     /**
      * Vertically align the clock and the shade in the available space considering only
      * a percentage of the clock height defined by {@code CLOCK_HEIGHT_WEIGHT}.
      * @return Clock Y in pixels.
      */
-    private int getExpandedClockPosition() {
+    public int getExpandedClockPosition() {
         final int availableHeight = mMaxShadeBottom - mMinTopMargin;
         final int containerCenter = mMinTopMargin + availableHeight / 2;
 
@@ -212,8 +202,7 @@
                 mMinTopMargin : -mKeyguardStatusHeight;
 
         // Move clock up while collapsing the shade
-        float shadeExpansion = mExpandedHeight / mMaxPanelHeight;
-        shadeExpansion = Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(shadeExpansion);
+        float shadeExpansion = Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(mPanelExpansion);
         final float clockY = MathUtils.lerp(clockYTarget, clockYRegular, shadeExpansion);
 
         return (int) MathUtils.lerp(clockY, clockYDark, mDarkAmount);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 04bfcdd..66176b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -544,7 +544,12 @@
 
             // Set visibility, may fail if a11y service is active.
             // If invisible, call will stop animation.
-            mNavigationBarView.setRotateButtonVisibility(true);
+            int appliedVisibility = mNavigationBarView.setRotateButtonVisibility(true);
+            if (appliedVisibility == View.VISIBLE) {
+                // If the button will actually become visible and the navbar is about to hide,
+                // tell the statusbar to keep it around for longer
+                mStatusBar.touchAutoHide();
+            }
 
         } else { // Hide
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index c2053b6..6dbe9f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -766,13 +766,13 @@
         if (setIcon) getRotateSuggestionButton().setImageDrawable(mRotateSuggestionIcon);
     }
 
-    public void setRotateButtonVisibility(final boolean visible) {
+    public int setRotateButtonVisibility(final boolean visible) {
         // Never show if a11y is visible
         final boolean adjVisible = visible && !mShowAccessibilityButton;
         final int vis = adjVisible ? View.VISIBLE : View.INVISIBLE;
 
         // No need to do anything if the request matches the current state
-        if (vis == getRotateSuggestionButton().getVisibility()) return;
+        if (vis == getRotateSuggestionButton().getVisibility()) return vis;
 
         getRotateSuggestionButton().setVisibility(vis);
         mShowRotateButton = visible;
@@ -789,6 +789,9 @@
 
         // Hide/restore other button visibility, if necessary
         updateNavButtonIcons();
+
+        // Return applied visibility
+        return vis;
     }
 
     public boolean isRotateButtonVisible() { return mShowRotateButton; }
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 351633b..a0a97c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -20,7 +20,6 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.app.ActivityManager;
@@ -44,7 +43,6 @@
 import android.view.VelocityTracker;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
 import android.view.WindowInsets;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.FrameLayout;
@@ -69,8 +67,11 @@
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
+import com.android.systemui.statusbar.notification.PropertyAnimator;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+import com.android.systemui.statusbar.stack.AnimationProperties;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.stack.StackStateAnimator;
 
@@ -101,6 +102,8 @@
 
     public static final long DOZE_ANIMATION_DURATION = 700;
 
+    private static final AnimationProperties CLOCK_ANIMATION_PROPERTIES = new AnimationProperties()
+            .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
     private static final FloatProperty<NotificationPanelView> SET_DARK_AMOUNT_PROPERTY =
             new FloatProperty<NotificationPanelView>("mDarkAmount") {
                 @Override
@@ -122,11 +125,10 @@
     private QS mQs;
     private FrameLayout mQsFrame;
     private KeyguardStatusView mKeyguardStatusView;
-    private View mReserveNotificationSpace;
     private View mQsNavbarScrim;
     protected NotificationsQuickSettingsContainer mNotificationContainerParent;
     protected NotificationStackScrollLayout mNotificationStackScroller;
-    private boolean mAnimateNextTopPaddingChange;
+    private boolean mAnimateNextPositionUpdate;
 
     private int mTrackingPointer;
     private VelocityTracker mQsVelocityTracker;
@@ -173,9 +175,6 @@
     private int mUnlockMoveDistance;
     private float mEmptyDragAmount;
 
-    private Animator mClockAnimator;
-    private int mClockAnimationTargetX = Integer.MIN_VALUE;
-    private int mClockAnimationTargetY = Integer.MIN_VALUE;
     private KeyguardClockPositionAlgorithm mClockPositionAlgorithm =
             new KeyguardClockPositionAlgorithm();
     private KeyguardClockPositionAlgorithm.Result mClockPositionResult =
@@ -183,7 +182,6 @@
     private boolean mIsExpanding;
 
     private boolean mBlockTouches;
-    private int mNotificationScrimWaitDistance;
     // Used for two finger gesture as well as accessibility shortcut to QS.
     private boolean mQsExpandImmediate;
     private boolean mTwoFingerQsExpandPossible;
@@ -310,8 +308,6 @@
                 getResources().getDimensionPixelSize(R.dimen.header_notifications_collide_distance);
         mUnlockMoveDistance = getResources().getDimensionPixelOffset(R.dimen.unlock_move_distance);
         mClockPositionAlgorithm.loadDimens(getResources());
-        mNotificationScrimWaitDistance =
-                getResources().getDimensionPixelSize(R.dimen.notification_scrim_wait_distance);
         mQsFalsingThreshold = getResources().getDimensionPixelSize(
                 R.dimen.qs_falsing_threshold);
         mPositionMinSideMargin = getResources().getDimensionPixelSize(
@@ -461,19 +457,19 @@
      */
     private void positionClockAndNotifications() {
         boolean animate = mNotificationStackScroller.isAddOrRemoveAnimationPending();
+        boolean animateClock = animate || mAnimateNextPositionUpdate;
         int stackScrollerPadding;
         if (mStatusBarState != StatusBarState.KEYGUARD) {
             stackScrollerPadding = (mQs != null ? mQs.getHeader().getHeight() : 0) + mQsPeekHeight
             +  mQsNotificationTopPadding;
         } else {
-            final int totalHeight = getHeight();
-            final int bottomPadding = Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding);
+            int totalHeight = getHeight();
+            int bottomPadding = Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding);
             mClockPositionAlgorithm.setup(
                     mStatusBarMinHeight,
                     totalHeight - bottomPadding,
-                    calculatePanelHeightShade() - mNotificationStackScroller.getTopPadding(),
-                    getExpandedHeight(),
-                    getMaxPanelHeight(),
+                    mNotificationStackScroller.getIntrinsicContentHeight(),
+                    getExpandedFraction(),
                     totalHeight,
                     mKeyguardStatusView.getHeight(),
                     mDarkAmount,
@@ -481,12 +477,10 @@
                     mPulsing,
                     mBouncerTop);
             mClockPositionAlgorithm.run(mClockPositionResult);
-            if (animate || mClockAnimator != null) {
-                startClockAnimation(mClockPositionResult.clockX, mClockPositionResult.clockY);
-            } else {
-                mKeyguardStatusView.setX(mClockPositionResult.clockX);
-                mKeyguardStatusView.setY(mClockPositionResult.clockY);
-            }
+            PropertyAnimator.setProperty(mKeyguardStatusView, AnimatableProperty.X,
+                    mClockPositionResult.clockX, CLOCK_ANIMATION_PROPERTIES, animateClock);
+            PropertyAnimator.setProperty(mKeyguardStatusView, AnimatableProperty.Y,
+                    mClockPositionResult.clockY, CLOCK_ANIMATION_PROPERTIES, animateClock);
             updateClock();
             stackScrollerPadding = mClockPositionResult.stackScrollerPadding;
         }
@@ -497,6 +491,7 @@
         mStackScrollerMeasuringPass++;
         requestScrollerTopPaddingUpdate(animate);
         mStackScrollerMeasuringPass = 0;
+        mAnimateNextPositionUpdate = false;
     }
 
     /**
@@ -558,42 +553,6 @@
         positionClockAndNotifications();
     }
 
-    private void startClockAnimation(int x, int y) {
-        if (mClockAnimationTargetX == x && mClockAnimationTargetY == y) {
-            return;
-        }
-        mClockAnimationTargetX = x;
-        mClockAnimationTargetY = y;
-        getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
-            @Override
-            public boolean onPreDraw() {
-                getViewTreeObserver().removeOnPreDrawListener(this);
-                if (mClockAnimator != null) {
-                    mClockAnimator.removeAllListeners();
-                    mClockAnimator.cancel();
-                }
-                AnimatorSet set = new AnimatorSet();
-                set.play(ObjectAnimator.ofFloat(
-                        mKeyguardStatusView, View.Y, mClockAnimationTargetY))
-                        .with(ObjectAnimator.ofFloat(
-                                mKeyguardStatusView, View.X, mClockAnimationTargetX));
-                mClockAnimator = set;
-                mClockAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
-                mClockAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
-                mClockAnimator.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        mClockAnimator = null;
-                        mClockAnimationTargetX = Integer.MIN_VALUE;
-                        mClockAnimationTargetY = Integer.MIN_VALUE;
-                    }
-                });
-                mClockAnimator.start();
-                return true;
-            }
-        });
-    }
-
     private void updateClock() {
         if (!mKeyguardStatusViewAnimating) {
             mKeyguardStatusView.setAlpha(mClockPositionResult.clockAlpha);
@@ -601,9 +560,9 @@
     }
 
     public void animateToFullShade(long delay) {
-        mAnimateNextTopPaddingChange = true;
         mNotificationStackScroller.goToFullShade(delay);
         requestLayout();
+        mAnimateNextPositionUpdate = true;
     }
 
     public void setQsExpansionEnabled(boolean qsExpansionEnabled) {
@@ -1411,10 +1370,8 @@
 
     protected void requestScrollerTopPaddingUpdate(boolean animate) {
         mNotificationStackScroller.updateTopPadding(calculateQsTopPadding(),
-                mAnimateNextTopPaddingChange || animate,
-                mKeyguardShowing
+                animate, mKeyguardShowing
                         && (mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted));
-        mAnimateNextTopPaddingChange = false;
     }
 
     private void trackMovement(MotionEvent event) {
@@ -1535,7 +1492,7 @@
         if (mQsExpandImmediate || mQsExpanded || mIsExpanding && mQsExpandedWhenExpandingStarted) {
             maxHeight = calculatePanelHeightQsExpanded();
         } else {
-            maxHeight = Math.max(calculatePanelHeightShade(), calculatePanelHeightShadeExpanded());
+            maxHeight = calculatePanelHeightShade();
         }
         maxHeight = Math.max(maxHeight, min);
         return maxHeight;
@@ -1606,14 +1563,15 @@
         int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin();
         int maxHeight = mNotificationStackScroller.getHeight() - emptyBottomMargin;
         maxHeight += mNotificationStackScroller.getTopPaddingOverflow();
-        return maxHeight;
-    }
 
-    private int calculatePanelHeightShadeExpanded() {
-        return mNotificationStackScroller.getHeight()
-                - mNotificationStackScroller.getEmptyBottomMargin()
-                - mNotificationStackScroller.getTopPadding()
-                + mClockPositionAlgorithm.getExpandedClockBottom();
+        if (mStatusBarState == StatusBarState.KEYGUARD) {
+            int minKeyguardPanelBottom = mClockPositionAlgorithm.getExpandedClockPosition()
+                    + mKeyguardStatusView.getHeight()
+                    + mNotificationStackScroller.getIntrinsicContentHeight();
+            return Math.max(maxHeight, minKeyguardPanelBottom);
+        } else {
+            return maxHeight;
+        }
     }
 
     private int calculatePanelHeightQsExpanded() {
@@ -1652,7 +1610,8 @@
 
     private void updateNotificationTranslucency() {
         float alpha = 1f;
-        if (mClosingWithAlphaFadeOut && !mExpandingFromHeadsUp && !mHeadsUpManager.hasPinnedHeadsUp()) {
+        if (mClosingWithAlphaFadeOut && !mExpandingFromHeadsUp &&
+                !mHeadsUpManager.hasPinnedHeadsUp()) {
             alpha = getFadeoutAlpha();
         }
         mNotificationStackScroller.setAlpha(alpha);
@@ -1907,13 +1866,16 @@
         if (view == null && mQsExpanded) {
             return;
         }
+        if (needsAnimation) {
+            mAnimateNextPositionUpdate = true;
+        }
         ExpandableView firstChildNotGone = mNotificationStackScroller.getFirstChildNotGone();
         ExpandableNotificationRow firstRow = firstChildNotGone instanceof ExpandableNotificationRow
                 ? (ExpandableNotificationRow) firstChildNotGone
                 : null;
         if (firstRow != null
                 && (view == firstRow || (firstRow.getNotificationParent() == firstRow))) {
-            requestScrollerTopPaddingUpdate(false);
+            requestScrollerTopPaddingUpdate(false /* animate */);
         }
         requestPanelHeightUpdate();
     }
@@ -1965,14 +1927,12 @@
 
     @Override
     public void onClick(View v) {
-        if (v.getId() == R.id.expand_indicator) {
-            onQsExpansionStarted();
-            if (mQsExpanded) {
-                flingSettings(0 /* vel */, false /* expand */, null, true /* isClick */);
-            } else if (mQsExpansionEnabled) {
-                mLockscreenGestureLogger.write(MetricsEvent.ACTION_SHADE_QS_TAP, 0, 0);
-                flingSettings(0 /* vel */, true /* expand */, null, true /* isClick */);
-            }
+        onQsExpansionStarted();
+        if (mQsExpanded) {
+            flingSettings(0 /* vel */, false /* expand */, null, true /* isClick */);
+        } else if (mQsExpansionEnabled) {
+            mLockscreenGestureLogger.write(MetricsEvent.ACTION_SHADE_QS_TAP, 0, 0);
+            flingSettings(0 /* vel */, true /* expand */, null, true /* isClick */);
         }
     }
 
@@ -2339,15 +2299,15 @@
             p.setColor(Color.YELLOW);
             canvas.drawLine(0, calculatePanelHeightShade(), getWidth(),
                     calculatePanelHeightShade(), p);
-            p.setColor(Color.GRAY);
-            canvas.drawLine(0, calculatePanelHeightShadeExpanded(), getWidth(),
-                    calculatePanelHeightShadeExpanded(), p);
             p.setColor(Color.MAGENTA);
             canvas.drawLine(0, calculateQsTopPadding(), getWidth(),
                     calculateQsTopPadding(), p);
             p.setColor(Color.CYAN);
-            canvas.drawLine(0, mNotificationStackScroller.getTopPadding(), getWidth(),
+            canvas.drawLine(0, mClockPositionResult.stackScrollerPadding, getWidth(),
                     mNotificationStackScroller.getTopPadding(), p);
+            p.setColor(Color.GRAY);
+            canvas.drawLine(0, mClockPositionResult.clockY, getWidth(),
+                    mClockPositionResult.clockY, p);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index 304a499..347a4b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -828,7 +828,7 @@
     }
 
     @Override
-    protected void onLayout (boolean changed, int left, int top, int right, int bottom) {
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
         mStatusBar.onPanelLaidOut();
         requestPanelHeightUpdate();
@@ -1088,13 +1088,10 @@
         }
         cancelPeek();
         notifyExpandingStarted();
-        startUnlockHintAnimationPhase1(new Runnable() {
-            @Override
-            public void run() {
-                notifyExpandingFinished();
-                onUnlockHintFinished();
-                mHintAnimationRunning = false;
-            }
+        startUnlockHintAnimationPhase1(() -> {
+            notifyExpandingFinished();
+            onUnlockHintFinished();
+            mHintAnimationRunning = false;
         });
         onUnlockHintStarted();
         mHintAnimationRunning = true;
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 04557b4..e63a2e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -165,14 +165,15 @@
         // • The user quickly taps on the display and we show "swipe up to unlock."
         // • Keyguard will be dismissed by an action. a.k.a: FLAG_DISMISS_KEYGUARD_ACTIVITY
         // • Full-screen user switcher is displayed.
-        if (mOccluded || mNotificationPanelView.isUnlockHintRunning()
-                || mBouncer.willDismissWithAction()
+        if (mNotificationPanelView.isUnlockHintRunning()) {
+            mBouncer.setExpansion(1);
+        } else if (mOccluded || mBouncer.willDismissWithAction()
                 || mStatusBar.isFullScreenUserSwitcherState()) {
             mBouncer.setExpansion(0);
-        } else if (mShowing && mStatusBar.isKeyguardCurrentlySecure() && !mDozing) {
+        } else if (mShowing && !mDozing) {
             mBouncer.setExpansion(expansion);
-            if (expansion != 1 && tracking && !mBouncer.isShowing()
-                    && !mBouncer.isAnimatingAway()) {
+            if (expansion != 1 && tracking && mStatusBar.isKeyguardCurrentlySecure()
+                    && !mBouncer.isShowing() && !mBouncer.isAnimatingAway()) {
                 mBouncer.show(false /* resetSecuritySelection */, false /* animated */);
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
index 1b02e15..22a48f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
@@ -269,13 +269,6 @@
                 }
                 if (mCode != 0) {
                     if (doIt) {
-                        // If there was a pending remote recents animation, then we need to
-                        // cancel the animation now before we handle the button itself. In the case
-                        // where we are going home and the recents animation has already started,
-                        // just cancel the recents animation, leaving the home stack in place
-                        boolean isHomeKey = mCode == KEYCODE_HOME;
-                        ActivityManagerWrapper.getInstance().cancelRecentsAnimation(!isHomeKey);
-
                         sendEvent(KeyEvent.ACTION_UP, 0);
                         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
                     } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 7c64811..b1c0a96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -164,6 +164,7 @@
 
     private Paint mDebugPaint;
     private int mContentHeight;
+    private int mIntrinsicContentHeight;
     private int mCollapsedSize;
     private int mPaddingBetweenElements;
     private int mIncreasedPaddingBetweenElements;
@@ -538,15 +539,16 @@
                 canvas.drawRect(darkLeft, darkTop, darkRight, darkBottom, mBackgroundPaint);
             }
         } else {
-            float animProgress = Interpolators.FAST_OUT_SLOW_IN
-                    .getInterpolation(1f - mDarkAmount);
-            float sidePaddingsProgress = Interpolators.FAST_OUT_SLOW_IN
-                    .getInterpolation((1f - mDarkAmount) * 2);
+            float inverseDark = 1 - mDarkAmount;
+            float yProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(inverseDark);
+            float xProgress = Interpolators.FAST_OUT_SLOW_IN
+                    .getInterpolation(inverseDark * 2f);
+
             mBackgroundAnimationRect.set(
-                    (int) MathUtils.lerp(darkLeft, lockScreenLeft, sidePaddingsProgress),
-                    (int) MathUtils.lerp(darkTop, lockScreenTop, animProgress),
-                    (int) MathUtils.lerp(darkRight, lockScreenRight, sidePaddingsProgress),
-                    (int) MathUtils.lerp(darkBottom, lockScreenBottom, animProgress));
+                    (int) MathUtils.lerp(darkLeft, lockScreenLeft, xProgress),
+                    (int) MathUtils.lerp(darkTop, lockScreenTop, yProgress),
+                    (int) MathUtils.lerp(darkRight, lockScreenRight, xProgress),
+                    (int) MathUtils.lerp(darkBottom, lockScreenBottom, yProgress));
             if (!mAmbientState.isDark() || mFirstVisibleBackgroundChild != null) {
                 canvas.drawRoundRect(mBackgroundAnimationRect.left, mBackgroundAnimationRect.top,
                         mBackgroundAnimationRect.right, mBackgroundAnimationRect.bottom,
@@ -628,8 +630,12 @@
     }
 
     private void notifyHeightChangeListener(ExpandableView view) {
+        notifyHeightChangeListener(view, false /* needsAnimation */);
+    }
+
+    private void notifyHeightChangeListener(ExpandableView view, boolean needsAnimation) {
         if (mOnHeightChangedListener != null) {
-            mOnHeightChangedListener.onHeightChanged(view, false /* needsAnimation */);
+            mOnHeightChangedListener.onHeightChanged(view, needsAnimation);
         }
     }
 
@@ -850,7 +856,7 @@
                 mNeedsAnimation =  true;
             }
             requestChildrenUpdate();
-            notifyHeightChangeListener(null);
+            notifyHeightChangeListener(null, animate);
         }
     }
 
@@ -915,6 +921,13 @@
         updateClipping();
     }
 
+    /**
+     * Return the height of the content ignoring the footer.
+     */
+    public int getIntrinsicContentHeight() {
+        return mIntrinsicContentHeight;
+    }
+
     public void updateClipping() {
         boolean animatingClipping = mDarkAmount > 0 && mDarkAmount < 1;
         boolean clipped = mRequestedClipBounds != null && !mInHeadsUpPinnedMode
@@ -2130,8 +2143,9 @@
 
         for (int i = 0; i < getChildCount(); i++) {
             ExpandableView expandableView = (ExpandableView) getChildAt(i);
+            boolean footerViewOnLockScreen = expandableView == mFooterView && onKeyguard();
             if (expandableView.getVisibility() != View.GONE
-                    && !expandableView.hasNoContentHeight()) {
+                    && !expandableView.hasNoContentHeight() && !footerViewOnLockScreen) {
                 boolean limitReached = maxDisplayedNotifications != -1
                         && numShownItems >= maxDisplayedNotifications;
                 boolean notificationOnAmbientThatIsNotPulsing = mAmbientState.isFullyDark()
@@ -2179,6 +2193,7 @@
                 }
             }
         }
+        mIntrinsicContentHeight = height;
         mContentHeight = height + mTopPadding + mBottomMargin;
         updateScrollability();
         clampScrollPosition();
@@ -3656,7 +3671,7 @@
         updateContentHeight();
         updateScrollPositionOnExpandInBottom(view);
         clampScrollPosition();
-        notifyHeightChangeListener(view);
+        notifyHeightChangeListener(view, needsAnimation);
         ExpandableNotificationRow row = view instanceof ExpandableNotificationRow
                 ? (ExpandableNotificationRow) view
                 : null;
@@ -3970,6 +3985,7 @@
                 mShelf.fadeInTranslating();
             }
         }
+        updateAlgorithmHeightAndPadding();
         updateBackgroundDimming();
         updateAntiBurnInTranslation();
         requestChildrenUpdate();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
index a153140..f0ca3ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
@@ -39,6 +39,7 @@
 import com.android.systemui.statusbar.stack.AnimationProperties;
 import com.android.systemui.statusbar.stack.ViewState;
 
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -227,4 +228,13 @@
         assertNotNull(animator);
         assertTrue(animator.getListeners().contains(mFinishListener));
     }
+
+    @Test
+    public void testIsAnimating() {
+        mAnimationFilter.reset();
+        mAnimationFilter.animate(mProperty.getProperty());
+        assertFalse(PropertyAnimator.isAnimating(mView, mProperty));
+        PropertyAnimator.startAnimation(mView, mProperty, 200f, mAnimationProperties);
+        assertTrue(PropertyAnimator.isAnimating(mView, mProperty));
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
index d30e777..6591715 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
@@ -136,10 +136,9 @@
     }
 
     protected void setWifiEnabled(boolean enabled) {
-        Intent i = new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION);
-        i.putExtra(WifiManager.EXTRA_WIFI_STATE,
+        when(mMockWm.getWifiState()).thenReturn(
                 enabled ? WifiManager.WIFI_STATE_ENABLED : WifiManager.WIFI_STATE_DISABLED);
-        mNetworkController.onReceive(mContext, i);
+        mNetworkController.onReceive(mContext, new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION));
     }
 
     protected void setWifiState(boolean connected, String ssid) {
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 1d62eb7..e6b2a35 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -2151,11 +2151,8 @@
             if (saveTriggerId != null) {
                 writeLog(MetricsEvent.AUTOFILL_EXPLICIT_SAVE_TRIGGER_DEFINITION);
             }
-            int flags = saveInfo.getFlags();
-            if (mCompatMode) {
-                flags |= SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE;
-            }
-            mSaveOnAllViewsInvisible = (flags & SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE) != 0;
+            mSaveOnAllViewsInvisible =
+                    (saveInfo.getFlags() & SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE) != 0;
 
             // We only need to track views if we want to save once they become invisible.
             if (mSaveOnAllViewsInvisible) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 6951c50..6718d95 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -26633,6 +26633,11 @@
         }
 
         @Override
+        public void cancelRecentsAnimation(boolean restoreHomeStackPosition) {
+            ActivityManagerService.this.cancelRecentsAnimation(restoreHomeStackPosition);
+        }
+
+        @Override
         public boolean isUidActive(int uid) {
             synchronized (ActivityManagerService.this) {
                 final UidRecord uidRec = mActiveUids.get(uid);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 8212463..cc3a489 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -5971,6 +5971,8 @@
                 mConnectedDevices.remove(deviceKey);
                 return true;
             }
+            Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey + ", deviceSpec="
+                       + deviceSpec + ", connect=" + connect);
         }
         return false;
     }
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index 2845383..c81624a 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -822,9 +822,9 @@
         private void resolveStrictModeHostname() {
             try {
                 // Do a blocking DNS resolution using the network-assigned nameservers.
-                mPrivateDnsConfig = new PrivateDnsConfig(
-                        mPrivateDnsProviderHostname,
-                        mNetwork.getAllByName(mPrivateDnsProviderHostname));
+                final InetAddress[] ips = ResolvUtil.blockingResolveAllLocally(
+                        mNetwork, mPrivateDnsProviderHostname);
+                mPrivateDnsConfig = new PrivateDnsConfig(mPrivateDnsProviderHostname, ips);
             } catch (UnknownHostException uhe) {
                 mPrivateDnsConfig = null;
             }
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
index 6490964..61d67b7 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
@@ -17,6 +17,7 @@
 package com.android.server.net;
 
 import android.net.Network;
+import android.net.NetworkTemplate;
 import android.telephony.SubscriptionPlan;
 
 import java.util.Set;
@@ -58,6 +59,11 @@
      */
     public abstract SubscriptionPlan getSubscriptionPlan(Network network);
 
+    /**
+     * Return the active {@link SubscriptionPlan} for the given template.
+     */
+    public abstract SubscriptionPlan getSubscriptionPlan(NetworkTemplate template);
+
     public static final int QUOTA_TYPE_JOBS = 1;
     public static final int QUOTA_TYPE_MULTIPATH = 2;
 
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 5b8e8c0..5bd7c0b 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -189,6 +189,7 @@
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
+import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
 import android.telephony.SubscriptionPlan;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
@@ -198,6 +199,7 @@
 import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.DataUnit;
+import android.util.IntArray;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Range;
@@ -228,6 +230,7 @@
 import com.android.server.SystemConfig;
 
 import libcore.io.IoUtils;
+import libcore.util.EmptyArray;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlSerializer;
@@ -249,6 +252,7 @@
 import java.time.ZonedDateTime;
 import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Calendar;
 import java.util.List;
 import java.util.Objects;
@@ -415,7 +419,6 @@
     final Object mNetworkPoliciesSecondLock = new Object();
 
     @GuardedBy("allLocks") volatile boolean mSystemReady;
-    volatile boolean mSystemReallyReady;
 
     @GuardedBy("mUidRulesFirstLock") volatile boolean mRestrictBackground;
     @GuardedBy("mUidRulesFirstLock") volatile boolean mRestrictPower;
@@ -510,7 +513,6 @@
     /** Map from network ID to last observed meteredness state */
     @GuardedBy("mNetworkPoliciesSecondLock")
     private final SparseBooleanArray mNetworkMetered = new SparseBooleanArray();
-
     /** Map from network ID to last observed roaming state */
     @GuardedBy("mNetworkPoliciesSecondLock")
     private final SparseBooleanArray mNetworkRoaming = new SparseBooleanArray();
@@ -519,6 +521,13 @@
     @GuardedBy("mNetworkPoliciesSecondLock")
     private final SparseIntArray mNetIdToSubId = new SparseIntArray();
 
+    /** Map from subId to subscriberId as of last update */
+    @GuardedBy("mNetworkPoliciesSecondLock")
+    private final SparseArray<String> mSubIdToSubscriberId = new SparseArray<>();
+    /** Set of all merged subscriberId as of last update */
+    @GuardedBy("mNetworkPoliciesSecondLock")
+    private String[] mMergedSubscriberIds = EmptyArray.STRING;
+
     /**
      * Indicates the uids restricted by admin from accessing metered data. It's a mapping from
      * userId to restricted uids which belong to that user.
@@ -843,6 +852,16 @@
                     new NetworkRequest.Builder().build(), mNetworkCallback);
 
             mUsageStats.addAppIdleStateChangeListener(new AppIdleStateChangeListener());
+
+            // Listen for subscriber changes
+            mContext.getSystemService(SubscriptionManager.class).addOnSubscriptionsChangedListener(
+                    new OnSubscriptionsChangedListener(mHandler.getLooper()) {
+                        @Override
+                        public void onSubscriptionsChanged() {
+                            updateNetworksInternal();
+                        }
+                    });
+
             // tell systemReady() that the service has been initialized
             initCompleteSignal.countDown();
         } finally {
@@ -868,7 +887,6 @@
             Thread.currentThread().interrupt();
             throw new IllegalStateException("Service " + TAG + " init interrupted", e);
         }
-        mSystemReallyReady = true;
     }
 
     final private IUidObserver mUidObserver = new IUidObserver.Stub() {
@@ -1114,7 +1132,7 @@
         final long now = mClock.millis();
         for (int i = mNetworkPolicy.size()-1; i >= 0; i--) {
             final NetworkPolicy policy = mNetworkPolicy.valueAt(i);
-            final int subId = findRelevantSubId(policy.template);
+            final int subId = findRelevantSubIdNL(policy.template);
 
             // ignore policies that aren't relevant to user
             if (subId == INVALID_SUBSCRIPTION_ID) continue;
@@ -1245,14 +1263,11 @@
      * @return relevant subId, or {@link #INVALID_SUBSCRIPTION_ID} when no
      *         matching subId found.
      */
-    private int findRelevantSubId(NetworkTemplate template) {
-        final TelephonyManager tele = mContext.getSystemService(TelephonyManager.class);
-        final SubscriptionManager sub = mContext.getSystemService(SubscriptionManager.class);
-
+    private int findRelevantSubIdNL(NetworkTemplate template) {
         // Mobile template is relevant when any active subscriber matches
-        final int[] subIds = ArrayUtils.defeatNullable(sub.getActiveSubscriptionIdList());
-        for (int subId : subIds) {
-            final String subscriberId = tele.getSubscriberId(subId);
+        for (int i = 0; i < mSubIdToSubscriberId.size(); i++) {
+            final int subId = mSubIdToSubscriberId.keyAt(i);
+            final String subscriberId = mSubIdToSubscriberId.valueAt(i);
             final NetworkIdentity probeIdent = new NetworkIdentity(TYPE_MOBILE,
                     TelephonyManager.NETWORK_TYPE_UNKNOWN, subscriberId, null, false, true,
                     true);
@@ -1407,23 +1422,29 @@
         public void onReceive(Context context, Intent intent) {
             // on background handler thread, and verified CONNECTIVITY_INTERNAL
             // permission above.
-
-            if (!mSystemReallyReady) return;
-            synchronized (mUidRulesFirstLock) {
-                synchronized (mNetworkPoliciesSecondLock) {
-                    ensureActiveMobilePolicyAL();
-                    normalizePoliciesNL();
-                    updateNetworkEnabledNL();
-                    updateNetworkRulesNL();
-                    updateNotificationsNL();
-                }
-            }
+            updateNetworksInternal();
         }
     };
 
+    private void updateNetworksInternal() {
+        // Get all of our cross-process communication with telephony out of
+        // the way before we acquire internal locks.
+        updateSubscriptions();
+
+        synchronized (mUidRulesFirstLock) {
+            synchronized (mNetworkPoliciesSecondLock) {
+                ensureActiveMobilePolicyAL();
+                normalizePoliciesNL();
+                updateNetworkEnabledNL();
+                updateNetworkRulesNL();
+                updateNotificationsNL();
+            }
+        }
+    }
+
     @VisibleForTesting
     public void updateNetworks() throws InterruptedException {
-        mConnReceiver.onReceive(null, null);
+        updateNetworksInternal();
         final CountDownLatch latch = new CountDownLatch(1);
         mHandler.post(() -> {
             latch.countDown();
@@ -1438,14 +1459,11 @@
      * @param subId that has its associated NetworkPolicy updated if necessary
      * @return if any policies were updated
      */
-    private boolean maybeUpdateMobilePolicyCycleAL(int subId) {
+    private boolean maybeUpdateMobilePolicyCycleAL(int subId, String subscriberId) {
         if (LOGV) Slog.v(TAG, "maybeUpdateMobilePolicyCycleAL()");
 
-        boolean policyUpdated = false;
-        final String subscriberId = mContext.getSystemService(TelephonyManager.class)
-                .getSubscriberId(subId);
-
         // find and update the mobile NetworkPolicy for this subscriber id
+        boolean policyUpdated = false;
         final NetworkIdentity probeIdent = new NetworkIdentity(TYPE_MOBILE,
                 TelephonyManager.NETWORK_TYPE_UNKNOWN, subscriberId, null, false, true, true);
         for (int i = mNetworkPolicy.size() - 1; i >= 0; i--) {
@@ -1568,15 +1586,21 @@
                 return;
             }
             final int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, -1);
-            final TelephonyManager tele = mContext.getSystemService(TelephonyManager.class);
-            final String subscriberId = tele.getSubscriberId(subId);
+
+            // Get all of our cross-process communication with telephony out of
+            // the way before we acquire internal locks.
+            updateSubscriptions();
 
             synchronized (mUidRulesFirstLock) {
                 synchronized (mNetworkPoliciesSecondLock) {
-                    final boolean added = ensureActiveMobilePolicyAL(subId, subscriberId);
-                    if (added) return;
-                    final boolean updated = maybeUpdateMobilePolicyCycleAL(subId);
-                    if (!updated) return;
+                    final String subscriberId = mSubIdToSubscriberId.get(subId, null);
+                    if (subscriberId != null) {
+                        ensureActiveMobilePolicyAL(subId, subscriberId);
+                        maybeUpdateMobilePolicyCycleAL(subId, subscriberId);
+                    } else {
+                        Slog.wtf(TAG, "Missing subscriberId for subId " + subId);
+                    }
+
                     // update network and notification rules, as the data cycle changed and it's
                     // possible that we should be triggering warnings/limits now
                     handleNetworkPoliciesUpdateAL(true);
@@ -1659,20 +1683,29 @@
         if (template.getMatchRule() == MATCH_MOBILE) {
             // If mobile data usage hits the limit or if the user resumes the data, we need to
             // notify telephony.
-            final SubscriptionManager sm = mContext.getSystemService(SubscriptionManager.class);
-            final TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
 
-            final int[] subIds = ArrayUtils.defeatNullable(sm.getActiveSubscriptionIdList());
-            for (int subId : subIds) {
-                final String subscriberId = tm.getSubscriberId(subId);
-                final NetworkIdentity probeIdent = new NetworkIdentity(TYPE_MOBILE,
-                        TelephonyManager.NETWORK_TYPE_UNKNOWN, subscriberId, null, false, true,
-                        true);
-                // Template is matched when subscriber id matches.
-                if (template.matches(probeIdent)) {
-                    tm.setPolicyDataEnabled(enabled, subId);
+            final IntArray matchingSubIds = new IntArray();
+            synchronized (mNetworkPoliciesSecondLock) {
+                for (int i = 0; i < mSubIdToSubscriberId.size(); i++) {
+                    final int subId = mSubIdToSubscriberId.keyAt(i);
+                    final String subscriberId = mSubIdToSubscriberId.valueAt(i);
+
+                    final NetworkIdentity probeIdent = new NetworkIdentity(TYPE_MOBILE,
+                            TelephonyManager.NETWORK_TYPE_UNKNOWN, subscriberId, null, false, true,
+                            true);
+                    // Template is matched when subscriber id matches.
+                    if (template.matches(probeIdent)) {
+                        matchingSubIds.add(subId);
+                    }
                 }
             }
+
+            // Only talk with telephony outside of locks
+            final TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+            for (int i = 0; i < matchingSubIds.size(); i++) {
+                final int subId = matchingSubIds.get(i);
+                tm.setPolicyDataEnabled(enabled, subId);
+            }
         }
     }
 
@@ -1693,6 +1726,46 @@
     }
 
     /**
+     * Examine all currently active subscriptions from
+     * {@link SubscriptionManager#getActiveSubscriptionIdList()} and update
+     * internal data structures.
+     * <p>
+     * Callers <em>must not</em> hold any locks when this method called.
+     */
+    void updateSubscriptions() {
+        if (LOGV) Slog.v(TAG, "updateSubscriptions()");
+        Trace.traceBegin(TRACE_TAG_NETWORK, "updateSubscriptions");
+
+        final TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+        final SubscriptionManager sm = mContext.getSystemService(SubscriptionManager.class);
+
+        final int[] subIds = ArrayUtils.defeatNullable(sm.getActiveSubscriptionIdList());
+        final String[] mergedSubscriberIds = ArrayUtils.defeatNullable(tm.getMergedSubscriberIds());
+
+        final SparseArray<String> subIdToSubscriberId = new SparseArray<>(subIds.length);
+        for (int subId : subIds) {
+            final String subscriberId = tm.getSubscriberId(subId);
+            if (!TextUtils.isEmpty(subscriberId)) {
+                subIdToSubscriberId.put(subId, subscriberId);
+            } else {
+                Slog.wtf(TAG, "Missing subscriberId for subId " + subId);
+            }
+        }
+
+        synchronized (mNetworkPoliciesSecondLock) {
+            mSubIdToSubscriberId.clear();
+            for (int i = 0; i < subIdToSubscriberId.size(); i++) {
+                mSubIdToSubscriberId.put(subIdToSubscriberId.keyAt(i),
+                        subIdToSubscriberId.valueAt(i));
+            }
+
+            mMergedSubscriberIds = mergedSubscriberIds;
+        }
+
+        Trace.traceEnd(TRACE_TAG_NETWORK);
+    }
+
+    /**
      * Examine all connected {@link NetworkState}, looking for
      * {@link NetworkPolicy} that need to be enforced. When matches found, set
      * remaining quota based on usage cycle and historical stats.
@@ -1885,12 +1958,10 @@
         if (LOGV) Slog.v(TAG, "ensureActiveMobilePolicyAL()");
         if (mSuppressDefaultPolicy) return;
 
-        final TelephonyManager tele = mContext.getSystemService(TelephonyManager.class);
-        final SubscriptionManager sub = mContext.getSystemService(SubscriptionManager.class);
+        for (int i = 0; i < mSubIdToSubscriberId.size(); i++) {
+            final int subId = mSubIdToSubscriberId.keyAt(i);
+            final String subscriberId = mSubIdToSubscriberId.valueAt(i);
 
-        final int[] subIds = ArrayUtils.defeatNullable(sub.getActiveSubscriptionIdList());
-        for (int subId : subIds) {
-            final String subscriberId = tele.getSubscriberId(subId);
             ensureActiveMobilePolicyAL(subId, subscriberId);
         }
     }
@@ -2633,14 +2704,11 @@
     }
 
     private void normalizePoliciesNL(NetworkPolicy[] policies) {
-        final TelephonyManager tele = mContext.getSystemService(TelephonyManager.class);
-        final String[] merged = tele.getMergedSubscriberIds();
-
         mNetworkPolicy.clear();
         for (NetworkPolicy policy : policies) {
             // When two normalized templates conflict, prefer the most
             // restrictive policy
-            policy.template = NetworkTemplate.normalize(policy.template, merged);
+            policy.template = NetworkTemplate.normalize(policy.template, mMergedSubscriberIds);
             final NetworkPolicy existing = mNetworkPolicy.get(policy.template);
             if (existing == null || existing.compareTo(policy) > 0) {
                 if (existing != null) {
@@ -3092,10 +3160,14 @@
                     mSubscriptionPlans.put(subId, plans);
                     mSubscriptionPlansOwner.put(subId, callingPackage);
 
-                    final String subscriberId = mContext.getSystemService(TelephonyManager.class)
-                            .getSubscriberId(subId);
-                    ensureActiveMobilePolicyAL(subId, subscriberId);
-                    maybeUpdateMobilePolicyCycleAL(subId);
+                    final String subscriberId = mSubIdToSubscriberId.get(subId, null);
+                    if (subscriberId != null) {
+                        ensureActiveMobilePolicyAL(subId, subscriberId);
+                        maybeUpdateMobilePolicyCycleAL(subId, subscriberId);
+                    } else {
+                        Slog.wtf(TAG, "Missing subscriberId for subId " + subId);
+                    }
+
                     handleNetworkPoliciesUpdateAL(true);
                 }
             }
@@ -3213,6 +3285,21 @@
                 fout.decreaseIndent();
 
                 fout.println();
+                fout.println("Active subscriptions:");
+                fout.increaseIndent();
+                for (int i = 0; i < mSubIdToSubscriberId.size(); i++) {
+                    final int subId = mSubIdToSubscriberId.keyAt(i);
+                    final String subscriberId = mSubIdToSubscriberId.valueAt(i);
+
+                    fout.println(subId + "=" + NetworkIdentity.scrubSubscriberId(subscriberId));
+                }
+                fout.decreaseIndent();
+
+                fout.println();
+                fout.println("Merged subscriptions: "
+                        + Arrays.toString(NetworkIdentity.scrubSubscriberId(mMergedSubscriberIds)));
+
+                fout.println();
                 fout.println("Policy for UIDs:");
                 fout.increaseIndent();
                 int size = mUidPolicy.size();
@@ -4810,6 +4897,14 @@
         }
 
         @Override
+        public SubscriptionPlan getSubscriptionPlan(NetworkTemplate template) {
+            synchronized (mNetworkPoliciesSecondLock) {
+                final int subId = findRelevantSubIdNL(template);
+                return getPrimarySubscriptionPlanLocked(subId);
+            }
+        }
+
+        @Override
         public long getSubscriptionOpportunisticQuota(Network network, int quotaType) {
             final long quotaBytes;
             synchronized (mNetworkPoliciesSecondLock) {
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 7ee17bc..ae7058d 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -116,7 +116,6 @@
 import android.provider.Settings.Global;
 import android.service.NetworkInterfaceProto;
 import android.service.NetworkStatsServiceDumpProto;
-import android.telephony.SubscriptionManager;
 import android.telephony.SubscriptionPlan;
 import android.telephony.TelephonyManager;
 import android.text.format.DateUtils;
@@ -679,22 +678,12 @@
     private SubscriptionPlan resolveSubscriptionPlan(NetworkTemplate template, int flags) {
         SubscriptionPlan plan = null;
         if ((flags & NetworkStatsManager.FLAG_AUGMENT_WITH_SUBSCRIPTION_PLAN) != 0
-                && (template.getMatchRule() == NetworkTemplate.MATCH_MOBILE)
                 && mSettings.getAugmentEnabled()) {
             if (LOGD) Slog.d(TAG, "Resolving plan for " + template);
             final long token = Binder.clearCallingIdentity();
             try {
-                final SubscriptionManager sm = mContext.getSystemService(SubscriptionManager.class);
-                final TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
-                for (int subId : sm.getActiveSubscriptionIdList()) {
-                    if (template.matchesSubscriberId(tm.getSubscriberId(subId))) {
-                        if (LOGD) Slog.d(TAG, "Found active matching subId " + subId);
-                        final List<SubscriptionPlan> plans = sm.getSubscriptionPlans(subId);
-                        if (!plans.isEmpty()) {
-                            plan = plans.get(0);
-                        }
-                    }
-                }
+                plan = LocalServices.getService(NetworkPolicyManagerInternal.class)
+                        .getSubscriptionPlan(template);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 9fce12c..a200231 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2462,7 +2462,7 @@
                 installer, mInstallLock);
         mDexManager = new DexManager(this, mPackageDexOptimizer, installer, mInstallLock,
                 dexManagerListener);
-        mArtManagerService = new ArtManagerService(this, installer, mInstallLock);
+        mArtManagerService = new ArtManagerService(mContext, this, installer, mInstallLock);
         mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper());
 
         mOnPermissionChangeListeners = new OnPermissionChangeListeners(
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index 9c2ad463..1d5c580 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -16,8 +16,9 @@
 
 package com.android.server.pm.dex;
 
-import android.Manifest;
 import android.annotation.UserIdInt;
+import android.app.AppOpsManager;
+import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
@@ -38,7 +39,9 @@
 import android.os.UserHandle;
 import android.system.Os;
 import android.util.ArrayMap;
+import android.util.Log;
 import android.util.Slog;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.ArrayUtils;
@@ -47,14 +50,17 @@
 import com.android.server.pm.Installer;
 import com.android.server.pm.Installer.InstallerException;
 import com.android.server.pm.PackageManagerServiceCompilerMapping;
+
 import dalvik.system.DexFile;
 import dalvik.system.VMRuntime;
-import java.io.File;
-import java.io.FileNotFoundException;
+
 import libcore.io.IoUtils;
 import libcore.util.NonNull;
 import libcore.util.Nullable;
 
+import java.io.File;
+import java.io.FileNotFoundException;
+
 /**
  * A system service that provides access to runtime and compiler artifacts.
  *
@@ -69,9 +75,7 @@
  */
 public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub {
     private static final String TAG = "ArtManagerService";
-
-    private static boolean DEBUG = false;
-    private static boolean DEBUG_IGNORE_PERMISSIONS = false;
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     // Package name used to create the profile directory layout when
     // taking a snapshot of the boot image profile.
@@ -79,6 +83,7 @@
     // Profile name used for the boot image profile.
     private static final String BOOT_IMAGE_PROFILE_NAME = "android.prof";
 
+    private final Context mContext;
     private final IPackageManager mPackageManager;
     private final Object mInstallLock;
     @GuardedBy("mInstallLock")
@@ -90,7 +95,9 @@
         verifyTronLoggingConstants();
     }
 
-    public ArtManagerService(IPackageManager pm, Installer installer, Object installLock) {
+    public ArtManagerService(Context context, IPackageManager pm, Installer installer,
+            Object installLock) {
+        mContext = context;
         mPackageManager = pm;
         mInstaller = installer;
         mInstallLock = installLock;
@@ -99,9 +106,37 @@
         LocalServices.addService(ArtManagerInternal.class, new ArtManagerInternalImpl());
     }
 
+    private boolean checkPermission(int callingUid, String callingPackage) {
+        // Callers always need this permission
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.READ_RUNTIME_PROFILES, TAG);
+
+        // Callers also need the ability to read usage statistics
+        switch (mContext.getSystemService(AppOpsManager.class)
+                .noteOp(AppOpsManager.OP_GET_USAGE_STATS, callingUid, callingPackage)) {
+            case AppOpsManager.MODE_ALLOWED:
+                return true;
+            case AppOpsManager.MODE_DEFAULT:
+                mContext.enforceCallingOrSelfPermission(
+                        android.Manifest.permission.PACKAGE_USAGE_STATS, TAG);
+                return true;
+            default:
+                return false;
+        }
+    }
+
     @Override
     public void snapshotRuntimeProfile(@ProfileType int profileType, @Nullable String packageName,
-            @Nullable String codePath, @NonNull ISnapshotRuntimeProfileCallback callback) {
+            @Nullable String codePath, @NonNull ISnapshotRuntimeProfileCallback callback,
+            String callingPackage) {
+        if (!checkPermission(Binder.getCallingUid(), callingPackage)) {
+            try {
+                callback.onError(ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
+            } catch (RemoteException ignored) {
+            }
+            return;
+        }
+
         // Sanity checks on the arguments.
         Preconditions.checkNotNull(callback);
 
@@ -111,9 +146,8 @@
             Preconditions.checkStringNotEmpty(packageName);
         }
 
-        // Verify that the caller has the right permissions and that the runtime profiling is
-        // enabled. The call to isRuntimePermissions will checkReadRuntimeProfilePermission.
-        if (!isRuntimeProfilingEnabled(profileType)) {
+        // Verify that runtime profiling is enabled.
+        if (!isRuntimeProfilingEnabled(profileType, callingPackage)) {
             throw new IllegalStateException("Runtime profiling is not enabled for " + profileType);
         }
 
@@ -231,9 +265,10 @@
     }
 
     @Override
-    public boolean isRuntimeProfilingEnabled(@ProfileType int profileType) {
-        // Verify that the caller has the right permissions.
-        checkReadRuntimeProfilePermission();
+    public boolean isRuntimeProfilingEnabled(@ProfileType int profileType, String callingPackage) {
+        if (!checkPermission(Binder.getCallingUid(), callingPackage)) {
+            return false;
+        }
 
         switch (profileType) {
             case ArtManager.PROFILE_APPS :
@@ -306,27 +341,6 @@
     }
 
     /**
-     * Verify that the binder calling uid has {@code android.permission.READ_RUNTIME_PROFILE}.
-     * If not, it throws a {@link SecurityException}.
-     */
-    private void checkReadRuntimeProfilePermission() {
-        if (DEBUG_IGNORE_PERMISSIONS) {
-            return;
-        }
-        try {
-            int result = mPackageManager.checkUidPermission(
-                    Manifest.permission.READ_RUNTIME_PROFILES, Binder.getCallingUid());
-            if (result != PackageManager.PERMISSION_GRANTED) {
-                throw new SecurityException("You need "
-                        + Manifest.permission.READ_RUNTIME_PROFILES
-                        + " permission to snapshot profiles.");
-            }
-        } catch (RemoteException e) {
-            // Should not happen.
-        }
-    }
-
-    /**
      * Prepare the application profiles.
      * For all code paths:
      *   - create the current primary profile to save time at app startup time.
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 920e77f..bd4210c 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -6091,6 +6091,14 @@
                 && (!isNavBarVirtKey || mNavBarVirtualKeyHapticFeedbackEnabled)
                 && event.getRepeatCount() == 0;
 
+        // Cancel any pending remote recents animations before handling the button itself. In the
+        // case where we are going home and the recents animation has already started, just cancel
+        // the recents animation, leaving the home stack in place for the pending start activity
+        if (isNavBarVirtKey && !down) {
+            boolean isHomeKey = keyCode == KeyEvent.KEYCODE_HOME;
+            mActivityManagerInternal.cancelRecentsAnimation(!isHomeKey);
+        }
+
         // Handle special keys.
         switch (keyCode) {
             case KeyEvent.KEYCODE_BACK: {
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 608d0aa..68be50c 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -811,36 +811,35 @@
                             return;
                         }
                         mInvalidated = false;
-                        Canvas canvas = null;
-                        try {
-                            // Empty dirty rectangle means unspecified.
-                            if (mDirtyRect.isEmpty()) {
-                                mBounds.getBounds(mDirtyRect);
-                            }
-                            mDirtyRect.inset(- mHalfBorderWidth, - mHalfBorderWidth);
-                            canvas = mSurface.lockCanvas(mDirtyRect);
-                            if (DEBUG_VIEWPORT_WINDOW) {
-                                Slog.i(LOG_TAG, "Dirty rect: " + mDirtyRect);
-                            }
-                        } catch (IllegalArgumentException iae) {
-                            /* ignore */
-                        } catch (Surface.OutOfResourcesException oore) {
-                            /* ignore */
-                        }
-                        if (canvas == null) {
-                            return;
-                        }
-                        if (DEBUG_VIEWPORT_WINDOW) {
-                            Slog.i(LOG_TAG, "Bounds: " + mBounds);
-                        }
-                        canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
-                        mPaint.setAlpha(mAlpha);
-                        Path path = mBounds.getBoundaryPath();
-                        canvas.drawPath(path, mPaint);
-
-                        mSurface.unlockCanvasAndPost(canvas);
-
                         if (mAlpha > 0) {
+                            Canvas canvas = null;
+                            try {
+                                // Empty dirty rectangle means unspecified.
+                                if (mDirtyRect.isEmpty()) {
+                                    mBounds.getBounds(mDirtyRect);
+                                }
+                                mDirtyRect.inset(-mHalfBorderWidth, -mHalfBorderWidth);
+                                canvas = mSurface.lockCanvas(mDirtyRect);
+                                if (DEBUG_VIEWPORT_WINDOW) {
+                                    Slog.i(LOG_TAG, "Dirty rect: " + mDirtyRect);
+                                }
+                            } catch (IllegalArgumentException iae) {
+                                /* ignore */
+                            } catch (Surface.OutOfResourcesException oore) {
+                                /* ignore */
+                            }
+                            if (canvas == null) {
+                                return;
+                            }
+                            if (DEBUG_VIEWPORT_WINDOW) {
+                                Slog.i(LOG_TAG, "Bounds: " + mBounds);
+                            }
+                            canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
+                            mPaint.setAlpha(mAlpha);
+                            Path path = mBounds.getBoundaryPath();
+                            canvas.drawPath(path, mPaint);
+
+                            mSurface.unlockCanvasAndPost(canvas);
                             mSurfaceControl.show();
                         } else {
                             mSurfaceControl.hide();
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 08fa153..79b230d 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -369,7 +369,7 @@
     }
 
     void cancelAnimation(@ReorderMode int reorderMode, String reason) {
-        if (DEBUG_RECENTS_ANIMATIONS) Slog.d(TAG, "cancelAnimation()");
+        if (DEBUG_RECENTS_ANIMATIONS) Slog.d(TAG, "cancelAnimation(): reason=" + reason);
         synchronized (mService.getWindowManagerLock()) {
             if (mCanceled) {
                 // We've already canceled the animation
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index 1b06b2f..67ef471 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -83,7 +83,8 @@
      */
     AnimationAdapter createAnimationAdapter(AppWindowToken appWindowToken, Point position,
             Rect stackBounds) {
-        if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "createAnimationAdapter(): token=" + appWindowToken);
+        if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "createAnimationAdapter(): token="
+                + appWindowToken);
         final RemoteAnimationAdapterWrapper adapter = new RemoteAnimationAdapterWrapper(
                 appWindowToken, position, stackBounds);
         mPendingAnimations.add(adapter);
@@ -96,8 +97,9 @@
     void goodToGo() {
         if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "goodToGo()");
         if (mPendingAnimations.isEmpty() || mCanceled) {
-            if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "goodToGo(): Animation finished before good to go, canceled="
-                    + mCanceled + " mPendingAnimations=" + mPendingAnimations.size());
+            if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "goodToGo(): Animation finished already,"
+                    + " canceled=" + mCanceled
+                    + " mPendingAnimations=" + mPendingAnimations.size());
             onAnimationFinished();
             return;
         }
@@ -123,10 +125,6 @@
             }
             if (DEBUG_REMOTE_ANIMATIONS) {
                 Slog.d(TAG, "startAnimation(): Notify animation start:");
-                for (int i = 0; i < mPendingAnimations.size(); i++) {
-                    Slog.d(TAG, "\t" + mPendingAnimations.get(i).mAppWindowToken);
-                }
-            } else if (DEBUG_APP_TRANSITIONS) {
                 writeStartDebugStatement();
             }
         });
@@ -166,7 +164,8 @@
                 if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\tAdd token=" + wrapper.mAppWindowToken);
                 targets.add(target);
             } else {
-                if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\tRemove token=" + wrapper.mAppWindowToken);
+                if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\tRemove token="
+                        + wrapper.mAppWindowToken);
 
                 // We can't really start an animation but we still need to make sure to finish the
                 // pending animation that was started by SurfaceAnimator
@@ -188,7 +187,8 @@
             releaseFinishedCallback();
             mService.openSurfaceTransaction();
             try {
-                if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "onAnimationFinished(): Notify animation finished:");
+                if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG,
+                        "onAnimationFinished(): Notify animation finished:");
                 for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
                     final RemoteAnimationAdapterWrapper adapter = mPendingAnimations.get(i);
                     adapter.mCapturedFinishCallback.onAnimationFinished(adapter);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 61ce062..6af730f 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1088,6 +1088,8 @@
                             :  Math.max(mFrame.bottom - mStableFrame.bottom, 0));
         }
 
+        mDisplayCutout = displayCutout.calculateRelativeTo(mFrame);
+
         // Offset the actual frame by the amount layout frame is off.
         mFrame.offset(-layoutXDiff, -layoutYDiff);
         mCompatFrame.offset(-layoutXDiff, -layoutYDiff);
@@ -1095,10 +1097,6 @@
         mVisibleFrame.offset(-layoutXDiff, -layoutYDiff);
         mStableFrame.offset(-layoutXDiff, -layoutYDiff);
 
-        // TODO(roosa): Figure out what frame exactly this needs to be calculated with.
-        mDisplayCutout = displayCutout.calculateRelativeTo(mFrame);
-
-
         mCompatFrame.set(mFrame);
         if (mEnforceSizeCompat) {
             // If there is a size compatibility scale being applied to the
diff --git a/services/net/java/android/net/apf/ApfGenerator.java b/services/net/java/android/net/apf/ApfGenerator.java
index fcfb19c..99b2fc6 100644
--- a/services/net/java/android/net/apf/ApfGenerator.java
+++ b/services/net/java/android/net/apf/ApfGenerator.java
@@ -841,30 +841,30 @@
 
     /**
      * Add an instruction to the end of the program to load 32 bits from the data memory into
-     * {@code register}. The source address is computed by adding @{code offset} to the other
-     * register.
+     * {@code register}. The source address is computed by adding the signed immediate
+     * @{code offset} to the other register.
      * Requires APF v3 or greater.
      */
     public ApfGenerator addLoadData(Register destinationRegister, int offset)
             throws IllegalInstructionException {
         requireApfVersion(3);
         Instruction instruction = new Instruction(Opcodes.LDDW, destinationRegister);
-        instruction.setUnsignedImm(offset);
+        instruction.setSignedImm(offset);
         addInstruction(instruction);
         return this;
     }
 
     /**
      * Add an instruction to the end of the program to store 32 bits from {@code register} into the
-     * data memory. The destination address is computed by adding @{code offset} to the other
-     * register.
+     * data memory. The destination address is computed by adding the signed immediate
+     * @{code offset} to the other register.
      * Requires APF v3 or greater.
      */
     public ApfGenerator addStoreData(Register sourceRegister, int offset)
             throws IllegalInstructionException {
         requireApfVersion(3);
         Instruction instruction = new Instruction(Opcodes.STDW, sourceRegister);
-        instruction.setUnsignedImm(offset);
+        instruction.setSignedImm(offset);
         addInstruction(instruction);
         return this;
     }
diff --git a/services/net/java/android/net/ip/IpClient.java b/services/net/java/android/net/ip/IpClient.java
index 87249df..9fdb31e 100644
--- a/services/net/java/android/net/ip/IpClient.java
+++ b/services/net/java/android/net/ip/IpClient.java
@@ -16,6 +16,7 @@
 
 package android.net.ip;
 
+import com.android.internal.util.HexDump;
 import com.android.internal.util.MessageUtils;
 import com.android.internal.util.WakeupMessage;
 
@@ -142,6 +143,12 @@
         // Install an APF program to filter incoming packets.
         public void installPacketFilter(byte[] filter) {}
 
+        // Asynchronously read back the APF program & data buffer from the wifi driver.
+        // Due to Wifi HAL limitations, the current implementation only supports dumping the entire
+        // buffer. In response to this request, the driver returns the data buffer asynchronously
+        // by sending an IpClient#EVENT_READ_PACKET_FILTER_COMPLETE message.
+        public void startReadPacketFilter() {}
+
         // If multicast filtering cannot be accomplished with APF, this function will be called to
         // actuate multicast filtering using another means.
         public void setFallbackMulticastFilter(boolean enabled) {}
@@ -248,6 +255,11 @@
             log("installPacketFilter(byte[" + filter.length + "])");
         }
         @Override
+        public void startReadPacketFilter() {
+            mCallback.startReadPacketFilter();
+            log("startReadPacketFilter()");
+        }
+        @Override
         public void setFallbackMulticastFilter(boolean enabled) {
             mCallback.setFallbackMulticastFilter(enabled);
             log("setFallbackMulticastFilter(" + enabled + ")");
@@ -559,6 +571,7 @@
     private static final int CMD_SET_MULTICAST_FILTER             = 9;
     private static final int EVENT_PROVISIONING_TIMEOUT           = 10;
     private static final int EVENT_DHCPACTION_TIMEOUT             = 11;
+    private static final int EVENT_READ_PACKET_FILTER_COMPLETE    = 12;
 
     private static final int MAX_LOG_RECORDS = 500;
     private static final int MAX_PACKET_RECORDS = 100;
@@ -611,6 +624,7 @@
     private ApfFilter mApfFilter;
     private boolean mMulticastFiltering;
     private long mStartTimeMillis;
+    private byte[] mApfDataSnapshot;
 
     public static class Dependencies {
         public INetworkManagementService getNMS() {
@@ -823,6 +837,10 @@
         sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
     }
 
+    public void readPacketFilterComplete(byte[] data) {
+        sendMessage(EVENT_READ_PACKET_FILTER_COMPLETE, data);
+    }
+
     /**
      * Set the TCP buffer sizes to use.
      *
@@ -863,6 +881,7 @@
         final ProvisioningConfiguration provisioningConfig = mConfiguration;
         final ApfCapabilities apfCapabilities = (provisioningConfig != null)
                 ? provisioningConfig.mApfCapabilities : null;
+        final byte[] apfDataSnapshot = mApfDataSnapshot;
 
         IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
         pw.println(mTag + " APF dump:");
@@ -880,6 +899,14 @@
             }
         }
         pw.decreaseIndent();
+        pw.println(mTag + " latest APF data snapshot: ");
+        pw.increaseIndent();
+        if (apfDataSnapshot != null) {
+            pw.println(HexDump.dumpHexString(apfDataSnapshot));
+        } else {
+            pw.println("No last snapshot.");
+        }
+        pw.decreaseIndent();
 
         pw.println();
         pw.println(mTag + " current ProvisioningConfiguration:");
@@ -1676,6 +1703,11 @@
                     break;
                 }
 
+                case EVENT_READ_PACKET_FILTER_COMPLETE: {
+                    mApfDataSnapshot = (byte[]) msg.obj;
+                    break;
+                }
+
                 case EVENT_DHCPACTION_TIMEOUT:
                     stopDhcpAction();
                     break;
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
index 4638635..5a56332 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
@@ -441,6 +441,28 @@
         assertEquals(w.mDisplayCutout.getDisplayCutout().getSafeInsetRight(), 0);
     }
 
+    @Test
+    public void testDisplayCutout_tempInsetBounds() {
+        // Regular fullscreen task and window
+        TaskWithBounds task = new TaskWithBounds(new Rect(0, -500, 1000, 1500));
+        task.mFullscreenForTest = false;
+        task.mInsetBounds.set(0, 0, 1000, 2000);
+        WindowState w = createWindow(task, FILL_PARENT, FILL_PARENT);
+        w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP;
+
+        final Rect pf = new Rect(0, -500, 1000, 1500);
+        // Create a display cutout of size 50x50, aligned top-center
+        final WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+                fromBoundingRect(500, 0, 550, 50), pf.width(), pf.height());
+
+        w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf, cutout, false);
+
+        assertEquals(w.mDisplayCutout.getDisplayCutout().getSafeInsetTop(), 50);
+        assertEquals(w.mDisplayCutout.getDisplayCutout().getSafeInsetBottom(), 0);
+        assertEquals(w.mDisplayCutout.getDisplayCutout().getSafeInsetLeft(), 0);
+        assertEquals(w.mDisplayCutout.getDisplayCutout().getSafeInsetRight(), 0);
+    }
+
     private WindowStateWithTask createWindow(Task task, int width, int height) {
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_APPLICATION);
         attrs.width = width;
diff --git a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
index dd56e0e..4ca175f 100644
--- a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
+++ b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
@@ -98,7 +98,7 @@
     private static final String LAUNCH_FILE = "applaunch.txt";
     private static final String TRACE_SUB_DIRECTORY = "atrace_logs";
     private static final String DEFAULT_TRACE_CATEGORIES =
-            "sched,freq,gfx,view,dalvik,webview,input,wm,disk,am,wm";
+            "sched,freq,gfx,view,dalvik,webview,input,wm,disk,am,wm,binder_driver,hal";
     private static final String DEFAULT_TRACE_BUFFER_SIZE = "20000";
     private static final String DEFAULT_TRACE_DUMP_INTERVAL = "10";
     private static final String TRIAL_LAUNCH = "TRIAL_LAUNCH";
@@ -310,7 +310,8 @@
                     try {
                         atraceLogger.atraceStart(traceCategoriesSet, traceBufferSize,
                                 traceDumpInterval, rootTraceSubDir,
-                                String.format("%s-%s", launch.getApp(), launch.getLaunchReason()));
+                                String.format("%s-%s-%s", launch.getApp(),
+                                        launch.getCompilerFilter(), launch.getLaunchReason()));
                         startApp(launch.getApp(), launch.getLaunchReason());
                         sleep(POST_LAUNCH_IDLE_TIMEOUT);
                     } finally {
diff --git a/tests/net/java/android/net/LinkPropertiesTest.java b/tests/net/java/android/net/LinkPropertiesTest.java
index f3c22a5..9695e9a 100644
--- a/tests/net/java/android/net/LinkPropertiesTest.java
+++ b/tests/net/java/android/net/LinkPropertiesTest.java
@@ -27,6 +27,7 @@
 import android.net.LinkProperties.CompareResult;
 import android.net.LinkProperties.ProvisioningChange;
 import android.net.RouteInfo;
+import android.os.Parcel;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.system.OsConstants;
@@ -82,6 +83,9 @@
         assertTrue(source.isIdenticalPrivateDns(target));
         assertTrue(target.isIdenticalPrivateDns(source));
 
+        assertTrue(source.isIdenticalValidatedPrivateDnses(target));
+        assertTrue(target.isIdenticalValidatedPrivateDnses(source));
+
         assertTrue(source.isIdenticalRoutes(target));
         assertTrue(target.isIdenticalRoutes(source));
 
@@ -784,4 +788,35 @@
         assertEquals(new ArraySet<>(expectAdded), new ArraySet<>(result.added));
         assertEquals(new ArraySet<>(expectRemoved), (new ArraySet<>(result.removed)));
     }
+
+    @Test
+    public void testLinkPropertiesParcelable() {
+        LinkProperties source = new LinkProperties();
+        source.setInterfaceName(NAME);
+        // set 2 link addresses
+        source.addLinkAddress(LINKADDRV4);
+        source.addLinkAddress(LINKADDRV6);
+        // set 2 dnses
+        source.addDnsServer(DNS1);
+        source.addDnsServer(DNS2);
+        // set 2 gateways
+        source.addRoute(new RouteInfo(GATEWAY1));
+        source.addRoute(new RouteInfo(GATEWAY2));
+        // set 2 validated private dnses
+        source.addValidatedPrivateDnsServer(DNS6);
+        source.addValidatedPrivateDnsServer(GATEWAY61);
+
+        source.setMtu(MTU);
+
+        Parcel p = Parcel.obtain();
+        source.writeToParcel(p, /* flags */ 0);
+        p.setDataPosition(0);
+        final byte[] marshalled = p.marshall();
+        p = Parcel.obtain();
+        p.unmarshall(marshalled, 0, marshalled.length);
+        p.setDataPosition(0);
+        LinkProperties dest = LinkProperties.CREATOR.createFromParcel(p);
+
+        assertEquals(source, dest);
+    }
 }
diff --git a/tests/net/java/android/net/apf/ApfTest.java b/tests/net/java/android/net/apf/ApfTest.java
index 082f310..f8a4132 100644
--- a/tests/net/java/android/net/apf/ApfTest.java
+++ b/tests/net/java/android/net/apf/ApfTest.java
@@ -160,7 +160,7 @@
             throw new Exception(
                     "program: " + HexDump.toHexString(program) +
                     "\ndata memory: " + HexDump.toHexString(data) +
-                    "\nexpected: " + HexDump.toHexString(expected_data));
+                    "\nexpected:    " + HexDump.toHexString(expected_data));
         }
     }
 
@@ -625,18 +625,19 @@
     @Test
     public void testApfDataWrite() throws IllegalInstructionException, Exception {
         byte[] packet = new byte[MIN_PKT_SIZE];
-        byte[] data = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
+        byte[] data = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
         byte[] expected_data = data.clone();
 
         // No memory access instructions: should leave the data segment untouched.
         ApfGenerator gen = new ApfGenerator(3);
         assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data);
 
-        // Expect value 0x87654321 to be stored starting from address 3 + 2, in big-endian order.
+        // Expect value 0x87654321 to be stored starting from address -11 from the end of the
+        // data buffer, in big-endian order.
         gen = new ApfGenerator(3);
         gen.addLoadImmediate(Register.R0, 0x87654321);
-        gen.addLoadImmediate(Register.R1, 2);
-        gen.addStoreData(Register.R0, 3);
+        gen.addLoadImmediate(Register.R1, -5);
+        gen.addStoreData(Register.R0, -6);  // -5 + -6 = -11 (offset +5 with data_len=16)
         expected_data[5] = (byte)0x87;
         expected_data[6] = (byte)0x65;
         expected_data[7] = (byte)0x43;
@@ -646,16 +647,16 @@
 
     @Test
     public void testApfDataRead() throws IllegalInstructionException, Exception {
-        // Program that DROPs if address 11 (7 + 3) contains 0x87654321.
+        // Program that DROPs if address 10 (-6) contains 0x87654321.
         ApfGenerator gen = new ApfGenerator(3);
-        gen.addLoadImmediate(Register.R1, 3);
-        gen.addLoadData(Register.R0, 7);
+        gen.addLoadImmediate(Register.R1, 10);
+        gen.addLoadData(Register.R0, -16);  // 10 + -16 = -6 (offset +10 with data_len=16)
         gen.addJumpIfR0Equals(0x87654321, gen.DROP_LABEL);
         byte[] program = gen.generate();
         byte[] packet = new byte[MIN_PKT_SIZE];
 
         // Content is incorrect (last byte does not match) -> PASS
-        byte[] data = new byte[32];
+        byte[] data = new byte[16];
         data[10] = (byte)0x87;
         data[11] = (byte)0x65;
         data[12] = (byte)0x43;
@@ -672,10 +673,10 @@
     @Test
     public void testApfDataReadModifyWrite() throws IllegalInstructionException, Exception {
         ApfGenerator gen = new ApfGenerator(3);
-        gen.addLoadImmediate(Register.R1, 3);
-        gen.addLoadData(Register.R0, 7);  // Load from address 7 + 3 = 10
+        gen.addLoadImmediate(Register.R1, -22);
+        gen.addLoadData(Register.R0, 0);  // Load from address 32 -22 + 0 = 10
         gen.addAdd(0x78453412);  // 87654321 + 78453412 = FFAA7733
-        gen.addStoreData(Register.R0, 11);  // Write back to address 11 + 3 = 14
+        gen.addStoreData(Register.R0, 4);  // Write back to address 32 -22 + 4 = 14
 
         byte[] packet = new byte[MIN_PKT_SIZE];
         byte[] data = new byte[32];
@@ -718,10 +719,17 @@
         gen.addJump(gen.DROP_LABEL);
         assertDataMemoryContents(DROP, gen.generate(), packet, data, expected_data);
 
-        // ...but underflowing isn't allowed.
+        // ...and underflowing simply wraps around to the end of the buffer...
         gen = new ApfGenerator(3);
         gen.addLoadImmediate(Register.R0, 20);
         gen.addLoadData(Register.R1, -30);
+        gen.addJump(gen.DROP_LABEL);
+        assertDataMemoryContents(DROP, gen.generate(), packet, data, expected_data);
+
+        // ...but doesn't allow accesses before the start of the buffer
+        gen = new ApfGenerator(3);
+        gen.addLoadImmediate(Register.R0, 20);
+        gen.addLoadData(Register.R1, -1000);
         gen.addJump(gen.DROP_LABEL);  // Not reached.
         assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data);
     }
diff --git a/tests/net/jni/apf_jni.cpp b/tests/net/jni/apf_jni.cpp
index 79444e3..1ea9e27 100644
--- a/tests/net/jni/apf_jni.cpp
+++ b/tests/net/jni/apf_jni.cpp
@@ -36,11 +36,20 @@
     uint32_t program_len = env->GetArrayLength(program);
     uint32_t packet_len = env->GetArrayLength(packet);
     uint32_t data_len = data ? env->GetArrayLength(data) : 0;
-    jint result = accept_packet(program_raw, program_len, packet_raw,
-                                packet_len, data_raw, data_len, filter_age);
+
+    // Merge program and data into a single buffer.
+    uint8_t* program_and_data = (uint8_t*)malloc(program_len + data_len);
+    memcpy(program_and_data, program_raw, program_len);
+    memcpy(program_and_data + program_len, data_raw, data_len);
+
+    jint result =
+        accept_packet(program_and_data, program_len, program_len + data_len,
+                      packet_raw, packet_len, filter_age);
     if (data) {
+        memcpy(data_raw, program_and_data + program_len, data_len);
         env->ReleaseByteArrayElements(data, (jbyte*)data_raw, 0 /* copy back */);
     }
+    free(program_and_data);
     env->ReleaseByteArrayElements(packet, (jbyte*)packet_raw, JNI_ABORT);
     env->ReleaseByteArrayElements(program, (jbyte*)program_raw, JNI_ABORT);
     return result;
@@ -109,8 +118,8 @@
         jstring jpcap_filename, jbyteArray japf_program) {
     ScopedUtfChars filter(env, jfilter);
     ScopedUtfChars pcap_filename(env, jpcap_filename);
-    const uint8_t* apf_program = (uint8_t*)env->GetByteArrayElements(japf_program, NULL);
-    const uint32_t apf_program_len = env->GetArrayLength(japf_program);
+    uint8_t* apf_program = (uint8_t*)env->GetByteArrayElements(japf_program, NULL);
+    uint32_t apf_program_len = env->GetArrayLength(japf_program);
 
     // Open pcap file for BPF filtering
     ScopedFILE bpf_fp(fopen(pcap_filename.c_str(), "rb"));
@@ -152,8 +161,8 @@
         do {
             apf_packet = pcap_next(apf_pcap.get(), &apf_header);
         } while (apf_packet != NULL && !accept_packet(
-                apf_program, apf_program_len, apf_packet, apf_header.len,
-                nullptr, 0, 0));
+                apf_program, apf_program_len, 0 /* data_len */,
+                apf_packet, apf_header.len, 0 /* filter_age */));
 
         // Make sure both filters matched the same packet.
         if (apf_packet == NULL && bpf_packet == NULL)