Merge "Migrating Incremental* APIs to PackageManager APIs."
diff --git a/CleanSpec.mk b/CleanSpec.mk
index b84e715..0b7ea28 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -259,6 +259,7 @@
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/framework/service-statsd.jar)
 $(call add-clean-step, rm -rf $(SOONG_OUT_DIR)/.intermediates/frameworks/base/libincremental_aidl-cpp-source/)
 $(call add-clean-step, rm -rf $(SOONG_OUT_DIR)/.intermediates/frameworks/base/libincremental_manager_aidl-cpp-source/)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/InProcessTethering)
 # ******************************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER
 # ******************************************************************
diff --git a/api/current.txt b/api/current.txt
index bf082d3..86aa80b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -19618,6 +19618,197 @@
 
 }
 
+package android.icu.number {
+
+  public class CompactNotation extends android.icu.number.Notation {
+  }
+
+  public abstract class CurrencyPrecision extends android.icu.number.Precision {
+    method public android.icu.number.Precision withCurrency(android.icu.util.Currency);
+  }
+
+  public class FormattedNumber implements java.lang.CharSequence {
+    method public char charAt(int);
+    method public int length();
+    method public CharSequence subSequence(int, int);
+    method public java.math.BigDecimal toBigDecimal();
+    method public java.text.AttributedCharacterIterator toCharacterIterator();
+  }
+
+  public class FormattedNumberRange implements java.lang.CharSequence {
+    method public char charAt(int);
+    method public java.math.BigDecimal getFirstBigDecimal();
+    method public android.icu.number.NumberRangeFormatter.RangeIdentityResult getIdentityResult();
+    method public java.math.BigDecimal getSecondBigDecimal();
+    method public int length();
+    method public CharSequence subSequence(int, int);
+    method public java.text.AttributedCharacterIterator toCharacterIterator();
+  }
+
+  public abstract class FractionPrecision extends android.icu.number.Precision {
+    method public android.icu.number.Precision withMaxDigits(int);
+    method public android.icu.number.Precision withMinDigits(int);
+  }
+
+  public class IntegerWidth {
+    method public android.icu.number.IntegerWidth truncateAt(int);
+    method public static android.icu.number.IntegerWidth zeroFillTo(int);
+  }
+
+  public class LocalizedNumberFormatter extends android.icu.number.NumberFormatterSettings<android.icu.number.LocalizedNumberFormatter> {
+    method public android.icu.number.FormattedNumber format(long);
+    method public android.icu.number.FormattedNumber format(double);
+    method public android.icu.number.FormattedNumber format(Number);
+    method public android.icu.number.FormattedNumber format(android.icu.util.Measure);
+    method public java.text.Format toFormat();
+  }
+
+  public class LocalizedNumberRangeFormatter extends android.icu.number.NumberRangeFormatterSettings<android.icu.number.LocalizedNumberRangeFormatter> {
+    method public android.icu.number.FormattedNumberRange formatRange(int, int);
+    method public android.icu.number.FormattedNumberRange formatRange(double, double);
+    method public android.icu.number.FormattedNumberRange formatRange(Number, Number);
+  }
+
+  public class Notation {
+    method public static android.icu.number.CompactNotation compactLong();
+    method public static android.icu.number.CompactNotation compactShort();
+    method public static android.icu.number.ScientificNotation engineering();
+    method public static android.icu.number.ScientificNotation scientific();
+    method public static android.icu.number.SimpleNotation simple();
+  }
+
+  public final class NumberFormatter {
+    method public static android.icu.number.UnlocalizedNumberFormatter with();
+    method public static android.icu.number.LocalizedNumberFormatter withLocale(java.util.Locale);
+    method public static android.icu.number.LocalizedNumberFormatter withLocale(android.icu.util.ULocale);
+  }
+
+  public enum NumberFormatter.DecimalSeparatorDisplay {
+    enum_constant public static final android.icu.number.NumberFormatter.DecimalSeparatorDisplay ALWAYS;
+    enum_constant public static final android.icu.number.NumberFormatter.DecimalSeparatorDisplay AUTO;
+  }
+
+  public enum NumberFormatter.GroupingStrategy {
+    enum_constant public static final android.icu.number.NumberFormatter.GroupingStrategy AUTO;
+    enum_constant public static final android.icu.number.NumberFormatter.GroupingStrategy MIN2;
+    enum_constant public static final android.icu.number.NumberFormatter.GroupingStrategy OFF;
+    enum_constant public static final android.icu.number.NumberFormatter.GroupingStrategy ON_ALIGNED;
+    enum_constant public static final android.icu.number.NumberFormatter.GroupingStrategy THOUSANDS;
+  }
+
+  public enum NumberFormatter.SignDisplay {
+    enum_constant public static final android.icu.number.NumberFormatter.SignDisplay ACCOUNTING;
+    enum_constant public static final android.icu.number.NumberFormatter.SignDisplay ACCOUNTING_ALWAYS;
+    enum_constant public static final android.icu.number.NumberFormatter.SignDisplay ACCOUNTING_EXCEPT_ZERO;
+    enum_constant public static final android.icu.number.NumberFormatter.SignDisplay ALWAYS;
+    enum_constant public static final android.icu.number.NumberFormatter.SignDisplay AUTO;
+    enum_constant public static final android.icu.number.NumberFormatter.SignDisplay EXCEPT_ZERO;
+    enum_constant public static final android.icu.number.NumberFormatter.SignDisplay NEVER;
+  }
+
+  public enum NumberFormatter.UnitWidth {
+    enum_constant public static final android.icu.number.NumberFormatter.UnitWidth FULL_NAME;
+    enum_constant public static final android.icu.number.NumberFormatter.UnitWidth HIDDEN;
+    enum_constant public static final android.icu.number.NumberFormatter.UnitWidth ISO_CODE;
+    enum_constant public static final android.icu.number.NumberFormatter.UnitWidth NARROW;
+    enum_constant public static final android.icu.number.NumberFormatter.UnitWidth SHORT;
+  }
+
+  public abstract class NumberFormatterSettings<T extends android.icu.number.NumberFormatterSettings<?>> {
+    method public T decimal(android.icu.number.NumberFormatter.DecimalSeparatorDisplay);
+    method public T grouping(android.icu.number.NumberFormatter.GroupingStrategy);
+    method public T integerWidth(android.icu.number.IntegerWidth);
+    method public T notation(android.icu.number.Notation);
+    method public T perUnit(android.icu.util.MeasureUnit);
+    method public T precision(android.icu.number.Precision);
+    method public T roundingMode(java.math.RoundingMode);
+    method public T scale(android.icu.number.Scale);
+    method public T sign(android.icu.number.NumberFormatter.SignDisplay);
+    method public T symbols(android.icu.text.DecimalFormatSymbols);
+    method public T symbols(android.icu.text.NumberingSystem);
+    method public T unit(android.icu.util.MeasureUnit);
+    method public T unitWidth(android.icu.number.NumberFormatter.UnitWidth);
+  }
+
+  public abstract class NumberRangeFormatter {
+    method public static android.icu.number.UnlocalizedNumberRangeFormatter with();
+    method public static android.icu.number.LocalizedNumberRangeFormatter withLocale(java.util.Locale);
+    method public static android.icu.number.LocalizedNumberRangeFormatter withLocale(android.icu.util.ULocale);
+  }
+
+  public enum NumberRangeFormatter.RangeCollapse {
+    enum_constant public static final android.icu.number.NumberRangeFormatter.RangeCollapse ALL;
+    enum_constant public static final android.icu.number.NumberRangeFormatter.RangeCollapse AUTO;
+    enum_constant public static final android.icu.number.NumberRangeFormatter.RangeCollapse NONE;
+    enum_constant public static final android.icu.number.NumberRangeFormatter.RangeCollapse UNIT;
+  }
+
+  public enum NumberRangeFormatter.RangeIdentityFallback {
+    enum_constant public static final android.icu.number.NumberRangeFormatter.RangeIdentityFallback APPROXIMATELY;
+    enum_constant public static final android.icu.number.NumberRangeFormatter.RangeIdentityFallback APPROXIMATELY_OR_SINGLE_VALUE;
+    enum_constant public static final android.icu.number.NumberRangeFormatter.RangeIdentityFallback RANGE;
+    enum_constant public static final android.icu.number.NumberRangeFormatter.RangeIdentityFallback SINGLE_VALUE;
+  }
+
+  public enum NumberRangeFormatter.RangeIdentityResult {
+    enum_constant public static final android.icu.number.NumberRangeFormatter.RangeIdentityResult EQUAL_AFTER_ROUNDING;
+    enum_constant public static final android.icu.number.NumberRangeFormatter.RangeIdentityResult EQUAL_BEFORE_ROUNDING;
+    enum_constant public static final android.icu.number.NumberRangeFormatter.RangeIdentityResult NOT_EQUAL;
+  }
+
+  public abstract class NumberRangeFormatterSettings<T extends android.icu.number.NumberRangeFormatterSettings<?>> {
+    method public T collapse(android.icu.number.NumberRangeFormatter.RangeCollapse);
+    method public T identityFallback(android.icu.number.NumberRangeFormatter.RangeIdentityFallback);
+    method public T numberFormatterBoth(android.icu.number.UnlocalizedNumberFormatter);
+    method public T numberFormatterFirst(android.icu.number.UnlocalizedNumberFormatter);
+    method public T numberFormatterSecond(android.icu.number.UnlocalizedNumberFormatter);
+  }
+
+  public abstract class Precision implements java.lang.Cloneable {
+    method public Object clone();
+    method public static android.icu.number.CurrencyPrecision currency(android.icu.util.Currency.CurrencyUsage);
+    method public static android.icu.number.FractionPrecision fixedFraction(int);
+    method public static android.icu.number.Precision fixedSignificantDigits(int);
+    method public static android.icu.number.Precision increment(java.math.BigDecimal);
+    method public static android.icu.number.FractionPrecision integer();
+    method public static android.icu.number.FractionPrecision maxFraction(int);
+    method public static android.icu.number.Precision maxSignificantDigits(int);
+    method public static android.icu.number.FractionPrecision minFraction(int);
+    method public static android.icu.number.FractionPrecision minMaxFraction(int, int);
+    method public static android.icu.number.Precision minMaxSignificantDigits(int, int);
+    method public static android.icu.number.Precision minSignificantDigits(int);
+    method public static android.icu.number.Precision unlimited();
+  }
+
+  public class Scale {
+    method public static android.icu.number.Scale byBigDecimal(java.math.BigDecimal);
+    method public static android.icu.number.Scale byDouble(double);
+    method public static android.icu.number.Scale byDoubleAndPowerOfTen(double, int);
+    method public static android.icu.number.Scale none();
+    method public static android.icu.number.Scale powerOfTen(int);
+  }
+
+  public class ScientificNotation extends android.icu.number.Notation implements java.lang.Cloneable {
+    method public Object clone();
+    method public android.icu.number.ScientificNotation withExponentSignDisplay(android.icu.number.NumberFormatter.SignDisplay);
+    method public android.icu.number.ScientificNotation withMinExponentDigits(int);
+  }
+
+  public class SimpleNotation extends android.icu.number.Notation {
+  }
+
+  public class UnlocalizedNumberFormatter extends android.icu.number.NumberFormatterSettings<android.icu.number.UnlocalizedNumberFormatter> {
+    method public android.icu.number.LocalizedNumberFormatter locale(java.util.Locale);
+    method public android.icu.number.LocalizedNumberFormatter locale(android.icu.util.ULocale);
+  }
+
+  public class UnlocalizedNumberRangeFormatter extends android.icu.number.NumberRangeFormatterSettings<android.icu.number.UnlocalizedNumberRangeFormatter> {
+    method public android.icu.number.LocalizedNumberRangeFormatter locale(java.util.Locale);
+    method public android.icu.number.LocalizedNumberRangeFormatter locale(android.icu.util.ULocale);
+  }
+
+}
+
 package android.icu.text {
 
   public final class AlphabeticIndex<V> implements java.lang.Iterable<android.icu.text.AlphabeticIndex.Bucket<V>> {
diff --git a/api/system-current.txt b/api/system-current.txt
index dc27f22..05e2ca5 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5394,11 +5394,15 @@
     ctor public EasyConnectStatusCallback();
     method public abstract void onConfiguratorSuccess(int);
     method public abstract void onEnrolleeSuccess(int);
-    method public abstract void onFailure(int);
+    method public void onFailure(int);
+    method public void onFailure(int, @Nullable String, @NonNull android.util.SparseArray<int[]>, @NonNull int[]);
     method public abstract void onProgress(int);
     field public static final int EASY_CONNECT_EVENT_FAILURE_AUTHENTICATION = -2; // 0xfffffffe
     field public static final int EASY_CONNECT_EVENT_FAILURE_BUSY = -5; // 0xfffffffb
+    field public static final int EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK = -10; // 0xfffffff6
     field public static final int EASY_CONNECT_EVENT_FAILURE_CONFIGURATION = -4; // 0xfffffffc
+    field public static final int EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION = -11; // 0xfffffff5
+    field public static final int EASY_CONNECT_EVENT_FAILURE_ENROLLEE_REJECTED_CONFIGURATION = -12; // 0xfffffff4
     field public static final int EASY_CONNECT_EVENT_FAILURE_GENERIC = -7; // 0xfffffff9
     field public static final int EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK = -9; // 0xfffffff7
     field public static final int EASY_CONNECT_EVENT_FAILURE_INVALID_URI = -1; // 0xffffffff
@@ -5406,7 +5410,10 @@
     field public static final int EASY_CONNECT_EVENT_FAILURE_NOT_SUPPORTED = -8; // 0xfffffff8
     field public static final int EASY_CONNECT_EVENT_FAILURE_TIMEOUT = -6; // 0xfffffffa
     field public static final int EASY_CONNECT_EVENT_PROGRESS_AUTHENTICATION_SUCCESS = 0; // 0x0
+    field public static final int EASY_CONNECT_EVENT_PROGRESS_CONFIGURATION_ACCEPTED = 3; // 0x3
+    field public static final int EASY_CONNECT_EVENT_PROGRESS_CONFIGURATION_SENT_WAITING_RESPONSE = 2; // 0x2
     field public static final int EASY_CONNECT_EVENT_PROGRESS_RESPONSE_PENDING = 1; // 0x1
+    field public static final int EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_APPLIED = 1; // 0x1
     field public static final int EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_SENT = 0; // 0x0
   }
 
@@ -8899,6 +8906,7 @@
   }
 
   public class CarrierConfigManager {
+    method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getDefaultCarrierServicePackageName();
     method @NonNull public static android.os.PersistableBundle getDefaultConfig();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void overrideConfig(int, @Nullable android.os.PersistableBundle);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void updateConfigForPhoneId(int, String);
@@ -9697,6 +9705,7 @@
   }
 
   public class ServiceState implements android.os.Parcelable {
+    method public int getDataRegistrationState();
     method @Nullable public android.telephony.NetworkRegistrationInfo getNetworkRegistrationInfo(int, int);
     method @NonNull public java.util.List<android.telephony.NetworkRegistrationInfo> getNetworkRegistrationInfoList();
     method @NonNull public java.util.List<android.telephony.NetworkRegistrationInfo> getNetworkRegistrationInfoListForDomain(int);
diff --git a/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp b/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp
index f6e1345..63ee8a6 100644
--- a/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp
+++ b/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp
@@ -16,6 +16,7 @@
 
 #include "idmap2/PrettyPrintVisitor.h"
 
+#include <istream>
 #include <string>
 
 #include "android-base/macros.h"
@@ -28,21 +29,30 @@
 
 #define RESID(pkg, type, entry) (((pkg) << 24) | ((type) << 16) | (entry))
 
+#define TAB "    "
+
 void PrettyPrintVisitor::visit(const Idmap& idmap ATTRIBUTE_UNUSED) {
 }
 
 void PrettyPrintVisitor::visit(const IdmapHeader& header) {
-  stream_ << "target apk path  : " << header.GetTargetPath() << std::endl
-          << "overlay apk path : " << header.GetOverlayPath() << std::endl;
+  stream_ << "Paths:" << std::endl
+          << TAB "target apk path  : " << header.GetTargetPath() << std::endl
+          << TAB "overlay apk path : " << header.GetOverlayPath() << std::endl;
   const std::string& debug = header.GetDebugInfo();
   if (!debug.empty()) {
-    stream_ << debug;  // assume newline terminated
+    std::istringstream debug_stream(debug);
+    std::string line;
+    stream_ << "Debug info:" << std::endl;
+    while (std::getline(debug_stream, line)) {
+      stream_ << TAB << line << std::endl;
+    }
   }
 
   target_apk_ = ApkAssets::Load(header.GetTargetPath().to_string());
   if (target_apk_) {
     target_am_.SetApkAssets({target_apk_.get()});
   }
+  stream_ << "Mapping:" << std::endl;
 }
 
 void PrettyPrintVisitor::visit(const IdmapData::Header& header ATTRIBUTE_UNUSED) {
@@ -55,7 +65,7 @@
   const size_t string_pool_offset = data.GetHeader()->GetStringPoolIndexOffset();
 
   for (auto& target_entry : data.GetTargetEntries()) {
-    stream_ << base::StringPrintf("0x%08x ->", target_entry.target_id);
+    stream_ << TAB << base::StringPrintf("0x%08x ->", target_entry.target_id);
 
     if (target_entry.data_type != Res_value::TYPE_REFERENCE &&
         target_entry.data_type != Res_value::TYPE_DYNAMIC_REFERENCE) {
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index a2cfff2..2270974 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -5995,6 +5995,14 @@
         USER_GRANTED_ONE_TIME = 10;
         // user ignored request by leaving the request screen without choosing any option
         USER_IGNORED = 11;
+        // user granted the permission after being linked to settings
+        USER_GRANTED_IN_SETTINGS = 12;
+        // user denied the permission after being linked to settings
+        USER_DENIED_IN_SETTINGS = 13;
+        // user denied the permission with prejudice after being linked to settings
+        USER_DENIED_WITH_PREJUDICE_IN_SETTINGS = 14;
+        // permission was automatically revoked after one-time permission expired
+        AUTO_ONE_TIME_PERMISSION_REVOKED = 15;
     }
     // The result of the permission grant
     optional Result result = 6;
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index ff581c0..9e0c2fc 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -8674,7 +8674,6 @@
      * @hide
      */
     @RequiresPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS)
-    @UnsupportedAppUsage
     public void registerRemoteAnimations(RemoteAnimationDefinition definition) {
         try {
             ActivityTaskManager.getService().registerRemoteAnimations(mToken, definition);
@@ -8683,6 +8682,20 @@
         }
     }
 
+    /**
+     * Unregisters all remote animations for this activity.
+     *
+     * @hide
+     */
+    @RequiresPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS)
+    public void unregisterRemoteAnimations() {
+        try {
+            ActivityTaskManager.getService().unregisterRemoteAnimations(mToken);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     class HostCallbacks extends FragmentHostCallback<Activity> {
         public HostCallbacks() {
             super(Activity.this /*activity*/);
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index e2b1b86..df5d6c7 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -437,6 +437,11 @@
     void registerRemoteAnimations(in IBinder token, in RemoteAnimationDefinition definition);
 
     /**
+     * Unregisters all remote animations for a specific activity.
+     */
+    void unregisterRemoteAnimations(in IBinder token);
+
+    /**
      * Registers a remote animation to be run for all activity starts from a certain package during
      * a short predefined amount of time.
      */
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index ac8b40c..2507991 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -33,6 +33,7 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.Resources.NotFoundException;
 import android.graphics.Bitmap;
@@ -40,6 +41,8 @@
 import android.graphics.BitmapRegionDecoder;
 import android.graphics.Canvas;
 import android.graphics.ColorFilter;
+import android.graphics.ColorSpace;
+import android.graphics.ImageDecoder;
 import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.PixelFormat;
@@ -63,11 +66,15 @@
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
+import android.view.Display;
 import android.view.WindowManagerGlobal;
 
+import com.android.internal.R;
+
 import libcore.io.IoUtils;
 
 import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
@@ -76,7 +83,10 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -193,7 +203,13 @@
      */
     public static final int FLAG_LOCK = 1 << 1;
 
+    private static final Object sSync = new Object[0];
+    @UnsupportedAppUsage
+    private static Globals sGlobals;
+
     private final Context mContext;
+    private final boolean mWcgEnabled;
+    private final ColorManagementProxy mCmProxy;
 
     /**
      * Special drawable that draws a wallpaper as fast as possible.  Assumes
@@ -388,13 +404,14 @@
         }
 
         public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault,
-                @SetWallpaperFlags int which) {
+                @SetWallpaperFlags int which, ColorManagementProxy cmProxy) {
             return peekWallpaperBitmap(context, returnDefault, which, context.getUserId(),
-                    false /* hardware */);
+                    false /* hardware */, cmProxy);
         }
 
         public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault,
-                @SetWallpaperFlags int which, int userId, boolean hardware) {
+                @SetWallpaperFlags int which, int userId, boolean hardware,
+                ColorManagementProxy cmProxy) {
             if (mService != null) {
                 try {
                     if (!mService.isWallpaperSupported(context.getOpPackageName())) {
@@ -412,7 +429,8 @@
                 mCachedWallpaper = null;
                 mCachedWallpaperUserId = 0;
                 try {
-                    mCachedWallpaper = getCurrentWallpaperLocked(context, userId, hardware);
+                    mCachedWallpaper = getCurrentWallpaperLocked(
+                            context, userId, hardware, cmProxy);
                     mCachedWallpaperUserId = userId;
                 } catch (OutOfMemoryError e) {
                     Log.w(TAG, "Out of memory loading the current wallpaper: " + e);
@@ -450,7 +468,8 @@
             }
         }
 
-        private Bitmap getCurrentWallpaperLocked(Context context, int userId, boolean hardware) {
+        private Bitmap getCurrentWallpaperLocked(Context context, int userId, boolean hardware,
+                ColorManagementProxy cmProxy) {
             if (mService == null) {
                 Log.w(TAG, "WallpaperService not running");
                 return null;
@@ -458,21 +477,29 @@
 
             try {
                 Bundle params = new Bundle();
-                ParcelFileDescriptor fd = mService.getWallpaperWithFeature(
+                ParcelFileDescriptor pfd = mService.getWallpaperWithFeature(
                         context.getOpPackageName(), context.getFeatureId(), this, FLAG_SYSTEM,
                         params, userId);
-                if (fd != null) {
-                    try {
-                        BitmapFactory.Options options = new BitmapFactory.Options();
-                        if (hardware) {
-                            options.inPreferredConfig = Bitmap.Config.HARDWARE;
+
+                if (pfd != null) {
+                    try (BufferedInputStream bis = new BufferedInputStream(
+                            new ParcelFileDescriptor.AutoCloseInputStream(pfd))) {
+                        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                        int data;
+                        while ((data = bis.read()) != -1) {
+                            baos.write(data);
                         }
-                        return BitmapFactory.decodeFileDescriptor(
-                                fd.getFileDescriptor(), null, options);
-                    } catch (OutOfMemoryError e) {
+                        ImageDecoder.Source src = ImageDecoder.createSource(baos.toByteArray());
+                        return ImageDecoder.decodeBitmap(src, ((decoder, info, source) -> {
+                            // Mutable and hardware config can't be set at the same time.
+                            decoder.setMutableRequired(!hardware);
+                            // Let's do color management
+                            if (cmProxy != null) {
+                                cmProxy.doColorManagement(decoder, info);
+                            }
+                        }));
+                    } catch (OutOfMemoryError | IOException e) {
                         Log.w(TAG, "Can't decode file", e);
-                    } finally {
-                        IoUtils.closeQuietly(fd);
                     }
                 }
             } catch (RemoteException e) {
@@ -497,10 +524,6 @@
         }
     }
 
-    private static final Object sSync = new Object[0];
-    @UnsupportedAppUsage
-    private static Globals sGlobals;
-
     static void initGlobals(IWallpaperManager service, Looper looper) {
         synchronized (sSync) {
             if (sGlobals == null) {
@@ -514,6 +537,10 @@
         if (service != null) {
             initGlobals(service, context.getMainLooper());
         }
+        // Check if supports mixed color spaces composition in hardware.
+        mWcgEnabled = context.getResources().getConfiguration().isScreenWideColorGamut()
+                && context.getResources().getBoolean(R.bool.config_enableWcgMode);
+        mCmProxy = new ColorManagementProxy(context);
     }
 
     /**
@@ -531,6 +558,22 @@
     }
 
     /**
+     * Indicate whether wcg (Wide Color Gamut) should be enabled.
+     * <p>
+     * Some devices lack of capability of mixed color spaces composition,
+     * enable wcg on such devices might cause memory or battery concern.
+     * <p>
+     * Therefore, in addition to {@link Configuration#isScreenWideColorGamut()},
+     * we also take mixed color spaces composition (config_enableWcgMode) into account.
+     *
+     * @see Configuration#isScreenWideColorGamut()
+     * @return True if wcg should be enabled for this device.
+     */
+    private boolean shouldEnableWideColorGamut() {
+        return mWcgEnabled;
+    }
+
+    /**
      * Retrieve the current system wallpaper; if
      * no wallpaper is set, the system built-in static wallpaper is returned.
      * This is returned as an
@@ -546,7 +589,7 @@
      *     is not able to access the wallpaper.
      */
     public Drawable getDrawable() {
-        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM);
+        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, mCmProxy);
         if (bm != null) {
             Drawable dr = new BitmapDrawable(mContext.getResources(), bm);
             dr.setDither(false);
@@ -777,7 +820,7 @@
      * null pointer if these is none.
      */
     public Drawable peekDrawable() {
-        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false, FLAG_SYSTEM);
+        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false, FLAG_SYSTEM, mCmProxy);
         if (bm != null) {
             Drawable dr = new BitmapDrawable(mContext.getResources(), bm);
             dr.setDither(false);
@@ -801,7 +844,7 @@
      */
     @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
     public Drawable getFastDrawable() {
-        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM);
+        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, mCmProxy);
         if (bm != null) {
             return new FastBitmapDrawable(bm);
         }
@@ -817,7 +860,7 @@
      */
     @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
     public Drawable peekFastDrawable() {
-       Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false, FLAG_SYSTEM);
+        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false, FLAG_SYSTEM, mCmProxy);
         if (bm != null) {
             return new FastBitmapDrawable(bm);
         }
@@ -825,6 +868,27 @@
     }
 
     /**
+     * Whether the wallpaper supports Wide Color Gamut or not.
+     * @param which The wallpaper whose image file is to be retrieved. Must be a single
+     *     defined kind of wallpaper, either {@link #FLAG_SYSTEM} or {@link #FLAG_LOCK}.
+     * @return true when supported.
+     *
+     * @see #FLAG_LOCK
+     * @see #FLAG_SYSTEM
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
+    public boolean wallpaperSupportsWcg(int which) {
+        if (!shouldEnableWideColorGamut()) {
+            return false;
+        }
+        Bitmap bitmap = sGlobals.peekWallpaperBitmap(mContext, false, which, mCmProxy);
+        return bitmap != null && bitmap.getColorSpace() != null
+                && bitmap.getColorSpace() != ColorSpace.get(ColorSpace.Named.SRGB)
+                && mCmProxy.isSupportedColorSpace(bitmap.getColorSpace());
+    }
+
+    /**
      * Like {@link #getDrawable()} but returns a Bitmap with default {@link Bitmap.Config}.
      *
      * @hide
@@ -852,7 +916,8 @@
      * @hide
      */
     public Bitmap getBitmapAsUser(int userId, boolean hardware) {
-        return sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, userId, hardware);
+        return sGlobals.peekWallpaperBitmap(
+                mContext, true, FLAG_SYSTEM, userId, hardware, mCmProxy);
     }
 
     /**
@@ -1975,6 +2040,33 @@
         return false;
     }
 
+    /**
+     * A private class to help Globals#getCurrentWallpaperLocked handle color management.
+     */
+    private static class ColorManagementProxy {
+        private final Set<ColorSpace> mSupportedColorSpaces = new HashSet<>();
+
+        ColorManagementProxy(Context context) {
+            // Get a list of supported wide gamut color spaces.
+            Display display = context.getDisplay();
+            if (display != null) {
+                mSupportedColorSpaces.addAll(Arrays.asList(display.getSupportedWideColorGamut()));
+            }
+        }
+
+        boolean isSupportedColorSpace(ColorSpace colorSpace) {
+            return colorSpace != null && (colorSpace == ColorSpace.get(ColorSpace.Named.SRGB)
+                    || mSupportedColorSpaces.contains(colorSpace));
+        }
+
+        void doColorManagement(ImageDecoder decoder, ImageDecoder.ImageInfo info) {
+            if (!isSupportedColorSpace(info.getColorSpace())) {
+                decoder.setTargetColorSpace(ColorSpace.get(ColorSpace.Named.SRGB));
+                Log.w(TAG, "Not supported color space: " + info.getColorSpace());
+            }
+        }
+    }
+
     // Private completion callback for setWallpaper() synchronization
     private class WallpaperSetCompletion extends IWallpaperManagerCallback.Stub {
         final CountDownLatch mLatch;
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 1a6ec4e..b555d20 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -43,7 +43,9 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 
 /**
  * Provides information about the size and density of a logical display.
@@ -382,6 +384,23 @@
     /** @hide */
     public static final int COLOR_MODE_DISPLAY_P3 = 9;
 
+    /** @hide **/
+    @IntDef(prefix = {"COLOR_MODE_"}, value = {
+            COLOR_MODE_INVALID,
+            COLOR_MODE_DEFAULT,
+            COLOR_MODE_BT601_625,
+            COLOR_MODE_BT601_625_UNADJUSTED,
+            COLOR_MODE_BT601_525,
+            COLOR_MODE_BT601_525_UNADJUSTED,
+            COLOR_MODE_BT709,
+            COLOR_MODE_DCI_P3,
+            COLOR_MODE_SRGB,
+            COLOR_MODE_ADOBE_RGB,
+            COLOR_MODE_DISPLAY_P3
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ColorMode {}
+
     /**
      * Indicates that when display is removed, all its activities will be moved to the primary
      * display and the topmost activity should become focused.
@@ -960,6 +979,37 @@
     }
 
     /**
+     * Gets the supported wide color gamuts of this device.
+     *
+     * @return Supported WCG color spaces.
+     * @hide
+     */
+    public @ColorMode ColorSpace[] getSupportedWideColorGamut() {
+        synchronized (this) {
+            final ColorSpace[] defaultColorSpaces = new ColorSpace[0];
+            updateDisplayInfoLocked();
+            if (!isWideColorGamut()) {
+                return defaultColorSpaces;
+            }
+
+            final int[] colorModes = getSupportedColorModes();
+            final List<ColorSpace> colorSpaces = new ArrayList<>();
+            for (int colorMode : colorModes) {
+                // Refer to DisplayInfo#isWideColorGamut.
+                switch (colorMode) {
+                    case COLOR_MODE_DCI_P3:
+                        colorSpaces.add(ColorSpace.get(ColorSpace.Named.DCI_P3));
+                        break;
+                    case COLOR_MODE_DISPLAY_P3:
+                        colorSpaces.add(ColorSpace.get(ColorSpace.Named.DISPLAY_P3));
+                        break;
+                }
+            }
+            return colorSpaces.toArray(defaultColorSpaces);
+        }
+    }
+
+    /**
      * Gets the app VSYNC offset, in nanoseconds.  This is a positive value indicating
      * the phase offset of the VSYNC events provided by Choreographer relative to the
      * display refresh.  For example, if Choreographer reports that the refresh occurred
diff --git a/core/java/android/view/RemoteAnimationDefinition.java b/core/java/android/view/RemoteAnimationDefinition.java
index c9bd92a..5a8ac54 100644
--- a/core/java/android/view/RemoteAnimationDefinition.java
+++ b/core/java/android/view/RemoteAnimationDefinition.java
@@ -22,9 +22,12 @@
 import android.app.WindowConfiguration;
 import android.app.WindowConfiguration.ActivityType;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.RemoteException;
 import android.util.ArraySet;
+import android.util.Slog;
 import android.util.SparseArray;
 import android.view.WindowManager.TransitionType;
 
@@ -124,6 +127,20 @@
         }
     }
 
+    /**
+     * Links the death of the runner to the provided death recipient.
+     */
+    public void linkToDeath(IBinder.DeathRecipient deathRecipient) {
+        try {
+            for (int i = 0; i < mTransitionAnimationMap.size(); i++) {
+                mTransitionAnimationMap.valueAt(i).adapter.getRunner().asBinder()
+                        .linkToDeath(deathRecipient, 0 /* flags */);
+            }
+        } catch (RemoteException e) {
+            Slog.e("RemoteAnimationDefinition", "Failed to link to death recipient");
+        }
+    }
+
     @Override
     public int describeContents() {
         return 0;
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 48853bf..246a92c 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -1377,9 +1377,8 @@
             for (TargetInfo innerInfo : mti.getTargets()) {
                 labels.add(innerInfo.getResolveInfo().loadLabel(getPackageManager()));
             }
-            f = new ResolverTargetActionsDialogFragment(
-                    mti.getResolveInfo().loadLabel(getPackageManager()), name, mti.getTargets(),
-                    labels);
+            f = new ResolverTargetActionsDialogFragment(mti.getDisplayLabel(), name,
+                    mti.getTargets(), labels);
         } else {
             f = new ResolverTargetActionsDialogFragment(
                     ti.getResolveInfo().loadLabel(getPackageManager()), name, pinned);
diff --git a/core/java/com/android/internal/app/chooser/MultiDisplayResolveInfo.java b/core/java/com/android/internal/app/chooser/MultiDisplayResolveInfo.java
index 4c52411..55200c8 100644
--- a/core/java/com/android/internal/app/chooser/MultiDisplayResolveInfo.java
+++ b/core/java/com/android/internal/app/chooser/MultiDisplayResolveInfo.java
@@ -25,7 +25,6 @@
 public class MultiDisplayResolveInfo extends DisplayResolveInfo {
 
     List<DisplayResolveInfo> mTargetInfos = new ArrayList<>();
-    String mPackageName;
     // We'll use this DRI for basic presentation info - eg icon, name.
     final DisplayResolveInfo mBaseInfo;
 
@@ -38,6 +37,12 @@
         mTargetInfos.add(firstInfo);
     }
 
+    @Override
+    public CharSequence getExtendedInfo() {
+        // Never show subtitle for stacked apps
+        return null;
+    }
+
     /**
      * Add another DisplayResolveInfo to the list included for this target.
      */
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index e21eefb..6e0d5d8 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -828,7 +828,7 @@
         }
     }
 
-    for (dmabufinfo::DmaBuffer buf : dmabufs) {
+    for (const dmabufinfo::DmaBuffer& buf : dmabufs) {
         ionPss += buf.size() / 1024;
     }
 
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 32ec95a..f28f08b 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4260,6 +4260,10 @@
         <!-- Add packages here -->
     </string-array>
 
+    <!-- Whether or not wcg (wide color gamut) should be enabled on this device,
+         we only enabled it while the device has ability of mixed color spaces composition -->
+    <bool name="config_enableWcgMode">false</bool>
+
     <!-- When true, enables the whitelisted app to handle bug reports from power menu short press. -->
     <bool name="config_bugReportHandlerEnabled">false</bool>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d30c7bf..7cf2b78 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3788,6 +3788,9 @@
 
   <java-symbol type="string" name="accessibility_freeform_caption" />
 
+  <!-- For Wide Color Gamut -->
+  <java-symbol type="bool" name="config_enableWcgMode" />
+
   <!-- For contacts provider. -->
   <java-symbol type="string" name="config_rawContactsLocalAccountName" />
   <java-symbol type="string" name="config_rawContactsLocalAccountType" />
diff --git a/media/jni/android_media_MediaMetricsJNI.cpp b/media/jni/android_media_MediaMetricsJNI.cpp
index 37aca08..b82d8e2 100644
--- a/media/jni/android_media_MediaMetricsJNI.cpp
+++ b/media/jni/android_media_MediaMetricsJNI.cpp
@@ -18,6 +18,7 @@
 
 #include <binder/Parcel.h>
 #include <jni.h>
+#include <media/IMediaMetricsService.h>
 #include <media/MediaMetricsItem.h>
 #include <nativehelper/JNIHelp.h>
 #include <variant>
@@ -150,14 +151,12 @@
         return (jint)BAD_VALUE;
     }
 
-    // TODO: directly record item to MediaMetrics service.
-    mediametrics::Item item;
-    if (item.readFromByteString((char *)buffer, length) != NO_ERROR) {
-        ALOGW("%s: cannot read from byte string", __func__);
-        return (jint)BAD_VALUE;
+    sp<IMediaMetricsService> service = mediametrics::BaseItem::getService();
+    if (service == nullptr) {
+        ALOGW("Cannot retrieve mediametrics service");
+        return (jint)NO_INIT;
     }
-    item.selfrecord();
-    return (jint)NO_ERROR;
+    return (jint)service->submitBuffer((char *)buffer, length);
 }
 
 // Helper function to convert a native PersistableBundle to a Java
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraErrorCollector.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraErrorCollector.java
index 6facec4..41914b8 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraErrorCollector.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraErrorCollector.java
@@ -969,7 +969,7 @@
      */
     private static class IntInMatcher extends InMatcher<Integer> {
         public IntInMatcher(int[] values) {
-            Preconditions.checkNotNull("values", values);
+            Objects.requireNonNull(values, "values");
             mValues = new ArrayList<>(values.length);
             for (int i : values) {
                 mValues.add(i);
@@ -1005,7 +1005,7 @@
      */
     private static class BooleanInMatcher extends InMatcher<Boolean> {
         public BooleanInMatcher(boolean[] values) {
-            Preconditions.checkNotNull("values", values);
+            Objects.requireNonNull(values, "values");
             mValues = new ArrayList<>(values.length);
             for (boolean i : values) {
                 mValues.add(i);
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/InMatcher.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/InMatcher.java
index e25a140..c77a042 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/InMatcher.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/InMatcher.java
@@ -36,7 +36,7 @@
     protected Collection<T> mValues;
 
     public InMatcher(Collection<T> values) {
-        Preconditions.checkNotNull("values", values);
+        Objects.requireNonNull(values, "values");
         mValues = values;
     }
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityCompat.java
index 18dc185..0c7e56e 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityCompat.java
@@ -37,6 +37,13 @@
     }
 
     /**
+     * @see Activity#unregisterRemoteAnimations
+     */
+    public void unregisterRemoteAnimations() {
+        mWrapped.unregisterRemoteAnimations();
+    }
+
+    /**
      * @see android.view.ViewDebug#dumpv2(View, ByteArrayOutputStream)
      */
     public boolean encodeViewHierarchy(ByteArrayOutputStream out) {
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index 29a7167..e50d08c 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -108,12 +108,13 @@
             if (mController != null) {
                 mController.addCallback(this /* StateListener */);
             }
-            mEglHelper = new EglHelper();
-            mRenderer = new ImageWallpaperRenderer(context, this /* SurfaceProxy */);
         }
 
         @Override
         public void onCreate(SurfaceHolder surfaceHolder) {
+            mEglHelper = new EglHelper();
+            // Deferred init renderer because we need to get wallpaper by display context.
+            mRenderer = new ImageWallpaperRenderer(getDisplayContext(), this /* SurfaceProxy */);
             setFixedSizeAllowed(true);
             setOffsetNotificationsEnabled(true);
             updateSurfaceSize();
@@ -177,14 +178,13 @@
                 mRenderer = null;
                 mEglHelper.finish();
                 mEglHelper = null;
-                getSurfaceHolder().getSurface().hwuiDestroy();
             });
         }
 
         @Override
         public void onSurfaceCreated(SurfaceHolder holder) {
             mWorker.getThreadHandler().post(() -> {
-                mEglHelper.init(holder);
+                mEglHelper.init(holder, needSupportWideColorGamut());
                 mRenderer.onSurfaceCreated();
             });
         }
@@ -257,7 +257,7 @@
 
             // Check if we need to recreate egl surface.
             if (mEglHelper.hasEglContext() && !mEglHelper.hasEglSurface()) {
-                if (!mEglHelper.createEglSurface(getSurfaceHolder())) {
+                if (!mEglHelper.createEglSurface(getSurfaceHolder(), needSupportWideColorGamut())) {
                     Log.w(TAG, "recreate egl surface failed!");
                 }
             }
@@ -340,6 +340,10 @@
                     && mController.getState() == StatusBarState.KEYGUARD;
         }
 
+        private boolean needSupportWideColorGamut() {
+            return mRenderer.isWcgContent();
+        }
+
         @Override
         protected void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) {
             super.dump(prefix, fd, out, args);
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java
index c6812a7..4b28540 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java
@@ -51,40 +51,65 @@
 import android.opengl.EGLDisplay;
 import android.opengl.EGLSurface;
 import android.opengl.GLUtils;
+import android.text.TextUtils;
 import android.util.Log;
 import android.view.SurfaceHolder;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
 
 /**
  * A helper class to handle EGL management.
  */
 public class EglHelper {
     private static final String TAG = EglHelper.class.getSimpleName();
+    private static final int OPENGLES_VERSION = 2;
     // Below two constants make drawing at low priority, so other things can preempt our drawing.
     private static final int EGL_CONTEXT_PRIORITY_LEVEL_IMG = 0x3100;
     private static final int EGL_CONTEXT_PRIORITY_LOW_IMG = 0x3103;
     private static final boolean DEBUG = true;
+
+    private static final int EGL_GL_COLORSPACE_KHR = 0x309D;
+    private static final int EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT = 0x3490;
+
     private static final String EGL_IMG_CONTEXT_PRIORITY = "EGL_IMG_context_priority";
 
+    /**
+     * https://www.khronos.org/registry/EGL/extensions/KHR/EGL_KHR_gl_colorspace.txt
+     */
+    private static final String KHR_GL_COLOR_SPACE = "EGL_KHR_gl_colorspace";
+
+    /**
+     * https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_gl_colorspace_display_p3_passthrough.txt
+     */
+    private static final String EXT_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH =
+            "EGL_EXT_gl_colorspace_display_p3_passthrough";
+
     private EGLDisplay mEglDisplay;
     private EGLConfig mEglConfig;
     private EGLContext mEglContext;
     private EGLSurface mEglSurface;
     private final int[] mEglVersion = new int[2];
     private boolean mEglReady;
-    private boolean mContextPrioritySupported;
+    private final Set<String> mExts;
+
+    public EglHelper() {
+        mExts = new HashSet<>();
+        connectDisplay();
+    }
 
     /**
-     * Initialize EGL and prepare EglSurface.
+     * Initialize render context.
      * @param surfaceHolder surface holder.
-     * @return true if EglSurface is ready.
+     * @param wideColorGamut claim if a wcg surface is necessary.
+     * @return true if the render context is ready.
      */
-    public boolean init(SurfaceHolder surfaceHolder) {
-        mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
-        if (mEglDisplay == EGL_NO_DISPLAY) {
-            Log.w(TAG, "eglGetDisplay failed: " + GLUtils.getEGLErrorString(eglGetError()));
+    public boolean init(SurfaceHolder surfaceHolder, boolean wideColorGamut) {
+        if (!hasEglDisplay() && !connectDisplay()) {
+            Log.w(TAG, "Can not connect display, abort!");
             return false;
         }
 
@@ -105,25 +130,38 @@
             return false;
         }
 
-        if (!createEglSurface(surfaceHolder)) {
+        if (!createEglSurface(surfaceHolder, wideColorGamut)) {
             Log.w(TAG, "Can't create EGLSurface!");
             return false;
         }
 
-        mContextPrioritySupported = isContextPrioritySuppported();
-
         mEglReady = true;
         return true;
     }
 
-    private boolean isContextPrioritySuppported() {
-        String[] extensions = eglQueryString(mEglDisplay, EGL_EXTENSIONS).split(" ");
-        for (String extension : extensions) {
-            if (extension.equals(EGL_IMG_CONTEXT_PRIORITY)) {
-                return true;
-            }
+    private boolean connectDisplay() {
+        mExts.clear();
+        mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+        if (!hasEglDisplay()) {
+            Log.w(TAG, "eglGetDisplay failed: " + GLUtils.getEGLErrorString(eglGetError()));
+            return false;
         }
-        return false;
+        String queryString = eglQueryString(mEglDisplay, EGL_EXTENSIONS);
+        if (!TextUtils.isEmpty(queryString)) {
+            Collections.addAll(mExts, queryString.split(" "));
+        }
+        return true;
+    }
+
+    private boolean checkExtensionCapability(String extName) {
+        return mExts.contains(extName);
+    }
+
+    private int getWcgCapability() {
+        if (checkExtensionCapability(EXT_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH)) {
+            return EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT;
+        }
+        return 0;
     }
 
     private EGLConfig chooseEglConfig() {
@@ -148,7 +186,7 @@
             EGL_RED_SIZE, 8,
             EGL_GREEN_SIZE, 8,
             EGL_BLUE_SIZE, 8,
-            EGL_ALPHA_SIZE, 8,
+            EGL_ALPHA_SIZE, 0,
             EGL_DEPTH_SIZE, 0,
             EGL_STENCIL_SIZE, 0,
             EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
@@ -160,21 +198,27 @@
     /**
      * Prepare an EglSurface.
      * @param surfaceHolder surface holder.
+     * @param wcg if need to support wcg.
      * @return true if EglSurface is ready.
      */
-    public boolean createEglSurface(SurfaceHolder surfaceHolder) {
+    public boolean createEglSurface(SurfaceHolder surfaceHolder, boolean wcg) {
         if (DEBUG) {
             Log.d(TAG, "createEglSurface start");
         }
 
         if (hasEglDisplay()) {
-            mEglSurface = eglCreateWindowSurface(mEglDisplay, mEglConfig, surfaceHolder, null, 0);
+            int[] attrs = null;
+            int wcgCapability = getWcgCapability();
+            if (wcg && checkExtensionCapability(KHR_GL_COLOR_SPACE) && wcgCapability > 0) {
+                attrs = new int[] {EGL_GL_COLORSPACE_KHR, wcgCapability, EGL_NONE};
+            }
+            mEglSurface = eglCreateWindowSurface(mEglDisplay, mEglConfig, surfaceHolder, attrs, 0);
         } else {
             Log.w(TAG, "mEglDisplay is null");
             return false;
         }
 
-        if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) {
+        if (!hasEglSurface()) {
             Log.w(TAG, "createWindowSurface failed: " + GLUtils.getEGLErrorString(eglGetError()));
             return false;
         }
@@ -221,12 +265,12 @@
         int[] attrib_list = new int[5];
         int idx = 0;
         attrib_list[idx++] = EGL_CONTEXT_CLIENT_VERSION;
-        attrib_list[idx++] = 2;
-        if (mContextPrioritySupported) {
+        attrib_list[idx++] = OPENGLES_VERSION;
+        if (checkExtensionCapability(EGL_IMG_CONTEXT_PRIORITY)) {
             attrib_list[idx++] = EGL_CONTEXT_PRIORITY_LEVEL_IMG;
             attrib_list[idx++] = EGL_CONTEXT_PRIORITY_LOW_IMG;
         }
-        attrib_list[idx++] = EGL_NONE;
+        attrib_list[idx] = EGL_NONE;
         if (hasEglDisplay()) {
             mEglContext = eglCreateContext(mEglDisplay, mEglConfig, EGL_NO_CONTEXT, attrib_list, 0);
         } else {
@@ -234,7 +278,7 @@
             return false;
         }
 
-        if (mEglContext == EGL_NO_CONTEXT) {
+        if (!hasEglContext()) {
             Log.w(TAG, "eglCreateContext failed: " + GLUtils.getEGLErrorString(eglGetError()));
             return false;
         }
@@ -260,7 +304,7 @@
      * @return true if EglContext is ready.
      */
     public boolean hasEglContext() {
-        return mEglContext != null;
+        return mEglContext != null && mEglContext != EGL_NO_CONTEXT;
     }
 
     /**
@@ -268,7 +312,7 @@
      * @return true if EglDisplay is ready.
      */
     public boolean hasEglDisplay() {
-        return mEglDisplay != null;
+        return mEglDisplay != null && mEglDisplay != EGL_NO_DISPLAY;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/GLWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/GLWallpaperRenderer.java
index 60ea1cdf..88ab9ef4 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/GLWallpaperRenderer.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/GLWallpaperRenderer.java
@@ -27,6 +27,11 @@
 public interface GLWallpaperRenderer {
 
     /**
+     * Check if the content to render is a WCG content.
+     */
+    boolean isWcgContent();
+
+    /**
      * Called when the surface is created or recreated.
      */
     void onSurfaceCreated();
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageProcessHelper.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageProcessHelper.java
index 24a4b9e..54eca0e 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageProcessHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageProcessHelper.java
@@ -116,7 +116,8 @@
             int width = bitmap.getWidth();
             int height = bitmap.getHeight();
 
-            Bitmap grayscale = Bitmap.createBitmap(width, height, bitmap.getConfig());
+            Bitmap grayscale = Bitmap.createBitmap(width, height, bitmap.getConfig(),
+                    false /* hasAlpha */, bitmap.getColorSpace());
             Canvas canvas = new Canvas(grayscale);
             ColorMatrix cm = new ColorMatrix(LUMINOSITY_MATRIX);
             Paint paint = new Paint();
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
index a8371e3..fa8269d 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
@@ -62,6 +62,7 @@
     private boolean mScissorMode;
     private float mXOffset;
     private float mYOffset;
+    private boolean mWcgContent;
 
     public ImageWallpaperRenderer(Context context, SurfaceProxy proxy) {
         mWallpaperManager = context.getSystemService(WallpaperManager.class);
@@ -94,6 +95,11 @@
     }
 
     @Override
+    public boolean isWcgContent() {
+        return mWcgContent;
+    }
+
+    @Override
     public void onSurfaceCreated() {
         glClearColor(0f, 0f, 0f, 1.0f);
         mProgram.useGLProgram(
@@ -112,7 +118,8 @@
             Log.d(TAG, "loadBitmap: mBitmap=" + mBitmap);
         }
         if (mWallpaperManager != null && mBitmap == null) {
-            mBitmap = mWallpaperManager.getBitmap();
+            mBitmap = mWallpaperManager.getBitmap(false /* hardware */);
+            mWcgContent = mWallpaperManager.wallpaperSupportsWcg(WallpaperManager.FLAG_SYSTEM);
             mWallpaperManager.forgetLoadedWallpaper();
             if (mBitmap != null) {
                 float scale = (float) mScissor.height() / mBitmap.getHeight();
@@ -231,6 +238,7 @@
         out.print(prefix); out.print("mYOffset="); out.print(mYOffset);
         out.print(prefix); out.print("threshold="); out.print(mImageProcessHelper.getThreshold());
         out.print(prefix); out.print("mReveal="); out.print(mImageRevealHelper.getReveal());
+        out.print(prefix); out.print("mWcgContent="); out.print(mWcgContent);
         mWallpaper.dump(prefix, fd, out, args);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 00220ce..c9813db 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.screenshot;
 
-import static android.content.Context.NOTIFICATION_SERVICE;
-import static android.os.AsyncTask.THREAD_POOL_EXECUTOR;
 import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI;
 import static android.view.View.VISIBLE;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
@@ -31,14 +29,10 @@
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.annotation.Nullable;
-import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.Notification;
-import android.app.NotificationManager;
 import android.app.PendingIntent;
-import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Configuration;
@@ -55,7 +49,6 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManager;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.util.DisplayMetrics;
@@ -74,20 +67,13 @@
 import android.widget.TextView;
 import android.widget.Toast;
 
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.systemui.R;
-import com.android.systemui.SystemUI;
-import com.android.systemui.SystemUIFactory;
 import com.android.systemui.dagger.qualifiers.MainResources;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.util.NotificationChannels;
 
-import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
-import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
@@ -108,29 +94,20 @@
      * POD used in the AsyncTask which saves an image in the background.
      */
     static class SaveImageInBackgroundData {
-        public Context context;
         public Bitmap image;
         public Uri imageUri;
         public Consumer<Uri> finisher;
         public GlobalScreenshot.ActionsReadyListener mActionsReadyListener;
-        public int iconSize;
-        public int previewWidth;
-        public int previewheight;
         public int errorMsgResId;
 
         void clearImage() {
             image = null;
             imageUri = null;
-            iconSize = 0;
-        }
-
-        void clearContext() {
-            context = null;
         }
     }
 
     abstract static class ActionsReadyListener {
-        abstract void onActionsReady(PendingIntent shareAction, PendingIntent editAction);
+        abstract void onActionsReady(Uri imageUri, List<Notification.Action> actions);
     }
 
     // These strings are used for communicating the action invoked to
@@ -165,13 +142,12 @@
     private static final float SCREENSHOT_CORNER_MIN_SCALE_OFFSET = .1f;
     private static final long SCREENSHOT_CORNER_TIMEOUT_MILLIS = 8000;
     private static final int MESSAGE_CORNER_TIMEOUT = 2;
-    private final int mPreviewWidth;
-    private final int mPreviewHeight;
+
+    private final ScreenshotNotificationsController mNotificationsController;
 
     private Context mContext;
     private WindowManager mWindowManager;
     private WindowManager.LayoutParams mWindowLayoutParams;
-    private NotificationManager mNotificationManager;
     private Display mDisplay;
     private DisplayMetrics mDisplayMetrics;
 
@@ -182,13 +158,9 @@
     private ImageView mScreenshotView;
     private ImageView mScreenshotFlash;
     private LinearLayout mActionsView;
-    private TextView mShareAction;
-    private TextView mEditAction;
-    private TextView mScrollAction;
 
     private AnimatorSet mScreenshotAnimation;
 
-    private int mNotificationIconSize;
     private float mBgPadding;
     private float mBgPaddingScale;
 
@@ -213,9 +185,11 @@
      * @param context everything needs a context :(
      */
     @Inject
-    public GlobalScreenshot(Context context, @MainResources Resources resources,
-            LayoutInflater layoutInflater) {
+    public GlobalScreenshot(
+            Context context, @MainResources Resources resources, LayoutInflater layoutInflater,
+            ScreenshotNotificationsController screenshotNotificationsController) {
         mContext = context;
+        mNotificationsController = screenshotNotificationsController;
 
         // Inflate the screenshot layout
         mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot, null);
@@ -223,21 +197,6 @@
         mScreenshotView = mScreenshotLayout.findViewById(R.id.global_screenshot);
         mActionsView = mScreenshotLayout.findViewById(R.id.global_screenshot_actions);
 
-        mShareAction = (TextView) layoutInflater.inflate(
-                R.layout.global_screenshot_action_chip, mActionsView, false);
-        mEditAction = (TextView) layoutInflater.inflate(
-                R.layout.global_screenshot_action_chip, mActionsView, false);
-        mScrollAction = (TextView) layoutInflater.inflate(
-                R.layout.global_screenshot_action_chip, mActionsView, false);
-
-        mShareAction.setText(com.android.internal.R.string.share);
-        mEditAction.setText(com.android.internal.R.string.screenshot_edit);
-        mScrollAction.setText("Scroll"); // TODO (mkephart): Add to resources and translate
-
-        mActionsView.addView(mShareAction);
-        mActionsView.addView(mEditAction);
-        mActionsView.addView(mScrollAction);
-
         mScreenshotFlash = mScreenshotLayout.findViewById(R.id.global_screenshot_flash);
         mScreenshotSelectorView = mScreenshotLayout.findViewById(R.id.global_screenshot_selector);
         mScreenshotLayout.setFocusable(true);
@@ -260,32 +219,14 @@
         mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
         mWindowLayoutParams.setFitWindowInsetsTypes(0 /* types */);
         mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
-        mNotificationManager =
-                (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
         mDisplay = mWindowManager.getDefaultDisplay();
         mDisplayMetrics = new DisplayMetrics();
         mDisplay.getRealMetrics(mDisplayMetrics);
 
-        // Get the various target sizes
-        mNotificationIconSize =
-                resources.getDimensionPixelSize(android.R.dimen.notification_large_icon_height);
-
         // Scale has to account for both sides of the bg
         mBgPadding = (float) resources.getDimensionPixelSize(R.dimen.global_screenshot_bg_padding);
         mBgPaddingScale = mBgPadding / mDisplayMetrics.widthPixels;
 
-        // determine the optimal preview size
-        int panelWidth = 0;
-        try {
-            panelWidth = resources.getDimensionPixelSize(R.dimen.notification_panel_width);
-        } catch (Resources.NotFoundException e) {
-        }
-        if (panelWidth <= 0) {
-            // includes notification_panel_width==match_parent (-1)
-            panelWidth = mDisplayMetrics.widthPixels;
-        }
-        mPreviewWidth = panelWidth;
-        mPreviewHeight = resources.getDimensionPixelSize(R.dimen.notification_max_height);
 
         // Setup the Camera shutter sound
         mCameraSound = new MediaActionSound();
@@ -298,22 +239,20 @@
     private void saveScreenshotInWorkerThread(
             Consumer<Uri> finisher, @Nullable ActionsReadyListener actionsReadyListener) {
         SaveImageInBackgroundData data = new SaveImageInBackgroundData();
-        data.context = mContext;
         data.image = mScreenBitmap;
-        data.iconSize = mNotificationIconSize;
         data.finisher = finisher;
         data.mActionsReadyListener = actionsReadyListener;
-        data.previewWidth = mPreviewWidth;
-        data.previewheight = mPreviewHeight;
         if (mSaveInBgTask != null) {
             mSaveInBgTask.cancel(false);
         }
-        mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data, mNotificationManager)
-                .execute();
-    }
 
-    private void saveScreenshotInWorkerThread(Consumer<Uri> finisher) {
-        saveScreenshotInWorkerThread(finisher, null);
+        if (!DeviceConfig.getBoolean(
+                NAMESPACE_SYSTEMUI, SCREENSHOT_CORNER_FLOW, false)) {
+            mNotificationsController.reset();
+            mNotificationsController.setImage(mScreenBitmap);
+            mNotificationsController.showSavingScreenshotNotification();
+        }
+        mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data).execute();
     }
 
     /**
@@ -328,7 +267,7 @@
         // Take the screenshot
         mScreenBitmap = SurfaceControl.screenshot(crop, width, height, rot);
         if (mScreenBitmap == null) {
-            notifyScreenshotError(mContext, mNotificationManager,
+            mNotificationsController.notifyScreenshotError(
                     R.string.screenshot_failed_to_capture_text);
             finisher.accept(null);
             return;
@@ -373,12 +312,8 @@
                         if (rect != null) {
                             if (rect.width() != 0 && rect.height() != 0) {
                                 // Need mScreenshotLayout to handle it after the view disappears
-                                mScreenshotLayout.post(new Runnable() {
-                                    public void run() {
-                                        takeScreenshot(finisher, statusBarVisible, navBarVisible,
-                                                rect);
-                                    }
-                                });
+                                mScreenshotLayout.post(() -> takeScreenshot(
+                                        finisher, statusBarVisible, navBarVisible, rect));
                             }
                         }
 
@@ -464,15 +399,30 @@
             public void onAnimationEnd(Animator animation) {
                 // Save the screenshot once we have a bit of time now
                 if (!useCornerFlow) {
-                    saveScreenshotInWorkerThread(finisher);
+                    saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
+                        @Override
+                        void onActionsReady(Uri uri, List<Notification.Action> actions) {
+                            if (uri == null) {
+                                mNotificationsController.notifyScreenshotError(
+                                        R.string.screenshot_failed_to_capture_text);
+                            } else {
+                                mNotificationsController
+                                        .showScreenshotActionsNotification(uri, actions);
+                            }
+                        }
+                    });
                     clearScreenshot();
                 } else {
                     saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
                         @Override
-                        void onActionsReady(PendingIntent shareAction, PendingIntent editAction) {
-                            mScreenshotHandler.post(() ->
-                                    createScreenshotActionsShadeAnimation(shareAction, editAction)
-                                            .start());
+                        void onActionsReady(Uri uri, List<Notification.Action> actions) {
+                            if (uri == null) {
+                                mNotificationsController.notifyScreenshotError(
+                                        R.string.screenshot_failed_to_capture_text);
+                            } else {
+                                mScreenshotHandler.post(() ->
+                                        createScreenshotActionsShadeAnimation(actions).start());
+                            }
                         }
                     });
                     mScreenshotHandler.sendMessageDelayed(
@@ -679,8 +629,33 @@
         return anim;
     }
 
-    private ValueAnimator createScreenshotActionsShadeAnimation(
-            PendingIntent shareAction, PendingIntent editAction) {
+    private ValueAnimator createScreenshotActionsShadeAnimation(List<Notification.Action> actions) {
+        LayoutInflater inflater = LayoutInflater.from(mContext);
+        mActionsView.removeAllViews();
+
+        for (Notification.Action action : actions) {
+            TextView actionChip = (TextView) inflater.inflate(
+                    R.layout.global_screenshot_action_chip, mActionsView, false);
+            actionChip.setText(action.title);
+            actionChip.setOnClickListener(v -> {
+                try {
+                    action.actionIntent.send();
+                    clearScreenshot();
+                } catch (PendingIntent.CanceledException e) {
+                    Log.e(TAG,
+                            String.format("Intent cancelled (title: %s)", action.title), e);
+                }
+            });
+            mActionsView.addView(actionChip);
+        }
+        TextView scrollChip = (TextView) inflater.inflate(
+                R.layout.global_screenshot_action_chip, mActionsView, false);
+        Toast scrollNotImplemented = Toast.makeText(
+                mContext, "Not implemented", Toast.LENGTH_SHORT);
+        scrollChip.setText("Scroll"); // TODO (mkephart): add resource and translate
+        scrollChip.setOnClickListener(v -> scrollNotImplemented.show());
+        mActionsView.addView(scrollChip);
+
         ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
         mActionsView.setY(mDisplayMetrics.heightPixels);
         mActionsView.setVisibility(VISIBLE);
@@ -698,158 +673,11 @@
             public void onAnimationEnd(Animator animation) {
                 super.onAnimationEnd(animation);
                 mScreenshotView.requestFocus();
-                mShareAction.setOnClickListener(v -> {
-                    try {
-                        shareAction.send();
-                        clearScreenshot();
-                    } catch (PendingIntent.CanceledException e) {
-                        Log.e(TAG, "Share intent cancelled", e);
-                    }
-                });
-                mEditAction.setOnClickListener(v -> {
-                    try {
-                        editAction.send();
-                        clearScreenshot();
-                    } catch (PendingIntent.CanceledException e) {
-                        Log.e(TAG, "Edit intent cancelled", e);
-                    }
-                });
-                Toast scrollNotImplemented = Toast.makeText(
-                        mContext, "Not implemented", Toast.LENGTH_SHORT);
-                mScrollAction.setOnClickListener(v -> scrollNotImplemented.show());
             }
         });
         return animator;
     }
 
-    static void notifyScreenshotError(Context context, NotificationManager nManager, int msgResId) {
-        Resources r = context.getResources();
-        String errorMsg = r.getString(msgResId);
-
-        // Repurpose the existing notification to notify the user of the error
-        Notification.Builder b = new Notification.Builder(context, NotificationChannels.ALERTS)
-                .setTicker(r.getString(R.string.screenshot_failed_title))
-                .setContentTitle(r.getString(R.string.screenshot_failed_title))
-                .setContentText(errorMsg)
-                .setSmallIcon(R.drawable.stat_notify_image_error)
-                .setWhen(System.currentTimeMillis())
-                .setVisibility(Notification.VISIBILITY_PUBLIC) // ok to show outside lockscreen
-                .setCategory(Notification.CATEGORY_ERROR)
-                .setAutoCancel(true)
-                .setColor(context.getColor(
-                        com.android.internal.R.color.system_notification_accent_color));
-        final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
-                Context.DEVICE_POLICY_SERVICE);
-        final Intent intent = dpm.createAdminSupportIntent(
-                DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE);
-        if (intent != null) {
-            final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
-                    context, 0, intent, 0, null, UserHandle.CURRENT);
-            b.setContentIntent(pendingIntent);
-        }
-
-        SystemUI.overrideNotificationAppName(context, b, true);
-
-        Notification n = new Notification.BigTextStyle(b)
-                .bigText(errorMsg)
-                .build();
-        nManager.notify(SystemMessage.NOTE_GLOBAL_SCREENSHOT, n);
-    }
-
-    @VisibleForTesting
-    static CompletableFuture<List<Notification.Action>> getSmartActionsFuture(String screenshotId,
-            Bitmap image, ScreenshotNotificationSmartActionsProvider smartActionsProvider,
-            boolean smartActionsEnabled, boolean isManagedProfile) {
-        if (!smartActionsEnabled) {
-            Slog.i(TAG, "Screenshot Intelligence not enabled, returning empty list.");
-            return CompletableFuture.completedFuture(Collections.emptyList());
-        }
-        if (image.getConfig() != Bitmap.Config.HARDWARE) {
-            Slog.w(TAG, String.format(
-                    "Bitmap expected: Hardware, Bitmap found: %s. Returning empty list.",
-                    image.getConfig()));
-            return CompletableFuture.completedFuture(Collections.emptyList());
-        }
-
-        Slog.d(TAG, "Screenshot from a managed profile: " + isManagedProfile);
-        CompletableFuture<List<Notification.Action>> smartActionsFuture;
-        long startTimeMs = SystemClock.uptimeMillis();
-        try {
-            ActivityManager.RunningTaskInfo runningTask =
-                    ActivityManagerWrapper.getInstance().getRunningTask();
-            ComponentName componentName =
-                    (runningTask != null && runningTask.topActivity != null)
-                            ? runningTask.topActivity
-                            : new ComponentName("", "");
-            smartActionsFuture = smartActionsProvider.getActions(screenshotId, image,
-                    componentName,
-                    isManagedProfile);
-        } catch (Throwable e) {
-            long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs;
-            smartActionsFuture = CompletableFuture.completedFuture(Collections.emptyList());
-            Slog.e(TAG, "Failed to get future for screenshot notification smart actions.", e);
-            notifyScreenshotOp(screenshotId, smartActionsProvider,
-                    ScreenshotNotificationSmartActionsProvider.ScreenshotOp.REQUEST_SMART_ACTIONS,
-                    ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.ERROR,
-                    waitTimeMs);
-        }
-        return smartActionsFuture;
-    }
-
-    @VisibleForTesting
-    static List<Notification.Action> getSmartActions(String screenshotId,
-            CompletableFuture<List<Notification.Action>> smartActionsFuture, int timeoutMs,
-            ScreenshotNotificationSmartActionsProvider smartActionsProvider) {
-        long startTimeMs = SystemClock.uptimeMillis();
-        try {
-            List<Notification.Action> actions = smartActionsFuture.get(timeoutMs,
-                    TimeUnit.MILLISECONDS);
-            long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs;
-            Slog.d(TAG, String.format("Got %d smart actions. Wait time: %d ms",
-                    actions.size(), waitTimeMs));
-            notifyScreenshotOp(screenshotId, smartActionsProvider,
-                    ScreenshotNotificationSmartActionsProvider.ScreenshotOp.WAIT_FOR_SMART_ACTIONS,
-                    ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.SUCCESS,
-                    waitTimeMs);
-            return actions;
-        } catch (Throwable e) {
-            long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs;
-            Slog.e(TAG, String.format("Error getting smart actions. Wait time: %d ms", waitTimeMs),
-                    e);
-            ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus status =
-                    (e instanceof TimeoutException)
-                            ? ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.TIMEOUT
-                            : ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.ERROR;
-            notifyScreenshotOp(screenshotId, smartActionsProvider,
-                    ScreenshotNotificationSmartActionsProvider.ScreenshotOp.WAIT_FOR_SMART_ACTIONS,
-                    status, waitTimeMs);
-            return Collections.emptyList();
-        }
-    }
-
-    static void notifyScreenshotOp(String screenshotId,
-            ScreenshotNotificationSmartActionsProvider smartActionsProvider,
-            ScreenshotNotificationSmartActionsProvider.ScreenshotOp op,
-            ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus status, long durationMs) {
-        try {
-            smartActionsProvider.notifyOp(screenshotId, op, status, durationMs);
-        } catch (Throwable e) {
-            Slog.e(TAG, "Error in notifyScreenshotOp: ", e);
-        }
-    }
-
-    static void notifyScreenshotAction(Context context, String screenshotId, String action,
-            boolean isSmartAction) {
-        try {
-            ScreenshotNotificationSmartActionsProvider provider =
-                    SystemUIFactory.getInstance().createScreenshotNotificationSmartActionsProvider(
-                            context, THREAD_POOL_EXECUTOR, new Handler());
-            provider.notifyAction(screenshotId, action, isSmartAction);
-        } catch (Throwable e) {
-            Slog.e(TAG, "Error in notifyScreenshotAction: ", e);
-        }
-    }
-
     /**
      * Receiver to proxy the share or edit intent, used to clean up the notification and send
      * appropriate signals to the system (ie. to dismiss the keyguard if necessary).
@@ -878,7 +706,7 @@
 
                 Intent actionIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT);
                 if (intent.getBooleanExtra(EXTRA_CANCEL_NOTIFICATION, false)) {
-                    cancelScreenshotNotification(context);
+                    ScreenshotNotificationsController.cancelScreenshotNotification(context);
                 }
                 ActivityOptions opts = ActivityOptions.makeBasic();
                 opts.setDisallowEnterPictureInPictureWhileLaunching(
@@ -897,8 +725,8 @@
             if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) {
                 String actionType = Intent.ACTION_EDIT.equals(intent.getAction()) ? ACTION_TYPE_EDIT
                         : ACTION_TYPE_SHARE;
-                notifyScreenshotAction(context, intent.getStringExtra(EXTRA_ID),
-                        actionType, false);
+                ScreenshotSmartActions.notifyScreenshotAction(
+                        context, intent.getStringExtra(EXTRA_ID), actionType, false);
             }
         }
     }
@@ -910,7 +738,7 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             // Clear the notification only after the user has chosen a share action
-            cancelScreenshotNotification(context);
+            ScreenshotNotificationsController.cancelScreenshotNotification(context);
         }
     }
 
@@ -925,15 +753,14 @@
             }
 
             // Clear the notification when the image is deleted
-            cancelScreenshotNotification(context);
+            ScreenshotNotificationsController.cancelScreenshotNotification(context);
 
             // And delete the image from the media store
             final Uri uri = Uri.parse(intent.getStringExtra(SCREENSHOT_URI_ID));
             new DeleteImageInBackgroundTask(context).execute(uri);
             if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) {
-                notifyScreenshotAction(context, intent.getStringExtra(EXTRA_ID),
-                        ACTION_TYPE_DELETE,
-                        false);
+                ScreenshotSmartActions.notifyScreenshotAction(
+                        context, intent.getStringExtra(EXTRA_ID), ACTION_TYPE_DELETE, false);
             }
         }
     }
@@ -952,15 +779,8 @@
             context.startActivityAsUser(actionIntent, opts.toBundle(),
                     UserHandle.CURRENT);
 
-            notifyScreenshotAction(context, intent.getStringExtra(EXTRA_ID),
-                    actionType,
-                    true);
+            ScreenshotSmartActions.notifyScreenshotAction(
+                    context, intent.getStringExtra(EXTRA_ID), actionType, true);
         }
     }
-
-    private static void cancelScreenshotNotification(Context context) {
-        final NotificationManager nm =
-                (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
-        nm.cancel(SystemMessage.NOTE_GLOBAL_SCREENSHOT);
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index 2f401e5..6bad15c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -18,7 +18,6 @@
 
 import android.app.ActivityTaskManager;
 import android.app.Notification;
-import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.ClipData;
 import android.content.ClipDescription;
@@ -30,12 +29,7 @@
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.ColorMatrix;
-import android.graphics.ColorMatrixColorFilter;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.Picture;
+import android.graphics.drawable.Icon;
 import android.media.ExifInterface;
 import android.net.Uri;
 import android.os.AsyncTask;
@@ -57,13 +51,8 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
-import com.android.internal.messages.nano.SystemMessageProto;
 import com.android.systemui.R;
-import com.android.systemui.SystemUI;
 import com.android.systemui.SystemUIFactory;
-import com.android.systemui.util.NotificationChannels;
-
-import libcore.io.IoUtils;
 
 import java.io.File;
 import java.io.IOException;
@@ -76,6 +65,7 @@
 import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Date;
 import java.util.List;
 import java.util.Objects;
@@ -93,22 +83,17 @@
     private static final String SCREENSHOT_ID_TEMPLATE = "Screenshot_%s";
     private static final String SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)";
 
+    private final Context mContext;
     private final GlobalScreenshot.SaveImageInBackgroundData mParams;
-    private final NotificationManager mNotificationManager;
-    private final Notification.Builder mNotificationBuilder, mPublicNotificationBuilder;
     private final String mImageFileName;
     private final long mImageTime;
-    private final Notification.BigPictureStyle mNotificationStyle;
-    private final int mImageWidth;
-    private final int mImageHeight;
     private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider;
     private final String mScreenshotId;
     private final boolean mSmartActionsEnabled;
     private final Random mRandom = new Random();
 
-    SaveImageInBackgroundTask(Context context, GlobalScreenshot.SaveImageInBackgroundData data,
-            NotificationManager nManager) {
-        Resources r = context.getResources();
+    SaveImageInBackgroundTask(Context context, GlobalScreenshot.SaveImageInBackgroundData data) {
+        mContext = context;
 
         // Prepare all the output metadata
         mParams = data;
@@ -129,143 +114,6 @@
             // If smart actions is not enabled use empty implementation.
             mSmartActionsProvider = new ScreenshotNotificationSmartActionsProvider();
         }
-
-        // Create the large notification icon
-        mImageWidth = data.image.getWidth();
-        mImageHeight = data.image.getHeight();
-        int iconSize = data.iconSize;
-        int previewWidth = data.previewWidth;
-        int previewHeight = data.previewheight;
-
-        Paint paint = new Paint();
-        ColorMatrix desat = new ColorMatrix();
-        desat.setSaturation(0.25f);
-        paint.setColorFilter(new ColorMatrixColorFilter(desat));
-        Matrix matrix = new Matrix();
-        int overlayColor = 0x40FFFFFF;
-
-        matrix.setTranslate((previewWidth - mImageWidth) / 2,
-                (previewHeight - mImageHeight) / 2);
-        Bitmap picture = generateAdjustedHwBitmap(data.image, previewWidth, previewHeight,
-                matrix, paint, overlayColor);
-
-        // Note, we can't use the preview for the small icon, since it is non-square
-        float scale = (float) iconSize / Math.min(mImageWidth, mImageHeight);
-        matrix.setScale(scale, scale);
-        matrix.postTranslate((iconSize - (scale * mImageWidth)) / 2,
-                (iconSize - (scale * mImageHeight)) / 2);
-        Bitmap icon = generateAdjustedHwBitmap(data.image, iconSize, iconSize, matrix, paint,
-                overlayColor);
-
-        mNotificationManager = nManager;
-        final long now = System.currentTimeMillis();
-
-        // Setup the notification
-        mNotificationStyle = new Notification.BigPictureStyle()
-                .bigPicture(picture.createAshmemBitmap());
-
-        // The public notification will show similar info but with the actual screenshot omitted
-        mPublicNotificationBuilder =
-                new Notification.Builder(context, NotificationChannels.SCREENSHOTS_HEADSUP)
-                        .setContentTitle(r.getString(R.string.screenshot_saving_title))
-                        .setSmallIcon(R.drawable.stat_notify_image)
-                        .setCategory(Notification.CATEGORY_PROGRESS)
-                        .setWhen(now)
-                        .setShowWhen(true)
-                        .setColor(r.getColor(
-                                com.android.internal.R.color.system_notification_accent_color));
-        SystemUI.overrideNotificationAppName(context, mPublicNotificationBuilder, true);
-
-        mNotificationBuilder = new Notification.Builder(context,
-                NotificationChannels.SCREENSHOTS_HEADSUP)
-                .setContentTitle(r.getString(R.string.screenshot_saving_title))
-                .setSmallIcon(R.drawable.stat_notify_image)
-                .setWhen(now)
-                .setShowWhen(true)
-                .setColor(r.getColor(
-                        com.android.internal.R.color.system_notification_accent_color))
-                .setStyle(mNotificationStyle)
-                .setPublicVersion(mPublicNotificationBuilder.build());
-        mNotificationBuilder.setFlag(Notification.FLAG_NO_CLEAR, true);
-        SystemUI.overrideNotificationAppName(context, mNotificationBuilder, true);
-
-        mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT,
-                mNotificationBuilder.build());
-
-        /**
-         * NOTE: The following code prepares the notification builder for updating the
-         * notification after the screenshot has been written to disk.
-         */
-
-        // On the tablet, the large icon makes the notification appear as if it is clickable
-        // (and on small devices, the large icon is not shown) so defer showing the large icon
-        // until we compose the final post-save notification below.
-        mNotificationBuilder.setLargeIcon(icon.createAshmemBitmap());
-        // But we still don't set it for the expanded view, allowing the smallIcon to show here.
-        mNotificationStyle.bigLargeIcon((Bitmap) null);
-    }
-
-    private int getUserHandleOfForegroundApplication(Context context) {
-        // This logic matches
-        // com.android.systemui.statusbar.phone.PhoneStatusBarPolicy#updateManagedProfile
-        try {
-            return ActivityTaskManager.getService().getLastResumedActivityUserId();
-        } catch (RemoteException e) {
-            Slog.w(TAG, "getUserHandleOfForegroundApplication: ", e);
-            return context.getUserId();
-        }
-    }
-
-    private boolean isManagedProfile(Context context) {
-        UserManager manager = UserManager.get(context);
-        UserInfo info = manager.getUserInfo(getUserHandleOfForegroundApplication(context));
-        return info.isManagedProfile();
-    }
-
-    private List<Notification.Action> buildSmartActions(
-            List<Notification.Action> actions, Context context) {
-        List<Notification.Action> broadcastActions = new ArrayList<>();
-        for (Notification.Action action : actions) {
-            // Proxy smart actions through {@link GlobalScreenshot.SmartActionsReceiver}
-            // for logging smart actions.
-            Bundle extras = action.getExtras();
-            String actionType = extras.getString(
-                    ScreenshotNotificationSmartActionsProvider.ACTION_TYPE,
-                    ScreenshotNotificationSmartActionsProvider.DEFAULT_ACTION_TYPE);
-            Intent intent = new Intent(context,
-                    GlobalScreenshot.SmartActionsReceiver.class).putExtra(
-                    GlobalScreenshot.EXTRA_ACTION_INTENT, action.actionIntent);
-            addIntentExtras(mScreenshotId, intent, actionType, mSmartActionsEnabled);
-            PendingIntent broadcastIntent = PendingIntent.getBroadcast(context,
-                    mRandom.nextInt(),
-                    intent,
-                    PendingIntent.FLAG_CANCEL_CURRENT);
-            broadcastActions.add(new Notification.Action.Builder(action.getIcon(), action.title,
-                    broadcastIntent).setContextual(true).addExtras(extras).build());
-        }
-        return broadcastActions;
-    }
-
-    private static void addIntentExtras(String screenshotId, Intent intent, String actionType,
-            boolean smartActionsEnabled) {
-        intent
-                .putExtra(GlobalScreenshot.EXTRA_ACTION_TYPE, actionType)
-                .putExtra(GlobalScreenshot.EXTRA_ID, screenshotId)
-                .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED, smartActionsEnabled);
-    }
-
-    /**
-     * Generates a new hardware bitmap with specified values, copying the content from the
-     * passed in bitmap.
-     */
-    private Bitmap generateAdjustedHwBitmap(Bitmap bitmap, int width, int height, Matrix matrix,
-            Paint paint, int color) {
-        Picture picture = new Picture();
-        Canvas canvas = picture.beginRecording(width, height);
-        canvas.drawColor(color);
-        canvas.drawBitmap(bitmap, matrix, paint);
-        picture.endRecording();
-        return Bitmap.createBitmap(picture);
     }
 
     @Override
@@ -278,15 +126,15 @@
         // so bump it back up so that we save a little quicker.
         Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
 
-        Context context = mParams.context;
-        ContentResolver resolver = context.getContentResolver();
+        ContentResolver resolver = mContext.getContentResolver();
         Bitmap image = mParams.image;
-        Resources r = context.getResources();
+        Resources r = mContext.getResources();
 
         try {
             CompletableFuture<List<Notification.Action>> smartActionsFuture =
-                    GlobalScreenshot.getSmartActionsFuture(mScreenshotId, image,
-                            mSmartActionsProvider, mSmartActionsEnabled, isManagedProfile(context));
+                    ScreenshotSmartActions.getSmartActionsFuture(
+                            mScreenshotId, image, mSmartActionsProvider,
+                            mSmartActionsEnabled, isManagedProfile(mContext));
 
             // Save the screenshot to the MediaStore
             final ContentValues values = new ContentValues();
@@ -347,8 +195,9 @@
                 throw e;
             }
 
-            populateNotificationActions(context, r, uri, smartActionsFuture, mNotificationBuilder);
-
+            List<Notification.Action> actions =
+                    populateNotificationActions(mContext, r, uri, smartActionsFuture);
+            mParams.mActionsReadyListener.onActionsReady(uri, actions);
             mParams.imageUri = uri;
             mParams.image = null;
             mParams.errorMsgResId = 0;
@@ -358,6 +207,7 @@
             Slog.e(TAG, "unable to save screenshot", e);
             mParams.clearImage();
             mParams.errorMsgResId = R.string.screenshot_failed_to_save_text;
+            mParams.mActionsReadyListener.onActionsReady(null, null);
         }
 
         // Recycle the bitmap data
@@ -368,10 +218,24 @@
         return null;
     }
 
+    @Override
+    protected void onPostExecute(Void params) {
+        mParams.finisher.accept(mParams.imageUri);
+    }
+
+    @Override
+    protected void onCancelled(Void params) {
+        // If we are cancelled while the task is running in the background, we may get null
+        // params. The finisher is expected to always be called back, so just use the baked-in
+        // params from the ctor in any case.
+        mParams.mActionsReadyListener.onActionsReady(null, null);
+        mParams.finisher.accept(null);
+        mParams.clearImage();
+    }
+
     @VisibleForTesting
-    void populateNotificationActions(Context context, Resources r, Uri uri,
-            CompletableFuture<List<Notification.Action>> smartActionsFuture,
-            Notification.Builder notificationBuilder) {
+    List<Notification.Action> populateNotificationActions(Context context, Resources r, Uri uri,
+            CompletableFuture<List<Notification.Action>> smartActionsFuture) {
         // Note: Both the share and edit actions are proxied through ActionProxyReceiver in
         // order to do some common work like dismissing the keyguard and sending
         // closeSystemWindows
@@ -399,10 +263,10 @@
         PendingIntent chooserAction = PendingIntent.getBroadcast(context, requestCode,
                 new Intent(context, GlobalScreenshot.TargetChosenReceiver.class),
                 PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
-        Intent sharingChooserIntent = Intent.createChooser(sharingIntent, null,
-                chooserAction.getIntentSender())
-                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK)
-                .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        Intent sharingChooserIntent =
+                Intent.createChooser(sharingIntent, null, chooserAction.getIntentSender())
+                        .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK)
+                        .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
 
         // Create a share action for the notification
         PendingIntent shareAction = PendingIntent.getBroadcastAsUser(context, requestCode,
@@ -414,10 +278,10 @@
                                 mSmartActionsEnabled)
                         .setAction(Intent.ACTION_SEND),
                 PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
+
         Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder(
-                R.drawable.ic_screenshot_share,
+                Icon.createWithResource(r, R.drawable.ic_screenshot_share),
                 r.getString(com.android.internal.R.string.share), shareAction);
-        notificationBuilder.addAction(shareActionBuilder.build());
 
         // Create an edit intent, if a specific package is provided as the editor, then
         // launch that directly
@@ -443,12 +307,8 @@
                         .setAction(Intent.ACTION_EDIT),
                 PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
         Notification.Action.Builder editActionBuilder = new Notification.Action.Builder(
-                R.drawable.ic_screenshot_edit,
+                Icon.createWithResource(r, R.drawable.ic_screenshot_edit),
                 r.getString(com.android.internal.R.string.screenshot_edit), editAction);
-        notificationBuilder.addAction(editActionBuilder.build());
-        if (mParams.mActionsReadyListener != null) {
-            mParams.mActionsReadyListener.onActionsReady(shareAction, editAction);
-        }
 
         // Create a delete action for the notification
         PendingIntent deleteAction = PendingIntent.getBroadcast(context, requestCode,
@@ -459,88 +319,73 @@
                                 mSmartActionsEnabled),
                 PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
         Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder(
-                R.drawable.ic_screenshot_delete,
+                Icon.createWithResource(r, R.drawable.ic_screenshot_delete),
                 r.getString(com.android.internal.R.string.delete), deleteAction);
-        notificationBuilder.addAction(deleteActionBuilder.build());
 
+        ArrayList<Notification.Action> actions = new ArrayList<>(
+                Arrays.asList(shareActionBuilder.build(), editActionBuilder.build(),
+                        deleteActionBuilder.build()));
         if (mSmartActionsEnabled) {
-            int timeoutMs = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
-                    SystemUiDeviceConfigFlags
-                            .SCREENSHOT_NOTIFICATION_SMART_ACTIONS_TIMEOUT_MS,
+            int timeoutMs = DeviceConfig.getInt(
+                    DeviceConfig.NAMESPACE_SYSTEMUI,
+                    SystemUiDeviceConfigFlags.SCREENSHOT_NOTIFICATION_SMART_ACTIONS_TIMEOUT_MS,
                     1000);
-            List<Notification.Action> smartActions = GlobalScreenshot.getSmartActions(mScreenshotId,
-                    smartActionsFuture, timeoutMs, mSmartActionsProvider);
-            smartActions = buildSmartActions(smartActions, context);
-            for (Notification.Action action : smartActions) {
-                notificationBuilder.addAction(action);
-            }
+            actions.addAll(buildSmartActions(
+                    ScreenshotSmartActions.getSmartActions(
+                            mScreenshotId, smartActionsFuture, timeoutMs, mSmartActionsProvider),
+                    context));
+        }
+        return actions;
+    }
+
+    private int getUserHandleOfForegroundApplication(Context context) {
+        // This logic matches
+        // com.android.systemui.statusbar.phone.PhoneStatusBarPolicy#updateManagedProfile
+        try {
+            return ActivityTaskManager.getService().getLastResumedActivityUserId();
+        } catch (RemoteException e) {
+            Slog.w(TAG, "getUserHandleOfForegroundApplication: ", e);
+            return context.getUserId();
         }
     }
 
-    @Override
-    protected void onPostExecute(Void params) {
-        if (mParams.errorMsgResId != 0) {
-            // Show a message that we've failed to save the image to disk
-            GlobalScreenshot.notifyScreenshotError(mParams.context, mNotificationManager,
-                    mParams.errorMsgResId);
-        } else {
-            if (mParams.mActionsReadyListener != null) {
-                // Cancel the "saving screenshot" notification
-                mNotificationManager.cancel(
-                        SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT);
-            } else {
-                // Show the final notification to indicate screenshot saved
-                Context context = mParams.context;
-                Resources r = context.getResources();
+    private boolean isManagedProfile(Context context) {
+        UserManager manager = UserManager.get(context);
+        UserInfo info = manager.getUserInfo(getUserHandleOfForegroundApplication(context));
+        return info.isManagedProfile();
+    }
 
-                // Create the intent to show the screenshot in gallery
-                Intent launchIntent = new Intent(Intent.ACTION_VIEW);
-                launchIntent.setDataAndType(mParams.imageUri, "image/png");
-                launchIntent.setFlags(
-                        Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION);
-
-                final long now = System.currentTimeMillis();
-
-                // Update the text and the icon for the existing notification
-                mPublicNotificationBuilder
-                        .setContentTitle(r.getString(R.string.screenshot_saved_title))
-                        .setContentText(r.getString(R.string.screenshot_saved_text))
-                        .setContentIntent(
-                                PendingIntent.getActivity(mParams.context, 0, launchIntent, 0))
-                        .setWhen(now)
-                        .setAutoCancel(true)
-                        .setColor(context.getColor(
-                                com.android.internal.R.color.system_notification_accent_color));
-                mNotificationBuilder
-                        .setContentTitle(r.getString(R.string.screenshot_saved_title))
-                        .setContentText(r.getString(R.string.screenshot_saved_text))
-                        .setContentIntent(PendingIntent.getActivity(mParams.context, 0,
-                                launchIntent, 0))
-                        .setWhen(now)
-                        .setAutoCancel(true)
-                        .setColor(context.getColor(
-                                com.android.internal.R.color.system_notification_accent_color))
-                        .setPublicVersion(mPublicNotificationBuilder.build())
-                        .setFlag(Notification.FLAG_NO_CLEAR, false);
-
-                mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT,
-                        mNotificationBuilder.build());
-            }
+    private List<Notification.Action> buildSmartActions(
+            List<Notification.Action> actions, Context context) {
+        List<Notification.Action> broadcastActions = new ArrayList<>();
+        for (Notification.Action action : actions) {
+            // Proxy smart actions through {@link GlobalScreenshot.SmartActionsReceiver}
+            // for logging smart actions.
+            Bundle extras = action.getExtras();
+            String actionType = extras.getString(
+                    ScreenshotNotificationSmartActionsProvider.ACTION_TYPE,
+                    ScreenshotNotificationSmartActionsProvider.DEFAULT_ACTION_TYPE);
+            Intent intent = new Intent(context,
+                    GlobalScreenshot.SmartActionsReceiver.class).putExtra(
+                    GlobalScreenshot.EXTRA_ACTION_INTENT, action.actionIntent);
+            addIntentExtras(mScreenshotId, intent, actionType, mSmartActionsEnabled);
+            PendingIntent broadcastIntent = PendingIntent.getBroadcast(context,
+                    mRandom.nextInt(),
+                    intent,
+                    PendingIntent.FLAG_CANCEL_CURRENT);
+            broadcastActions.add(new Notification.Action.Builder(action.getIcon(), action.title,
+                    broadcastIntent).setContextual(true).addExtras(extras).build());
         }
-        mParams.finisher.accept(mParams.imageUri);
-        mParams.clearContext();
+        return broadcastActions;
     }
 
-    @Override
-    protected void onCancelled(Void params) {
-        // If we are cancelled while the task is running in the background, we may get null
-        // params. The finisher is expected to always be called back, so just use the baked-in
-        // params from the ctor in any case.
-        mParams.finisher.accept(null);
-        mParams.clearImage();
-        mParams.clearContext();
-
-        // Cancel the posted notification
-        mNotificationManager.cancel(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT);
+    private static void addIntentExtras(String screenshotId, Intent intent, String actionType,
+            boolean smartActionsEnabled) {
+        intent
+                .putExtra(GlobalScreenshot.EXTRA_ACTION_TYPE, actionType)
+                .putExtra(GlobalScreenshot.EXTRA_ID, screenshotId)
+                .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED, smartActionsEnabled);
     }
+
+
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
new file mode 100644
index 0000000..42fca94
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot;
+
+import static android.content.Context.NOTIFICATION_SERVICE;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Picture;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.util.DisplayMetrics;
+import android.view.WindowManager;
+
+import com.android.internal.messages.nano.SystemMessageProto;
+import com.android.systemui.R;
+import com.android.systemui.SystemUI;
+import com.android.systemui.util.NotificationChannels;
+
+import java.util.List;
+
+import javax.inject.Inject;
+
+/**
+ * Convenience class to handle showing and hiding notifications while taking a screenshot.
+ */
+public class ScreenshotNotificationsController {
+    private static final String TAG = "ScreenshotNotificationManager";
+
+    private final Context mContext;
+    private final Resources mResources;
+    private final NotificationManager mNotificationManager;
+    private final Notification.BigPictureStyle mNotificationStyle;
+
+    private int mIconSize;
+    private int mPreviewWidth, mPreviewHeight;
+    private Notification.Builder mNotificationBuilder, mPublicNotificationBuilder;
+
+    @Inject
+    ScreenshotNotificationsController(Context context, WindowManager windowManager) {
+        mContext = context;
+        mResources = context.getResources();
+
+        mNotificationManager =
+                (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
+
+        mIconSize = mResources.getDimensionPixelSize(
+                android.R.dimen.notification_large_icon_height);
+
+        DisplayMetrics displayMetrics = new DisplayMetrics();
+        windowManager.getDefaultDisplay().getRealMetrics(displayMetrics);
+
+
+        // determine the optimal preview size
+        int panelWidth = 0;
+        try {
+            panelWidth = mResources.getDimensionPixelSize(R.dimen.notification_panel_width);
+        } catch (Resources.NotFoundException e) {
+        }
+        if (panelWidth <= 0) {
+            // includes notification_panel_width==match_parent (-1)
+            panelWidth = displayMetrics.widthPixels;
+        }
+        mPreviewWidth = panelWidth;
+        mPreviewHeight = mResources.getDimensionPixelSize(R.dimen.notification_max_height);
+
+        // Setup the notification
+        mNotificationStyle = new Notification.BigPictureStyle();
+    }
+
+    /**
+     * Resets the notification builders.
+     */
+    public void reset() {
+        // The public notification will show similar info but with the actual screenshot omitted
+        mPublicNotificationBuilder =
+                new Notification.Builder(mContext, NotificationChannels.SCREENSHOTS_HEADSUP);
+        mNotificationBuilder =
+                new Notification.Builder(mContext, NotificationChannels.SCREENSHOTS_HEADSUP);
+    }
+
+    /**
+     * Sets the current screenshot bitmap.
+     *
+     * @param image the bitmap of the current screenshot (used for preview)
+     */
+    public void setImage(Bitmap image) {
+        // Create the large notification icon
+        int imageWidth = image.getWidth();
+        int imageHeight = image.getHeight();
+
+        Paint paint = new Paint();
+        ColorMatrix desat = new ColorMatrix();
+        desat.setSaturation(0.25f);
+        paint.setColorFilter(new ColorMatrixColorFilter(desat));
+        Matrix matrix = new Matrix();
+        int overlayColor = 0x40FFFFFF;
+
+        matrix.setTranslate((mPreviewWidth - imageWidth) / 2f, (mPreviewHeight - imageHeight) / 2f);
+
+        Bitmap picture = generateAdjustedHwBitmap(
+                image, mPreviewWidth, mPreviewHeight, matrix, paint, overlayColor);
+
+        mNotificationStyle.bigPicture(picture.createAshmemBitmap());
+
+        // Note, we can't use the preview for the small icon, since it is non-square
+        float scale = (float) mIconSize / Math.min(imageWidth, imageHeight);
+        matrix.setScale(scale, scale);
+        matrix.postTranslate(
+                (mIconSize - (scale * imageWidth)) / 2,
+                (mIconSize - (scale * imageHeight)) / 2);
+        Bitmap icon =
+                generateAdjustedHwBitmap(image, mIconSize, mIconSize, matrix, paint, overlayColor);
+
+        /**
+         * NOTE: The following code prepares the notification builder for updating the
+         * notification after the screenshot has been written to disk.
+         */
+
+        // On the tablet, the large icon makes the notification appear as if it is clickable
+        // (and on small devices, the large icon is not shown) so defer showing the large icon
+        // until we compose the final post-save notification below.
+        mNotificationBuilder.setLargeIcon(icon.createAshmemBitmap());
+        // But we still don't set it for the expanded view, allowing the smallIcon to show here.
+        mNotificationStyle.bigLargeIcon((Bitmap) null);
+    }
+
+    /**
+     * Shows a notification to inform the user that a screenshot is currently being saved.
+     */
+    public void showSavingScreenshotNotification() {
+        final long now = System.currentTimeMillis();
+
+        mPublicNotificationBuilder
+                .setContentTitle(mResources.getString(R.string.screenshot_saving_title))
+                .setSmallIcon(R.drawable.stat_notify_image)
+                .setCategory(Notification.CATEGORY_PROGRESS)
+                .setWhen(now)
+                .setShowWhen(true)
+                .setColor(mResources.getColor(
+                        com.android.internal.R.color.system_notification_accent_color));
+        SystemUI.overrideNotificationAppName(mContext, mPublicNotificationBuilder, true);
+
+        mNotificationBuilder
+                .setContentTitle(mResources.getString(R.string.screenshot_saving_title))
+                .setSmallIcon(R.drawable.stat_notify_image)
+                .setWhen(now)
+                .setShowWhen(true)
+                .setColor(mResources.getColor(
+                        com.android.internal.R.color.system_notification_accent_color))
+                .setStyle(mNotificationStyle)
+                .setPublicVersion(mPublicNotificationBuilder.build());
+        mNotificationBuilder.setFlag(Notification.FLAG_NO_CLEAR, true);
+        SystemUI.overrideNotificationAppName(mContext, mNotificationBuilder, true);
+
+        mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT,
+                mNotificationBuilder.build());
+    }
+
+    /**
+     * Shows a notification with the saved screenshot and actions that can be taken with it.
+     *
+     * @param imageUri URI for the saved image
+     * @param actions  a list of notification actions which can be taken
+     */
+    public void showScreenshotActionsNotification(
+            Uri imageUri,
+            List<Notification.Action> actions) {
+        for (Notification.Action action : actions) {
+            mNotificationBuilder.addAction(action);
+        }
+
+        // Create the intent to show the screenshot in gallery
+        Intent launchIntent = new Intent(Intent.ACTION_VIEW);
+        launchIntent.setDataAndType(imageUri, "image/png");
+        launchIntent.setFlags(
+                Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+        final long now = System.currentTimeMillis();
+
+        // Update the text and the icon for the existing notification
+        mPublicNotificationBuilder
+                .setContentTitle(mResources.getString(R.string.screenshot_saved_title))
+                .setContentText(mResources.getString(R.string.screenshot_saved_text))
+                .setContentIntent(PendingIntent.getActivity(mContext, 0, launchIntent, 0))
+                .setWhen(now)
+                .setAutoCancel(true)
+                .setColor(mContext.getColor(
+                        com.android.internal.R.color.system_notification_accent_color));
+        mNotificationBuilder
+                .setContentTitle(mResources.getString(R.string.screenshot_saved_title))
+                .setContentText(mResources.getString(R.string.screenshot_saved_text))
+                .setContentIntent(PendingIntent.getActivity(mContext, 0, launchIntent, 0))
+                .setWhen(now)
+                .setAutoCancel(true)
+                .setColor(mContext.getColor(
+                        com.android.internal.R.color.system_notification_accent_color))
+                .setPublicVersion(mPublicNotificationBuilder.build())
+                .setFlag(Notification.FLAG_NO_CLEAR, false);
+
+        mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT,
+                mNotificationBuilder.build());
+    }
+
+    /**
+     * Sends a notification that the screenshot capture has failed.
+     */
+    public void notifyScreenshotError(int msgResId) {
+        Resources res = mContext.getResources();
+        String errorMsg = res.getString(msgResId);
+
+        // Repurpose the existing notification to notify the user of the error
+        Notification.Builder b = new Notification.Builder(mContext, NotificationChannels.ALERTS)
+                .setTicker(res.getString(R.string.screenshot_failed_title))
+                .setContentTitle(res.getString(R.string.screenshot_failed_title))
+                .setContentText(errorMsg)
+                .setSmallIcon(R.drawable.stat_notify_image_error)
+                .setWhen(System.currentTimeMillis())
+                .setVisibility(Notification.VISIBILITY_PUBLIC) // ok to show outside lockscreen
+                .setCategory(Notification.CATEGORY_ERROR)
+                .setAutoCancel(true)
+                .setColor(mContext.getColor(
+                        com.android.internal.R.color.system_notification_accent_color));
+        final DevicePolicyManager dpm =
+                (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+        final Intent intent =
+                dpm.createAdminSupportIntent(DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE);
+        if (intent != null) {
+            final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
+                    mContext, 0, intent, 0, null, UserHandle.CURRENT);
+            b.setContentIntent(pendingIntent);
+        }
+
+        SystemUI.overrideNotificationAppName(mContext, b, true);
+
+        Notification n = new Notification.BigTextStyle(b)
+                .bigText(errorMsg)
+                .build();
+        mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT, n);
+    }
+
+    /**
+     * Cancels the current screenshot notification.
+     */
+    public void cancelNotification() {
+        mNotificationManager.cancel(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT);
+    }
+
+    /**
+     * Generates a new hardware bitmap with specified values, copying the content from the
+     * passed in bitmap.
+     */
+    private Bitmap generateAdjustedHwBitmap(Bitmap bitmap, int width, int height, Matrix matrix,
+            Paint paint, int color) {
+        Picture picture = new Picture();
+        Canvas canvas = picture.beginRecording(width, height);
+        canvas.drawColor(color);
+        canvas.drawBitmap(bitmap, matrix, paint);
+        picture.endRecording();
+        return Bitmap.createBitmap(picture);
+    }
+
+    static void cancelScreenshotNotification(Context context) {
+        final NotificationManager nm =
+                (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
+        nm.cancel(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java
index fc2a1e4..522f729 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java
@@ -16,10 +16,10 @@
 
 package com.android.systemui.screenshot;
 
-import android.app.NotificationManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.view.WindowManager;
 
 import com.android.systemui.R;
 
@@ -32,9 +32,9 @@
     @Override
     public void onReceive(final Context context, Intent intent) {
         // Show a message that we've failed to save the image to disk
-        NotificationManager nm = (NotificationManager)
-                context.getSystemService(Context.NOTIFICATION_SERVICE);
-        GlobalScreenshot.notifyScreenshotError(context, nm,
-                R.string.screenshot_failed_to_save_unknown_text);
+        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+        ScreenshotNotificationsController controller =
+                new ScreenshotNotificationsController(context, wm);
+        controller.notifyScreenshotError(R.string.screenshot_failed_to_save_unknown_text);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java
new file mode 100644
index 0000000..e76e37e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot;
+
+import static android.os.AsyncTask.THREAD_POOL_EXECUTOR;
+
+import android.app.ActivityManager;
+import android.app.Notification;
+import android.content.ComponentName;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.SystemUIFactory;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Collects the static functions for retrieving and acting on smart actions.
+ */
+public class ScreenshotSmartActions {
+    private static final String TAG = "ScreenshotSmartActions";
+
+    @VisibleForTesting
+    static CompletableFuture<List<Notification.Action>> getSmartActionsFuture(String screenshotId,
+            Bitmap image, ScreenshotNotificationSmartActionsProvider smartActionsProvider,
+            boolean smartActionsEnabled, boolean isManagedProfile) {
+        if (!smartActionsEnabled) {
+            Slog.i(TAG, "Screenshot Intelligence not enabled, returning empty list.");
+            return CompletableFuture.completedFuture(Collections.emptyList());
+        }
+        if (image.getConfig() != Bitmap.Config.HARDWARE) {
+            Slog.w(TAG, String.format(
+                    "Bitmap expected: Hardware, Bitmap found: %s. Returning empty list.",
+                    image.getConfig()));
+            return CompletableFuture.completedFuture(Collections.emptyList());
+        }
+
+        Slog.d(TAG, "Screenshot from a managed profile: " + isManagedProfile);
+        CompletableFuture<List<Notification.Action>> smartActionsFuture;
+        long startTimeMs = SystemClock.uptimeMillis();
+        try {
+            ActivityManager.RunningTaskInfo runningTask =
+                    ActivityManagerWrapper.getInstance().getRunningTask();
+            ComponentName componentName =
+                    (runningTask != null && runningTask.topActivity != null)
+                            ? runningTask.topActivity
+                            : new ComponentName("", "");
+            smartActionsFuture = smartActionsProvider.getActions(screenshotId, image,
+                    componentName,
+                    isManagedProfile);
+        } catch (Throwable e) {
+            long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs;
+            smartActionsFuture = CompletableFuture.completedFuture(Collections.emptyList());
+            Slog.e(TAG, "Failed to get future for screenshot notification smart actions.", e);
+            notifyScreenshotOp(screenshotId, smartActionsProvider,
+                    ScreenshotNotificationSmartActionsProvider.ScreenshotOp.REQUEST_SMART_ACTIONS,
+                    ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.ERROR,
+                    waitTimeMs);
+        }
+        return smartActionsFuture;
+    }
+
+    @VisibleForTesting
+    static List<Notification.Action> getSmartActions(String screenshotId,
+            CompletableFuture<List<Notification.Action>> smartActionsFuture, int timeoutMs,
+            ScreenshotNotificationSmartActionsProvider smartActionsProvider) {
+        long startTimeMs = SystemClock.uptimeMillis();
+        try {
+            List<Notification.Action> actions = smartActionsFuture.get(timeoutMs,
+                    TimeUnit.MILLISECONDS);
+            long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs;
+            Slog.d(TAG, String.format("Got %d smart actions. Wait time: %d ms",
+                    actions.size(), waitTimeMs));
+            notifyScreenshotOp(screenshotId, smartActionsProvider,
+                    ScreenshotNotificationSmartActionsProvider.ScreenshotOp.WAIT_FOR_SMART_ACTIONS,
+                    ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.SUCCESS,
+                    waitTimeMs);
+            return actions;
+        } catch (Throwable e) {
+            long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs;
+            Slog.e(TAG, String.format("Error getting smart actions. Wait time: %d ms", waitTimeMs),
+                    e);
+            ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus status =
+                    (e instanceof TimeoutException)
+                            ? ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.TIMEOUT
+                            : ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.ERROR;
+            notifyScreenshotOp(screenshotId, smartActionsProvider,
+                    ScreenshotNotificationSmartActionsProvider.ScreenshotOp.WAIT_FOR_SMART_ACTIONS,
+                    status, waitTimeMs);
+            return Collections.emptyList();
+        }
+    }
+
+    static void notifyScreenshotOp(String screenshotId,
+            ScreenshotNotificationSmartActionsProvider smartActionsProvider,
+            ScreenshotNotificationSmartActionsProvider.ScreenshotOp op,
+            ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus status, long durationMs) {
+        try {
+            smartActionsProvider.notifyOp(screenshotId, op, status, durationMs);
+        } catch (Throwable e) {
+            Slog.e(TAG, "Error in notifyScreenshotOp: ", e);
+        }
+    }
+
+    static void notifyScreenshotAction(Context context, String screenshotId, String action,
+            boolean isSmartAction) {
+        try {
+            ScreenshotNotificationSmartActionsProvider provider =
+                    SystemUIFactory.getInstance().createScreenshotNotificationSmartActionsProvider(
+                            context, THREAD_POOL_EXECUTOR, new Handler());
+            provider.notifyAction(screenshotId, action, isSmartAction);
+        } catch (Throwable e) {
+            Slog.e(TAG, "Error in notifyScreenshotAction: ", e);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 6243b4b..29b96a9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -42,14 +42,11 @@
         @Override
         public void handleMessage(Message msg) {
             final Messenger callback = msg.replyTo;
-            Consumer<Uri> finisher = new Consumer<Uri>() {
-                @Override
-                public void accept(Uri uri) {
-                    Message reply = Message.obtain(null, 1, uri);
-                    try {
-                        callback.send(reply);
-                    } catch (RemoteException e) {
-                    }
+            Consumer<Uri> finisher = uri -> {
+                Message reply = Message.obtain(null, 1, uri);
+                try {
+                    callback.send(reply);
+                } catch (RemoteException e) {
                 }
             };
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index f5c1587..af218c49 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -160,6 +160,7 @@
     private boolean mHovering = false;
     private boolean mShowActiveStreamOnly;
     private boolean mConfigChanged = false;
+    private boolean mIsAnimatingDismiss = false;
     private boolean mHasSeenODICaptionsTooltip;
     private ViewStub mODICaptionsTooltipViewStub;
     private View mODICaptionsTooltipView = null;
@@ -693,6 +694,7 @@
 
         initSettingsH();
         mShowing = true;
+        mIsAnimatingDismiss = false;
         mDialog.show();
         Events.writeEvent(Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked());
         mController.notifyVisible(true);
@@ -737,6 +739,10 @@
         }
         mHandler.removeMessages(H.DISMISS);
         mHandler.removeMessages(H.SHOW);
+        if (mIsAnimatingDismiss) {
+            return;
+        }
+        mIsAnimatingDismiss = true;
         mDialogView.animate().cancel();
         if (mShowing) {
             mShowing = false;
@@ -752,6 +758,7 @@
                 .withEndAction(() -> mHandler.postDelayed(() -> {
                     mDialog.dismiss();
                     tryToRemoveCaptionsTooltip();
+                    mIsAnimatingDismiss = false;
                 }, 50));
         if (!isLandscape()) animator.translationX(mDialogView.getWidth() / 2.0f);
         animator.start();
diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayWindowController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayWindowController.java
index aa56ffb..aed90eb 100644
--- a/packages/SystemUI/src/com/android/systemui/wm/DisplayWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayWindowController.java
@@ -99,13 +99,17 @@
                             if (mDisplays.get(displayId) != null) {
                                 return;
                             }
+                            Display display = getDisplay(displayId);
+                            if (display == null) {
+                                // It's likely that the display is private to some app and thus not
+                                // accessible by system-ui.
+                                return;
+                            }
                             DisplayRecord record = new DisplayRecord();
                             record.mDisplayId = displayId;
-                            // TODO(b/146566787): disabled for MultiDisplayActivityLaunchTests
-                            // Display display = getDisplay(displayId);
-                            // record.mContext = (displayId == Display.DEFAULT_DISPLAY) ? mContext
-                            //         : mContext.createDisplayContext(display);
-                            // record.mDisplayLayout = new DisplayLayout(record.mContext, display);
+                            record.mContext = (displayId == Display.DEFAULT_DISPLAY) ? mContext
+                                    : mContext.createDisplayContext(display);
+                            record.mDisplayLayout = new DisplayLayout(record.mContext, display);
                             mDisplays.put(displayId, record);
                             for (int i = 0; i < mDisplayChangedListeners.size(); ++i) {
                                 mDisplayChangedListeners.get(i).onDisplayAdded(displayId);
@@ -124,14 +128,13 @@
                                         + " display.");
                                 return;
                             }
-                            // TODO(b/146566787): disabled for MultiDisplaySystemDecorationTests
-                            // Display display = getDisplay(displayId);
-                            // Context perDisplayContext = mContext;
-                            // if (displayId != Display.DEFAULT_DISPLAY) {
-                            //     perDisplayContext = mContext.createDisplayContext(display);
-                            // }
-                            // dr.mContext = perDisplayContext.createConfigurationContext(newConfig);
-                            // dr.mDisplayLayout = new DisplayLayout(dr.mContext, display);
+                            Display display = getDisplay(displayId);
+                            Context perDisplayContext = mContext;
+                            if (displayId != Display.DEFAULT_DISPLAY) {
+                                perDisplayContext = mContext.createDisplayContext(display);
+                            }
+                            dr.mContext = perDisplayContext.createConfigurationContext(newConfig);
+                            dr.mDisplayLayout = new DisplayLayout(dr.mContext, display);
                             for (int i = 0; i < mDisplayChangedListeners.size(); ++i) {
                                 mDisplayChangedListeners.get(i).onDisplayConfigurationChanged(
                                         displayId, newConfig);
@@ -144,6 +147,9 @@
                 public void onDisplayRemoved(int displayId) {
                     mHandler.post(() -> {
                         synchronized (mDisplays) {
+                            if (mDisplays.get(displayId) == null) {
+                                return;
+                            }
                             for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) {
                                 mDisplayChangedListeners.get(i).onDisplayRemoved(displayId);
                             }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java
index b4a60d6..a5722e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java
@@ -52,7 +52,7 @@
 
     @Test
     public void testInit_finish() {
-        mEglHelper.init(mSurfaceHolder);
+        mEglHelper.init(mSurfaceHolder, false /* wideColorGamut */);
         mEglHelper.finish();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
index 6d85d37..2c7cee3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.screenshot;
 
-import static android.content.Context.NOTIFICATION_SERVICE;
-
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
@@ -29,7 +27,6 @@
 import static org.mockito.Mockito.when;
 
 import android.app.Notification;
-import android.app.NotificationManager;
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.net.Uri;
@@ -42,7 +39,6 @@
 
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.util.NotificationChannels;
 
 import org.junit.Assert;
 import org.junit.Before;
@@ -84,7 +80,7 @@
                 eq(false))).thenThrow(
                 RuntimeException.class);
         CompletableFuture<List<Notification.Action>> smartActionsFuture =
-                GlobalScreenshot.getSmartActionsFuture("", bitmap,
+                ScreenshotSmartActions.getSmartActionsFuture("", bitmap,
                         smartActionsProvider, true, false);
         Assert.assertNotNull(smartActionsFuture);
         List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
@@ -101,7 +97,7 @@
         int timeoutMs = 1000;
         when(smartActionsFuture.get(timeoutMs, TimeUnit.MILLISECONDS)).thenThrow(
                 RuntimeException.class);
-        List<Notification.Action> actions = GlobalScreenshot.getSmartActions(
+        List<Notification.Action> actions = ScreenshotSmartActions.getSmartActions(
                 "", smartActionsFuture, timeoutMs, mSmartActionsProvider);
         Assert.assertEquals(Collections.emptyList(), actions);
     }
@@ -112,7 +108,7 @@
             throws Exception {
         doThrow(RuntimeException.class).when(mSmartActionsProvider).notifyOp(any(), any(), any(),
                 anyLong());
-        GlobalScreenshot.notifyScreenshotOp(null, mSmartActionsProvider, null, null, -1);
+        ScreenshotSmartActions.notifyScreenshotOp(null, mSmartActionsProvider, null, null, -1);
     }
 
     // Tests for a non-hardware bitmap, ScreenshotNotificationSmartActionsProvider is never invoked
@@ -123,7 +119,7 @@
         Bitmap bitmap = mock(Bitmap.class);
         when(bitmap.getConfig()).thenReturn(Bitmap.Config.RGB_565);
         CompletableFuture<List<Notification.Action>> smartActionsFuture =
-                GlobalScreenshot.getSmartActionsFuture("", bitmap,
+                ScreenshotSmartActions.getSmartActionsFuture("", bitmap,
                         mSmartActionsProvider, true, true);
         verify(mSmartActionsProvider, never()).getActions(any(), any(), any(),
                 eq(false));
@@ -137,7 +133,7 @@
     public void testScreenshotNotificationSmartActionsProviderInvokedOnce() {
         Bitmap bitmap = mock(Bitmap.class);
         when(bitmap.getConfig()).thenReturn(Bitmap.Config.HARDWARE);
-        GlobalScreenshot.getSmartActionsFuture("", bitmap, mSmartActionsProvider,
+        ScreenshotSmartActions.getSmartActionsFuture("", bitmap, mSmartActionsProvider,
                 true, true);
         verify(mSmartActionsProvider, times(1))
                 .getActions(any(), any(), any(), eq(true));
@@ -153,7 +149,7 @@
                 SystemUIFactory.getInstance().createScreenshotNotificationSmartActionsProvider(
                         mContext, null, mHandler);
         CompletableFuture<List<Notification.Action>> smartActionsFuture =
-                GlobalScreenshot.getSmartActionsFuture("", bitmap,
+                ScreenshotSmartActions.getSmartActionsFuture("", bitmap,
                         actionsProvider,
                         true, true);
         Assert.assertNotNull(smartActionsFuture);
@@ -167,31 +163,23 @@
         if (Looper.myLooper() == null) {
             Looper.prepare();
         }
-        NotificationManager notificationManager =
-                (NotificationManager) mContext.getSystemService(NOTIFICATION_SERVICE);
+
         GlobalScreenshot.SaveImageInBackgroundData
                 data = new GlobalScreenshot.SaveImageInBackgroundData();
-        data.context = mContext;
         data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
-        data.iconSize = 10;
         data.finisher = null;
         data.mActionsReadyListener = null;
-        data.previewWidth = 10;
-        data.previewheight = 10;
-        SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, data,
-                notificationManager);
-        Notification.Builder notificationBuilder = new Notification.Builder(mContext,
-                NotificationChannels.SCREENSHOTS_HEADSUP);
-        task.populateNotificationActions(mContext, mContext.getResources(),
+        SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, data);
+        List<Notification.Action> actions = task.populateNotificationActions(
+                mContext, mContext.getResources(),
                 Uri.parse("Screenshot_123.png"),
-                CompletableFuture.completedFuture(Collections.emptyList()), notificationBuilder);
+                CompletableFuture.completedFuture(Collections.emptyList()));
 
-        Notification notification = notificationBuilder.build();
-        Assert.assertEquals(notification.actions.length, 3);
+        Assert.assertEquals(actions.size(), 3);
         boolean isShareFound = false;
         boolean isEditFound = false;
         boolean isDeleteFound = false;
-        for (Notification.Action action : notification.actions) {
+        for (Notification.Action action : actions) {
             Intent intent = action.actionIntent.getIntent();
             Assert.assertNotNull(intent);
             Bundle bundle = intent.getExtras();
diff --git a/packages/Tethering/CleanSpec.mk b/packages/Tethering/CleanSpec.mk
deleted file mode 100644
index 30bdd58..0000000
--- a/packages/Tethering/CleanSpec.mk
+++ /dev/null
@@ -1,58 +0,0 @@
-# Copyright (C) 2019 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# If you don't need to do a full clean build but would like to touch
-# a file or delete some intermediate files, add a clean step to the end
-# of the list.  These steps will only be run once, if they haven't been
-# run before.
-#
-# E.g.:
-#     $(call add-clean-step, touch -c external/sqlite/sqlite3.h)
-#     $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates)
-#
-# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with
-# files that are missing or have been moved.
-#
-# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory.
-# Use $(OUT_DIR) to refer to the "out" directory.
-#
-# If you need to re-do something that's already mentioned, just copy
-# the command and add it to the bottom of the list.  E.g., if a change
-# that you made last week required touching a file and a change you
-# made today requires touching the same file, just copy the old
-# touch step and add it to the end of the list.
-#
-# *****************************************************************
-# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THE BANNER
-# *****************************************************************
-
-# For example:
-#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/AndroidTests_intermediates)
-#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates)
-#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f)
-#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*)
-
-$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/Tethering)
-$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/InProcessTethering)
-$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/InProcessTethering*)
-$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/APPS/InProcessTethering*)
-$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system_other/system/priv-app/InProcessTethering)
-$(call add-clean-step, rm -rf $(PRODUCT_OUT)/apex/com.android.tethering)
-$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/apex/com.android.tethering.apex)
-$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/ETC/com.android.tethering*)
-
-# ******************************************************************
-# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER
-# ******************************************************************
diff --git a/packages/overlays/Android.mk b/packages/overlays/Android.mk
index 3eb9049..eecc101 100644
--- a/packages/overlays/Android.mk
+++ b/packages/overlays/Android.mk
@@ -42,7 +42,7 @@
 	IconPackRoundedLauncherOverlay \
 	IconPackRoundedSettingsOverlay \
 	IconPackRoundedSystemUIOverlay \
-	IconPackRoundedThemePickerUIOverlay \
+	IconPackRoundedThemePickerOverlay \
 	IconShapeRoundedRectOverlay \
 	IconShapeSquircleOverlay \
 	IconShapeTeardropOverlay \
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 21c4784..00fc6d0 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -355,8 +355,15 @@
                     mConnectedDevices.replace(key, di);
                 }
             }
-            if (AudioSystem.handleDeviceConfigChange(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address,
-                    BtHelper.getName(btDevice), a2dpCodec) != AudioSystem.AUDIO_STATUS_OK) {
+            final int res = AudioSystem.handleDeviceConfigChange(
+                    AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address,
+                    BtHelper.getName(btDevice), a2dpCodec);
+
+            if (res != AudioSystem.AUDIO_STATUS_OK) {
+                AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+                        "APM handleDeviceConfigChange failed for A2DP device addr="
+                        + address + " codec=" + a2dpCodec).printLog(TAG));
+
                 int musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC);
                 // force A2DP device disconnection in case of error so that AudioService state is
                 // consistent with audio policy manager state
@@ -364,6 +371,10 @@
                         btDevice, BluetoothA2dp.STATE_DISCONNECTED, BluetoothProfile.A2DP,
                         false /* suppressNoisyIntent */, musicDevice,
                         -1 /* a2dpVolume */);
+            } else {
+                AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+                        "APM handleDeviceConfigChange success for A2DP device addr="
+                                + address + " codec=" + a2dpCodec).printLog(TAG));
             }
         }
     }
@@ -702,8 +713,20 @@
         mDeviceBroker.setBluetoothA2dpOnInt(true, eventSource);
         // at this point there could be another A2DP device already connected in APM, but it
         // doesn't matter as this new one will overwrite the previous one
-        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+        final int res = AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
                 AudioSystem.DEVICE_STATE_AVAILABLE, address, name, a2dpCodec);
+
+        if (res != AudioSystem.AUDIO_STATUS_OK) {
+            AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+                    "APM failed to make available A2DP device addr=" + address
+                            + " error=" + res).printLog(TAG));
+            // TODO: connection failed, stop here
+            // TODO: return;
+        } else {
+            AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+                    "A2DP device addr=" + address + " now available").printLog(TAG));
+        }
+
         // Reset A2DP suspend state each time a new sink is connected
         AudioSystem.setParameters("A2dpSuspended=false");
 
@@ -738,8 +761,19 @@
 
         // device to remove was visible by APM, update APM
         mDeviceBroker.setAvrcpAbsoluteVolumeSupported(false);
-        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+        final int res = AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
                 AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", a2dpCodec);
+
+        if (res != AudioSystem.AUDIO_STATUS_OK) {
+            AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+                    "APM failed to make unavailable A2DP device addr=" + address
+                            + " error=" + res).printLog(TAG));
+            // TODO:  failed to disconnect, stop here
+            // TODO: return;
+        } else {
+            AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
+                    "A2DP device addr=" + address + " made unavailable")).printLog(TAG));
+        }
         mApmConnectedDevices.remove(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
         // Remove A2DP routes as well
         setCurrentAudioRouteNameIfPossible(null);
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
index fdbb7d9..73a815a 100644
--- a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
+++ b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
@@ -27,6 +27,9 @@
 import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS;
 import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS;
 import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS;
+import static com.android.server.integrity.serializer.RuleIndexingDetails.APP_CERTIFICATE_INDEXED;
+import static com.android.server.integrity.serializer.RuleIndexingDetails.NOT_INDEXED;
+import static com.android.server.integrity.serializer.RuleIndexingDetails.PACKAGE_NAME_INDEXED;
 
 import android.content.integrity.AtomicFormula;
 import android.content.integrity.CompoundFormula;
@@ -36,36 +39,16 @@
 import com.android.server.integrity.model.BitOutputStream;
 
 import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.io.OutputStream;
 import java.nio.charset.StandardCharsets;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 
 /** A helper class to serialize rules from the {@link Rule} model to Binary representation. */
 public class RuleBinarySerializer implements RuleSerializer {
 
-    // Get the byte representation for a list of rules, and write them to an output stream.
-    @Override
-    public void serialize(
-            List<Rule> rules, Optional<Integer> formatVersion, OutputStream outputStream)
-            throws RuleSerializeException {
-        try {
-            BitOutputStream bitOutputStream = new BitOutputStream();
-
-            int formatVersionValue = formatVersion.orElse(DEFAULT_FORMAT_VERSION);
-            bitOutputStream.setNext(FORMAT_VERSION_BITS, formatVersionValue);
-            outputStream.write(bitOutputStream.toByteArray());
-
-            for (Rule rule : rules) {
-                bitOutputStream.clear();
-                serializeRule(rule, bitOutputStream);
-                outputStream.write(bitOutputStream.toByteArray());
-            }
-        } catch (Exception e) {
-            throw new RuleSerializeException(e.getMessage(), e);
-        }
-    }
-
     // Get the byte representation for a list of rules.
     @Override
     public byte[] serialize(List<Rule> rules, Optional<Integer> formatVersion)
@@ -79,6 +62,45 @@
         }
     }
 
+    // Get the byte representation for a list of rules, and write them to an output stream.
+    @Override
+    public void serialize(
+            List<Rule> rules, Optional<Integer> formatVersion, OutputStream outputStream)
+            throws RuleSerializeException {
+        try {
+            // Determine the indexing groups and the order of the rules within each indexed group.
+            Map<Integer, List<Rule>> indexedRules =
+                    RuleIndexingDetailsIdentifier.splitRulesIntoIndexBuckets(rules);
+
+            serializeRuleFileMetadata(formatVersion, outputStream);
+
+            serializeIndexedRules(indexedRules.get(PACKAGE_NAME_INDEXED), outputStream);
+            serializeIndexedRules(indexedRules.get(APP_CERTIFICATE_INDEXED), outputStream);
+            serializeIndexedRules(indexedRules.get(NOT_INDEXED), outputStream);
+        } catch (Exception e) {
+            throw new RuleSerializeException(e.getMessage(), e);
+        }
+    }
+
+    private void serializeRuleFileMetadata(
+            Optional<Integer> formatVersion, OutputStream outputStream) throws IOException {
+        int formatVersionValue = formatVersion.orElse(DEFAULT_FORMAT_VERSION);
+
+        BitOutputStream bitOutputStream = new BitOutputStream();
+        bitOutputStream.setNext(FORMAT_VERSION_BITS, formatVersionValue);
+        outputStream.write(bitOutputStream.toByteArray());
+    }
+
+    private void serializeIndexedRules(List<Rule> rules, OutputStream outputStream)
+            throws IOException {
+        BitOutputStream bitOutputStream = new BitOutputStream();
+        for (Rule rule : rules) {
+            bitOutputStream.clear();
+            serializeRule(rule, bitOutputStream);
+            outputStream.write(bitOutputStream.toByteArray());
+        }
+    }
+
     private void serializeRule(Rule rule, BitOutputStream bitOutputStream) {
         if (rule == null) {
             throw new IllegalArgumentException("Null rule can not be serialized");
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java
index 7d5e836..f9c7912 100644
--- a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java
+++ b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java
@@ -62,7 +62,14 @@
         // Split the rules into the appropriate indexed pattern. The Tree Maps help us to keep the
         // entries sorted by their index key.
         for (Rule rule : rules) {
-            RuleIndexingDetails indexingDetails = getIndexingDetails(rule.getFormula());
+            RuleIndexingDetails indexingDetails;
+            try {
+                indexingDetails = getIndexingDetails(rule.getFormula());
+            } catch (Exception e) {
+                throw new IllegalArgumentException(
+                        String.format("Malformed rule identified. [%s]", rule.toString()));
+            }
+
             String ruleKey =
                     indexingDetails.getIndexType() != NOT_INDEXED
                             ? indexingDetails.getRuleKey()
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 9114996..1503282 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1811,7 +1811,7 @@
         } else if (newTask || !processRunning || (taskSwitch && !activityCreated)) {
             return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
         } else if (taskSwitch && allowTaskSnapshot) {
-            return snapshot == null ? STARTING_WINDOW_TYPE_SPLASH_SCREEN
+            return snapshot == null ? STARTING_WINDOW_TYPE_NONE
                     : snapshotOrientationSameAsTask(snapshot) || fromRecents
                             ? STARTING_WINDOW_TYPE_SNAPSHOT : STARTING_WINDOW_TYPE_SPLASH_SCREEN;
         } else {
@@ -6060,6 +6060,13 @@
 
     void registerRemoteAnimations(RemoteAnimationDefinition definition) {
         mRemoteAnimationDefinition = definition;
+        if (definition != null) {
+            definition.linkToDeath(this::unregisterRemoteAnimations);
+        }
+    }
+
+    void unregisterRemoteAnimations() {
+        mRemoteAnimationDefinition = null;
     }
 
     RemoteAnimationDefinition getRemoteAnimationDefinition() {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 45b4818..60f051c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4700,6 +4700,24 @@
     }
 
     @Override
+    public void unregisterRemoteAnimations(IBinder token) {
+        mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,
+                "unregisterRemoteAnimations");
+        synchronized (mGlobalLock) {
+            final ActivityRecord r = ActivityRecord.isInStackLocked(token);
+            if (r == null) {
+                return;
+            }
+            final long origId = Binder.clearCallingIdentity();
+            try {
+                r.unregisterRemoteAnimations();
+            } finally {
+                Binder.restoreCallingIdentity(origId);
+            }
+        }
+    }
+
+    @Override
     public void registerRemoteAnimationForNextActivityStart(String packageName,
             RemoteAnimationAdapter adapter) {
         mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 39091a6..b255b5e 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -754,8 +754,7 @@
         // Only apply the input consumer if it is enabled, it is not the target (home/recents)
         // being revealed with the transition, and we are actively animating the app as a part of
         // the animation
-        return mInputConsumerEnabled && mTargetActivityRecord != activity
-                && isAnimatingApp(activity);
+        return mInputConsumerEnabled && !isTargetApp(activity) && isAnimatingApp(activity);
     }
 
     boolean updateInputConsumerForApp(InputWindowHandle inputWindowHandle,
@@ -810,7 +809,9 @@
                     PooledLambda.__(ActivityRecord.class));
             boolean isAnimatingApp = task.forAllActivities(f);
             f.recycle();
-            return isAnimatingApp;
+            if (isAnimatingApp) {
+                return true;
+            }
         }
         return false;
     }
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 456068c..3182a72 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -281,7 +281,8 @@
      *
      * @return true if the state of the task is ok to proceed
      */
-    private boolean prepareTaskSnapshot(Task task, float scaleFraction, int pixelFormat,
+    @VisibleForTesting
+    boolean prepareTaskSnapshot(Task task, float scaleFraction, int pixelFormat,
             TaskSnapshot.Builder builder) {
         if (!mService.mPolicy.isScreenOn()) {
             if (DEBUG_SCREENSHOT) {
@@ -339,6 +340,7 @@
                 && (!activity.fillsParent() || isWindowTranslucent);
 
         builder.setTopActivityComponent(activity.mActivityComponent);
+        builder.setPixelFormat(pixelFormat);
         builder.setIsTranslucent(isTranslucent);
         builder.setOrientation(activity.getTask().getConfiguration().orientation);
         builder.setWindowingMode(task.getWindowingMode());
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 5771f2c..1313eeb 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2691,18 +2691,18 @@
 
         return mActivityRecord.getTask().getTaskStack().shouldIgnoreInput()
                 || !mActivityRecord.mVisibleRequested
-                || isAnimatingToRecents();
+                || isRecentsAnimationConsumingAppInput();
     }
 
     /**
-     * Returns {@code true} if the window is animating to home as part of the recents animation.
+     * Returns {@code true} if the window is animating to home as part of the recents animation and
+     * it is consuming input from the app.
      */
-    private boolean isAnimatingToRecents() {
+    private boolean isRecentsAnimationConsumingAppInput() {
         final RecentsAnimationController recentsAnimationController =
                 mWmService.getRecentsAnimationController();
         return recentsAnimationController != null
-                && recentsAnimationController.isAnimatingTask(getTask())
-                && !recentsAnimationController.isTargetApp(mActivityRecord);
+                && recentsAnimationController.shouldApplyInputConsumer(mActivityRecord);
     }
 
     @Override
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
index 901277d..2304bc6 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
@@ -47,13 +47,18 @@
 import org.junit.runners.JUnit4;
 
 import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.List;
 import java.util.Optional;
 
 @RunWith(JUnit4.class)
 public class RuleBinarySerializerTest {
 
+    private static final String SAMPLE_INSTALLER_NAME = "com.test.installer";
+    private static final String SAMPLE_INSTALLER_CERT = "installer_cert";
+
     private static final String COMPOUND_FORMULA_START_BITS =
             getBits(COMPOUND_FORMULA_START, SEPARATOR_BITS);
     private static final String COMPOUND_FORMULA_END_BITS =
@@ -67,6 +72,9 @@
 
     private static final String PACKAGE_NAME = getBits(AtomicFormula.PACKAGE_NAME, KEY_BITS);
     private static final String APP_CERTIFICATE = getBits(AtomicFormula.APP_CERTIFICATE, KEY_BITS);
+    private static final String INSTALLER_NAME = getBits(AtomicFormula.INSTALLER_NAME, KEY_BITS);
+    private static final String INSTALLER_CERTIFICATE =
+            getBits(AtomicFormula.INSTALLER_CERTIFICATE, KEY_BITS);
     private static final String VERSION_CODE = getBits(AtomicFormula.VERSION_CODE, KEY_BITS);
     private static final String PRE_INSTALLED = getBits(AtomicFormula.PRE_INSTALLED, KEY_BITS);
 
@@ -83,17 +91,28 @@
             getBytes(getBits(DEFAULT_FORMAT_VERSION, FORMAT_VERSION_BITS));
 
     @Test
-    public void testBinaryString_serializeEmptyRule() throws Exception {
-        Rule rule = null;
+    public void testBinaryString_serializeNullRules() {
         RuleSerializer binarySerializer = new RuleBinarySerializer();
 
         assertExpectException(
                 RuleSerializeException.class,
-                /* expectedExceptionMessageRegex= */ "Null rule can not be serialized",
+                /* expectedExceptionMessageRegex= */
+                "Index buckets cannot be created for null rule list.",
                 () ->
-                        binarySerializer.serialize(
-                                Collections.singletonList(rule),
-                                /* formatVersion= */ Optional.empty()));
+                        binarySerializer.serialize(null, /* formatVersion= */ Optional.empty()));
+    }
+
+    @Test
+    public void testBinaryString_emptyRules() throws Exception {
+        ByteArrayOutputStream expectedArrayOutputStream = new ByteArrayOutputStream();
+        expectedArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
+
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        RuleSerializer binarySerializer = new RuleBinarySerializer();
+        binarySerializer.serialize(
+                Collections.emptyList(), /* formatVersion= */ Optional.empty(), outputStream);
+
+        assertThat(outputStream.toByteArray()).isEqualTo(expectedArrayOutputStream.toByteArray());
     }
 
     @Test
@@ -381,7 +400,7 @@
 
         assertExpectException(
                 RuleSerializeException.class,
-                /* expectedExceptionMessageRegex= */ "Invalid formula type",
+                /* expectedExceptionMessageRegex= */ "Malformed rule identified.",
                 () ->
                         binarySerializer.serialize(
                                 Collections.singletonList(rule),
@@ -402,6 +421,165 @@
         assertThat(actualRules).isEqualTo(expectedRules);
     }
 
+    @Test
+    public void testBinaryString_serializeComplexCompoundFormula_indexingOrderValid()
+            throws Exception {
+        String packageNameA = "aaa";
+        String packageNameB = "bbb";
+        String packageNameC = "ccc";
+        String appCert1 = "cert1";
+        String appCert2 = "cert2";
+        String appCert3 = "cert3";
+        Rule installerRule =
+                new Rule(
+                        new CompoundFormula(
+                                CompoundFormula.AND,
+                                Arrays.asList(
+                                        new AtomicFormula.StringAtomicFormula(
+                                                AtomicFormula.INSTALLER_NAME,
+                                                SAMPLE_INSTALLER_NAME,
+                                                /* isHashedValue= */ false),
+                                        new AtomicFormula.StringAtomicFormula(
+                                                AtomicFormula.INSTALLER_CERTIFICATE,
+                                                SAMPLE_INSTALLER_CERT,
+                                                /* isHashedValue= */ false))),
+                        Rule.DENY);
+
+        RuleSerializer binarySerializer = new RuleBinarySerializer();
+        List<Rule> ruleList = new ArrayList();
+        ruleList.add(getRuleWithAppCertificateAndSampleInstallerName(appCert3));
+        ruleList.add(getRuleWithAppCertificateAndSampleInstallerName(appCert2));
+        ruleList.add(getRuleWithAppCertificateAndSampleInstallerName(appCert1));
+        ruleList.add(getRuleWithPackageNameAndSampleInstallerName(packageNameB));
+        ruleList.add(getRuleWithPackageNameAndSampleInstallerName(packageNameC));
+        ruleList.add(getRuleWithPackageNameAndSampleInstallerName(packageNameA));
+        ruleList.add(installerRule);
+        byte[] actualRules =
+                binarySerializer.serialize(ruleList, /* formatVersion= */ Optional.empty());
+
+
+        // Note that ordering is important here and the test verifies that the rules are written
+        // in this sorted order.
+        ByteArrayOutputStream expectedArrayOutputStream = new ByteArrayOutputStream();
+        expectedArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
+        expectedArrayOutputStream.write(
+                getBytes(getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
+                        packageNameA)));
+        expectedArrayOutputStream.write(
+                getBytes(getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
+                        packageNameB)));
+        expectedArrayOutputStream.write(
+                getBytes(getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
+                        packageNameC)));
+        expectedArrayOutputStream.write(
+                getBytes(getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName(
+                        appCert1)));
+        expectedArrayOutputStream.write(
+                getBytes(getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName(
+                        appCert2)));
+        expectedArrayOutputStream.write(
+                getBytes(getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName(
+                        appCert3)));
+        String expectedBitsForInstallerRule =
+                START_BIT
+                        + COMPOUND_FORMULA_START_BITS
+                        + AND
+                        + ATOMIC_FORMULA_START_BITS
+                        + INSTALLER_NAME
+                        + EQ
+                        + IS_NOT_HASHED
+                        + getBits(SAMPLE_INSTALLER_NAME.length(), VALUE_SIZE_BITS)
+                        + getValueBits(SAMPLE_INSTALLER_NAME)
+                        + ATOMIC_FORMULA_START_BITS
+                        + INSTALLER_CERTIFICATE
+                        + EQ
+                        + IS_NOT_HASHED
+                        + getBits(SAMPLE_INSTALLER_CERT.length(), VALUE_SIZE_BITS)
+                        + getValueBits(SAMPLE_INSTALLER_CERT)
+                        + COMPOUND_FORMULA_END_BITS
+                        + DENY
+                        + END_BIT;
+        expectedArrayOutputStream.write(getBytes(expectedBitsForInstallerRule));
+
+        assertThat(actualRules).isEqualTo(expectedArrayOutputStream.toByteArray());
+    }
+
+    private Rule getRuleWithPackageNameAndSampleInstallerName(String packageName) {
+        return new Rule(
+                new CompoundFormula(
+                        CompoundFormula.AND,
+                        Arrays.asList(
+                                new AtomicFormula.StringAtomicFormula(
+                                        AtomicFormula.PACKAGE_NAME,
+                                        packageName,
+                                        /* isHashedValue= */ false),
+                                new AtomicFormula.StringAtomicFormula(
+                                        AtomicFormula.INSTALLER_NAME,
+                                        SAMPLE_INSTALLER_NAME,
+                                        /* isHashedValue= */ false))),
+                Rule.DENY);
+    }
+
+    private String getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
+            String packageName) {
+        return START_BIT
+                + COMPOUND_FORMULA_START_BITS
+                + AND
+                + ATOMIC_FORMULA_START_BITS
+                + PACKAGE_NAME
+                + EQ
+                + IS_NOT_HASHED
+                + getBits(packageName.length(), VALUE_SIZE_BITS)
+                + getValueBits(packageName)
+                + ATOMIC_FORMULA_START_BITS
+                + INSTALLER_NAME
+                + EQ
+                + IS_NOT_HASHED
+                + getBits(SAMPLE_INSTALLER_NAME.length(), VALUE_SIZE_BITS)
+                + getValueBits(SAMPLE_INSTALLER_NAME)
+                + COMPOUND_FORMULA_END_BITS
+                + DENY
+                + END_BIT;
+    }
+
+    private Rule getRuleWithAppCertificateAndSampleInstallerName(String certificate) {
+        return new Rule(
+                new CompoundFormula(
+                        CompoundFormula.AND,
+                        Arrays.asList(
+                                new AtomicFormula.StringAtomicFormula(
+                                        AtomicFormula.APP_CERTIFICATE,
+                                        certificate,
+                                        /* isHashedValue= */ false),
+                                new AtomicFormula.StringAtomicFormula(
+                                        AtomicFormula.INSTALLER_NAME,
+                                        SAMPLE_INSTALLER_NAME,
+                                        /* isHashedValue= */ false))),
+                Rule.DENY);
+    }
+
+    private String getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName(
+            String appCertificate) {
+        return START_BIT
+                + COMPOUND_FORMULA_START_BITS
+                + AND
+                + ATOMIC_FORMULA_START_BITS
+                + APP_CERTIFICATE
+                + EQ
+                + IS_NOT_HASHED
+                + getBits(appCertificate.length(), VALUE_SIZE_BITS)
+                + getValueBits(appCertificate)
+                + ATOMIC_FORMULA_START_BITS
+                + INSTALLER_NAME
+                + EQ
+                + IS_NOT_HASHED
+                + getBits(SAMPLE_INSTALLER_NAME.length(), VALUE_SIZE_BITS)
+                + getValueBits(SAMPLE_INSTALLER_NAME)
+                + COMPOUND_FORMULA_END_BITS
+                + DENY
+                + END_BIT;
+    }
+
     private static Formula getInvalidFormula() {
         return new Formula() {
             @Override
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
index 90ec19e..94e11c6 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
@@ -129,7 +129,7 @@
 
         assertExpectException(
                 IllegalArgumentException.class,
-                /* expectedExceptionMessageRegex= */ "Invalid formula tag type.",
+                /* expectedExceptionMessageRegex= */ "Malformed rule identified.",
                 () -> splitRulesIntoIndexBuckets(ruleList));
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
index e17e0d8..8fe0cdb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
@@ -19,10 +19,13 @@
 import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
 import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.server.wm.TaskSnapshotController.SNAPSHOT_MODE_APP_THEME;
 import static com.android.server.wm.TaskSnapshotController.SNAPSHOT_MODE_REAL;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 
@@ -175,4 +178,22 @@
             }
         }
     }
+
+    @Test
+    public void testPrepareTaskSnapshot() {
+        mAppWindow.mWinAnimator.mLastAlpha = 1f;
+        spyOn(mAppWindow.mWinAnimator);
+        doReturn(true).when(mAppWindow.mWinAnimator).getShown();
+        doReturn(true).when(mAppWindow.mActivityRecord).isSurfaceShowing();
+
+        final ActivityManager.TaskSnapshot.Builder builder =
+                new ActivityManager.TaskSnapshot.Builder();
+        final float scaleFraction = 0.8f;
+        mWm.mTaskSnapshotController.prepareTaskSnapshot(mAppWindow.mActivityRecord.getTask(),
+                scaleFraction, PixelFormat.UNKNOWN, builder);
+
+        assertEquals(scaleFraction, builder.getScaleFraction(), 0 /* delta */);
+        // The pixel format should be selected automatically.
+        assertNotEquals(PixelFormat.UNKNOWN, builder.getPixelFormat());
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 0018c63..6d23b2e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -596,7 +596,7 @@
 
         // Mock active recents animation
         RecentsAnimationController recentsController = mock(RecentsAnimationController.class);
-        when(recentsController.isAnimatingTask(win0.mActivityRecord.getTask())).thenReturn(true);
+        when(recentsController.shouldApplyInputConsumer(win0.mActivityRecord)).thenReturn(true);
         mWm.setRecentsAnimationController(recentsController);
         assertTrue(win0.cantReceiveTouchInput());
     }
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 813b9fa..4bb237f 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -4050,13 +4050,28 @@
         }
     }
 
-    /** {@hide} */
+    /**
+     * Gets the package name for a default carrier service.
+     * @return the package name for a default carrier service; empty string if not available.
+     *
+     * @hide
+     */
+    @NonNull
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public String getDefaultCarrierServicePackageName() {
         try {
-            return getICarrierConfigLoader().getDefaultCarrierServicePackageName();
-        } catch (Throwable t) {
-            return null;
+            ICarrierConfigLoader loader = getICarrierConfigLoader();
+            if (loader == null) {
+                Rlog.w(TAG, "getDefaultCarrierServicePackageName ICarrierConfigLoader is null");
+                return "";
+            }
+            return loader.getDefaultCarrierServicePackageName();
+        } catch (RemoteException ex) {
+            Rlog.e(TAG, "getDefaultCarrierServicePackageName ICarrierConfigLoader is null"
+                    + ex.toString());
         }
+        return "";
     }
 
     /**
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index d7d85c2..3f065f8 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -65,6 +65,13 @@
     static final boolean DBG = false;
     static final boolean VDBG = false;  // STOPSHIP if true
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "STATE_",
+            value = {STATE_IN_SERVICE, STATE_OUT_OF_SERVICE, STATE_EMERGENCY_ONLY,
+                    STATE_POWER_OFF})
+    public @interface RegState {}
+
     /**
      * Normal operation condition, the phone is registered
      * with an operator either in home network or in roaming.
@@ -83,6 +90,7 @@
     /**
      * The phone is registered and locked.  Only emergency numbers are allowed. {@more}
      */
+    //TODO: This state is not used anymore. It should be deprecated in a future release.
     public static final int STATE_EMERGENCY_ONLY =
             TelephonyProtoEnums.SERVICE_STATE_EMERGENCY_ONLY;  // 2
 
@@ -501,13 +509,15 @@
     }
 
     /**
-     * Get current data service state
+     * Get current data registration state.
      *
      * @see #STATE_IN_SERVICE
      * @see #STATE_OUT_OF_SERVICE
      * @see #STATE_EMERGENCY_ONLY
      * @see #STATE_POWER_OFF
      *
+     * @return current data registration state {@link RegState}
+     *
      * @hide
      */
     @UnsupportedAppUsage
@@ -516,6 +526,23 @@
     }
 
     /**
+     * Get current data registration state.
+     *
+     * @see #STATE_IN_SERVICE
+     * @see #STATE_OUT_OF_SERVICE
+     * @see #STATE_EMERGENCY_ONLY
+     * @see #STATE_POWER_OFF
+     *
+     * @return current data registration state {@link RegState}
+     *
+     * @hide
+     */
+    @SystemApi
+    public @RegState int getDataRegistrationState() {
+        return getDataRegState();
+    }
+
+    /**
      * Get the current duplex mode
      *
      * @see #DUPLEX_MODE_UNKNOWN
@@ -1408,7 +1435,15 @@
         return getRilDataRadioTechnology();
     }
 
-    /** @hide */
+    /**
+     * Transform RIL radio technology {@link RilRadioTechnology} value to Network
+     * type {@link NetworkType}.
+     *
+     * @param rat The RIL radio technology {@link RilRadioTechnology}.
+     * @return The network type {@link NetworkType}.
+     *
+     * @hide
+     */
     public static int rilRadioTechnologyToNetworkType(@RilRadioTechnology int rat) {
         switch(rat) {
             case RIL_RADIO_TECHNOLOGY_GPRS:
@@ -1490,7 +1525,15 @@
         }
     }
 
-    /** @hide */
+    /**
+     * Transform network type {@link NetworkType} value to RIL radio technology
+     * {@link RilRadioTechnology}.
+     *
+     * @param networkType The network type {@link NetworkType}.
+     * @return The RIL radio technology {@link RilRadioTechnology}.
+     *
+     * @hide
+     */
     public static int networkTypeToRilRadioTechnology(int networkType) {
         switch(networkType) {
             case TelephonyManager.NETWORK_TYPE_GPRS:
@@ -1691,7 +1734,14 @@
         return bearerBitmask;
     }
 
-    /** @hide */
+    /**
+     * Convert network type bitmask to bearer bitmask.
+     *
+     * @param networkTypeBitmask The network type bitmask value
+     * @return The bearer bitmask value.
+     *
+     * @hide
+     */
     public static int convertNetworkTypeBitmaskToBearerBitmask(int networkTypeBitmask) {
         if (networkTypeBitmask == 0) {
             return 0;
@@ -1705,7 +1755,14 @@
         return bearerBitmask;
     }
 
-    /** @hide */
+    /**
+     * Convert bearer bitmask to network type bitmask.
+     *
+     * @param bearerBitmask The bearer bitmask value.
+     * @return The network type bitmask value.
+     *
+     * @hide
+     */
     public static int convertBearerBitmaskToNetworkTypeBitmask(int bearerBitmask) {
         if (bearerBitmask == 0) {
             return 0;
diff --git a/wifi/java/android/net/wifi/EasyConnectStatusCallback.java b/wifi/java/android/net/wifi/EasyConnectStatusCallback.java
index b8c82fd..4fa93ee 100644
--- a/wifi/java/android/net/wifi/EasyConnectStatusCallback.java
+++ b/wifi/java/android/net/wifi/EasyConnectStatusCallback.java
@@ -17,32 +17,46 @@
 package android.net.wifi;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
-import android.os.Handler;
+import android.util.SparseArray;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
 
 /**
  * Easy Connect (DPP) Status Callback. Use this callback to get status updates (success, failure,
  * progress) from the Easy Connect operation started with
- * {@link WifiManager#startEasyConnectAsConfiguratorInitiator(String,
- * int, int, Handler, EasyConnectStatusCallback)} or
- * {@link WifiManager#startEasyConnectAsEnrolleeInitiator(String,
- * Handler, EasyConnectStatusCallback)}
+ * {@link WifiManager#startEasyConnectAsConfiguratorInitiator(String, int, int, Executor,
+ * EasyConnectStatusCallback)} or {@link WifiManager#startEasyConnectAsEnrolleeInitiator(String,
+ * Executor, EasyConnectStatusCallback)}
  *
  * @hide
  */
 @SystemApi
 public abstract class EasyConnectStatusCallback {
     /**
-     * Easy Connect Success event: Configuration sent (Configurator mode).
+     * Easy Connect R1 Success event: Configuration sent (Configurator mode). This is the last
+     * and final Easy Connect event when either the local device or remote device implement R1.
+     * If both devices implement R2, this event will never be received, and the
+     * {@link EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_APPLIED} will be received.
      */
     public static final int EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_SENT = 0;
 
+    /**
+     * East Connect R2 Success event: Configuration applied by Enrollee (Configurator mode).
+     * This is the last and final Easy Connect event when both the local device and remote device
+     * implement R2. If either the local device or remote device implement R1, this event will never
+     * be received, and the {@link EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_SENT} will be received.
+     */
+    public static final int EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_APPLIED = 1;
+
     /** @hide */
     @IntDef(prefix = {"EASY_CONNECT_EVENT_SUCCESS_"}, value = {
             EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_SENT,
+            EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_APPLIED,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface EasyConnectSuccessStatusCode {
@@ -58,10 +72,22 @@
      */
     public static final int EASY_CONNECT_EVENT_PROGRESS_RESPONSE_PENDING = 1;
 
+    /**
+     * Easy Connect R2 Progress event: Configuration sent to Enrollee, waiting for response
+     */
+    public static final int EASY_CONNECT_EVENT_PROGRESS_CONFIGURATION_SENT_WAITING_RESPONSE = 2;
+
+    /**
+     * Easy Connect R2 Progress event: Configuration accepted by Enrollee, waiting for response
+     */
+    public static final int EASY_CONNECT_EVENT_PROGRESS_CONFIGURATION_ACCEPTED = 3;
+
     /** @hide */
     @IntDef(prefix = {"EASY_CONNECT_EVENT_PROGRESS_"}, value = {
             EASY_CONNECT_EVENT_PROGRESS_AUTHENTICATION_SUCCESS,
             EASY_CONNECT_EVENT_PROGRESS_RESPONSE_PENDING,
+            EASY_CONNECT_EVENT_PROGRESS_CONFIGURATION_SENT_WAITING_RESPONSE,
+            EASY_CONNECT_EVENT_PROGRESS_CONFIGURATION_ACCEPTED,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface EasyConnectProgressStatusCode {
@@ -114,6 +140,20 @@
      */
     public static final int EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK = -9;
 
+    /**
+     * Easy Connect R2 Failure event: Enrollee cannot find the network.
+     */
+    public static final int EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK = -10;
+
+    /**
+     * Easy Connect R2 Failure event: Enrollee failed to authenticate with the network.
+     */
+    public static final int EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION = -11;
+
+    /**
+     * Easy Connect R2 Failure event: Enrollee rejected the configuration.
+     */
+    public static final int EASY_CONNECT_EVENT_FAILURE_ENROLLEE_REJECTED_CONFIGURATION = -12;
 
     /** @hide */
     @IntDef(prefix = {"EASY_CONNECT_EVENT_FAILURE_"}, value = {
@@ -126,6 +166,9 @@
             EASY_CONNECT_EVENT_FAILURE_GENERIC,
             EASY_CONNECT_EVENT_FAILURE_NOT_SUPPORTED,
             EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK,
+            EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK,
+            EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION,
+            EASY_CONNECT_EVENT_FAILURE_ENROLLEE_REJECTED_CONFIGURATION,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface EasyConnectFailureStatusCode {
@@ -138,9 +181,8 @@
      * current Easy Connect
      * session, and no further callbacks will be called. This callback is the successful outcome
      * of a Easy Connect flow starting with
-     * {@link WifiManager#startEasyConnectAsEnrolleeInitiator(String,
-     * Handler,
-     * EasyConnectStatusCallback)}.
+     * {@link WifiManager#startEasyConnectAsEnrolleeInitiator(String, Executor,
+     * EasyConnectStatusCallback)} .
      *
      * @param newNetworkId New Wi-Fi configuration with a network ID received from the configurator
      */
@@ -148,13 +190,11 @@
 
     /**
      * Called when a Easy Connect success event takes place, except for when configuration is
-     * received from
-     * an external Configurator. The callback onSuccessConfigReceived will be used in this case.
-     * This callback marks the successful end of the current Easy Connect session, and no further
-     * callbacks will be called. This callback is the successful outcome of a Easy Connect flow
-     * starting with
-     * {@link WifiManager#startEasyConnectAsConfiguratorInitiator(String, int, int, Handler,
-     * EasyConnectStatusCallback)}.
+     * received from an external Configurator. The callback onSuccessConfigReceived will be used in
+     * this case. This callback marks the successful end of the current Easy Connect session, and no
+     * further callbacks will be called. This callback is the successful outcome of a Easy Connect
+     * flow starting with {@link WifiManager#startEasyConnectAsConfiguratorInitiator(String, int,
+     * int, Executor,EasyConnectStatusCallback)}.
      *
      * @param code Easy Connect success status code.
      */
@@ -162,12 +202,36 @@
 
     /**
      * Called when a Easy Connect Failure event takes place. This callback marks the unsuccessful
-     * end of the
-     * current Easy Connect session, and no further callbacks will be called.
+     * end of the current Easy Connect session, and no further callbacks will be called.
      *
      * @param code Easy Connect failure status code.
      */
-    public abstract void onFailure(@EasyConnectFailureStatusCode int code);
+    public void onFailure(@EasyConnectFailureStatusCode int code) {}
+
+    /**
+     * Called when a Easy Connect Failure event takes place. This callback marks the unsuccessful
+     * end of the current Easy Connect session, and no further callbacks will be called.
+     *
+     * Note: Easy Connect (DPP) R2, provides additional details for the Configurator when the
+     * remote Enrollee is unable to connect to a network. The ssid, channelList and bandList
+     * inputs are initialized only for the EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK failure
+     * code, and the ssid and bandList are initialized for the
+     * EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION failure code.
+     *
+     * @param code Easy Connect failure status code.
+     * @param ssid SSID of the network the Enrollee tried to connect to.
+     * @param channelListArray List of Global Operating classes and channel sets the Enrollee used
+     *                         to scan to find the network, see the "DPP Connection Status Object"
+     *                         section in the specification for the format, and Table E-4 in
+     *                         IEEE Std 802.11-2016 - Global operating classes for more details.
+     * @param operatingClassArray Array of bands the Enrollee supports as expressed as the Global
+     *                            Operating Class, see Table E-4 in IEEE Std 802.11-2016 - Global
+     *                            operating classes.
+     */
+    public void onFailure(@EasyConnectFailureStatusCode int code, @Nullable String ssid,
+            @NonNull SparseArray<int[]> channelListArray, @NonNull int[] operatingClassArray) {
+        onFailure(code);
+    }
 
     /**
      * Called when Easy Connect events that indicate progress take place. Can be used by UI elements
diff --git a/wifi/java/android/net/wifi/IDppCallback.aidl b/wifi/java/android/net/wifi/IDppCallback.aidl
index c452c76..d7a958a 100644
--- a/wifi/java/android/net/wifi/IDppCallback.aidl
+++ b/wifi/java/android/net/wifi/IDppCallback.aidl
@@ -38,7 +38,7 @@
     /**
      * Called when DPP Failure events take place.
      */
-    void onFailure(int status);
+    void onFailure(int status, String ssid, String channelList, in int[] bandArray);
 
     /**
      * Called when DPP events that indicate progress take place. Can be used by UI elements
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index b98d64d..5ab0583 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -54,6 +54,7 @@
 import android.util.CloseGuard;
 import android.util.Log;
 import android.util.Pair;
+import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -70,6 +71,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.StringTokenizer;
 import java.util.concurrent.Executor;
 
 /**
@@ -5177,6 +5179,7 @@
         @Override
         public void onSuccessConfigReceived(int newNetworkId) {
             Log.d(TAG, "Easy Connect onSuccessConfigReceived callback");
+            Binder.clearCallingIdentity();
             mExecutor.execute(() -> {
                 mEasyConnectStatusCallback.onEnrolleeSuccess(newNetworkId);
             });
@@ -5185,22 +5188,28 @@
         @Override
         public void onSuccess(int status) {
             Log.d(TAG, "Easy Connect onSuccess callback");
+            Binder.clearCallingIdentity();
             mExecutor.execute(() -> {
                 mEasyConnectStatusCallback.onConfiguratorSuccess(status);
             });
         }
 
         @Override
-        public void onFailure(int status) {
+        public void onFailure(int status, String ssid, String channelList,
+                int[] operatingClassArray) {
             Log.d(TAG, "Easy Connect onFailure callback");
+            Binder.clearCallingIdentity();
             mExecutor.execute(() -> {
-                mEasyConnectStatusCallback.onFailure(status);
+                SparseArray<int[]> channelListArray = parseDppChannelList(channelList);
+                mEasyConnectStatusCallback.onFailure(status, ssid, channelListArray,
+                        operatingClassArray);
             });
         }
 
         @Override
         public void onProgress(int status) {
             Log.d(TAG, "Easy Connect onProgress callback");
+            Binder.clearCallingIdentity();
             mExecutor.execute(() -> {
                 mEasyConnectStatusCallback.onProgress(status);
             });
@@ -5532,4 +5541,77 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Parse the list of channels the DPP enrollee reports when it fails to find an AP.
+     *
+     * @param channelList List of channels in the format defined in the DPP specification.
+     * @return A parsed sparse array, where the operating class is the key.
+     * @hide
+     */
+    @VisibleForTesting
+    public static SparseArray<int[]> parseDppChannelList(String channelList) {
+        SparseArray<int[]> channelListArray = new SparseArray<>();
+
+        if (TextUtils.isEmpty(channelList)) {
+            return channelListArray;
+        }
+        StringTokenizer str = new StringTokenizer(channelList, ",");
+        String classStr = null;
+        List<Integer> channelsInClass = new ArrayList<>();
+
+        try {
+            while (str.hasMoreElements()) {
+                String cur = str.nextToken();
+
+                /**
+                 * Example for a channel list:
+                 *
+                 * 81/1,2,3,4,5,6,7,8,9,10,11,115/36,40,44,48,118/52,56,60,64,121/100,104,108,112,
+                 * 116,120,124,128,132,136,140,0/144,124/149,153,157,161,125/165
+                 *
+                 * Detect operating class by the delimiter of '/' and use a string tokenizer with
+                 * ',' as a delimiter.
+                 */
+                int classDelim = cur.indexOf('/');
+                if (classDelim != -1) {
+                    if (classStr != null) {
+                        // Store the last channel array in the sparse array, where the operating
+                        // class is the key (as an integer).
+                        int[] channelsArray = new int[channelsInClass.size()];
+                        for (int i = 0; i < channelsInClass.size(); i++) {
+                            channelsArray[i] = channelsInClass.get(i);
+                        }
+                        channelListArray.append(Integer.parseInt(classStr), channelsArray);
+                        channelsInClass = new ArrayList<>();
+                    }
+
+                    // Init a new operating class and store the first channel
+                    classStr = cur.substring(0, classDelim);
+                    String channelStr = cur.substring(classDelim + 1);
+                    channelsInClass.add(Integer.parseInt(channelStr));
+                } else {
+                    if (classStr == null) {
+                        // Invalid format
+                        Log.e(TAG, "Cannot parse DPP channel list");
+                        return new SparseArray<>();
+                    }
+                    channelsInClass.add(Integer.parseInt(cur));
+                }
+            }
+
+            // Store the last array
+            if (classStr != null) {
+                int[] channelsArray = new int[channelsInClass.size()];
+                for (int i = 0; i < channelsInClass.size(); i++) {
+                    channelsArray[i] = channelsInClass.get(i);
+                }
+                channelListArray.append(Integer.parseInt(classStr), channelsArray);
+            }
+            return channelListArray;
+        } catch (NumberFormatException e) {
+            Log.e(TAG, "Cannot parse DPP channel list");
+            return new SparseArray<>();
+        }
+    }
 }
diff --git a/wifi/tests/src/android/net/wifi/EasyConnectStatusCallbackTest.java b/wifi/tests/src/android/net/wifi/EasyConnectStatusCallbackTest.java
new file mode 100644
index 0000000..b101414
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/EasyConnectStatusCallbackTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.util.SparseArray;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link android.net.wifi.EasyConnectStatusCallbackTest}.
+ */
+@SmallTest
+public class EasyConnectStatusCallbackTest {
+    private EasyConnectStatusCallback mEasyConnectStatusCallback = new EasyConnectStatusCallback() {
+        @Override
+        public void onEnrolleeSuccess(int newNetworkId) {
+
+        }
+
+        @Override
+        public void onConfiguratorSuccess(int code) {
+
+        }
+
+        @Override
+        public void onProgress(int code) {
+
+        }
+
+        @Override
+        public void onFailure(int code) {
+            mOnFailureR1EventReceived = true;
+            mLastCode = code;
+        }
+    };
+    private boolean mOnFailureR1EventReceived;
+    private int mLastCode;
+
+    @Before
+    public void setUp() {
+        mOnFailureR1EventReceived = false;
+        mLastCode = 0;
+    }
+
+    /**
+     * Test that the legacy R1 onFailure is called by default if the R2 onFailure is not overridden
+     * by the app.
+     */
+    @Test
+    public void testR1OnFailureCalled() {
+
+        SparseArray<int[]> channelList = new SparseArray<>();
+        int[] channelArray = new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
+
+        channelList.append(81, channelArray);
+        mEasyConnectStatusCallback.onFailure(
+                EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK,
+                "SomeSSID", channelList, new int[] {81});
+
+        assertTrue(mOnFailureR1EventReceived);
+        assertEquals(mLastCode,
+                EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK);
+    }
+}
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index 4a46744..8216611 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -89,6 +89,7 @@
 import android.os.RemoteException;
 import android.os.connectivity.WifiActivityEnergyInfo;
 import android.os.test.TestLooper;
+import android.util.SparseArray;
 
 import androidx.test.filters.SmallTest;
 
@@ -2089,4 +2090,63 @@
                 .thenReturn(new Long(~WifiManager.WIFI_FEATURE_WAPI));
         assertFalse(mWifiManager.isWapiSupported());
     }
+
+    /*
+     * Test that DPP channel list is parsed correctly
+     */
+    @Test
+    public void testparseDppChannelList() throws Exception {
+        String channelList = "81/1,2,3,4,5,6,7,8,9,10,11,115/36,40,44,48";
+        SparseArray<int[]> expectedResult = new SparseArray<>();
+        expectedResult.append(81, new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11});
+        expectedResult.append(115, new int[]{36, 40, 44, 48});
+
+        SparseArray<int[]> result = WifiManager.parseDppChannelList(channelList);
+        assertEquals(expectedResult.size(), result.size());
+
+        int index = 0;
+        int key;
+
+        // Compare the two primitive int arrays
+        do {
+            try {
+                key = result.keyAt(index);
+            } catch (java.lang.ArrayIndexOutOfBoundsException e) {
+                break;
+            }
+            int[] expected = expectedResult.get(key);
+            int[] output = result.get(key);
+            assertEquals(expected.length, output.length);
+            for (int i = 0; i < output.length; i++) {
+                assertEquals(expected[i], output[i]);
+            }
+            index++;
+        } while (true);
+    }
+
+    /*
+     * Test that DPP channel list parser gracefully fails for invalid input
+     */
+    @Test
+    public void testparseDppChannelListWithInvalidFormats() throws Exception {
+        String channelList = "1,2,3,4,5,6,7,8,9,10,11,36,40,44,48";
+        SparseArray<int[]> result = WifiManager.parseDppChannelList(channelList);
+        assertEquals(result.size(), 0);
+
+        channelList = "ajgalskgjalskjg3-09683dh";
+        result = WifiManager.parseDppChannelList(channelList);
+        assertEquals(result.size(), 0);
+
+        channelList = "13/abc,46////";
+        result = WifiManager.parseDppChannelList(channelList);
+        assertEquals(result.size(), 0);
+
+        channelList = "11/4,5,13/";
+        result = WifiManager.parseDppChannelList(channelList);
+        assertEquals(result.size(), 0);
+
+        channelList = "/24,6";
+        result = WifiManager.parseDppChannelList(channelList);
+        assertEquals(result.size(), 0);
+    }
 }