Merge "Listen TRANSPORT_BLUETOOTH network change in GnssLocationProvider"
diff --git a/api/current.txt b/api/current.txt
index 49d6b47..0dc0c31 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -49217,7 +49217,6 @@
     method public void goBack();
     method public void goBackOrForward(int);
     method public void goForward();
-    method public static void initSafeBrowsing(android.content.Context, android.webkit.ValueCallback<java.lang.Boolean>);
     method public void invokeZoomPicker();
     method public boolean isPrivateBrowsingEnabled();
     method public void loadData(java.lang.String, java.lang.String, java.lang.String);
@@ -49265,7 +49264,7 @@
     method public void setWebViewClient(android.webkit.WebViewClient);
     method public deprecated boolean shouldDelayChildPressedState();
     method public deprecated boolean showFindDialog(java.lang.String, boolean);
-    method public static void shutdownSafeBrowsing();
+    method public static void startSafeBrowsing(android.content.Context, android.webkit.ValueCallback<java.lang.Boolean>);
     method public void stopLoading();
     method public void zoomBy(float);
     method public boolean zoomIn();
diff --git a/api/system-current.txt b/api/system-current.txt
index e56be3c..bde877e 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -52921,7 +52921,6 @@
     method public void goBack();
     method public void goBackOrForward(int);
     method public void goForward();
-    method public static void initSafeBrowsing(android.content.Context, android.webkit.ValueCallback<java.lang.Boolean>);
     method public void invokeZoomPicker();
     method public boolean isPrivateBrowsingEnabled();
     method public void loadData(java.lang.String, java.lang.String, java.lang.String);
@@ -52969,7 +52968,7 @@
     method public void setWebViewClient(android.webkit.WebViewClient);
     method public deprecated boolean shouldDelayChildPressedState();
     method public deprecated boolean showFindDialog(java.lang.String, boolean);
-    method public static void shutdownSafeBrowsing();
+    method public static void startSafeBrowsing(android.content.Context, android.webkit.ValueCallback<java.lang.Boolean>);
     method public void stopLoading();
     method public void zoomBy(float);
     method public boolean zoomIn();
@@ -53166,7 +53165,6 @@
     method public abstract android.net.Uri[] parseFileChooserResult(int, android.content.Intent);
     method public abstract void setSafeBrowsingWhitelist(java.util.List<java.lang.String>, android.webkit.ValueCallback<java.lang.Boolean>);
     method public abstract void setWebContentsDebuggingEnabled(boolean);
-    method public abstract void shutdownSafeBrowsing();
   }
 
   public class WebViewFragment extends android.app.Fragment {
diff --git a/api/test-current.txt b/api/test-current.txt
index abd678d..2f7dc5cc 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -49682,7 +49682,6 @@
     method public void goBack();
     method public void goBackOrForward(int);
     method public void goForward();
-    method public static void initSafeBrowsing(android.content.Context, android.webkit.ValueCallback<java.lang.Boolean>);
     method public void invokeZoomPicker();
     method public boolean isPrivateBrowsingEnabled();
     method public void loadData(java.lang.String, java.lang.String, java.lang.String);
@@ -49730,7 +49729,7 @@
     method public void setWebViewClient(android.webkit.WebViewClient);
     method public deprecated boolean shouldDelayChildPressedState();
     method public deprecated boolean showFindDialog(java.lang.String, boolean);
-    method public static void shutdownSafeBrowsing();
+    method public static void startSafeBrowsing(android.content.Context, android.webkit.ValueCallback<java.lang.Boolean>);
     method public void stopLoading();
     method public void zoomBy(float);
     method public boolean zoomIn();
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 586f13f..35b2012 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -6951,6 +6951,7 @@
                 customContent = customContent.clone();
                 remoteViews.removeAllViewsExceptId(R.id.notification_main_column, R.id.progress);
                 remoteViews.addView(R.id.notification_main_column, customContent, 0 /* index */);
+                remoteViews.setReapplyDisallowed();
             }
             // also update the end margin if there is an image
             int endMargin = R.dimen.notification_content_margin_end;
@@ -7057,6 +7058,7 @@
                 customContent = customContent.clone();
                 remoteViews.removeAllViews(id);
                 remoteViews.addView(id, customContent);
+                remoteViews.setReapplyDisallowed();
             }
             return remoteViews;
         }
diff --git a/core/java/android/net/ScoredNetwork.java b/core/java/android/net/ScoredNetwork.java
index 666da0a..e38d227 100644
--- a/core/java/android/net/ScoredNetwork.java
+++ b/core/java/android/net/ScoredNetwork.java
@@ -16,18 +16,14 @@
 
 package android.net;
 
-import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 
-import java.lang.Math;
-import java.lang.UnsupportedOperationException;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * A network identifier along with a score for the quality of that network.
@@ -195,7 +191,28 @@
         return Objects.equals(networkKey, that.networkKey)
                 && Objects.equals(rssiCurve, that.rssiCurve)
                 && Objects.equals(meteredHint, that.meteredHint)
-                && Objects.equals(attributes, that.attributes);
+                && bundleEquals(attributes, that.attributes);
+    }
+
+    private boolean bundleEquals(Bundle bundle1, Bundle bundle2) {
+        if (bundle1 == bundle2) {
+            return true;
+        }
+        if (bundle1 == null || bundle2 == null) {
+            return false;
+        }
+        if (bundle1.size() != bundle2.size()) {
+            return false;
+        }
+        Set<String> keys = bundle1.keySet();
+        for (String key : keys) {
+            Object value1 = bundle1.get(key);
+            Object value2 = bundle2.get(key);
+            if (!Objects.equals(value1, value2)) {
+                return false;
+            }
+        }
+        return true;
     }
 
     @Override
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 049f1ef..4b98e35 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -1628,42 +1628,28 @@
     }
 
     /**
-     * Starts Safe Browsing initialization. This should only be called once.
-     *
+     * Starts Safe Browsing initialization.
      * <p>
-     * Because the Safe Browsing feature takes time to initialize, WebView may temporarily whitelist
-     * URLs until the feature is ready. The callback will be invoked with true once initialization
-     * is complete.
-     * </p>
-     *
+     * URL loads are not guaranteed to be protected by Safe Browsing until after {@code callback} is
+     * invoked with {@code true}. Safe Browsing is not fully supported on all devices. For those
+     * devices {@code callback} will receive {@code false}.
      * <p>
-     * This does not enable the Safe Browsing feature itself, and should only be used if the feature
-     * is otherwise enabled.
-     * </p>
-     *
+     * This does not enable the Safe Browsing feature itself, and should only be called if Safe
+     * Browsing is enabled by the manifest tag or {@link WebSettings#setSafeBrowsingEnabled}. This
+     * prepares resources used for Safe Browsing.
      * <p>
-     * This does not require an Activity Context, and will always use the application Context to do
-     * its work.
-     * </p>
+     * This should be called with the Application Context (and will always use the Application
+     * context to do its work regardless).
      *
      * @param context Application Context.
-     * @param callback will be called with the value true if initialization is
-     * successful. The callback will be run on the UI thread.
+     * @param callback will be called on the UI thread with {@code true} if initialization is
+     * successful, {@code false} otherwise.
      */
-    public static void initSafeBrowsing(Context context, ValueCallback<Boolean> callback) {
+    public static void startSafeBrowsing(Context context, ValueCallback<Boolean> callback) {
         getFactory().getStatics().initSafeBrowsing(context, callback);
     }
 
     /**
-     * Shuts down Safe Browsing. This should only be called once. This does not disable the feature,
-     * it only frees resources used by Safe Browsing code. To disable Safe Browsing on an individual
-     * WebView, see {@link WebSettings#setSafeBrowsingEnabled}
-     */
-    public static void shutdownSafeBrowsing() {
-        getFactory().getStatics().shutdownSafeBrowsing();
-    }
-
-    /**
      * Sets the list of domains that are exempt from SafeBrowsing checks. The list is
      * global for all the WebViews.
      * <p>
diff --git a/core/java/android/webkit/WebViewFactoryProvider.java b/core/java/android/webkit/WebViewFactoryProvider.java
index 4f6513f..4c47abc 100644
--- a/core/java/android/webkit/WebViewFactoryProvider.java
+++ b/core/java/android/webkit/WebViewFactoryProvider.java
@@ -80,17 +80,11 @@
 
         /**
          * Implement the API method
-         * {@link android.webkit.WebView#initSafeBrowsing(Context , ValueCallback<Boolean>)}
+         * {@link android.webkit.WebView#startSafeBrowsing(Context , ValueCallback<Boolean>)}
          */
         void initSafeBrowsing(Context context, ValueCallback<Boolean> callback);
 
         /**
-         * Implement the API method
-         * {@link android.webkit.WebView#shutdownSafeBrowsing()}
-         */
-        void shutdownSafeBrowsing();
-
-        /**
         * Implement the API method
         * {@link android.webkit.WebView#setSafeBrowsingWhitelist(List<String>,
         * ValueCallback<Boolean>)}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index b77aa1c..5c63c75 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -154,6 +154,12 @@
     private boolean mIsRoot = true;
 
     /**
+     * Whether reapply is disallowed on this remoteview. This maybe be true if some actions modify
+     * the layout in a way that isn't recoverable, since views are being removed.
+     */
+    private boolean mReapplyDisallowed;
+
+    /**
      * Constants to whether or not this RemoteViews is composed of a landscape and portrait
      * RemoteViews.
      */
@@ -215,6 +221,26 @@
     }
 
     /**
+     * Set that it is disallowed to reapply another remoteview with the same layout as this view.
+     * This should be done if an action is destroying the view tree of the base layout.
+     *
+     * @hide
+     */
+    public void setReapplyDisallowed() {
+        mReapplyDisallowed = true;
+    }
+
+    /**
+     * @return Whether it is disallowed to reapply another remoteview with the same layout as this
+     * view. True if this remoteview has actions that destroyed view tree of the base layout.
+     *
+     * @hide
+     */
+    public boolean isReapplyDisallowed() {
+        return mReapplyDisallowed;
+    }
+
+    /**
      * Handle with care!
      */
     static class MutablePair<F, S> {
@@ -2429,6 +2455,7 @@
             mApplication = mPortrait.mApplication;
             mLayoutId = mPortrait.getLayoutId();
         }
+        mReapplyDisallowed = parcel.readInt() == 0;
 
         // setup the memory usage statistics
         mMemoryUsageCounter = new MemoryUsageCounter();
@@ -3738,6 +3765,7 @@
             // Both RemoteViews already share the same package and user
             mPortrait.writeToParcel(dest, flags | PARCELABLE_ELIDE_DUPLICATES);
         }
+        dest.writeInt(mReapplyDisallowed ? 1 : 0);
     }
 
     private static ApplicationInfo getApplicationInfo(String packageName, int userId) {
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index f99637d..42e9273 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -76,6 +76,9 @@
         case SkEncodedImageFormat::kWEBP:
             mimeType = "image/webp";
             break;
+        case SkEncodedImageFormat::kHEIF:
+            mimeType = "image/heif";
+            break;
         case SkEncodedImageFormat::kWBMP:
             mimeType = "image/vnd.wap.wbmp";
             break;
diff --git a/core/tests/coretests/src/android/net/ScoredNetworkTest.java b/core/tests/coretests/src/android/net/ScoredNetworkTest.java
index dc17da2..109f32e 100644
--- a/core/tests/coretests/src/android/net/ScoredNetworkTest.java
+++ b/core/tests/coretests/src/android/net/ScoredNetworkTest.java
@@ -49,6 +49,55 @@
         = new NetworkKey(new WifiKey("\"ssid\"", "00:00:00:00:00:00"));
 
     @Test
+    public void scoredNetworksWithBothNullAttributeBundle_equal() {
+        ScoredNetwork scoredNetwork1 =
+                new ScoredNetwork(KEY, CURVE, false /* meteredHint */, null /* attributes */);
+        ScoredNetwork scoredNetwork2 =
+                new ScoredNetwork(KEY, CURVE, false /* meteredHint */, null /* attributes */);
+        assertTrue(scoredNetwork1.equals(scoredNetwork2));
+    }
+
+    @Test
+    public void scoredNetworksWithOneNullAttributeBundle_notEqual() {
+        ScoredNetwork scoredNetwork1 =
+                new ScoredNetwork(KEY, CURVE, false /* meteredHint */, ATTRIBUTES);
+        ScoredNetwork scoredNetwork2 =
+                new ScoredNetwork(KEY, CURVE, false /* meteredHint */, null /* attributes */);
+        assertFalse(scoredNetwork1.equals(scoredNetwork2));
+    }
+
+    @Test
+    public void scoredNetworksWithDifferentSizedAttributeBundle_notEqual() {
+        ScoredNetwork scoredNetwork1 =
+                new ScoredNetwork(KEY, CURVE, false /* meteredHint */, ATTRIBUTES);
+        Bundle attr = new Bundle(ATTRIBUTES);
+        attr.putBoolean(ScoredNetwork.ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL, true);
+        ScoredNetwork scoredNetwork2 =
+                new ScoredNetwork(KEY, CURVE, false /* meteredHint */, attr);
+        assertFalse(scoredNetwork1.equals(scoredNetwork2));
+    }
+
+    @Test
+    public void scoredNetworksWithDifferentAttributeValues_notEqual() {
+        ScoredNetwork scoredNetwork1 =
+                new ScoredNetwork(KEY, CURVE, false /* meteredHint */, ATTRIBUTES);
+        Bundle attr = new Bundle();
+        attr.putInt(ScoredNetwork.ATTRIBUTES_KEY_RANKING_SCORE_OFFSET, Integer.MIN_VALUE);
+        ScoredNetwork scoredNetwork2 =
+                new ScoredNetwork(KEY, CURVE, false /* meteredHint */, attr);
+        assertFalse(scoredNetwork1.equals(scoredNetwork2));
+    }
+
+    @Test
+    public void scoredNetworksWithSameAttributeValuesAndSize_equal() {
+        ScoredNetwork scoredNetwork1 =
+                new ScoredNetwork(KEY, CURVE, false /* meteredHint */, ATTRIBUTES);
+        ScoredNetwork scoredNetwork2 =
+                new ScoredNetwork(KEY, CURVE, false /* meteredHint */, ATTRIBUTES);
+        assertTrue(scoredNetwork1.equals(scoredNetwork2));
+    }
+
+    @Test
     public void calculateRankingOffsetShouldThrowUnsupportedOperationException() {
         // No curve or ranking score offset set in curve
         ScoredNetwork scoredNetwork = new ScoredNetwork(KEY, null);
diff --git a/media/java/android/media/MediaFile.java b/media/java/android/media/MediaFile.java
index 2f48ffb..fc4d15f 100644
--- a/media/java/android/media/MediaFile.java
+++ b/media/java/android/media/MediaFile.java
@@ -80,8 +80,9 @@
     public static final int FILE_TYPE_BMP     = 34;
     public static final int FILE_TYPE_WBMP    = 35;
     public static final int FILE_TYPE_WEBP    = 36;
+    public static final int FILE_TYPE_HEIF    = 37;
     private static final int FIRST_IMAGE_FILE_TYPE = FILE_TYPE_JPEG;
-    private static final int LAST_IMAGE_FILE_TYPE = FILE_TYPE_WEBP;
+    private static final int LAST_IMAGE_FILE_TYPE = FILE_TYPE_HEIF;
 
     // Raw image file types
     public static final int FILE_TYPE_DNG     = 300;
@@ -239,6 +240,8 @@
         addFileType("BMP", FILE_TYPE_BMP, "image/x-ms-bmp", MtpConstants.FORMAT_BMP, true);
         addFileType("WBMP", FILE_TYPE_WBMP, "image/vnd.wap.wbmp", MtpConstants.FORMAT_DEFINED, false);
         addFileType("WEBP", FILE_TYPE_WEBP, "image/webp", MtpConstants.FORMAT_DEFINED, false);
+        addFileType("HEIC", FILE_TYPE_HEIF, "image/heif", MtpConstants.FORMAT_HEIF, true);
+        addFileType("HEIF", FILE_TYPE_HEIF, "image/heif", MtpConstants.FORMAT_HEIF, false);
 
         addFileType("DNG", FILE_TYPE_DNG, "image/x-adobe-dng", MtpConstants.FORMAT_DNG, true);
         addFileType("CR2", FILE_TYPE_CR2, "image/x-canon-cr2", MtpConstants.FORMAT_TIFF, false);
diff --git a/media/java/android/mtp/MtpConstants.java b/media/java/android/mtp/MtpConstants.java
index 7d078d7..88e287e 100644
--- a/media/java/android/mtp/MtpConstants.java
+++ b/media/java/android/mtp/MtpConstants.java
@@ -211,6 +211,8 @@
     public static final int FORMAT_JPX = 0x3810;
     /** Format code for DNG files */
     public static final int FORMAT_DNG = 0x3811;
+    /** Format code for HEIF files {@hide} */
+    public static final int FORMAT_HEIF = 0x3812;
     /** Format code for firmware files */
     public static final int FORMAT_UNDEFINED_FIRMWARE = 0xB802;
     /** Format code for Windows image files */
diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java
index 698c9c9..9847d705 100755
--- a/media/java/android/mtp/MtpDatabase.java
+++ b/media/java/android/mtp/MtpDatabase.java
@@ -595,6 +595,7 @@
             MtpConstants.FORMAT_XML_DOCUMENT,
             MtpConstants.FORMAT_FLAC,
             MtpConstants.FORMAT_DNG,
+            MtpConstants.FORMAT_HEIF,
         };
     }
 
@@ -705,6 +706,7 @@
             case MtpConstants.FORMAT_PNG:
             case MtpConstants.FORMAT_BMP:
             case MtpConstants.FORMAT_DNG:
+            case MtpConstants.FORMAT_HEIF:
                 return IMAGE_PROPERTIES;
             default:
                 return FILE_PROPERTIES;
diff --git a/media/jni/android_mtp_MtpDatabase.cpp b/media/jni/android_mtp_MtpDatabase.cpp
index cf4458a..5b874cd 100644
--- a/media/jni/android_mtp_MtpDatabase.cpp
+++ b/media/jni/android_mtp_MtpDatabase.cpp
@@ -849,6 +849,7 @@
     // read EXIF data for thumbnail information
     switch (info.mFormat) {
         case MTP_FORMAT_EXIF_JPEG:
+        case MTP_FORMAT_HEIF:
         case MTP_FORMAT_JFIF: {
             ExifData *exifdata = exif_data_new_from_file(path);
             if (exifdata) {
@@ -906,6 +907,7 @@
     if (getObjectFilePath(handle, path, length, format) == MTP_RESPONSE_OK) {
         switch (format) {
             case MTP_FORMAT_EXIF_JPEG:
+            case MTP_FORMAT_HEIF:
             case MTP_FORMAT_JFIF: {
                 ExifData *exifdata = exif_data_new_from_file(path);
                 if (exifdata) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
index f3be8d0..0ecb8be 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
@@ -15,6 +15,7 @@
  */
 package com.android.settingslib.wifi;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
@@ -60,9 +61,10 @@
             R.string.accessibility_wifi_signal_full
     };
 
-    private final StateListDrawable mFrictionSld;
+    @Nullable private final StateListDrawable mFrictionSld;
     private final int mBadgePadding;
     private final UserBadgeCache mBadgeCache;
+    private final IconInjector mIconInjector;
     private TextView mTitleView;
 
     private boolean mForSavedNetworks = false;
@@ -86,60 +88,53 @@
         return builder.toString();
     }
 
+    @Nullable
+    private static StateListDrawable getFrictionStateListDrawable(Context context) {
+        TypedArray frictionSld;
+        try {
+            frictionSld = context.getTheme().obtainStyledAttributes(FRICTION_ATTRS);
+        } catch (Resources.NotFoundException e) {
+            // Fallback for platforms that do not need friction icon resources.
+            frictionSld = null;
+        }
+        return frictionSld != null ? (StateListDrawable) frictionSld.getDrawable(0) : null;
+    }
+
     // Used for dummy pref.
     public AccessPointPreference(Context context, AttributeSet attrs) {
         super(context, attrs);
         mFrictionSld = null;
         mBadgePadding = 0;
         mBadgeCache = null;
+        mIconInjector = new IconInjector(context);
     }
 
     public AccessPointPreference(AccessPoint accessPoint, Context context, UserBadgeCache cache,
             boolean forSavedNetworks) {
-        super(context);
-        setWidgetLayoutResource(R.layout.access_point_friction_widget);
-        mBadgeCache = cache;
-        mAccessPoint = accessPoint;
-        mForSavedNetworks = forSavedNetworks;
-        mAccessPoint.setTag(this);
-        mLevel = -1;
-
-        TypedArray frictionSld;
-        try {
-            frictionSld = context.getTheme().obtainStyledAttributes(FRICTION_ATTRS);
-        } catch (Resources.NotFoundException e) {
-            // Fallback for platforms that do not need friction icon resources.
-            frictionSld = null;
-        }
-        mFrictionSld = frictionSld != null ? (StateListDrawable) frictionSld.getDrawable(0) : null;
-
-        // Distance from the end of the title at which this AP's user badge should sit.
-        mBadgePadding = context.getResources()
-                .getDimensionPixelSize(R.dimen.wifi_preference_badge_padding);
+        this(accessPoint, context, cache, 0 /* iconResId */, forSavedNetworks);
         refresh();
     }
 
     public AccessPointPreference(AccessPoint accessPoint, Context context, UserBadgeCache cache,
             int iconResId, boolean forSavedNetworks) {
+        this(accessPoint, context, cache, iconResId, forSavedNetworks,
+                getFrictionStateListDrawable(context), -1 /* level */, new IconInjector(context));
+    }
+
+    @VisibleForTesting
+    AccessPointPreference(AccessPoint accessPoint, Context context, UserBadgeCache cache,
+                          int iconResId, boolean forSavedNetworks, StateListDrawable frictionSld,
+                          int level, IconInjector iconInjector) {
         super(context);
         setWidgetLayoutResource(R.layout.access_point_friction_widget);
         mBadgeCache = cache;
         mAccessPoint = accessPoint;
         mForSavedNetworks = forSavedNetworks;
         mAccessPoint.setTag(this);
-        mLevel = -1;
+        mLevel = level;
         mDefaultIconResId = iconResId;
-
-        TypedArray frictionSld;
-        try {
-            frictionSld = context.getTheme().obtainStyledAttributes(FRICTION_ATTRS);
-        } catch (Resources.NotFoundException e) {
-            // Fallback for platforms that do not need friction icon resources.
-            frictionSld = null;
-        }
-        mFrictionSld = frictionSld != null ? (StateListDrawable) frictionSld.getDrawable(0) : null;
-
-        // Distance from the end of the title at which this AP's user badge should sit.
+        mFrictionSld = frictionSld;
+        mIconInjector = iconInjector;
         mBadgePadding = context.getResources()
                 .getDimensionPixelSize(R.dimen.wifi_preference_badge_padding);
     }
@@ -179,7 +174,7 @@
         }
         TronUtils.logWifiSettingsSpeed(context, mWifiSpeed);
 
-        Drawable drawable = context.getDrawable(Utils.getWifiIconResource(level));
+        Drawable drawable = mIconInjector.getIcon(level);
         if (!mForSavedNetworks && drawable != null) {
             drawable.setTint(Utils.getColorAttr(context, android.R.attr.colorControlNormal));
             setIcon(drawable);
@@ -319,4 +314,16 @@
             return mBadges.valueAt(index);
         }
     }
+
+    static class IconInjector {
+        private final Context mContext;
+
+        public IconInjector(Context context) {
+            mContext = context;
+        }
+
+        public Drawable getIcon(int level) {
+            return mContext.getDrawable(Utils.getWifiIconResource(level));
+        }
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java
index 08757b2..7f89fd5 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java
@@ -16,16 +16,23 @@
 package com.android.settingslib.wifi;
 
 import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
 
 import android.content.Context;
 
+import android.graphics.drawable.ColorDrawable;
 import com.android.settingslib.SettingsLibRobolectricTestRunner;
 import com.android.settingslib.TestConfig;
 
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
 
@@ -35,6 +42,22 @@
 
     private Context mContext = RuntimeEnvironment.application;
 
+    @Mock private AccessPoint mockAccessPoint;
+    @Mock private AccessPointPreference.UserBadgeCache mockUserBadgeCache;
+    @Mock private AccessPointPreference.IconInjector mockIconInjector;
+
+    private AccessPointPreference createWithAccessPoint(AccessPoint accessPoint) {
+        return new AccessPointPreference(accessPoint, mContext, mockUserBadgeCache,
+                0, true, null, -1, mockIconInjector);
+    }
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        when(mockIconInjector.getIcon(anyInt())).thenReturn(new ColorDrawable());
+    }
+
     @Test
     public void generatePreferenceKey_shouldReturnSsidPlusSecurity() {
         String ssid = "ssid";
@@ -78,4 +101,16 @@
                 RuntimeEnvironment.application, pref, ap))
                 .isEqualTo("ssid,connected,Wifi signal full.,Secure network");
     }
+
+    @Test
+    public void refresh_shouldUpdateIcon() {
+        int level = 1;
+        when(mockAccessPoint.getSpeed()).thenReturn(0);
+        when(mockAccessPoint.getLevel()).thenReturn(level);
+
+        AccessPointPreference pref = createWithAccessPoint(mockAccessPoint);
+        pref.refresh();
+
+        verify(mockIconInjector).getIcon(level);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java
index bf926c6..f967118 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java
@@ -193,7 +193,7 @@
 
         int flag = FLAG_REINFLATE_CONTENT_VIEW;
         if ((reInflateFlags & flag) != 0) {
-            boolean isNewView = !compareRemoteViews(result.newContentView, entry.cachedContentView);
+            boolean isNewView = !canReapplyRemoteView(result.newContentView, entry.cachedContentView);
             ApplyCallback applyCallback = new ApplyCallback() {
                 @Override
                 public void setResultView(View v) {
@@ -215,7 +215,7 @@
         flag = FLAG_REINFLATE_EXPANDED_VIEW;
         if ((reInflateFlags & flag) != 0) {
             if (result.newExpandedView != null) {
-                boolean isNewView = !compareRemoteViews(result.newExpandedView,
+                boolean isNewView = !canReapplyRemoteView(result.newExpandedView,
                         entry.cachedBigContentView);
                 ApplyCallback applyCallback = new ApplyCallback() {
                     @Override
@@ -240,7 +240,7 @@
         flag = FLAG_REINFLATE_HEADS_UP_VIEW;
         if ((reInflateFlags & flag) != 0) {
             if (result.newHeadsUpView != null) {
-                boolean isNewView = !compareRemoteViews(result.newHeadsUpView,
+                boolean isNewView = !canReapplyRemoteView(result.newHeadsUpView,
                         entry.cachedHeadsUpContentView);
                 ApplyCallback applyCallback = new ApplyCallback() {
                     @Override
@@ -264,7 +264,7 @@
 
         flag = FLAG_REINFLATE_PUBLIC_VIEW;
         if ((reInflateFlags & flag) != 0) {
-            boolean isNewView = !compareRemoteViews(result.newPublicView,
+            boolean isNewView = !canReapplyRemoteView(result.newPublicView,
                     entry.cachedPublicContentView);
             ApplyCallback applyCallback = new ApplyCallback() {
                 @Override
@@ -288,7 +288,7 @@
         if ((reInflateFlags & flag) != 0) {
             NotificationContentView newParent = redactAmbient ? publicLayout : privateLayout;
             boolean isNewView = !canReapplyAmbient(row, redactAmbient) ||
-                    !compareRemoteViews(result.newAmbientView, entry.cachedAmbientContentView);
+                    !canReapplyRemoteView(result.newAmbientView, entry.cachedAmbientContentView);
             ApplyCallback applyCallback = new ApplyCallback() {
                 @Override
                 public void setResultView(View v) {
@@ -486,14 +486,21 @@
         return builder.createContentView(useLarge);
     }
 
-    // Returns true if the RemoteViews are the same.
-    private static boolean compareRemoteViews(final RemoteViews a, final RemoteViews b) {
-        return (a == null && b == null) ||
-                (a != null && b != null
-                        && b.getPackage() != null
-                        && a.getPackage() != null
-                        && a.getPackage().equals(b.getPackage())
-                        && a.getLayoutId() == b.getLayoutId());
+    /**
+     * @param newView The new view that will be applied
+     * @param oldView The old view that was applied to the existing view before
+     * @return {@code true} if the RemoteViews are the same and the view can be reused to reapply.
+     */
+     @VisibleForTesting
+     static boolean canReapplyRemoteView(final RemoteViews newView,
+            final RemoteViews oldView) {
+        return (newView == null && oldView == null) ||
+                (newView != null && oldView != null
+                        && oldView.getPackage() != null
+                        && newView.getPackage() != null
+                        && newView.getPackage().equals(oldView.getPackage())
+                        && newView.getLayoutId() == oldView.getLayoutId()
+                        && !oldView.isReapplyDisallowed());
     }
 
     public void setInflationCallback(InflationCallback callback) {
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 e65bab6..f7aa818 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -151,7 +151,7 @@
         progress = Math.max(0.0f, Math.min(progress, 1.0f));
         progress = mAccelerateInterpolator.getInterpolation(progress);
         progress *= Math.pow(1 + mEmptyDragAmount / mDensity / 300, 0.3f);
-        return progress;
+        return interpolate(progress, 1, mDarkAmount);
     }
 
     private int getClockNotificationsPadding() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationInflaterTest.java
index fb64a7f..54a96cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationInflaterTest.java
@@ -198,6 +198,18 @@
         runningTask.abort();
     }
 
+    @Test
+    public void doesntReapplyDisallowedRemoteView() throws Exception {
+        mBuilder.setStyle(new Notification.MediaStyle());
+        RemoteViews mediaView = mBuilder.createContentView();
+        mBuilder.setStyle(new Notification.DecoratedCustomViewStyle());
+        mBuilder.setCustomContentView(new RemoteViews(getContext().getPackageName(),
+                R.layout.custom_view_dark));
+        RemoteViews decoratedMediaView = mBuilder.createContentView();
+        Assert.assertFalse("The decorated media style doesn't allow a view to be reapplied!",
+                NotificationInflater.canReapplyRemoteView(mediaView, decoratedMediaView));
+    }
+
     public static void runThenWaitForInflation(Runnable block,
             NotificationInflater inflater) throws Exception {
         runThenWaitForInflation(block, false /* expectingException */, inflater);
diff --git a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
index 4393e35..bcc74c0 100644
--- a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
+++ b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
@@ -32,11 +32,13 @@
 import android.net.RouteInfo;
 import android.net.util.SharedLog;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.INetworkManagementService;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.provider.Settings;
 import android.text.TextUtils;
+import com.android.server.connectivity.tethering.OffloadHardwareInterface.ForwardedStats;
 
 import com.android.internal.util.IndentingPrintWriter;
 
@@ -46,8 +48,10 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -79,8 +83,8 @@
     // Maps upstream interface names to offloaded traffic statistics.
     // Always contains the latest value received from the hardware for each interface, regardless of
     // whether offload is currently running on that interface.
-    private HashMap<String, OffloadHardwareInterface.ForwardedStats>
-            mForwardedStats = new HashMap<>();
+    private ConcurrentHashMap<String, ForwardedStats> mForwardedStats =
+            new ConcurrentHashMap<>(16, 0.75F, 1);
 
     // Maps upstream interface names to interface quotas.
     // Always contains the latest value received from the framework for each interface, regardless
@@ -205,27 +209,29 @@
     private class OffloadTetheringStatsProvider extends ITetheringStatsProvider.Stub {
         @Override
         public NetworkStats getTetherStats(int how) {
+            // getTetherStats() is the only function in OffloadController that can be called from
+            // a different thread. Do not attempt to update stats by querying the offload HAL
+            // synchronously from a different thread than our Handler thread. http://b/64771555.
+            Runnable updateStats = () -> { updateStatsForCurrentUpstream(); };
+            if (Looper.myLooper() == mHandler.getLooper()) {
+                updateStats.run();
+            } else {
+                mHandler.post(updateStats);
+            }
+
             NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0);
+            NetworkStats.Entry entry = new NetworkStats.Entry();
+            entry.set = SET_DEFAULT;
+            entry.tag = TAG_NONE;
+            entry.uid = (how == STATS_PER_UID) ? UID_TETHERING : UID_ALL;
 
-            // We can't just post to mHandler because we are mostly (but not always) called by
-            // NetworkStatsService#performPollLocked, which is (currently) on the same thread as us.
-            mHandler.runWithScissors(() -> {
-                // We have to report both per-interface and per-UID stats, because offloaded traffic
-                // is not seen by kernel interface counters.
-                NetworkStats.Entry entry = new NetworkStats.Entry();
-                entry.set = SET_DEFAULT;
-                entry.tag = TAG_NONE;
-                entry.uid = (how == STATS_PER_UID) ? UID_TETHERING : UID_ALL;
-
-                updateStatsForCurrentUpstream();
-
-                for (String iface : mForwardedStats.keySet()) {
-                    entry.iface = iface;
-                    entry.rxBytes = mForwardedStats.get(iface).rxBytes;
-                    entry.txBytes = mForwardedStats.get(iface).txBytes;
-                    stats.addValues(entry);
-                }
-            }, 0);
+            for (Map.Entry<String, ForwardedStats> kv : mForwardedStats.entrySet()) {
+                ForwardedStats value = kv.getValue();
+                entry.iface = kv.getKey();
+                entry.rxBytes = value.rxBytes;
+                entry.txBytes = value.txBytes;
+                stats.addValues(entry);
+            }
 
             return stats;
         }
@@ -247,10 +253,21 @@
             return;
         }
 
-        if (!mForwardedStats.containsKey(iface)) {
-            mForwardedStats.put(iface, new OffloadHardwareInterface.ForwardedStats());
+        // Always called on the handler thread.
+        //
+        // Use get()/put() instead of updating ForwardedStats in place because we can be called
+        // concurrently with getTetherStats. In combination with the guarantees provided by
+        // ConcurrentHashMap, this ensures that getTetherStats always gets the most recent copy of
+        // the stats for each interface, and does not observe partial writes where rxBytes is
+        // updated and txBytes is not.
+        ForwardedStats diff = mHwInterface.getForwardedStats(iface);
+        ForwardedStats base = mForwardedStats.get(iface);
+        if (base != null) {
+            diff.add(base);
         }
-        mForwardedStats.get(iface).add(mHwInterface.getForwardedStats(iface));
+        mForwardedStats.put(iface, diff);
+        // diff is a new object, just created by getForwardedStats(). Therefore, anyone reading from
+        // mForwardedStats (i.e., any caller of getTetherStats) will see the new stats immediately.
     }
 
     private boolean maybeUpdateDataLimit(String iface) {
diff --git a/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java b/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
index 53a9b5a..c7290e7 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
@@ -400,23 +400,39 @@
         when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(ethernetStats);
         when(mHardware.getForwardedStats(eq(mobileIface))).thenReturn(mobileStats);
 
+        InOrder inOrder = inOrder(mHardware);
+
         final LinkProperties lp = new LinkProperties();
         lp.setInterfaceName(ethernetIface);
         offload.setUpstreamLinkProperties(lp);
+        // Previous upstream was null, so no stats are fetched.
+        inOrder.verify(mHardware, never()).getForwardedStats(any());
 
         lp.setInterfaceName(mobileIface);
         offload.setUpstreamLinkProperties(lp);
+        // Expect that we fetch stats from the previous upstream.
+        inOrder.verify(mHardware, times(1)).getForwardedStats(eq(ethernetIface));
 
         lp.setInterfaceName(ethernetIface);
         offload.setUpstreamLinkProperties(lp);
+        // Expect that we fetch stats from the previous upstream.
+        inOrder.verify(mHardware, times(1)).getForwardedStats(eq(mobileIface));
 
+        ethernetStats = new ForwardedStats();
         ethernetStats.rxBytes = 100000;
         ethernetStats.txBytes = 100000;
+        when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(ethernetStats);
         offload.setUpstreamLinkProperties(null);
+        // Expect that we fetch stats from the previous upstream.
+        inOrder.verify(mHardware, times(1)).getForwardedStats(eq(ethernetIface));
 
         ITetheringStatsProvider provider = mTetherStatsProviderCaptor.getValue();
         NetworkStats stats = provider.getTetherStats(STATS_PER_IFACE);
         NetworkStats perUidStats = provider.getTetherStats(STATS_PER_UID);
+        waitForIdle();
+        // There is no current upstream, so no stats are fetched.
+        inOrder.verify(mHardware, never()).getForwardedStats(eq(ethernetIface));
+        inOrder.verifyNoMoreInteractions();
 
         assertEquals(2, stats.size());
         assertEquals(2, perUidStats.size());