Merge "Add white edgelights to AOSP" into qt-dev
diff --git a/api/test-current.txt b/api/test-current.txt
index ee6fb51..1a912a1c 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1917,6 +1917,10 @@
     method public boolean hasSingleFileDescriptor();
   }
 
+  public final class Parcel {
+    method public int readExceptionCode();
+  }
+
   public class ParcelFileDescriptor implements java.io.Closeable android.os.Parcelable {
     method public static java.io.File getFile(java.io.FileDescriptor) throws java.io.IOException;
   }
@@ -1986,6 +1990,7 @@
   public class SystemProperties {
     method @NonNull public static String get(@NonNull String);
     method @NonNull public static String get(@NonNull String, @Nullable String);
+    method public static boolean getBoolean(@NonNull String, boolean);
   }
 
   public final class UserHandle implements android.os.Parcelable {
@@ -2140,6 +2145,36 @@
 
 }
 
+package android.os.image {
+
+  public class DynamicSystemClient {
+    ctor public DynamicSystemClient(@NonNull android.content.Context);
+    method @RequiresPermission("android.permission.INSTALL_DYNAMIC_SYSTEM") public void bind();
+    method public void setOnStatusChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.os.image.DynamicSystemClient.OnStatusChangedListener);
+    method public void setOnStatusChangedListener(@NonNull android.os.image.DynamicSystemClient.OnStatusChangedListener);
+    method @RequiresPermission("android.permission.INSTALL_DYNAMIC_SYSTEM") public void start(@NonNull android.net.Uri, long);
+    method @RequiresPermission("android.permission.INSTALL_DYNAMIC_SYSTEM") public void start(@NonNull android.net.Uri, long, long);
+    method @RequiresPermission("android.permission.INSTALL_DYNAMIC_SYSTEM") public void unbind();
+    field public static final int CAUSE_ERROR_EXCEPTION = 6; // 0x6
+    field public static final int CAUSE_ERROR_INVALID_URL = 4; // 0x4
+    field public static final int CAUSE_ERROR_IO = 3; // 0x3
+    field public static final int CAUSE_ERROR_IPC = 5; // 0x5
+    field public static final int CAUSE_INSTALL_CANCELLED = 2; // 0x2
+    field public static final int CAUSE_INSTALL_COMPLETED = 1; // 0x1
+    field public static final int CAUSE_NOT_SPECIFIED = 0; // 0x0
+    field public static final int STATUS_IN_PROGRESS = 2; // 0x2
+    field public static final int STATUS_IN_USE = 4; // 0x4
+    field public static final int STATUS_NOT_STARTED = 1; // 0x1
+    field public static final int STATUS_READY = 3; // 0x3
+    field public static final int STATUS_UNKNOWN = 0; // 0x0
+  }
+
+  public static interface DynamicSystemClient.OnStatusChangedListener {
+    method public void onStatusChanged(int, int, long, @Nullable Throwable);
+  }
+
+}
+
 package android.os.storage {
 
   public class StorageManager {
@@ -2946,6 +2981,21 @@
     method public E valueAtUnchecked(int);
   }
 
+  public class FeatureFlagUtils {
+    ctor public FeatureFlagUtils();
+    method public static java.util.Map<java.lang.String,java.lang.String> getAllFeatureFlags();
+    method public static boolean isEnabled(android.content.Context, String);
+    method public static void setEnabled(android.content.Context, String, boolean);
+    field public static final String DYNAMIC_SYSTEM = "settings_dynamic_system";
+    field public static final String FFLAG_OVERRIDE_PREFIX = "sys.fflag.override.";
+    field public static final String FFLAG_PREFIX = "sys.fflag.";
+    field public static final String HEARING_AID_SETTINGS = "settings_bluetooth_hearing_aid";
+    field public static final String PERSIST_PREFIX = "persist.sys.fflag.override.";
+    field public static final String PIXEL_WALLPAPER_CATEGORY_SWITCH = "settings_pixel_wallpaper_category_switch";
+    field public static final String SCREENRECORD_LONG_PRESS = "settings_screenrecord_long_press";
+    field public static final String SEAMLESS_TRANSFER = "settings_seamless_transfer";
+  }
+
   public class TimeUtils {
     method public static String formatDuration(long);
   }
diff --git a/config/hiddenapi-greylist-packages.txt b/config/hiddenapi-greylist-packages.txt
index cae3bd9..986d259 100644
--- a/config/hiddenapi-greylist-packages.txt
+++ b/config/hiddenapi-greylist-packages.txt
@@ -1,2 +1,43 @@
+gov.nist.core
+gov.nist.core.net
+gov.nist.javax.sip
+gov.nist.javax.sip.address
+gov.nist.javax.sip.clientauthutils
+gov.nist.javax.sip.header
+gov.nist.javax.sip.header.extensions
+gov.nist.javax.sip.header.ims
+gov.nist.javax.sip.message
+gov.nist.javax.sip.parser
+gov.nist.javax.sip.parser.extensions
+gov.nist.javax.sip.parser.ims
+gov.nist.javax.sip.stack
+org.apache.xalan
+org.apache.xalan.extensions
+org.apache.xalan.processor
+org.apache.xalan.res
+org.apache.xalan.serialize
+org.apache.xalan.templates
+org.apache.xalan.transformer
+org.apache.xalan.xslt
+org.apache.xml.dtm
+org.apache.xml.dtm.ref
+org.apache.xml.dtm.ref.dom2dtm
+org.apache.xml.dtm.ref.sax2dtm
+org.apache.xml.res
+org.apache.xml.serializer
+org.apache.xml.serializer.dom3
+org.apache.xml.serializer.utils
+org.apache.xml.utils
+org.apache.xml.utils.res
+org.apache.xpath
+org.apache.xpath.axes
+org.apache.xpath.compiler
+org.apache.xpath.domapi
+org.apache.xpath.functions
+org.apache.xpath.jaxp
+org.apache.xpath.objects
+org.apache.xpath.operations
+org.apache.xpath.patterns
+org.apache.xpath.res
 org.ccil.cowan.tagsoup
 org.ccil.cowan.tagsoup.jaxp
diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt
index 82d0fbe..10f25ea 100644
--- a/config/hiddenapi-greylist.txt
+++ b/config/hiddenapi-greylist.txt
@@ -1774,715 +1774,3 @@
 Lcom/google/android/mms/util/SqliteWrapper;->requery(Landroid/content/Context;Landroid/database/Cursor;)Z
 Lcom/google/android/mms/util/SqliteWrapper;->update(Landroid/content/Context;Landroid/content/ContentResolver;Landroid/net/Uri;Landroid/content/ContentValues;Ljava/lang/String;[Ljava/lang/String;)I
 Lcom/google/android/util/AbstractMessageParser$Token$Type;->values()[Lcom/google/android/util/AbstractMessageParser$Token$Type;
-Lgov/nist/core/Debug;->printStackTrace(Ljava/lang/Exception;)V
-Lgov/nist/core/GenericObject;-><init>()V
-Lgov/nist/core/GenericObject;->dbgPrint()V
-Lgov/nist/core/GenericObject;->debugDump(I)Ljava/lang/String;
-Lgov/nist/core/GenericObject;->encode()Ljava/lang/String;
-Lgov/nist/core/GenericObject;->getMatcher()Lgov/nist/core/Match;
-Lgov/nist/core/GenericObject;->indentation:I
-Lgov/nist/core/GenericObject;->isMySubclass(Ljava/lang/Class;)Z
-Lgov/nist/core/GenericObject;->match(Ljava/lang/Object;)Z
-Lgov/nist/core/GenericObject;->matchExpression:Lgov/nist/core/Match;
-Lgov/nist/core/GenericObject;->merge(Ljava/lang/Object;)V
-Lgov/nist/core/GenericObject;->sprint(Ljava/lang/String;)V
-Lgov/nist/core/GenericObject;->stringRepresentation:Ljava/lang/String;
-Lgov/nist/core/GenericObjectList;-><init>()V
-Lgov/nist/core/GenericObjectList;-><init>(Ljava/lang/String;)V
-Lgov/nist/core/GenericObjectList;-><init>(Ljava/lang/String;Ljava/lang/Class;)V
-Lgov/nist/core/GenericObjectList;-><init>(Ljava/lang/String;Ljava/lang/String;)V
-Lgov/nist/core/GenericObjectList;->concatenate(Lgov/nist/core/GenericObjectList;)V
-Lgov/nist/core/GenericObjectList;->concatenate(Lgov/nist/core/GenericObjectList;Z)V
-Lgov/nist/core/GenericObjectList;->debugDump(I)Ljava/lang/String;
-Lgov/nist/core/GenericObjectList;->first()Lgov/nist/core/GenericObject;
-Lgov/nist/core/GenericObjectList;->getIndentation()Ljava/lang/String;
-Lgov/nist/core/GenericObjectList;->indentation:I
-Lgov/nist/core/GenericObjectList;->isMySubclass(Ljava/lang/Class;)Z
-Lgov/nist/core/GenericObjectList;->match(Ljava/lang/Object;)Z
-Lgov/nist/core/GenericObjectList;->myClass:Ljava/lang/Class;
-Lgov/nist/core/GenericObjectList;->next()Lgov/nist/core/GenericObject;
-Lgov/nist/core/GenericObjectList;->next(Ljava/util/ListIterator;)Lgov/nist/core/GenericObject;
-Lgov/nist/core/GenericObjectList;->setMyClass(Ljava/lang/Class;)V
-Lgov/nist/core/GenericObjectList;->stringRep:Ljava/lang/String;
-Lgov/nist/core/Host;-><init>()V
-Lgov/nist/core/Host;-><init>(Ljava/lang/String;)V
-Lgov/nist/core/Host;->encode()Ljava/lang/String;
-Lgov/nist/core/Host;->getAddress()Ljava/lang/String;
-Lgov/nist/core/Host;->getHostname()Ljava/lang/String;
-Lgov/nist/core/Host;->isIPv6Reference(Ljava/lang/String;)Z
-Lgov/nist/core/Host;->setAddress(Ljava/lang/String;)V
-Lgov/nist/core/Host;->setHostname(Ljava/lang/String;)V
-Lgov/nist/core/HostNameParser;-><init>(Lgov/nist/core/LexerCore;)V
-Lgov/nist/core/HostNameParser;-><init>(Ljava/lang/String;)V
-Lgov/nist/core/HostNameParser;->host()Lgov/nist/core/Host;
-Lgov/nist/core/HostNameParser;->hostPort(Z)Lgov/nist/core/HostPort;
-Lgov/nist/core/HostPort;-><init>()V
-Lgov/nist/core/HostPort;->encode()Ljava/lang/String;
-Lgov/nist/core/HostPort;->encode(Ljava/lang/StringBuffer;)Ljava/lang/StringBuffer;
-Lgov/nist/core/HostPort;->getHost()Lgov/nist/core/Host;
-Lgov/nist/core/HostPort;->getInetAddress()Ljava/net/InetAddress;
-Lgov/nist/core/HostPort;->getPort()I
-Lgov/nist/core/HostPort;->hasPort()Z
-Lgov/nist/core/HostPort;->removePort()V
-Lgov/nist/core/HostPort;->setHost(Lgov/nist/core/Host;)V
-Lgov/nist/core/HostPort;->setPort(I)V
-Lgov/nist/core/InternalErrorHandler;->handleException(Ljava/lang/Exception;)V
-Lgov/nist/core/InternalErrorHandler;->handleException(Ljava/lang/String;)V
-Lgov/nist/core/LexerCore;-><init>(Ljava/lang/String;Ljava/lang/String;)V
-Lgov/nist/core/LexerCore;->byteStringNoSemicolon()Ljava/lang/String;
-Lgov/nist/core/LexerCore;->byteStringNoSlash()Ljava/lang/String;
-Lgov/nist/core/LexerCore;->charAsString(I)Ljava/lang/String;
-Lgov/nist/core/LexerCore;->comment()Ljava/lang/String;
-Lgov/nist/core/LexerCore;->createParseException()Ljava/text/ParseException;
-Lgov/nist/core/LexerCore;->currentLexer:Ljava/util/Hashtable;
-Lgov/nist/core/LexerCore;->getBuffer()Ljava/lang/String;
-Lgov/nist/core/LexerCore;->getNextId()Ljava/lang/String;
-Lgov/nist/core/LexerCore;->getNextToken()Lgov/nist/core/Token;
-Lgov/nist/core/LexerCore;->getPtr()I
-Lgov/nist/core/LexerCore;->getRest()Ljava/lang/String;
-Lgov/nist/core/LexerCore;->getString(C)Ljava/lang/String;
-Lgov/nist/core/LexerCore;->isTokenChar(C)Z
-Lgov/nist/core/LexerCore;->lexerTables:Ljava/util/Hashtable;
-Lgov/nist/core/LexerCore;->markInputPosition()I
-Lgov/nist/core/LexerCore;->match(I)Lgov/nist/core/Token;
-Lgov/nist/core/LexerCore;->number()Ljava/lang/String;
-Lgov/nist/core/LexerCore;->peekNextToken()Lgov/nist/core/Token;
-Lgov/nist/core/LexerCore;->peekNextToken(I)[Lgov/nist/core/Token;
-Lgov/nist/core/LexerCore;->quotedString()Ljava/lang/String;
-Lgov/nist/core/LexerCore;->rewindInputPosition(I)V
-Lgov/nist/core/LexerCore;->selectLexer(Ljava/lang/String;)V
-Lgov/nist/core/LexerCore;->SPorHT()V
-Lgov/nist/core/LexerCore;->startsId()Z
-Lgov/nist/core/LexerCore;->ttoken()Ljava/lang/String;
-Lgov/nist/core/LexerCore;->ttokenSafe()Ljava/lang/String;
-Lgov/nist/core/Match;->match(Ljava/lang/String;)Z
-Lgov/nist/core/NameValue;-><init>()V
-Lgov/nist/core/NameValue;-><init>(Ljava/lang/String;Ljava/lang/Object;)V
-Lgov/nist/core/NameValue;-><init>(Ljava/lang/String;Ljava/lang/Object;Z)V
-Lgov/nist/core/NameValue;->encode()Ljava/lang/String;
-Lgov/nist/core/NameValue;->encode(Ljava/lang/StringBuffer;)Ljava/lang/StringBuffer;
-Lgov/nist/core/NameValue;->getName()Ljava/lang/String;
-Lgov/nist/core/NameValue;->getValueAsObject()Ljava/lang/Object;
-Lgov/nist/core/NameValue;->setName(Ljava/lang/String;)V
-Lgov/nist/core/NameValue;->setQuotedValue()V
-Lgov/nist/core/NameValue;->setSeparator(Ljava/lang/String;)V
-Lgov/nist/core/NameValue;->setValueAsObject(Ljava/lang/Object;)V
-Lgov/nist/core/NameValueList;-><init>()V
-Lgov/nist/core/NameValueList;-><init>(Z)V
-Lgov/nist/core/NameValueList;->delete(Ljava/lang/String;)Z
-Lgov/nist/core/NameValueList;->encode()Ljava/lang/String;
-Lgov/nist/core/NameValueList;->encode(Ljava/lang/StringBuffer;)Ljava/lang/StringBuffer;
-Lgov/nist/core/NameValueList;->getNames()Ljava/util/Iterator;
-Lgov/nist/core/NameValueList;->getNameValue(Ljava/lang/String;)Lgov/nist/core/NameValue;
-Lgov/nist/core/NameValueList;->getParameter(Ljava/lang/String;)Ljava/lang/String;
-Lgov/nist/core/NameValueList;->getValue(Ljava/lang/String;)Ljava/lang/Object;
-Lgov/nist/core/NameValueList;->hasNameValue(Ljava/lang/String;)Z
-Lgov/nist/core/NameValueList;->iterator()Ljava/util/Iterator;
-Lgov/nist/core/NameValueList;->set(Lgov/nist/core/NameValue;)V
-Lgov/nist/core/NameValueList;->set(Ljava/lang/String;Ljava/lang/Object;)V
-Lgov/nist/core/NameValueList;->setSeparator(Ljava/lang/String;)V
-Lgov/nist/core/net/DefaultNetworkLayer;->SINGLETON:Lgov/nist/core/net/DefaultNetworkLayer;
-Lgov/nist/core/net/NetworkLayer;->createDatagramSocket()Ljava/net/DatagramSocket;
-Lgov/nist/core/net/NetworkLayer;->createDatagramSocket(ILjava/net/InetAddress;)Ljava/net/DatagramSocket;
-Lgov/nist/core/net/NetworkLayer;->createServerSocket(IILjava/net/InetAddress;)Ljava/net/ServerSocket;
-Lgov/nist/core/net/NetworkLayer;->createSocket(Ljava/net/InetAddress;I)Ljava/net/Socket;
-Lgov/nist/core/net/NetworkLayer;->createSSLServerSocket(IILjava/net/InetAddress;)Ljavax/net/ssl/SSLServerSocket;
-Lgov/nist/core/net/NetworkLayer;->createSSLSocket(Ljava/net/InetAddress;I)Ljavax/net/ssl/SSLSocket;
-Lgov/nist/core/ParserCore;-><init>()V
-Lgov/nist/core/ParserCore;->lexer:Lgov/nist/core/LexerCore;
-Lgov/nist/core/StringTokenizer;->ptr:I
-Lgov/nist/core/ThreadAuditor$ThreadHandle;->getPingIntervalInMillisecs()J
-Lgov/nist/core/ThreadAuditor$ThreadHandle;->ping()V
-Lgov/nist/core/ThreadAuditor;-><init>()V
-Lgov/nist/core/ThreadAuditor;->addCurrentThread()Lgov/nist/core/ThreadAuditor$ThreadHandle;
-Lgov/nist/core/ThreadAuditor;->getPingIntervalInMillisecs()J
-Lgov/nist/core/ThreadAuditor;->isEnabled()Z
-Lgov/nist/core/ThreadAuditor;->setPingIntervalInMillisecs(J)V
-Lgov/nist/core/Token;-><init>()V
-Lgov/nist/core/Token;->getTokenType()I
-Lgov/nist/core/Token;->getTokenValue()Ljava/lang/String;
-Lgov/nist/javax/sip/address/GenericURI;-><init>()V
-Lgov/nist/javax/sip/address/GenericURI;->encode()Ljava/lang/String;
-Lgov/nist/javax/sip/address/GenericURI;->getScheme()Ljava/lang/String;
-Lgov/nist/javax/sip/address/SipUri;->getHost()Ljava/lang/String;
-Lgov/nist/javax/sip/address/SipUri;->getParameter(Ljava/lang/String;)Ljava/lang/String;
-Lgov/nist/javax/sip/address/SipUri;->getPort()I
-Lgov/nist/javax/sip/address/SipUri;->getUser()Ljava/lang/String;
-Lgov/nist/javax/sip/address/SipUri;->removeParameter(Ljava/lang/String;)V
-Lgov/nist/javax/sip/address/SipUri;->setParameter(Ljava/lang/String;Ljava/lang/String;)V
-Lgov/nist/javax/sip/address/SipUri;->setUserParam(Ljava/lang/String;)V
-Lgov/nist/javax/sip/parser/URLParser;-><init>(Ljava/lang/String;)V
-Lgov/nist/javax/sip/parser/URLParser;->sipURL(Z)Lgov/nist/javax/sip/address/SipUri;
-Lorg/apache/xalan/extensions/ExpressionContext;->getContextNode()Lorg/w3c/dom/Node;
-Lorg/apache/xalan/extensions/ExpressionContext;->getErrorListener()Ljavax/xml/transform/ErrorListener;
-Lorg/apache/xalan/extensions/ExpressionContext;->getVariableOrParam(Lorg/apache/xml/utils/QName;)Lorg/apache/xpath/objects/XObject;
-Lorg/apache/xalan/extensions/ExpressionContext;->getXPathContext()Lorg/apache/xpath/XPathContext;
-Lorg/apache/xalan/extensions/ExtensionHandler;-><init>(Ljava/lang/String;Ljava/lang/String;)V
-Lorg/apache/xalan/extensions/ExtensionHandler;->callFunction(Ljava/lang/String;Ljava/util/Vector;Ljava/lang/Object;Lorg/apache/xalan/extensions/ExpressionContext;)Ljava/lang/Object;
-Lorg/apache/xalan/extensions/ExtensionHandler;->getClassForName(Ljava/lang/String;)Ljava/lang/Class;
-Lorg/apache/xalan/extensions/ObjectFactory$ConfigurationError;-><init>(Ljava/lang/String;Ljava/lang/Exception;)V
-Lorg/apache/xalan/extensions/ObjectFactory;->findClassLoader()Ljava/lang/ClassLoader;
-Lorg/apache/xalan/extensions/ObjectFactory;->findProviderClass(Ljava/lang/String;Ljava/lang/ClassLoader;Z)Ljava/lang/Class;
-Lorg/apache/xalan/processor/TransformerFactoryImpl;-><init>()V
-Lorg/apache/xalan/res/XSLMessages;->createMessage(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
-Lorg/apache/xalan/res/XSLTErrorResources;-><init>()V
-Lorg/apache/xalan/serialize/SerializerUtils;->outputResultTreeFragment(Lorg/apache/xml/serializer/SerializationHandler;Lorg/apache/xpath/objects/XObject;Lorg/apache/xpath/XPathContext;)V
-Lorg/apache/xalan/templates/AVT;->evaluate(Lorg/apache/xpath/XPathContext;ILorg/apache/xml/utils/PrefixResolver;)Ljava/lang/String;
-Lorg/apache/xalan/templates/ElemElement;->execute(Lorg/apache/xalan/transformer/TransformerImpl;)V
-Lorg/apache/xalan/templates/ElemExsltFunction;->execute(Lorg/apache/xalan/transformer/TransformerImpl;[Lorg/apache/xpath/objects/XObject;)V
-Lorg/apache/xalan/templates/ElemExtensionCall;->getAttribute(Ljava/lang/String;Lorg/w3c/dom/Node;Lorg/apache/xalan/transformer/TransformerImpl;)Ljava/lang/String;
-Lorg/apache/xalan/templates/ElemLiteralResult;->getLiteralResultAttribute(Ljava/lang/String;)Lorg/apache/xalan/templates/AVT;
-Lorg/apache/xalan/templates/ElemTemplate;->getMatch()Lorg/apache/xpath/XPath;
-Lorg/apache/xalan/templates/ElemTemplate;->getName()Lorg/apache/xml/utils/QName;
-Lorg/apache/xalan/templates/ElemTemplateElement;->getFirstChildElem()Lorg/apache/xalan/templates/ElemTemplateElement;
-Lorg/apache/xalan/templates/ElemTemplateElement;->getNextSiblingElem()Lorg/apache/xalan/templates/ElemTemplateElement;
-Lorg/apache/xalan/templates/ElemTemplateElement;->getParentElem()Lorg/apache/xalan/templates/ElemTemplateElement;
-Lorg/apache/xalan/templates/ElemTemplateElement;->getStylesheetRoot()Lorg/apache/xalan/templates/StylesheetRoot;
-Lorg/apache/xalan/templates/ElemTemplateElement;->getXSLToken()I
-Lorg/apache/xalan/templates/ElemTextLiteral;->getChars()[C
-Lorg/apache/xalan/templates/KeyDeclaration;->getName()Lorg/apache/xml/utils/QName;
-Lorg/apache/xalan/templates/KeyDeclaration;->getUse()Lorg/apache/xpath/XPath;
-Lorg/apache/xalan/templates/StylesheetRoot;->getDefaultRootRule()Lorg/apache/xalan/templates/ElemTemplate;
-Lorg/apache/xalan/templates/StylesheetRoot;->getDefaultRule()Lorg/apache/xalan/templates/ElemTemplate;
-Lorg/apache/xalan/templates/StylesheetRoot;->getDefaultTextRule()Lorg/apache/xalan/templates/ElemTemplate;
-Lorg/apache/xalan/templates/StylesheetRoot;->getTemplateComposed(Lorg/apache/xml/utils/QName;)Lorg/apache/xalan/templates/ElemTemplate;
-Lorg/apache/xalan/transformer/ClonerToResultTree;->cloneToResultTree(IILorg/apache/xml/dtm/DTM;Lorg/apache/xml/serializer/SerializationHandler;Z)V
-Lorg/apache/xalan/transformer/DecimalToRoman;-><init>(JLjava/lang/String;JLjava/lang/String;)V
-Lorg/apache/xalan/transformer/DecimalToRoman;->m_postLetter:Ljava/lang/String;
-Lorg/apache/xalan/transformer/DecimalToRoman;->m_postValue:J
-Lorg/apache/xalan/transformer/DecimalToRoman;->m_preLetter:Ljava/lang/String;
-Lorg/apache/xalan/transformer/DecimalToRoman;->m_preValue:J
-Lorg/apache/xalan/transformer/MsgMgr;->error(Ljavax/xml/transform/SourceLocator;Lorg/w3c/dom/Node;Lorg/w3c/dom/Node;Ljava/lang/String;)V
-Lorg/apache/xalan/transformer/TransformerImpl;->createSerializationHandler(Ljavax/xml/transform/Result;Lorg/apache/xalan/templates/OutputProperties;)Lorg/apache/xml/serializer/SerializationHandler;
-Lorg/apache/xalan/transformer/TransformerImpl;->executeChildTemplates(Lorg/apache/xalan/templates/ElemTemplateElement;Lorg/w3c/dom/Node;Lorg/apache/xml/utils/QName;Lorg/xml/sax/ContentHandler;)V
-Lorg/apache/xalan/transformer/TransformerImpl;->executeChildTemplates(Lorg/apache/xalan/templates/ElemTemplateElement;Z)V
-Lorg/apache/xalan/transformer/TransformerImpl;->getCountersTable()Lorg/apache/xalan/transformer/CountersTable;
-Lorg/apache/xalan/transformer/TransformerImpl;->getCurrentTemplateElements()Lorg/apache/xml/utils/ObjectStack;
-Lorg/apache/xalan/transformer/TransformerImpl;->getCurrentTemplateElementsCount()I
-Lorg/apache/xalan/transformer/TransformerImpl;->getMatchedNode()I
-Lorg/apache/xalan/transformer/TransformerImpl;->getMatchedTemplate()Lorg/apache/xalan/templates/ElemTemplate;
-Lorg/apache/xalan/transformer/TransformerImpl;->getMode()Lorg/apache/xml/utils/QName;
-Lorg/apache/xalan/transformer/TransformerImpl;->getMsgMgr()Lorg/apache/xalan/transformer/MsgMgr;
-Lorg/apache/xalan/transformer/TransformerImpl;->getOutputFormat()Lorg/apache/xalan/templates/OutputProperties;
-Lorg/apache/xalan/transformer/TransformerImpl;->getResultTreeHandler()Lorg/apache/xml/serializer/SerializationHandler;
-Lorg/apache/xalan/transformer/TransformerImpl;->getSerializationHandler()Lorg/apache/xml/serializer/SerializationHandler;
-Lorg/apache/xalan/transformer/TransformerImpl;->getXPathContext()Lorg/apache/xpath/XPathContext;
-Lorg/apache/xalan/transformer/TransformerImpl;->m_attrSetStack:Ljava/util/Stack;
-Lorg/apache/xalan/transformer/TransformerImpl;->m_currentMatchedNodes:Lorg/apache/xml/utils/NodeVector;
-Lorg/apache/xalan/transformer/TransformerImpl;->m_currentMatchTemplates:Ljava/util/Stack;
-Lorg/apache/xalan/transformer/TransformerImpl;->m_currentTemplateElements:Lorg/apache/xml/utils/ObjectStack;
-Lorg/apache/xalan/transformer/TransformerImpl;->m_currentTemplateRuleIsNull:Lorg/apache/xml/utils/BoolStack;
-Lorg/apache/xalan/transformer/TransformerImpl;->m_inputContentHandler:Lorg/xml/sax/ContentHandler;
-Lorg/apache/xalan/transformer/TransformerImpl;->m_outputTarget:Ljavax/xml/transform/Result;
-Lorg/apache/xalan/transformer/TransformerImpl;->m_stringWriterObjectPool:Lorg/apache/xml/utils/ObjectPool;
-Lorg/apache/xalan/transformer/TransformerImpl;->m_urlOfSource:Ljava/lang/String;
-Lorg/apache/xalan/transformer/TransformerImpl;->m_xcontext:Lorg/apache/xpath/XPathContext;
-Lorg/apache/xalan/transformer/TransformerImpl;->popCurrentFuncResult()Ljava/lang/Object;
-Lorg/apache/xalan/transformer/TransformerImpl;->pushCurrentFuncResult(Ljava/lang/Object;)V
-Lorg/apache/xalan/transformer/TransformerImpl;->pushElemTemplateElement(Lorg/apache/xalan/templates/ElemTemplateElement;)V
-Lorg/apache/xalan/Version;->getVersion()Ljava/lang/String;
-Lorg/apache/xalan/xslt/EnvironmentCheck;-><init>()V
-Lorg/apache/xalan/xslt/EnvironmentCheck;->appendEnvironmentReport(Lorg/w3c/dom/Node;Lorg/w3c/dom/Document;Ljava/util/Hashtable;)V
-Lorg/apache/xalan/xslt/EnvironmentCheck;->getEnvironmentHash()Ljava/util/Hashtable;
-Lorg/apache/xalan/xslt/ObjectFactory;->findClassLoader()Ljava/lang/ClassLoader;
-Lorg/apache/xalan/xslt/ObjectFactory;->newInstance(Ljava/lang/String;Ljava/lang/ClassLoader;Z)Ljava/lang/Object;
-Lorg/apache/xml/dtm/Axis;->getNames(I)Ljava/lang/String;
-Lorg/apache/xml/dtm/Axis;->isReverse(I)Z
-Lorg/apache/xml/dtm/DTM;->getDocument()I
-Lorg/apache/xml/dtm/DTM;->getDocumentRoot(I)I
-Lorg/apache/xml/dtm/DTM;->getFirstChild(I)I
-Lorg/apache/xml/dtm/DTM;->getNextSibling(I)I
-Lorg/apache/xml/dtm/DTM;->getNode(I)Lorg/w3c/dom/Node;
-Lorg/apache/xml/dtm/DTM;->getNodeName(I)Ljava/lang/String;
-Lorg/apache/xml/dtm/DTM;->getNodeType(I)S
-Lorg/apache/xml/dtm/DTM;->getParent(I)I
-Lorg/apache/xml/dtm/DTM;->getSourceLocatorFor(I)Ljavax/xml/transform/SourceLocator;
-Lorg/apache/xml/dtm/DTM;->getStringValue(I)Lorg/apache/xml/utils/XMLString;
-Lorg/apache/xml/dtm/DTM;->migrateTo(Lorg/apache/xml/dtm/DTMManager;)V
-Lorg/apache/xml/dtm/DTMAxisIterator;->cloneIterator()Lorg/apache/xml/dtm/DTMAxisIterator;
-Lorg/apache/xml/dtm/DTMAxisIterator;->getLast()I
-Lorg/apache/xml/dtm/DTMAxisIterator;->getNodeByPosition(I)I
-Lorg/apache/xml/dtm/DTMAxisIterator;->getPosition()I
-Lorg/apache/xml/dtm/DTMAxisIterator;->gotoMark()V
-Lorg/apache/xml/dtm/DTMAxisIterator;->isReverse()Z
-Lorg/apache/xml/dtm/DTMAxisIterator;->next()I
-Lorg/apache/xml/dtm/DTMAxisIterator;->reset()Lorg/apache/xml/dtm/DTMAxisIterator;
-Lorg/apache/xml/dtm/DTMAxisIterator;->setMark()V
-Lorg/apache/xml/dtm/DTMAxisIterator;->setRestartable(Z)V
-Lorg/apache/xml/dtm/DTMAxisIterator;->setStartNode(I)Lorg/apache/xml/dtm/DTMAxisIterator;
-Lorg/apache/xml/dtm/DTMException;-><init>(Ljava/lang/String;)V
-Lorg/apache/xml/dtm/DTMFilter;->acceptNode(II)S
-Lorg/apache/xml/dtm/DTMIterator;->cloneWithReset()Lorg/apache/xml/dtm/DTMIterator;
-Lorg/apache/xml/dtm/DTMIterator;->getCurrentPos()I
-Lorg/apache/xml/dtm/DTMIterator;->getDTM(I)Lorg/apache/xml/dtm/DTM;
-Lorg/apache/xml/dtm/DTMIterator;->nextNode()I
-Lorg/apache/xml/dtm/DTMIterator;->runTo(I)V
-Lorg/apache/xml/dtm/DTMIterator;->setCurrentPos(I)V
-Lorg/apache/xml/dtm/DTMIterator;->setRoot(ILjava/lang/Object;)V
-Lorg/apache/xml/dtm/DTMIterator;->setShouldCacheNodes(Z)V
-Lorg/apache/xml/dtm/DTMManager;->getDTM(Ljavax/xml/transform/Source;ZLorg/apache/xml/dtm/DTMWSFilter;ZZ)Lorg/apache/xml/dtm/DTM;
-Lorg/apache/xml/dtm/DTMManager;->getXMLStringFactory()Lorg/apache/xml/utils/XMLStringFactory;
-Lorg/apache/xml/dtm/DTMManager;->release(Lorg/apache/xml/dtm/DTM;Z)Z
-Lorg/apache/xml/dtm/ref/CoroutineManager;-><init>()V
-Lorg/apache/xml/dtm/ref/CoroutineManager;->co_joinCoroutineSet(I)I
-Lorg/apache/xml/dtm/ref/DTMAxisIteratorBase;-><init>()V
-Lorg/apache/xml/dtm/ref/DTMAxisIteratorBase;->includeSelf()Lorg/apache/xml/dtm/DTMAxisIterator;
-Lorg/apache/xml/dtm/ref/DTMAxisIteratorBase;->reset()Lorg/apache/xml/dtm/DTMAxisIterator;
-Lorg/apache/xml/dtm/ref/DTMAxisIteratorBase;->resetPosition()Lorg/apache/xml/dtm/DTMAxisIterator;
-Lorg/apache/xml/dtm/ref/DTMAxisIteratorBase;->returnNode(I)I
-Lorg/apache/xml/dtm/ref/DTMAxisIteratorBase;->setRestartable(Z)V
-Lorg/apache/xml/dtm/ref/DTMAxisIteratorBase;->_includeSelf:Z
-Lorg/apache/xml/dtm/ref/DTMAxisIteratorBase;->_isRestartable:Z
-Lorg/apache/xml/dtm/ref/DTMAxisIteratorBase;->_last:I
-Lorg/apache/xml/dtm/ref/DTMAxisIteratorBase;->_markedNode:I
-Lorg/apache/xml/dtm/ref/DTMAxisIteratorBase;->_position:I
-Lorg/apache/xml/dtm/ref/DTMAxisIteratorBase;->_startNode:I
-Lorg/apache/xml/dtm/ref/DTMAxisIterNodeList;-><init>(Lorg/apache/xml/dtm/DTM;Lorg/apache/xml/dtm/DTMAxisIterator;)V
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->appendChild(IZZ)V
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->appendTextChild(Ljava/lang/String;)V
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->declareNamespaceInContext(II)V
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->documentRegistration()V
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->documentRelease()V
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->ensureSizeOfIndex(II)V
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->error(Ljava/lang/String;)V
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->findGTE([IIII)I
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->findInSortedSuballocatedIntVector(Lorg/apache/xml/utils/SuballocatedIntVector;I)I
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->findNamespaceContext(I)Lorg/apache/xml/utils/SuballocatedIntVector;
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->getDocument()I
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->getDocumentAllDeclarationsProcessed()Z
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->getDocumentBaseURI()Ljava/lang/String;
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->getDocumentEncoding(I)Ljava/lang/String;
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->getDocumentRoot(I)I
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->getDocumentStandalone(I)Ljava/lang/String;
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->getDocumentSystemIdentifier(I)Ljava/lang/String;
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->getDocumentVersion(I)Ljava/lang/String;
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->getDTMIDs()Lorg/apache/xml/utils/SuballocatedIntVector;
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->getExpandedTypeID(I)I
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->getExpandedTypeID(Ljava/lang/String;Ljava/lang/String;I)I
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->getFirstChild(I)I
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->getFirstNamespaceNode(IZ)I
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->getLastChild(I)I
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->getLevel(I)S
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->getLocalNameFromExpandedNameID(I)Ljava/lang/String;
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->getManager()Lorg/apache/xml/dtm/DTMManager;
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->getNamespaceFromExpandedNameID(I)Ljava/lang/String;
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->getNamespaceType(I)I
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->getNextAttribute(I)I
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->getNextNamespaceNode(IIZ)I
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->getNextSibling(I)I
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->getNode(I)Lorg/w3c/dom/Node;
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->getNodeHandle(I)I
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->getNodeIdent(I)I
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->getNodeType(I)S
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->getOwnerDocument(I)I
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->getParent(I)I
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->getPreviousSibling(I)I
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->getShouldStripWhitespace()Z
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->getStringValueChunk(II[I)[C
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->getStringValueChunkCount(I)I
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->hasChildNodes(I)Z
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->indexNode(II)V
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->isCharacterElementContentWhitespace(I)Z
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->isDocumentAllDeclarationsProcessed(I)Z
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->isNodeAfter(II)Z
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->isSupported(Ljava/lang/String;Ljava/lang/String;)Z
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->makeNodeHandle(I)I
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->makeNodeIdentity(I)I
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->m_expandedNameTable:Lorg/apache/xml/dtm/ref/ExpandedNameTable;
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->m_exptype:Lorg/apache/xml/utils/SuballocatedIntVector;
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->m_firstch:Lorg/apache/xml/utils/SuballocatedIntVector;
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->m_nextsib:Lorg/apache/xml/utils/SuballocatedIntVector;
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->m_parent:Lorg/apache/xml/utils/SuballocatedIntVector;
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->m_prevsib:Lorg/apache/xml/utils/SuballocatedIntVector;
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->m_size:I
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->m_wsfilter:Lorg/apache/xml/dtm/DTMWSFilter;
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->m_xstrf:Lorg/apache/xml/utils/XMLStringFactory;
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->popShouldStripWhitespace()V
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->pushShouldStripWhitespace(Z)V
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->setDocumentBaseURI(Ljava/lang/String;)V
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->setFeature(Ljava/lang/String;Z)V
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->setShouldStripWhitespace(Z)V
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->supportsPreStripping()Z
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->_exptype(I)I
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->_firstch(I)I
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->_level(I)I
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->_nextsib(I)I
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->_parent(I)I
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->_prevsib(I)I
-Lorg/apache/xml/dtm/ref/DTMDefaultBase;->_type(I)S
-Lorg/apache/xml/dtm/ref/DTMDefaultBaseIterators$InternalAxisIteratorBase;-><init>(Lorg/apache/xml/dtm/ref/DTMDefaultBaseIterators;)V
-Lorg/apache/xml/dtm/ref/DTMDefaultBaseIterators$InternalAxisIteratorBase;->_currentNode:I
-Lorg/apache/xml/dtm/ref/DTMDefaultBaseIterators$NamespaceIterator;-><init>(Lorg/apache/xml/dtm/ref/DTMDefaultBaseIterators;)V
-Lorg/apache/xml/dtm/ref/DTMDefaultBaseIterators$NamespaceIterator;->next()I
-Lorg/apache/xml/dtm/ref/DTMDefaultBaseIterators$NamespaceIterator;->setStartNode(I)Lorg/apache/xml/dtm/DTMAxisIterator;
-Lorg/apache/xml/dtm/ref/DTMDefaultBaseIterators$NthDescendantIterator;-><init>(Lorg/apache/xml/dtm/ref/DTMDefaultBaseIterators;I)V
-Lorg/apache/xml/dtm/ref/DTMDefaultBaseIterators$SingletonIterator;-><init>(Lorg/apache/xml/dtm/ref/DTMDefaultBaseIterators;)V
-Lorg/apache/xml/dtm/ref/DTMDefaultBaseIterators$SingletonIterator;-><init>(Lorg/apache/xml/dtm/ref/DTMDefaultBaseIterators;I)V
-Lorg/apache/xml/dtm/ref/DTMDefaultBaseIterators;-><init>(Lorg/apache/xml/dtm/DTMManager;Ljavax/xml/transform/Source;ILorg/apache/xml/dtm/DTMWSFilter;Lorg/apache/xml/utils/XMLStringFactory;Z)V
-Lorg/apache/xml/dtm/ref/DTMDefaultBaseIterators;->getAxisIterator(I)Lorg/apache/xml/dtm/DTMAxisIterator;
-Lorg/apache/xml/dtm/ref/DTMDefaultBaseIterators;->getTypedAxisIterator(II)Lorg/apache/xml/dtm/DTMAxisIterator;
-Lorg/apache/xml/dtm/ref/DTMDefaultBaseTraversers;->getAxisTraverser(I)Lorg/apache/xml/dtm/DTMAxisTraverser;
-Lorg/apache/xml/dtm/ref/DTMManagerDefault;-><init>()V
-Lorg/apache/xml/dtm/ref/DTMManagerDefault;->addDTM(Lorg/apache/xml/dtm/DTM;I)V
-Lorg/apache/xml/dtm/ref/DTMManagerDefault;->addDTM(Lorg/apache/xml/dtm/DTM;II)V
-Lorg/apache/xml/dtm/ref/DTMManagerDefault;->getFirstFreeDTMID()I
-Lorg/apache/xml/dtm/ref/DTMManagerDefault;->getXMLReader(Ljavax/xml/transform/Source;)Lorg/xml/sax/XMLReader;
-Lorg/apache/xml/dtm/ref/DTMManagerDefault;->releaseXMLReader(Lorg/xml/sax/XMLReader;)V
-Lorg/apache/xml/dtm/ref/DTMNodeIterator;-><init>(Lorg/apache/xml/dtm/DTMIterator;)V
-Lorg/apache/xml/dtm/ref/DTMNodeIterator;->getDTMIterator()Lorg/apache/xml/dtm/DTMIterator;
-Lorg/apache/xml/dtm/ref/DTMNodeIterator;->getRoot()Lorg/w3c/dom/Node;
-Lorg/apache/xml/dtm/ref/DTMNodeList;-><init>(Lorg/apache/xml/dtm/DTMIterator;)V
-Lorg/apache/xml/dtm/ref/DTMNodeProxy;-><init>(Lorg/apache/xml/dtm/DTM;I)V
-Lorg/apache/xml/dtm/ref/DTMNodeProxy;->getDTM()Lorg/apache/xml/dtm/DTM;
-Lorg/apache/xml/dtm/ref/DTMNodeProxy;->getDTMNodeNumber()I
-Lorg/apache/xml/dtm/ref/DTMNodeProxy;->getStringValue()Ljava/lang/String;
-Lorg/apache/xml/dtm/ref/DTMStringPool;-><init>()V
-Lorg/apache/xml/dtm/ref/DTMStringPool;->indexToString(I)Ljava/lang/String;
-Lorg/apache/xml/dtm/ref/DTMStringPool;->m_intToString:Ljava/util/Vector;
-Lorg/apache/xml/dtm/ref/DTMStringPool;->removeAllElements()V
-Lorg/apache/xml/dtm/ref/DTMStringPool;->stringToIndex(Ljava/lang/String;)I
-Lorg/apache/xml/dtm/ref/ExpandedNameTable;->getExpandedTypeID(Ljava/lang/String;Ljava/lang/String;I)I
-Lorg/apache/xml/dtm/ref/ExpandedNameTable;->getExpandedTypeID(Ljava/lang/String;Ljava/lang/String;IZ)I
-Lorg/apache/xml/dtm/ref/ExpandedNameTable;->getLocalName(I)Ljava/lang/String;
-Lorg/apache/xml/dtm/ref/ExpandedNameTable;->getSize()I
-Lorg/apache/xml/dtm/ref/ExpandedNameTable;->getType(I)S
-Lorg/apache/xml/dtm/ref/IncrementalSAXSource;->deliverMoreNodes(Z)Ljava/lang/Object;
-Lorg/apache/xml/dtm/ref/IncrementalSAXSource;->setContentHandler(Lorg/xml/sax/ContentHandler;)V
-Lorg/apache/xml/dtm/ref/IncrementalSAXSource;->setLexicalHandler(Lorg/xml/sax/ext/LexicalHandler;)V
-Lorg/apache/xml/dtm/ref/IncrementalSAXSource;->startParse(Lorg/xml/sax/InputSource;)V
-Lorg/apache/xml/dtm/ref/IncrementalSAXSource_Filter;-><init>()V
-Lorg/apache/xml/dtm/ref/IncrementalSAXSource_Filter;->setXMLReader(Lorg/xml/sax/XMLReader;)V
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2$AncestorIterator;-><init>(Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2;)V
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2$AncestorIterator;->next()I
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2$AncestorIterator;->setStartNode(I)Lorg/apache/xml/dtm/DTMAxisIterator;
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2$AttributeIterator;-><init>(Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2;)V
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2$ChildrenIterator;-><init>(Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2;)V
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2$ChildrenIterator;->setStartNode(I)Lorg/apache/xml/dtm/DTMAxisIterator;
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2$DescendantIterator;-><init>(Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2;)V
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2$FollowingIterator;-><init>(Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2;)V
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2$FollowingSiblingIterator;-><init>(Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2;)V
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2$ParentIterator;-><init>(Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2;)V
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2$ParentIterator;->setNodeType(I)Lorg/apache/xml/dtm/DTMAxisIterator;
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2$PrecedingIterator;-><init>(Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2;)V
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2$PrecedingSiblingIterator;-><init>(Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2;)V
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2$TypedAncestorIterator;-><init>(Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2;I)V
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2$TypedAttributeIterator;-><init>(Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2;I)V
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2$TypedChildrenIterator;-><init>(Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2;I)V
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2$TypedDescendantIterator;-><init>(Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2;I)V
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2$TypedFollowingIterator;-><init>(Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2;I)V
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2$TypedFollowingSiblingIterator;-><init>(Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2;I)V
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2$TypedPrecedingIterator;-><init>(Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2;I)V
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2$TypedPrecedingSiblingIterator;-><init>(Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2;I)V
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2$TypedSingletonIterator;-><init>(Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2;I)V
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2;-><init>(Lorg/apache/xml/dtm/DTMManager;Ljavax/xml/transform/Source;ILorg/apache/xml/dtm/DTMWSFilter;Lorg/apache/xml/utils/XMLStringFactory;ZIZZZ)V
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2;->copyAttribute(IILorg/apache/xml/serializer/SerializationHandler;)V
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2;->copyAttributes(ILorg/apache/xml/serializer/SerializationHandler;)V
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2;->copyElement(IILorg/apache/xml/serializer/SerializationHandler;)Ljava/lang/String;
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2;->copyNS(ILorg/apache/xml/serializer/SerializationHandler;Z)V
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2;->copyTextNode(ILorg/apache/xml/serializer/SerializationHandler;)V
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2;->dispatchCharactersEvents(ILorg/xml/sax/ContentHandler;Z)V
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2;->getFirstAttribute(I)I
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2;->getIdForNamespace(Ljava/lang/String;)I
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2;->getLocalName(I)Ljava/lang/String;
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2;->getNodeName(I)Ljava/lang/String;
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2;->getNodeNameX(I)Ljava/lang/String;
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2;->getNodeValue(I)Ljava/lang/String;
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2;->getStringValue()Ljava/lang/String;
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2;->getStringValue(I)Lorg/apache/xml/utils/XMLString;
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2;->getStringValueX(I)Ljava/lang/String;
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2;->m_buildIdIndex:Z
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2;->_exptype2(I)I
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2;->_exptype2Type(I)I
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2;->_firstch2(I)I
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM2;->_nextsib2(I)I
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM;->dispatchToEvents(ILorg/xml/sax/ContentHandler;)V
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM;->getAttributeNode(ILjava/lang/String;Ljava/lang/String;)I
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM;->getContentHandler()Lorg/xml/sax/ContentHandler;
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM;->getDeclHandler()Lorg/xml/sax/ext/DeclHandler;
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM;->getDocumentTypeDeclarationPublicIdentifier()Ljava/lang/String;
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM;->getDocumentTypeDeclarationSystemIdentifier()Ljava/lang/String;
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM;->getDTDHandler()Lorg/xml/sax/DTDHandler;
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM;->getEntityResolver()Lorg/xml/sax/EntityResolver;
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM;->getErrorHandler()Lorg/xml/sax/ErrorHandler;
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM;->getLexicalHandler()Lorg/xml/sax/ext/LexicalHandler;
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM;->getNamespaceURI(I)Ljava/lang/String;
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM;->getNumberOfNodes()I
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM;->getPrefix(I)Ljava/lang/String;
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM;->getSourceLocatorFor(I)Ljavax/xml/transform/SourceLocator;
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM;->getUnparsedEntityURI(Ljava/lang/String;)Ljava/lang/String;
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM;->isAttributeSpecified(I)Z
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM;->migrateTo(Lorg/apache/xml/dtm/DTMManager;)V
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM;->m_idAttributes:Ljava/util/Hashtable;
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM;->m_parents:Lorg/apache/xml/utils/IntStack;
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM;->m_previous:I
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM;->needsTwoThreads()Z
-Lorg/apache/xml/dtm/ref/sax2dtm/SAX2DTM;->setProperty(Ljava/lang/String;Ljava/lang/Object;)V
-Lorg/apache/xml/dtm/ref/SecuritySupport;->getContextClassLoader()Ljava/lang/ClassLoader;
-Lorg/apache/xml/dtm/ref/SecuritySupport;->getFileExists(Ljava/io/File;)Z
-Lorg/apache/xml/dtm/ref/SecuritySupport;->getFileInputStream(Ljava/io/File;)Ljava/io/FileInputStream;
-Lorg/apache/xml/dtm/ref/SecuritySupport;->getInstance()Lorg/apache/xml/dtm/ref/SecuritySupport;
-Lorg/apache/xml/dtm/ref/SecuritySupport;->getLastModified(Ljava/io/File;)J
-Lorg/apache/xml/dtm/ref/SecuritySupport;->getParentClassLoader(Ljava/lang/ClassLoader;)Ljava/lang/ClassLoader;
-Lorg/apache/xml/dtm/ref/SecuritySupport;->getResourceAsStream(Ljava/lang/ClassLoader;Ljava/lang/String;)Ljava/io/InputStream;
-Lorg/apache/xml/dtm/ref/SecuritySupport;->getSystemClassLoader()Ljava/lang/ClassLoader;
-Lorg/apache/xml/dtm/ref/SecuritySupport;->getSystemProperty(Ljava/lang/String;)Ljava/lang/String;
-Lorg/apache/xml/res/XMLErrorResources;-><init>()V
-Lorg/apache/xml/res/XMLMessages;->createXMLMessage(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
-Lorg/apache/xml/serializer/CharInfo$CharKey;-><init>(C)V
-Lorg/apache/xml/serializer/CharInfo;-><init>(Ljava/lang/String;Ljava/lang/String;Z)V
-Lorg/apache/xml/serializer/CharInfo;->get(I)Z
-Lorg/apache/xml/serializer/CharInfo;->getCharInfo(Ljava/lang/String;Ljava/lang/String;)Lorg/apache/xml/serializer/CharInfo;
-Lorg/apache/xml/serializer/CharInfo;->set(I)V
-Lorg/apache/xml/serializer/dom3/LSSerializerImpl;-><init>()V
-Lorg/apache/xml/serializer/DOMSerializer;->serialize(Lorg/w3c/dom/Node;)V
-Lorg/apache/xml/serializer/ElemContext;->m_elementName:Ljava/lang/String;
-Lorg/apache/xml/serializer/ElemContext;->m_elementURI:Ljava/lang/String;
-Lorg/apache/xml/serializer/ElemContext;->m_startTagOpen:Z
-Lorg/apache/xml/serializer/ElemContext;->push(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lorg/apache/xml/serializer/ElemContext;
-Lorg/apache/xml/serializer/ElemDesc;->isAttrFlagSet(Ljava/lang/String;I)Z
-Lorg/apache/xml/serializer/Encodings;->convertMime2JavaEncoding(Ljava/lang/String;)Ljava/lang/String;
-Lorg/apache/xml/serializer/Encodings;->getMimeEncoding(Ljava/lang/String;)Ljava/lang/String;
-Lorg/apache/xml/serializer/Encodings;->getWriter(Ljava/io/OutputStream;Ljava/lang/String;)Ljava/io/Writer;
-Lorg/apache/xml/serializer/NamespaceMappings;-><init>()V
-Lorg/apache/xml/serializer/NamespaceMappings;->generateNextPrefix()Ljava/lang/String;
-Lorg/apache/xml/serializer/NamespaceMappings;->lookupNamespace(Ljava/lang/String;)Ljava/lang/String;
-Lorg/apache/xml/serializer/NamespaceMappings;->lookupPrefix(Ljava/lang/String;)Ljava/lang/String;
-Lorg/apache/xml/serializer/OutputPropertiesFactory;->getDefaultMethodProperties(Ljava/lang/String;)Ljava/util/Properties;
-Lorg/apache/xml/serializer/OutputPropertyUtils;->getBooleanProperty(Ljava/lang/String;Ljava/util/Properties;)Z
-Lorg/apache/xml/serializer/OutputPropertyUtils;->getIntProperty(Ljava/lang/String;Ljava/util/Properties;)I
-Lorg/apache/xml/serializer/SerializationHandler;->close()V
-Lorg/apache/xml/serializer/SerializationHandler;->flushPending()V
-Lorg/apache/xml/serializer/SerializationHandler;->setEscaping(Z)Z
-Lorg/apache/xml/serializer/SerializationHandler;->setIndentAmount(I)V
-Lorg/apache/xml/serializer/SerializationHandler;->setNamespaceMappings(Lorg/apache/xml/serializer/NamespaceMappings;)V
-Lorg/apache/xml/serializer/Serializer;->asContentHandler()Lorg/xml/sax/ContentHandler;
-Lorg/apache/xml/serializer/Serializer;->asDOMSerializer()Lorg/apache/xml/serializer/DOMSerializer;
-Lorg/apache/xml/serializer/Serializer;->getOutputFormat()Ljava/util/Properties;
-Lorg/apache/xml/serializer/Serializer;->getOutputStream()Ljava/io/OutputStream;
-Lorg/apache/xml/serializer/Serializer;->getWriter()Ljava/io/Writer;
-Lorg/apache/xml/serializer/Serializer;->reset()Z
-Lorg/apache/xml/serializer/Serializer;->setOutputFormat(Ljava/util/Properties;)V
-Lorg/apache/xml/serializer/Serializer;->setOutputStream(Ljava/io/OutputStream;)V
-Lorg/apache/xml/serializer/Serializer;->setWriter(Ljava/io/Writer;)V
-Lorg/apache/xml/serializer/SerializerBase;->fireCharEvent([CII)V
-Lorg/apache/xml/serializer/SerializerBase;->fireCommentEvent([CII)V
-Lorg/apache/xml/serializer/SerializerBase;->fireEndDoc()V
-Lorg/apache/xml/serializer/SerializerBase;->fireEndElem(Ljava/lang/String;)V
-Lorg/apache/xml/serializer/SerializerBase;->fireEscapingEvent(Ljava/lang/String;Ljava/lang/String;)V
-Lorg/apache/xml/serializer/SerializerBase;->getDoctypePublic()Ljava/lang/String;
-Lorg/apache/xml/serializer/SerializerBase;->getDoctypeSystem()Ljava/lang/String;
-Lorg/apache/xml/serializer/SerializerBase;->getEncoding()Ljava/lang/String;
-Lorg/apache/xml/serializer/SerializerBase;->getPrefixPart(Ljava/lang/String;)Ljava/lang/String;
-Lorg/apache/xml/serializer/SerializerBase;->getVersion()Ljava/lang/String;
-Lorg/apache/xml/serializer/SerializerBase;->m_attributes:Lorg/apache/xml/serializer/AttributesImplSerializer;
-Lorg/apache/xml/serializer/SerializerBase;->m_charsBuff:[C
-Lorg/apache/xml/serializer/SerializerBase;->m_elemContext:Lorg/apache/xml/serializer/ElemContext;
-Lorg/apache/xml/serializer/SerializerBase;->m_needToCallStartDocument:Z
-Lorg/apache/xml/serializer/SerializerBase;->m_tracer:Lorg/apache/xml/serializer/SerializerTrace;
-Lorg/apache/xml/serializer/SerializerBase;->setDoctypePublic(Ljava/lang/String;)V
-Lorg/apache/xml/serializer/SerializerBase;->setDoctypeSystem(Ljava/lang/String;)V
-Lorg/apache/xml/serializer/SerializerBase;->setIndent(Z)V
-Lorg/apache/xml/serializer/SerializerBase;->setMediaType(Ljava/lang/String;)V
-Lorg/apache/xml/serializer/SerializerBase;->setOmitXMLDeclaration(Z)V
-Lorg/apache/xml/serializer/SerializerBase;->setStandalone(Ljava/lang/String;)V
-Lorg/apache/xml/serializer/SerializerBase;->setStandaloneInternal(Ljava/lang/String;)V
-Lorg/apache/xml/serializer/SerializerBase;->setVersion(Ljava/lang/String;)V
-Lorg/apache/xml/serializer/SerializerFactory;->getSerializer(Ljava/util/Properties;)Lorg/apache/xml/serializer/Serializer;
-Lorg/apache/xml/serializer/SerializerTraceWriter;-><init>(Ljava/io/Writer;Lorg/apache/xml/serializer/SerializerTrace;)V
-Lorg/apache/xml/serializer/ToHTMLStream;-><init>()V
-Lorg/apache/xml/serializer/ToHTMLStream;->getElemDesc(Ljava/lang/String;)Lorg/apache/xml/serializer/ElemDesc;
-Lorg/apache/xml/serializer/ToSAXHandler;-><init>(Lorg/xml/sax/ContentHandler;Ljava/lang/String;)V
-Lorg/apache/xml/serializer/ToSAXHandler;-><init>(Lorg/xml/sax/ContentHandler;Lorg/xml/sax/ext/LexicalHandler;Ljava/lang/String;)V
-Lorg/apache/xml/serializer/ToSAXHandler;->m_lexHandler:Lorg/xml/sax/ext/LexicalHandler;
-Lorg/apache/xml/serializer/ToSAXHandler;->m_saxHandler:Lorg/xml/sax/ContentHandler;
-Lorg/apache/xml/serializer/ToSAXHandler;->reset()Z
-Lorg/apache/xml/serializer/ToSAXHandler;->startDocumentInternal()V
-Lorg/apache/xml/serializer/ToSAXHandler;->startElement(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-Lorg/apache/xml/serializer/ToStream;->setCdataSectionElements(Ljava/lang/String;Ljava/util/Properties;)V
-Lorg/apache/xml/serializer/ToStream;->setEncoding(Ljava/lang/String;)V
-Lorg/apache/xml/serializer/ToStream;->setIndentAmount(I)V
-Lorg/apache/xml/serializer/ToTextSAXHandler;-><init>(Lorg/xml/sax/ContentHandler;Ljava/lang/String;)V
-Lorg/apache/xml/serializer/ToTextSAXHandler;-><init>(Lorg/xml/sax/ContentHandler;Lorg/xml/sax/ext/LexicalHandler;Ljava/lang/String;)V
-Lorg/apache/xml/serializer/ToTextStream;-><init>()V
-Lorg/apache/xml/serializer/ToUnknownStream;-><init>()V
-Lorg/apache/xml/serializer/ToXMLSAXHandler;-><init>(Lorg/xml/sax/ContentHandler;Ljava/lang/String;)V
-Lorg/apache/xml/serializer/ToXMLSAXHandler;-><init>(Lorg/xml/sax/ContentHandler;Lorg/xml/sax/ext/LexicalHandler;Ljava/lang/String;)V
-Lorg/apache/xml/serializer/ToXMLStream;-><init>()V
-Lorg/apache/xml/serializer/WriterToASCI;-><init>(Ljava/io/OutputStream;)V
-Lorg/apache/xml/serializer/WriterToUTF8Buffered;-><init>(Ljava/io/OutputStream;)V
-Lorg/apache/xml/utils/DefaultErrorHandler;-><init>()V
-Lorg/apache/xml/utils/DefaultErrorHandler;->printLocation(Ljava/io/PrintWriter;Ljava/lang/Throwable;)V
-Lorg/apache/xml/utils/DOMHelper;->isNodeAfter(Lorg/w3c/dom/Node;Lorg/w3c/dom/Node;)Z
-Lorg/apache/xml/utils/DOMHelper;->isNodeTheSame(Lorg/w3c/dom/Node;Lorg/w3c/dom/Node;)Z
-Lorg/apache/xml/utils/FastStringBuffer;->append(Ljava/lang/String;)V
-Lorg/apache/xml/utils/FastStringBuffer;->getString(II)Ljava/lang/String;
-Lorg/apache/xml/utils/FastStringBuffer;->length()I
-Lorg/apache/xml/utils/IntStack;->peek()I
-Lorg/apache/xml/utils/ObjectVector;->elementAt(I)Ljava/lang/Object;
-Lorg/apache/xml/utils/ObjectVector;->size()I
-Lorg/apache/xml/utils/PrefixResolverDefault;-><init>(Lorg/w3c/dom/Node;)V
-Lorg/apache/xml/utils/PrefixResolverDefault;->getNamespaceForPrefix(Ljava/lang/String;)Ljava/lang/String;
-Lorg/apache/xml/utils/QName;-><init>(Ljava/lang/String;)V
-Lorg/apache/xml/utils/QName;-><init>(Ljava/lang/String;Ljava/lang/String;)V
-Lorg/apache/xml/utils/QName;->getLocalName()Ljava/lang/String;
-Lorg/apache/xml/utils/SAXSourceLocator;-><init>(Lorg/xml/sax/SAXParseException;)V
-Lorg/apache/xml/utils/StringBufferPool;->free(Lorg/apache/xml/utils/FastStringBuffer;)V
-Lorg/apache/xml/utils/StringBufferPool;->get()Lorg/apache/xml/utils/FastStringBuffer;
-Lorg/apache/xml/utils/StringVector;->elementAt(I)Ljava/lang/String;
-Lorg/apache/xml/utils/StringVector;->size()I
-Lorg/apache/xml/utils/StylesheetPIHandler;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-Lorg/apache/xml/utils/StylesheetPIHandler;->getAssociatedStylesheet()Ljavax/xml/transform/Source;
-Lorg/apache/xml/utils/StylesheetPIHandler;->setBaseId(Ljava/lang/String;)V
-Lorg/apache/xml/utils/StylesheetPIHandler;->setURIResolver(Ljavax/xml/transform/URIResolver;)V
-Lorg/apache/xml/utils/SuballocatedIntVector;-><init>(I)V
-Lorg/apache/xml/utils/SuballocatedIntVector;->elementAt(I)I
-Lorg/apache/xml/utils/SuballocatedIntVector;->setElementAt(II)V
-Lorg/apache/xml/utils/SuballocatedIntVector;->size()I
-Lorg/apache/xml/utils/SystemIDResolver;->getAbsoluteURI(Ljava/lang/String;)Ljava/lang/String;
-Lorg/apache/xml/utils/SystemIDResolver;->getAbsoluteURI(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
-Lorg/apache/xml/utils/SystemIDResolver;->getAbsoluteURIFromRelative(Ljava/lang/String;)Ljava/lang/String;
-Lorg/apache/xml/utils/SystemIDResolver;->isAbsoluteURI(Ljava/lang/String;)Z
-Lorg/apache/xml/utils/URI$MalformedURIException;-><init>(Ljava/lang/String;)V
-Lorg/apache/xml/utils/URI;-><init>(Ljava/lang/String;)V
-Lorg/apache/xml/utils/URI;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-Lorg/apache/xml/utils/URI;-><init>(Lorg/apache/xml/utils/URI;)V
-Lorg/apache/xml/utils/URI;-><init>(Lorg/apache/xml/utils/URI;Ljava/lang/String;)V
-Lorg/apache/xml/utils/URI;->getFragment()Ljava/lang/String;
-Lorg/apache/xml/utils/URI;->getHost()Ljava/lang/String;
-Lorg/apache/xml/utils/URI;->getPath()Ljava/lang/String;
-Lorg/apache/xml/utils/URI;->getPort()I
-Lorg/apache/xml/utils/URI;->getQueryString()Ljava/lang/String;
-Lorg/apache/xml/utils/URI;->getScheme()Ljava/lang/String;
-Lorg/apache/xml/utils/URI;->getUserinfo()Ljava/lang/String;
-Lorg/apache/xml/utils/URI;->setFragment(Ljava/lang/String;)V
-Lorg/apache/xml/utils/URI;->setHost(Ljava/lang/String;)V
-Lorg/apache/xml/utils/URI;->setPort(I)V
-Lorg/apache/xml/utils/URI;->setScheme(Ljava/lang/String;)V
-Lorg/apache/xml/utils/URI;->setUserinfo(Ljava/lang/String;)V
-Lorg/apache/xml/utils/WrappedRuntimeException;-><init>(Ljava/lang/Exception;)V
-Lorg/apache/xml/utils/WrappedRuntimeException;->getException()Ljava/lang/Exception;
-Lorg/apache/xml/utils/XML11Char;->isXML11ValidNCName(Ljava/lang/String;)Z
-Lorg/apache/xml/utils/XML11Char;->isXML11ValidQName(Ljava/lang/String;)Z
-Lorg/apache/xml/utils/XMLReaderManager;->getInstance()Lorg/apache/xml/utils/XMLReaderManager;
-Lorg/apache/xml/utils/XMLReaderManager;->getXMLReader()Lorg/xml/sax/XMLReader;
-Lorg/apache/xml/utils/XMLReaderManager;->releaseXMLReader(Lorg/xml/sax/XMLReader;)V
-Lorg/apache/xml/utils/XMLString;->dispatchCharactersEvents(Lorg/xml/sax/ContentHandler;)V
-Lorg/apache/xml/utils/XMLString;->equals(Lorg/apache/xml/utils/XMLString;)Z
-Lorg/apache/xml/utils/XMLString;->fixWhiteSpace(ZZZ)Lorg/apache/xml/utils/XMLString;
-Lorg/apache/xml/utils/XMLStringDefault;-><init>(Ljava/lang/String;)V
-Lorg/apache/xml/utils/XMLStringFactory;-><init>()V
-Lorg/apache/xml/utils/XMLStringFactory;->emptystr()Lorg/apache/xml/utils/XMLString;
-Lorg/apache/xml/utils/XMLStringFactory;->newstr(Ljava/lang/String;)Lorg/apache/xml/utils/XMLString;
-Lorg/apache/xpath/axes/ChildTestIterator;-><init>(Lorg/apache/xml/dtm/DTMAxisTraverser;)V
-Lorg/apache/xpath/axes/DescendantIterator;-><init>()V
-Lorg/apache/xpath/axes/LocPathIterator;->getDTM(I)Lorg/apache/xml/dtm/DTM;
-Lorg/apache/xpath/axes/LocPathIterator;->getPrefixResolver()Lorg/apache/xml/utils/PrefixResolver;
-Lorg/apache/xpath/axes/LocPathIterator;->getXPathContext()Lorg/apache/xpath/XPathContext;
-Lorg/apache/xpath/axes/NodeSequence;->getContainedIter()Lorg/apache/xml/dtm/DTMIterator;
-Lorg/apache/xpath/axes/NodeSequence;->nextNode()I
-Lorg/apache/xpath/axes/OneStepIterator;-><init>(Lorg/apache/xml/dtm/DTMAxisIterator;I)V
-Lorg/apache/xpath/CachedXPathAPI;-><init>()V
-Lorg/apache/xpath/CachedXPathAPI;-><init>(Lorg/apache/xpath/CachedXPathAPI;)V
-Lorg/apache/xpath/CachedXPathAPI;->eval(Lorg/w3c/dom/Node;Ljava/lang/String;)Lorg/apache/xpath/objects/XObject;
-Lorg/apache/xpath/CachedXPathAPI;->getXPathContext()Lorg/apache/xpath/XPathContext;
-Lorg/apache/xpath/CachedXPathAPI;->selectNodeList(Lorg/w3c/dom/Node;Ljava/lang/String;)Lorg/w3c/dom/NodeList;
-Lorg/apache/xpath/CachedXPathAPI;->selectNodeList(Lorg/w3c/dom/Node;Ljava/lang/String;Lorg/w3c/dom/Node;)Lorg/w3c/dom/NodeList;
-Lorg/apache/xpath/CachedXPathAPI;->selectSingleNode(Lorg/w3c/dom/Node;Ljava/lang/String;)Lorg/w3c/dom/Node;
-Lorg/apache/xpath/CachedXPathAPI;->selectSingleNode(Lorg/w3c/dom/Node;Ljava/lang/String;Lorg/w3c/dom/Node;)Lorg/w3c/dom/Node;
-Lorg/apache/xpath/compiler/FunctionTable;-><init>()V
-Lorg/apache/xpath/compiler/FunctionTable;->installFunction(Ljava/lang/String;Ljava/lang/Class;)I
-Lorg/apache/xpath/Expression;->assertion(ZLjava/lang/String;)V
-Lorg/apache/xpath/Expression;->error(Lorg/apache/xpath/XPathContext;Ljava/lang/String;[Ljava/lang/Object;)V
-Lorg/apache/xpath/Expression;->exprGetParent()Lorg/apache/xpath/ExpressionNode;
-Lorg/apache/xpath/ExpressionNode;->exprGetParent()Lorg/apache/xpath/ExpressionNode;
-Lorg/apache/xpath/functions/FuncCurrent;-><init>()V
-Lorg/apache/xpath/functions/FuncExtFunction;->getFunctionName()Ljava/lang/String;
-Lorg/apache/xpath/functions/FuncExtFunction;->getMethodKey()Ljava/lang/Object;
-Lorg/apache/xpath/functions/Function;-><init>()V
-Lorg/apache/xpath/functions/WrongNumberArgsException;-><init>(Ljava/lang/String;)V
-Lorg/apache/xpath/NodeSet;-><init>()V
-Lorg/apache/xpath/NodeSet;-><init>(Lorg/w3c/dom/Node;)V
-Lorg/apache/xpath/NodeSet;-><init>(Lorg/w3c/dom/NodeList;)V
-Lorg/apache/xpath/NodeSet;-><init>(Lorg/w3c/dom/traversal/NodeIterator;)V
-Lorg/apache/xpath/NodeSet;->addElement(Lorg/w3c/dom/Node;)V
-Lorg/apache/xpath/NodeSet;->addNode(Lorg/w3c/dom/Node;)V
-Lorg/apache/xpath/NodeSet;->contains(Lorg/w3c/dom/Node;)Z
-Lorg/apache/xpath/NodeSet;->elementAt(I)Lorg/w3c/dom/Node;
-Lorg/apache/xpath/NodeSet;->setShouldCacheNodes(Z)V
-Lorg/apache/xpath/NodeSetDTM;-><init>(Lorg/w3c/dom/NodeList;Lorg/apache/xpath/XPathContext;)V
-Lorg/apache/xpath/NodeSetDTM;-><init>(Lorg/w3c/dom/traversal/NodeIterator;Lorg/apache/xpath/XPathContext;)V
-Lorg/apache/xpath/NodeSetDTM;->addNode(I)V
-Lorg/apache/xpath/NodeSetDTM;->detach()V
-Lorg/apache/xpath/NodeSetDTM;->getLength()I
-Lorg/apache/xpath/NodeSetDTM;->item(I)I
-Lorg/apache/xpath/objects/XBoolean;-><init>(Z)V
-Lorg/apache/xpath/objects/XBoolean;->bool()Z
-Lorg/apache/xpath/objects/XBoolean;->str()Ljava/lang/String;
-Lorg/apache/xpath/objects/XBooleanStatic;-><init>(Z)V
-Lorg/apache/xpath/objects/XNodeSet;-><init>(ILorg/apache/xml/dtm/DTMManager;)V
-Lorg/apache/xpath/objects/XNodeSet;-><init>(Lorg/apache/xml/dtm/DTMIterator;)V
-Lorg/apache/xpath/objects/XNodeSet;-><init>(Lorg/apache/xml/dtm/DTMManager;)V
-Lorg/apache/xpath/objects/XNodeSet;->iterRaw()Lorg/apache/xml/dtm/DTMIterator;
-Lorg/apache/xpath/objects/XNodeSet;->mutableNodeset()Lorg/apache/xpath/NodeSetDTM;
-Lorg/apache/xpath/objects/XNodeSet;->nodelist()Lorg/w3c/dom/NodeList;
-Lorg/apache/xpath/objects/XNumber;-><init>(D)V
-Lorg/apache/xpath/objects/XNumber;->num()D
-Lorg/apache/xpath/objects/XNumber;->str()Ljava/lang/String;
-Lorg/apache/xpath/objects/XObject;->bool()Z
-Lorg/apache/xpath/objects/XObject;->create(Ljava/lang/Object;)Lorg/apache/xpath/objects/XObject;
-Lorg/apache/xpath/objects/XObject;->getType()I
-Lorg/apache/xpath/objects/XObject;->getTypeString()Ljava/lang/String;
-Lorg/apache/xpath/objects/XObject;->iter()Lorg/apache/xml/dtm/DTMIterator;
-Lorg/apache/xpath/objects/XObject;->nodelist()Lorg/w3c/dom/NodeList;
-Lorg/apache/xpath/objects/XObject;->nodeset()Lorg/w3c/dom/traversal/NodeIterator;
-Lorg/apache/xpath/objects/XObject;->num()D
-Lorg/apache/xpath/objects/XObject;->object()Ljava/lang/Object;
-Lorg/apache/xpath/objects/XObject;->str()Ljava/lang/String;
-Lorg/apache/xpath/objects/XObject;->xstr()Lorg/apache/xml/utils/XMLString;
-Lorg/apache/xpath/objects/XRTreeFrag;-><init>(ILorg/apache/xpath/XPathContext;)V
-Lorg/apache/xpath/objects/XRTreeFrag;->asNodeIterator()Lorg/apache/xml/dtm/DTMIterator;
-Lorg/apache/xpath/objects/XRTreeFrag;->convertToNodeset()Lorg/w3c/dom/NodeList;
-Lorg/apache/xpath/objects/XString;-><init>(Ljava/lang/String;)V
-Lorg/apache/xpath/objects/XString;->num()D
-Lorg/apache/xpath/patterns/NodeTest;->setWhatToShow(I)V
-Lorg/apache/xpath/res/XPATHErrorResources;-><init>()V
-Lorg/apache/xpath/res/XPATHMessages;->createXPATHMessage(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
-Lorg/apache/xpath/XPath;-><init>(Ljava/lang/String;Ljavax/xml/transform/SourceLocator;Lorg/apache/xml/utils/PrefixResolver;I)V
-Lorg/apache/xpath/XPath;-><init>(Ljava/lang/String;Ljavax/xml/transform/SourceLocator;Lorg/apache/xml/utils/PrefixResolver;ILjavax/xml/transform/ErrorListener;)V
-Lorg/apache/xpath/XPath;->execute(Lorg/apache/xpath/XPathContext;ILorg/apache/xml/utils/PrefixResolver;)Lorg/apache/xpath/objects/XObject;
-Lorg/apache/xpath/XPath;->execute(Lorg/apache/xpath/XPathContext;Lorg/w3c/dom/Node;Lorg/apache/xml/utils/PrefixResolver;)Lorg/apache/xpath/objects/XObject;
-Lorg/apache/xpath/XPath;->getPatternString()Ljava/lang/String;
-Lorg/apache/xpath/XPathAPI;->selectNodeList(Lorg/w3c/dom/Node;Ljava/lang/String;)Lorg/w3c/dom/NodeList;
-Lorg/apache/xpath/XPathAPI;->selectNodeList(Lorg/w3c/dom/Node;Ljava/lang/String;Lorg/w3c/dom/Node;)Lorg/w3c/dom/NodeList;
-Lorg/apache/xpath/XPathAPI;->selectSingleNode(Lorg/w3c/dom/Node;Ljava/lang/String;)Lorg/w3c/dom/Node;
-Lorg/apache/xpath/XPathAPI;->selectSingleNode(Lorg/w3c/dom/Node;Ljava/lang/String;Lorg/w3c/dom/Node;)Lorg/w3c/dom/Node;
-Lorg/apache/xpath/XPathContext$XPathExpressionContext;->getDTMManager()Lorg/apache/xml/dtm/DTMManager;
-Lorg/apache/xpath/XPathContext$XPathExpressionContext;->getXPathContext()Lorg/apache/xpath/XPathContext;
-Lorg/apache/xpath/XPathContext;-><init>()V
-Lorg/apache/xpath/XPathContext;-><init>(Ljava/lang/Object;)V
-Lorg/apache/xpath/XPathContext;->getAxesIteratorStackStacks()Ljava/util/Stack;
-Lorg/apache/xpath/XPathContext;->getContextNodeList()Lorg/apache/xml/dtm/DTMIterator;
-Lorg/apache/xpath/XPathContext;->getContextNodeListsStack()Ljava/util/Stack;
-Lorg/apache/xpath/XPathContext;->getCurrentExpressionNodeStack()Lorg/apache/xml/utils/IntStack;
-Lorg/apache/xpath/XPathContext;->getCurrentNode()I
-Lorg/apache/xpath/XPathContext;->getCurrentNodeStack()Lorg/apache/xml/utils/IntStack;
-Lorg/apache/xpath/XPathContext;->getDTM(I)Lorg/apache/xml/dtm/DTM;
-Lorg/apache/xpath/XPathContext;->getDTMHandleFromNode(Lorg/w3c/dom/Node;)I
-Lorg/apache/xpath/XPathContext;->getDTMManager()Lorg/apache/xml/dtm/DTMManager;
-Lorg/apache/xpath/XPathContext;->getExpressionContext()Lorg/apache/xalan/extensions/ExpressionContext;
-Lorg/apache/xpath/XPathContext;->getNamespaceContext()Lorg/apache/xml/utils/PrefixResolver;
-Lorg/apache/xpath/XPathContext;->getOwnerObject()Ljava/lang/Object;
-Lorg/apache/xpath/XPathContext;->getSAXLocator()Ljavax/xml/transform/SourceLocator;
-Lorg/apache/xpath/XPathContext;->getVarStack()Lorg/apache/xpath/VariableStack;
-Lorg/apache/xpath/XPathContext;->m_dtmManager:Lorg/apache/xml/dtm/DTMManager;
-Lorg/apache/xpath/XPathContext;->popContextNodeList()V
-Lorg/apache/xpath/XPathContext;->popCurrentNode()V
-Lorg/apache/xpath/XPathContext;->pushContextNodeList(Lorg/apache/xml/dtm/DTMIterator;)V
-Lorg/apache/xpath/XPathContext;->pushCurrentNode(I)V
-Lorg/apache/xpath/XPathContext;->reset()V
-Lorg/apache/xpath/XPathContext;->setAxesIteratorStackStacks(Ljava/util/Stack;)V
-Lorg/apache/xpath/XPathContext;->setContextNodeListsStack(Ljava/util/Stack;)V
-Lorg/apache/xpath/XPathContext;->setCurrentExpressionNodeStack(Lorg/apache/xml/utils/IntStack;)V
-Lorg/apache/xpath/XPathContext;->setCurrentNodeStack(Lorg/apache/xml/utils/IntStack;)V
-Lorg/apache/xpath/XPathContext;->setSecureProcessing(Z)V
-Lorg/apache/xpath/XPathContext;->setVarStack(Lorg/apache/xpath/VariableStack;)V
diff --git a/core/java/android/bluetooth/BluetoothProfileConnector.java b/core/java/android/bluetooth/BluetoothProfileConnector.java
index d9987249..863fd36 100644
--- a/core/java/android/bluetooth/BluetoothProfileConnector.java
+++ b/core/java/android/bluetooth/BluetoothProfileConnector.java
@@ -32,12 +32,12 @@
  * @hide
  */
 public abstract class BluetoothProfileConnector<T> {
-    private int mProfileId;
+    private final int mProfileId;
     private BluetoothProfile.ServiceListener mServiceListener;
-    private BluetoothProfile mProfileProxy;
+    private final BluetoothProfile mProfileProxy;
     private Context mContext;
-    private String mProfileName;
-    private String mServiceName;
+    private final String mProfileName;
+    private final String mServiceName;
     private volatile T mService;
 
     private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
@@ -65,7 +65,7 @@
             logDebug("Proxy object disconnected");
             doUnbind();
             if (mServiceListener != null) {
-                mServiceListener.onServiceDisconnected(BluetoothProfile.A2DP);
+                mServiceListener.onServiceDisconnected(mProfileId);
             }
         }
     };
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b09eada..cb939f0 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -807,7 +807,7 @@
      *
      * @hide
      */
-    public static final int INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS = 0x00000200;
+    public static final int INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS = 0x00400000;
 
     /** {@hide} */
     public static final int INSTALL_FORCE_VOLUME_UUID = 0x00000200;
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index 41be38a..58aacc2 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -153,14 +153,21 @@
     public static final int CLONE_REMOVE_RES_NAMES = 1 << 3;
 
     /** @hide */
+    public static final int CLONE_REMOVE_PERSON = 1 << 4;
+
+    /** @hide */
     public static final int CLONE_REMOVE_FOR_CREATOR = CLONE_REMOVE_ICON | CLONE_REMOVE_RES_NAMES;
 
     /** @hide */
     public static final int CLONE_REMOVE_FOR_LAUNCHER = CLONE_REMOVE_ICON | CLONE_REMOVE_INTENT
-            | CLONE_REMOVE_RES_NAMES;
+            | CLONE_REMOVE_RES_NAMES | CLONE_REMOVE_PERSON;
 
     /** @hide */
     public static final int CLONE_REMOVE_FOR_LAUNCHER_APPROVAL = CLONE_REMOVE_INTENT
+            | CLONE_REMOVE_RES_NAMES | CLONE_REMOVE_PERSON;
+
+    /** @hide */
+    public static final int CLONE_REMOVE_FOR_APP_PREDICTION = CLONE_REMOVE_ICON
             | CLONE_REMOVE_RES_NAMES;
 
     /** @hide */
@@ -169,8 +176,11 @@
             CLONE_REMOVE_INTENT,
             CLONE_REMOVE_NON_KEY_INFO,
             CLONE_REMOVE_RES_NAMES,
+            CLONE_REMOVE_PERSON,
             CLONE_REMOVE_FOR_CREATOR,
-            CLONE_REMOVE_FOR_LAUNCHER
+            CLONE_REMOVE_FOR_LAUNCHER,
+            CLONE_REMOVE_FOR_LAUNCHER_APPROVAL,
+            CLONE_REMOVE_FOR_APP_PREDICTION
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CloneFlags {}
@@ -548,7 +558,9 @@
             mDisabledMessage = source.mDisabledMessage;
             mDisabledMessageResId = source.mDisabledMessageResId;
             mCategories = cloneCategories(source.mCategories);
-            mPersons = clonePersons(source.mPersons);
+            if ((cloneFlags & CLONE_REMOVE_PERSON) == 0) {
+                mPersons = clonePersons(source.mPersons);
+            }
             if ((cloneFlags & CLONE_REMOVE_INTENT) == 0) {
                 mIntents = cloneIntents(source.mIntents);
                 mIntentPersistableExtrases =
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index e892b65..bb344e2 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -18,6 +18,7 @@
 
 import static android.os.Process.CLAT_UID;
 
+import android.annotation.NonNull;
 import android.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -33,6 +34,7 @@
 import java.io.CharArrayWriter;
 import java.io.PrintWriter;
 import java.util.Arrays;
+import java.util.function.Predicate;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Objects;
@@ -993,23 +995,33 @@
         if (limitUid == UID_ALL && limitTag == TAG_ALL && limitIfaces == INTERFACES_ALL) {
             return;
         }
+        filter(e -> (limitUid == UID_ALL || limitUid == e.uid)
+            && (limitTag == TAG_ALL || limitTag == e.tag)
+            && (limitIfaces == INTERFACES_ALL
+                    || ArrayUtils.contains(limitIfaces, e.iface)));
+    }
 
+    /**
+     * Only keep entries with {@link #set} value less than {@link #SET_DEBUG_START}.
+     *
+     * <p>This mutates the original structure in place.
+     */
+    public void filterDebugEntries() {
+        filter(e -> e.set < SET_DEBUG_START);
+    }
+
+    private void filter(Predicate<Entry> predicate) {
         Entry entry = new Entry();
         int nextOutputEntry = 0;
         for (int i = 0; i < size; i++) {
             entry = getValues(i, entry);
-            final boolean matches =
-                    (limitUid == UID_ALL || limitUid == entry.uid)
-                    && (limitTag == TAG_ALL || limitTag == entry.tag)
-                    && (limitIfaces == INTERFACES_ALL
-                            || ArrayUtils.contains(limitIfaces, entry.iface));
-
-            if (matches) {
-                setValues(nextOutputEntry, entry);
+            if (predicate.test(entry)) {
+                if (nextOutputEntry != i) {
+                    setValues(nextOutputEntry, entry);
+                }
                 nextOutputEntry++;
             }
         }
-
         size = nextOutputEntry;
     }
 
@@ -1175,133 +1187,221 @@
     /**
      * VPN accounting. Move some VPN's underlying traffic to other UIDs that use tun0 iface.
      *
-     * This method should only be called on delta NetworkStats. Do not call this method on a
-     * snapshot {@link NetworkStats} object because the tunUid and/or the underlyingIface may
-     * change over time.
+     * <p>This method should only be called on delta NetworkStats. Do not call this method on a
+     * snapshot {@link NetworkStats} object because the tunUid and/or the underlyingIface may change
+     * over time.
      *
-     * This method performs adjustments for one active VPN package and one VPN iface at a time.
-     *
-     * It is possible for the VPN software to use multiple underlying networks. This method
-     * only migrates traffic for the primary underlying network.
+     * <p>This method performs adjustments for one active VPN package and one VPN iface at a time.
      *
      * @param tunUid uid of the VPN application
      * @param tunIface iface of the vpn tunnel
-     * @param underlyingIface the primary underlying network iface used by the VPN application
-     * @return true if it successfully adjusts the accounting for VPN, false otherwise
+     * @param underlyingIfaces underlying network ifaces used by the VPN application
      */
-    public boolean migrateTun(int tunUid, String tunIface, String underlyingIface) {
-        Entry tunIfaceTotal = new Entry();
-        Entry underlyingIfaceTotal = new Entry();
+    public void migrateTun(int tunUid, @NonNull String tunIface,
+            @NonNull String[] underlyingIfaces) {
+        // Combined usage by all apps using VPN.
+        final Entry tunIfaceTotal = new Entry();
+        // Usage by VPN, grouped by its {@code underlyingIfaces}.
+        final Entry[] perInterfaceTotal = new Entry[underlyingIfaces.length];
+        // Usage by VPN, summed across all its {@code underlyingIfaces}.
+        final Entry underlyingIfacesTotal = new Entry();
 
-        tunAdjustmentInit(tunUid, tunIface, underlyingIface, tunIfaceTotal, underlyingIfaceTotal);
+        for (int i = 0; i < perInterfaceTotal.length; i++) {
+            perInterfaceTotal[i] = new Entry();
+        }
 
-        // If tunIface < underlyingIface, it leaves the overhead traffic in the VPN app.
-        // If tunIface > underlyingIface, the VPN app doesn't get credit for data compression.
+        tunAdjustmentInit(tunUid, tunIface, underlyingIfaces, tunIfaceTotal, perInterfaceTotal,
+                underlyingIfacesTotal);
+
+        // If tunIface < underlyingIfacesTotal, it leaves the overhead traffic in the VPN app.
+        // If tunIface > underlyingIfacesTotal, the VPN app doesn't get credit for data compression.
         // Negative stats should be avoided.
-        Entry pool = tunGetPool(tunIfaceTotal, underlyingIfaceTotal);
-        if (pool.isEmpty()) {
-            return true;
-        }
-        Entry moved =
-                addTrafficToApplications(tunUid, tunIface, underlyingIface, tunIfaceTotal, pool);
-        deductTrafficFromVpnApp(tunUid, underlyingIface, moved);
-
-        if (!moved.isEmpty()) {
-            Slog.wtf(TAG, "Failed to deduct underlying network traffic from VPN package. Moved="
-                    + moved);
-            return false;
-        }
-        return true;
+        final Entry[] moved =
+                addTrafficToApplications(tunUid, tunIface, underlyingIfaces, tunIfaceTotal,
+                        perInterfaceTotal, underlyingIfacesTotal);
+        deductTrafficFromVpnApp(tunUid, underlyingIfaces, moved);
     }
 
     /**
      * Initializes the data used by the migrateTun() method.
      *
-     * This is the first pass iteration which does the following work:
-     * (1) Adds up all the traffic through the tunUid's underlyingIface
-     *     (both foreground and background).
-     * (2) Adds up all the traffic through tun0 excluding traffic from the vpn app itself.
+     * <p>This is the first pass iteration which does the following work:
+     *
+     * <ul>
+     *   <li>Adds up all the traffic through the tunUid's underlyingIfaces (both foreground and
+     *       background).
+     *   <li>Adds up all the traffic through tun0 excluding traffic from the vpn app itself.
+     * </ul>
+     *
+     * @param tunUid uid of the VPN application
+     * @param tunIface iface of the vpn tunnel
+     * @param underlyingIfaces underlying network ifaces used by the VPN application
+     * @param tunIfaceTotal output parameter; combined data usage by all apps using VPN
+     * @param perInterfaceTotal output parameter; data usage by VPN app, grouped by its {@code
+     *     underlyingIfaces}
+     * @param underlyingIfacesTotal output parameter; data usage by VPN, summed across all of its
+     *     {@code underlyingIfaces}
      */
-    private void tunAdjustmentInit(int tunUid, String tunIface, String underlyingIface,
-            Entry tunIfaceTotal, Entry underlyingIfaceTotal) {
-        Entry recycle = new Entry();
+    private void tunAdjustmentInit(int tunUid, @NonNull String tunIface,
+            @NonNull String[] underlyingIfaces, @NonNull Entry tunIfaceTotal,
+            @NonNull Entry[] perInterfaceTotal, @NonNull Entry underlyingIfacesTotal) {
+        final Entry recycle = new Entry();
         for (int i = 0; i < size; i++) {
             getValues(i, recycle);
             if (recycle.uid == UID_ALL) {
                 throw new IllegalStateException(
                         "Cannot adjust VPN accounting on an iface aggregated NetworkStats.");
-            } if (recycle.set == SET_DBG_VPN_IN || recycle.set == SET_DBG_VPN_OUT) {
+            }
+            if (recycle.set == SET_DBG_VPN_IN || recycle.set == SET_DBG_VPN_OUT) {
                 throw new IllegalStateException(
                         "Cannot adjust VPN accounting on a NetworkStats containing SET_DBG_VPN_*");
             }
-
-            if (recycle.uid == tunUid && recycle.tag == TAG_NONE
-                    && Objects.equals(underlyingIface, recycle.iface)) {
-                underlyingIfaceTotal.add(recycle);
+            if (recycle.tag != TAG_NONE) {
+                // TODO(b/123666283): Take all tags for tunUid into account.
+                continue;
             }
 
-            if (recycle.uid != tunUid && recycle.tag == TAG_NONE
-                    && Objects.equals(tunIface, recycle.iface)) {
+            if (recycle.uid == tunUid) {
+                // Add up traffic through tunUid's underlying interfaces.
+                for (int j = 0; j < underlyingIfaces.length; j++) {
+                    if (Objects.equals(underlyingIfaces[j], recycle.iface)) {
+                        perInterfaceTotal[j].add(recycle);
+                        underlyingIfacesTotal.add(recycle);
+                        break;
+                    }
+                }
+            } else if (tunIface.equals(recycle.iface)) {
                 // Add up all tunIface traffic excluding traffic from the vpn app itself.
                 tunIfaceTotal.add(recycle);
             }
         }
     }
 
-    private static Entry tunGetPool(Entry tunIfaceTotal, Entry underlyingIfaceTotal) {
-        Entry pool = new Entry();
-        pool.rxBytes = Math.min(tunIfaceTotal.rxBytes, underlyingIfaceTotal.rxBytes);
-        pool.rxPackets = Math.min(tunIfaceTotal.rxPackets, underlyingIfaceTotal.rxPackets);
-        pool.txBytes = Math.min(tunIfaceTotal.txBytes, underlyingIfaceTotal.txBytes);
-        pool.txPackets = Math.min(tunIfaceTotal.txPackets, underlyingIfaceTotal.txPackets);
-        pool.operations = Math.min(tunIfaceTotal.operations, underlyingIfaceTotal.operations);
-        return pool;
-    }
+    /**
+     * Distributes traffic across apps that are using given {@code tunIface}, and returns the total
+     * traffic that should be moved off of {@code tunUid} grouped by {@code underlyingIfaces}.
+     *
+     * @param tunUid uid of the VPN application
+     * @param tunIface iface of the vpn tunnel
+     * @param underlyingIfaces underlying network ifaces used by the VPN application
+     * @param tunIfaceTotal combined data usage across all apps using {@code tunIface}
+     * @param perInterfaceTotal data usage by VPN app, grouped by its {@code underlyingIfaces}
+     * @param underlyingIfacesTotal data usage by VPN, summed across all of its {@code
+     *     underlyingIfaces}
+     */
+    private Entry[] addTrafficToApplications(int tunUid, @NonNull String tunIface,
+            @NonNull String[] underlyingIfaces, @NonNull Entry tunIfaceTotal,
+            @NonNull Entry[] perInterfaceTotal, @NonNull Entry underlyingIfacesTotal) {
+        // Traffic that should be moved off of each underlying interface for tunUid (see
+        // deductTrafficFromVpnApp below).
+        final Entry[] moved = new Entry[underlyingIfaces.length];
+        for (int i = 0; i < underlyingIfaces.length; i++) {
+            moved[i] = new Entry();
+        }
 
-    private Entry addTrafficToApplications(int tunUid, String tunIface, String underlyingIface,
-            Entry tunIfaceTotal, Entry pool) {
-        Entry moved = new Entry();
-        Entry tmpEntry = new Entry();
-        tmpEntry.iface = underlyingIface;
-        for (int i = 0; i < size; i++) {
-            // the vpn app is excluded from the redistribution but all moved traffic will be
-            // deducted from the vpn app (see deductTrafficFromVpnApp below).
-            if (Objects.equals(iface[i], tunIface) && uid[i] != tunUid) {
-                if (tunIfaceTotal.rxBytes > 0) {
-                    tmpEntry.rxBytes = pool.rxBytes * rxBytes[i] / tunIfaceTotal.rxBytes;
-                } else {
-                    tmpEntry.rxBytes = 0;
-                }
-                if (tunIfaceTotal.rxPackets > 0) {
-                    tmpEntry.rxPackets = pool.rxPackets * rxPackets[i] / tunIfaceTotal.rxPackets;
-                } else {
-                    tmpEntry.rxPackets = 0;
-                }
-                if (tunIfaceTotal.txBytes > 0) {
-                    tmpEntry.txBytes = pool.txBytes * txBytes[i] / tunIfaceTotal.txBytes;
-                } else {
-                    tmpEntry.txBytes = 0;
-                }
-                if (tunIfaceTotal.txPackets > 0) {
-                    tmpEntry.txPackets = pool.txPackets * txPackets[i] / tunIfaceTotal.txPackets;
-                } else {
-                    tmpEntry.txPackets = 0;
-                }
-                if (tunIfaceTotal.operations > 0) {
-                    tmpEntry.operations =
-                            pool.operations * operations[i] / tunIfaceTotal.operations;
-                } else {
-                    tmpEntry.operations = 0;
-                }
-                tmpEntry.uid = uid[i];
-                tmpEntry.tag = tag[i];
+        final Entry tmpEntry = new Entry();
+        final int origSize = size;
+        for (int i = 0; i < origSize; i++) {
+            if (!Objects.equals(iface[i], tunIface)) {
+                // Consider only entries that go onto the VPN interface.
+                continue;
+            }
+            if (uid[i] == tunUid) {
+                // Exclude VPN app from the redistribution, as it can choose to create packet
+                // streams by writing to itself.
+                continue;
+            }
+            tmpEntry.uid = uid[i];
+            tmpEntry.tag = tag[i];
+            tmpEntry.metered = metered[i];
+            tmpEntry.roaming = roaming[i];
+            tmpEntry.defaultNetwork = defaultNetwork[i];
+
+            // In a first pass, compute this entry's total share of data across all
+            // underlyingIfaces. This is computed on the basis of the share of this entry's usage
+            // over tunIface.
+            // TODO: Consider refactoring first pass into a separate helper method.
+            long totalRxBytes = 0;
+            if (tunIfaceTotal.rxBytes > 0) {
+                // Note - The multiplication below should not overflow since NetworkStatsService
+                // processes this every time device has transmitted/received amount equivalent to
+                // global threshold alert (~ 2MB) across all interfaces.
+                final long rxBytesAcrossUnderlyingIfaces =
+                        underlyingIfacesTotal.rxBytes * rxBytes[i] / tunIfaceTotal.rxBytes;
+                // app must not be blamed for more than it consumed on tunIface
+                totalRxBytes = Math.min(rxBytes[i], rxBytesAcrossUnderlyingIfaces);
+            }
+            long totalRxPackets = 0;
+            if (tunIfaceTotal.rxPackets > 0) {
+                final long rxPacketsAcrossUnderlyingIfaces =
+                        underlyingIfacesTotal.rxPackets * rxPackets[i] / tunIfaceTotal.rxPackets;
+                totalRxPackets = Math.min(rxPackets[i], rxPacketsAcrossUnderlyingIfaces);
+            }
+            long totalTxBytes = 0;
+            if (tunIfaceTotal.txBytes > 0) {
+                final long txBytesAcrossUnderlyingIfaces =
+                        underlyingIfacesTotal.txBytes * txBytes[i] / tunIfaceTotal.txBytes;
+                totalTxBytes = Math.min(txBytes[i], txBytesAcrossUnderlyingIfaces);
+            }
+            long totalTxPackets = 0;
+            if (tunIfaceTotal.txPackets > 0) {
+                final long txPacketsAcrossUnderlyingIfaces =
+                        underlyingIfacesTotal.txPackets * txPackets[i] / tunIfaceTotal.txPackets;
+                totalTxPackets = Math.min(txPackets[i], txPacketsAcrossUnderlyingIfaces);
+            }
+            long totalOperations = 0;
+            if (tunIfaceTotal.operations > 0) {
+                final long operationsAcrossUnderlyingIfaces =
+                        underlyingIfacesTotal.operations * operations[i] / tunIfaceTotal.operations;
+                totalOperations = Math.min(operations[i], operationsAcrossUnderlyingIfaces);
+            }
+            // In a second pass, distribute these values across interfaces in the proportion that
+            // each interface represents of the total traffic of the underlying interfaces.
+            for (int j = 0; j < underlyingIfaces.length; j++) {
+                tmpEntry.iface = underlyingIfaces[j];
+                tmpEntry.rxBytes = 0;
+                // Reset 'set' to correct value since it gets updated when adding debug info below.
                 tmpEntry.set = set[i];
-                tmpEntry.metered = metered[i];
-                tmpEntry.roaming = roaming[i];
-                tmpEntry.defaultNetwork = defaultNetwork[i];
+                if (underlyingIfacesTotal.rxBytes > 0) {
+                    tmpEntry.rxBytes =
+                            totalRxBytes
+                                    * perInterfaceTotal[j].rxBytes
+                                    / underlyingIfacesTotal.rxBytes;
+                }
+                tmpEntry.rxPackets = 0;
+                if (underlyingIfacesTotal.rxPackets > 0) {
+                    tmpEntry.rxPackets =
+                            totalRxPackets
+                                    * perInterfaceTotal[j].rxPackets
+                                    / underlyingIfacesTotal.rxPackets;
+                }
+                tmpEntry.txBytes = 0;
+                if (underlyingIfacesTotal.txBytes > 0) {
+                    tmpEntry.txBytes =
+                            totalTxBytes
+                                    * perInterfaceTotal[j].txBytes
+                                    / underlyingIfacesTotal.txBytes;
+                }
+                tmpEntry.txPackets = 0;
+                if (underlyingIfacesTotal.txPackets > 0) {
+                    tmpEntry.txPackets =
+                            totalTxPackets
+                                    * perInterfaceTotal[j].txPackets
+                                    / underlyingIfacesTotal.txPackets;
+                }
+                tmpEntry.operations = 0;
+                if (underlyingIfacesTotal.operations > 0) {
+                    tmpEntry.operations =
+                            totalOperations
+                                    * perInterfaceTotal[j].operations
+                                    / underlyingIfacesTotal.operations;
+                }
+                // tmpEntry now contains the migrated data of the i-th entry for the j-th underlying
+                // interface. Add that data usage to this object.
                 combineValues(tmpEntry);
                 if (tag[i] == TAG_NONE) {
-                    moved.add(tmpEntry);
+                    // Add the migrated data to moved so it is deducted from the VPN app later.
+                    moved[j].add(tmpEntry);
                     // Add debug info
                     tmpEntry.set = SET_DBG_VPN_IN;
                     combineValues(tmpEntry);
@@ -1311,38 +1411,45 @@
         return moved;
     }
 
-    private void deductTrafficFromVpnApp(int tunUid, String underlyingIface, Entry moved) {
-        // Add debug info
-        moved.uid = tunUid;
-        moved.set = SET_DBG_VPN_OUT;
-        moved.tag = TAG_NONE;
-        moved.iface = underlyingIface;
-        moved.metered = METERED_ALL;
-        moved.roaming = ROAMING_ALL;
-        moved.defaultNetwork = DEFAULT_NETWORK_ALL;
-        combineValues(moved);
+    private void deductTrafficFromVpnApp(
+            int tunUid,
+            @NonNull String[] underlyingIfaces,
+            @NonNull Entry[] moved) {
+        for (int i = 0; i < underlyingIfaces.length; i++) {
+            moved[i].uid = tunUid;
+            // Add debug info
+            moved[i].set = SET_DBG_VPN_OUT;
+            moved[i].tag = TAG_NONE;
+            moved[i].iface = underlyingIfaces[i];
+            moved[i].metered = METERED_ALL;
+            moved[i].roaming = ROAMING_ALL;
+            moved[i].defaultNetwork = DEFAULT_NETWORK_ALL;
+            combineValues(moved[i]);
 
-        // Caveat: if the vpn software uses tag, the total tagged traffic may be greater than
-        // the TAG_NONE traffic.
-        //
-        // Relies on the fact that the underlying traffic only has state ROAMING_NO and METERED_NO,
-        // which should be the case as it comes directly from the /proc file. We only blend in the
-        // roaming data after applying these adjustments, by checking the NetworkIdentity of the
-        // underlying iface.
-        int idxVpnBackground = findIndex(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE,
-                METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO);
-        if (idxVpnBackground != -1) {
-            tunSubtract(idxVpnBackground, this, moved);
-        }
+            // Caveat: if the vpn software uses tag, the total tagged traffic may be greater than
+            // the TAG_NONE traffic.
+            //
+            // Relies on the fact that the underlying traffic only has state ROAMING_NO and
+            // METERED_NO, which should be the case as it comes directly from the /proc file.
+            // We only blend in the roaming data after applying these adjustments, by checking the
+            // NetworkIdentity of the underlying iface.
+            final int idxVpnBackground = findIndex(underlyingIfaces[i], tunUid, SET_DEFAULT,
+                            TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO);
+            if (idxVpnBackground != -1) {
+                // Note - tunSubtract also updates moved[i]; whatever traffic that's left is removed
+                // from foreground usage.
+                tunSubtract(idxVpnBackground, this, moved[i]);
+            }
 
-        int idxVpnForeground = findIndex(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE,
-                METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO);
-        if (idxVpnForeground != -1) {
-            tunSubtract(idxVpnForeground, this, moved);
+            final int idxVpnForeground = findIndex(underlyingIfaces[i], tunUid, SET_FOREGROUND,
+                            TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO);
+            if (idxVpnForeground != -1) {
+                tunSubtract(idxVpnForeground, this, moved[i]);
+            }
         }
     }
 
-    private static void tunSubtract(int i, NetworkStats left, Entry right) {
+    private static void tunSubtract(int i, @NonNull NetworkStats left, @NonNull Entry right) {
         long rxBytes = Math.min(left.rxBytes[i], right.rxBytes);
         left.rxBytes[i] -= rxBytes;
         right.rxBytes -= rxBytes;
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index 5039b31..24a1477 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -231,13 +231,6 @@
     }
 
     /**
-     * Check whether application is profileable
-     */
-    private static boolean isProfileable(Context context) {
-        return context.getApplicationInfo().isProfileableByShell();
-    }
-
-    /**
      * Store the layer paths available to the loader.
      */
     public void setLayerPaths(ClassLoader classLoader,
@@ -287,11 +280,11 @@
         String layerPaths = "";
 
         // Only enable additional debug functionality if the following conditions are met:
-        // 1. App is debuggable, profileable, or device is rooted
+        // 1. App is debuggable or device is rooted
         // 2. ENABLE_GPU_DEBUG_LAYERS is true
         // 3. Package name is equal to GPU_DEBUG_APP
 
-        if (isDebuggable(context) || isProfileable(context) || (getCanLoadSystemLibraries() == 1)) {
+        if (isDebuggable(context) || (getCanLoadSystemLibraries() == 1)) {
 
             final int enable = coreSettings.getInt(Settings.Global.ENABLE_GPU_DEBUG_LAYERS, 0);
 
@@ -473,9 +466,8 @@
      */
     private String getAngleDebugPackage(Context context, Bundle coreSettings) {
         final boolean appIsDebuggable = isDebuggable(context);
-        final boolean appIsProfileable = isProfileable(context);
         final boolean deviceIsDebuggable = getCanLoadSystemLibraries() == 1;
-        if (appIsDebuggable || appIsProfileable || deviceIsDebuggable) {
+        if (appIsDebuggable || deviceIsDebuggable) {
             String debugPackage;
 
             if (coreSettings != null) {
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index de963c9..fe2e948 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -2001,6 +2002,7 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @TestApi
     public final int readExceptionCode() {
         int code = readInt();
         if (code == EX_HAS_REPLY_HEADER) {
diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java
index edfdda8..4538410 100644
--- a/core/java/android/os/SystemProperties.java
+++ b/core/java/android/os/SystemProperties.java
@@ -174,6 +174,7 @@
      * @hide
      */
     @SystemApi
+    @TestApi
     public static boolean getBoolean(@NonNull String key, boolean def) {
         if (TRACK_KEY_ACCESS) onKeyAccess(key);
         return native_get_boolean(key, def);
diff --git a/core/java/android/os/image/DynamicSystemClient.java b/core/java/android/os/image/DynamicSystemClient.java
index f1f24fb..921f0f2 100644
--- a/core/java/android/os/image/DynamicSystemClient.java
+++ b/core/java/android/os/image/DynamicSystemClient.java
@@ -22,6 +22,7 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -67,6 +68,7 @@
  * @hide
  */
 @SystemApi
+@TestApi
 public class DynamicSystemClient {
     /** @hide */
     @IntDef(prefix = { "STATUS_" }, value = {
@@ -283,6 +285,7 @@
      * @hide
      */
     @SystemApi
+    @TestApi
     public DynamicSystemClient(@NonNull Context context) {
         mContext = context;
         mConnection = new DynSystemServiceConnection();
@@ -314,8 +317,11 @@
      * Bind to {@code DynamicSystem} installation service. Binding to the installation service
      * allows it to send status updates to {@link #OnStatusChangedListener}. It is recommanded
      * to bind before calling {@link #start} and get status updates.
+     * @hide
      */
     @RequiresPermission(android.Manifest.permission.INSTALL_DYNAMIC_SYSTEM)
+    @SystemApi
+    @TestApi
     public void bind() {
         if (!featureFlagEnabled()) {
             Slog.w(TAG, FeatureFlagUtils.DYNAMIC_SYSTEM + " not enabled; bind() aborted.");
@@ -334,8 +340,11 @@
     /**
      * Unbind from {@code DynamicSystem} installation service. Unbinding from the installation
      * service stops it from sending following status updates.
+     * @hide
      */
     @RequiresPermission(android.Manifest.permission.INSTALL_DYNAMIC_SYSTEM)
+    @SystemApi
+    @TestApi
     public void unbind() {
         if (!mBound) {
             return;
@@ -367,8 +376,11 @@
      *
      * @param systemUrl a network Uri, a file Uri or a content Uri pointing to a system image file.
      * @param systemSize size of system image.
+     * @hide
      */
     @RequiresPermission(android.Manifest.permission.INSTALL_DYNAMIC_SYSTEM)
+    @SystemApi
+    @TestApi
     public void start(@NonNull Uri systemUrl, @BytesLong long systemSize) {
         start(systemUrl, systemSize, DEFAULT_USERDATA_SIZE);
     }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 66b9d168..0db5c36 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8081,6 +8081,15 @@
                 "lock_screen_show_silent_notifications";
 
         /**
+         * Indicates whether snooze options should be shown on notifications
+         * <p>
+         * Type: int (0 for false, 1 for true)
+         *
+         * @hide
+         */
+        public static final String SHOW_NOTIFICATION_SNOOZE = "show_notification_snooze";
+
+        /**
          * List of TV inputs that are currently hidden. This is a string
          * containing the IDs of all hidden TV inputs. Each ID is encoded by
          * {@link android.net.Uri#encode(String)} and separated by ':'.
@@ -8968,6 +8977,7 @@
             LOCK_SCREEN_CUSTOM_CLOCK_FACE,
             LOCK_SCREEN_SHOW_NOTIFICATIONS,
             LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS,
+            SHOW_NOTIFICATION_SNOOZE,
             ZEN_DURATION,
             SHOW_ZEN_UPGRADE_NOTIFICATION,
             SHOW_ZEN_SETTINGS_SUGGESTION,
@@ -9150,6 +9160,7 @@
             VALIDATORS.put(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, BOOLEAN_VALIDATOR);
             VALIDATORS.put(LOCK_SCREEN_SHOW_NOTIFICATIONS, BOOLEAN_VALIDATOR);
             VALIDATORS.put(LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, BOOLEAN_VALIDATOR);
+            VALIDATORS.put(SHOW_NOTIFICATION_SNOOZE, BOOLEAN_VALIDATOR);
             VALIDATORS.put(ZEN_DURATION, ZEN_DURATION_VALIDATOR);
             VALIDATORS.put(SHOW_ZEN_UPGRADE_NOTIFICATION, BOOLEAN_VALIDATOR);
             VALIDATORS.put(SHOW_ZEN_SETTINGS_SUGGESTION, BOOLEAN_VALIDATOR);
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index c42dc81..324e02c 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -16,6 +16,7 @@
 
 package android.util;
 
+import android.annotation.TestApi;
 import android.content.Context;
 import android.os.SystemProperties;
 import android.provider.Settings;
@@ -29,6 +30,7 @@
  *
  * @hide
  */
+@TestApi
 public class FeatureFlagUtils {
 
     public static final String FFLAG_PREFIX = "sys.fflag.";
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 9436633..73e0e4b 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -1301,7 +1301,7 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
         final boolean isOutside =
-                event.getAction() == MotionEvent.ACTION_DOWN && isOutOfBounds(context, event)
+                event.getAction() == MotionEvent.ACTION_UP && isOutOfBounds(context, event)
                 || event.getAction() == MotionEvent.ACTION_OUTSIDE;
         if (mCloseOnTouchOutside && peekDecorView() != null && isOutside) {
             return true;
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index fca97fe..ee99837 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -1434,6 +1434,22 @@
                 List<ShortcutManager.ShareShortcutInfo> resultList,
                 List<DisplayResolveInfo> driList,
                 @Nullable List<AppTarget> appTargets) {
+        if (appTargets != null && appTargets.size() != resultList.size()) {
+            throw new RuntimeException("resultList and appTargets must have the same size."
+                    + " resultList.size()=" + resultList.size()
+                    + " appTargets.size()=" + appTargets.size());
+        }
+
+        for (int i = resultList.size() - 1; i >= 0; i--) {
+            final String packageName = resultList.get(i).getTargetComponent().getPackageName();
+            if (!isPackageEnabled(packageName)) {
+                resultList.remove(i);
+                if (appTargets != null) {
+                    appTargets.remove(i);
+                }
+            }
+        }
+
         // Match ShareShortcutInfos with DisplayResolveInfos to be able to use the old code path
         // for direct share targets. After ShareSheet is refactored we should use the
         // ShareShortcutInfos directly.
@@ -1447,7 +1463,6 @@
                     ChooserTarget chooserTarget = convertToChooserTarget(shareShortcutInfo);
                     chooserTargets.add(chooserTarget);
                     if (mDirectShareAppTargetCache != null && appTargets != null) {
-                        // Note that appTargets.size() == resultList.size() is always true.
                         mDirectShareAppTargetCache.put(chooserTarget, appTargets.get(j));
                     }
                 }
@@ -1473,6 +1488,24 @@
         mChooserHandler.sendMessage(msg);
     }
 
+    private boolean isPackageEnabled(String packageName) {
+        if (TextUtils.isEmpty(packageName)) {
+            return false;
+        }
+        ApplicationInfo appInfo;
+        try {
+            appInfo = getPackageManager().getApplicationInfo(packageName, 0);
+        } catch (NameNotFoundException e) {
+            return false;
+        }
+
+        if (appInfo != null && appInfo.enabled
+                && (appInfo.flags & ApplicationInfo.FLAG_SUSPENDED) == 0) {
+            return true;
+        }
+        return false;
+    }
+
     private ChooserTarget convertToChooserTarget(ShortcutManager.ShareShortcutInfo shareShortcut) {
         ShortcutInfo shortcutInfo = shareShortcut.getShortcutInfo();
         Bundle extras = new Bundle();
@@ -1603,7 +1636,8 @@
      */
     @Nullable
     private AppPredictor getAppPredictorForDirectShareIfEnabled() {
-        return USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS ? getAppPredictor() : null;
+        return USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS && !ActivityManager.isLowRamDeviceStatic()
+                ? getAppPredictor() : null;
     }
 
     /**
@@ -2349,6 +2383,8 @@
 
         @Override
         public void onListRebuilt() {
+            updateAlphabeticalList();
+
             // don't support direct share on low ram devices
             if (ActivityManager.isLowRamDeviceStatic()) {
                 return;
@@ -2379,7 +2415,6 @@
 
                 queryTargetServices(this);
             }
-            updateAlphabeticalList();
         }
 
         @Override
@@ -2830,7 +2865,7 @@
         // There can be at most one row in the listview, that is internally
         // a ViewGroup with 2 rows
         public int getServiceTargetRowCount() {
-            if (isSendAction(getTargetIntent())) {
+            if (isSendAction(getTargetIntent()) && !ActivityManager.isLowRamDeviceStatic()) {
                 return 1;
             }
             return 0;
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 538c81d..0a01beb 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -383,9 +383,11 @@
                 mSystemWindowInsets.right, 0);
 
         View emptyView = findViewById(R.id.empty);
-        emptyView.setPadding(0, 0, 0, mSystemWindowInsets.bottom
-                + getResources().getDimensionPixelSize(
-                        R.dimen.chooser_edge_margin_normal) * 2);
+        if (emptyView != null) {
+            emptyView.setPadding(0, 0, 0, mSystemWindowInsets.bottom
+                    + getResources().getDimensionPixelSize(
+                            R.dimen.chooser_edge_margin_normal) * 2);
+        }
 
         if (mFooterSpacer == null) {
             mFooterSpacer = new Space(getApplicationContext());
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 14fe6ab..28ac8cc 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -113,5 +113,25 @@
      */
     public static final String PROPERTY_PERMISSIONS_HUB_ENABLED = "permissions_hub_enabled";
 
+    // Flags related to Assistant Handles
+
+    /**
+     * (String) Which behavior mode for the Assistant Handles to use.
+     */
+    public static final String ASSIST_HANDLES_BEHAVIOR_MODE = "assist_handles_behavior_mode";
+
+    /**
+     * (long) How long, in milliseconds, to display Assist Handles when showing them temporarily.
+     */
+    public static final String ASSIST_HANDLES_SHOW_AND_GO_DURATION_MS =
+            "assist_handles_show_and_go_duration_ms";
+
+    /**
+     * (long) How long, in milliseconds, to wait before displaying Assist Handles temporarily after
+     * hiding them.
+     */
+    public static final String ASSIST_HANDLES_SHOWN_FREQUENCY_THRESHOLD_MS =
+            "assist_handles_shown_frequency_threshold_ms";
+
     private SystemUiDeviceConfigFlags() { }
 }
diff --git a/core/java/com/android/internal/net/VpnInfo.java b/core/java/com/android/internal/net/VpnInfo.java
index b1a41287..e74af5e 100644
--- a/core/java/com/android/internal/net/VpnInfo.java
+++ b/core/java/com/android/internal/net/VpnInfo.java
@@ -19,6 +19,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.Arrays;
+
 /**
  * A lightweight container used to carry information of the ongoing VPN.
  * Internal use only..
@@ -28,14 +30,14 @@
 public class VpnInfo implements Parcelable {
     public int ownerUid;
     public String vpnIface;
-    public String primaryUnderlyingIface;
+    public String[] underlyingIfaces;
 
     @Override
     public String toString() {
         return "VpnInfo{"
                 + "ownerUid=" + ownerUid
                 + ", vpnIface='" + vpnIface + '\''
-                + ", primaryUnderlyingIface='" + primaryUnderlyingIface + '\''
+                + ", underlyingIfaces='" + Arrays.toString(underlyingIfaces) + '\''
                 + '}';
     }
 
@@ -48,7 +50,7 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(ownerUid);
         dest.writeString(vpnIface);
-        dest.writeString(primaryUnderlyingIface);
+        dest.writeStringArray(underlyingIfaces);
     }
 
     public static final Parcelable.Creator<VpnInfo> CREATOR = new Parcelable.Creator<VpnInfo>() {
@@ -57,7 +59,7 @@
             VpnInfo info = new VpnInfo();
             info.ownerUid = source.readInt();
             info.vpnIface = source.readString();
-            info.primaryUnderlyingIface = source.readString();
+            info.underlyingIfaces = source.readStringArray();
             return info;
         }
 
diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto
index 3f8ddff..2873379 100644
--- a/core/proto/android/server/jobscheduler.proto
+++ b/core/proto/android/server/jobscheduler.proto
@@ -273,9 +273,11 @@
         // The maximum number of jobs an app can run within this particular standby bucket's
         // window size.
         optional int32 max_job_count_rare = 11;
+        // The period of time used to rate limit recently run jobs.
+        optional int32 rate_limiting_window_ms = 19;
         // The maximum number of jobs that should be allowed to run in the past
-        // {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS}.
-        optional int32 max_job_count_per_allowed_time = 12;
+        // rate_limiting_window_ms.
+        optional int32 max_job_count_per_rate_limiting_window = 12;
         // The maximum number of timing sessions an app can run within this particular standby
         // bucket's window size.
         optional int32 max_session_count_active = 13;
@@ -289,8 +291,8 @@
         // bucket's window size.
         optional int32 max_session_count_rare = 16;
         // The maximum number of timing sessions that should be allowed to run in the past
-        // {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS}.
-        optional int32 max_session_count_per_allowed_time = 17;
+        // rate_limiting_window_ms.
+        optional int32 max_session_count_per_rate_limiting_window = 17;
         // Treat two distinct {@link TimingSession}s as the same if they start and end within this
         // amount of time of each other.
         optional int64 timing_session_coalescing_duration_ms = 18;
@@ -517,63 +519,56 @@
             optional int64 expiration_time_elapsed = 2;
             optional int64 window_size_ms = 3;
 
-            /** The total amount of time the app ran in its respective bucket window size. */
+            optional int32 job_count_limit = 14;
+            optional int32 session_count_limit = 15;
+
+            // The total amount of time the app ran in its respective bucket window size.
             optional int64 execution_time_in_window_ms = 4;
             optional int32 bg_job_count_in_window = 5;
 
-            /**
-             * The total amount of time the app ran in the last
-             * {@link QuotaController#MAX_PERIOD_MS}.
-             */
+            // The total amount of time the app ran in the last
+            // {@link QuotaController#MAX_PERIOD_MS}.
             optional int64 execution_time_in_max_period_ms = 6;
             optional int32 bg_job_count_in_max_period = 7;
 
-            /**
-             * The number of {@link TimingSession}s within the bucket window size. This will include
-             * sessions that started before the window as long as they end within the window.
-             */
+            // The number of {@link TimingSession}s within the bucket window size. This will include
+            // sessions that started before the window as long as they end within the window.
             optional int32 session_count_in_window = 11;
 
-            /**
-             * The time after which the sum of all the app's sessions plus
-             * ConstantsProto.QuotaController.in_quota_buffer_ms equals the quota. This is only
-             * valid if
-             * execution_time_in_window_ms >=
-             *   ConstantsProto.QuotaController.allowed_time_per_period_ms
-             * or
-             * execution_time_in_max_period_ms >=
-             *   ConstantsProto.QuotaController.max_execution_time_ms.
-             */
-            optional int64 quota_cutoff_time_elapsed = 8;
+            // The time after which the app will be under the bucket quota. This is only valid if
+            // execution_time_in_window_ms >=
+            //   ConstantsProto.QuotaController.allowed_time_per_period_ms
+            // or
+            // execution_time_in_max_period_ms >=
+            //   ConstantsProto.QuotaController.max_execution_time_ms
+            // or
+            // bg_job_count_in_window >= job_count_limit
+            // or
+            // session_count_in_window >= session_count_limit.
+            optional int64 in_quota_time_elapsed = 8;
 
-            /**
-             * The time after which job_count_in_allowed_time should be considered invalid, in the
-             * elapsed realtime timebase.
-             */
+            // The time after which job_count_in_rate_limiting_window should be considered invalid,
+            // in the elapsed realtime timebase.
             optional int64 job_count_expiration_time_elapsed = 9;
 
-            /**
-             * The number of jobs that ran in at least the last
-             * ConstantsProto.QuotaController.allowed_time_per_period_ms.
-             * It may contain a few stale entries since cleanup won't happen exactly every
-             * ConstantsProto.QuotaController.allowed_time_per_period_ms.
-             */
-            optional int32 job_count_in_allowed_time = 10;
+            // The number of jobs that ran in at least the last
+            // ConstantsProto.QuotaController.rate_limiting_window_ms.
+            // It may contain a few stale entries since cleanup won't happen exactly every
+            // ConstantsProto.QuotaController.rate_limiting_window_ms. This should only be
+            // considered valid before elapsed realtime has reached
+            // job_count_expiration_time_elapsed.
+            optional int32 job_count_in_rate_limiting_window = 10;
 
-            /**
-             * The time after which {@link #timingSessionCountInAllowedTime} should be considered
-             * invalid, in the elapsed realtime timebase.
-             */
+            // The time after which {@link #timingSessionCountInAllowedTime} should be considered
+            // invalid, in the elapsed realtime timebase.
             optional int64 session_count_expiration_time_elapsed = 12;
 
-            /**
-             * The number of {@link TimingSession}s that ran in at least the last
-             * {@link #mAllowedTimePerPeriodMs}. It may contain a few stale entries since cleanup won't
-             * happen exactly every {@link #mAllowedTimePerPeriodMs}. This should only be considered
-             * valid before elapsed realtime has reached
-             * {@link #timingSessionCountExpirationTimeElapsed}.
-             */
-            optional int32 session_count_in_allowed_time = 13;
+            // The number of {@link TimingSession}s that ran in at least the last
+            // ConstantsProto.QuotaController.rate_limiting_window_ms. It may contain a few stale
+            // entries since cleanup won't happen exactly every
+            // ConstantsProto.QuotaController.rate_limiting_window_ms. This should only be considered
+            // valid before elapsed realtime has reached session_count_expiration_time_elapsed.
+            optional int32 session_count_in_rate_limiting_window = 13;
         }
 
         message Package {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ab7aed3..890ad5e 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2337,7 +2337,7 @@
          The app can check whether it has this authorization by calling
          {@link android.provider.Settings#canDrawOverlays
          Settings.canDrawOverlays()}.
-         <p>Protection level: signature -->
+         <p>Protection level: signature|preinstalled|appop|pre23|development -->
     <permission android:name="android.permission.SYSTEM_ALERT_WINDOW"
         android:label="@string/permlab_systemAlertWindow"
         android:description="@string/permdesc_systemAlertWindow"
@@ -2522,7 +2522,7 @@
         can check whether it has this authorization by calling {@link
         android.provider.Settings.System#canWrite Settings.System.canWrite()}.
 
-        <p>Protection level: signature
+        <p>Protection level: signature|preinstalled|appop|pre23
     -->
     <permission android:name="android.permission.WRITE_SETTINGS"
         android:label="@string/permlab_writeSettings"
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index 6289262..b901048 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -1473,10 +1473,14 @@
         <item name="colorError">@color/error_color_device_default_light</item>
         <item name="colorEdgeEffect">@color/edge_effect_device_default_light</item>
 
-        <!-- Add white nav bar with divider that matches material -->
+        <!-- Add divider that matches material -->
         <item name="navigationBarDividerColor">@color/navigation_bar_divider_device_default_settings</item>
-        <item name="navigationBarColor">@android:color/white</item>
+
+        <!-- Add transparent nav and status bars with light icons to support drawing edge-to-edge
+        for Q gestural navigation-->
+        <item name="navigationBarColor">@android:color/transparent</item>
         <item name="windowLightNavigationBar">true</item>
+        <item name="statusBarColor">@android:color/transparent</item>
 
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
diff --git a/core/tests/benchmarks/src/android/net/NetworkStatsBenchmark.java b/core/tests/benchmarks/src/android/net/NetworkStatsBenchmark.java
index 1b65603..707d7b3 100644
--- a/core/tests/benchmarks/src/android/net/NetworkStatsBenchmark.java
+++ b/core/tests/benchmarks/src/android/net/NetworkStatsBenchmark.java
@@ -19,13 +19,22 @@
 import com.google.caliper.BeforeExperiment;
 import com.google.caliper.Param;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
 public class NetworkStatsBenchmark {
-    private static final String UNDERLYING_IFACE = "wlan0";
+    private static final String[] UNDERLYING_IFACES = {"wlan0", "rmnet0"};
     private static final String TUN_IFACE = "tun0";
     private static final int TUN_UID = 999999999;
 
     @Param({"100", "1000"})
     private int mSize;
+    /**
+     * Should not be more than the length of {@link #UNDERLYING_IFACES}.
+     */
+    @Param({"1", "2"})
+    private int mNumUnderlyingIfaces;
     private NetworkStats mNetworkStats;
 
     @BeforeExperiment
@@ -33,8 +42,10 @@
         mNetworkStats = new NetworkStats(0, mSize + 2);
         int uid = 0;
         NetworkStats.Entry recycle = new NetworkStats.Entry();
+        final List<String> allIfaces = getAllIfacesForBenchmark(); // also contains TUN_IFACE.
+        final int totalIfaces = allIfaces.size();
         for (int i = 0; i < mSize; i++) {
-            recycle.iface = (i < mSize / 2) ? TUN_IFACE : UNDERLYING_IFACE;
+            recycle.iface = allIfaces.get(i % totalIfaces);
             recycle.uid = uid;
             recycle.set = i % 2;
             recycle.tag = NetworkStats.TAG_NONE;
@@ -48,22 +59,39 @@
                 uid++;
             }
         }
-        recycle.iface = UNDERLYING_IFACE;
-        recycle.uid = TUN_UID;
-        recycle.set = NetworkStats.SET_FOREGROUND;
-        recycle.tag = NetworkStats.TAG_NONE;
-        recycle.rxBytes = 90000 * mSize;
-        recycle.rxPackets = 40 * mSize;
-        recycle.txBytes = 180000 * mSize;
-        recycle.txPackets = 1200 * mSize;
-        recycle.operations = 0;
-        mNetworkStats.addValues(recycle);
+
+        for (int i = 0; i < mNumUnderlyingIfaces; i++) {
+            recycle.iface = UNDERLYING_IFACES[i];
+            recycle.uid = TUN_UID;
+            recycle.set = NetworkStats.SET_FOREGROUND;
+            recycle.tag = NetworkStats.TAG_NONE;
+            recycle.rxBytes = 90000 * mSize;
+            recycle.rxPackets = 40 * mSize;
+            recycle.txBytes = 180000 * mSize;
+            recycle.txPackets = 1200 * mSize;
+            recycle.operations = 0;
+            mNetworkStats.addValues(recycle);
+        }
+    }
+
+    private String[] getVpnUnderlyingIfaces() {
+        return Arrays.copyOf(UNDERLYING_IFACES, mNumUnderlyingIfaces);
+    }
+
+    /**
+     * Same as {@link #getVpnUnderlyingIfaces}, but also contains {@link #TUN_IFACE}.
+     */
+    private List<String> getAllIfacesForBenchmark() {
+        List<String> ifaces = new ArrayList<>();
+        ifaces.add(TUN_IFACE);
+        ifaces.addAll(Arrays.asList(getVpnUnderlyingIfaces()));
+        return ifaces;
     }
 
     public void timeMigrateTun(int reps) {
         for (int i = 0; i < reps; i++) {
             NetworkStats stats = mNetworkStats.clone();
-            stats.migrateTun(TUN_UID, TUN_IFACE, UNDERLYING_IFACE);
+            stats.migrateTun(TUN_UID, TUN_IFACE, getVpnUnderlyingIfaces());
         }
     }
 
diff --git a/data/etc/car/Android.bp b/data/etc/car/Android.bp
index 37020fc..9272ea5 100644
--- a/data/etc/car/Android.bp
+++ b/data/etc/car/Android.bp
@@ -31,6 +31,13 @@
 }
 
 prebuilt_etc {
+    name: "privapp_whitelist_android.car.cluster",
+    sub_dir: "permissions",
+    src: "android.car.cluster.xml",
+    filename_from_src: true,
+}
+
+prebuilt_etc {
     name: "privapp_whitelist_android.car.usb.handler",
     sub_dir: "permissions",
     src: "android.car.usb.handler.xml",
diff --git a/data/etc/car/android.car.cluster.xml b/data/etc/car/android.car.cluster.xml
new file mode 100644
index 0000000..d7f29da
--- /dev/null
+++ b/data/etc/car/android.car.cluster.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<permissions>
+    <privapp-permissions package="android.car.cluster">
+        <permission name="android.permission.CONTROL_INCALL_EXPERIENCE"/>
+        <permission name="android.permission.INTERACT_ACROSS_USERS"/>
+        <permission name="android.permission.MANAGE_USERS"/>
+        <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
+    </privapp-permissions>
+</permissions>
diff --git a/data/etc/car/com.android.car.xml b/data/etc/car/com.android.car.xml
index f1797de..19548bc 100644
--- a/data/etc/car/com.android.car.xml
+++ b/data/etc/car/com.android.car.xml
@@ -24,6 +24,7 @@
         <permission name="android.permission.PROVIDE_TRUST_AGENT"/>
         <permission name="android.permission.REAL_GET_TASKS"/>
         <permission name="android.permission.REBOOT"/>
+        <permission name="android.permission.READ_LOGS"/>
         <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
     </privapp-permissions>
 </permissions>
diff --git a/data/etc/car/com.google.android.car.kitchensink.xml b/data/etc/car/com.google.android.car.kitchensink.xml
index 6b26e8f..d36a826 100644
--- a/data/etc/car/com.google.android.car.kitchensink.xml
+++ b/data/etc/car/com.google.android.car.kitchensink.xml
@@ -25,6 +25,7 @@
         <permission name="android.permission.MODIFY_PHONE_STATE"/>
         <permission name="android.permission.PROVIDE_TRUST_AGENT"/>
         <permission name="android.permission.REAL_GET_TASKS"/>
+        <permission name="android.permission.READ_LOGS"/>
         <permission name="android.permission.REBOOT"/>
         <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
     </privapp-permissions>
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index dc3041f..820d82d 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -222,10 +222,10 @@
     public final static int SUPPRESSIBLE_MEDIA = 5;
     /**
      * @hide
-     * Denotes a usage for all other sounds not caught in SUPPRESSIBLE_NOTIFICATION,
+     * Denotes a usage for sounds not caught in SUPPRESSIBLE_NOTIFICATION,
      * SUPPRESSIBLE_CALL,SUPPRESSIBLE_NEVER, SUPPRESSIBLE_ALARM or SUPPRESSIBLE_MEDIA.
-     * This includes system, sonification and unknown sounds.
-     * These will be muted when the Zen priority mode doesn't allow sytem sounds
+     * This includes sonification sounds.
+     * These will be muted when the Zen priority mode doesn't allow system sounds
      * @see #SUPPRESSIBLE_USAGES
      */
     public final static int SUPPRESSIBLE_SYSTEM = 6;
@@ -248,6 +248,7 @@
         SUPPRESSIBLE_USAGES.put(USAGE_NOTIFICATION_EVENT,                SUPPRESSIBLE_NOTIFICATION);
         SUPPRESSIBLE_USAGES.put(USAGE_ASSISTANCE_ACCESSIBILITY,          SUPPRESSIBLE_NEVER);
         SUPPRESSIBLE_USAGES.put(USAGE_VOICE_COMMUNICATION,               SUPPRESSIBLE_NEVER);
+        SUPPRESSIBLE_USAGES.put(USAGE_VOICE_COMMUNICATION_SIGNALLING,    SUPPRESSIBLE_NEVER);
         SUPPRESSIBLE_USAGES.put(USAGE_ALARM,                             SUPPRESSIBLE_ALARM);
         SUPPRESSIBLE_USAGES.put(USAGE_MEDIA,                             SUPPRESSIBLE_MEDIA);
         SUPPRESSIBLE_USAGES.put(USAGE_ASSISTANCE_NAVIGATION_GUIDANCE,    SUPPRESSIBLE_MEDIA);
@@ -255,7 +256,6 @@
         SUPPRESSIBLE_USAGES.put(USAGE_ASSISTANT,                         SUPPRESSIBLE_MEDIA);
         /** default volume assignment is STREAM_MUSIC, handle unknown usage as media */
         SUPPRESSIBLE_USAGES.put(USAGE_UNKNOWN,                           SUPPRESSIBLE_MEDIA);
-        SUPPRESSIBLE_USAGES.put(USAGE_VOICE_COMMUNICATION_SIGNALLING,    SUPPRESSIBLE_SYSTEM);
         SUPPRESSIBLE_USAGES.put(USAGE_ASSISTANCE_SONIFICATION,           SUPPRESSIBLE_SYSTEM);
     }
 
diff --git a/media/java/android/media/MediaHTTPConnection.java b/media/java/android/media/MediaHTTPConnection.java
index b5c4cca..8ee929e 100644
--- a/media/java/android/media/MediaHTTPConnection.java
+++ b/media/java/android/media/MediaHTTPConnection.java
@@ -38,6 +38,7 @@
 import java.net.UnknownServiceException;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /** @hide */
 public class MediaHTTPConnection extends IMediaHTTPConnection.Stub {
@@ -83,6 +84,10 @@
     private final static int HTTP_TEMP_REDIRECT = 307;
     private final static int MAX_REDIRECTS = 20;
 
+    // The number of threads that are currently running disconnect() (possibly
+    // not yet holding the synchronized lock).
+    private final AtomicInteger mNumDisconnectingThreads = new AtomicInteger(0);
+
     @UnsupportedAppUsage
     public MediaHTTPConnection() {
         CookieHandler cookieHandler = CookieHandler.getDefault();
@@ -155,19 +160,24 @@
     @Override
     @UnsupportedAppUsage
     public void disconnect() {
-        HttpURLConnection connectionToDisconnect = mConnection;
-        // Call disconnect() before blocking for the lock in order to ensure that any
-        // other thread that is blocked in readAt() will return quickly.
-        if (connectionToDisconnect != null) {
-            connectionToDisconnect.disconnect();
-        }
-        synchronized (this) {
-            // It's unlikely but possible that while we were waiting to acquire the lock, another
-            // thread concurrently started a new connection; if so, we're disconnecting that one
-            // here, too.
-            teardownConnection();
-            mHeaders = null;
-            mURL = null;
+        mNumDisconnectingThreads.incrementAndGet();
+        try {
+            HttpURLConnection connectionToDisconnect = mConnection;
+            // Call disconnect() before blocking for the lock in order to ensure that any
+            // other thread that is blocked in readAt() will return quickly.
+            if (connectionToDisconnect != null) {
+                connectionToDisconnect.disconnect();
+            }
+            synchronized (this) {
+                // It's possible that while we were waiting to acquire the lock, another thread
+                // concurrently started a new connection; if so, we're disconnecting that one
+                // here, too.
+                teardownConnection();
+                mHeaders = null;
+                mURL = null;
+            }
+        } finally {
+            mNumDisconnectingThreads.decrementAndGet();
         }
     }
 
@@ -224,11 +234,36 @@
             boolean noProxy = isLocalHost(url);
 
             while (true) {
+                // If another thread is concurrently disconnect()ing, there's a race
+                // between them and us. Therefore, we check mNumDisconnectingThreads shortly
+                // (not atomically) before & after writing mConnection. This guarantees that
+                // we won't "lose" a disconnect by creating a new connection that might
+                // miss the disconnect.
+                //
+                // Note that throwing an instanceof IOException is also what this thread
+                // would have done if another thread disconnect()ed the connection while
+                // this thread was blocked reading from that connection further down in this
+                // loop.
+                if (mNumDisconnectingThreads.get() > 0) {
+                    throw new IOException("concurrently disconnecting");
+                }
                 if (noProxy) {
                     mConnection = (HttpURLConnection)url.openConnection(Proxy.NO_PROXY);
                 } else {
                     mConnection = (HttpURLConnection)url.openConnection();
                 }
+                // If another thread is concurrently disconnecting, throwing IOException will
+                // cause us to release the lock, giving the other thread a chance to acquire
+                // it. It also ensures that the catch block will run, which will tear down
+                // the connection even if the other thread happens to already be on its way
+                // out of disconnect().
+                if (mNumDisconnectingThreads.get() > 0) {
+                    throw new IOException("concurrently disconnecting");
+                }
+                // If we get here without having thrown, we know that other threads
+                // will see our write to mConnection. Any disconnect() on that mConnection
+                // instance will cause our read from/write to that connection instance below
+                // to encounter an instanceof IOException.
                 mConnection.setConnectTimeout(CONNECT_TIMEOUT_MS);
 
                 // handle redirects ourselves if we do not allow cross-domain redirect
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CameraTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CameraTest.java
index 7f23ba5..9b643ad 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CameraTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CameraTest.java
@@ -25,7 +25,6 @@
 import android.hardware.Camera.PreviewCallback;
 import android.hardware.Camera.ShutterCallback;
 import android.os.ConditionVariable;
-import android.os.Environment;
 import android.os.Looper;
 import android.test.ActivityInstrumentationTestCase;
 import android.test.suitebuilder.annotation.LargeTest;
@@ -159,7 +158,7 @@
                 if (rawData != null) {
                     int rawDataLength = rawData.length;
                     File rawoutput = new File(
-                            Environment.getExternalStorageDirectory().toString(), "/test.bmp");
+                            mContext.getExternalFilesDir(null).getPath(), "/test.bmp");
                     FileOutputStream outstream = new FileOutputStream(rawoutput);
                     outstream.write(rawData);                   
                     Log.v(TAG, "JpegPictureCallback rawDataLength = " + rawDataLength);
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestHelper.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestHelper.java
index 84153d60..bd236a6 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestHelper.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestHelper.java
@@ -22,10 +22,11 @@
 import android.hardware.Camera.Parameters;
 import android.hardware.Camera.PictureCallback;
 import android.hardware.Camera.ShutterCallback;
-import android.os.Environment;
 import android.util.Log;
 import android.view.SurfaceHolder;
 
+import androidx.test.InstrumentationRegistry;
+
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
@@ -70,7 +71,8 @@
             try {
                 Log.v(TAG, "JPEG picture taken");
                 fos = new FileOutputStream(String.format("%s/%s/%s-%d.jpg",
-                        Environment.getExternalStorageDirectory(), CAMERA_STRESS_IMAGES_DIRECTORY,
+                        InstrumentationRegistry.getInstrumentation().getTargetContext()
+                        .getExternalFilesDir(null).getPath(), CAMERA_STRESS_IMAGES_DIRECTORY,
                         CAMERA_STRESS_IMAGES_PREFIX, System.currentTimeMillis()));
                 fos.write(data);
             } catch (FileNotFoundException e) {
@@ -95,7 +97,8 @@
     public void setupCameraTest() {
         // Create the test images directory if it doesn't exist
         File stressImagesDirectory = new File(String.format("%s/%s",
-                Environment.getExternalStorageDirectory(), CAMERA_STRESS_IMAGES_DIRECTORY));
+                InstrumentationRegistry.getInstrumentation().getTargetContext()
+                .getExternalFilesDir(null).getPath(), CAMERA_STRESS_IMAGES_DIRECTORY));
         if (!stressImagesDirectory.exists()) {
             stressImagesDirectory.mkdir();
         }
@@ -129,7 +132,8 @@
     public void cleanupTestImages() {
         try {
             File stressImagesDirectory = new File(String.format("%s/%s",
-                    Environment.getExternalStorageDirectory(), CAMERA_STRESS_IMAGES_DIRECTORY));
+                    InstrumentationRegistry.getInstrumentation().getTargetContext()
+                    .getExternalFilesDir(null).getPath(), CAMERA_STRESS_IMAGES_DIRECTORY));
             File[] stressImages = stressImagesDirectory.listFiles();
             for (File f : stressImages) {
                 f.delete();
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java
index 0340cec..0ae640d 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java
@@ -54,7 +54,6 @@
 import android.media.ImageReader;
 import android.media.ImageWriter;
 import android.os.Build;
-import android.os.Environment;
 import android.os.Handler;
 import android.util.Log;
 import android.util.Pair;
@@ -63,6 +62,8 @@
 import android.view.Surface;
 import android.view.WindowManager;
 
+import androidx.test.InstrumentationRegistry;
+
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.lang.reflect.Array;
@@ -128,7 +129,8 @@
     private static final Location sTestLocation2 = new Location(LocationManager.NETWORK_PROVIDER);
 
     protected static final String DEBUG_FILE_NAME_BASE =
-            Environment.getExternalStorageDirectory().getPath();
+            InstrumentationRegistry.getInstrumentation().getTargetContext()
+            .getExternalFilesDir(null).getPath();
 
     static {
         sTestLocation0.setTime(1199145600L);
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2SwitchPreviewTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2SwitchPreviewTest.java
index 11327ca..a26ee2d 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2SwitchPreviewTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2SwitchPreviewTest.java
@@ -104,7 +104,6 @@
     private static final double AE_COMPENSATION_ERROR_TOLERANCE = 0.2;
     // 5 percent error margin for resulting metering regions
     private static final float METERING_REGION_ERROR_PERCENT_DELTA = 0.05f;
-    private final String VIDEO_FILE_PATH = Environment.getExternalStorageDirectory().getPath();
 
     private static final boolean DEBUG_DUMP = Log.isLoggable(TAG, Log.DEBUG);
     private static final int RECORDING_DURATION_MS = 3000;
@@ -137,10 +136,12 @@
     private int mVideoFrameRate;
     private Size mVideoSize;
     private long mRecordingStartTime;
+    private String mVideoFilePath;
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
+        mVideoFilePath = mContext.getExternalFilesDir(null).getPath();
     }
 
     @Override
@@ -371,9 +372,9 @@
         }
 
         // Configure preview and recording surfaces.
-        mOutMediaFileName = VIDEO_FILE_PATH + "/test_video.mp4";
+        mOutMediaFileName = mVideoFilePath + "/test_video.mp4";
         if (DEBUG_DUMP) {
-            mOutMediaFileName = VIDEO_FILE_PATH + "/test_video_" + cameraId + "_"
+            mOutMediaFileName = mVideoFilePath + "/test_video_" + cameraId + "_"
                     + videoSz.toString() + ".mp4";
         }
 
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/CameraStressTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/CameraStressTest.java
index d1193de..74244b9 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/CameraStressTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/CameraStressTest.java
@@ -28,7 +28,6 @@
 import java.util.List;
 
 import android.hardware.Camera.Parameters;
-import android.os.Environment;
 import android.os.Handler;
 import android.os.Looper;
 import android.test.ActivityInstrumentationTestCase2;
@@ -36,6 +35,8 @@
 import android.util.Log;
 import android.view.SurfaceHolder;
 
+import androidx.test.InstrumentationRegistry;
+
 /**
  * Junit / Instrumentation test case for the following camera APIs:
  *  - camera zoom
@@ -85,7 +86,8 @@
 
         mCameraTestHelper = new CameraTestHelper();
         File stressOutFile = new File(String.format("%s/%s",
-                Environment.getExternalStorageDirectory(), CAMERA_STRESS_OUTPUT));
+                InstrumentationRegistry.getInstrumentation().getTargetContext()
+                .getExternalFilesDir(null).getPath(), CAMERA_STRESS_OUTPUT));
         mOutput = new BufferedWriter(new FileWriter(stressOutFile, true));
         mOutput.write(this.getName() + "\n");
     }
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 7ee2f0a..8fe4fec 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -384,6 +384,9 @@
     transaction->setCrop(surfaceControl, static_cast<const Rect&>(source));
     transaction->setFrame(surfaceControl, static_cast<const Rect&>(destination));
     transaction->setTransform(surfaceControl, transform);
+    bool transformToInverseDisplay = (NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY & transform) ==
+            NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY;
+    transaction->setTransformToDisplayInverse(surfaceControl, transformToInverseDisplay);
 }
 
 void ASurfaceTransaction_setBufferTransparency(ASurfaceTransaction* aSurfaceTransaction,
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index da3416b..1b27b52 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -638,7 +638,7 @@
                     final String docId = DocumentsContract.getDocumentId(documentUri);
                     try {
                         final Bundle out = new Bundle();
-                        final Uri uri = Uri.fromFile(getFileForDocId(docId));
+                        final Uri uri = Uri.fromFile(getFileForDocId(docId, true));
                         out.putParcelable(DocumentsContract.EXTRA_URI, uri);
                         return out;
                     } catch (FileNotFoundException e) {
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 4c72f48..0e91839 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -578,6 +578,8 @@
     <string name="wifi_display_certification">Wireless display certification</string>
     <!-- Setting Checkbox title whether to enable WiFi Verbose Logging. [CHAR LIMIT=40] -->
     <string name="wifi_verbose_logging">Enable Wi\u2011Fi Verbose Logging</string>
+    <!-- Setting Checkbox title whether to disable WiFi Scan Throttling. [CHAR LIMIT=40] -->
+    <string name="wifi_scan_throttling">Wi\u2011Fi scan throttling</string>
     <!-- Setting Checkbox title whether to always keep mobile data active. [CHAR LIMIT=80] -->
     <string name="mobile_data_always_on">Mobile data always active</string>
     <!-- Setting Checkbox title whether to enable hardware acceleration for tethering. [CHAR LIMIT=80] -->
@@ -633,6 +635,8 @@
     <string name="wifi_display_certification_summary">Show options for wireless display certification</string>
     <!-- Setting Checkbox summary whether to enable Wifi verbose Logging [CHAR LIMIT=80] -->
     <string name="wifi_verbose_logging_summary">Increase Wi\u2011Fi logging level, show per SSID RSSI in Wi\u2011Fi Picker</string>
+    <!-- Setting Checkbox summary whether to disable Wifi scan throttling [CHAR LIMIT=NONE] -->
+    <string name="wifi_scan_throttling_summary">Reduces battery drain &amp; improves network performance</string>
     <!-- Label indicating network has been manually marked as metered -->
     <string name="wifi_metered_label">Metered</string>
     <!-- Label indicating network has been manually marked as unmetered -->
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index a245086..da139d7 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -206,8 +206,9 @@
     <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES"/>
 
     <application android:label="@string/app_label"
-                 android:defaultToDeviceProtectedStorage="true"
-                 android:directBootAware="true">
+                android:theme="@android:style/Theme.DeviceDefault.DayNight"
+                android:defaultToDeviceProtectedStorage="true"
+                android:directBootAware="true">
         <provider
             android:name="androidx.core.content.FileProvider"
             android:authorities="com.android.shell"
@@ -232,7 +233,6 @@
 
         <activity
             android:name=".BugreportWarningActivity"
-            android:theme="@android:style/Theme.DeviceDefault.Light.Dialog.Alert"
             android:finishOnCloseSystemDialogs="true"
             android:excludeFromRecents="true"
             android:exported="false" />
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 1060c7b..e57a5de 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -99,6 +99,7 @@
 import android.util.Pair;
 import android.util.Patterns;
 import android.util.SparseArray;
+import android.view.ContextThemeWrapper;
 import android.view.IWindowManager;
 import android.view.View;
 import android.view.WindowManager;
@@ -1467,11 +1468,13 @@
         void initialize(final Context context, BugreportInfo info) {
             final String dialogTitle =
                     context.getString(R.string.bugreport_info_dialog_title, info.id);
+            final Context themedContext = new ContextThemeWrapper(
+                    context, com.android.internal.R.style.Theme_DeviceDefault_DayNight);
             // First initializes singleton.
             if (mDialog == null) {
                 @SuppressLint("InflateParams")
                 // It's ok pass null ViewRoot on AlertDialogs.
-                final View view = View.inflate(context, R.layout.dialog_bugreport_info, null);
+                final View view = View.inflate(themedContext, R.layout.dialog_bugreport_info, null);
 
                 mInfoName = (EditText) view.findViewById(R.id.name);
                 mInfoTitle = (EditText) view.findViewById(R.id.title);
@@ -1488,7 +1491,7 @@
                     }
                 });
 
-                mDialog = new AlertDialog.Builder(context)
+                mDialog = new AlertDialog.Builder(themedContext)
                         .setView(view)
                         .setTitle(dialogTitle)
                         .setCancelable(true)
diff --git a/packages/SystemUI/res/drawable/corner_gesture_hint.xml b/packages/SystemUI/res/drawable/corner_gesture_hint.xml
deleted file mode 100644
index 3f4abb0..0000000
--- a/packages/SystemUI/res/drawable/corner_gesture_hint.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<!--
-    Copyright (C) 2019 The Android Open Source Project
-
-    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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:height="12dp"
-    android:width="12dp"
-    android:viewportWidth="12"
-    android:viewportHeight="12">
-
-    <path android:fillColor="#00000000"
-          android:pathData="M 1.18 10.65 C 1.18 5.58 5.41 1.18 10.65 1.18"
-          android:strokeColor="#000"
-          android:strokeLineCap="round"
-          android:strokeWidth="1.3" />
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_notifications_alert.xml b/packages/SystemUI/res/drawable/ic_notifications_alert.xml
index eb7b8ee..d53d698 100644
--- a/packages/SystemUI/res/drawable/ic_notifications_alert.xml
+++ b/packages/SystemUI/res/drawable/ic_notifications_alert.xml
@@ -14,11 +14,11 @@
     limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24.0dp"
-        android:height="24.0dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
     <path
-        android:pathData="M7.58 4.08L6.15 2.65C3.75 4.48 2.17 7.3 2.03 10.5h2c.15-2.65 1.51-4.97 3.55-6.42zm12.39 6.42h2c-.15-3.2-1.73-6.02-4.12-7.85l-1.42 1.43c2.02 1.45 3.39 3.77 3.54 6.42zM18 11c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2v-5zm-6 11c.14 0 .27-.01.4-.04.65-.14 1.18-.58 1.44-1.18.1-.24.15-.5.15-.78h-4c.01 1.1.9 2 2.01 2z"
-        android:fillColor="#FF000000"/>
-</vector>
+        android:fillColor="@android:color/white"
+        android:pathData="M18 17v-6c0-3.07-1.63-5.64-4.5-6.32V4c0-0.83-0.67-1.5-1.5-1.5s-1.5 0.67 -1.5 1.5v0.68C7.64 5.36 6 7.92 6 11v6H4v2h16v-2h-2zm-2 0H8v-6c0-2.48 1.51-4.5 4-4.5s4 2.02 4 4.5v6zm-6 3h4c0 1.1-0.9 2-2 2s-2-0.9-2-2zm12-9h-2c0-2.74-1.23-5.19-3.16-6.84l1.41-1.41C20.54 4.77 22 7.71 22 11zM5.75 2.75l1.41 1.41C5.23 5.81 4 8.26 4 11H2c0-3.29 1.46-6.23 3.75-8.25z" />
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_notifications_silence.xml b/packages/SystemUI/res/drawable/ic_notifications_silence.xml
index ff136eb..a6cc81b 100644
--- a/packages/SystemUI/res/drawable/ic_notifications_silence.xml
+++ b/packages/SystemUI/res/drawable/ic_notifications_silence.xml
@@ -14,15 +14,11 @@
     limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24.0dp"
-        android:height="24.0dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
     <path
-        android:pathData="M0 0h24v24H0z"
-    />
-    <path
-        android:pathData="M20 18.69L7.84 6.14 5.27 3.49 4 4.76l2.8 2.8v.01c-.52.99-.8 2.16-.8 3.42v5l-2 2v1h13.73l2 2L21 19.72l-1-1.03zM12 22c1.11 0 2-.89 2-2h-4c0 1.11.89 2 2 2zm6-7.32V11c0-3.08-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68c-.15.03-.29.08-.42.12-.1.03-.2.07-.3.11h-.01c-.01 0-.01 0-.02.01-.23.09-.46.2-.68.31 0 0-.01 0-.01.01L18 14.68z"
-        android:fillColor="#FF000000"
-    />
-</vector>
+        android:fillColor="@android:color/white"
+        android:pathData="M12 22c1.1 0 2-0.9 2-2h-4c0 1.1 0.9 2 2 2zm4-6L2.81 2.81 1.39 4.22l4.85 4.85C6.09 9.68 6 10.33 6 11v6H4v2h12.17l3.61 3.61 1.41-1.41L16 16zm-8 1l0.01-6.16L14.17 17H8zm4-10.5c2.49 0 4 2.02 4 4.5v2.17l2 2V11c0-3.07-1.63-5.64-4.5-6.32V4c0-0.83-0.67-1.5-1.5-1.5s-1.5 0.67 -1.5 1.5v0.68c-0.78 0.18 -1.45 0.52 -2.04 0.95 L9.93 7.1c0.58-0.37 1.27-0.6 2.07-0.6z" />
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_volume_media_bt.xml b/packages/SystemUI/res/drawable/ic_volume_media_bt.xml
index 9f7744e..23cb206 100644
--- a/packages/SystemUI/res/drawable/ic_volume_media_bt.xml
+++ b/packages/SystemUI/res/drawable/ic_volume_media_bt.xml
@@ -14,14 +14,12 @@
      limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24.0dp"
-    android:height="24.0dp"
-    android:viewportWidth="24.0"
-    android:viewportHeight="24.0"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
     android:tint="?android:attr/colorControlNormal">
-
     <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M9,3l0.01,10.55C8.41,13.21 7.73,13 7.01,13C4.79,13 3,14.79 3,17c0,2.21 1.79,4 4.01,4S11,19.21 11,17V7h4V3H9zM7.01,19c-1.1,0 -2,-0.9 -2,-2c0,-1.1 0.9,-2 2,-2s2,0.9 2,2C9.01,18.1 8.11,19 7.01,19zM21,12.43L17.57,9h-0.6v4.55l-2.75,-2.75l-0.85,0.85L16.73,15l-3.35,3.35l0.85,0.85l2.75,-2.75V21h0.6L21,17.57L18.42,15L21,12.43zM18.17,11.3l1.13,1.13l-1.13,1.13V11.3zM19.3,17.57l-1.13,1.13v-2.26L19.3,17.57z"/>
-
-</vector>
+        android:fillColor="@android:color/white"
+        android:pathData="M9 3l0.01 10.55c-0.6-0.34-1.28-0.55-2-0.55C4.79 13 3 14.79 3 17s1.79 4 4.01 4S11 19.21 11 17V7h4V3H9zm12 9.43L17.57 9h-0.6v4.55l-2.75-2.75-0.85 0.85 L16.73 15l-3.35 3.35 0.85 0.85 2.75-2.75V21h0.6L21 17.57 18.42 15 21 12.43zm-2.83-1.13l1.13 1.13-1.13 1.13V11.3zm1.13 6.27l-1.13 1.13v-2.26l1.13 1.13z" />
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_volume_media_bt_mute.xml b/packages/SystemUI/res/drawable/ic_volume_media_bt_mute.xml
index 12e0f2e..2469ddc 100644
--- a/packages/SystemUI/res/drawable/ic_volume_media_bt_mute.xml
+++ b/packages/SystemUI/res/drawable/ic_volume_media_bt_mute.xml
@@ -14,14 +14,12 @@
      limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:height="24dp"
-    android:viewportHeight="24.0"
-    android:viewportWidth="24.0"
     android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
     android:tint="?android:attr/colorControlNormal">
-
     <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M9,6.17L9,3h6v4h-4v1.17L9,6.17zM19.42,15L22,17.57l-0.8,0.8l-6.78,-6.78l0.8,-0.8l2.75,2.75V9h0.6L22,12.43L19.42,15zM19.17,13.55l1.13,-1.13l-1.13,-1.13V13.55zM17.21,17.21l3.98,3.98l-1.41,1.41l-3.98,-3.98l-0.58,0.58l-0.85,-0.85l0.58,-0.58L11,13.83V17c0,2.21 -1.78,4 -3.99,4S3,19.21 3,17c0,-2.21 1.79,-4 4.01,-4c0.73,0 1.41,0.21 2,0.55l0,-1.72L1.39,4.22l1.41,-1.41l13.56,13.56L17.21,17.21zM9.01,17c0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2c0,1.1 0.9,2 2,2S9.01,18.1 9.01,17z"/>
-
-</vector>
+        android:fillColor="@android:color/white"
+        android:pathData="M9 6.17V3h6v4h-4v1.17l-2-2zM19.42 15L22 17.57l-0.8 0.8 -6.78-6.78 0.8 -0.8 2.75 2.75V9h0.6L22 12.43 19.42 15zm-0.25-1.45l1.13-1.13-1.13-1.13v2.26zm-1.96 3.66l3.98 3.98-1.41 1.41-3.98-3.98-0.58 0.58 -0.85-0.85 0.58 -0.58L11 13.83V17c0 2.21-1.78 4-3.99 4S3 19.21 3 17s1.79-4 4.01-4c0.73 0 1.41 0.21 2 0.55v-1.72L1.39 4.22 2.8 2.81l13.56 13.56 0.85 0.84z" />
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/rounded_corners.xml b/packages/SystemUI/res/layout/rounded_corners.xml
index 02651a2..b409c8f 100644
--- a/packages/SystemUI/res/layout/rounded_corners.xml
+++ b/packages/SystemUI/res/layout/rounded_corners.xml
@@ -18,36 +18,30 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
+    <com.android.systemui.CornerHandleView
+        android:id="@+id/assist_hint_left"
+        android:layout_width="36dp"
+        android:layout_height="36dp"
+        android:layout_gravity="left|top"
+        android:visibility="gone"/>
+    <com.android.systemui.CornerHandleView
+        android:id="@+id/assist_hint_right"
+        android:layout_width="36dp"
+        android:layout_height="36dp"
+        android:layout_gravity="right|bottom"
+        android:visibility="gone"/>
     <ImageView
         android:id="@+id/left"
         android:layout_width="12dp"
         android:layout_height="12dp"
         android:layout_gravity="left|top"
         android:tint="#ff000000"
-        android:src="@drawable/rounded" />
+        android:src="@drawable/rounded"/>
     <ImageView
         android:id="@+id/right"
         android:layout_width="12dp"
         android:layout_height="12dp"
         android:tint="#ff000000"
         android:layout_gravity="right|bottom"
-        android:src="@drawable/rounded" />
-    <ImageView
-        android:id="@+id/assist_hint_left"
-        android:layout_width="32dp"
-        android:layout_height="32dp"
-        android:padding="6dp"
-        android:layout_gravity="left|top"
-        android:src="@drawable/corner_gesture_hint"
-        android:tint="#ffffffff"
-        android:visibility="gone" />
-    <ImageView
-        android:id="@+id/assist_hint_right"
-        android:layout_width="32dp"
-        android:layout_height="32dp"
-        android:padding="6dp"
-        android:layout_gravity="right|bottom"
-        android:src="@drawable/corner_gesture_hint"
-        android:tint="#ffffffff"
-        android:visibility="gone" />
+        android:src="@drawable/rounded"/>
 </com.android.systemui.RegionInterceptingFrameLayout>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 1987494..c04d28a 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -38,7 +38,6 @@
     <dimen name="navigation_handle_sample_horizontal_margin">10dp</dimen>
     <dimen name="navigation_home_handle_width">72dp</dimen>
 
-
     <!-- Size of the nav bar edge panels, should be greater to the
          edge sensitivity + the drag threshold -->
     <dimen name="navigation_edge_panel_width">70dp</dimen>
@@ -57,6 +56,9 @@
     <!-- Luminance change threshold that allows applying new value if difference was exceeded -->
     <item name="navigation_luminance_change_threshold" type="dimen" format="float">0.05</item>
 
+    <dimen name="floating_rotation_button_diameter">40dp</dimen>
+    <dimen name="floating_rotation_button_margin">4dp</dimen>
+
     <!-- Height of notification icons in the status bar -->
     <dimen name="status_bar_icon_size">@*android:dimen/status_bar_icon_size</dimen>
 
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index de4c798..bce5c23 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -322,9 +322,6 @@
         mCharging = pluggedIn;
         mLevel = level;
         updatePercentText();
-        setContentDescription(
-                getContext().getString(charging ? R.string.accessibility_battery_level_charging
-                        : R.string.accessibility_battery_level, level));
     }
 
     @Override
@@ -358,6 +355,9 @@
                 mBatteryController.getEstimatedTimeRemainingString((String estimate) -> {
                     if (estimate != null) {
                         mBatteryPercentView.setText(estimate);
+                        setContentDescription(getContext().getString(
+                                R.string.battery_low_percent_format_hybrid, mLevel, estimate));
+
                     } else {
                         setPercentTextAtCurrentLevel();
                     }
@@ -371,6 +371,9 @@
     private void setPercentTextAtCurrentLevel() {
         mBatteryPercentView.setText(
                 NumberFormat.getPercentInstance().format(mLevel / 100f));
+        setContentDescription(
+                getContext().getString(mCharging ? R.string.accessibility_battery_level_charging
+                        : R.string.accessibility_battery_level, mLevel));
     }
 
     private void updateShowPercent() {
diff --git a/packages/SystemUI/src/com/android/systemui/CornerHandleView.java b/packages/SystemUI/src/com/android/systemui/CornerHandleView.java
new file mode 100644
index 0000000..528db5a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/CornerHandleView.java
@@ -0,0 +1,143 @@
+/*
+ * 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;
+
+import android.animation.ArgbEvaluator;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.os.SystemProperties;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.ContextThemeWrapper;
+import android.view.View;
+
+import com.android.settingslib.Utils;
+
+/**
+ * CornerHandleView draws an inset arc intended to be displayed within the screen decoration
+ * corners.
+ */
+public class CornerHandleView extends View {
+    private static final boolean ALLOW_TUNING = false;
+    private static final int ANGLE_DEGREES = 50;
+    public static final int MARGIN_DP = 11;
+    public static final int RADIUS_DP = 37;
+    public static final float STROKE_DP = 2.5f;
+
+    private Paint mPaint;
+    private int mLightColor;
+    private int mDarkColor;
+    private RectF mOval;
+
+
+    public CornerHandleView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        mPaint = new Paint();
+        mPaint.setAntiAlias(true);
+        mPaint.setStyle(Paint.Style.STROKE);
+        mPaint.setStrokeCap(Paint.Cap.ROUND);
+        mPaint.setStrokeWidth(getStrokePx());
+
+        final int dualToneDarkTheme = Utils.getThemeAttr(mContext,
+                R.attr.darkIconTheme);
+        final int dualToneLightTheme = Utils.getThemeAttr(mContext,
+                R.attr.lightIconTheme);
+        Context lightContext = new ContextThemeWrapper(mContext, dualToneLightTheme);
+        Context darkContext = new ContextThemeWrapper(mContext, dualToneDarkTheme);
+        mLightColor = Utils.getColorAttrDefaultColor(lightContext,
+                R.attr.singleToneColor);
+        mDarkColor = Utils.getColorAttrDefaultColor(darkContext,
+                R.attr.singleToneColor);
+
+        updateOval();
+    }
+
+    /**
+     * Receives an intensity from 0 (lightest) to 1 (darkest) and sets the handle color
+     * approriately. Intention is to match the home handle color.
+     */
+    public void updateDarkness(float darkIntensity) {
+        mPaint.setColor((int) ArgbEvaluator.getInstance().evaluate(darkIntensity,
+                mLightColor,
+                mDarkColor));
+        invalidate();
+    }
+
+    @Override
+    public void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        if (ALLOW_TUNING) {
+            mPaint.setStrokeWidth(getStrokePx());
+            updateOval();
+        }
+
+        canvas.drawArc(mOval, 180 + ((90 - getAngle()) / 2), getAngle(), false,
+                mPaint);
+    }
+
+    // TODO(b/133834204): Remove tweaking of corner handles
+    private static float convertDpToPixel(float dp, Context context) {
+        return dp * ((float) context.getResources().getDisplayMetrics().densityDpi
+                / DisplayMetrics.DENSITY_DEFAULT);
+    }
+
+    private void updateOval() {
+        mOval = new RectF(getMarginPx() - (getStrokePx() / 2.f),
+                getMarginPx() - (getStrokePx() / 2.f),
+                getMarginPx() + (2 * (getRadiusPx()) + (getStrokePx() / 2.f)),
+                getMarginPx() + 2 * getRadiusPx() + (getStrokePx() / 2.f));
+    }
+
+    private int getAngle() {
+        if (ALLOW_TUNING) {
+            return SystemProperties.getInt("CORNER_HANDLE_ANGLE_DEGREES", ANGLE_DEGREES);
+        } else {
+            return ANGLE_DEGREES;
+        }
+    }
+
+    private int getMarginPx() {
+        if (ALLOW_TUNING) {
+            return SystemProperties.getInt("CORNER_HANDLE_MARGIN_PX",
+                    (int) convertDpToPixel(MARGIN_DP, getContext()));
+        } else {
+            return (int) convertDpToPixel(MARGIN_DP, getContext());
+        }
+    }
+
+    private int getRadiusPx() {
+        if (ALLOW_TUNING) {
+            return SystemProperties.getInt("CORNER_HANDLE_RADIUS_PX",
+                    (int) convertDpToPixel(RADIUS_DP, getContext()));
+        } else {
+            return (int) convertDpToPixel(RADIUS_DP, getContext());
+        }
+    }
+
+    private int getStrokePx() {
+        if (ALLOW_TUNING) {
+            return SystemProperties.getInt("CORNER_HANDLE_STROKE_PX",
+                    (int) convertDpToPixel(STROKE_DP, getContext()));
+        } else {
+            return (int) convertDpToPixel(STROKE_DP, getContext());
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 390a9e6..4aaf85a 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -60,6 +60,12 @@
 import android.view.ViewGroup.LayoutParams;
 import android.view.ViewTreeObserver;
 import android.view.WindowManager;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.OvershootInterpolator;
+import android.view.animation.PathInterpolator;
+import android.view.animation.ScaleAnimation;
+import android.view.animation.TranslateAnimation;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 
@@ -72,6 +78,7 @@
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.qs.SecureSetting;
 import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
+import com.android.systemui.statusbar.phone.NavigationBarTransitions;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.tuner.TunablePadding;
 import com.android.systemui.tuner.TunerService;
@@ -85,7 +92,8 @@
  * An overlay that draws screen decorations in software (e.g for rounded corners or display cutout)
  * for antialiasing and emulation purposes.
  */
-public class ScreenDecorations extends SystemUI implements Tunable {
+public class ScreenDecorations extends SystemUI implements Tunable,
+        NavigationBarTransitions.DarkIntensityListener {
     private static final boolean DEBUG = false;
     private static final String TAG = "ScreenDecorations";
 
@@ -93,13 +101,17 @@
     public static final String PADDING = "sysui_rounded_content_padding";
     private static final boolean DEBUG_SCREENSHOT_ROUNDED_CORNERS =
             SystemProperties.getBoolean("debug.screenshot_rounded_corners", false);
+    private static final boolean VERBOSE = false;
 
     private DisplayManager mDisplayManager;
     private DisplayManager.DisplayListener mDisplayListener;
 
-    @VisibleForTesting protected int mRoundedDefault;
-    @VisibleForTesting protected int mRoundedDefaultTop;
-    @VisibleForTesting protected int mRoundedDefaultBottom;
+    @VisibleForTesting
+    protected int mRoundedDefault;
+    @VisibleForTesting
+    protected int mRoundedDefaultTop;
+    @VisibleForTesting
+    protected int mRoundedDefaultBottom;
     private View mOverlay;
     private View mBottomOverlay;
     private float mDensity;
@@ -111,6 +123,8 @@
     private SecureSetting mColorInversionSetting;
     private boolean mPendingRotationChange;
     private Handler mHandler;
+    private boolean mAssistHintBlocked = false;
+    private boolean mIsReceivingNavBarColor = false;
 
     /**
      * Converts a set of {@link Rect}s into a {@link Region}
@@ -137,15 +151,32 @@
         putComponent(ScreenDecorations.class, this);
     }
 
-    private void fade(View view, boolean fadeIn) {
+    private void fade(View view, boolean fadeIn, boolean isLeft) {
         if (fadeIn) {
             view.animate().cancel();
-            view.setAlpha(0f);
+            view.setAlpha(1f);
             view.setVisibility(View.VISIBLE);
-            view.animate().alpha(1f);
+
+            AnimationSet anim = new AnimationSet(true);
+            anim.setDuration(900);
+
+            Animation scaleAnimation = new ScaleAnimation(2f, 1f, 2f, 1f,
+                    ScaleAnimation.RELATIVE_TO_SELF, 0.5f, ScaleAnimation.RELATIVE_TO_SELF, 0.5f);
+            anim.addAnimation(scaleAnimation);
+            anim.setInterpolator(new PathInterpolator(0.02f, 0.44f, 0.67f, 1.00f));
+
+            Animation translateAnimation = new TranslateAnimation(
+                    TranslateAnimation.RELATIVE_TO_SELF, isLeft ? -0.2f : 0.2f,
+                    TranslateAnimation.RELATIVE_TO_SELF,
+                    0f,
+                    TranslateAnimation.RELATIVE_TO_SELF, 0.2f, TranslateAnimation.RELATIVE_TO_SELF,
+                    0f);
+            anim.addAnimation(translateAnimation);
+            anim.setInterpolator(new OvershootInterpolator());
+            view.startAnimation(anim);
         } else {
             view.animate().cancel();
-            view.animate().alpha(0f).withEndAction(() -> view.setVisibility(View.INVISIBLE));
+            view.animate().setDuration(400).alpha(0f);
         }
 
     }
@@ -161,35 +192,59 @@
             return;
         }
 
+        if (mAssistHintBlocked && visible) {
+            if (VERBOSE) {
+                Log.v(TAG, "Assist hint blocked, cannot make it visible");
+            }
+            return;
+        }
+
         if (mAssistHintVisible != visible) {
             mAssistHintVisible = visible;
 
-            View assistHintTopLeft = mOverlay.findViewById(R.id.assist_hint_left);
-            View assistHintTopRight = mOverlay.findViewById(R.id.assist_hint_right);
-            View assistHintBottomLeft = mBottomOverlay.findViewById(R.id.assist_hint_left);
-            View assistHintBottomRight = mBottomOverlay.findViewById(R.id.assist_hint_right);
+            CornerHandleView assistHintTopLeft = mOverlay.findViewById(R.id.assist_hint_left);
+            CornerHandleView assistHintTopRight = mOverlay.findViewById(R.id.assist_hint_right);
+            CornerHandleView assistHintBottomLeft = mBottomOverlay.findViewById(
+                    R.id.assist_hint_left);
+            CornerHandleView assistHintBottomRight = mBottomOverlay.findViewById(
+                    R.id.assist_hint_right);
 
             switch (mRotation) {
                 case RotationUtils.ROTATION_NONE:
-                    fade(assistHintBottomLeft, mAssistHintVisible);
-                    fade(assistHintBottomRight, mAssistHintVisible);
+                    fade(assistHintBottomLeft, mAssistHintVisible, /* isLeft = */ true);
+                    fade(assistHintBottomRight, mAssistHintVisible, /* isLeft = */ false);
                     break;
                 case RotationUtils.ROTATION_LANDSCAPE:
-                    fade(assistHintTopRight, mAssistHintVisible);
-                    fade(assistHintBottomRight, mAssistHintVisible);
+                    fade(assistHintTopRight, mAssistHintVisible, /* isLeft = */ true);
+                    fade(assistHintBottomRight, mAssistHintVisible, /* isLeft = */ false);
                     break;
                 case RotationUtils.ROTATION_SEASCAPE:
-                    fade(assistHintTopLeft, mAssistHintVisible);
-                    fade(assistHintBottomLeft, mAssistHintVisible);
+                    fade(assistHintTopLeft, mAssistHintVisible, /* isLeft = */ false);
+                    fade(assistHintBottomLeft, mAssistHintVisible,  /* isLeft = */ true);
                     break;
                 case RotationUtils.ROTATION_UPSIDE_DOWN:
-                    fade(assistHintTopLeft, mAssistHintVisible);
-                    fade(assistHintTopRight, mAssistHintVisible);
+                    fade(assistHintTopLeft, mAssistHintVisible, /* isLeft = */ false);
+                    fade(assistHintTopRight, mAssistHintVisible, /* isLeft = */ true);
                     break;
             }
         }
     }
 
+    /**
+     * Prevents the assist hint from becoming visible even if `mAssistHintVisible` is true.
+     */
+    public void setAssistHintBlocked(boolean blocked) {
+        if (!mHandler.getLooper().isCurrentThread()) {
+            mHandler.post(() -> setAssistHintBlocked(blocked));
+            return;
+        }
+
+        mAssistHintBlocked = blocked;
+        if (mAssistHintVisible && mAssistHintBlocked) {
+            setAssistHintVisible(false);
+        }
+    }
+
     @VisibleForTesting
     Handler startHandlerThread() {
         HandlerThread thread = new HandlerThread("ScreenDecorations");
@@ -253,12 +308,12 @@
                 .inflate(R.layout.rounded_corners, null);
         mCutoutTop = new DisplayCutoutView(mContext, true,
                 this::updateWindowVisibilities, this);
-        ((ViewGroup)mOverlay).addView(mCutoutTop);
+        ((ViewGroup) mOverlay).addView(mCutoutTop);
         mBottomOverlay = LayoutInflater.from(mContext)
                 .inflate(R.layout.rounded_corners, null);
         mCutoutBottom = new DisplayCutoutView(mContext, false,
                 this::updateWindowVisibilities, this);
-        ((ViewGroup)mBottomOverlay).addView(mCutoutBottom);
+        ((ViewGroup) mBottomOverlay).addView(mCutoutBottom);
 
         mOverlay.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
         mOverlay.setAlpha(0);
@@ -378,10 +433,26 @@
             if (mOverlay != null) {
                 updateLayoutParams();
                 updateViews();
+                if (mAssistHintVisible) {
+                    // If assist handles are visible, hide them without animation and then make them
+                    // show once again (with corrected rotation).
+                    hideAssistHandles();
+                    setAssistHintVisible(true);
+                }
             }
         }
     }
 
+    private void hideAssistHandles() {
+        if (mOverlay != null && mBottomOverlay != null) {
+            mOverlay.findViewById(R.id.assist_hint_left).setVisibility(View.GONE);
+            mOverlay.findViewById(R.id.assist_hint_right).setVisibility(View.GONE);
+            mBottomOverlay.findViewById(R.id.assist_hint_left).setVisibility(View.GONE);
+            mBottomOverlay.findViewById(R.id.assist_hint_right).setVisibility(View.GONE);
+            mAssistHintVisible = false;
+        }
+    }
+
     private void updateRoundedCornerRadii() {
         final int newRoundedDefault = mContext.getResources().getDimensionPixelSize(
                 com.android.internal.R.dimen.rounded_corner_radius);
@@ -416,7 +487,7 @@
         } else if (mRotation == RotationUtils.ROTATION_LANDSCAPE) {
             updateView(topLeft, Gravity.TOP | Gravity.LEFT, 0);
             updateView(topRight, Gravity.BOTTOM | Gravity.LEFT, 270);
-            updateView(bottomLeft, Gravity.TOP | Gravity.RIGHT, 90);;
+            updateView(bottomLeft, Gravity.TOP | Gravity.RIGHT, 90);
             updateView(bottomRight, Gravity.BOTTOM | Gravity.RIGHT, 180);
         } else if (mRotation == RotationUtils.ROTATION_UPSIDE_DOWN) {
             updateView(topLeft, Gravity.BOTTOM | Gravity.LEFT, 270);
@@ -477,7 +548,7 @@
     }
 
     private void updateView(View v, int gravity, int rotation) {
-        ((FrameLayout.LayoutParams)v.getLayoutParams()).gravity = gravity;
+        ((FrameLayout.LayoutParams) v.getLayoutParams()).gravity = gravity;
         v.setRotation(rotation);
     }
 
@@ -613,6 +684,10 @@
                 setSize(mOverlay.findViewById(R.id.right), sizeTop);
                 setSize(mBottomOverlay.findViewById(R.id.left), sizeBottom);
                 setSize(mBottomOverlay.findViewById(R.id.right), sizeBottom);
+                setSize(mOverlay.findViewById(R.id.assist_hint_left), sizeTop * 2);
+                setSize(mOverlay.findViewById(R.id.assist_hint_right), sizeTop * 2);
+                setSize(mBottomOverlay.findViewById(R.id.assist_hint_left), sizeBottom * 2);
+                setSize(mBottomOverlay.findViewById(R.id.assist_hint_right), sizeBottom * 2);
             }
         });
     }
@@ -624,6 +699,27 @@
         view.setLayoutParams(params);
     }
 
+    @Override
+    public void onDarkIntensity(float darkIntensity) {
+        if (mOverlay != null) {
+            CornerHandleView assistHintTopLeft = mOverlay.findViewById(R.id.assist_hint_left);
+            CornerHandleView assistHintTopRight = mOverlay.findViewById(R.id.assist_hint_right);
+
+            assistHintTopLeft.updateDarkness(darkIntensity);
+            assistHintTopRight.updateDarkness(darkIntensity);
+        }
+
+        if (mBottomOverlay != null) {
+            CornerHandleView assistHintBottomLeft = mBottomOverlay.findViewById(
+                    R.id.assist_hint_left);
+            CornerHandleView assistHintBottomRight = mBottomOverlay.findViewById(
+                    R.id.assist_hint_right);
+
+            assistHintBottomLeft.updateDarkness(darkIntensity);
+            assistHintBottomRight.updateDarkness(darkIntensity);
+        }
+    }
+
     @VisibleForTesting
     static class TunablePaddingTagListener implements FragmentListener {
 
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java
index 0203d9e..ff508ad 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java
@@ -24,9 +24,13 @@
 import android.os.Handler;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.provider.DeviceConfig;
 import android.util.Log;
 
+import androidx.annotation.Nullable;
+
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.systemui.Dependency;
 import com.android.systemui.ScreenDecorations;
 import com.android.systemui.SysUiServiceProvider;
@@ -46,6 +50,7 @@
 public final class AssistHandleBehaviorController implements AssistHandleCallbacks {
 
     private static final String TAG = "AssistHandleBehavior";
+
     private static final boolean IS_DEBUG_DEVICE =
             Build.TYPE.toLowerCase(Locale.ROOT).contains("debug")
                     || Build.TYPE.toLowerCase(Locale.ROOT).equals("eng");
@@ -87,6 +92,19 @@
                 Dependency.get(NavigationModeController.class)
                         .addListener(this::handleNavigationModeChange));
 
+        setBehavior(DeviceConfig.getString(
+                DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.ASSIST_HANDLES_BEHAVIOR_MODE,
+                mCurrentBehavior.toString()));
+        DeviceConfig.addOnPropertyChangedListener(
+                DeviceConfig.NAMESPACE_SYSTEMUI,
+                mHandler::post,
+                (namespace, name, value) -> {
+                    if (SystemUiDeviceConfigFlags.ASSIST_HANDLES_BEHAVIOR_MODE.equals(name)) {
+                        setBehavior(value);
+                    }
+                });
+
         if (IS_DEBUG_DEVICE) {
             context.registerReceiver(new BroadcastReceiver() {
                 @Override
@@ -136,13 +154,29 @@
         mCurrentBehavior = behavior;
     }
 
-    private static long getShownFrequencyThreshold() {
-        return SystemProperties.getLong(
-                SHOWN_FREQUENCY_THRESHOLD_KEY, DEFAULT_SHOWN_FREQUENCY_THRESHOLD_MS);
+    private void setBehavior(@Nullable String behavior) {
+        try {
+            setBehavior(AssistHandleBehavior.valueOf(behavior));
+        } catch (IllegalArgumentException | NullPointerException e) {
+            Log.e(TAG, "Invalid behavior: " + behavior, e);
+        }
     }
 
-    private static long getShowAndGoDuration() {
-        return SystemProperties.getLong(SHOW_AND_GO_DURATION_KEY, DEFAULT_SHOW_AND_GO_DURATION_MS);
+    private long getShownFrequencyThreshold() {
+        long configValue = DeviceConfig.getLong(
+                DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOWN_FREQUENCY_THRESHOLD_MS,
+                DEFAULT_SHOWN_FREQUENCY_THRESHOLD_MS);
+        return SystemProperties.getLong(
+                SHOWN_FREQUENCY_THRESHOLD_KEY, configValue);
+    }
+
+    private long getShowAndGoDuration() {
+        long configValue = DeviceConfig.getLong(
+                DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOW_AND_GO_DURATION_MS,
+                DEFAULT_SHOW_AND_GO_DURATION_MS);
+        return SystemProperties.getLong(SHOW_AND_GO_DURATION_KEY, configValue);
     }
 
     private void maybeShowHandles(boolean ignoreThreshold) {
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleLikeHomeBehavior.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleLikeHomeBehavior.java
index e89e93c..05e504c 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleLikeHomeBehavior.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleLikeHomeBehavior.java
@@ -16,17 +16,15 @@
 
 package com.android.systemui.assist;
 
-import android.app.StatusBarManager;
 import android.content.Context;
 
 import androidx.annotation.Nullable;
 
 import com.android.systemui.Dependency;
-import com.android.systemui.SysUiServiceProvider;
 import com.android.systemui.assist.AssistHandleBehaviorController.BehaviorController;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.NavigationBarController;
-import com.android.systemui.statusbar.phone.NavigationBarFragment;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.shared.system.QuickStepContract;
 
 /**
  * Assistant Handle behavior that makes Assistant handles show/hide when the home handle is
@@ -34,47 +32,68 @@
  */
 final class AssistHandleLikeHomeBehavior implements BehaviorController {
 
-    private final CommandQueue.Callbacks mCallbacks = new CommandQueue.Callbacks() {
+    private final StatusBarStateController.StateListener mStatusBarStateListener =
+            new StatusBarStateController.StateListener() {
+                @Override
+                public void onDozingChanged(boolean isDozing) {
+                    handleDozingChanged(isDozing);
+                }
+            };
+    private final OverviewProxyService.OverviewProxyListener mOverviewProxyListener =
+            new OverviewProxyService.OverviewProxyListener() {
         @Override
-        public void setWindowState(int displayId, int window, int state) {
-            if (mNavBarDisplayId == displayId
-                    && window == StatusBarManager.WINDOW_NAVIGATION_BAR) {
-                handleWindowStateChanged(state);
-            }
+        public void onSystemUiStateChanged(int sysuiStateFlags) {
+            handleSystemUiStateChange(sysuiStateFlags);
         }
     };
+    private final StatusBarStateController mStatusBarStateController;
+    private final OverviewProxyService mOverviewProxyService;
 
-    private CommandQueue mCommandQueue;
-    private int mNavBarDisplayId;
-    private boolean mIsNavBarWindowVisible;
+    private boolean mIsDozing;
+    private boolean mIsHomeHandleHiding;
 
     @Nullable private AssistHandleCallbacks mAssistHandleCallbacks;
 
+    AssistHandleLikeHomeBehavior() {
+        mStatusBarStateController = Dependency.get(StatusBarStateController.class);
+        mOverviewProxyService = Dependency.get(OverviewProxyService.class);
+    }
+
     @Override
     public void onModeActivated(Context context, AssistHandleCallbacks callbacks) {
         mAssistHandleCallbacks = callbacks;
-        NavigationBarFragment navigationBarFragment =
-                Dependency.get(NavigationBarController.class).getDefaultNavigationBarFragment();
-        mNavBarDisplayId = navigationBarFragment.mDisplayId;
-        mIsNavBarWindowVisible = navigationBarFragment.isNavBarWindowVisible();
-        mCommandQueue = SysUiServiceProvider.getComponent(context, CommandQueue.class);
-        mCommandQueue.addCallback(mCallbacks);
+        mIsDozing = mStatusBarStateController.isDozing();
+        mStatusBarStateController.addCallback(mStatusBarStateListener);
+        mOverviewProxyService.addCallback(mOverviewProxyListener);
         callbackForCurrentState();
     }
 
     @Override
     public void onModeDeactivated() {
         mAssistHandleCallbacks = null;
-        mCommandQueue.removeCallback(mCallbacks);
+        mOverviewProxyService.removeCallback(mOverviewProxyListener);
     }
 
-    private void handleWindowStateChanged(int state) {
-        boolean newVisibility = state == StatusBarManager.WINDOW_STATE_SHOWING;
-        if (mIsNavBarWindowVisible == newVisibility) {
+    private static boolean isHomeHandleHiding(int sysuiStateFlags) {
+        return (sysuiStateFlags & QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN) != 0;
+    }
+
+    private void handleDozingChanged(boolean isDozing) {
+        if (mIsDozing == isDozing) {
             return;
         }
 
-        mIsNavBarWindowVisible = newVisibility;
+        mIsDozing = isDozing;
+        callbackForCurrentState();
+    }
+
+    private void handleSystemUiStateChange(int sysuiStateFlags) {
+        boolean isHomeHandleHiding = isHomeHandleHiding(sysuiStateFlags);
+        if (mIsHomeHandleHiding == isHomeHandleHiding) {
+            return;
+        }
+
+        mIsHomeHandleHiding = isHomeHandleHiding;
         callbackForCurrentState();
     }
 
@@ -83,10 +102,10 @@
             return;
         }
 
-        if (mIsNavBarWindowVisible) {
-            mAssistHandleCallbacks.showAndStay();
-        } else {
+        if (mIsHomeHandleHiding || mIsDozing) {
             mAssistHandleCallbacks.hide();
+        } else {
+            mAssistHandleCallbacks.showAndStay();
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java
index 4a5e0e8..93e4886 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java
@@ -16,22 +16,19 @@
 
 package com.android.systemui.assist;
 
+import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.Context;
-import android.graphics.Rect;
-import android.view.View;
-import android.view.WindowManager;
 
 import androidx.annotation.Nullable;
 
 import com.android.systemui.Dependency;
-import com.android.systemui.SysUiServiceProvider;
 import com.android.systemui.assist.AssistHandleBehaviorController.BehaviorController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.TaskStackChangeListener;
-import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.StatusBarState;
 
 /**
@@ -65,53 +62,45 @@
                     handleTaskStackTopChanged(taskId);
                 }
             };
-    private final CommandQueue.Callbacks mCallbacks = new CommandQueue.Callbacks() {
-        @Override
-        public void setSystemUiVisibility(int displayId, int vis,
-                int fullscreenStackVis, int dockedStackVis, int mask,
-                Rect fullscreenStackBounds, Rect dockedStackBounds,
-                boolean navbarColorManagedByIme) {
-            if (mStatusBarDisplayId == displayId) {
-                handleSystemUiVisibilityChange(vis, mask);
-            }
-        }
-    };
     private final OverviewProxyService.OverviewProxyListener mOverviewProxyListener =
             new OverviewProxyService.OverviewProxyListener() {
                 @Override
                 public void onOverviewShown(boolean fromHome) {
                     handleOverviewShown();
                 }
+
+                @Override
+                public void onSystemUiStateChanged(int sysuiStateFlags) {
+                    handleSystemUiStateChanged(sysuiStateFlags);
+                }
             };
 
-    private StatusBarStateController mStatusBarStateController;
-    private ActivityManagerWrapper mActivityManagerWrapper;
-    private OverviewProxyService mOverviewProxyService;
-    private int mStatusBarDisplayId;
-    private CommandQueue mCommandQueue;
+    private final StatusBarStateController mStatusBarStateController;
+    private final ActivityManagerWrapper mActivityManagerWrapper;
+    private final OverviewProxyService mOverviewProxyService;
+
     private boolean mOnLockscreen;
     private boolean mIsDozing;
     private int mRunningTaskId;
-    private boolean mIsImmersive;
+    private boolean mIsNavBarHidden;
 
     @Nullable private AssistHandleCallbacks mAssistHandleCallbacks;
 
+    AssistHandleReminderExpBehavior() {
+        mStatusBarStateController = Dependency.get(StatusBarStateController.class);
+        mActivityManagerWrapper = ActivityManagerWrapper.getInstance();
+        mOverviewProxyService = Dependency.get(OverviewProxyService.class);
+    }
+
     @Override
     public void onModeActivated(Context context, AssistHandleCallbacks callbacks) {
         mAssistHandleCallbacks = callbacks;
-        mStatusBarStateController = Dependency.get(StatusBarStateController.class);
         mOnLockscreen = onLockscreen(mStatusBarStateController.getState());
         mIsDozing = mStatusBarStateController.isDozing();
         mStatusBarStateController.addCallback(mStatusBarStateListener);
-        mActivityManagerWrapper = ActivityManagerWrapper.getInstance();
-        mRunningTaskId = mActivityManagerWrapper.getRunningTask().taskId;
+        ActivityManager.RunningTaskInfo runningTaskInfo = mActivityManagerWrapper.getRunningTask();
+        mRunningTaskId = runningTaskInfo == null ? 0 : runningTaskInfo.taskId;
         mActivityManagerWrapper.registerTaskStackListener(mTaskStackChangeListener);
-        mStatusBarDisplayId =
-                ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE))
-                        .getDefaultDisplay().getDisplayId();
-        mCommandQueue = SysUiServiceProvider.getComponent(context, CommandQueue.class);
-        mCommandQueue.addCallback(mCallbacks);
-        mOverviewProxyService = Dependency.get(OverviewProxyService.class);
         mOverviewProxyService.addCallback(mOverviewProxyListener);
         callbackForCurrentState();
     }
@@ -121,10 +110,13 @@
         mAssistHandleCallbacks = null;
         mStatusBarStateController.removeCallback(mStatusBarStateListener);
         mActivityManagerWrapper.unregisterTaskStackListener(mTaskStackChangeListener);
-        mCommandQueue.removeCallback(mCallbacks);
         mOverviewProxyService.removeCallback(mOverviewProxyListener);
     }
 
+    private static boolean isNavBarHidden(int sysuiStateFlags) {
+        return (sysuiStateFlags & QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN) != 0;
+    }
+
     private void handleStatusBarStateChanged(int newState) {
         boolean onLockscreen = onLockscreen(newState);
         if (mOnLockscreen == onLockscreen) {
@@ -153,13 +145,13 @@
         callbackForCurrentState();
     }
 
-    private void handleSystemUiVisibilityChange(int vis, int mask) {
-        boolean isImmersive = isImmersive(vis, mask);
-        if (mIsImmersive == isImmersive) {
+    private void handleSystemUiStateChanged(int sysuiStateFlags) {
+        boolean isNavBarHidden = isNavBarHidden(sysuiStateFlags);
+        if (mIsNavBarHidden == isNavBarHidden) {
             return;
         }
 
-        mIsImmersive = isImmersive;
+        mIsNavBarHidden = isNavBarHidden;
         callbackForCurrentState();
     }
 
@@ -172,17 +164,12 @@
                 || statusBarState == StatusBarState.SHADE_LOCKED;
     }
 
-    private boolean isImmersive(int vis, int mask) {
-        return ((vis & mask)
-                & (View.SYSTEM_UI_FLAG_IMMERSIVE | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)) != 0;
-    }
-
     private void callbackForCurrentState() {
         if (mAssistHandleCallbacks == null) {
             return;
         }
 
-        if (mIsDozing || mIsImmersive) {
+        if (mIsDozing || mIsNavBarHidden) {
             mAssistHandleCallbacks.hide();
         } else if (mOnLockscreen) {
             mAssistHandleCallbacks.showAndStay();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 35b8d203..d4c7366 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -56,6 +56,8 @@
 import com.android.systemui.statusbar.policy.NextAlarmControllerImpl;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.statusbar.policy.ZenModeControllerImpl;
+import com.android.systemui.util.wakelock.SettableWakeLock;
+import com.android.systemui.util.wakelock.WakeLock;
 
 import java.util.Date;
 import java.util.HashSet;
@@ -101,6 +103,8 @@
     private final Handler mHandler;
     private final AlarmManager.OnAlarmListener mUpdateNextAlarm = this::updateNextAlarm;
     private final HashSet<Integer> mMediaInvisibleStates;
+    private final Object mMediaToken = new Object();
+    private SettableWakeLock mMediaWakeLock;
     private ZenModeController mZenModeController;
     private String mDatePattern;
     private DateFormat mDateFormat;
@@ -114,7 +118,8 @@
     private PendingIntent mPendingIntent;
     protected NotificationMediaManager mMediaManager;
     private StatusBarStateController mStatusBarStateController;
-    protected MediaMetadata mMediaMetaData;
+    private CharSequence mMediaTitle;
+    private CharSequence mMediaArtist;
     protected boolean mDozing;
     private boolean mMediaIsVisible;
 
@@ -218,24 +223,18 @@
     }
 
     protected boolean needsMediaLocked() {
-        return mMediaMetaData != null && mMediaIsVisible && mDozing;
+        return !TextUtils.isEmpty(mMediaTitle) && mMediaIsVisible && mDozing;
     }
 
     protected void addMediaLocked(ListBuilder listBuilder) {
-        if (mMediaMetaData == null) {
+        if (TextUtils.isEmpty(mMediaTitle)) {
             return;
         }
+        listBuilder.setHeader(new ListBuilder.HeaderBuilder(mHeaderUri).setTitle(mMediaTitle));
 
-        CharSequence title = mMediaMetaData.getText(MediaMetadata.METADATA_KEY_TITLE);
-        if (TextUtils.isEmpty(title)) {
-            title = getContext().getResources().getString(R.string.music_controls_no_title);
-        }
-        listBuilder.setHeader(new ListBuilder.HeaderBuilder(mHeaderUri).setTitle(title));
-
-        CharSequence album = mMediaMetaData.getText(MediaMetadata.METADATA_KEY_ARTIST);
-        if (!TextUtils.isEmpty(album)) {
+        if (!TextUtils.isEmpty(mMediaArtist)) {
             RowBuilder albumBuilder = new RowBuilder(mMediaUri);
-            albumBuilder.setTitle(album);
+            albumBuilder.setTitle(mMediaArtist);
 
             Icon mediaIcon = mMediaManager == null ? null : mMediaManager.getMediaIcon();
             IconCompat mediaIconCompat = mediaIcon == null ? null
@@ -306,6 +305,8 @@
         mZenModeController.addCallback(this);
         mDatePattern = getContext().getString(R.string.system_ui_aod_date_pattern);
         mPendingIntent = PendingIntent.getActivity(getContext(), 0, new Intent(), 0);
+        mMediaWakeLock = new SettableWakeLock(WakeLock.createPartial(getContext(), "media"),
+                "media");
         KeyguardSliceProvider.sInstance = this;
         registerClockUpdate();
         updateClockLocked();
@@ -425,12 +426,42 @@
     public void onMetadataOrStateChanged(MediaMetadata metadata, @PlaybackState.State int state) {
         synchronized (this) {
             boolean nextVisible = !mMediaInvisibleStates.contains(state);
-            if (nextVisible == mMediaIsVisible && metadata == mMediaMetaData) {
-                return;
+            mHandler.removeCallbacksAndMessages(mMediaToken);
+            if (mMediaIsVisible && !nextVisible) {
+                // We need to delay this event for a few millis when stopping to avoid jank in the
+                // animation. The media app might not send its update when buffering, and the slice
+                // would end up without a header for 0.5 second.
+                mMediaWakeLock.setAcquired(true);
+                mHandler.postDelayed(() -> {
+                    updateMediaStateLocked(metadata, state);
+                    mMediaWakeLock.setAcquired(false);
+                }, mMediaToken, 2000);
+            } else {
+                mMediaWakeLock.setAcquired(false);
+                updateMediaStateLocked(metadata, state);
             }
-            mMediaMetaData = metadata;
-            mMediaIsVisible = nextVisible;
         }
+    }
+
+    private void updateMediaStateLocked(MediaMetadata metadata, @PlaybackState.State int state) {
+        boolean nextVisible = !mMediaInvisibleStates.contains(state);
+        CharSequence title = null;
+        if (metadata != null) {
+            title = metadata.getText(MediaMetadata.METADATA_KEY_TITLE);
+            if (TextUtils.isEmpty(title)) {
+                title = getContext().getResources().getString(R.string.music_controls_no_title);
+            }
+        }
+        CharSequence artist = metadata == null ? null : metadata.getText(
+                MediaMetadata.METADATA_KEY_ARTIST);
+
+        if (nextVisible == mMediaIsVisible && TextUtils.equals(title, mMediaTitle)
+                && TextUtils.equals(artist, mMediaArtist)) {
+            return;
+        }
+        mMediaTitle = title;
+        mMediaArtist = artist;
+        mMediaIsVisible = nextVisible;
         notifyChange();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index c65e90e..7c193b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -65,7 +65,7 @@
     private val mDozeParameters: DozeParameters;
     var willWakeUp = false
         set(value) {
-            if (value && mDozeAmount != 0.0f) {
+            if (!value || mDozeAmount != 0.0f) {
                 field = value
             }
         }
@@ -228,7 +228,7 @@
                     // if we animate, we see the shelf briefly visible. Instead we fully animate
                     // the notification and its background out
                     animate = false
-                } else {
+                } else if (!mWakingUp && !willWakeUp){
                     entry.setAmbientGoingAway(true)
                     mEntrySetToClearWhenFinished.add(entry)
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index dbc9a8b..d625b31 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -648,6 +648,9 @@
         if (!getShowingLayout().isDimmable()) {
             return false;
         }
+        if (showingAmbientPulsing()) {
+            return false;
+        }
         return super.isDimmable();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index ecbb216..d911e1a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.notification.row;
 
+import static android.provider.Settings.Secure.SHOW_NOTIFICATION_SNOOZE;
+
 import static com.android.systemui.SwipeHelper.SWIPED_FAR_ENOUGH_SIZE_FRACTION;
 
 import android.animation.Animator;
@@ -29,6 +31,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.os.Looper;
+import android.provider.Settings;
 import android.service.notification.StatusBarNotification;
 import android.util.ArrayMap;
 import android.view.LayoutInflater;
@@ -255,9 +258,13 @@
         mVertSpaceForIcons = res.getDimensionPixelSize(R.dimen.notification_min_height);
         mLeftMenuItems.clear();
         mRightMenuItems.clear();
+
+        boolean showSnooze = Settings.Secure.getInt(mContext.getContentResolver(),
+                SHOW_NOTIFICATION_SNOOZE, 0) == 1;
+
         // Construct the menu items based on the notification
-        if (!isForeground) {
-            // Only show snooze for non-foreground notifications
+        if (!isForeground && showSnooze) {
+            // Only show snooze for non-foreground notifications, and if the setting is on
             mSnoozeItem = createSnoozeItem(mContext);
         }
         mAppOpsItem = createAppOpsItem(mContext);
@@ -268,7 +275,7 @@
         }
 
         if (!mIsUsingBidirectionalSwipe) {
-            if (!isForeground) {
+            if (!isForeground && showSnooze) {
                 mRightMenuItems.add(mSnoozeItem);
             }
             mRightMenuItems.add(mInfoItem);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
index 91c43a1..ee2dacd6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
@@ -25,6 +25,7 @@
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
 import android.media.session.PlaybackState;
+import android.metrics.LogMaker;
 import android.os.Handler;
 import android.text.format.DateUtils;
 import android.util.Log;
@@ -35,6 +36,9 @@
 import android.widget.TextView;
 
 import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.Dependency;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.TransformableView;
@@ -62,8 +66,11 @@
     private NotificationMediaManager mMediaManager;
     private View mSeekBarView;
     private Context mContext;
+    private MetricsLogger mMetricsLogger;
 
-    private SeekBar.OnSeekBarChangeListener mSeekListener = new SeekBar.OnSeekBarChangeListener() {
+    @VisibleForTesting
+    protected SeekBar.OnSeekBarChangeListener mSeekListener =
+            new SeekBar.OnSeekBarChangeListener() {
         @Override
         public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
         }
@@ -76,6 +83,7 @@
         public void onStopTrackingTouch(SeekBar seekBar) {
             if (mMediaController != null && canSeekMedia()) {
                 mMediaController.getTransportControls().seekTo(mSeekBar.getProgress());
+                mMetricsLogger.write(newLog(MetricsEvent.TYPE_UPDATE));
             }
         }
     };
@@ -93,7 +101,8 @@
                 // Update the UI once, in case playback info changed while we were paused
                 mUpdatePlaybackUi.run();
                 clearTimer();
-            } else if (mSeekBarTimer == null) {
+            } else if (mSeekBarTimer == null && mSeekBarView != null
+                    && mSeekBarView.getVisibility() != View.GONE) {
                 startTimer();
             }
         }
@@ -104,6 +113,7 @@
         super(ctx, view, row);
         mContext = ctx;
         mMediaManager = Dependency.get(NotificationMediaManager.class);
+        mMetricsLogger = Dependency.get(MetricsLogger.class);
     }
 
     private void resolveViews() {
@@ -121,11 +131,13 @@
         }
 
         // Check for existing media controller and clean up / create as necessary
+        boolean controllerUpdated = false;
         if (mMediaController == null || !mMediaController.getSessionToken().equals(token)) {
             if (mMediaController != null) {
                 mMediaController.unregisterCallback(mMediaCallback);
             }
             mMediaController = new MediaController(mContext, token);
+            controllerUpdated = true;
         }
 
         if (mMediaController.getMetadata() != null) {
@@ -134,14 +146,21 @@
             if (duration <= 0) {
                 // Don't include the seekbar if this is a livestream
                 Log.d(TAG, "removing seekbar");
-                if (mSeekBarView != null) {
+                if (mSeekBarView != null && mSeekBarView.getVisibility() != View.GONE) {
                     mSeekBarView.setVisibility(View.GONE);
+                    mMetricsLogger.write(newLog(MetricsEvent.TYPE_CLOSE));
+                    clearTimer();
+                } else if (mSeekBarView == null && controllerUpdated) {
+                    // Only log if the controller changed, otherwise we would log multiple times for
+                    // the same notification when user pauses/resumes
+                    mMetricsLogger.write(newLog(MetricsEvent.TYPE_CLOSE));
                 }
                 return;
             } else {
                 // Otherwise, make sure the seekbar is visible
-                if (mSeekBarView != null) {
+                if (mSeekBarView != null && mSeekBarView.getVisibility() == View.GONE) {
                     mSeekBarView.setVisibility(View.VISIBLE);
+                    mMetricsLogger.write(newLog(MetricsEvent.TYPE_OPEN));
                 }
             }
         }
@@ -153,6 +172,7 @@
             stub.setLayoutInflater(layoutInflater);
             stub.setLayoutResource(R.layout.notification_material_media_seekbar);
             mSeekBarView = stub.inflate();
+            mMetricsLogger.write(newLog(MetricsEvent.TYPE_OPEN));
 
             mSeekBar = mSeekBarView.findViewById(R.id.notification_media_progress_bar);
             mSeekBar.setOnSeekBarChangeListener(mSeekListener);
@@ -161,17 +181,14 @@
             mSeekBarTotalTime = mSeekBarView.findViewById(R.id.notification_media_total_time);
 
             if (mSeekBarTimer == null) {
-                // Disable seeking if it is not supported for this media session
-                if (!canSeekMedia()) {
-                    mSeekBar.getThumb().setAlpha(0);
-                    mSeekBar.setEnabled(false);
+                if (canSeekMedia()) {
+                    // Log initial state, since it will not be updated
+                    mMetricsLogger.write(newLog(MetricsEvent.TYPE_DETAIL, 1));
                 } else {
-                    mSeekBar.getThumb().setAlpha(255);
-                    mSeekBar.setEnabled(true);
+                    setScrubberVisible(false);
                 }
 
                 startTimer();
-
                 mMediaController.registerCallback(mMediaCallback);
             }
         }
@@ -209,6 +226,16 @@
         return ((actions & PlaybackState.ACTION_SEEK_TO) != 0);
     }
 
+    private void setScrubberVisible(boolean isVisible) {
+        if (mSeekBar == null || mSeekBar.isEnabled() == isVisible) {
+            return;
+        }
+
+        mSeekBar.getThumb().setAlpha(isVisible ? 255 : 0);
+        mSeekBar.setEnabled(isVisible);
+        mMetricsLogger.write(newLog(MetricsEvent.TYPE_DETAIL, isVisible ? 1 : 0));
+    }
+
     protected final Runnable mUpdatePlaybackUi = new Runnable() {
         @Override
         public void run() {
@@ -228,6 +255,9 @@
                     mSeekBar.setProgress((int) position);
 
                     mSeekBarElapsedTime.setText(millisecondsToTimeString(position));
+
+                    // Update scrubber in case available actions have changed
+                    setScrubberVisible(canSeekMedia());
                 } else {
                     Log.d(TAG, "Controller missing data " + metadata + " " + playbackState);
                     clearTimer();
@@ -293,4 +323,28 @@
     public boolean shouldClipToRounding(boolean topRounded, boolean bottomRounded) {
         return true;
     }
+
+    /**
+     * Returns an initialized LogMaker for logging changes to the seekbar
+     * @return new LogMaker
+     */
+    private LogMaker newLog(int event) {
+        String packageName = mRow.getEntry().notification.getPackageName();
+
+        return new LogMaker(MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR)
+                .setType(event)
+                .setPackageName(packageName);
+    }
+
+    /**
+     * Returns an initialized LogMaker for logging changes with subtypes
+     * @return new LogMaker
+     */
+    private LogMaker newLog(int event, int subtype) {
+        String packageName = mRow.getEntry().notification.getPackageName();
+        return new LogMaker(MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR)
+                .setType(event)
+                .setSubtype(subtype)
+                .setPackageName(packageName);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index e5fbf63..78dc9a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -212,7 +212,9 @@
     }
 
     public boolean isDimmed() {
-        return mDimmed;
+        // While we are expanding from pulse, we want the notifications not to be dimmed, otherwise
+        // you'd see the difference to the pulsing notification
+        return mDimmed && !(isPulseExpanding() && mDozeAmount == 1.0f);
     }
 
     public boolean isDark() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index dccf404..a517e76 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -5709,6 +5709,7 @@
                 view.setTranslationY(wakeUplocation);
             }
         }
+        mDimmedNeedsAnimation = true;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java
index 215aa77..a79b625 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.phone;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.PixelFormat;
 import android.view.ContextThemeWrapper;
 import android.view.Gravity;
@@ -35,6 +36,8 @@
     private final Context mContext;
     private final WindowManager mWindowManager;
     private final KeyButtonView mKeyButtonView;
+    private final int mDiameter;
+    private final int mMargin;
     private KeyButtonDrawable mKeyButtonDrawable;
     private boolean mIsShowing;
     private boolean mCanShow = true;
@@ -46,6 +49,11 @@
         mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
         mKeyButtonView = (KeyButtonView) LayoutInflater.from(mContext).inflate(
                 R.layout.rotate_suggestion, null);
+        mKeyButtonView.setVisibility(View.VISIBLE);
+
+        Resources resources = mContext.getResources();
+        mDiameter = resources.getDimensionPixelSize(R.dimen.floating_rotation_button_diameter);
+        mMargin = resources.getDimensionPixelSize(R.dimen.floating_rotation_button_margin);
     }
 
     @Override
@@ -66,11 +74,8 @@
         mIsShowing = true;
         int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
-        float density = mContext.getResources().getDisplayMetrics().density;
-        int diameter = (int) density * 48;
-        int margin = (int) density * 4;
-        final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(diameter, diameter,
-                margin, margin, WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, flags,
+        final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(mDiameter, mDiameter,
+                mMargin, mMargin, WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, flags,
                 PixelFormat.TRANSLUCENT);
         lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
         lp.setTitle("FloatingRotationButton");
@@ -131,7 +136,10 @@
 
     @Override
     public void setOnClickListener(View.OnClickListener onClickListener) {
-        mKeyButtonView.setOnClickListener(onClickListener);
+        mKeyButtonView.setOnClickListener(view -> {
+            hide();
+            onClickListener.onClick(view);
+        });
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 337c6b1..d94a335 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -86,6 +86,7 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.ScreenDecorations;
 import com.android.systemui.SysUiServiceProvider;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.fragments.FragmentHostManager;
@@ -170,6 +171,7 @@
     public int mDisplayId;
     private boolean mIsOnDefaultDisplay;
     public boolean mHomeBlockedThisTouch;
+    private ScreenDecorations mScreenDecorations;
 
     private Handler mHandler = Dependency.get(Dependency.MAIN_HANDLER);
 
@@ -348,12 +350,17 @@
             mDisabledFlags2 |= StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS;
         }
         setDisabled2Flags(mDisabledFlags2);
+
+        mScreenDecorations = SysUiServiceProvider.getComponent(getContext(),
+                ScreenDecorations.class);
+        getBarTransitions().addDarkIntensityListener(mScreenDecorations);
     }
 
     @Override
     public void onDestroyView() {
         super.onDestroyView();
         if (mNavigationBarView != null) {
+            mNavigationBarView.getBarTransitions().removeDarkIntensityListener(mScreenDecorations);
             mNavigationBarView.getBarTransitions().destroy();
             mNavigationBarView.getLightTransitionsController().destroy(getContext());
         }
@@ -1020,7 +1027,7 @@
         getBarTransitions().transitionTo(barMode, animate);
     }
 
-    private BarTransitions getBarTransitions() {
+    public NavigationBarTransitions getBarTransitions() {
         return mNavigationBarView.getBarTransitions();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
index 8a28c6f..2b5a28e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
@@ -36,9 +36,23 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 
+import java.util.ArrayList;
+import java.util.List;
+
 public final class NavigationBarTransitions extends BarTransitions implements
         LightBarTransitionsController.DarkIntensityApplier {
 
+    /**
+     * Notified when the color of nav bar elements changes.
+     */
+    public interface DarkIntensityListener {
+        /**
+         * Called when the color of nav bar elements changes.
+         * @param darkIntensity 0 is the lightest color, 1 is the darkest.
+         */
+        void onDarkIntensity(float darkIntensity);
+    }
+
     private final NavigationBarView mView;
     private final IStatusBarService mBarService;
     private final LightBarTransitionsController mLightTransitionsController;
@@ -49,6 +63,7 @@
     private boolean mAutoDim;
     private View mNavButtons;
     private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
+    private List<DarkIntensityListener> mDarkIntensityListeners;
 
     private final Handler mHandler = Handler.getMain();
     private final IWallpaperVisibilityListener mWallpaperVisibilityListener =
@@ -69,6 +84,7 @@
         mLightTransitionsController = new LightBarTransitionsController(view.getContext(), this);
         mAllowAutoDimWallpaperNotVisible = view.getContext().getResources()
                 .getBoolean(R.bool.config_navigation_bar_enable_auto_dim_no_visible_wallpaper);
+        mDarkIntensityListeners = new ArrayList();
 
         IWindowManager windowManagerService = Dependency.get(IWindowManager.class);
         try {
@@ -168,12 +184,16 @@
         applyDarkIntensity(mLightTransitionsController.getCurrentDarkIntensity());
     }
 
+    @Override
     public void applyDarkIntensity(float darkIntensity) {
         SparseArray<ButtonDispatcher> buttonDispatchers = mView.getButtonDispatchers();
         for (int i = buttonDispatchers.size() - 1; i >= 0; i--) {
             buttonDispatchers.valueAt(i).setDarkIntensity(darkIntensity);
         }
         mView.getRotationButtonController().setDarkIntensity(darkIntensity);
+        for (DarkIntensityListener listener : mDarkIntensityListeners) {
+            listener.onDarkIntensity(darkIntensity);
+        }
         if (mAutoDim) {
             applyLightsOut(false, true);
         }
@@ -190,4 +210,18 @@
     public void onNavigationModeChanged(int mode) {
         mNavBarMode = mode;
     }
+
+    /**
+     * Register {@code listener} to be notified when the color of nav bar elements changes.
+     */
+    public void addDarkIntensityListener(DarkIntensityListener listener) {
+        mDarkIntensityListeners.add(listener);
+    }
+
+    /**
+     * Remove {@code listener} from being notified when the color of nav bar elements changes.
+     */
+    public void removeDarkIntensityListener(DarkIntensityListener listener) {
+        mDarkIntensityListeners.remove(listener);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 7682e8a..8a895e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -299,7 +299,7 @@
         return mTintController;
     }
 
-    public BarTransitions getBarTransitions() {
+    public NavigationBarTransitions getBarTransitions() {
         return mBarTransitions;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java
index f1460a6..b117dec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java
@@ -66,7 +66,9 @@
 
     @Override
     public void onDestroy() {
-        mRotationButtonController.cleanUp();
+        if (mRotationButtonController != null) {
+            mRotationButtonController.cleanUp();
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
index 43ea92f..13b9d56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
@@ -15,6 +15,7 @@
 package com.android.systemui.statusbar.notification.row;
 
 import static android.provider.Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL;
+import static android.provider.Settings.Secure.SHOW_NOTIFICATION_SNOOZE;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
@@ -100,8 +101,31 @@
 
     @Test
     public void testNoAppOpsInSlowSwipe() {
-        Settings.Secure.putInt(mContext.getContentResolver(),
-                NOTIFICATION_NEW_INTERRUPTION_MODEL, 0);
+        Settings.Secure.putInt(mContext.getContentResolver(), SHOW_NOTIFICATION_SNOOZE, 0);
+
+        NotificationMenuRow row = new NotificationMenuRow(mContext);
+        row.createMenu(mRow, null);
+
+        ViewGroup container = (ViewGroup) row.getMenuView();
+        // noti blocking
+        assertEquals(1, container.getChildCount());
+    }
+
+    @Test
+    public void testNoSnoozeInSlowSwipe() {
+        Settings.Secure.putInt(mContext.getContentResolver(), SHOW_NOTIFICATION_SNOOZE, 0);
+
+        NotificationMenuRow row = new NotificationMenuRow(mContext);
+        row.createMenu(mRow, null);
+
+        ViewGroup container = (ViewGroup) row.getMenuView();
+        // just for noti blocking
+        assertEquals(1, container.getChildCount());
+    }
+
+    @Test
+    public void testSnoozeInSlowSwipe() {
+        Settings.Secure.putInt(mContext.getContentResolver(), SHOW_NOTIFICATION_SNOOZE, 1);
 
         NotificationMenuRow row = new NotificationMenuRow(mContext);
         row.createMenu(mRow, null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java
new file mode 100644
index 0000000..df41a84
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.wrapper;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.Notification;
+import android.media.MediaMetadata;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+import android.view.View;
+import android.widget.RemoteViews;
+import android.widget.SeekBar;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.NotificationTestHelper;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class NotificationMediaTemplateViewWrapperTest extends SysuiTestCase {
+
+    private ExpandableNotificationRow mRow;
+    private Notification mNotif;
+    private View mView;
+    private NotificationMediaTemplateViewWrapper mWrapper;
+
+    @Mock
+    private MetricsLogger mMetricsLogger;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
+
+        mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
+    }
+
+    private void makeTestNotification(long duration, boolean allowSeeking) throws Exception {
+        Notification.Builder builder = new Notification.Builder(mContext)
+                .setSmallIcon(R.drawable.ic_person)
+                .setContentTitle("Title")
+                .setContentText("Text");
+
+        MediaMetadata metadata = new MediaMetadata.Builder()
+                .putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
+                .build();
+        MediaSession session = new MediaSession(mContext, "TEST_CHANNEL");
+        session.setMetadata(metadata);
+
+        PlaybackState playbackState = new PlaybackState.Builder()
+                .setActions(allowSeeking ? PlaybackState.ACTION_SEEK_TO : 0)
+                .build();
+
+        session.setPlaybackState(playbackState);
+
+        builder.setStyle(new Notification.MediaStyle()
+                .setMediaSession(session.getSessionToken())
+        );
+
+        mNotif = builder.build();
+        assertTrue(mNotif.hasMediaSession());
+
+        mRow = new NotificationTestHelper(mContext).createRow(mNotif);
+
+        RemoteViews views = new RemoteViews(mContext.getPackageName(),
+                com.android.internal.R.layout.notification_template_material_big_media);
+        mView = views.apply(mContext, null);
+        mWrapper = new NotificationMediaTemplateViewWrapper(mContext,
+                mView, mRow);
+        mWrapper.onContentUpdated(mRow);
+    }
+
+    @Test
+    public void testLogging_NoSeekbar() throws Exception {
+        // Media sessions with duration <= 0 should not include a seekbar
+        makeTestNotification(0, false);
+
+        verify(mMetricsLogger).write(argThat(logMaker ->
+                logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR
+                        && logMaker.getType() == MetricsEvent.TYPE_CLOSE
+        ));
+
+        verify(mMetricsLogger, times(0)).write(argThat(logMaker ->
+                logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR
+                        && logMaker.getType() == MetricsEvent.TYPE_OPEN
+        ));
+    }
+
+    @Test
+    public void testLogging_HasSeekbarNoScrubber() throws Exception {
+        // Media sessions that do not support seeking should have a seekbar, but no scrubber
+        makeTestNotification(1000, false);
+
+        verify(mMetricsLogger).write(argThat(logMaker ->
+                logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR
+                        && logMaker.getType() == MetricsEvent.TYPE_OPEN
+        ));
+
+        // Ensure the callback runs at least once
+        mWrapper.mUpdatePlaybackUi.run();
+
+        verify(mMetricsLogger).write(argThat(logMaker ->
+                logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR
+                && logMaker.getType() == MetricsEvent.TYPE_DETAIL
+                && logMaker.getSubtype() == 0
+        ));
+    }
+
+    @Test
+    public void testLogging_HasSeekbarAndScrubber() throws Exception {
+        makeTestNotification(1000, true);
+
+        verify(mMetricsLogger).write(argThat(logMaker ->
+                logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR
+                        && logMaker.getType() == MetricsEvent.TYPE_OPEN
+        ));
+
+        verify(mMetricsLogger).write(argThat(logMaker ->
+                logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR
+                        && logMaker.getType() == MetricsEvent.TYPE_DETAIL
+                        && logMaker.getSubtype() == 1
+        ));
+    }
+
+    @Test
+    public void testLogging_UpdateSeekbar() throws Exception {
+        makeTestNotification(1000, true);
+
+        SeekBar seekbar = mView.findViewById(
+                com.android.internal.R.id.notification_media_progress_bar);
+        assertTrue(seekbar != null);
+
+        mWrapper.mSeekListener.onStopTrackingTouch(seekbar);
+
+        verify(mMetricsLogger).write(argThat(logMaker ->
+                logMaker.getCategory() == MetricsEvent.MEDIA_NOTIFICATION_SEEKBAR
+                        && logMaker.getType() == MetricsEvent.TYPE_UPDATE));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
index 4181d38..faf5a97 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
@@ -268,7 +268,7 @@
             when(view.getRecentsButton()).thenReturn(mock(ButtonDispatcher.class));
             when(view.getAccessibilityButton()).thenReturn(mock(ButtonDispatcher.class));
             when(view.getRotateSuggestionButton()).thenReturn(mock(RotationContextButton.class));
-            when(view.getBarTransitions()).thenReturn(mock(BarTransitions.class));
+            when(view.getBarTransitions()).thenReturn(mock(NavigationBarTransitions.class));
             when(view.getLightTransitionsController()).thenReturn(
                     mock(LightBarTransitionsController.class));
             when(view.getRotationButtonController()).thenReturn(
diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto
index 3061981..0f0e6f9 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -7379,6 +7379,15 @@
     // Custom tag for NotificationItem. Hash of the NAS that made adjustments.
     FIELD_NOTIFICATION_ASSISTANT_SERVICE_HASH = 1742;
 
+    // Report interactions with seekbar on media notifications
+    // OPEN: Seekbar is visible
+    // CLOSE: Seekbar is not visible
+    // DETAIL: Seekbar scrubber enabled / disabled
+    //  Subtype: 0 disabled, cannot seek; 1 enabled, can seek
+    // UPDATE: Scrubber was moved by user
+    // CATEGORY: NOTIFICATION
+    MEDIA_NOTIFICATION_SEEKBAR = 1743;
+
     // ---- End Q Constants, all Q constants go above this line ----
     // Add new aosp constants above this line.
     // END OF AOSP CONSTANTS
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index dfbb55a..d0edaaa 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -1763,7 +1763,7 @@
                                 + ", callingPackage: " + callingPackage;
                 // STOPSHIP (b/128866264): Just to catch breakages. Remove before final release.
                 Slog.wtf(TAG, errorMsg);
-                throw new UnsupportedOperationException(errorMsg);
+                throw new IllegalStateException(errorMsg);
             }
             setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, maxElapsed,
                     interval, operation, directReceiver, listenerTag, flags, true, workSource,
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 0a1dbff..45f7360 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -4363,7 +4363,7 @@
 
     /**
      * @return VPN information for accounting, or null if we can't retrieve all required
-     *         information, e.g primary underlying iface.
+     *         information, e.g underlying ifaces.
      */
     @Nullable
     private VpnInfo createVpnInfo(Vpn vpn) {
@@ -4375,17 +4375,24 @@
         // see VpnService.setUnderlyingNetworks()'s javadoc about how to interpret
         // the underlyingNetworks list.
         if (underlyingNetworks == null) {
-            NetworkAgentInfo defaultNetwork = getDefaultNetwork();
-            if (defaultNetwork != null && defaultNetwork.linkProperties != null) {
-                info.primaryUnderlyingIface = getDefaultNetwork().linkProperties.getInterfaceName();
-            }
-        } else if (underlyingNetworks.length > 0) {
-            LinkProperties linkProperties = getLinkProperties(underlyingNetworks[0]);
-            if (linkProperties != null) {
-                info.primaryUnderlyingIface = linkProperties.getInterfaceName();
+            NetworkAgentInfo defaultNai = getDefaultNetwork();
+            if (defaultNai != null) {
+                underlyingNetworks = new Network[] { defaultNai.network };
             }
         }
-        return info.primaryUnderlyingIface == null ? null : info;
+        if (underlyingNetworks != null && underlyingNetworks.length > 0) {
+            List<String> interfaces = new ArrayList<>();
+            for (Network network : underlyingNetworks) {
+                LinkProperties lp = getLinkProperties(network);
+                if (lp != null && !TextUtils.isEmpty(lp.getInterfaceName())) {
+                    interfaces.add(lp.getInterfaceName());
+                }
+            }
+            if (!interfaces.isEmpty()) {
+                info.underlyingIfaces = interfaces.toArray(new String[interfaces.size()]);
+            }
+        }
+        return info.underlyingIfaces == null ? null : info;
     }
 
     /**
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index bbbec66..d2b992b 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -2842,8 +2842,9 @@
         }
     }
 
+    /** Not thread safe */
     class AppFuseMountScope extends AppFuseBridge.MountScope {
-        boolean opened = false;
+        private boolean mMounted = false;
 
         public AppFuseMountScope(int uid, int mountId) {
             super(uid, mountId);
@@ -2852,8 +2853,9 @@
         @Override
         public ParcelFileDescriptor open() throws NativeDaemonConnectorException {
             try {
-                return new ParcelFileDescriptor(
-                        mVold.mountAppFuse(uid, mountId));
+                final FileDescriptor fd = mVold.mountAppFuse(uid, mountId);
+                mMounted = true;
+                return new ParcelFileDescriptor(fd);
             } catch (Exception e) {
                 throw new NativeDaemonConnectorException("Failed to mount", e);
             }
@@ -2872,9 +2874,9 @@
 
         @Override
         public void close() throws Exception {
-            if (opened) {
+            if (mMounted) {
                 mVold.unmountAppFuse(uid, mountId);
-                opened = false;
+                mMounted = false;
             }
         }
     }
@@ -3582,6 +3584,10 @@
             }
 
             final String[] packagesForUid = mIPackageManager.getPackagesForUid(uid);
+            if (ArrayUtils.isEmpty(packagesForUid)) {
+                // It's possible the package got uninstalled already, so just ignore.
+                return Zygote.MOUNT_EXTERNAL_NONE;
+            }
             if (packageName == null) {
                 packageName = packagesForUid[0];
             }
diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
index bdbff3d..4b48ef9 100644
--- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java
+++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
@@ -661,12 +661,6 @@
         return mTestUserKeyFile == null ? getAdbFile(ADB_KEYS_FILE) : mTestUserKeyFile;
     }
 
-    private void createKeyFile(File keyFile) throws IOException {
-        keyFile.createNewFile();
-        FileUtils.setPermissions(keyFile.toString(),
-                FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP, -1, -1);
-    }
-
     private void writeKey(String key) {
         try {
             File keyFile = getUserKeyFile();
@@ -675,14 +669,13 @@
                 return;
             }
 
-            if (!keyFile.exists()) {
-                createKeyFile(keyFile);
-            }
-
             FileOutputStream fo = new FileOutputStream(keyFile, true);
             fo.write(key.getBytes());
             fo.write('\n');
             fo.close();
+
+            FileUtils.setPermissions(keyFile.toString(),
+                    FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP, -1, -1);
         } catch (IOException ex) {
             Slog.e(TAG, "Error writing key:" + ex);
         }
@@ -698,10 +691,6 @@
                 return;
             }
 
-            if (!keyFile.exists()) {
-                createKeyFile(keyFile);
-            }
-
             atomicKeyFile = new AtomicFile(keyFile);
             fo = atomicKeyFile.startWrite();
             for (String key : keys) {
@@ -709,6 +698,9 @@
                 fo.write('\n');
             }
             atomicKeyFile.finishWrite(fo);
+
+            FileUtils.setPermissions(keyFile.toString(),
+                    FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP, -1, -1);
         } catch (IOException ex) {
             Slog.e(TAG, "Error writing keys: " + ex);
             if (atomicKeyFile != null) {
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index af25ad5..b279b37 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -775,8 +775,7 @@
                     // if the application has the permission, let it to access user's clipboard.
                     // To passed synthesized uid user#10_app#systemui may not tell the real uid.
                     // userId must pass intending userId. i.e. user#10.
-                    allowed = mContentCaptureInternal.isContentCaptureServiceForUser(
-                            Binder.getCallingUid(), userId);
+                    allowed = mContentCaptureInternal.isContentCaptureServiceForUser(uid, userId);
                 }
                 if (!allowed && mAutofillInternal != null) {
                     // ...or the Augmented Autofill Service
@@ -785,8 +784,7 @@
                     // if the application has the permission, let it to access user's clipboard.
                     // To passed synthesized uid user#10_app#systemui may not tell the real uid.
                     // userId must pass intending userId. i.e. user#10.
-                    allowed = mAutofillInternal.isAugmentedAutofillServiceForUser(
-                            Binder.getCallingUid(), userId);
+                    allowed = mAutofillInternal.isAugmentedAutofillServiceForUser(uid, userId);
                 }
                 if (!allowed) {
                     Slog.e(TAG, "Denying clipboard access to " + callingPackage
diff --git a/services/core/java/com/android/server/job/controllers/QuotaController.java b/services/core/java/com/android/server/job/controllers/QuotaController.java
index f560d69..ef6944e 100644
--- a/services/core/java/com/android/server/job/controllers/QuotaController.java
+++ b/services/core/java/com/android/server/job/controllers/QuotaController.java
@@ -16,6 +16,10 @@
 
 package com.android.server.job.controllers;
 
+import static android.text.format.DateUtils.HOUR_IN_MILLIS;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+import static android.text.format.DateUtils.SECOND_IN_MILLIS;
+
 import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
 import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
 import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
@@ -75,17 +79,28 @@
 /**
  * Controller that tracks whether an app has exceeded its standby bucket quota.
  *
- * Each job in each bucket is given 10 minutes to run within its respective time window. Active
- * jobs can run indefinitely, working set jobs can run for 10 minutes within a 2 hour window,
- * frequent jobs get to run 10 minutes in an 8 hour window, and rare jobs get to run 10 minutes in
- * a 24 hour window. The windows are rolling, so as soon as a job would have some quota based on its
- * bucket, it will be eligible to run. When a job's bucket changes, its new quota is immediately
- * applied to it.
+ * With initial defaults, each app in each bucket is given 10 minutes to run within its respective
+ * time window. Active jobs can run indefinitely, working set jobs can run for 10 minutes within a
+ * 2 hour window, frequent jobs get to run 10 minutes in an 8 hour window, and rare jobs get to run
+ * 10 minutes in a 24 hour window. The windows are rolling, so as soon as a job would have some
+ * quota based on its bucket, it will be eligible to run. When a job's bucket changes, its new
+ * quota is immediately applied to it.
+ *
+ * Job and session count limits are included to prevent abuse/spam. Each bucket has its own limit on
+ * the number of jobs or sessions that can run within the window. Regardless of bucket, apps will
+ * not be allowed to run more than 20 jobs within the past 10 minutes.
  *
  * Jobs are throttled while an app is not in a foreground state. All jobs are allowed to run
  * freely when an app enters the foreground state and are restricted when the app leaves the
- * foreground state. However, jobs that are started while the app is in the TOP state are not
- * restricted regardless of the app's state change.
+ * foreground state. However, jobs that are started while the app is in the TOP state do not count
+ * towards any quota and are not restricted regardless of the app's state change.
+ *
+ * Jobs will not be throttled when the device is charging. The device is considered to be charging
+ * once the {@link BatteryManager#ACTION_CHARGING} intent has been broadcast.
+ *
+ * Note: all limits are enforced per bucket window unless explicitly stated otherwise.
+ * All stated values are configurable and subject to change. See {@link QcConstants} for current
+ * defaults.
  *
  * Test: atest com.android.server.job.controllers.QuotaControllerTest
  */
@@ -94,8 +109,6 @@
     private static final boolean DEBUG = JobSchedulerService.DEBUG
             || Log.isLoggable(TAG, Log.DEBUG);
 
-    private static final long MINUTE_IN_MILLIS = 60 * 1000L;
-
     private static final String ALARM_TAG_CLEANUP = "*job.cleanup*";
     private static final String ALARM_TAG_QUOTA_CHECK = "*job.quota_check*";
 
@@ -244,6 +257,8 @@
         public long expirationTimeElapsed;
 
         public long windowSizeMs;
+        public int jobCountLimit;
+        public int sessionCountLimit;
 
         /** The total amount of time the app ran in its respective bucket window size. */
         public long executionTimeInWindowMs;
@@ -260,54 +275,58 @@
         public int sessionCountInWindow;
 
         /**
-         * The time after which the sum of all the app's sessions plus {@link #mQuotaBufferMs}
-         * equals the quota. This is only valid if
-         * executionTimeInWindowMs >= {@link #mAllowedTimePerPeriodMs} or
-         * executionTimeInMaxPeriodMs >= {@link #mMaxExecutionTimeMs}.
+         * The time after which the app will be under the bucket quota and can start running jobs
+         * again. This is only valid if
+         * {@link #executionTimeInWindowMs} >= {@link #mAllowedTimePerPeriodMs},
+         * {@link #executionTimeInMaxPeriodMs} >= {@link #mMaxExecutionTimeMs},
+         * {@link #bgJobCountInWindow} >= {@link #jobCountLimit}, or
+         * {@link #sessionCountInWindow} >= {@link #sessionCountLimit}.
          */
-        public long quotaCutoffTimeElapsed;
+        public long inQuotaTimeElapsed;
 
         /**
-         * The time after which {@link #jobCountInAllowedTime} should be considered invalid, in the
-         * elapsed realtime timebase.
+         * The time after which {@link #jobCountInRateLimitingWindow} should be considered invalid,
+         * in the elapsed realtime timebase.
          */
-        public long jobCountExpirationTimeElapsed;
+        public long jobRateLimitExpirationTimeElapsed;
 
         /**
-         * The number of jobs that ran in at least the last {@link #mAllowedTimePerPeriodMs}.
+         * The number of jobs that ran in at least the last {@link #mRateLimitingWindowMs}.
          * It may contain a few stale entries since cleanup won't happen exactly every
-         * {@link #mAllowedTimePerPeriodMs}.
+         * {@link #mRateLimitingWindowMs}.
          */
-        public int jobCountInAllowedTime;
+        public int jobCountInRateLimitingWindow;
 
         /**
-         * The time after which {@link #sessionCountInAllowedTime} should be considered
+         * The time after which {@link #sessionCountInRateLimitingWindow} should be considered
          * invalid, in the elapsed realtime timebase.
          */
-        public long sessionCountExpirationTimeElapsed;
+        public long sessionRateLimitExpirationTimeElapsed;
 
         /**
          * The number of {@link TimingSession}s that ran in at least the last
-         * {@link #mAllowedTimePerPeriodMs}. It may contain a few stale entries since cleanup won't
-         * happen exactly every {@link #mAllowedTimePerPeriodMs}. This should only be considered
-         * valid before elapsed realtime has reached {@link #sessionCountExpirationTimeElapsed}.
+         * {@link #mRateLimitingWindowMs}. It may contain a few stale entries since cleanup won't
+         * happen exactly every {@link #mRateLimitingWindowMs}. This should only be considered
+         * valid before elapsed realtime has reached {@link #sessionRateLimitExpirationTimeElapsed}.
          */
-        public int sessionCountInAllowedTime;
+        public int sessionCountInRateLimitingWindow;
 
         @Override
         public String toString() {
             return "expirationTime=" + expirationTimeElapsed + ", "
-                    + "windowSize=" + windowSizeMs + ", "
+                    + "windowSizeMs=" + windowSizeMs + ", "
+                    + "jobCountLimit=" + jobCountLimit + ", "
+                    + "sessionCountLimit=" + sessionCountLimit + ", "
                     + "executionTimeInWindow=" + executionTimeInWindowMs + ", "
                     + "bgJobCountInWindow=" + bgJobCountInWindow + ", "
                     + "executionTimeInMaxPeriod=" + executionTimeInMaxPeriodMs + ", "
                     + "bgJobCountInMaxPeriod=" + bgJobCountInMaxPeriod + ", "
                     + "sessionCountInWindow=" + sessionCountInWindow + ", "
-                    + "quotaCutoffTime=" + quotaCutoffTimeElapsed + ", "
-                    + "jobCountExpirationTime=" + jobCountExpirationTimeElapsed + ", "
-                    + "jobCountInAllowedTime=" + jobCountInAllowedTime + ", "
-                    + "sessionCountExpirationTime=" + sessionCountExpirationTimeElapsed + ", "
-                    + "sessionCountInAllowedTime=" + sessionCountInAllowedTime;
+                    + "inQuotaTime=" + inQuotaTimeElapsed + ", "
+                    + "jobCountExpirationTime=" + jobRateLimitExpirationTimeElapsed + ", "
+                    + "jobCountInRateLimitingWindow=" + jobCountInRateLimitingWindow + ", "
+                    + "sessionCountExpirationTime=" + sessionRateLimitExpirationTimeElapsed + ", "
+                    + "sessionCountInRateLimitingWindow=" + sessionCountInRateLimitingWindow;
         }
 
         @Override
@@ -316,18 +335,21 @@
                 ExecutionStats other = (ExecutionStats) obj;
                 return this.expirationTimeElapsed == other.expirationTimeElapsed
                         && this.windowSizeMs == other.windowSizeMs
+                        && this.jobCountLimit == other.jobCountLimit
+                        && this.sessionCountLimit == other.sessionCountLimit
                         && this.executionTimeInWindowMs == other.executionTimeInWindowMs
                         && this.bgJobCountInWindow == other.bgJobCountInWindow
                         && this.executionTimeInMaxPeriodMs == other.executionTimeInMaxPeriodMs
                         && this.sessionCountInWindow == other.sessionCountInWindow
                         && this.bgJobCountInMaxPeriod == other.bgJobCountInMaxPeriod
-                        && this.quotaCutoffTimeElapsed == other.quotaCutoffTimeElapsed
-                        && this.jobCountExpirationTimeElapsed == other.jobCountExpirationTimeElapsed
-                        && this.jobCountInAllowedTime == other.jobCountInAllowedTime
-                        && this.sessionCountExpirationTimeElapsed
-                        == other.sessionCountExpirationTimeElapsed
-                        && this.sessionCountInAllowedTime
-                        == other.sessionCountInAllowedTime;
+                        && this.inQuotaTimeElapsed == other.inQuotaTimeElapsed
+                        && this.jobRateLimitExpirationTimeElapsed
+                                == other.jobRateLimitExpirationTimeElapsed
+                        && this.jobCountInRateLimitingWindow == other.jobCountInRateLimitingWindow
+                        && this.sessionRateLimitExpirationTimeElapsed
+                                == other.sessionRateLimitExpirationTimeElapsed
+                        && this.sessionCountInRateLimitingWindow
+                                == other.sessionCountInRateLimitingWindow;
             } else {
                 return false;
             }
@@ -338,16 +360,18 @@
             int result = 0;
             result = 31 * result + hashLong(expirationTimeElapsed);
             result = 31 * result + hashLong(windowSizeMs);
+            result = 31 * result + hashLong(jobCountLimit);
+            result = 31 * result + hashLong(sessionCountLimit);
             result = 31 * result + hashLong(executionTimeInWindowMs);
             result = 31 * result + bgJobCountInWindow;
             result = 31 * result + hashLong(executionTimeInMaxPeriodMs);
             result = 31 * result + bgJobCountInMaxPeriod;
             result = 31 * result + sessionCountInWindow;
-            result = 31 * result + hashLong(quotaCutoffTimeElapsed);
-            result = 31 * result + hashLong(jobCountExpirationTimeElapsed);
-            result = 31 * result + jobCountInAllowedTime;
-            result = 31 * result + hashLong(sessionCountExpirationTimeElapsed);
-            result = 31 * result + sessionCountInAllowedTime;
+            result = 31 * result + hashLong(inQuotaTimeElapsed);
+            result = 31 * result + hashLong(jobRateLimitExpirationTimeElapsed);
+            result = 31 * result + jobCountInRateLimitingWindow;
+            result = 31 * result + hashLong(sessionRateLimitExpirationTimeElapsed);
+            result = 31 * result + sessionCountInRateLimitingWindow;
             return result;
         }
     }
@@ -399,19 +423,19 @@
     private boolean mShouldThrottle;
 
     /** How much time each app will have to run jobs within their standby bucket window. */
-    private long mAllowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS;
+    private long mAllowedTimePerPeriodMs = QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_MS;
 
     /**
      * The maximum amount of time an app can have its jobs running within a {@link #MAX_PERIOD_MS}
      * window.
      */
-    private long mMaxExecutionTimeMs = 4 * 60 * MINUTE_IN_MILLIS;
+    private long mMaxExecutionTimeMs = QcConstants.DEFAULT_MAX_EXECUTION_TIME_MS;
 
     /**
      * How much time the app should have before transitioning from out-of-quota to in-quota.
      * This should not affect processing if the app is already in-quota.
      */
-    private long mQuotaBufferMs = 30 * 1000L; // 30 seconds
+    private long mQuotaBufferMs = QcConstants.DEFAULT_IN_QUOTA_BUFFER_MS;
 
     /**
      * {@link #mAllowedTimePerPeriodMs} - {@link #mQuotaBufferMs}. This can be used to determine
@@ -425,14 +449,19 @@
      */
     private long mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
 
-    /** The maximum number of jobs that can run within the past {@link #mAllowedTimePerPeriodMs}. */
-    private int mMaxJobCountPerAllowedTime = 20;
+    /** The period of time used to rate limit recently run jobs. */
+    private long mRateLimitingWindowMs = QcConstants.DEFAULT_RATE_LIMITING_WINDOW_MS;
+
+    /** The maximum number of jobs that can run within the past {@link #mRateLimitingWindowMs}. */
+    private int mMaxJobCountPerRateLimitingWindow =
+            QcConstants.DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
 
     /**
      * The maximum number of {@link TimingSession}s that can run within the past {@link
-     * #mAllowedTimePerPeriodMs}.
+     * #mRateLimitingWindowMs}.
      */
-    private int mMaxSessionCountPerAllowedTime = 20;
+    private int mMaxSessionCountPerRateLimitingWindow =
+            QcConstants.DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW;
 
     private long mNextCleanupTimeElapsed = 0;
     private final AlarmManager.OnAlarmListener mSessionCleanupAlarmListener =
@@ -486,11 +515,11 @@
      * The rolling window size for each standby bucket. Within each window, an app will have 10
      * minutes to run its jobs.
      */
-    private final long[] mBucketPeriodsMs = new long[] {
-            10 * MINUTE_IN_MILLIS, // 10 minutes for ACTIVE -- ACTIVE apps can run jobs at any time
-            2 * 60 * MINUTE_IN_MILLIS, // 2 hours for WORKING
-            8 * 60 * MINUTE_IN_MILLIS, // 8 hours for FREQUENT
-            24 * 60 * MINUTE_IN_MILLIS // 24 hours for RARE
+    private final long[] mBucketPeriodsMs = new long[]{
+            QcConstants.DEFAULT_WINDOW_SIZE_ACTIVE_MS,
+            QcConstants.DEFAULT_WINDOW_SIZE_WORKING_MS,
+            QcConstants.DEFAULT_WINDOW_SIZE_FREQUENT_MS,
+            QcConstants.DEFAULT_WINDOW_SIZE_RARE_MS
     };
 
     /** The maximum period any bucket can have. */
@@ -503,16 +532,13 @@
      *
      * @see #mBucketPeriodsMs
      */
-    private final int[] mMaxBucketJobCounts = new int[] {
-            200,  // ACTIVE   -- 1200/hr
-            1200, // WORKING  -- 600/hr
-            1800, // FREQUENT -- 225/hr
-            2400  // RARE     -- 100/hr
+    private final int[] mMaxBucketJobCounts = new int[]{
+            QcConstants.DEFAULT_MAX_JOB_COUNT_ACTIVE,
+            QcConstants.DEFAULT_MAX_JOB_COUNT_WORKING,
+            QcConstants.DEFAULT_MAX_JOB_COUNT_FREQUENT,
+            QcConstants.DEFAULT_MAX_JOB_COUNT_RARE
     };
 
-    /** The minimum number of jobs that any bucket will be allowed to run. */
-    private static final int MIN_BUCKET_JOB_COUNT = 100;
-
     /**
      * The maximum number of {@link TimingSession}s based on its standby bucket. For each max value
      * count in the array, the app will not be allowed to have more than that many number of
@@ -527,14 +553,12 @@
             QcConstants.DEFAULT_MAX_SESSION_COUNT_RARE
     };
 
-    /** The minimum number of {@link TimingSession}s that any bucket will be allowed to run. */
-    private static final int MIN_BUCKET_SESSION_COUNT = 3;
-
     /**
      * Treat two distinct {@link TimingSession}s as the same if they start and end within this
      * amount of time of each other.
      */
-    private long mTimingSessionCoalescingDurationMs = 0;
+    private long mTimingSessionCoalescingDurationMs =
+            QcConstants.DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS;
 
     /** An app has reached its quota. The message should contain a {@link Package} object. */
     private static final int MSG_REACHED_QUOTA = 0;
@@ -761,8 +785,8 @@
             final int standbyBucket) {
         final long now = sElapsedRealtimeClock.millis();
         final boolean isUnderAllowedTimeQuota =
-                (stats.jobCountExpirationTimeElapsed <= now
-                        || stats.jobCountInAllowedTime < mMaxJobCountPerAllowedTime);
+                (stats.jobRateLimitExpirationTimeElapsed <= now
+                        || stats.jobCountInRateLimitingWindow < mMaxJobCountPerRateLimitingWindow);
         return isUnderAllowedTimeQuota
                 && (stats.bgJobCountInWindow < mMaxBucketJobCounts[standbyBucket]);
     }
@@ -770,10 +794,8 @@
     private boolean isUnderSessionCountQuotaLocked(@NonNull ExecutionStats stats,
             final int standbyBucket) {
         final long now = sElapsedRealtimeClock.millis();
-        final boolean isUnderAllowedTimeQuota =
-                (stats.sessionCountExpirationTimeElapsed <= now
-                        || stats.sessionCountInAllowedTime
-                        < mMaxSessionCountPerAllowedTime);
+        final boolean isUnderAllowedTimeQuota = (stats.sessionRateLimitExpirationTimeElapsed <= now
+                || stats.sessionCountInRateLimitingWindow < mMaxSessionCountPerRateLimitingWindow);
         return isUnderAllowedTimeQuota
                 && stats.sessionCountInWindow < mMaxBucketSessionCounts[standbyBucket];
     }
@@ -924,12 +946,18 @@
         }
         if (refreshStatsIfOld) {
             final long bucketWindowSizeMs = mBucketPeriodsMs[standbyBucket];
+            final int jobCountLimit = mMaxBucketJobCounts[standbyBucket];
+            final int sessionCountLimit = mMaxBucketSessionCounts[standbyBucket];
             Timer timer = mPkgTimers.get(userId, packageName);
             if ((timer != null && timer.isActive())
                     || stats.expirationTimeElapsed <= sElapsedRealtimeClock.millis()
-                    || stats.windowSizeMs != bucketWindowSizeMs) {
+                    || stats.windowSizeMs != bucketWindowSizeMs
+                    || stats.jobCountLimit != jobCountLimit
+                    || stats.sessionCountLimit != sessionCountLimit) {
                 // The stats are no longer valid.
                 stats.windowSizeMs = bucketWindowSizeMs;
+                stats.jobCountLimit = jobCountLimit;
+                stats.sessionCountLimit = sessionCountLimit;
                 updateExecutionStatsLocked(userId, packageName, stats);
             }
         }
@@ -945,7 +973,7 @@
         stats.executionTimeInMaxPeriodMs = 0;
         stats.bgJobCountInMaxPeriod = 0;
         stats.sessionCountInWindow = 0;
-        stats.quotaCutoffTimeElapsed = 0;
+        stats.inQuotaTimeElapsed = 0;
 
         Timer timer = mPkgTimers.get(userId, packageName);
         final long nowElapsed = sElapsedRealtimeClock.millis();
@@ -958,12 +986,12 @@
             // invalidate now.
             stats.expirationTimeElapsed = nowElapsed;
             if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) {
-                stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed,
-                        nowElapsed - mAllowedTimeIntoQuotaMs);
+                stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
+                        nowElapsed - mAllowedTimeIntoQuotaMs + stats.windowSizeMs);
             }
             if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) {
-                stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed,
-                        nowElapsed - mMaxExecutionTimeIntoQuotaMs);
+                stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
+                        nowElapsed - mMaxExecutionTimeIntoQuotaMs + MAX_PERIOD_MS);
             }
         }
 
@@ -985,36 +1013,40 @@
             TimingSession session = sessions.get(i);
 
             // Window management.
-            if (startWindowElapsed < session.startTimeElapsed) {
-                stats.executionTimeInWindowMs += session.endTimeElapsed - session.startTimeElapsed;
+            if (startWindowElapsed < session.endTimeElapsed) {
+                final long start;
+                if (startWindowElapsed < session.startTimeElapsed) {
+                    start = session.startTimeElapsed;
+                    emptyTimeMs =
+                            Math.min(emptyTimeMs, session.startTimeElapsed - startWindowElapsed);
+                } else {
+                    // The session started before the window but ended within the window. Only
+                    // include the portion that was within the window.
+                    start = startWindowElapsed;
+                    emptyTimeMs = 0;
+                }
+
+                stats.executionTimeInWindowMs += session.endTimeElapsed - start;
                 stats.bgJobCountInWindow += session.bgJobCount;
-                emptyTimeMs = Math.min(emptyTimeMs, session.startTimeElapsed - startWindowElapsed);
                 if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) {
-                    stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed,
-                            session.startTimeElapsed + stats.executionTimeInWindowMs
-                                    - mAllowedTimeIntoQuotaMs);
+                    stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
+                            start + stats.executionTimeInWindowMs - mAllowedTimeIntoQuotaMs
+                                    + stats.windowSizeMs);
+                }
+                if (stats.bgJobCountInWindow >= stats.jobCountLimit) {
+                    stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
+                            session.endTimeElapsed + stats.windowSizeMs);
                 }
                 if (i == loopStart
                         || (sessions.get(i + 1).startTimeElapsed - session.endTimeElapsed)
                                 > mTimingSessionCoalescingDurationMs) {
                     // Coalesce sessions if they are very close to each other in time
                     sessionCountInWindow++;
-                }
-            } else if (startWindowElapsed < session.endTimeElapsed) {
-                // The session started before the window but ended within the window. Only include
-                // the portion that was within the window.
-                stats.executionTimeInWindowMs += session.endTimeElapsed - startWindowElapsed;
-                stats.bgJobCountInWindow += session.bgJobCount;
-                emptyTimeMs = 0;
-                if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) {
-                    stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed,
-                            startWindowElapsed + stats.executionTimeInWindowMs
-                                    - mAllowedTimeIntoQuotaMs);
-                }
-                if (i == loopStart
-                        || (sessions.get(i + 1).startTimeElapsed - session.endTimeElapsed)
-                                > mTimingSessionCoalescingDurationMs) {
-                    sessionCountInWindow++;
+
+                    if (sessionCountInWindow >= stats.sessionCountLimit) {
+                        stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
+                                session.endTimeElapsed + stats.windowSizeMs);
+                    }
                 }
             }
 
@@ -1025,9 +1057,9 @@
                 stats.bgJobCountInMaxPeriod += session.bgJobCount;
                 emptyTimeMs = Math.min(emptyTimeMs, session.startTimeElapsed - startMaxElapsed);
                 if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) {
-                    stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed,
+                    stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
                             session.startTimeElapsed + stats.executionTimeInMaxPeriodMs
-                                    - mMaxExecutionTimeIntoQuotaMs);
+                                    - mMaxExecutionTimeIntoQuotaMs + MAX_PERIOD_MS);
                 }
             } else if (startMaxElapsed < session.endTimeElapsed) {
                 // The session started before the window but ended within the window. Only include
@@ -1036,9 +1068,9 @@
                 stats.bgJobCountInMaxPeriod += session.bgJobCount;
                 emptyTimeMs = 0;
                 if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) {
-                    stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed,
+                    stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
                             startMaxElapsed + stats.executionTimeInMaxPeriodMs
-                                    - mMaxExecutionTimeIntoQuotaMs);
+                                    - mMaxExecutionTimeIntoQuotaMs + MAX_PERIOD_MS);
                 }
             } else {
                 // This session ended before the window. No point in going any further.
@@ -1094,11 +1126,11 @@
                 stats = new ExecutionStats();
                 appStats[i] = stats;
             }
-            if (stats.jobCountExpirationTimeElapsed <= now) {
-                stats.jobCountExpirationTimeElapsed = now + mAllowedTimePerPeriodMs;
-                stats.jobCountInAllowedTime = 0;
+            if (stats.jobRateLimitExpirationTimeElapsed <= now) {
+                stats.jobRateLimitExpirationTimeElapsed = now + mRateLimitingWindowMs;
+                stats.jobCountInRateLimitingWindow = 0;
             }
-            stats.jobCountInAllowedTime += count;
+            stats.jobCountInRateLimitingWindow += count;
         }
     }
 
@@ -1115,11 +1147,11 @@
                 stats = new ExecutionStats();
                 appStats[i] = stats;
             }
-            if (stats.sessionCountExpirationTimeElapsed <= now) {
-                stats.sessionCountExpirationTimeElapsed = now + mAllowedTimePerPeriodMs;
-                stats.sessionCountInAllowedTime = 0;
+            if (stats.sessionRateLimitExpirationTimeElapsed <= now) {
+                stats.sessionRateLimitExpirationTimeElapsed = now + mRateLimitingWindowMs;
+                stats.sessionCountInRateLimitingWindow = 0;
             }
-            stats.sessionCountInAllowedTime++;
+            stats.sessionCountInRateLimitingWindow++;
         }
     }
 
@@ -1367,18 +1399,17 @@
         }
 
         // The time this app will have quota again.
-        long inQuotaTimeElapsed = stats.quotaCutoffTimeElapsed + stats.windowSizeMs;
-        if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeMs) {
+        long inQuotaTimeElapsed = stats.inQuotaTimeElapsed;
+        if (!isUnderJobCountQuota && stats.bgJobCountInWindow < stats.jobCountLimit) {
+            // App hit the rate limit.
             inQuotaTimeElapsed = Math.max(inQuotaTimeElapsed,
-                    stats.quotaCutoffTimeElapsed + MAX_PERIOD_MS);
+                    stats.jobRateLimitExpirationTimeElapsed);
         }
-        if (!isUnderJobCountQuota) {
+        if (!isUnderTimingSessionCountQuota
+                && stats.sessionCountInWindow < stats.sessionCountLimit) {
+            // App hit the rate limit.
             inQuotaTimeElapsed = Math.max(inQuotaTimeElapsed,
-                    stats.jobCountExpirationTimeElapsed + mAllowedTimePerPeriodMs);
-        }
-        if (!isUnderTimingSessionCountQuota) {
-            inQuotaTimeElapsed = Math.max(inQuotaTimeElapsed,
-                    stats.sessionCountExpirationTimeElapsed + mAllowedTimePerPeriodMs);
+                    stats.sessionRateLimitExpirationTimeElapsed);
         }
         // Only schedule the alarm if:
         // 1. There isn't one currently scheduled
@@ -1399,7 +1430,7 @@
                     ALARM_TAG_QUOTA_CHECK, alarmListener, mHandler);
             alarmListener.setTriggerTime(inQuotaTimeElapsed);
         } else if (DEBUG) {
-            Slog.d(TAG, "No need to scheduling start alarm for " + pkgString);
+            Slog.d(TAG, "No need to schedule start alarm for " + pkgString);
         }
     }
 
@@ -1968,14 +1999,15 @@
         private static final String KEY_MAX_JOB_COUNT_WORKING = "max_job_count_working";
         private static final String KEY_MAX_JOB_COUNT_FREQUENT = "max_job_count_frequent";
         private static final String KEY_MAX_JOB_COUNT_RARE = "max_job_count_rare";
-        private static final String KEY_MAX_JOB_COUNT_PER_ALLOWED_TIME =
-                "max_count_per_allowed_time";
+        private static final String KEY_RATE_LIMITING_WINDOW_MS = "rate_limiting_window_ms";
+        private static final String KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW =
+                "max_job_count_per_rate_limiting_window";
         private static final String KEY_MAX_SESSION_COUNT_ACTIVE = "max_session_count_active";
         private static final String KEY_MAX_SESSION_COUNT_WORKING = "max_session_count_working";
         private static final String KEY_MAX_SESSION_COUNT_FREQUENT = "max_session_count_frequent";
         private static final String KEY_MAX_SESSION_COUNT_RARE = "max_session_count_rare";
-        private static final String KEY_MAX_SESSION_COUNT_PER_ALLOWED_TIME =
-                "max_session_count_per_allowed_time";
+        private static final String KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW =
+                "max_session_count_per_rate_limiting_window";
         private static final String KEY_TIMING_SESSION_COALESCING_DURATION_MS =
                 "timing_session_coalescing_duration_ms";
 
@@ -1984,7 +2016,7 @@
         private static final long DEFAULT_IN_QUOTA_BUFFER_MS =
                 30 * 1000L; // 30 seconds
         private static final long DEFAULT_WINDOW_SIZE_ACTIVE_MS =
-                10 * 60 * 1000L; // 10 minutes for ACTIVE -- ACTIVE apps can run jobs at any time
+                DEFAULT_ALLOWED_TIME_PER_PERIOD_MS; // ACTIVE apps can run jobs at any time
         private static final long DEFAULT_WINDOW_SIZE_WORKING_MS =
                 2 * 60 * 60 * 1000L; // 2 hours
         private static final long DEFAULT_WINDOW_SIZE_FREQUENT_MS =
@@ -1992,16 +2024,18 @@
         private static final long DEFAULT_WINDOW_SIZE_RARE_MS =
                 24 * 60 * 60 * 1000L; // 24 hours
         private static final long DEFAULT_MAX_EXECUTION_TIME_MS =
-                4 * 60 * 60 * 1000L; // 4 hours
-        private static final int DEFAULT_MAX_JOB_COUNT_ACTIVE =
-                200; // 1200/hr
-        private static final int DEFAULT_MAX_JOB_COUNT_WORKING =
-                1200; // 600/hr
-        private static final int DEFAULT_MAX_JOB_COUNT_FREQUENT =
-                1800; // 225/hr
-        private static final int DEFAULT_MAX_JOB_COUNT_RARE =
-                2400; // 100/hr
-        private static final int DEFAULT_MAX_JOB_COUNT_PER_ALLOWED_TIME = 20;
+                4 * HOUR_IN_MILLIS;
+        private static final long DEFAULT_RATE_LIMITING_WINDOW_MS =
+                10 * MINUTE_IN_MILLIS;
+        private static final int DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 20;
+        private static final int DEFAULT_MAX_JOB_COUNT_ACTIVE = // 20/window = 120/hr = 1/session
+                DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
+        private static final int DEFAULT_MAX_JOB_COUNT_WORKING = // 120/window = 60/hr = 12/session
+                (int) (60.0 * DEFAULT_WINDOW_SIZE_WORKING_MS / HOUR_IN_MILLIS);
+        private static final int DEFAULT_MAX_JOB_COUNT_FREQUENT = // 200/window = 25/hr = 25/session
+                (int) (25.0 * DEFAULT_WINDOW_SIZE_FREQUENT_MS / HOUR_IN_MILLIS);
+        private static final int DEFAULT_MAX_JOB_COUNT_RARE = // 48/window = 2/hr = 16/session
+                (int) (2.0 * DEFAULT_WINDOW_SIZE_RARE_MS / HOUR_IN_MILLIS);
         private static final int DEFAULT_MAX_SESSION_COUNT_ACTIVE =
                 20; // 120/hr
         private static final int DEFAULT_MAX_SESSION_COUNT_WORKING =
@@ -2010,8 +2044,8 @@
                 8; // 1/hr
         private static final int DEFAULT_MAX_SESSION_COUNT_RARE =
                 3; // .125/hr
-        private static final int DEFAULT_MAX_SESSION_COUNT_PER_ALLOWED_TIME = 20;
-        private static final long DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS = 0;
+        private static final int DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 20;
+        private static final long DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS = 5000; // 5 seconds
 
         /** How much time each app will have to run jobs within their standby bucket window. */
         public long ALLOWED_TIME_PER_PERIOD_MS = DEFAULT_ALLOWED_TIME_PER_PERIOD_MS;
@@ -2079,11 +2113,14 @@
          */
         public int MAX_JOB_COUNT_RARE = DEFAULT_MAX_JOB_COUNT_RARE;
 
+        /** The period of time used to rate limit recently run jobs. */
+        public long RATE_LIMITING_WINDOW_MS = DEFAULT_RATE_LIMITING_WINDOW_MS;
+
         /**
-         * The maximum number of jobs that can run within the past
-         * {@link #ALLOWED_TIME_PER_PERIOD_MS}.
+         * The maximum number of jobs that can run within the past {@link #RATE_LIMITING_WINDOW_MS}.
          */
-        public int MAX_JOB_COUNT_PER_ALLOWED_TIME = DEFAULT_MAX_JOB_COUNT_PER_ALLOWED_TIME;
+        public int MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW =
+                DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
 
         /**
          * The maximum number of {@link TimingSession}s an app can run within this particular
@@ -2113,7 +2150,8 @@
          * The maximum number of {@link TimingSession}s that can run within the past
          * {@link #ALLOWED_TIME_PER_PERIOD_MS}.
          */
-        public int MAX_SESSION_COUNT_PER_ALLOWED_TIME = DEFAULT_MAX_SESSION_COUNT_PER_ALLOWED_TIME;
+        public int MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW =
+                DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW;
 
         /**
          * Treat two distinct {@link TimingSession}s as the same if they start and end within this
@@ -2122,6 +2160,29 @@
         public long TIMING_SESSION_COALESCING_DURATION_MS =
                 DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS;
 
+        // Safeguards
+
+        /** The minimum number of jobs that any bucket will be allowed to run within its window. */
+        private static final int MIN_BUCKET_JOB_COUNT = 10;
+
+        /**
+         * The minimum number of {@link TimingSession}s that any bucket will be allowed to run
+         * within its window.
+         */
+        private static final int MIN_BUCKET_SESSION_COUNT = 1;
+
+        /** The minimum value that {@link #MAX_EXECUTION_TIME_MS} can have. */
+        private static final long MIN_MAX_EXECUTION_TIME_MS = 60 * MINUTE_IN_MILLIS;
+
+        /** The minimum value that {@link #MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW} can have. */
+        private static final int MIN_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 10;
+
+        /** The minimum value that {@link #MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW} can have. */
+        private static final int MIN_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 10;
+
+        /** The minimum value that {@link #RATE_LIMITING_WINDOW_MS} can have. */
+        private static final long MIN_RATE_LIMITING_WINDOW_MS = 30 * SECOND_IN_MILLIS;
+
         QcConstants(Handler handler) {
             super(handler);
         }
@@ -2167,8 +2228,11 @@
                     KEY_MAX_JOB_COUNT_FREQUENT, DEFAULT_MAX_JOB_COUNT_FREQUENT);
             MAX_JOB_COUNT_RARE = mParser.getInt(
                     KEY_MAX_JOB_COUNT_RARE, DEFAULT_MAX_JOB_COUNT_RARE);
-            MAX_JOB_COUNT_PER_ALLOWED_TIME = mParser.getInt(
-                    KEY_MAX_JOB_COUNT_PER_ALLOWED_TIME, DEFAULT_MAX_JOB_COUNT_PER_ALLOWED_TIME);
+            RATE_LIMITING_WINDOW_MS = mParser.getLong(
+                    KEY_RATE_LIMITING_WINDOW_MS, DEFAULT_RATE_LIMITING_WINDOW_MS);
+            MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = mParser.getInt(
+                    KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW,
+                    DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW);
             MAX_SESSION_COUNT_ACTIVE = mParser.getInt(
                     KEY_MAX_SESSION_COUNT_ACTIVE, DEFAULT_MAX_SESSION_COUNT_ACTIVE);
             MAX_SESSION_COUNT_WORKING = mParser.getInt(
@@ -2177,9 +2241,9 @@
                     KEY_MAX_SESSION_COUNT_FREQUENT, DEFAULT_MAX_SESSION_COUNT_FREQUENT);
             MAX_SESSION_COUNT_RARE = mParser.getInt(
                     KEY_MAX_SESSION_COUNT_RARE, DEFAULT_MAX_SESSION_COUNT_RARE);
-            MAX_SESSION_COUNT_PER_ALLOWED_TIME = mParser.getInt(
-                    KEY_MAX_SESSION_COUNT_PER_ALLOWED_TIME,
-                    DEFAULT_MAX_SESSION_COUNT_PER_ALLOWED_TIME);
+            MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = mParser.getInt(
+                    KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW,
+                    DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW);
             TIMING_SESSION_COALESCING_DURATION_MS = mParser.getLong(
                     KEY_TIMING_SESSION_COALESCING_DURATION_MS,
                     DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS);
@@ -2192,7 +2256,14 @@
             synchronized (mLock) {
                 boolean changed = false;
 
-                long newAllowedTimeMs = Math.min(MAX_PERIOD_MS,
+                long newMaxExecutionTimeMs = Math.max(MIN_MAX_EXECUTION_TIME_MS,
+                        Math.min(MAX_PERIOD_MS, MAX_EXECUTION_TIME_MS));
+                if (mMaxExecutionTimeMs != newMaxExecutionTimeMs) {
+                    mMaxExecutionTimeMs = newMaxExecutionTimeMs;
+                    mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
+                    changed = true;
+                }
+                long newAllowedTimeMs = Math.min(mMaxExecutionTimeMs,
                         Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_MS));
                 if (mAllowedTimePerPeriodMs != newAllowedTimeMs) {
                     mAllowedTimePerPeriodMs = newAllowedTimeMs;
@@ -2231,47 +2302,44 @@
                     mBucketPeriodsMs[RARE_INDEX] = newRarePeriodMs;
                     changed = true;
                 }
-                long newMaxExecutionTimeMs = Math.max(60 * MINUTE_IN_MILLIS,
-                        Math.min(MAX_PERIOD_MS, MAX_EXECUTION_TIME_MS));
-                if (mMaxExecutionTimeMs != newMaxExecutionTimeMs) {
-                    mMaxExecutionTimeMs = newMaxExecutionTimeMs;
-                    mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
+                long newRateLimitingWindowMs = Math.min(MAX_PERIOD_MS,
+                        Math.max(MIN_RATE_LIMITING_WINDOW_MS, RATE_LIMITING_WINDOW_MS));
+                if (mRateLimitingWindowMs != newRateLimitingWindowMs) {
+                    mRateLimitingWindowMs = newRateLimitingWindowMs;
                     changed = true;
                 }
-                int newMaxCountPerAllowedPeriod = Math.max(10,
-                        MAX_JOB_COUNT_PER_ALLOWED_TIME);
-                if (mMaxJobCountPerAllowedTime != newMaxCountPerAllowedPeriod) {
-                    mMaxJobCountPerAllowedTime = newMaxCountPerAllowedPeriod;
+                int newMaxJobCountPerRateLimitingWindow = Math.max(
+                        MIN_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW,
+                        MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW);
+                if (mMaxJobCountPerRateLimitingWindow != newMaxJobCountPerRateLimitingWindow) {
+                    mMaxJobCountPerRateLimitingWindow = newMaxJobCountPerRateLimitingWindow;
                     changed = true;
                 }
-                int newActiveMaxJobCount = Math.max(mMaxJobCountPerAllowedTime,
-                        Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_ACTIVE));
+                int newActiveMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_ACTIVE);
                 if (mMaxBucketJobCounts[ACTIVE_INDEX] != newActiveMaxJobCount) {
                     mMaxBucketJobCounts[ACTIVE_INDEX] = newActiveMaxJobCount;
                     changed = true;
                 }
-                int newWorkingMaxJobCount = Math.max(mMaxJobCountPerAllowedTime,
-                        Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_WORKING));
+                int newWorkingMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_WORKING);
                 if (mMaxBucketJobCounts[WORKING_INDEX] != newWorkingMaxJobCount) {
                     mMaxBucketJobCounts[WORKING_INDEX] = newWorkingMaxJobCount;
                     changed = true;
                 }
-                int newFrequentMaxJobCount = Math.max(mMaxJobCountPerAllowedTime,
-                        Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_FREQUENT));
+                int newFrequentMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_FREQUENT);
                 if (mMaxBucketJobCounts[FREQUENT_INDEX] != newFrequentMaxJobCount) {
                     mMaxBucketJobCounts[FREQUENT_INDEX] = newFrequentMaxJobCount;
                     changed = true;
                 }
-                int newRareMaxJobCount = Math.max(mMaxJobCountPerAllowedTime,
-                        Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_RARE));
+                int newRareMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_RARE);
                 if (mMaxBucketJobCounts[RARE_INDEX] != newRareMaxJobCount) {
                     mMaxBucketJobCounts[RARE_INDEX] = newRareMaxJobCount;
                     changed = true;
                 }
-                int newMaxSessionCountPerAllowedPeriod = Math.max(10,
-                        MAX_SESSION_COUNT_PER_ALLOWED_TIME);
-                if (mMaxSessionCountPerAllowedTime != newMaxSessionCountPerAllowedPeriod) {
-                    mMaxSessionCountPerAllowedTime = newMaxSessionCountPerAllowedPeriod;
+                int newMaxSessionCountPerRateLimitPeriod = Math.max(
+                        MIN_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW,
+                        MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW);
+                if (mMaxSessionCountPerRateLimitingWindow != newMaxSessionCountPerRateLimitPeriod) {
+                    mMaxSessionCountPerRateLimitingWindow = newMaxSessionCountPerRateLimitPeriod;
                     changed = true;
                 }
                 int newActiveMaxSessionCount =
@@ -2332,14 +2400,15 @@
             pw.printPair(KEY_MAX_JOB_COUNT_WORKING, MAX_JOB_COUNT_WORKING).println();
             pw.printPair(KEY_MAX_JOB_COUNT_FREQUENT, MAX_JOB_COUNT_FREQUENT).println();
             pw.printPair(KEY_MAX_JOB_COUNT_RARE, MAX_JOB_COUNT_RARE).println();
-            pw.printPair(KEY_MAX_JOB_COUNT_PER_ALLOWED_TIME, MAX_JOB_COUNT_PER_ALLOWED_TIME)
-                    .println();
+            pw.printPair(KEY_RATE_LIMITING_WINDOW_MS, RATE_LIMITING_WINDOW_MS).println();
+            pw.printPair(KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW,
+                    MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW).println();
             pw.printPair(KEY_MAX_SESSION_COUNT_ACTIVE, MAX_SESSION_COUNT_ACTIVE).println();
             pw.printPair(KEY_MAX_SESSION_COUNT_WORKING, MAX_SESSION_COUNT_WORKING).println();
             pw.printPair(KEY_MAX_SESSION_COUNT_FREQUENT, MAX_SESSION_COUNT_FREQUENT).println();
             pw.printPair(KEY_MAX_SESSION_COUNT_RARE, MAX_SESSION_COUNT_RARE).println();
-            pw.printPair(KEY_MAX_SESSION_COUNT_PER_ALLOWED_TIME, MAX_SESSION_COUNT_PER_ALLOWED_TIME)
-                    .println();
+            pw.printPair(KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW,
+                    MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW).println();
             pw.printPair(KEY_TIMING_SESSION_COALESCING_DURATION_MS,
                     TIMING_SESSION_COALESCING_DURATION_MS).println();
             pw.decreaseIndent();
@@ -2365,8 +2434,10 @@
             proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_FREQUENT,
                     MAX_JOB_COUNT_FREQUENT);
             proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_RARE, MAX_JOB_COUNT_RARE);
-            proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_PER_ALLOWED_TIME,
-                    MAX_JOB_COUNT_PER_ALLOWED_TIME);
+            proto.write(ConstantsProto.QuotaController.RATE_LIMITING_WINDOW_MS,
+                    RATE_LIMITING_WINDOW_MS);
+            proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW,
+                    MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW);
             proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_ACTIVE,
                     MAX_SESSION_COUNT_ACTIVE);
             proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_WORKING,
@@ -2375,8 +2446,8 @@
                     MAX_SESSION_COUNT_FREQUENT);
             proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_RARE,
                     MAX_SESSION_COUNT_RARE);
-            proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_PER_ALLOWED_TIME,
-                    MAX_SESSION_COUNT_PER_ALLOWED_TIME);
+            proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW,
+                    MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW);
             proto.write(ConstantsProto.QuotaController.TIMING_SESSION_COALESCING_DURATION_MS,
                     TIMING_SESSION_COALESCING_DURATION_MS);
             proto.end(qcToken);
@@ -2431,8 +2502,18 @@
     }
 
     @VisibleForTesting
-    int getMaxJobCountPerAllowedTime() {
-        return mMaxJobCountPerAllowedTime;
+    int getMaxJobCountPerRateLimitingWindow() {
+        return mMaxJobCountPerRateLimitingWindow;
+    }
+
+    @VisibleForTesting
+    int getMaxSessionCountPerRateLimitingWindow() {
+        return mMaxSessionCountPerRateLimitingWindow;
+    }
+
+    @VisibleForTesting
+    long getRateLimitingWindowMs() {
+        return mRateLimitingWindowMs;
     }
 
     @VisibleForTesting
@@ -2441,11 +2522,6 @@
     }
 
     @VisibleForTesting
-    int getMaxSessionCountPerAllowedTime() {
-        return mMaxSessionCountPerAllowedTime;
-    }
-
-    @VisibleForTesting
     @Nullable
     List<TimingSession> getTimingSessions(int userId, String packageName) {
         return mTimingSessions.get(userId, packageName);
@@ -2659,6 +2735,12 @@
                                 StateControllerProto.QuotaController.ExecutionStats.WINDOW_SIZE_MS,
                                 es.windowSizeMs);
                         proto.write(
+                                StateControllerProto.QuotaController.ExecutionStats.JOB_COUNT_LIMIT,
+                                es.jobCountLimit);
+                        proto.write(
+                                StateControllerProto.QuotaController.ExecutionStats.SESSION_COUNT_LIMIT,
+                                es.sessionCountLimit);
+                        proto.write(
                                 StateControllerProto.QuotaController.ExecutionStats.EXECUTION_TIME_IN_WINDOW_MS,
                                 es.executionTimeInWindowMs);
                         proto.write(
@@ -2674,20 +2756,20 @@
                                 StateControllerProto.QuotaController.ExecutionStats.SESSION_COUNT_IN_WINDOW,
                                 es.sessionCountInWindow);
                         proto.write(
-                                StateControllerProto.QuotaController.ExecutionStats.QUOTA_CUTOFF_TIME_ELAPSED,
-                                es.quotaCutoffTimeElapsed);
+                                StateControllerProto.QuotaController.ExecutionStats.IN_QUOTA_TIME_ELAPSED,
+                                es.inQuotaTimeElapsed);
                         proto.write(
                                 StateControllerProto.QuotaController.ExecutionStats.JOB_COUNT_EXPIRATION_TIME_ELAPSED,
-                                es.jobCountExpirationTimeElapsed);
+                                es.jobRateLimitExpirationTimeElapsed);
                         proto.write(
-                                StateControllerProto.QuotaController.ExecutionStats.JOB_COUNT_IN_ALLOWED_TIME,
-                                es.jobCountInAllowedTime);
+                                StateControllerProto.QuotaController.ExecutionStats.JOB_COUNT_IN_RATE_LIMITING_WINDOW,
+                                es.jobCountInRateLimitingWindow);
                         proto.write(
                                 StateControllerProto.QuotaController.ExecutionStats.SESSION_COUNT_EXPIRATION_TIME_ELAPSED,
-                                es.sessionCountExpirationTimeElapsed);
+                                es.sessionRateLimitExpirationTimeElapsed);
                         proto.write(
-                                StateControllerProto.QuotaController.ExecutionStats.SESSION_COUNT_IN_ALLOWED_TIME,
-                                es.sessionCountInAllowedTime);
+                                StateControllerProto.QuotaController.ExecutionStats.SESSION_COUNT_IN_RATE_LIMITING_WINDOW,
+                                es.sessionCountInRateLimitingWindow);
                         proto.end(esToken);
                     }
                 }
diff --git a/services/core/java/com/android/server/net/NetworkStatsAccess.java b/services/core/java/com/android/server/net/NetworkStatsAccess.java
index cebc472..7c1c1c7 100644
--- a/services/core/java/com/android/server/net/NetworkStatsAccess.java
+++ b/services/core/java/com/android/server/net/NetworkStatsAccess.java
@@ -109,7 +109,7 @@
         final TelephonyManager tm = (TelephonyManager)
                 context.getSystemService(Context.TELEPHONY_SERVICE);
         boolean hasCarrierPrivileges = tm != null &&
-                tm.checkCarrierPrivilegesForPackage(callingPackage) ==
+                tm.checkCarrierPrivilegesForPackageAnyPhone(callingPackage) ==
                         TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
         boolean isDeviceOwner = dpmi != null && dpmi.isActiveAdminWithPolicy(callingUid,
                 DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
diff --git a/services/core/java/com/android/server/net/NetworkStatsFactory.java b/services/core/java/com/android/server/net/NetworkStatsFactory.java
index 69efd02..473cc97 100644
--- a/services/core/java/com/android/server/net/NetworkStatsFactory.java
+++ b/services/core/java/com/android/server/net/NetworkStatsFactory.java
@@ -263,6 +263,10 @@
         return stats;
     }
 
+    /**
+     * @deprecated Use NetworkStatsService#getDetailedUidStats which also accounts for
+     * VPN traffic
+     */
     public NetworkStats readNetworkStatsDetail() throws IOException {
         return readNetworkStatsDetail(UID_ALL, null, TAG_ALL, null);
     }
diff --git a/services/core/java/com/android/server/net/NetworkStatsRecorder.java b/services/core/java/com/android/server/net/NetworkStatsRecorder.java
index a2e7e0c..bdff500 100644
--- a/services/core/java/com/android/server/net/NetworkStatsRecorder.java
+++ b/services/core/java/com/android/server/net/NetworkStatsRecorder.java
@@ -41,10 +41,10 @@
 import com.android.internal.util.FileRotator;
 import com.android.internal.util.IndentingPrintWriter;
 
-import libcore.io.IoUtils;
-
 import com.google.android.collect.Sets;
 
+import libcore.io.IoUtils;
+
 import java.io.ByteArrayOutputStream;
 import java.io.DataOutputStream;
 import java.io.File;
@@ -234,7 +234,7 @@
 
         if (vpnArray != null) {
             for (VpnInfo info : vpnArray) {
-                delta.migrateTun(info.ownerUid, info.vpnIface, info.primaryUnderlyingIface);
+                delta.migrateTun(info.ownerUid, info.vpnIface, info.underlyingIfaces);
             }
         }
 
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index f34ace5..a13368f 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -293,6 +293,22 @@
     /** Data layer operation counters for splicing into other structures. */
     private NetworkStats mUidOperations = new NetworkStats(0L, 10);
 
+    /**
+     * Snapshot containing most recent network stats for all UIDs across all interfaces and tags
+     * since boot.
+     *
+     * <p>Maintains migrated VPN stats which are result of performing TUN migration on {@link
+     * #mLastUidDetailSnapshot}.
+     */
+    @GuardedBy("mStatsLock")
+    private NetworkStats mTunAdjustedStats;
+    /**
+     * Used by {@link #mTunAdjustedStats} to migrate VPN traffic over delta between this snapshot
+     * and latest snapshot.
+     */
+    @GuardedBy("mStatsLock")
+    private NetworkStats mLastUidDetailSnapshot;
+
     /** Must be set in factory by calling #setHandler. */
     private Handler mHandler;
     private Handler.Callback mHandlerCallback;
@@ -812,15 +828,39 @@
     @Override
     public NetworkStats getDetailedUidStats(String[] requiredIfaces) {
         try {
+            // Get the latest snapshot from NetworkStatsFactory.
+            // TODO: Querying for INTERFACES_ALL may incur performance penalty. Consider restricting
+            // this to limited set of ifaces.
+            NetworkStats uidDetailStats = getNetworkStatsUidDetail(INTERFACES_ALL);
+
+            // Migrate traffic from VPN UID over delta and update mTunAdjustedStats.
+            NetworkStats result;
+            synchronized (mStatsLock) {
+                migrateTunTraffic(uidDetailStats, mVpnInfos);
+                result = mTunAdjustedStats.clone();
+            }
+
+            // Apply filter based on ifacesToQuery.
             final String[] ifacesToQuery =
                     NetworkStatsFactory.augmentWithStackedInterfaces(requiredIfaces);
-            return getNetworkStatsUidDetail(ifacesToQuery);
+            result.filter(UID_ALL, ifacesToQuery, TAG_ALL);
+            return result;
         } catch (RemoteException e) {
             Log.wtf(TAG, "Error compiling UID stats", e);
             return new NetworkStats(0L, 0);
         }
     }
 
+    @VisibleForTesting
+    NetworkStats getTunAdjustedStats() {
+        synchronized (mStatsLock) {
+            if (mTunAdjustedStats == null) {
+                return null;
+            }
+            return mTunAdjustedStats.clone();
+        }
+    }
+
     @Override
     public String[] getMobileIfaces() {
         return mMobileIfaces;
@@ -1295,6 +1335,34 @@
         // a race condition between the service handler thread and the observer's
         mStatsObservers.updateStats(xtSnapshot, uidSnapshot, new ArrayMap<>(mActiveIfaces),
                 new ArrayMap<>(mActiveUidIfaces), vpnArray, currentTime);
+
+        migrateTunTraffic(uidSnapshot, vpnArray);
+    }
+
+    /**
+     * Updates {@link #mTunAdjustedStats} with the delta containing traffic migrated off of VPNs.
+     */
+    @GuardedBy("mStatsLock")
+    private void migrateTunTraffic(NetworkStats uidDetailStats, VpnInfo[] vpnInfoArray) {
+        if (mTunAdjustedStats == null) {
+            // Either device booted or system server restarted, hence traffic cannot be migrated
+            // correctly without knowing the past state of VPN's underlying networks.
+            mTunAdjustedStats = uidDetailStats;
+            mLastUidDetailSnapshot = uidDetailStats;
+            return;
+        }
+        // Migrate delta traffic from VPN to other apps.
+        NetworkStats delta = uidDetailStats.subtract(mLastUidDetailSnapshot);
+        for (VpnInfo info : vpnInfoArray) {
+            delta.migrateTun(info.ownerUid, info.vpnIface, info.underlyingIfaces);
+        }
+        // Filter out debug entries as that may lead to over counting.
+        delta.filterDebugEntries();
+        // Update #mTunAdjustedStats with migrated delta.
+        mTunAdjustedStats.combineAllValues(delta);
+        mTunAdjustedStats.setElapsedRealtime(uidDetailStats.getElapsedRealtime());
+        // Update last snapshot.
+        mLastUidDetailSnapshot = uidDetailStats;
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 2a61fee..d35f952 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -17080,6 +17080,13 @@
                         cleanUpAppIdCreation(result);
                     }
                 }
+                // TODO(patb): create a more descriptive reason than unknown in future release
+                // mark all non-failure installs as UNKNOWN so we do not treat them as success
+                for (InstallRequest request : requests) {
+                    if (request.installResult.returnCode == PackageManager.INSTALL_SUCCEEDED) {
+                        request.installResult.returnCode = PackageManager.INSTALL_UNKNOWN;
+                    }
+                }
             }
             for (PrepareResult result : prepareResults.values()) {
                 if (result.freezer != null) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index f5c8049..81de8e2 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -2496,9 +2496,9 @@
                 default:
                     throw new IllegalArgumentException("Unknown option " + opt);
             }
-            if (replaceExisting) {
-                sessionParams.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
-            }
+        }
+        if (replaceExisting) {
+            sessionParams.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
         }
         return params;
     }
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index eec4b70..d6e87aa 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -674,9 +674,10 @@
             return new ArrayList<>();
         }
 
-        // Get the list of all dynamic shortcuts in this package
+        // Get the list of all dynamic shortcuts in this package.
         final ArrayList<ShortcutInfo> shortcuts = new ArrayList<>();
-        findAll(shortcuts, ShortcutInfo::isDynamicVisible, ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);
+        findAll(shortcuts, ShortcutInfo::isDynamicVisible,
+                ShortcutInfo.CLONE_REMOVE_FOR_APP_PREDICTION);
 
         final List<ShortcutManager.ShareShortcutInfo> result = new ArrayList<>();
         for (int i = 0; i < shortcuts.size(); i++) {
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index e2bbb2d..cfd3ae6 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -2085,7 +2085,8 @@
                     nextTimeout = -1;
                 }
 
-                if ((mUserActivitySummary & USER_ACTIVITY_SCREEN_BRIGHT) != 0) {
+                if ((mUserActivitySummary & USER_ACTIVITY_SCREEN_BRIGHT) != 0
+                        && (mWakeLockSummary & WAKE_LOCK_STAY_AWAKE) == 0) {
                     nextTimeout = mAttentionDetector.updateUserActivity(nextTimeout);
                 }
 
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
index 476a273..165055a 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
@@ -19,6 +19,7 @@
 import android.content.pm.PackageInfo;
 import android.os.AsyncTask;
 import android.os.UserHandle;
+import android.util.Slog;
 import android.webkit.WebViewProviderInfo;
 import android.webkit.WebViewProviderResponse;
 
@@ -154,7 +155,10 @@
         WebViewProviderInfo[] webviewProviders = mSystemInterface.getWebViewPackages();
         WebViewProviderInfo fallbackProvider = getFallbackProvider(webviewProviders);
         if (fallbackProvider != null) {
+            Slog.i(TAG, "One-time migration: enabling " + fallbackProvider.packageName);
             mSystemInterface.enablePackageForAllUsers(mContext, fallbackProvider.packageName, true);
+        } else {
+            Slog.i(TAG, "Skipping one-time migration: no fallback provider");
         }
         mSystemInterface.enableFallbackLogic(false);
     }
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index c5b2566..434239f 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -231,6 +231,11 @@
                 mService.mRootActivityContainer.sendPowerHintForLaunchEndIfNeeded();
             }
 
+            // Once the target is shown, prevent spurious background app switches
+            if (reorderMode == REORDER_MOVE_TO_TOP) {
+                mService.stopAppSwitches();
+            }
+
             mService.mH.post(
                     () -> mService.mAmInternal.setRunningRemoteAnimation(mCallingPid, false));
 
diff --git a/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
index 854537b..fb781b0 100644
--- a/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
+++ b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
@@ -113,7 +113,12 @@
         // statistics because it passes every touch event though a GestureDetector. By creating an
         // anonymous subclass of GestureDetector, these statistics will be recorded with a unique
         // source name that can be filtered.
-        mGestureDetector = new GestureDetector(mContext, new FlingGestureDetector(), mHandler) {};
+
+        // GestureDetector would get a ViewConfiguration instance by context, that may also
+        // create a new WindowManagerImpl for the new display, and lock WindowManagerGlobal
+        // temporarily in the constructor that would make a deadlock.
+        mHandler.post(() -> mGestureDetector =
+                new GestureDetector(mContext, new FlingGestureDetector(), mHandler) {});
     }
 
     @Override
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 4e89357..1db6b8e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -386,6 +386,8 @@
         ExecutionStats expectedStats = new ExecutionStats();
         expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
         expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
+        expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE;
+        expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE;
 
         final int uid = 10001;
         mQuotaController.onAppRemovedLocked("com.android.test.remove", uid);
@@ -424,6 +426,8 @@
         ExecutionStats expectedStats = new ExecutionStats();
         expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
         expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
+        expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE;
+        expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE;
 
         mQuotaController.onUserRemovedLocked(0);
         assertNull(mQuotaController.getTimingSessions(0, "com.android.test"));
@@ -456,6 +460,8 @@
         ExecutionStats inputStats = new ExecutionStats();
 
         inputStats.windowSizeMs = expectedStats.windowSizeMs = 12 * HOUR_IN_MILLIS;
+        inputStats.jobCountLimit = expectedStats.jobCountLimit = 100;
+        inputStats.sessionCountLimit = expectedStats.sessionCountLimit = 100;
         // Invalid time is now +24 hours since there are no sessions at all for the app.
         expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
         mQuotaController.updateExecutionStatsLocked(0, "com.android.test.not.run", inputStats);
@@ -520,6 +526,7 @@
         assertEquals(expectedStats, inputStats);
 
         inputStats.windowSizeMs = expectedStats.windowSizeMs = HOUR_IN_MILLIS;
+        inputStats.sessionCountLimit = expectedStats.sessionCountLimit = 2;
         // Invalid time is now since the start of the session is at the very edge of the window
         // cutoff time.
         expectedStats.expirationTimeElapsed = now;
@@ -528,10 +535,13 @@
         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInMaxPeriod = 15;
         expectedStats.sessionCountInWindow = 3;
+        expectedStats.inQuotaTimeElapsed = now + 11 * MINUTE_IN_MILLIS;
         mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
         assertEquals(expectedStats, inputStats);
 
         inputStats.windowSizeMs = expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
+        inputStats.jobCountLimit = expectedStats.jobCountLimit = 6;
+        inputStats.sessionCountLimit = expectedStats.sessionCountLimit = 100;
         // Invalid time is now since the session straddles the window cutoff time.
         expectedStats.expirationTimeElapsed = now;
         expectedStats.executionTimeInWindowMs = 11 * MINUTE_IN_MILLIS;
@@ -539,8 +549,7 @@
         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInMaxPeriod = 15;
         expectedStats.sessionCountInWindow = 4;
-        expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
-                + mQcConstants.IN_QUOTA_BUFFER_MS;
+        expectedStats.inQuotaTimeElapsed = now + 5 * MINUTE_IN_MILLIS;
         mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
         assertEquals(expectedStats, inputStats);
 
@@ -553,8 +562,9 @@
         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInMaxPeriod = 15;
         expectedStats.sessionCountInWindow = 4;
-        expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
-                + mQcConstants.IN_QUOTA_BUFFER_MS;
+        // App goes under job execution time limit in ~61 minutes, but will be under job count limit
+        // in 65 minutes.
+        expectedStats.inQuotaTimeElapsed = now + 65 * MINUTE_IN_MILLIS;
         mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
         assertEquals(expectedStats, inputStats);
 
@@ -567,8 +577,7 @@
         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInMaxPeriod = 15;
         expectedStats.sessionCountInWindow = 5;
-        expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
-                + mQcConstants.IN_QUOTA_BUFFER_MS;
+        expectedStats.inQuotaTimeElapsed = now + 4 * HOUR_IN_MILLIS + 5 * MINUTE_IN_MILLIS;
         mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
         assertEquals(expectedStats, inputStats);
 
@@ -577,6 +586,7 @@
                 .add(0,
                         createTimingSession(now - (23 * HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 3));
         inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
+        inputStats.jobCountLimit = expectedStats.jobCountLimit = 100;
         // Invalid time is now +1 hour since the earliest session in the max period is 1 hour
         // before the end of the max period cutoff time.
         expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
@@ -585,7 +595,7 @@
         expectedStats.executionTimeInMaxPeriodMs = 23 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInMaxPeriod = 18;
         expectedStats.sessionCountInWindow = 5;
-        expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
+        expectedStats.inQuotaTimeElapsed = now + 6 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS
                 + mQcConstants.IN_QUOTA_BUFFER_MS;
         mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
         assertEquals(expectedStats, inputStats);
@@ -602,7 +612,7 @@
         expectedStats.executionTimeInMaxPeriodMs = 24 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInMaxPeriod = 20;
         expectedStats.sessionCountInWindow = 5;
-        expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
+        expectedStats.inQuotaTimeElapsed = now + 6 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS
                 + mQcConstants.IN_QUOTA_BUFFER_MS;
         mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
         assertEquals(expectedStats, inputStats);
@@ -627,6 +637,8 @@
 
         // Active
         expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS;
+        expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE;
+        expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE;
         expectedStats.expirationTimeElapsed = now + 4 * MINUTE_IN_MILLIS;
         expectedStats.executionTimeInWindowMs = 3 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInWindow = 5;
@@ -638,45 +650,103 @@
 
         // Working
         expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
+        expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_WORKING;
+        expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_WORKING;
         expectedStats.expirationTimeElapsed = now;
         expectedStats.executionTimeInWindowMs = 13 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInWindow = 10;
         expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInMaxPeriod = 20;
         expectedStats.sessionCountInWindow = 2;
-        expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS)
+        expectedStats.inQuotaTimeElapsed = now + 3 * MINUTE_IN_MILLIS
                 + mQcConstants.IN_QUOTA_BUFFER_MS;
         assertEquals(expectedStats,
                 mQuotaController.getExecutionStatsLocked(0, "com.android.test", WORKING_INDEX));
 
         // Frequent
         expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
+        expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_FREQUENT;
+        expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_FREQUENT;
         expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
         expectedStats.executionTimeInWindowMs = 23 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInWindow = 15;
         expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInMaxPeriod = 20;
         expectedStats.sessionCountInWindow = 3;
-        expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS)
+        expectedStats.inQuotaTimeElapsed = now + 6 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS
                 + mQcConstants.IN_QUOTA_BUFFER_MS;
         assertEquals(expectedStats,
                 mQuotaController.getExecutionStatsLocked(0, "com.android.test", FREQUENT_INDEX));
 
         // Rare
         expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
+        expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE;
+        expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE;
         expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
         expectedStats.executionTimeInWindowMs = 33 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInWindow = 20;
         expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInMaxPeriod = 20;
         expectedStats.sessionCountInWindow = 4;
-        expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS)
+        expectedStats.inQuotaTimeElapsed = now + 22 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS
                 + mQcConstants.IN_QUOTA_BUFFER_MS;
         assertEquals(expectedStats,
                 mQuotaController.getExecutionStatsLocked(0, "com.android.test", RARE_INDEX));
     }
 
     /**
+     * Tests that getExecutionStatsLocked returns the correct stats soon after device startup.
+     */
+    @Test
+    public void testGetExecutionStatsLocked_Values_BeginningOfTime() {
+        // Set time to 3 minutes after boot.
+        advanceElapsedClock(-JobSchedulerService.sElapsedRealtimeClock.millis());
+        advanceElapsedClock(3 * MINUTE_IN_MILLIS);
+
+        mQuotaController.saveTimingSession(0, "com.android.test",
+                createTimingSession(MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 2));
+
+        ExecutionStats expectedStats = new ExecutionStats();
+
+        // Active
+        expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS;
+        expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE;
+        expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE;
+        expectedStats.expirationTimeElapsed = 11 * MINUTE_IN_MILLIS;
+        expectedStats.executionTimeInWindowMs = MINUTE_IN_MILLIS;
+        expectedStats.bgJobCountInWindow = 2;
+        expectedStats.executionTimeInMaxPeriodMs = MINUTE_IN_MILLIS;
+        expectedStats.bgJobCountInMaxPeriod = 2;
+        expectedStats.sessionCountInWindow = 1;
+        assertEquals(expectedStats,
+                mQuotaController.getExecutionStatsLocked(0, "com.android.test", ACTIVE_INDEX));
+
+        // Working
+        expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
+        expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_WORKING;
+        expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_WORKING;
+        expectedStats.expirationTimeElapsed = 2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS;
+        assertEquals(expectedStats,
+                mQuotaController.getExecutionStatsLocked(0, "com.android.test", WORKING_INDEX));
+
+        // Frequent
+        expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
+        expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_FREQUENT;
+        expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_FREQUENT;
+        expectedStats.expirationTimeElapsed = 8 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS;
+        assertEquals(expectedStats,
+                mQuotaController.getExecutionStatsLocked(0, "com.android.test", FREQUENT_INDEX));
+
+        // Rare
+        expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
+        expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE;
+        expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE;
+        expectedStats.expirationTimeElapsed = 24 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS;
+        assertEquals(expectedStats,
+                mQuotaController.getExecutionStatsLocked(0, "com.android.test", RARE_INDEX));
+    }
+
+    /**
      * Tests that getExecutionStatsLocked returns the correct timing session stats when coalescing.
      */
     @Test
@@ -850,13 +920,15 @@
 
         ExecutionStats expectedStats = new ExecutionStats();
         expectedStats.windowSizeMs = originalStatsActive.windowSizeMs;
+        expectedStats.jobCountLimit = originalStatsActive.jobCountLimit;
+        expectedStats.sessionCountLimit = originalStatsActive.sessionCountLimit;
         expectedStats.expirationTimeElapsed = originalStatsActive.expirationTimeElapsed;
         expectedStats.executionTimeInWindowMs = originalStatsActive.executionTimeInWindowMs;
         expectedStats.bgJobCountInWindow = originalStatsActive.bgJobCountInWindow;
         expectedStats.executionTimeInMaxPeriodMs = originalStatsActive.executionTimeInMaxPeriodMs;
         expectedStats.bgJobCountInMaxPeriod = originalStatsActive.bgJobCountInMaxPeriod;
         expectedStats.sessionCountInWindow = originalStatsActive.sessionCountInWindow;
-        expectedStats.quotaCutoffTimeElapsed = originalStatsActive.quotaCutoffTimeElapsed;
+        expectedStats.inQuotaTimeElapsed = originalStatsActive.inQuotaTimeElapsed;
         final ExecutionStats newStatsActive = mQuotaController.getExecutionStatsLocked(0,
                 "com.android.test", ACTIVE_INDEX);
         // Stats for the same bucket should use the same object.
@@ -864,33 +936,39 @@
         assertEquals(expectedStats, newStatsActive);
 
         expectedStats.windowSizeMs = originalStatsWorking.windowSizeMs;
+        expectedStats.jobCountLimit = originalStatsWorking.jobCountLimit;
+        expectedStats.sessionCountLimit = originalStatsWorking.sessionCountLimit;
         expectedStats.expirationTimeElapsed = originalStatsWorking.expirationTimeElapsed;
         expectedStats.executionTimeInWindowMs = originalStatsWorking.executionTimeInWindowMs;
         expectedStats.bgJobCountInWindow = originalStatsWorking.bgJobCountInWindow;
         expectedStats.sessionCountInWindow = originalStatsWorking.sessionCountInWindow;
-        expectedStats.quotaCutoffTimeElapsed = originalStatsWorking.quotaCutoffTimeElapsed;
+        expectedStats.inQuotaTimeElapsed = originalStatsWorking.inQuotaTimeElapsed;
         final ExecutionStats newStatsWorking = mQuotaController.getExecutionStatsLocked(0,
                 "com.android.test", WORKING_INDEX);
         assertTrue(originalStatsWorking == newStatsWorking);
         assertNotEquals(expectedStats, newStatsWorking);
 
         expectedStats.windowSizeMs = originalStatsFrequent.windowSizeMs;
+        expectedStats.jobCountLimit = originalStatsFrequent.jobCountLimit;
+        expectedStats.sessionCountLimit = originalStatsFrequent.sessionCountLimit;
         expectedStats.expirationTimeElapsed = originalStatsFrequent.expirationTimeElapsed;
         expectedStats.executionTimeInWindowMs = originalStatsFrequent.executionTimeInWindowMs;
         expectedStats.bgJobCountInWindow = originalStatsFrequent.bgJobCountInWindow;
         expectedStats.sessionCountInWindow = originalStatsFrequent.sessionCountInWindow;
-        expectedStats.quotaCutoffTimeElapsed = originalStatsFrequent.quotaCutoffTimeElapsed;
+        expectedStats.inQuotaTimeElapsed = originalStatsFrequent.inQuotaTimeElapsed;
         final ExecutionStats newStatsFrequent = mQuotaController.getExecutionStatsLocked(0,
                 "com.android.test", FREQUENT_INDEX);
         assertTrue(originalStatsFrequent == newStatsFrequent);
         assertNotEquals(expectedStats, newStatsFrequent);
 
         expectedStats.windowSizeMs = originalStatsRare.windowSizeMs;
+        expectedStats.jobCountLimit = originalStatsRare.jobCountLimit;
+        expectedStats.sessionCountLimit = originalStatsRare.sessionCountLimit;
         expectedStats.expirationTimeElapsed = originalStatsRare.expirationTimeElapsed;
         expectedStats.executionTimeInWindowMs = originalStatsRare.executionTimeInWindowMs;
         expectedStats.bgJobCountInWindow = originalStatsRare.bgJobCountInWindow;
         expectedStats.sessionCountInWindow = originalStatsRare.sessionCountInWindow;
-        expectedStats.quotaCutoffTimeElapsed = originalStatsRare.quotaCutoffTimeElapsed;
+        expectedStats.inQuotaTimeElapsed = originalStatsRare.inQuotaTimeElapsed;
         final ExecutionStats newStatsRare = mQuotaController.getExecutionStatsLocked(0,
                 "com.android.test", RARE_INDEX);
         assertTrue(originalStatsRare == newStatsRare);
@@ -1065,7 +1143,7 @@
     public void testIsWithinQuotaLocked_UnderDuration_OverJobCount() {
         setDischarging();
         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
-        final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_ALLOWED_TIME;
+        final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
         mQuotaController.saveTimingSession(0, "com.android.test.spam",
                 createTimingSession(now - (HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 25));
         mQuotaController.saveTimingSession(0, "com.android.test.spam",
@@ -1100,7 +1178,7 @@
     public void testIsWithinQuotaLocked_OverDuration_OverJobCount() {
         setDischarging();
         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
-        final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_ALLOWED_TIME;
+        final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
         mQuotaController.saveTimingSession(0, "com.android.test",
                 createTimingSession(now - (HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 25));
         mQuotaController.saveTimingSession(0, "com.android.test",
@@ -1141,7 +1219,7 @@
         advanceElapsedClock(10 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS);
 
         assertEquals(2, mQuotaController.getExecutionStatsLocked(
-                SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX).jobCountInAllowedTime);
+                SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX).jobCountInRateLimitingWindow);
         assertTrue(mQuotaController.isWithinQuotaLocked(jobStatus));
     }
 
@@ -1212,9 +1290,9 @@
                 mQuotaController.getTimingSessions(SOURCE_USER_ID, fgChangerPkgName).size());
         for (int i = ACTIVE_INDEX; i < RARE_INDEX; ++i) {
             assertEquals(42, mQuotaController.getExecutionStatsLocked(
-                    SOURCE_USER_ID, fgChangerPkgName, i).jobCountInAllowedTime);
+                    SOURCE_USER_ID, fgChangerPkgName, i).jobCountInRateLimitingWindow);
             assertEquals(1, mQuotaController.getExecutionStatsLocked(
-                    SOURCE_USER_ID, unaffectedPkgName, i).jobCountInAllowedTime);
+                    SOURCE_USER_ID, unaffectedPkgName, i).jobCountInRateLimitingWindow);
         }
     }
 
@@ -1555,26 +1633,29 @@
     }
 
     @Test
-    public void testMaybeScheduleStartAlarmLocked_JobCount_AllowedTime() {
+    public void testMaybeScheduleStartAlarmLocked_JobCount_RateLimitingWindow() {
+        // Set rate limiting period different from allowed time to confirm code sets based on
+        // the former.
+        mQcConstants.ALLOWED_TIME_PER_PERIOD_MS = 10 * MINUTE_IN_MILLIS;
+        mQcConstants.RATE_LIMITING_WINDOW_MS = 5 * MINUTE_IN_MILLIS;
+        mQcConstants.updateConstants();
+
         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
         final int standbyBucket = WORKING_INDEX;
         ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
                 SOURCE_PACKAGE, standbyBucket);
-        stats.jobCountInAllowedTime =
-                mQcConstants.MAX_JOB_COUNT_PER_ALLOWED_TIME + 2;
+        stats.jobCountInRateLimitingWindow =
+                mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW + 2;
 
         // Invalid time in the past, so the count shouldn't be used.
-        stats.jobCountExpirationTimeElapsed =
-                now - mQuotaController.getAllowedTimePerPeriodMs() / 2;
+        stats.jobRateLimitExpirationTimeElapsed = now - 5 * MINUTE_IN_MILLIS / 2;
         mQuotaController.maybeScheduleStartAlarmLocked(
                 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
-        // Invalid time in the future, so the count should be used.
-        stats.jobCountExpirationTimeElapsed =
-                now + mQuotaController.getAllowedTimePerPeriodMs() / 2;
-        final long expectedWorkingAlarmTime =
-                stats.jobCountExpirationTimeElapsed + mQuotaController.getAllowedTimePerPeriodMs();
+        // Valid time in the future, so the count should be used.
+        stats.jobRateLimitExpirationTimeElapsed = now + 5 * MINUTE_IN_MILLIS / 2;
+        final long expectedWorkingAlarmTime = stats.jobRateLimitExpirationTimeElapsed;
         mQuotaController.maybeScheduleStartAlarmLocked(
                 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         verify(mAlarmManager, times(1))
@@ -1732,12 +1813,13 @@
         mQcConstants.MAX_JOB_COUNT_WORKING = 4000;
         mQcConstants.MAX_JOB_COUNT_FREQUENT = 3000;
         mQcConstants.MAX_JOB_COUNT_RARE = 2000;
-        mQcConstants.MAX_JOB_COUNT_PER_ALLOWED_TIME = 500;
+        mQcConstants.RATE_LIMITING_WINDOW_MS = 15 * MINUTE_IN_MILLIS;
+        mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 500;
         mQcConstants.MAX_SESSION_COUNT_ACTIVE = 500;
         mQcConstants.MAX_SESSION_COUNT_WORKING = 400;
         mQcConstants.MAX_SESSION_COUNT_FREQUENT = 300;
         mQcConstants.MAX_SESSION_COUNT_RARE = 200;
-        mQcConstants.MAX_SESSION_COUNT_PER_ALLOWED_TIME = 50;
+        mQcConstants.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 50;
         mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = 10 * SECOND_IN_MILLIS;
 
         mQcConstants.updateConstants();
@@ -1750,12 +1832,13 @@
                 mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
         assertEquals(60 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
         assertEquals(3 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
-        assertEquals(500, mQuotaController.getMaxJobCountPerAllowedTime());
+        assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getRateLimitingWindowMs());
+        assertEquals(500, mQuotaController.getMaxJobCountPerRateLimitingWindow());
         assertEquals(5000, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]);
         assertEquals(4000, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]);
         assertEquals(3000, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]);
         assertEquals(2000, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]);
-        assertEquals(50, mQuotaController.getMaxSessionCountPerAllowedTime());
+        assertEquals(50, mQuotaController.getMaxSessionCountPerRateLimitingWindow());
         assertEquals(500, mQuotaController.getBucketMaxSessionCounts()[ACTIVE_INDEX]);
         assertEquals(400, mQuotaController.getBucketMaxSessionCounts()[WORKING_INDEX]);
         assertEquals(300, mQuotaController.getBucketMaxSessionCounts()[FREQUENT_INDEX]);
@@ -1778,12 +1861,13 @@
         mQcConstants.MAX_JOB_COUNT_WORKING = 1;
         mQcConstants.MAX_JOB_COUNT_FREQUENT = 1;
         mQcConstants.MAX_JOB_COUNT_RARE = 1;
-        mQcConstants.MAX_JOB_COUNT_PER_ALLOWED_TIME = 0;
+        mQcConstants.RATE_LIMITING_WINDOW_MS = 15 * SECOND_IN_MILLIS;
+        mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 0;
         mQcConstants.MAX_SESSION_COUNT_ACTIVE = -1;
-        mQcConstants.MAX_SESSION_COUNT_WORKING = 1;
-        mQcConstants.MAX_SESSION_COUNT_FREQUENT = 2;
-        mQcConstants.MAX_SESSION_COUNT_RARE = 1;
-        mQcConstants.MAX_SESSION_COUNT_PER_ALLOWED_TIME = 0;
+        mQcConstants.MAX_SESSION_COUNT_WORKING = 0;
+        mQcConstants.MAX_SESSION_COUNT_FREQUENT = -3;
+        mQcConstants.MAX_SESSION_COUNT_RARE = 0;
+        mQcConstants.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 0;
         mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = -1;
 
         mQcConstants.updateConstants();
@@ -1795,16 +1879,17 @@
         assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
         assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
         assertEquals(HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
-        assertEquals(10, mQuotaController.getMaxJobCountPerAllowedTime());
-        assertEquals(100, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]);
-        assertEquals(100, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]);
-        assertEquals(100, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]);
-        assertEquals(100, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]);
-        assertEquals(10, mQuotaController.getMaxSessionCountPerAllowedTime());
-        assertEquals(3, mQuotaController.getBucketMaxSessionCounts()[ACTIVE_INDEX]);
-        assertEquals(3, mQuotaController.getBucketMaxSessionCounts()[WORKING_INDEX]);
-        assertEquals(3, mQuotaController.getBucketMaxSessionCounts()[FREQUENT_INDEX]);
-        assertEquals(3, mQuotaController.getBucketMaxSessionCounts()[RARE_INDEX]);
+        assertEquals(30 * SECOND_IN_MILLIS, mQuotaController.getRateLimitingWindowMs());
+        assertEquals(10, mQuotaController.getMaxJobCountPerRateLimitingWindow());
+        assertEquals(10, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]);
+        assertEquals(10, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]);
+        assertEquals(10, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]);
+        assertEquals(10, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]);
+        assertEquals(10, mQuotaController.getMaxSessionCountPerRateLimitingWindow());
+        assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[ACTIVE_INDEX]);
+        assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[WORKING_INDEX]);
+        assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[FREQUENT_INDEX]);
+        assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[RARE_INDEX]);
         assertEquals(0, mQuotaController.getTimingSessionCoalescingDurationMs());
 
         // Test larger than a day. Controller should cap at one day.
@@ -1815,6 +1900,7 @@
         mQcConstants.WINDOW_SIZE_FREQUENT_MS = 25 * HOUR_IN_MILLIS;
         mQcConstants.WINDOW_SIZE_RARE_MS = 25 * HOUR_IN_MILLIS;
         mQcConstants.MAX_EXECUTION_TIME_MS = 25 * HOUR_IN_MILLIS;
+        mQcConstants.RATE_LIMITING_WINDOW_MS = 25 * HOUR_IN_MILLIS;
         mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = 25 * HOUR_IN_MILLIS;
 
         mQcConstants.updateConstants();
@@ -1826,6 +1912,7 @@
         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
+        assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getRateLimitingWindowMs());
         assertEquals(15 * MINUTE_IN_MILLIS,
                 mQuotaController.getTimingSessionCoalescingDurationMs());
     }
@@ -2126,7 +2213,7 @@
         assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
         ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
                 SOURCE_PACKAGE, standbyBucket);
-        assertEquals(0, stats.jobCountInAllowedTime);
+        assertEquals(0, stats.jobCountInRateLimitingWindow);
 
         setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
         mQuotaController.prepareForExecutionLocked(jobFg1);
@@ -2138,7 +2225,7 @@
         mQuotaController.maybeStopTrackingJobLocked(jobFg2, null, false);
         assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
 
-        assertEquals(0, stats.jobCountInAllowedTime);
+        assertEquals(0, stats.jobCountInRateLimitingWindow);
     }
 
     /**
@@ -2154,7 +2241,7 @@
 
         ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
                 SOURCE_PACKAGE, standbyBucket);
-        assertEquals(0, stats.jobCountInAllowedTime);
+        assertEquals(0, stats.jobCountInRateLimitingWindow);
 
         setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
         mQuotaController.prepareForExecutionLocked(jobBg1);
@@ -2165,7 +2252,7 @@
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
 
-        assertEquals(2, stats.jobCountInAllowedTime);
+        assertEquals(2, stats.jobCountInRateLimitingWindow);
     }
 
     /**
@@ -2421,10 +2508,10 @@
 
     /**
      * Tests that the start alarm is properly scheduled when a job has been throttled due to the job
-     * count quota.
+     * count rate limiting.
      */
     @Test
-    public void testStartAlarmScheduled_JobCount_AllowedTime() {
+    public void testStartAlarmScheduled_JobCount_RateLimitingWindow() {
         // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
         // because it schedules an alarm too. Prevent it from doing so.
         spyOn(mQuotaController);
@@ -2432,7 +2519,7 @@
 
         // Essentially disable session throttling.
         mQcConstants.MAX_SESSION_COUNT_WORKING =
-                mQcConstants.MAX_SESSION_COUNT_PER_ALLOWED_TIME = Integer.MAX_VALUE;
+                mQcConstants.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = Integer.MAX_VALUE;
         mQcConstants.updateConstants();
 
         final int standbyBucket = WORKING_INDEX;
@@ -2444,7 +2531,7 @@
         verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Ran jobs up to the job limit. All of them should be allowed to run.
-        for (int i = 0; i < mQcConstants.MAX_JOB_COUNT_PER_ALLOWED_TIME; ++i) {
+        for (int i = 0; i < mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW; ++i) {
             JobStatus job = createJobStatus("testStartAlarmScheduled_JobCount_AllowedTime", i);
             setStandbyBucket(WORKING_INDEX, job);
             mQuotaController.maybeStartTrackingJobLocked(job, null);
@@ -2466,18 +2553,17 @@
 
         ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
                 SOURCE_PACKAGE, standbyBucket);
-        final long expectedWorkingAlarmTime =
-                stats.jobCountExpirationTimeElapsed + mQcConstants.ALLOWED_TIME_PER_PERIOD_MS;
+        final long expectedWorkingAlarmTime = stats.jobRateLimitExpirationTimeElapsed;
         verify(mAlarmManager, times(1))
                 .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
     }
 
     /**
-     * Tests that the start alarm is properly scheduled when a job has been throttled due to the job
-     * count quota.
+     * Tests that the start alarm is properly scheduled when a job has been throttled due to the
+     * session count rate limiting.
      */
     @Test
-    public void testStartAlarmScheduled_TimingSessionCount_AllowedTime() {
+    public void testStartAlarmScheduled_TimingSessionCount_RateLimitingWindow() {
         // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
         // because it schedules an alarm too. Prevent it from doing so.
         spyOn(mQuotaController);
@@ -2485,10 +2571,10 @@
 
         // Essentially disable job count throttling.
         mQcConstants.MAX_JOB_COUNT_FREQUENT =
-                mQcConstants.MAX_JOB_COUNT_PER_ALLOWED_TIME = Integer.MAX_VALUE;
-        // Make sure throttling is because of COUNT_PER_ALLOWED_TIME.
+                mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = Integer.MAX_VALUE;
+        // Make sure throttling is because of COUNT_PER_RATE_LIMITING_WINDOW.
         mQcConstants.MAX_SESSION_COUNT_FREQUENT =
-                mQcConstants.MAX_SESSION_COUNT_PER_ALLOWED_TIME + 1;
+                mQcConstants.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW + 1;
         mQcConstants.updateConstants();
 
         final int standbyBucket = FREQUENT_INDEX;
@@ -2500,7 +2586,7 @@
         verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Ran jobs up to the job limit. All of them should be allowed to run.
-        for (int i = 0; i < mQcConstants.MAX_SESSION_COUNT_PER_ALLOWED_TIME; ++i) {
+        for (int i = 0; i < mQcConstants.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW; ++i) {
             JobStatus job = createJobStatus(
                     "testStartAlarmScheduled_TimingSessionCount_AllowedTime", i);
             setStandbyBucket(FREQUENT_INDEX, job);
@@ -2515,7 +2601,7 @@
         // Start alarm shouldn't have been scheduled since the app was in quota up until this point.
         verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
-        // The app is now out of job count quota
+        // The app is now out of session count quota
         JobStatus throttledJob = createJobStatus(
                 "testStartAlarmScheduled_TimingSessionCount_AllowedTime", 42);
         mQuotaController.maybeStartTrackingJobLocked(throttledJob, null);
@@ -2523,8 +2609,7 @@
 
         ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
                 SOURCE_PACKAGE, standbyBucket);
-        final long expectedWorkingAlarmTime =
-                stats.sessionCountExpirationTimeElapsed + mQcConstants.ALLOWED_TIME_PER_PERIOD_MS;
+        final long expectedWorkingAlarmTime = stats.sessionRateLimitExpirationTimeElapsed;
         verify(mAlarmManager, times(1))
                 .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
     }
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
index fa1bcac..fd3678d 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
@@ -423,9 +423,7 @@
         assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
         assertEquals(null, si.getIntent());
         assertEquals(123, si.getRank());
-        assertEquals("person", si.getPersons()[0].getName());
-        assertEquals("personKey", si.getPersons()[0].getKey());
-        assertEquals("personUri", si.getPersons()[0].getUri());
+        assertEquals(null, si.getPersons());
         assertEquals(1, si.getExtras().getInt("k"));
 
         assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_LONG_LIVED, si.getFlags());
@@ -455,6 +453,30 @@
 
         assertEquals(456, si.getIconResourceId());
         assertEquals(null, si.getIconResName());
+
+        si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_APP_PREDICTION);
+
+        assertEquals(mClientContext.getPackageName(), si.getPackage());
+        assertEquals("id", si.getId());
+        assertEquals(new ComponentName("a", "b"), si.getActivity());
+        assertEquals(null, si.getIcon());
+        assertEquals("title", si.getTitle());
+        assertEquals("text", si.getText());
+        assertEquals("dismes", si.getDisabledMessage());
+        assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
+        assertEquals("action", si.getIntent().getAction());
+        assertEquals("val", si.getIntent().getStringExtra("key"));
+        assertEquals(123, si.getRank());
+        assertEquals("person", si.getPersons()[0].getName());
+        assertEquals("personKey", si.getPersons()[0].getKey());
+        assertEquals("personUri", si.getPersons()[0].getUri());
+        assertEquals(1, si.getExtras().getInt("k"));
+
+        assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_LONG_LIVED, si.getFlags());
+        assertEquals(null, si.getBitmapPath());
+
+        assertEquals(456, si.getIconResourceId());
+        assertEquals(null, si.getIconResName());
     }
 
     public void testShortcutInfoClone_resId() {
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 984d8f7..5d39a2c 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -341,6 +341,7 @@
 
     private String mOperatorAlphaLongRaw;
     private String mOperatorAlphaShortRaw;
+    private boolean mIsIwlanPreferred;
 
     /**
      * get String description of roaming type
@@ -427,6 +428,7 @@
         mNrFrequencyRange = s.mNrFrequencyRange;
         mOperatorAlphaLongRaw = s.mOperatorAlphaLongRaw;
         mOperatorAlphaShortRaw = s.mOperatorAlphaShortRaw;
+        mIsIwlanPreferred = s.mIsIwlanPreferred;
     }
 
     /**
@@ -463,6 +465,7 @@
         mNrFrequencyRange = in.readInt();
         mOperatorAlphaLongRaw = in.readString();
         mOperatorAlphaShortRaw = in.readString();
+        mIsIwlanPreferred = in.readBoolean();
     }
 
     public void writeToParcel(Parcel out, int flags) {
@@ -492,6 +495,7 @@
         out.writeInt(mNrFrequencyRange);
         out.writeString(mOperatorAlphaLongRaw);
         out.writeString(mOperatorAlphaShortRaw);
+        out.writeBoolean(mIsIwlanPreferred);
     }
 
     public int describeContents() {
@@ -853,7 +857,8 @@
                     mNetworkRegistrationInfos,
                     mNrFrequencyRange,
                     mOperatorAlphaLongRaw,
-                    mOperatorAlphaShortRaw);
+                    mOperatorAlphaShortRaw,
+                    mIsIwlanPreferred);
         }
     }
 
@@ -885,7 +890,8 @@
                     && equalsHandlesNulls(mOperatorAlphaShortRaw, s.mOperatorAlphaShortRaw)
                     && mNetworkRegistrationInfos.size() == s.mNetworkRegistrationInfos.size()
                     && mNetworkRegistrationInfos.containsAll(s.mNetworkRegistrationInfos)
-                    && mNrFrequencyRange == s.mNrFrequencyRange;
+                    && mNrFrequencyRange == s.mNrFrequencyRange
+                    && mIsIwlanPreferred == s.mIsIwlanPreferred;
         }
     }
 
@@ -1043,6 +1049,7 @@
                     .append(", mNrFrequencyRange=").append(mNrFrequencyRange)
                     .append(", mOperatorAlphaLongRaw=").append(mOperatorAlphaLongRaw)
                     .append(", mOperatorAlphaShortRaw=").append(mOperatorAlphaShortRaw)
+                    .append(", mIsIwlanPreferred=").append(mIsIwlanPreferred)
                     .append("}").toString();
         }
     }
@@ -1085,6 +1092,7 @@
         }
         mOperatorAlphaLongRaw = null;
         mOperatorAlphaShortRaw = null;
+        mIsIwlanPreferred = false;
     }
 
     public void setStateOutOfService() {
@@ -1459,20 +1467,9 @@
     /** @hide */
     @UnsupportedAppUsage
     public int getRilDataRadioTechnology() {
-        NetworkRegistrationInfo wwanRegInfo = getNetworkRegistrationInfo(
-                NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
-        NetworkRegistrationInfo wlanRegInfo = getNetworkRegistrationInfo(
-                NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
-        if (wlanRegInfo != null
-                && wlanRegInfo.getAccessNetworkTechnology() == TelephonyManager.NETWORK_TYPE_IWLAN
-                && wlanRegInfo.getRegistrationState()
-                == NetworkRegistrationInfo.REGISTRATION_STATE_HOME) {
-            return RIL_RADIO_TECHNOLOGY_IWLAN;
-        } else if (wwanRegInfo != null) {
-            return networkTypeToRilRadioTechnology(wwanRegInfo.getAccessNetworkTechnology());
-        }
-        return RIL_RADIO_TECHNOLOGY_UNKNOWN;
+        return networkTypeToRilRadioTechnology(getDataNetworkType());
     }
+
     /**
      * @hide
      * @Deprecated to be removed Q3 2013 use {@link #getRilDataRadioTechnology} or
@@ -1608,26 +1605,40 @@
         }
     }
 
-    /** @hide */
+    /**
+     * Get current data network type.
+     *
+     * Note that for IWLAN AP-assisted mode device, which is reporting both camped access networks
+     * (cellular RAT and IWLAN)at the same time, this API is simulating the old legacy mode device
+     * behavior,
+     *
+     * @return Current data network type
+     * @hide
+     */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public @TelephonyManager.NetworkType int getDataNetworkType() {
-        final NetworkRegistrationInfo iwlanRegState = getNetworkRegistrationInfo(
+        final NetworkRegistrationInfo iwlanRegInfo = getNetworkRegistrationInfo(
                 NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
-        if (iwlanRegState != null && iwlanRegState.getRegistrationState()
-                == NetworkRegistrationInfo.REGISTRATION_STATE_HOME) {
-            // If the device is on IWLAN, return IWLAN as the network type. This is to simulate the
-            // behavior of legacy mode device. In the future caller should use
-            // requestNetworkRegistrationInfo() to retrieve the actual data network type on cellular
-            // or on IWLAN.
-            return iwlanRegState.getAccessNetworkTechnology();
+        final NetworkRegistrationInfo wwanRegInfo = getNetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        // For legacy mode device, or AP-assisted mode device but IWLAN is out of service, use
+        // the RAT from cellular.
+        if (iwlanRegInfo == null || !iwlanRegInfo.isInService()) {
+            return (wwanRegInfo != null) ? wwanRegInfo.getAccessNetworkTechnology()
+                    : TelephonyManager.NETWORK_TYPE_UNKNOWN;
         }
 
-        final NetworkRegistrationInfo regState = getNetworkRegistrationInfo(
-                NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
-        if (regState != null) {
-            return regState.getAccessNetworkTechnology();
+        // At this point, it must be an AP-assisted mode device and IWLAN is in service. We should
+        // use the RAT from IWLAN service is cellular is out of service, or when both are in service
+        // and any APN type of data is preferred on IWLAN.
+        if (!wwanRegInfo.isInService() || mIsIwlanPreferred) {
+            return iwlanRegInfo.getAccessNetworkTechnology();
         }
-        return TelephonyManager.NETWORK_TYPE_UNKNOWN;
+
+        // If both cellular and IWLAN are in service, but no APN is preferred on IWLAN, still use
+        // the RAT from cellular.
+        return wwanRegInfo.getAccessNetworkTechnology();
     }
 
     /** @hide */
@@ -1976,4 +1987,14 @@
     public String getOperatorAlphaShortRaw() {
         return mOperatorAlphaShortRaw;
     }
+
+    /**
+     * Set to {@code true} if any data network is preferred on IWLAN.
+     *
+     * @param isIwlanPreferred {@code true} if IWLAN is preferred.
+     * @hide
+     */
+    public void setIwlanPreferred(boolean isIwlanPreferred) {
+        mIsIwlanPreferred = isIwlanPreferred;
+    }
 }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index d63c37a..2ba34c6 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -5356,18 +5356,30 @@
      * Returns the MMS user agent.
      */
     public String getMmsUserAgent() {
-        if (mContext == null) return null;
-        return SubscriptionManager.getResourcesForSubId(mContext, getSubId()).getString(
-                com.android.internal.R.string.config_mms_user_agent);
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.getMmsUserAgent(getSubId());
+            }
+        } catch (RemoteException ex) {
+        } catch (NullPointerException ex) {
+        }
+        return null;
     }
 
     /**
      * Returns the MMS user agent profile URL.
      */
     public String getMmsUAProfUrl() {
-        if (mContext == null) return null;
-        return SubscriptionManager.getResourcesForSubId(mContext, getSubId()).getString(
-                com.android.internal.R.string.config_mms_user_agent_profile_url);
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.getMmsUAProfUrl(getSubId());
+            }
+        } catch (RemoteException ex) {
+        } catch (NullPointerException ex) {
+        }
+        return null;
     }
 
     /**
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 63aded1..cf1323a 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1986,4 +1986,14 @@
      * outgoing SmsManager operation.
      */
     oneway void enqueueSmsPickResult(String callingPackage, IIntegerConsumer subIdResult);
+
+    /**
+     * Returns the MMS user agent.
+     */
+    String getMmsUserAgent(int subId);
+
+    /**
+     * Returns the MMS user agent profile URL.
+     */
+    String getMmsUAProfUrl(int subId);
 }
diff --git a/tests/net/java/android/net/NetworkStatsTest.java b/tests/net/java/android/net/NetworkStatsTest.java
index b5b0384..c16a0f4 100644
--- a/tests/net/java/android/net/NetworkStatsTest.java
+++ b/tests/net/java/android/net/NetworkStatsTest.java
@@ -569,7 +569,7 @@
             .addValues(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
                     DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L);
 
-        assertTrue(delta.toString(), delta.migrateTun(tunUid, tunIface, underlyingIface));
+        delta.migrateTun(tunUid, tunIface, new String[] {underlyingIface});
         assertEquals(20, delta.size());
 
         // tunIface and TEST_IFACE entries are not changed.
@@ -650,7 +650,7 @@
             .addValues(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                     DEFAULT_NETWORK_NO,  75500L, 37L, 130000L, 70L, 0L);
 
-        assertTrue(delta.migrateTun(tunUid, tunIface, underlyingIface));
+        delta.migrateTun(tunUid, tunIface, new String[]{underlyingIface});
         assertEquals(9, delta.size());
 
         // tunIface entries should not be changed.
@@ -813,6 +813,37 @@
     }
 
     @Test
+    public void testFilterDebugEntries() {
+        NetworkStats.Entry entry1 = new NetworkStats.Entry(
+                "test1", 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+        NetworkStats.Entry entry2 = new NetworkStats.Entry(
+                "test2", 10101, SET_DBG_VPN_IN, TAG_NONE, METERED_NO, ROAMING_NO,
+                DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+        NetworkStats.Entry entry3 = new NetworkStats.Entry(
+                "test2", 10101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+        NetworkStats.Entry entry4 = new NetworkStats.Entry(
+                "test2", 10101, SET_DBG_VPN_OUT, TAG_NONE, METERED_NO, ROAMING_NO,
+                DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+        NetworkStats stats = new NetworkStats(TEST_START, 4)
+                .addValues(entry1)
+                .addValues(entry2)
+                .addValues(entry3)
+                .addValues(entry4);
+
+        stats.filterDebugEntries();
+
+        assertEquals(2, stats.size());
+        assertEquals(entry1, stats.getValues(0, null));
+        assertEquals(entry3, stats.getValues(1, null));
+    }
+
+    @Test
     public void testApply464xlatAdjustments() {
         final String v4Iface = "v4-wlan0";
         final String baseIface = "wlan0";
diff --git a/tests/net/java/com/android/server/net/NetworkStatsAccessTest.java b/tests/net/java/com/android/server/net/NetworkStatsAccessTest.java
index 6e725dd..858358c 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsAccessTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsAccessTest.java
@@ -161,7 +161,7 @@
     }
 
     private void setHasCarrierPrivileges(boolean hasPrivileges) {
-        when(mTm.checkCarrierPrivilegesForPackage(TEST_PKG)).thenReturn(
+        when(mTm.checkCarrierPrivilegesForPackageAnyPhone(TEST_PKG)).thenReturn(
                 hasPrivileges ? TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS
                         : TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
     }
diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
index bce526d..d9f2c20 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -57,11 +57,11 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -216,11 +216,16 @@
         expectNetworkStatsUidDetail(buildEmptyStats());
         expectSystemReady();
 
+        assertNull(mService.getTunAdjustedStats());
         mService.systemReady();
+        // Verify that system ready fetches realtime stats and initializes tun adjusted stats.
+        verify(mNetManager).getNetworkStatsUidDetail(UID_ALL, INTERFACES_ALL);
+        assertNotNull("failed to initialize TUN adjusted stats", mService.getTunAdjustedStats());
+        assertEquals(0, mService.getTunAdjustedStats().size());
+
         mSession = mService.openSession();
         assertNotNull("openSession() failed", mSession);
 
-
         // catch INetworkManagementEventObserver during systemReady()
         ArgumentCaptor<INetworkManagementEventObserver> networkObserver =
               ArgumentCaptor.forClass(INetworkManagementEventObserver.class);
@@ -733,11 +738,13 @@
 
         NetworkStats stats = mService.getDetailedUidStats(ifaceFilter);
 
-        verify(mNetManager, times(1)).getNetworkStatsUidDetail(eq(UID_ALL), argThat(ifaces ->
-                ifaces != null && ifaces.length == 2
-                        && ArrayUtils.contains(ifaces, TEST_IFACE)
-                        && ArrayUtils.contains(ifaces, stackedIface)));
-
+        // mNetManager#getNetworkStatsUidDetail(UID_ALL, INTERFACES_ALL) has following invocations:
+        // 1) NetworkStatsService#systemReady from #setUp.
+        // 2) mService#forceUpdateIfaces in the test above.
+        // 3) Finally, mService#getDetailedUidStats.
+        verify(mNetManager, times(3)).getNetworkStatsUidDetail(UID_ALL, INTERFACES_ALL);
+        assertTrue(ArrayUtils.contains(stats.getUniqueIfaces(), TEST_IFACE));
+        assertTrue(ArrayUtils.contains(stats.getUniqueIfaces(), stackedIface));
         assertEquals(2, stats.size());
         assertEquals(uidStats, stats.getValues(0, null));
         assertEquals(tetheredStats1, stats.getValues(1, null));
@@ -923,11 +930,11 @@
     }
 
     @Test
-    public void vpnWithOneUnderlyingIface() throws Exception {
+    public void vpnRewriteTrafficThroughItself() throws Exception {
         // WiFi network is connected and VPN is using WiFi (which has TEST_IFACE).
         expectDefaultSettings();
         NetworkState[] networkStates = new NetworkState[] {buildWifiState(), buildVpnState()};
-        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(TEST_IFACE)};
+        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
         expectNetworkStatsUidDetail(buildEmptyStats());
         expectBandwidthControlCheck();
 
@@ -938,23 +945,133 @@
                 getActiveIface(networkStates));
         // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
         // overhead per packet):
-        // 1000 bytes (100 packets) were sent/received by UID_RED over VPN.
-        // 500 bytes (50 packets) were sent/received by UID_BLUE over VPN.
-        // VPN sent/received 1650 bytes (150 packets) over WiFi.
-        // Of 1650 bytes over WiFi, expect 1000 bytes attributed to UID_RED, 500 bytes attributed to
-        // UID_BLUE, and 150 bytes attributed to UID_VPN for both rx/tx traffic.
+        //
+        // 1000 bytes (100 packets) were sent, and 2000 bytes (200 packets) were received by UID_RED
+        // over VPN.
+        // 500 bytes (50 packets) were sent, and 1000 bytes (100 packets) were received by UID_BLUE
+        // over VPN.
+        //
+        // VPN UID rewrites packets read from TUN back to TUN, plus some of its own traffic
+        // (100 bytes).
         incrementCurrentTime(HOUR_IN_MILLIS);
-        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3)
-                .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1000L, 100L, 1000L, 100L, 1L)
-                .addValues(TUN_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 500L, 50L, 500L, 50L, 1L)
-                .addValues(
-                    TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 1650L, 150L, 1650L, 150L, 2L));
+        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 5)
+                .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 2000L, 200L, 1000L, 100L, 1L)
+                .addValues(TUN_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 1000L, 100L, 500L, 50L, 1L)
+                // VPN rewrites all the packets read from TUN + 100 additional bytes of VPN's
+                // own traffic.
+                .addValues(TUN_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 0L, 0L, 1600L, 160L, 2L)
+                // VPN sent 1760 bytes over WiFi in foreground (SET_FOREGROUND) i.e. 1600
+                // bytes (160 packets) + 1 byte/packet overhead (=160 bytes).
+                .addValues(TEST_IFACE, UID_VPN, SET_FOREGROUND, TAG_NONE, 0L, 0L, 1760L, 176L, 1L)
+                // VPN received 3300 bytes over WiFi in background (SET_DEFAULT) i.e. 3000 bytes
+                // (300 packets) + 1 byte/packet encryption overhead (=300 bytes).
+                .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 3300L, 300L, 0L, 0L, 1L));
 
         forcePollAndWaitForIdle();
 
-        assertUidTotal(sTemplateWifi, UID_RED, 1000L, 100L, 1000L, 100L, 1);
-        assertUidTotal(sTemplateWifi, UID_BLUE, 500L, 50L, 500L, 50L, 1);
-        assertUidTotal(sTemplateWifi, UID_VPN, 150L, 0L, 150L, 0L, 2);
+        // Verify increased TUN usage by UID_VPN does not get attributed to other apps.
+        NetworkStats tunStats =
+                mService.getDetailedUidStats(new String[] {TUN_IFACE});
+        assertValues(
+                tunStats, TUN_IFACE, UID_RED, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
+                DEFAULT_NETWORK_ALL, 2000L, 200L, 1000L, 100L, 1);
+        assertValues(
+                tunStats, TUN_IFACE, UID_BLUE, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
+                DEFAULT_NETWORK_ALL, 1000L, 100L, 500L, 50L, 1);
+        assertValues(
+                tunStats, TUN_IFACE, UID_VPN, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
+                DEFAULT_NETWORK_ALL, 0L, 0L, 1600L, 160L, 2);
+
+        // Verify correct attribution over WiFi.
+        assertUidTotal(sTemplateWifi, UID_RED, 2000L, 200L, 1000L, 100L, 1);
+        assertUidTotal(sTemplateWifi, UID_BLUE, 1000L, 100L, 500L, 50L, 1);
+        assertUidTotal(sTemplateWifi, UID_VPN, 300L, 0L, 260L, 26L, 2);
+    }
+
+    @Test
+    public void vpnWithOneUnderlyingIface() throws Exception {
+        // WiFi network is connected and VPN is using WiFi (which has TEST_IFACE).
+        expectDefaultSettings();
+        NetworkState[] networkStates = new NetworkState[] {buildWifiState(), buildVpnState()};
+        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
+        expectNetworkStatsUidDetail(buildEmptyStats());
+        expectBandwidthControlCheck();
+
+        mService.forceUpdateIfaces(
+                new Network[] {WIFI_NETWORK, VPN_NETWORK},
+                vpnInfos,
+                networkStates,
+                getActiveIface(networkStates));
+        // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
+        // overhead per packet):
+        // 1000 bytes (100 packets) were sent, and 2000 bytes (200 packets) were received by UID_RED
+        // over VPN.
+        // 500 bytes (50 packets) were sent, and 1000 bytes (100 packets) were received by UID_BLUE
+        // over VPN.
+        // VPN sent 1650 bytes (150 packets), and received 3300 (300 packets) over WiFi.
+        // Of 1650 bytes sent over WiFi, expect 1000 bytes attributed to UID_RED, 500 bytes
+        // attributed to UID_BLUE, and 150 bytes attributed to UID_VPN.
+        // Of 3300 bytes received over WiFi, expect 2000 bytes attributed to UID_RED, 1000 bytes
+        // attributed to UID_BLUE, and 300 bytes attributed to UID_VPN.
+        incrementCurrentTime(HOUR_IN_MILLIS);
+        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3)
+                .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 2000L, 200L, 1000L, 100L, 1L)
+                .addValues(TUN_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 1000L, 100L, 500L, 50L, 1L)
+                // VPN received 3300 bytes over WiFi in background (SET_DEFAULT).
+                .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 3300L, 300L, 0L, 0L, 1L)
+                // VPN sent 1650 bytes over WiFi in foreground (SET_FOREGROUND).
+                .addValues(TEST_IFACE, UID_VPN, SET_FOREGROUND, TAG_NONE, 0L, 0L, 1650L, 150L, 1L));
+
+        forcePollAndWaitForIdle();
+
+        assertUidTotal(sTemplateWifi, UID_RED, 2000L, 200L, 1000L, 100L, 1);
+        assertUidTotal(sTemplateWifi, UID_BLUE, 1000L, 100L, 500L, 50L, 1);
+        assertUidTotal(sTemplateWifi, UID_VPN, 300L, 0L, 150L, 0L, 2);
+    }
+
+    @Test
+    public void vpnWithOneUnderlyingIfaceAndOwnTraffic() throws Exception {
+        // WiFi network is connected and VPN is using WiFi (which has TEST_IFACE).
+        expectDefaultSettings();
+        NetworkState[] networkStates = new NetworkState[] {buildWifiState(), buildVpnState()};
+        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
+        expectNetworkStatsUidDetail(buildEmptyStats());
+        expectBandwidthControlCheck();
+
+        mService.forceUpdateIfaces(
+                new Network[] {WIFI_NETWORK, VPN_NETWORK},
+                vpnInfos,
+                networkStates,
+                getActiveIface(networkStates));
+        // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
+        // overhead per packet):
+        // 1000 bytes (100 packets) were sent, and 2000 bytes (200 packets) were received by UID_RED
+        // over VPN.
+        // 500 bytes (50 packets) were sent, and 1000 bytes (100 packets) were received by UID_BLUE
+        // over VPN.
+        // Additionally, the VPN sends 6000 bytes (600 packets) of its own traffic into the tun
+        // interface (passing that traffic to the VPN endpoint), and receives 5000 bytes (500
+        // packets) from it. Including overhead that is 6600/5500 bytes.
+        // VPN sent 8250 bytes (750 packets), and received 8800 (800 packets) over WiFi.
+        // Of 8250 bytes sent over WiFi, expect 1000 bytes attributed to UID_RED, 500 bytes
+        // attributed to UID_BLUE, and 6750 bytes attributed to UID_VPN.
+        // Of 8800 bytes received over WiFi, expect 2000 bytes attributed to UID_RED, 1000 bytes
+        // attributed to UID_BLUE, and 5800 bytes attributed to UID_VPN.
+        incrementCurrentTime(HOUR_IN_MILLIS);
+        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3)
+                .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 2000L, 200L, 1000L, 100L, 1L)
+                .addValues(TUN_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 1000L, 100L, 500L, 50L, 1L)
+                .addValues(TUN_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 5000L, 500L, 6000L, 600L, 1L)
+                // VPN received 8800 bytes over WiFi in background (SET_DEFAULT).
+                .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 8800L, 800L, 0L, 0L, 1L)
+                // VPN sent 8250 bytes over WiFi in foreground (SET_FOREGROUND).
+                .addValues(TEST_IFACE, UID_VPN, SET_FOREGROUND, TAG_NONE, 0L, 0L, 8250L, 750L, 1L));
+
+        forcePollAndWaitForIdle();
+
+        assertUidTotal(sTemplateWifi, UID_RED, 2000L, 200L, 1000L, 100L, 1);
+        assertUidTotal(sTemplateWifi, UID_BLUE, 1000L, 100L, 500L, 50L, 1);
+        assertUidTotal(sTemplateWifi, UID_VPN, 5800L, 500L, 6750L, 600L, 2);
     }
 
     @Test
@@ -962,7 +1079,7 @@
         // WiFi network is connected and VPN is using WiFi (which has TEST_IFACE).
         expectDefaultSettings();
         NetworkState[] networkStates = new NetworkState[] {buildWifiState(), buildVpnState()};
-        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(TEST_IFACE)};
+        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
         expectNetworkStatsUidDetail(buildEmptyStats());
         expectBandwidthControlCheck();
 
@@ -993,6 +1110,136 @@
     }
 
     @Test
+    public void vpnWithTwoUnderlyingIfaces_packetDuplication() throws Exception {
+        // WiFi and Cell networks are connected and VPN is using WiFi (which has TEST_IFACE) and
+        // Cell (which has TEST_IFACE2) and has declared both of them in its underlying network set.
+        // Additionally, VPN is duplicating traffic across both WiFi and Cell.
+        expectDefaultSettings();
+        NetworkState[] networkStates =
+                new NetworkState[] {
+                    buildWifiState(), buildMobile4gState(TEST_IFACE2), buildVpnState()
+                };
+        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE, TEST_IFACE2})};
+        expectNetworkStatsUidDetail(buildEmptyStats());
+        expectBandwidthControlCheck();
+
+        mService.forceUpdateIfaces(
+                new Network[] {WIFI_NETWORK, VPN_NETWORK},
+                vpnInfos,
+                networkStates,
+                getActiveIface(networkStates));
+        // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
+        // overhead per packet):
+        // 1000 bytes (100 packets) were sent/received by UID_RED and UID_BLUE over VPN.
+        // VPN sent/received 4400 bytes (400 packets) over both WiFi and Cell (8800 bytes in total).
+        // Of 8800 bytes over WiFi/Cell, expect:
+        // - 500 bytes rx/tx each over WiFi/Cell attributed to both UID_RED and UID_BLUE.
+        // - 1200 bytes rx/tx each over WiFi/Cell for VPN_UID.
+        incrementCurrentTime(HOUR_IN_MILLIS);
+        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 4)
+                .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1000L, 100L, 1000L, 100L, 2L)
+                .addValues(TUN_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 1000L, 100L, 1000L, 100L, 2L)
+                .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 2200L, 200L, 2200L, 200L, 2L)
+                .addValues(
+                    TEST_IFACE2, UID_VPN, SET_DEFAULT, TAG_NONE, 2200L, 200L, 2200L, 200L, 2L));
+
+        forcePollAndWaitForIdle();
+
+        assertUidTotal(sTemplateWifi, UID_RED, 500L, 50L, 500L, 50L, 1);
+        assertUidTotal(sTemplateWifi, UID_BLUE, 500L, 50L, 500L, 50L, 1);
+        assertUidTotal(sTemplateWifi, UID_VPN, 1200L, 100L, 1200L, 100L, 2);
+
+        assertUidTotal(buildTemplateMobileWildcard(), UID_RED, 500L, 50L, 500L, 50L, 1);
+        assertUidTotal(buildTemplateMobileWildcard(), UID_BLUE, 500L, 50L, 500L, 50L, 1);
+        assertUidTotal(buildTemplateMobileWildcard(), UID_VPN, 1200L, 100L, 1200L, 100L, 2);
+    }
+
+    @Test
+    public void vpnWithTwoUnderlyingIfaces_splitTraffic() throws Exception {
+        // WiFi and Cell networks are connected and VPN is using WiFi (which has TEST_IFACE) and
+        // Cell (which has TEST_IFACE2) and has declared both of them in its underlying network set.
+        // Additionally, VPN is arbitrarily splitting traffic across WiFi and Cell.
+        expectDefaultSettings();
+        NetworkState[] networkStates =
+                new NetworkState[] {
+                    buildWifiState(), buildMobile4gState(TEST_IFACE2), buildVpnState()
+                };
+        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE, TEST_IFACE2})};
+        expectNetworkStatsUidDetail(buildEmptyStats());
+        expectBandwidthControlCheck();
+
+        mService.forceUpdateIfaces(
+                new Network[] {WIFI_NETWORK, VPN_NETWORK},
+                vpnInfos,
+                networkStates,
+                getActiveIface(networkStates));
+        // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
+        // overhead per packet):
+        // 1000 bytes (100 packets) were sent, and 500 bytes (50 packets) received by UID_RED over
+        // VPN.
+        // VPN sent 660 bytes (60 packets) over WiFi and 440 bytes (40 packets) over Cell.
+        // And, it received 330 bytes (30 packets) over WiFi and 220 bytes (20 packets) over Cell.
+        // For UID_RED, expect 600 bytes attributed over WiFi and 400 bytes over Cell for sent (tx)
+        // traffic. For received (rx) traffic, expect 300 bytes over WiFi and 200 bytes over Cell.
+        //
+        // For UID_VPN, expect 60 bytes attributed over WiFi and 40 bytes over Cell for tx traffic.
+        // And, 30 bytes over WiFi and 20 bytes over Cell for rx traffic.
+        incrementCurrentTime(HOUR_IN_MILLIS);
+        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3)
+              .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 500L, 50L, 1000L, 100L, 2L)
+              .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 330L, 30L, 660L, 60L, 1L)
+              .addValues(TEST_IFACE2, UID_VPN, SET_DEFAULT, TAG_NONE, 220L, 20L, 440L, 40L, 1L));
+
+        forcePollAndWaitForIdle();
+
+        assertUidTotal(sTemplateWifi, UID_RED, 300L, 30L, 600L, 60L, 1);
+        assertUidTotal(sTemplateWifi, UID_VPN, 30L, 0L, 60L, 0L, 1);
+
+        assertUidTotal(buildTemplateMobileWildcard(), UID_RED, 200L, 20L, 400L, 40L, 1);
+        assertUidTotal(buildTemplateMobileWildcard(), UID_VPN, 20L, 0L, 40L, 0L, 1);
+    }
+
+    @Test
+    public void vpnWithTwoUnderlyingIfaces_splitTrafficWithCompression() throws Exception {
+        // WiFi and Cell networks are connected and VPN is using WiFi (which has TEST_IFACE) and
+        // Cell (which has TEST_IFACE2) and has declared both of them in its underlying network set.
+        // Additionally, VPN is arbitrarily splitting compressed traffic across WiFi and Cell.
+        expectDefaultSettings();
+        NetworkState[] networkStates =
+                new NetworkState[] {
+                    buildWifiState(), buildMobile4gState(TEST_IFACE2), buildVpnState()
+                };
+        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE, TEST_IFACE2})};
+        expectNetworkStatsUidDetail(buildEmptyStats());
+        expectBandwidthControlCheck();
+
+        mService.forceUpdateIfaces(
+                new Network[] {WIFI_NETWORK, VPN_NETWORK},
+                vpnInfos,
+                networkStates,
+                getActiveIface(networkStates));
+        // create some traffic (assume 10 bytes of MTU for VPN interface:
+        // 1000 bytes (100 packets) were sent/received by UID_RED over VPN.
+        // VPN sent/received 600 bytes (60 packets) over WiFi and 200 bytes (20 packets) over Cell.
+        // For UID_RED, expect 600 bytes attributed over WiFi and 200 bytes over Cell for both
+        // rx/tx.
+        // UID_VPN gets nothing attributed to it (avoiding negative stats).
+        incrementCurrentTime(HOUR_IN_MILLIS);
+        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 4)
+              .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1000L, 100L, 1000L, 100L, 1L)
+              .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 600L, 60L, 600L, 60L, 0L)
+              .addValues(TEST_IFACE2, UID_VPN, SET_DEFAULT, TAG_NONE, 200L, 20L, 200L, 20L, 0L));
+
+        forcePollAndWaitForIdle();
+
+        assertUidTotal(sTemplateWifi, UID_RED, 600L, 60L, 600L, 60L, 0);
+        assertUidTotal(sTemplateWifi, UID_VPN, 0L, 0L, 0L, 0L, 0);
+
+        assertUidTotal(buildTemplateMobileWildcard(), UID_RED, 200L, 20L, 200L, 20L, 0);
+        assertUidTotal(buildTemplateMobileWildcard(), UID_VPN, 0L, 0L, 0L, 0L, 0);
+    }
+
+    @Test
     public void vpnWithIncorrectUnderlyingIface() throws Exception {
         // WiFi and Cell networks are connected and VPN is using Cell (which has TEST_IFACE2),
         // but has declared only WiFi (TEST_IFACE) in its underlying network set.
@@ -1001,7 +1248,7 @@
                 new NetworkState[] {
                     buildWifiState(), buildMobile4gState(TEST_IFACE2), buildVpnState()
                 };
-        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(TEST_IFACE)};
+        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
         expectNetworkStatsUidDetail(buildEmptyStats());
         expectBandwidthControlCheck();
 
@@ -1030,6 +1277,134 @@
     }
 
     @Test
+    public void recordSnapshot_migratesTunTrafficAndUpdatesTunAdjustedStats() throws Exception {
+        assertEquals(0, mService.getTunAdjustedStats().size());
+        // VPN using WiFi (TEST_IFACE).
+        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
+        expectBandwidthControlCheck();
+        // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
+        // overhead per packet):
+        // 1000 bytes (100 packets) were downloaded by UID_RED over VPN.
+        // VPN received 1100 bytes (100 packets) over WiFi.
+        incrementCurrentTime(HOUR_IN_MILLIS);
+        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 2)
+              .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1000L, 100L, 0L, 0L, 0L)
+              .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 1100L, 100L, 0L, 0L, 0L));
+
+        // this should lead to NSS#recordSnapshotLocked
+        mService.forceUpdateIfaces(
+                new Network[0], vpnInfos, new NetworkState[0], null /* activeIface */);
+
+        // Verify TUN adjusted stats have traffic migrated correctly.
+        // Of 1100 bytes VPN received over WiFi, expect 1000 bytes attributed to UID_RED and 100
+        // bytes attributed to UID_VPN.
+        NetworkStats tunAdjStats = mService.getTunAdjustedStats();
+        assertValues(
+                tunAdjStats, TEST_IFACE, UID_RED, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
+                DEFAULT_NETWORK_ALL, 1000L, 100L, 0L, 0L, 0);
+        assertValues(
+                tunAdjStats, TEST_IFACE, UID_VPN, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
+                DEFAULT_NETWORK_ALL, 100L, 0L, 0L, 0L, 0);
+    }
+
+    @Test
+    public void getDetailedUidStats_migratesTunTrafficAndUpdatesTunAdjustedStats()
+            throws Exception {
+        assertEquals(0, mService.getTunAdjustedStats().size());
+        // VPN using WiFi (TEST_IFACE).
+        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
+        expectBandwidthControlCheck();
+        mService.forceUpdateIfaces(
+                new Network[0], vpnInfos, new NetworkState[0], null /* activeIface */);
+        // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
+        // overhead per packet):
+        // 1000 bytes (100 packets) were downloaded by UID_RED over VPN.
+        // VPN received 1100 bytes (100 packets) over WiFi.
+        incrementCurrentTime(HOUR_IN_MILLIS);
+        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 2)
+              .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1000L, 100L, 0L, 0L, 0L)
+              .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 1100L, 100L, 0L, 0L, 0L));
+
+        mService.getDetailedUidStats(INTERFACES_ALL);
+
+        // Verify internally maintained TUN adjusted stats
+        NetworkStats tunAdjStats = mService.getTunAdjustedStats();
+        // Verify stats for TEST_IFACE (WiFi):
+        // Of 1100 bytes VPN received over WiFi, expect 1000 bytes attributed to UID_RED and 100
+        // bytes attributed to UID_VPN.
+        assertValues(
+                tunAdjStats, TEST_IFACE, UID_RED, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
+                DEFAULT_NETWORK_ALL, 1000L, 100L, 0L, 0L, 0);
+        assertValues(
+                tunAdjStats, TEST_IFACE, UID_VPN, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
+                DEFAULT_NETWORK_ALL, 100L, 0L, 0L, 0L, 0);
+        // Verify stats for TUN_IFACE; only UID_RED should have usage on it.
+        assertValues(
+                tunAdjStats, TUN_IFACE, UID_RED, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
+                DEFAULT_NETWORK_ALL, 1000L, 100L, 0L, 0L, 0);
+        assertValues(
+                tunAdjStats, TUN_IFACE, UID_VPN, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
+                DEFAULT_NETWORK_ALL, 0L, 0L, 0L, 0L, 0);
+
+        // lets assume that since last time, VPN received another 1100 bytes (same assumptions as
+        // before i.e. UID_RED downloaded another 1000 bytes).
+        incrementCurrentTime(HOUR_IN_MILLIS);
+        // Note - NetworkStatsFactory returns counters that are monotonically increasing.
+        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 2)
+              .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 2000L, 200L, 0L, 0L, 0L)
+              .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 2200L, 200L, 0L, 0L, 0L));
+
+        mService.getDetailedUidStats(INTERFACES_ALL);
+
+        tunAdjStats = mService.getTunAdjustedStats();
+        // verify TEST_IFACE stats:
+        assertValues(
+                tunAdjStats, TEST_IFACE, UID_RED, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
+                DEFAULT_NETWORK_ALL, 2000L, 200L, 0L, 0L, 0);
+        assertValues(
+                tunAdjStats, TEST_IFACE, UID_VPN, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
+                DEFAULT_NETWORK_ALL, 200L, 0L, 0L, 0L, 0);
+        // verify TUN_IFACE stats:
+        assertValues(
+                tunAdjStats, TUN_IFACE, UID_RED, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
+                DEFAULT_NETWORK_ALL, 2000L, 200L, 0L, 0L, 0);
+        assertValues(
+                tunAdjStats, TUN_IFACE, UID_VPN, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
+                DEFAULT_NETWORK_ALL, 0L, 0L, 0L, 0L, 0);
+    }
+
+    @Test
+    public void getDetailedUidStats_returnsCorrectStatsWithVpnRunning() throws Exception {
+        // VPN using WiFi (TEST_IFACE).
+        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
+        expectBandwidthControlCheck();
+        mService.forceUpdateIfaces(
+                new Network[0], vpnInfos, new NetworkState[0], null /* activeIface */);
+        // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
+        // overhead per packet):
+        // 1000 bytes (100 packets) were downloaded by UID_RED over VPN.
+        // VPN received 1100 bytes (100 packets) over WiFi.
+        incrementCurrentTime(HOUR_IN_MILLIS);
+        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 2)
+              .addValues(TUN_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1000L, 100L, 0L, 0L, 0L)
+              .addValues(TEST_IFACE, UID_VPN, SET_DEFAULT, TAG_NONE, 1100L, 100L, 0L, 0L, 0L));
+
+        // Query realtime stats for TEST_IFACE.
+        NetworkStats queriedStats =
+                mService.getDetailedUidStats(new String[] {TEST_IFACE});
+
+        assertEquals(HOUR_IN_MILLIS, queriedStats.getElapsedRealtime());
+        // verify that returned stats are only for TEST_IFACE and VPN traffic is migrated correctly.
+        assertEquals(new String[] {TEST_IFACE}, queriedStats.getUniqueIfaces());
+        assertValues(
+                queriedStats, TEST_IFACE, UID_RED, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
+                DEFAULT_NETWORK_ALL, 1000L, 100L, 0L, 0L, 0);
+        assertValues(
+                queriedStats, TEST_IFACE, UID_VPN, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
+                DEFAULT_NETWORK_ALL, 100L, 0L, 0L, 0L, 0);
+    }
+
+    @Test
     public void testRegisterUsageCallback() throws Exception {
         // pretend that wifi network comes online; service should ask about full
         // network state, and poll any existing interfaces before updating.
@@ -1382,11 +1757,11 @@
         return new NetworkState(info, prop, new NetworkCapabilities(), VPN_NETWORK, null, null);
     }
 
-    private static VpnInfo createVpnInfo(String underlyingIface) {
+    private static VpnInfo createVpnInfo(String[] underlyingIfaces) {
         VpnInfo info = new VpnInfo();
         info.ownerUid = UID_VPN;
         info.vpnIface = TUN_IFACE;
-        info.primaryUnderlyingIface = underlyingIface;
+        info.underlyingIfaces = underlyingIfaces;
         return info;
     }