diff options
216 files changed, 6495 insertions, 3264 deletions
diff --git a/api/test-current.txt b/api/test-current.txt index ee6fb51705a4..1a912a1c69b1 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -1917,6 +1917,10 @@ package android.os { 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 @@ package android.os { 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.health { } +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 @@ package android.util { 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/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index 16af0719d9dd..30f4ad4c5c69 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -69,6 +69,7 @@ static const char OEM_BOOTANIMATION_FILE[] = "/oem/media/bootanimation.zip"; static const char PRODUCT_BOOTANIMATION_DARK_FILE[] = "/product/media/bootanimation-dark.zip"; static const char PRODUCT_BOOTANIMATION_FILE[] = "/product/media/bootanimation.zip"; static const char SYSTEM_BOOTANIMATION_FILE[] = "/system/media/bootanimation.zip"; +static const char APEX_BOOTANIMATION_FILE[] = "/apex/com.android.bootanimation/etc/bootanimation.zip"; static const char PRODUCT_ENCRYPTED_BOOTANIMATION_FILE[] = "/product/media/bootanimation-encrypted.zip"; static const char SYSTEM_ENCRYPTED_BOOTANIMATION_FILE[] = "/system/media/bootanimation-encrypted.zip"; static const char OEM_SHUTDOWNANIMATION_FILE[] = "/oem/media/shutdownanimation.zip"; @@ -358,10 +359,10 @@ void BootAnimation::findBootAnimationFile() { const bool playDarkAnim = android::base::GetIntProperty("ro.boot.theme", 0) == 1; static const char* bootFiles[] = - {playDarkAnim ? PRODUCT_BOOTANIMATION_DARK_FILE : PRODUCT_BOOTANIMATION_FILE, + {APEX_BOOTANIMATION_FILE, playDarkAnim ? PRODUCT_BOOTANIMATION_DARK_FILE : PRODUCT_BOOTANIMATION_FILE, OEM_BOOTANIMATION_FILE, SYSTEM_BOOTANIMATION_FILE}; static const char* shutdownFiles[] = - {PRODUCT_SHUTDOWNANIMATION_FILE, OEM_SHUTDOWNANIMATION_FILE, SYSTEM_SHUTDOWNANIMATION_FILE}; + {PRODUCT_SHUTDOWNANIMATION_FILE, OEM_SHUTDOWNANIMATION_FILE, SYSTEM_SHUTDOWNANIMATION_FILE, ""}; for (const char* f : (!mShuttingDown ? bootFiles : shutdownFiles)) { if (access(f, R_OK) == 0) { diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 2899d49cf179..495a09f2e99a 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -48,6 +48,7 @@ import "frameworks/base/core/proto/android/stats/docsui/docsui_enums.proto"; import "frameworks/base/core/proto/android/stats/enums.proto"; import "frameworks/base/core/proto/android/stats/intelligence/enums.proto"; import "frameworks/base/core/proto/android/stats/launcher/launcher.proto"; +import "frameworks/base/core/proto/android/stats/location/location_enums.proto"; import "frameworks/base/core/proto/android/stats/mediametrics/mediametrics.proto"; import "frameworks/base/core/proto/android/stats/storage/storage_enums.proto"; import "frameworks/base/core/proto/android/stats/style/style_enums.proto"; @@ -301,6 +302,7 @@ message Atom { ContentCaptureServiceEvents content_capture_service_events = 207; ContentCaptureSessionEvents content_capture_session_events = 208; ContentCaptureFlushed content_capture_flushed = 209; + LocationManagerApiUsageReported location_manager_api_usage_reported = 210; } // Pulled events will start at field 10000. @@ -6485,3 +6487,60 @@ message AppOps { // while the app was in the background (only for trusted requests) optional int64 trusted_background_duration_millis = 9; } + +/** + * Location Manager API Usage information(e.g. API under usage, + * API call's parameters). + * Logged from: + * frameworks/base/services/core/java/com/android/server/LocationManagerService.java + */ +message LocationManagerApiUsageReported { + + // Indicating if usage starts or usage ends. + optional android.stats.location.UsageState state = 1; + + // LocationManagerService's API in use. + // We can identify which API from LocationManager is + // invoking current LMS API by the combination of + // API parameter(e.g. is_listener_null, is_intent_null, + // is_location_request_null) + optional android.stats.location.LocationManagerServiceApi api_in_use = 2; + + // Name of the package calling the API. + optional string calling_package_name = 3; + + // Type of the location provider. + optional android.stats.location.ProviderType provider = 4; + + // Quality of the location request + optional android.stats.location.LocationRequestQuality quality = 5; + + // The desired interval for active location updates, in milliseconds. + // Bucketized to reduce cardinality. + optional android.stats.location.LocationRequestIntervalBucket bucketized_interval = 6; + + // Minimum distance between location updates, in meters. + // Bucketized to reduce cardinality. + optional android.stats.location.SmallestDisplacementBucket + bucketized_smallest_displacement = 7; + + // The number of location updates. + optional int64 num_updates = 8; + + // The request expiration time, in millisecond since boot. + // Bucketized to reduce cardinality. + optional android.stats.location.ExpirationBucket + bucketized_expire_in = 9; + + // Type of Callback passed in for this API. + optional android.stats.location.CallbackType callback_type = 10; + + // The radius of the central point of the alert + // region, in meters. Only for API REQUEST_GEOFENCE. + // Bucketized to reduce cardinality. + optional android.stats.location.GeofenceRadiusBucket bucketized_radius = 11; + + // Activity Importance of API caller. + // Categorized to 3 types that are interesting from location's perspective. + optional android.stats.location.ActivityImportance activiy_importance = 12; +} diff --git a/config/hiddenapi-greylist-packages.txt b/config/hiddenapi-greylist-packages.txt index cae3bd94bc67..986d2591a007 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 82d0fbe32993..10f25ea1c8d1 100644 --- a/config/hiddenapi-greylist.txt +++ b/config/hiddenapi-greylist.txt @@ -1774,715 +1774,3 @@ Lcom/google/android/mms/util/SqliteWrapper;->query(Landroid/content/Context;Land 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/app/admin/DevicePolicyEventLogger.java b/core/java/android/app/admin/DevicePolicyEventLogger.java index 44ea21881e88..95a797392940 100644 --- a/core/java/android/app/admin/DevicePolicyEventLogger.java +++ b/core/java/android/app/admin/DevicePolicyEventLogger.java @@ -22,9 +22,10 @@ import android.stats.devicepolicy.nano.StringList; import android.util.StatsLog; import com.android.framework.protobuf.nano.MessageNano; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; +import java.util.Arrays; + /** * A wrapper for logging managed device events using {@link StatsLog}. * <p/> @@ -45,7 +46,7 @@ import com.android.internal.util.Preconditions; * @see StatsLog * @hide */ -public final class DevicePolicyEventLogger { +public class DevicePolicyEventLogger { private final int mEventId; private int mIntValue; private boolean mBooleanValue; @@ -71,7 +72,6 @@ public final class DevicePolicyEventLogger { /** * Returns the event id. */ - @VisibleForTesting public int getEventId() { return mEventId; } @@ -87,7 +87,6 @@ public final class DevicePolicyEventLogger { /** * Returns the generic <code>int</code> value. */ - @VisibleForTesting public int getInt() { return mIntValue; } @@ -103,7 +102,6 @@ public final class DevicePolicyEventLogger { /** * Returns the generic <code>boolean</code> value. */ - @VisibleForTesting public boolean getBoolean() { return mBooleanValue; } @@ -119,7 +117,6 @@ public final class DevicePolicyEventLogger { /** * Returns the time period in milliseconds. */ - @VisibleForTesting public long getTimePeriod() { return mTimePeriodMs; } @@ -162,11 +159,13 @@ public final class DevicePolicyEventLogger { } /** - * Returns the generic <code>String[]</code> value. + * Returns a copy of the generic <code>String[]</code> value. */ - @VisibleForTesting public String[] getStringArray() { - return mStringArrayValue; + if (mStringArrayValue == null) { + return null; + } + return Arrays.copyOf(mStringArrayValue, mStringArrayValue.length); } /** @@ -188,7 +187,6 @@ public final class DevicePolicyEventLogger { /** * Returns the package name of the admin application. */ - @VisibleForTesting public String getAdminPackageName() { return mAdminPackageName; } diff --git a/core/java/android/bluetooth/BluetoothProfileConnector.java b/core/java/android/bluetooth/BluetoothProfileConnector.java index d9987249a6e2..863fd3698cbd 100644 --- a/core/java/android/bluetooth/BluetoothProfileConnector.java +++ b/core/java/android/bluetooth/BluetoothProfileConnector.java @@ -32,12 +32,12 @@ import android.util.Log; * @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 @@ public abstract class BluetoothProfileConnector<T> { 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 b09eada27ce4..cb939f05ffc5 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -807,7 +807,7 @@ public abstract class PackageManager { * * @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 41be38a6e8dd..58aacc2c36c7 100644 --- a/core/java/android/content/pm/ShortcutInfo.java +++ b/core/java/android/content/pm/ShortcutInfo.java @@ -153,14 +153,21 @@ public final class ShortcutInfo implements Parcelable { 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 @@ public final class ShortcutInfo implements Parcelable { 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 @@ public final class ShortcutInfo implements Parcelable { 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/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index ab630fd7467b..82d4d1d10d7e 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -472,8 +472,12 @@ public class InputMethodService extends AbstractInputMethodService { */ @MainThread @Override - public final void initializeInternal(IBinder token, int displayId, + public final void initializeInternal(@NonNull IBinder token, int displayId, IInputMethodPrivilegedOperations privilegedOperations) { + if (InputMethodPrivilegedOperationsRegistry.isRegistered(token)) { + Log.w(TAG, "The token has already registered, ignore this initialization."); + return; + } mPrivOps.set(privilegedOperations); InputMethodPrivilegedOperationsRegistry.put(token, mPrivOps); updateInputMethodDisplay(displayId); diff --git a/core/java/android/net/DnsResolver.java b/core/java/android/net/DnsResolver.java index 4b2b4c35b292..7a85dcbfc830 100644 --- a/core/java/android/net/DnsResolver.java +++ b/core/java/android/net/DnsResolver.java @@ -16,7 +16,7 @@ package android.net; -import static android.net.NetworkUtils.getDnsNetId; +import static android.net.NetworkUtils.getDnsNetwork; import static android.net.NetworkUtils.resNetworkCancel; import static android.net.NetworkUtils.resNetworkQuery; import static android.net.NetworkUtils.resNetworkResult; @@ -333,7 +333,7 @@ public final class DnsResolver { final Object lock = new Object(); final Network queryNetwork; try { - queryNetwork = (network != null) ? network : new Network(getDnsNetId()); + queryNetwork = (network != null) ? network : getDnsNetwork(); } catch (ErrnoException e) { executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e))); return; @@ -433,7 +433,7 @@ public final class DnsResolver { final FileDescriptor queryfd; final Network queryNetwork; try { - queryNetwork = (network != null) ? network : new Network(getDnsNetId()); + queryNetwork = (network != null) ? network : getDnsNetwork(); queryfd = resNetworkQuery(queryNetwork.getNetIdForResolv(), domain, CLASS_IN, nsType, flags); } catch (ErrnoException e) { diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java index e892b650bf40..bb344e289657 100644 --- a/core/java/android/net/NetworkStats.java +++ b/core/java/android/net/NetworkStats.java @@ -18,6 +18,7 @@ package android.net; 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 libcore.util.EmptyArray; 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 @@ public class NetworkStats implements Parcelable { 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 @@ public class NetworkStats implements Parcelable { /** * 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. - * - * This method performs adjustments for one active VPN package and one VPN iface at a 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. * - * 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(); + + for (int i = 0; i < perInterfaceTotal.length; i++) { + perInterfaceTotal[i] = new Entry(); + } - tunAdjustmentInit(tunUid, tunIface, underlyingIface, tunIfaceTotal, underlyingIfaceTotal); + tunAdjustmentInit(tunUid, tunIface, underlyingIfaces, tunIfaceTotal, perInterfaceTotal, + underlyingIfacesTotal); - // 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. + // 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; + 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]; + if (underlyingIfacesTotal.rxBytes > 0) { + tmpEntry.rxBytes = + totalRxBytes + * perInterfaceTotal[j].rxBytes + / underlyingIfacesTotal.rxBytes; } - if (tunIfaceTotal.rxPackets > 0) { - tmpEntry.rxPackets = pool.rxPackets * rxPackets[i] / tunIfaceTotal.rxPackets; - } else { - tmpEntry.rxPackets = 0; + tmpEntry.rxPackets = 0; + if (underlyingIfacesTotal.rxPackets > 0) { + tmpEntry.rxPackets = + totalRxPackets + * perInterfaceTotal[j].rxPackets + / underlyingIfacesTotal.rxPackets; } - if (tunIfaceTotal.txBytes > 0) { - tmpEntry.txBytes = pool.txBytes * txBytes[i] / tunIfaceTotal.txBytes; - } else { - tmpEntry.txBytes = 0; + tmpEntry.txBytes = 0; + if (underlyingIfacesTotal.txBytes > 0) { + tmpEntry.txBytes = + totalTxBytes + * perInterfaceTotal[j].txBytes + / underlyingIfacesTotal.txBytes; } - if (tunIfaceTotal.txPackets > 0) { - tmpEntry.txPackets = pool.txPackets * txPackets[i] / tunIfaceTotal.txPackets; - } else { - tmpEntry.txPackets = 0; + tmpEntry.txPackets = 0; + if (underlyingIfacesTotal.txPackets > 0) { + tmpEntry.txPackets = + totalTxPackets + * perInterfaceTotal[j].txPackets + / underlyingIfacesTotal.txPackets; } - if (tunIfaceTotal.operations > 0) { + tmpEntry.operations = 0; + if (underlyingIfacesTotal.operations > 0) { tmpEntry.operations = - pool.operations * operations[i] / tunIfaceTotal.operations; - } else { - tmpEntry.operations = 0; + totalOperations + * perInterfaceTotal[j].operations + / underlyingIfacesTotal.operations; } - tmpEntry.uid = uid[i]; - tmpEntry.tag = tag[i]; - tmpEntry.set = set[i]; - tmpEntry.metered = metered[i]; - tmpEntry.roaming = roaming[i]; - tmpEntry.defaultNetwork = defaultNetwork[i]; + // 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 @@ public class NetworkStats implements Parcelable { 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); - - // 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); - } + 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. + 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/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java index a640f83ea5d2..d0f54b4c7f2d 100644 --- a/core/java/android/net/NetworkUtils.java +++ b/core/java/android/net/NetworkUtils.java @@ -158,10 +158,9 @@ public class NetworkUtils { /** * DNS resolver series jni method. - * Attempts to get netid of network which resolver will - * use if no network is explicitly selected. + * Attempts to get network which resolver will use if no network is explicitly selected. */ - public static native int getDnsNetId() throws ErrnoException; + public static native Network getDnsNetwork() throws ErrnoException; /** * Get the tcp repair window associated with the {@code fd}. diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index deb9eba1bc99..77d367f0d933 100755 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -113,8 +113,7 @@ public class Build { /** * A hardware serial number, if available. Alphanumeric only, case-insensitive. - * For apps targeting SDK higher than {@link Build.VERSION_CODES#O_MR1} this - * field is set to {@link Build#UNKNOWN}. + * This field is always set to {@link Build#UNKNOWN}. * * @deprecated Use {@link #getSerial()} instead. **/ diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java index 5039b31ed9ff..24a147783669 100644 --- a/core/java/android/os/GraphicsEnvironment.java +++ b/core/java/android/os/GraphicsEnvironment.java @@ -231,13 +231,6 @@ public class GraphicsEnvironment { } /** - * 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 @@ public class GraphicsEnvironment { 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 @@ public class GraphicsEnvironment { */ 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 de963c9927c2..fe2e948bfcd9 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -18,6 +18,7 @@ package android.os; 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 @@ public final class Parcel { * @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 edfdda8bb760..45384105cc8f 100644 --- a/core/java/android/os/SystemProperties.java +++ b/core/java/android/os/SystemProperties.java @@ -174,6 +174,7 @@ public class SystemProperties { * @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 f1f24fb4dd89..921f0f2ab1e2 100644 --- a/core/java/android/os/image/DynamicSystemClient.java +++ b/core/java/android/os/image/DynamicSystemClient.java @@ -22,6 +22,7 @@ import android.annotation.NonNull; 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 @@ import java.util.concurrent.Executor; * @hide */ @SystemApi +@TestApi public class DynamicSystemClient { /** @hide */ @IntDef(prefix = { "STATUS_" }, value = { @@ -283,6 +285,7 @@ public class DynamicSystemClient { * @hide */ @SystemApi + @TestApi public DynamicSystemClient(@NonNull Context context) { mContext = context; mConnection = new DynSystemServiceConnection(); @@ -314,8 +317,11 @@ public class DynamicSystemClient { * 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 @@ public class DynamicSystemClient { /** * 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 @@ public class DynamicSystemClient { * * @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 66b9d168d71d..0db5c36eb961 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -8081,6 +8081,15 @@ public final class Settings { "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 @@ public final class Settings { 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 @@ public final class Settings { 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 c42dc817bec4..324e02ceb1d5 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 @@ import java.util.Map; * * @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 9436633c0c4b..73e0e4b2eb8b 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -1301,7 +1301,7 @@ public abstract class Window { @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 fca97fee603e..ee998371c941 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -1434,6 +1434,22 @@ public class ChooserActivity extends ResolverActivity { 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 @@ public class ChooserActivity extends ResolverActivity { 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 @@ public class ChooserActivity extends ResolverActivity { 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 @@ public class ChooserActivity extends ResolverActivity { */ @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 @@ public class ChooserActivity extends ResolverActivity { @Override public void onListRebuilt() { + updateAlphabeticalList(); + // don't support direct share on low ram devices if (ActivityManager.isLowRamDeviceStatic()) { return; @@ -2379,7 +2415,6 @@ public class ChooserActivity extends ResolverActivity { queryTargetServices(this); } - updateAlphabeticalList(); } @Override @@ -2830,7 +2865,7 @@ public class ChooserActivity extends ResolverActivity { // 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 538c81d54129..0a01beba659f 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -383,9 +383,11 @@ public class ResolverActivity extends Activity { 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 14fe6ab7df1c..1b0a458b3fbc 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java @@ -113,5 +113,37 @@ public final class SystemUiDeviceConfigFlags { */ 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"; + + /** + * (long) How long, in milliseconds, for teaching behaviors to wait before considering the user + * taught. + */ + public static final String ASSIST_HANDLES_LEARN_TIME_MS = "assist_handles_learn_time_ms"; + + /** + * (int) How many times for teaching behaviors to see the user perform an action to consider it + * taught. + */ + public static final String ASSIST_HANDLES_LEARN_COUNT = "assist_handles_learn_count"; + private SystemUiDeviceConfigFlags() { } } diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperationsRegistry.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperationsRegistry.java index 1436aed5129a..049f952fceb8 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperationsRegistry.java +++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperationsRegistry.java @@ -132,4 +132,21 @@ public final class InputMethodPrivilegedOperationsRegistry { } } } + + /** + * Check the given IME token registration status. + * + * @param token IME token + * @return {@code true} when the IME token has already registered + * {@link InputMethodPrivilegedOperations}, {@code false} otherwise. + */ + @AnyThread + public static boolean isRegistered(IBinder token) { + synchronized (sLock) { + if (sRegistry == null) { + return false; + } + return sRegistry.containsKey(token); + } + } } diff --git a/core/java/com/android/internal/net/VpnInfo.java b/core/java/com/android/internal/net/VpnInfo.java index b1a412871bd2..e74af5eb50de 100644 --- a/core/java/com/android/internal/net/VpnInfo.java +++ b/core/java/com/android/internal/net/VpnInfo.java @@ -19,6 +19,8 @@ package com.android.internal.net; 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 @@ import android.os.Parcelable; 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 class VpnInfo implements Parcelable { 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 @@ public class VpnInfo implements Parcelable { 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/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp index 00e0e3a74d70..08aa1d97fa1c 100644 --- a/core/jni/android_net_NetUtils.cpp +++ b/core/jni/android_net_NetUtils.cpp @@ -304,13 +304,19 @@ static void android_net_utils_resNetworkCancel(JNIEnv *env, jobject thiz, jobjec jniSetFileDescriptorOfFD(env, javaFd, -1); } -static jint android_net_utils_getDnsNetId(JNIEnv *env, jobject thiz) { - int dnsNetId = getNetworkForDns(); - if (dnsNetId < 0) { - throwErrnoException(env, "getDnsNetId", -dnsNetId); +static jobject android_net_utils_getDnsNetwork(JNIEnv *env, jobject thiz) { + unsigned dnsNetId = 0; + if (int res = getNetworkForDns(&dnsNetId) < 0) { + throwErrnoException(env, "getDnsNetId", -res); + return nullptr; } + bool privateDnsBypass = dnsNetId & NETID_USE_LOCAL_NAMESERVERS; - return dnsNetId; + static jclass class_Network = MakeGlobalRefOrDie( + env, FindClassOrDie(env, "android/net/Network")); + static jmethodID ctor = env->GetMethodID(class_Network, "<init>", "(IZ)V"); + return env->NewObject( + class_Network, ctor, dnsNetId & ~NETID_USE_LOCAL_NAMESERVERS, privateDnsBypass); } static jobject android_net_utils_getTcpRepairWindow(JNIEnv *env, jobject thiz, jobject javaFd) { @@ -369,7 +375,7 @@ static const JNINativeMethod gNetworkUtilMethods[] = { { "resNetworkQuery", "(ILjava/lang/String;III)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkQuery }, { "resNetworkResult", "(Ljava/io/FileDescriptor;)Landroid/net/DnsResolver$DnsResponse;", (void*) android_net_utils_resNetworkResult }, { "resNetworkCancel", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_resNetworkCancel }, - { "getDnsNetId", "()I", (void*) android_net_utils_getDnsNetId }, + { "getDnsNetwork", "()Landroid/net/Network;", (void*) android_net_utils_getDnsNetwork }, }; int register_android_net_NetworkUtils(JNIEnv* env) diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto index 3f8ddff5327c..28733798b9ad 100644 --- a/core/proto/android/server/jobscheduler.proto +++ b/core/proto/android/server/jobscheduler.proto @@ -273,9 +273,11 @@ message ConstantsProto { // 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 @@ message ConstantsProto { // 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 @@ message StateControllerProto { 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 job_count_in_allowed_time should be considered invalid, in the - * elapsed realtime timebase. - */ + // 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_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 time after which {@link #timingSessionCountInAllowedTime} should be considered - * invalid, in the elapsed realtime timebase. - */ + // 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. 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/proto/android/stats/location/location_enums.proto b/core/proto/android/stats/location/location_enums.proto new file mode 100644 index 000000000000..553c01c5d0dd --- /dev/null +++ b/core/proto/android/stats/location/location_enums.proto @@ -0,0 +1,122 @@ +/* + * 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. + */ + +syntax = "proto2"; + +package android.stats.location; +option java_outer_classname = "LocationStatsEnums"; + + +// APIs from LocationManagerService +enum LocationManagerServiceApi { + API_UNKNOWN = 0; + API_REQUEST_LOCATION_UPDATES = 1; + API_ADD_GNSS_MEASUREMENTS_LISTENER = 2; + API_REGISTER_GNSS_STATUS_CALLBACK = 3; + API_REQUEST_GEOFENCE = 4; + API_SEND_EXTRA_COMMAND = 5; +} + +enum UsageState { + USAGE_STARTED = 0; + USAGE_ENDED = 1; +} + +// Type of location providers +enum ProviderType { + PROVIDER_UNKNOWN = 0; + PROVIDER_NETWORK = 1; + PROVIDER_GPS = 2; + PROVIDER_PASSIVE = 3; + PROVIDER_FUSED = 4; +} + +// Type of Callback passed in for this API +enum CallbackType { + CALLBACK_UNKNOWN = 0; + // Current API does not need a callback, e.g. sendExtraCommand + CALLBACK_NOT_APPLICABLE = 1; + CALLBACK_LISTENER = 2; + CALLBACK_PENDING_INTENT = 3; +} + +// Possible values for mQuality field in +// frameworks/base/location/java/android/location/LocationRequest.java +enum LocationRequestQuality { + QUALITY_UNKNOWN = 0; + ACCURACY_FINE = 100; + ACCURACY_BLOCK = 102; + ACCURACY_CITY = 104; + POWER_NONE = 200; + POWER_LOW = 201; + POWER_HIGH = 203; +} + +// Bucketized values for interval field in +// frameworks/base/location/java/android/location/LocationRequest.java +enum LocationRequestIntervalBucket { + INTERVAL_UNKNOWN = 0; + INTERVAL_BETWEEN_0_SEC_AND_1_SEC = 1; + INTERVAL_BETWEEN_1_SEC_AND_5_SEC = 2; + INTERVAL_BETWEEN_5_SEC_AND_1_MIN = 3; + INTERVAL_BETWEEN_1_MIN_AND_10_MIN = 4; + INTERVAL_BETWEEN_10_MIN_AND_1_HOUR = 5; + INTERVAL_LARGER_THAN_1_HOUR = 6; +} + +// Bucketized values for small displacement field in +// frameworks/base/location/java/android/location/LocationRequest.java +// Value in meters. +enum SmallestDisplacementBucket { + DISTANCE_UNKNOWN = 0; + DISTANCE_ZERO = 1; + DISTANCE_BETWEEN_0_AND_100 = 2; + DISTANCE_LARGER_THAN_100 = 3; +} + +// Bucketized values for expire_in field in +// frameworks/base/location/java/android/location/LocationRequest.java +enum ExpirationBucket { + EXPIRATION_UNKNOWN = 0; + EXPIRATION_BETWEEN_0_AND_20_SEC = 1; + EXPIRATION_BETWEEN_20_SEC_AND_1_MIN = 2; + EXPIRATION_BETWEEN_1_MIN_AND_10_MIN = 3; + EXPIRATION_BETWEEN_10_MIN_AND_1_HOUR = 4; + EXPIRATION_LARGER_THAN_1_HOUR = 5; + EXPIRATION_NO_EXPIRY = 6; +} + +// Bucketized values for radius field in +// frameworks/base/location/java/android/location/Geofence.java +// Value in meters. +enum GeofenceRadiusBucket { + RADIUS_UNKNOWN = 0; + RADIUS_BETWEEN_0_AND_100 = 1; + RADIUS_BETWEEN_100_AND_200 = 2; + RADIUS_BETWEEN_200_AND_300 = 3; + RADIUS_BETWEEN_300_AND_1000 = 4; + RADIUS_BETWEEN_1000_AND_10000 = 5; + RADIUS_LARGER_THAN_100000 = 6; + RADIUS_NEGATIVE = 7; +} + +// Caller Activity Importance. +enum ActivityImportance { + IMPORTANCE_UNKNOWN = 0; + IMPORTANCE_TOP = 1; + IMPORTANCE_FORGROUND_SERVICE = 2; + IMPORTANCE_BACKGROUND = 3; +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index ab7aed364401..890ad5e839f7 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/packages/SystemUI/res/values-mcc310-mnc030/config.xml b/core/res/res/values-mcc310-mnc030/config.xml index 26b9192e0cc3..26b9192e0cc3 100644 --- a/packages/SystemUI/res/values-mcc310-mnc030/config.xml +++ b/core/res/res/values-mcc310-mnc030/config.xml diff --git a/packages/SystemUI/res/values-mcc310-mnc070/config.xml b/core/res/res/values-mcc310-mnc070/config.xml index 26b9192e0cc3..26b9192e0cc3 100644 --- a/packages/SystemUI/res/values-mcc310-mnc070/config.xml +++ b/core/res/res/values-mcc310-mnc070/config.xml diff --git a/packages/SystemUI/res/values-mcc310-mnc170/config.xml b/core/res/res/values-mcc310-mnc170/config.xml index 26b9192e0cc3..26b9192e0cc3 100644 --- a/packages/SystemUI/res/values-mcc310-mnc170/config.xml +++ b/core/res/res/values-mcc310-mnc170/config.xml diff --git a/packages/SystemUI/res/values-mcc310-mnc280/config.xml b/core/res/res/values-mcc310-mnc280/config.xml index 26b9192e0cc3..26b9192e0cc3 100644 --- a/packages/SystemUI/res/values-mcc310-mnc280/config.xml +++ b/core/res/res/values-mcc310-mnc280/config.xml diff --git a/packages/SystemUI/res/values-mcc310-mnc380/config.xml b/core/res/res/values-mcc310-mnc380/config.xml index 26b9192e0cc3..26b9192e0cc3 100644 --- a/packages/SystemUI/res/values-mcc310-mnc380/config.xml +++ b/core/res/res/values-mcc310-mnc380/config.xml diff --git a/core/res/res/values-mcc310-mnc410/config.xml b/core/res/res/values-mcc310-mnc410/config.xml index 00ab7127c331..3fb3f0f7e9ff 100644 --- a/core/res/res/values-mcc310-mnc410/config.xml +++ b/core/res/res/values-mcc310-mnc410/config.xml @@ -48,4 +48,8 @@ <item>"#8"</item> <item>"#9"</item> </string-array> + + <!-- Enable 5 bar signal strength icon --> + <bool name="config_inflateSignalStrength">true</bool> + </resources> diff --git a/packages/SystemUI/res/values-mcc310-mnc410/config.xml b/core/res/res/values-mcc310-mnc560/config.xml index 26b9192e0cc3..26b9192e0cc3 100644 --- a/packages/SystemUI/res/values-mcc310-mnc410/config.xml +++ b/core/res/res/values-mcc310-mnc560/config.xml diff --git a/packages/SystemUI/res/values-mcc310-mnc560/config.xml b/core/res/res/values-mcc310-mnc950/config.xml index 26b9192e0cc3..26b9192e0cc3 100644 --- a/packages/SystemUI/res/values-mcc310-mnc560/config.xml +++ b/core/res/res/values-mcc310-mnc950/config.xml diff --git a/packages/SystemUI/res/values-mcc310-mnc950/config.xml b/core/res/res/values-mcc311-mnc180/config.xml index 26b9192e0cc3..26b9192e0cc3 100644 --- a/packages/SystemUI/res/values-mcc310-mnc950/config.xml +++ b/core/res/res/values-mcc311-mnc180/config.xml diff --git a/core/res/res/values-mcc311-mnc480/config.xml b/core/res/res/values-mcc311-mnc480/config.xml index db2f8d01f93a..336e30e45b00 100755 --- a/core/res/res/values-mcc311-mnc480/config.xml +++ b/core/res/res/values-mcc311-mnc480/config.xml @@ -40,4 +40,7 @@ <bool name="config_use_sim_language_file">true</bool> + <!-- Enable 5 bar signal strength icon --> + <bool name="config_inflateSignalStrength">true</bool> + </resources> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 2de539721195..2beef4250441 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1276,9 +1276,14 @@ meanings. --> <integer name="config_defaultRingVibrationIntensity">2</integer> + <!-- Whether to use the strict phone number matcher by default. --> <bool name="config_use_strict_phone_number_comparation">false</bool> - <bool name="config_use_strict_phone_number_comparation_for_russian">true</bool> + <!-- Whether to use the strict phone number matcher in Russia. --> + <bool name="config_use_strict_phone_number_comparation_for_russia">true</bool> + + <!-- Whether to use the strict phone number matcher in Kazakhstan. --> + <bool name="config_use_strict_phone_number_comparation_for_kazakhstan">true</bool> <!-- Display low battery warning when battery level dips to this value. Also, the battery stats are flushed to disk when we hit this level. --> @@ -3306,6 +3311,10 @@ (which normally prevents seamless rotation). --> <bool name="config_allowSeamlessRotationDespiteNavBarMoving">false</bool> + <!-- Controls whether hints for gestural navigation are shown when the device is setup. + This should only be set when the device has gestural navigation enabled by default. --> + <bool name="config_showGesturalNavigationHints">false</bool> + <!-- Default insets [LEFT/RIGHTxTOP/BOTTOM] from the screen edge for picture-in-picture windows. These values are in DPs and will be converted to pixel sizes internally. --> <string translatable="false" name="config_defaultPictureInPictureScreenEdgeInsets">16x16</string> @@ -4121,4 +4130,9 @@ even after user setup is complete. The defined component should be used for supervision purposes only. The component must be part of a system app. --> <string name="config_defaultSupervisionProfileOwnerComponent" translatable="false"></string> + + <!-- Whether to artificially interpret all signal strengths as + one bar higher than they actually are --> + <bool name="config_inflateSignalStrength">false</bool> + </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 6cdb3d67192c..4af05f699073 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -313,7 +313,8 @@ <java-symbol type="bool" name="config_ui_enableFadingMarquee" /> <java-symbol type="bool" name="config_enableHapticTextHandle" /> <java-symbol type="bool" name="config_use_strict_phone_number_comparation" /> - <java-symbol type="bool" name="config_use_strict_phone_number_comparation_for_russian" /> + <java-symbol type="bool" name="config_use_strict_phone_number_comparation_for_russia" /> + <java-symbol type="bool" name="config_use_strict_phone_number_comparation_for_kazakhstan" /> <java-symbol type="bool" name="config_single_volume" /> <java-symbol type="bool" name="config_voice_capable" /> <java-symbol type="bool" name="config_requireCallCapableAccountForHandle" /> @@ -2880,6 +2881,7 @@ <java-symbol type="bool" name="config_allowSeamlessRotationDespiteNavBarMoving" /> <java-symbol type="dimen" name="config_backGestureInset" /> <java-symbol type="color" name="system_bar_background_semi_transparent" /> + <java-symbol type="bool" name="config_showGesturalNavigationHints" /> <!-- EditText suggestion popup. --> <java-symbol type="id" name="suggestionWindowContainer" /> @@ -3797,4 +3799,5 @@ <java-symbol type="style" name="Animation.DeviceDefault.Activity.Resolver" /> <java-symbol type="string" name="config_defaultSupervisionProfileOwnerComponent" /> + <java-symbol type="bool" name="config_inflateSignalStrength" /> </resources> diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml index 628926231c65..b90104899e55 100644 --- a/core/res/res/values/themes_device_defaults.xml +++ b/core/res/res/values/themes_device_defaults.xml @@ -1473,10 +1473,14 @@ easier. <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 1b6560322a13..707d7b30e09b 100644 --- a/core/tests/benchmarks/src/android/net/NetworkStatsBenchmark.java +++ b/core/tests/benchmarks/src/android/net/NetworkStatsBenchmark.java @@ -19,13 +19,22 @@ package android.net; 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 @@ public class NetworkStatsBenchmark { 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 @@ public class NetworkStatsBenchmark { 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 37020fc8d79f..9272ea5e584b 100644 --- a/data/etc/car/Android.bp +++ b/data/etc/car/Android.bp @@ -31,6 +31,13 @@ prebuilt_etc { } 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 000000000000..d7f29da7a356 --- /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 f1797deed075..19548bc404d1 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 6b26e8f3d5f2..d36a82684e9e 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/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index b3856d501c72..a640122f2b32 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -196,6 +196,7 @@ applications that come with the platform <permission name="android.permission.USE_RESERVED_DISK"/> <permission name="android.permission.WRITE_MEDIA_STORAGE"/> <permission name="android.permission.WATCH_APPOPS"/> + <permission name="android.permission.UPDATE_DEVICE_STATS"/> </privapp-permissions> <privapp-permissions package="com.android.providers.telephony"> diff --git a/graphics/java/android/graphics/Outline.java b/graphics/java/android/graphics/Outline.java index 98c990a71601..1fc056c3652f 100644 --- a/graphics/java/android/graphics/Outline.java +++ b/graphics/java/android/graphics/Outline.java @@ -273,8 +273,12 @@ public final class Outline { } /** - * Sets the Constructs an Outline from a + * Sets the Outline to a * {@link android.graphics.Path#isConvex() convex path}. + * + * @param convexPath used to construct the Outline. As of + * {@link android.os.Build.VERSION_CODES#Q}, it is no longer required to be + * convex. */ public void setConvexPath(@NonNull Path convexPath) { if (convexPath.isEmpty()) { @@ -282,10 +286,6 @@ public final class Outline { return; } - if (!convexPath.isConvex()) { - throw new IllegalArgumentException("path must be convex"); - } - if (mPath == null) { mPath = new Path(); } diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java index dc3041fb4878..820d82dd3bd1 100644 --- a/media/java/android/media/AudioAttributes.java +++ b/media/java/android/media/AudioAttributes.java @@ -222,10 +222,10 @@ public final class AudioAttributes implements Parcelable { 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 @@ public final class AudioAttributes implements Parcelable { 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 @@ public final class AudioAttributes implements Parcelable { 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/AudioManager.java b/media/java/android/media/AudioManager.java index 2541982e5a5a..2d6cd242c702 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -4274,38 +4274,6 @@ public class AudioManager { } } - /** - * Indicate A2DP source or sink active device change and eventually suppress - * the {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY} intent. - * This operation is asynchronous but its execution will still be sequentially scheduled - * relative to calls to {@link #setBluetoothHearingAidDeviceConnectionState(BluetoothDevice, - * int, boolean, int)} and - * {@link #handleBluetoothA2dpDeviceConfigChange(BluetoothDevice)}. - * @param device Bluetooth device connected/disconnected - * @param state new connection state (BluetoothProfile.STATE_xxx) - * @param profile profile for the A2DP device - * (either {@link android.bluetooth.BluetoothProfile.A2DP} or - * {@link android.bluetooth.BluetoothProfile.A2DP_SINK}) - * @param a2dpVolume New volume for the connecting device. Does nothing if - * disconnecting. Pass value -1 in case you want this field to be ignored - * @param suppressNoisyIntent if true the - * {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY} intent will not be sent. - * @return a delay in ms that the caller should wait before broadcasting - * BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED intent. - * {@hide} - */ - public void handleBluetoothA2dpActiveDeviceChange( - BluetoothDevice device, int state, int profile, - boolean suppressNoisyIntent, int a2dpVolume) { - final IAudioService service = getService(); - try { - service.handleBluetoothA2dpActiveDeviceChange(device, - state, profile, suppressNoisyIntent, a2dpVolume); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - /** {@hide} */ public IRingtonePlayer getRingtonePlayer() { try { diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java index 58c6be9be4c7..5645ba5d7dac 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -1794,15 +1794,13 @@ public class ExifInterface { // Set thumbnail image offset and length setThumbnailData(inputStream); mIsSupportedFile = true; - } catch (IOException e) { + } catch (IOException | OutOfMemoryError e) { // Ignore exceptions in order to keep the compatibility with the old versions of // ExifInterface. mIsSupportedFile = false; - if (DEBUG) { - Log.d(TAG, "Invalid image: ExifInterface got an unsupported image format file" - + "(ExifInterface supports JPEG and some RAW image formats only) " - + "or a corrupted JPEG file to ExifInterface.", e); - } + Log.w(TAG, "Invalid image: ExifInterface got an unsupported image format file" + + "(ExifInterface supports JPEG and some RAW image formats only) " + + "or a corrupted JPEG file to ExifInterface.", e); } finally { addDefaultValuesForCompatibility(); diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index a790441aa36e..71f52a1b7d8e 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -178,9 +178,6 @@ interface IAudioService { void handleBluetoothA2dpDeviceConfigChange(in BluetoothDevice device); - void handleBluetoothA2dpActiveDeviceChange(in BluetoothDevice device, - int state, int profile, boolean suppressNoisyIntent, int a2dpVolume); - @UnsupportedAppUsage AudioRoutesInfo startWatchingRoutes(in IAudioRoutesObserver observer); diff --git a/media/java/android/media/MediaHTTPConnection.java b/media/java/android/media/MediaHTTPConnection.java index b5c4cca12ff7..8ee929e77899 100644 --- a/media/java/android/media/MediaHTTPConnection.java +++ b/media/java/android/media/MediaHTTPConnection.java @@ -38,6 +38,7 @@ import java.net.URL; 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 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub { 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 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub { @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 @@ public class MediaHTTPConnection extends IMediaHTTPConnection.Stub { 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 7f23ba576fbb..9b643ad3c086 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.PictureCallback; 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 @@ public class CameraTest extends ActivityInstrumentationTestCase<MediaFrameworkTe 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 84153d6089e9..bd236a69fe84 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; 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 @@ public class CameraTestHelper { 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 class CameraTestHelper { 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 class CameraTestHelper { 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 0340cec7432d..0ae640dd7910 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.Image.Plane; 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.Display; 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 @@ public class CameraTestUtils extends Assert { 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 11327ca301f3..a26ee2d1a668 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 @@ public class Camera2SwitchPreviewTest extends Camera2SurfaceViewTestCase { 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 @@ public class Camera2SwitchPreviewTest extends Camera2SurfaceViewTestCase { 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 @@ public class Camera2SwitchPreviewTest extends Camera2SurfaceViewTestCase { } // 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 d1193deacecf..74244b9745f0 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.concurrent.TimeUnit; 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.test.suitebuilder.annotation.LargeTest; 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 @@ public class CameraStressTest extends ActivityInstrumentationTestCase2<MediaFram 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 7ee2f0a1db18..8fe4fecceeb5 100644 --- a/native/android/surface_control.cpp +++ b/native/android/surface_control.cpp @@ -384,6 +384,9 @@ void ASurfaceTransaction_setGeometry(ASurfaceTransaction* aSurfaceTransaction, 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/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java index bda5743d27f7..23fb7b6c8ba2 100644 --- a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java +++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java @@ -485,7 +485,10 @@ public class CaptivePortalLoginActivity extends Activity { if (request.isForMainFrame()) { mMainFrameUrl = request.getUrl().toString(); } - return false; + // Be careful that two shouldOverrideUrlLoading methods are overridden, but + // shouldOverrideUrlLoading(WebView view, String url) was deprecated in API level 24. + // TODO: delete deprecated one ?? + return shouldOverrideUrlLoading(view, mMainFrameUrl); } // A web page consisting of a large broken lock icon to indicate SSL failure. diff --git a/packages/CarSystemUI/res/layout/car_ongoing_privacy_chip.xml b/packages/CarSystemUI/res/layout/car_ongoing_privacy_chip.xml deleted file mode 100644 index 918abd962dc2..000000000000 --- a/packages/CarSystemUI/res/layout/car_ongoing_privacy_chip.xml +++ /dev/null @@ -1,37 +0,0 @@ -<?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. ---> - -<com.android.systemui.statusbar.car.privacy.OngoingPrivacyChip - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/car_privacy_chip" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:layout_margin="@dimen/ongoing_appops_chip_margin" - android:layout_toStartOf="@+id/clock_container" - android:focusable="true" - android:gravity="center_vertical|end" - android:orientation="horizontal" - android:paddingEnd="@dimen/ongoing_appops_chip_side_padding" - android:paddingStart="@dimen/ongoing_appops_chip_side_padding" - android:visibility="visible"> - - <LinearLayout - android:id="@+id/icons_container" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:gravity="center_vertical|start"/> -</com.android.systemui.statusbar.car.privacy.OngoingPrivacyChip>
\ No newline at end of file diff --git a/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml index cae89c16a03e..925ccb4a162a 100644 --- a/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml +++ b/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml @@ -65,8 +65,6 @@ /> </FrameLayout> - <include layout="@layout/car_ongoing_privacy_chip"/> - <FrameLayout android:id="@+id/clock_container" android:layout_width="wrap_content" diff --git a/packages/CarSystemUI/res/layout/notification_center_activity.xml b/packages/CarSystemUI/res/layout/notification_center_activity.xml index 55b0d875de41..0af74c4462a6 100644 --- a/packages/CarSystemUI/res/layout/notification_center_activity.xml +++ b/packages/CarSystemUI/res/layout/notification_center_activity.xml @@ -38,7 +38,7 @@ android:layout_width="0dp" android:layout_height="0dp" android:orientation="vertical" - android:paddingStart="@dimen/notification_shade_list_padding_bottom" + android:paddingBottom="@dimen/notification_shade_list_padding_bottom" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" diff --git a/packages/CarSystemUI/res/values/dimens.xml b/packages/CarSystemUI/res/values/dimens.xml index 7027ce37f670..0358357b9c1a 100644 --- a/packages/CarSystemUI/res/values/dimens.xml +++ b/packages/CarSystemUI/res/values/dimens.xml @@ -59,16 +59,6 @@ <dimen name="car_keyline_1">24dp</dimen> <dimen name="car_keyline_2">96dp</dimen> <dimen name="car_keyline_3">128dp</dimen> - <dimen name="privacy_chip_icon_height">36dp</dimen> - <dimen name="privacy_chip_icon_padding_left">0dp</dimen> - <dimen name="privacy_chip_icon_padding_right">0dp</dimen> - <dimen name="privacy_chip_icon_padding_top">0dp</dimen> - <dimen name="privacy_chip_icon_padding_bottom">0dp</dimen> - - <dimen name="privacy_chip_text_padding_left">0dp</dimen> - <dimen name="privacy_chip_text_padding_right">0dp</dimen> - <dimen name="privacy_chip_text_padding_top">0dp</dimen> - <dimen name="privacy_chip_text_padding_bottom">0dp</dimen> <dimen name="privacy_chip_icon_max_height">100dp</dimen> @@ -86,16 +76,6 @@ <dimen name="ongoing_appops_chip_bg_padding">4dp</dimen> <!-- Radius of Ongoing App Ops chip corners --> <dimen name="ongoing_appops_chip_bg_corner_radius">12dp</dimen> - <!-- Start padding for the app icon displayed in the dialog --> - <dimen name="privacy_dialog_app_icon_padding_start">40dp</dimen> - <!-- End padding for the app opps icon displayed in the dialog --> - <dimen name="privacy_dialog_app_ops_icon_padding_end">40dp</dimen> - <!-- Top padding for the list of application displayed in the dialog --> - <dimen name="privacy_dialog_app_list_padding_top">20dp</dimen> - <!-- Top padding for the dialog container--> - <dimen name="privacy_dialog_container_padding_top">10dp</dimen> - <!-- Top padding for the dialog title--> - <dimen name="privacy_dialog_title_padding_start">10dp</dimen> <!-- Car volume dimens. --> <dimen name="car_volume_item_height">@*android:dimen/car_single_line_list_item_height</dimen> diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java index b6b34c760594..7fbdc2ed844b 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java @@ -389,22 +389,18 @@ public class CarStatusBar extends StatusBar implements } } }); + + // Attached to the Handle bar to close the notification shade + GestureDetector handleBarCloseNotificationGestureDetector = new GestureDetector(mContext, + new HandleBarCloseNotificationGestureListener()); + mNavBarNotificationTouchListener = (v, event) -> { boolean consumed = navBarCloseNotificationGestureDetector.onTouchEvent(event); if (consumed) { return true; } - if (event.getActionMasked() == MotionEvent.ACTION_UP - && mNotificationView.getVisibility() == View.VISIBLE) { - if (mSettleClosePercentage < mPercentageFromBottom) { - animateNotificationPanel( - DEFAULT_FLING_VELOCITY, false); - } else { - animateNotificationPanel(DEFAULT_FLING_VELOCITY, - true); - } - } + maybeCompleteAnimation(event); return true; }; @@ -418,15 +414,7 @@ public class CarStatusBar extends StatusBar implements if (consumed) { return true; } - if (event1.getActionMasked() == MotionEvent.ACTION_UP - && mNotificationView.getVisibility() == View.VISIBLE) { - if (mSettleOpenPercentage > mPercentageFromBottom) { - animateNotificationPanel(DEFAULT_FLING_VELOCITY, true); - } else { - animateNotificationPanel( - DEFAULT_FLING_VELOCITY, false); - } - } + maybeCompleteAnimation(event1); return true; } ); @@ -498,6 +486,13 @@ public class CarStatusBar extends StatusBar implements } return false; }); + + mHandleBar.setOnTouchListener((v, event) -> { + handleBarCloseNotificationGestureDetector.onTouchEvent(event); + maybeCompleteAnimation(event); + return true; + }); + mNotificationList = mNotificationView.findViewById(R.id.notifications); mNotificationList.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override @@ -612,6 +607,17 @@ public class CarStatusBar extends StatusBar implements setPanelExpanded(false); } + private void maybeCompleteAnimation(MotionEvent event) { + if (event.getActionMasked() == MotionEvent.ACTION_UP + && mNotificationView.getVisibility() == View.VISIBLE) { + if (mSettleClosePercentage < mPercentageFromBottom) { + animateNotificationPanel(DEFAULT_FLING_VELOCITY, false); + } else { + animateNotificationPanel(DEFAULT_FLING_VELOCITY, true); + } + } + } + /** * Animates the notification shade from one position to other. This is used to either open or * close the notification shade completely with a velocity. If the animation is to close the @@ -1054,8 +1060,10 @@ public class CarStatusBar extends StatusBar implements private static final int SWIPE_MAX_OFF_PATH = 75; private static final int SWIPE_THRESHOLD_VELOCITY = 200; - // Only responsible for open hooks. Since once the panel opens it covers all elements - // there is no need to merge with close. + /** + * Only responsible for open hooks. Since once the panel opens it covers all elements + * there is no need to merge with close. + */ private abstract class OpenNotificationGestureListener extends GestureDetector.SimpleOnGestureListener { @@ -1098,7 +1106,9 @@ public class CarStatusBar extends StatusBar implements protected abstract void openNotification(); } - // to be installed on the open panel notification panel + /** + * To be installed on the open panel notification panel + */ private abstract class CloseNotificationGestureListener extends GestureDetector.SimpleOnGestureListener { @@ -1172,7 +1182,9 @@ public class CarStatusBar extends StatusBar implements protected abstract void close(); } - // To be installed on the nav bars. + /** + * To be installed on the nav bars. + */ private abstract class NavBarCloseNotificationGestureListener extends CloseNotificationGestureListener { @Override @@ -1201,7 +1213,28 @@ public class CarStatusBar extends StatusBar implements } /** - * SystemUi version onf the notification manager that overrides methods such that the + * To be installed on the handle bar. + */ + private class HandleBarCloseNotificationGestureListener extends + GestureDetector.SimpleOnGestureListener { + + @Override + public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX, + float distanceY) { + calculatePercentageFromBottom(event2.getRawY()); + // To prevent the jump in the clip bounds while closing the notification shade using + // the handle bar we should calculate the height using the diff of event1 and event2. + // This will help the notification shade to clip smoothly as the event2 value changes + // as event1 value will be fixed. + int clipHeight = + mNotificationView.getHeight() - (int) (event1.getRawY() - event2.getRawY()); + setNotificationViewClipBounds(clipHeight); + return true; + } + } + + /** + * SystemUi version of the notification manager that overrides methods such that the * notifications end up in the status bar layouts instead of a standalone window. */ private class CarSystemUIHeadsUpNotificationManager extends CarHeadsUpNotificationManager { diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/OngoingPrivacyChip.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/OngoingPrivacyChip.java deleted file mode 100644 index ead1de2bd352..000000000000 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/OngoingPrivacyChip.java +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.car.privacy; - -import android.app.ActivityManager; -import android.app.AppOpsManager; -import android.content.Context; -import android.content.Intent; -import android.os.Handler; -import android.os.Looper; -import android.os.UserHandle; -import android.os.UserManager; -import android.util.AttributeSet; -import android.view.View; -import android.widget.ImageView; -import android.widget.LinearLayout; - -import com.android.systemui.Dependency; -import com.android.systemui.R; -import com.android.systemui.appops.AppOpItem; -import com.android.systemui.appops.AppOpsController; -import com.android.systemui.plugins.ActivityStarter; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -/** - * Layout defining the privacy chip that will be displayed in CarStatusRar with the information for - * which applications are using AppOpps permission fpr camera, mic and location. - */ -public class OngoingPrivacyChip extends LinearLayout implements View.OnClickListener { - - private Context mContext; - - private LinearLayout mIconsContainer; - private List<PrivacyItem> mPrivacyItems; - private static AppOpsController sAppOpsController; - private UserManager mUserManager; - private int mCurrentUser; - private List<Integer> mCurrentUserIds; - private boolean mListening = false; - PrivacyDialogBuilder mPrivacyDialogBuilder; - private LinearLayout mPrivacyChip; - private ActivityStarter mActivityStarter; - - protected static final int[] OPS = new int[]{ - AppOpsManager.OP_CAMERA, - AppOpsManager.OP_RECORD_AUDIO, - AppOpsManager.OP_COARSE_LOCATION, - AppOpsManager.OP_FINE_LOCATION - }; - - public OngoingPrivacyChip(Context context) { - super(context, null); - init(context); - } - - public OngoingPrivacyChip(Context context, AttributeSet attr) { - super(context, attr); - init(context); - } - - public OngoingPrivacyChip(Context context, AttributeSet attr, int defStyle) { - super(context, attr, defStyle); - init(context); - } - - public OngoingPrivacyChip(Context context, AttributeSet attr, int defStyle, int a) { - super(context, attr, defStyle, a); - init(context); - } - - private void init(Context context) { - mContext = context; - mPrivacyItems = new ArrayList<>(); - sAppOpsController = Dependency.get(AppOpsController.class); - mUserManager = mContext.getSystemService(UserManager.class); - mActivityStarter = Dependency.get(ActivityStarter.class); - mCurrentUser = ActivityManager.getCurrentUser(); - mCurrentUserIds = mUserManager.getProfiles(mCurrentUser).stream().map( - userInfo -> userInfo.id).collect(Collectors.toList()); - - mPrivacyDialogBuilder = new PrivacyDialogBuilder(context, mPrivacyItems); - } - - private AppOpsController.Callback mCallback = new AppOpsController.Callback() { - - @Override - public void onActiveStateChanged(int code, int uid, String packageName, boolean active) { - int userId = UserHandle.getUserId(uid); - if (mCurrentUserIds.contains(userId)) { - updatePrivacyList(); - } - } - }; - - @Override - public void onFinishInflate() { - mIconsContainer = findViewById(R.id.icons_container); - mPrivacyChip = (LinearLayout) findViewById(R.id.car_privacy_chip); - if (mPrivacyChip != null) { - mPrivacyChip.setOnClickListener(this); - setListening(true); - } - } - - @Override - public void onDetachedFromWindow() { - if (mPrivacyChip != null) { - setListening(false); - } - super.onDetachedFromWindow(); - } - - @Override - public void onClick(View v) { - updatePrivacyList(); - Handler mUiHandler = new Handler(Looper.getMainLooper()); - mUiHandler.post(() -> { - mActivityStarter.postStartActivityDismissingKeyguard( - new Intent(Intent.ACTION_REVIEW_ONGOING_PERMISSION_USAGE), 0); - }); - } - - private void setListening(boolean listen) { - if (mListening == listen) { - return; - } - mListening = listen; - if (mListening) { - sAppOpsController.addCallback(OPS, mCallback); - updatePrivacyList(); - } else { - sAppOpsController.removeCallback(OPS, mCallback); - } - } - - private void updatePrivacyList() { - mPrivacyItems = mCurrentUserIds.stream() - .flatMap(item -> sAppOpsController.getActiveAppOpsForUser(item).stream()) - .filter(Objects::nonNull) - .map(item -> toPrivacyItem(item)) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - mPrivacyDialogBuilder = new PrivacyDialogBuilder(mContext, mPrivacyItems); - - Handler refresh = new Handler(Looper.getMainLooper()); - refresh.post(new Runnable() { - @Override - public void run() { - updateView(); - } - }); - } - - private PrivacyItem toPrivacyItem(AppOpItem appOpItem) { - PrivacyType type; - switch (appOpItem.getCode()) { - case AppOpsManager.OP_CAMERA: - type = PrivacyType.TYPE_CAMERA; - break; - case AppOpsManager.OP_COARSE_LOCATION: - type = PrivacyType.TYPE_LOCATION; - break; - case AppOpsManager.OP_FINE_LOCATION: - type = PrivacyType.TYPE_LOCATION; - break; - case AppOpsManager.OP_RECORD_AUDIO: - type = PrivacyType.TYPE_MICROPHONE; - break; - default: - return null; - } - PrivacyApplication app = new PrivacyApplication(appOpItem.getPackageName(), mContext); - return new PrivacyItem(type, app, appOpItem.getTimeStarted()); - } - - // Should only be called if the mPrivacyDialogBuilder icons or app changed - private void updateView() { - if (mPrivacyItems.isEmpty()) { - mPrivacyChip.setVisibility(GONE); - return; - } - mPrivacyChip.setVisibility(VISIBLE); - setIcons(mPrivacyDialogBuilder); - - requestLayout(); - } - - private void setIcons(PrivacyDialogBuilder dialogBuilder) { - mIconsContainer.removeAllViews(); - dialogBuilder.generateIcons().forEach(item -> { - int size = mContext.getResources().getDimensionPixelSize( - R.dimen.privacy_chip_icon_height); - ImageView image = new ImageView(mContext); - image.setImageDrawable(item); - LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(size, size); - - int leftPadding = mContext.getResources().getDimensionPixelSize( - R.dimen.privacy_chip_icon_padding_left); - int topPadding = mContext.getResources().getDimensionPixelSize( - R.dimen.privacy_chip_icon_padding_top); - int rightPadding = mContext.getResources().getDimensionPixelSize( - R.dimen.privacy_chip_icon_padding_right); - int bottomPadding = mContext.getResources().getDimensionPixelSize( - R.dimen.privacy_chip_icon_padding_bottom); - image.setLayoutParams(layoutParams); - image.setPadding(leftPadding, topPadding, rightPadding, bottomPadding); - mIconsContainer.addView(image); - }); - } -} diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyApplication.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyApplication.java deleted file mode 100644 index 5ec7a77cb305..000000000000 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyApplication.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.car.privacy; - -import android.car.userlib.CarUserManagerHelper; -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.graphics.drawable.Drawable; -import android.util.Log; - -/** - * Class to hold the data for the applications that are using the AppOps permissions. - */ -public class PrivacyApplication { - private static final String TAG = "PrivacyApplication"; - - private Drawable mIcon; - private String mApplicationName; - - public PrivacyApplication(String packageName, Context context) { - try { - CarUserManagerHelper carUserManagerHelper = new CarUserManagerHelper(context); - ApplicationInfo app = context.getPackageManager() - .getApplicationInfoAsUser(packageName, 0, - carUserManagerHelper.getCurrentForegroundUserId()); - mIcon = context.getPackageManager().getApplicationIcon(app); - mApplicationName = context.getPackageManager().getApplicationLabel(app).toString(); - } catch (PackageManager.NameNotFoundException e) { - mApplicationName = packageName; - Log.e(TAG, "Failed to to find package name", e); - } - } - - /** - * Gets the application name. - */ - public Drawable getIcon() { - return mIcon; - } - - /** - * Gets the application name. - */ - public String getApplicationName() { - return mApplicationName; - } -} diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyDialogBuilder.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyDialogBuilder.java deleted file mode 100644 index 3b83e7cc0623..000000000000 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyDialogBuilder.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.car.privacy; - -import android.content.Context; -import android.graphics.drawable.Drawable; - -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; - -/** - * Helper class to build the {@link OngoingPrivacyDialog} - */ -public class PrivacyDialogBuilder { - - private Map<PrivacyType, List<PrivacyItem>> mItemsByType; - private PrivacyApplication mApplication; - private Context mContext; - - public PrivacyDialogBuilder(Context context, List<PrivacyItem> itemsList) { - mContext = context; - mItemsByType = itemsList.stream().filter(Objects::nonNull).collect( - Collectors.groupingBy(PrivacyItem::getPrivacyType)); - List<PrivacyApplication> apps = itemsList.stream().filter(Objects::nonNull).map( - PrivacyItem::getPrivacyApplication).distinct().collect(Collectors.toList()); - mApplication = apps.size() == 1 ? apps.get(0) : null; - } - - /** - * Gets the icon id for all the {@link PrivacyItem} in the same order as of itemList. - */ - public List<Drawable> generateIcons() { - return mItemsByType.keySet().stream().map(item -> item.getIconId(mContext)).collect( - Collectors.toList()); - } - - /** - * Gets the application object. - */ - public PrivacyApplication getApplication() { - return mApplication; - } -} diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyItem.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyItem.java deleted file mode 100644 index fca137392d74..000000000000 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyItem.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.car.privacy; - -/** - * Class for holding the data of each privacy item displayed in {@link OngoingPrivacyDialog} - */ -public class PrivacyItem { - - private PrivacyType mPrivacyType; - private PrivacyApplication mPrivacyApplication; - - public PrivacyItem(PrivacyType privacyType, PrivacyApplication privacyApplication, - long timeStarted) { - this.mPrivacyType = privacyType; - this.mPrivacyApplication = privacyApplication; - } - - /** - * Gets the application object. - */ - public PrivacyApplication getPrivacyApplication() { - return mPrivacyApplication; - } - - /** - * Gets the privacy type for the application. - */ - public PrivacyType getPrivacyType() { - return mPrivacyType; - } -} diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyType.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyType.java deleted file mode 100644 index 8955c87bbff4..000000000000 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyType.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.car.privacy; - -import android.content.Context; -import android.graphics.drawable.Drawable; - -import com.android.systemui.R; - -/** - * Enum for storing data for camera, mic and location. - */ -public enum PrivacyType { - TYPE_CAMERA(R.string.privacy_type_camera, com.android.internal.R.drawable.ic_camera), - TYPE_LOCATION(R.string.privacy_type_location, R.drawable.stat_sys_location), - TYPE_MICROPHONE(R.string.privacy_type_microphone, R.drawable.ic_mic_white); - - private int mNameId; - private int mIconId; - - PrivacyType(int nameId, int iconId) { - mNameId = nameId; - mIconId = iconId; - } - - /** - * Get the icon Id. - */ - public Drawable getIconId(Context context) { - return context.getResources().getDrawable(mIconId, null); - } - - /** - * Get the name Id. - */ - public String getNameId(Context context) { - return context.getResources().getString(mNameId); - } -} diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index da3416b886ad..1b27b52f1fa1 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -638,7 +638,7 @@ public class ExternalStorageProvider extends FileSystemProvider { 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/NetworkStack/src/com/android/server/NetworkStackService.java b/packages/NetworkStack/src/com/android/server/NetworkStackService.java index 2fae0c703084..9bf44579127e 100644 --- a/packages/NetworkStack/src/com/android/server/NetworkStackService.java +++ b/packages/NetworkStack/src/com/android/server/NetworkStackService.java @@ -195,6 +195,7 @@ public class NetworkStackService extends Service { @Override public void makeNetworkMonitor(Network network, String name, INetworkMonitorCallbacks cb) throws RemoteException { + checkNetworkStackCallingPermission(); updateSystemAidlVersion(cb.getInterfaceVersion()); final SharedLog log = addValidationLogs(network, name); final NetworkMonitor nm = new NetworkMonitor(mContext, cb, network, log); @@ -203,6 +204,7 @@ public class NetworkStackService extends Service { @Override public void makeIpClient(String ifName, IIpClientCallbacks cb) throws RemoteException { + checkNetworkStackCallingPermission(); updateSystemAidlVersion(cb.getInterfaceVersion()); final IpClient ipClient = new IpClient(mContext, ifName, cb, mObserverRegistry, this); @@ -228,6 +230,7 @@ public class NetworkStackService extends Service { @Override public void fetchIpMemoryStore(@NonNull final IIpMemoryStoreCallbacks cb) throws RemoteException { + checkNetworkStackCallingPermission(); updateSystemAidlVersion(cb.getInterfaceVersion()); cb.onIpMemoryStoreFetched(mIpMemoryStoreService); } diff --git a/packages/NetworkStack/src/com/android/server/util/PermissionUtil.java b/packages/NetworkStack/src/com/android/server/util/PermissionUtil.java index 6fbeeadb7e72..670138411bae 100644 --- a/packages/NetworkStack/src/com/android/server/util/PermissionUtil.java +++ b/packages/NetworkStack/src/com/android/server/util/PermissionUtil.java @@ -16,30 +16,56 @@ package com.android.server.util; +import static android.os.Binder.getCallingPid; import static android.os.Binder.getCallingUid; import android.os.Process; import android.os.UserHandle; +import java.util.concurrent.atomic.AtomicInteger; + /** * Utility class to check calling permissions on the network stack. */ public final class PermissionUtil { + private static final AtomicInteger sSystemPid = new AtomicInteger(-1); /** * Check that the caller is allowed to communicate with the network stack. * @throws SecurityException The caller is not allowed to communicate with the network stack. */ public static void checkNetworkStackCallingPermission() { - // TODO: check that the calling PID is the system server. final int caller = getCallingUid(); - if (caller != Process.SYSTEM_UID - && UserHandle.getAppId(caller) != Process.BLUETOOTH_UID - && UserHandle.getAppId(caller) != Process.PHONE_UID) { + if (caller == Process.SYSTEM_UID) { + checkConsistentSystemPid(); + return; + } + + if (UserHandle.getAppId(caller) != Process.BLUETOOTH_UID) { throw new SecurityException("Invalid caller: " + caller); } } + private static void checkConsistentSystemPid() { + // Apart from the system server process, no process with a system UID should try to + // communicate with the network stack. This is to ensure that the network stack does not + // need to maintain behavior for clients it was not designed to work with. + // Checking that all calls from a system UID originate from the same PID loosely enforces + // this restriction as if another system process calls the network stack first, the system + // server would lose access to the network stack and cause obvious failures. If the system + // server calls the network stack first, other clients would lose access as expected. + final int systemPid = getCallingPid(); + if (sSystemPid.compareAndSet(-1, systemPid)) { + // sSystemPid was unset (-1): this was the first call + return; + } + + if (sSystemPid.get() != systemPid) { + throw new SecurityException("Invalid PID for the system server, expected " + + sSystemPid.get() + " but was called from " + systemPid); + } + } + /** * Check that the caller is allowed to dump the network stack, e.g. dumpsys. * @throws SecurityException The caller is not allowed to dump the network stack. diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 4c72f48da87f..0e9183940f22 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 & 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/SettingsLib/src/com/android/settingslib/net/SignalStrengthUtil.java b/packages/SettingsLib/src/com/android/settingslib/net/SignalStrengthUtil.java new file mode 100644 index 000000000000..246f2ceac87c --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/net/SignalStrengthUtil.java @@ -0,0 +1,34 @@ +/* + * 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.settingslib.net; + +import android.content.Context; +import android.telephony.SubscriptionManager; + +/** + * Utilities for dealing with signal strength. + */ +public class SignalStrengthUtil { + /** + * @return whether we should artificially inflate the signal strength and number of levels by 1 + * bar for the subscription with the given id + */ + public static boolean shouldInflateSignalStrength(Context context, int subscriptionId) { + return SubscriptionManager.getResourcesForSubId(context, subscriptionId) + .getBoolean(com.android.internal.R.bool.config_inflateSignalStrength); + } +} diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index a2450860517e..da139d74f8ae 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 1060c7b7ce79..e57a5ded0698 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.Log; 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 @@ public class BugreportProgressService extends Service { 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 @@ public class BugreportProgressService extends Service { } }); - mDialog = new AlertDialog.Builder(context) + mDialog = new AlertDialog.Builder(themedContext) .setView(view) .setTitle(dialogTitle) .setCancelable(true) diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 94259416d274..91a8ab5f692f 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -58,6 +58,7 @@ android_library { "androidx.arch.core_core-runtime", "androidx.lifecycle_lifecycle-extensions", "androidx.dynamicanimation_dynamicanimation", + "androidx-constraintlayout_constraintlayout", "iconloader_base", "SystemUI-tags", "SystemUI-proto", @@ -111,6 +112,7 @@ android_library { "androidx.arch.core_core-runtime", "androidx.lifecycle_lifecycle-extensions", "androidx.dynamicanimation_dynamicanimation", + "androidx-constraintlayout_constraintlayout", "SystemUI-tags", "SystemUI-proto", "metrics-helper-lib", diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS index c071b8b15a43..83acfa06976f 100644 --- a/packages/SystemUI/OWNERS +++ b/packages/SystemUI/OWNERS @@ -12,6 +12,7 @@ cwren@google.com dupin@google.com ethibodeau@google.com evanlaird@google.com +hyunyoungs@google.com jmonk@google.com jaggies@google.com jjaggi@google.com @@ -24,6 +25,8 @@ kprevas@google.com lynhan@google.com madym@google.com mankoff@google.com +mrcasey@google.com +mrenouf@google.com nbenbernou@google.com nesciosquid@google.com ngmatthew@google.com 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 3f4abb0e0dd4..000000000000 --- 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 eb7b8eeb9251..d53d698e1bcb 100644 --- a/packages/SystemUI/res/drawable/ic_notifications_alert.xml +++ b/packages/SystemUI/res/drawable/ic_notifications_alert.xml @@ -14,11 +14,11 @@ Copyright (C) 2018 The Android Open Source Project 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 ff136eb44aac..a6cc81b5fdf7 100644 --- a/packages/SystemUI/res/drawable/ic_notifications_silence.xml +++ b/packages/SystemUI/res/drawable/ic_notifications_silence.xml @@ -14,15 +14,11 @@ Copyright (C) 2018 The Android Open Source Project 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.xml b/packages/SystemUI/res/drawable/ic_volume_media.xml index c8fa3fbdd50b..99be7b6e8fea 100644 --- a/packages/SystemUI/res/drawable/ic_volume_media.xml +++ b/packages/SystemUI/res/drawable/ic_volume_media.xml @@ -22,6 +22,6 @@ <path android:fillColor="#FFFFFFFF" - android:pathData="M12,3l0.01,10.55c-0.59,-0.34 -1.27,-0.55 -2,-0.55C7.79,13 6,14.79 6,17c0,2.21 1.79,4 4.01,4S14,19.21 14,17V7h4V3H12zM10.01,19c-1.1,0 -2,-0.9 -2,-2c0,-1.1 0.9,-2 2,-2s2,0.9 2,2C12.01,18.1 11.11,19 10.01,19z"/> + android:pathData="M12 3l0.01 10.55c-0.59-0.34-1.27-0.55-2-0.55C7.79 13 6 14.79 6 17s1.79 4 4.01 4S14 19.21 14 17V7h4V3h-6z" /> </vector> diff --git a/packages/SystemUI/res/drawable/ic_volume_media_bt.xml b/packages/SystemUI/res/drawable/ic_volume_media_bt.xml index 9f7744e3c1bf..23cb206085ea 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 12e0f2ee02c9..2469ddc4860f 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/drawable/ic_volume_media_mute.xml b/packages/SystemUI/res/drawable/ic_volume_media_mute.xml index 45b5b87e8ab5..3a495963fc76 100644 --- a/packages/SystemUI/res/drawable/ic_volume_media_mute.xml +++ b/packages/SystemUI/res/drawable/ic_volume_media_mute.xml @@ -22,9 +22,6 @@ <path android:fillColor="#FFFFFFFF" - android:pathData="M21.19,21.19L14,14l-2,-2l-9.2,-9.2L1.39,4.22l8.79,8.79c-0.06,0 -0.12,-0.01 -0.18,-0.01C7.79,13 6,14.79 6,17c0,2.21 1.79,4 4.01,4S14,19.21 14,17v-0.17l5.78,5.78L21.19,21.19zM10.01,19c-1.1,0 -2,-0.9 -2,-2c0,-1.1 0.9,-2 2,-2s2,0.9 2,2C12.01,18.1 11.11,19 10.01,19z"/> - <path - android:fillColor="#FFffffff" - android:pathData="M14,11.17l0,-4.17l4,0l0,-4l-6,0l0,6.17z"/> + android:pathData="M21.19 21.19L14 14l-2-2-9.2-9.2-1.41 1.42 8.79 8.79c-0.06 0-0.12-0.01-0.18-0.01-2.21 0-4 1.79-4 4s1.79 4 4.01 4S14 19.21 14 17v-0.17l5.78 5.78 1.41-1.42zM14 11.17V7h4V3h-6v6.17z" /> </vector> diff --git a/packages/SystemUI/res/layout/global_actions_grid.xml b/packages/SystemUI/res/layout/global_actions_grid.xml index 3928062e43d2..456553d404dc 100644 --- a/packages/SystemUI/res/layout/global_actions_grid.xml +++ b/packages/SystemUI/res/layout/global_actions_grid.xml @@ -1,73 +1,95 @@ <?xml version="1.0" encoding="utf-8"?> -<com.android.systemui.globalactions.GlobalActionsGridLayout +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@id/global_actions_view" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/global_actions_grid_root" android:layout_width="match_parent" android:layout_height="match_parent" - android:orientation="horizontal" - android:theme="@style/qs_theme" - android:gravity="bottom | center_horizontal" android:clipChildren="false" android:clipToPadding="false" - android:paddingBottom="@dimen/global_actions_grid_container_shadow_offset" android:layout_marginBottom="@dimen/global_actions_grid_container_negative_shadow_offset" > - <LinearLayout + + <FrameLayout + android:id="@+id/global_actions_panel_container" + android:layout_width="match_parent" + android:layout_height="0dp" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toTopOf="@id/global_actions_view" + /> + + <com.android.systemui.globalactions.GlobalActionsGridLayout + android:id="@id/global_actions_view" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:layoutDirection="ltr" + android:orientation="horizontal" + android:theme="@style/qs_theme" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + android:gravity="bottom | center_horizontal" android:clipChildren="false" android:clipToPadding="false" - android:layout_marginBottom="@dimen/global_actions_grid_container_bottom_margin" + android:paddingBottom="@dimen/global_actions_grid_container_shadow_offset" + android:layout_marginBottom="@dimen/global_actions_grid_container_negative_shadow_offset" > - <!-- For separated items--> <LinearLayout - android:id="@+id/separated_button" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:layout_marginLeft="@dimen/global_actions_grid_side_margin" - android:layout_marginRight="@dimen/global_actions_grid_side_margin" - android:paddingLeft="@dimen/global_actions_grid_horizontal_padding" - android:paddingRight="@dimen/global_actions_grid_horizontal_padding" - android:paddingTop="@dimen/global_actions_grid_vertical_padding" - android:paddingBottom="@dimen/global_actions_grid_vertical_padding" - android:orientation="vertical" - android:gravity="center" - android:translationZ="@dimen/global_actions_translate" - /> - <!-- Grid of action items --> - <com.android.systemui.globalactions.ListGridLayout - android:id="@android:id/list" - android:layout_width="wrap_content" android:layout_height="wrap_content" - android:orientation="vertical" - android:gravity="right" - android:layout_marginRight="@dimen/global_actions_grid_side_margin" - android:translationZ="@dimen/global_actions_translate" - android:paddingLeft="@dimen/global_actions_grid_horizontal_padding" - android:paddingRight="@dimen/global_actions_grid_horizontal_padding" - android:paddingTop="@dimen/global_actions_grid_vertical_padding" - android:paddingBottom="@dimen/global_actions_grid_vertical_padding" + android:layout_width="wrap_content" + android:layoutDirection="ltr" + android:clipChildren="false" + android:clipToPadding="false" + android:layout_marginBottom="@dimen/global_actions_grid_container_bottom_margin" > + <!-- For separated items--> <LinearLayout + android:id="@+id/separated_button" android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:visibility="gone" - android:layoutDirection="locale" - /> - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:visibility="gone" - android:layoutDirection="locale" + android:layout_height="match_parent" + android:layout_marginLeft="@dimen/global_actions_grid_side_margin" + android:layout_marginRight="@dimen/global_actions_grid_side_margin" + android:paddingLeft="@dimen/global_actions_grid_horizontal_padding" + android:paddingRight="@dimen/global_actions_grid_horizontal_padding" + android:paddingTop="@dimen/global_actions_grid_vertical_padding" + android:paddingBottom="@dimen/global_actions_grid_vertical_padding" + android:orientation="vertical" + android:gravity="center" + android:translationZ="@dimen/global_actions_translate" /> - <LinearLayout + <!-- Grid of action items --> + <com.android.systemui.globalactions.ListGridLayout + android:id="@android:id/list" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:visibility="gone" - android:layoutDirection="locale" - /> - </com.android.systemui.globalactions.ListGridLayout> - </LinearLayout> + android:orientation="vertical" + android:gravity="right" + android:layout_marginRight="@dimen/global_actions_grid_side_margin" + android:translationZ="@dimen/global_actions_translate" + android:paddingLeft="@dimen/global_actions_grid_horizontal_padding" + android:paddingRight="@dimen/global_actions_grid_horizontal_padding" + android:paddingTop="@dimen/global_actions_grid_vertical_padding" + android:paddingBottom="@dimen/global_actions_grid_vertical_padding" + > + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + android:layoutDirection="locale" + /> + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + android:layoutDirection="locale" + /> + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + android:layoutDirection="locale" + /> + </com.android.systemui.globalactions.ListGridLayout> + </LinearLayout> -</com.android.systemui.globalactions.GlobalActionsGridLayout> + </com.android.systemui.globalactions.GlobalActionsGridLayout> +</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/invocation_lights.xml b/packages/SystemUI/res/layout/invocation_lights.xml new file mode 100644 index 000000000000..ff78670d0719 --- /dev/null +++ b/packages/SystemUI/res/layout/invocation_lights.xml @@ -0,0 +1,23 @@ +<?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 + --> + +<com.android.systemui.assist.ui.InvocationLightsView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="bottom" + android:visibility="gone"/> diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml index 8ffa2d83cfa2..87de9d4d3b51 100644 --- a/packages/SystemUI/res/layout/notification_info.xml +++ b/packages/SystemUI/res/layout/notification_info.xml @@ -219,7 +219,7 @@ asked for it --> android:gravity="center" android:orientation="vertical"> - <LinearLayout + <com.android.systemui.statusbar.notification.row.ButtonLinearLayout android:id="@+id/alert" android:layout_width="match_parent" android:layout_height="wrap_content" @@ -268,9 +268,9 @@ asked for it --> android:ellipsize="end" android:maxLines="2" android:textAppearance="@style/TextAppearance.NotificationImportanceDetail"/> - </LinearLayout> + </com.android.systemui.statusbar.notification.row.ButtonLinearLayout> - <LinearLayout + <com.android.systemui.statusbar.notification.row.ButtonLinearLayout android:id="@+id/silence" android:layout_width="match_parent" android:layout_height="wrap_content" @@ -321,7 +321,7 @@ asked for it --> android:ellipsize="end" android:maxLines="2" android:textAppearance="@style/TextAppearance.NotificationImportanceDetail"/> - </LinearLayout> + </com.android.systemui.statusbar.notification.row.ButtonLinearLayout> </LinearLayout> diff --git a/packages/SystemUI/res/layout/rounded_corners.xml b/packages/SystemUI/res/layout/rounded_corners.xml index 02651a212816..b409c8f2ba5a 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-mcc311-mnc180/config.xml b/packages/SystemUI/res/values-mcc311-mnc180/config.xml deleted file mode 100644 index 26b9192e0cc3..000000000000 --- a/packages/SystemUI/res/values-mcc311-mnc180/config.xml +++ /dev/null @@ -1,26 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* -** Copyright 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. -*/ ---> - -<!-- These resources are around just to allow their values to be customized - for different hardware and product builds. --> -<resources> - <!-- Enable 5 bar signal strength icon --> - <bool name="config_inflateSignalStrength">true</bool> -</resources> - diff --git a/packages/SystemUI/res/values-mcc311-mnc480/config.xml b/packages/SystemUI/res/values-mcc311-mnc480/config.xml deleted file mode 100644 index 7dadae7f9107..000000000000 --- a/packages/SystemUI/res/values-mcc311-mnc480/config.xml +++ /dev/null @@ -1,26 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* -** Copyright 2017, 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. -*/ ---> - -<!-- These resources are around just to allow their values to be customized - for different hardware and product builds. --> -<resources> - <!-- Enable 5 bar signal strength icon --> - <bool name="config_inflateSignalStrength">true</bool> -</resources> - diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 3f84b32ee0c2..abf4fdf03b93 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -166,14 +166,17 @@ <color name="smart_reply_button_stroke">#ffdadce0</color> <!-- Biometric dialog colors --> - <color name="biometric_dialog_dim_color">#80000000</color> <!-- 50% black --> + <color name="biometric_dialog_dim_color">#80000000</color> <!-- 50% black --> <color name="biometric_dialog_gray">#ff757575</color> - <color name="biometric_dialog_accent">#ff008577</color> <!-- dark teal --> - <color name="biometric_dialog_error">#ffd93025</color> <!-- red 600 --> + <color name="biometric_dialog_accent">#ff008577</color> <!-- dark teal --> + <color name="biometric_dialog_error">#ffd93025</color> <!-- red 600 --> <!-- Logout button --> <color name="logout_button_bg_color">#ccffffff</color> + <!-- Color for the Assistant invocation lights --> + <color name="default_invocation_lights_color">#ffffffff</color> <!-- white --> + <!-- GM2 colors --> <color name="GM2_grey_50">#F8F9FA</color> <color name="GM2_grey_100">#F1F3F4</color> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 27431f7257f2..19e7b734912a 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -417,10 +417,6 @@ it has been expanded to reveal its children. --> <bool name="config_showGroupNotificationBgWhenExpanded">false</bool> - <!-- Whether to artificially interpret all signal strengths as - one bar higher than they actually are --> - <bool name="config_inflateSignalStrength">false</bool> - <!-- Should we vibrate on an icon animation of the shelf. This should only be active if the vibrator is capable of subtle vibrations --> <bool name="config_vibrateOnIconAnimation">false</bool> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 19874948cb54..c04d28a6cda8 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/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 8bc84c6d4a44..8174434cb51d 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2459,4 +2459,11 @@ <string name="bubble_accessibility_action_move_bottom_right">Move bottom right</string> <!-- Text used for the bubble dismiss area. Bubbles dragged to, or flung towards, this area will go away. [CHAR LIMIT=20] --> <string name="bubble_dismiss_text">Dismiss</string> + + <!-- Notification content text when the system navigation mode changes as a result of changing the default launcher [CHAR LIMIT=NONE] --> + <string name="notification_content_system_nav_changed">System navigation updated. To make changes, go to Settings.</string> + + <!-- Notification content text when switching to a default launcher that supports gesture navigation [CHAR LIMIT=NONE] --> + <string name="notification_content_gesture_nav_available">Go to Settings to update system navigation</string> + </resources> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java index 6567b6ad1171..2b1fce8a4cf5 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java @@ -18,7 +18,6 @@ package com.android.systemui.shared.system; import android.os.Bundle; import android.os.Looper; -import android.util.Pair; import android.view.BatchedInputEventReceiver; import android.view.Choreographer; import android.view.InputChannel; @@ -42,19 +41,6 @@ public class InputChannelCompat { } /** - * Creates a dispatcher and receiver pair to better handle events across threads. - */ - public static Pair<InputEventDispatcher, InputEventReceiver> createPair(String name, - Looper looper, Choreographer choreographer, InputEventListener listener) { - InputChannel[] channels = InputChannel.openInputChannelPair(name); - - InputEventDispatcher dispatcher = new InputEventDispatcher(channels[0], looper); - InputEventReceiver receiver = new InputEventReceiver(channels[1], looper, choreographer, - listener); - return Pair.create(dispatcher, receiver); - } - - /** * Creates a dispatcher from the extras received as part on onInitialize */ public static InputEventReceiver fromBundle(Bundle params, String key, diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SystemGestureExclusionListenerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SystemGestureExclusionListenerCompat.java new file mode 100644 index 000000000000..9fdecfbd44a0 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SystemGestureExclusionListenerCompat.java @@ -0,0 +1,85 @@ +/** + * 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.shared.system; + +import android.graphics.Region; +import android.os.RemoteException; +import android.util.Log; +import android.view.ISystemGestureExclusionListener; +import android.view.WindowManagerGlobal; + +/** + * Utility class to listen for exclusion rect changes. + */ +public abstract class SystemGestureExclusionListenerCompat { + + private static final String TAG = "SGEListenerCompat"; + + private final int mDisplayId; + + private ISystemGestureExclusionListener mGestureExclusionListener = + new ISystemGestureExclusionListener.Stub() { + @Override + public void onSystemGestureExclusionChanged(int displayId, + Region systemGestureExclusion) { + if (displayId == mDisplayId) { + onExclusionChanged(systemGestureExclusion); + } + } + }; + private boolean mRegistered; + + public SystemGestureExclusionListenerCompat(int displayId) { + mDisplayId = displayId; + } + + /** + * Called when the exclusion region has changed + */ + public abstract void onExclusionChanged(Region systemGestureExclusion); + + /** + * Registers the listener for getting exclusion rect changes. + */ + public void register() { + if (!mRegistered) { + try { + WindowManagerGlobal.getWindowManagerService() + .registerSystemGestureExclusionListener( + mGestureExclusionListener, mDisplayId); + mRegistered = true; + } catch (RemoteException e) { + Log.e(TAG, "Failed to register window manager callbacks", e); + } + } + } + + /** + * Unregisters the receiver if previously registered + */ + public void unregister() { + if (mRegistered) { + try { + WindowManagerGlobal.getWindowManagerService() + .unregisterSystemGestureExclusionListener( + mGestureExclusionListener, mDisplayId); + mRegistered = false; + } catch (RemoteException e) { + Log.e(TAG, "Failed to unregister window manager callbacks", e); + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java index de4c79839f25..bce5c23bc9cc 100644 --- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java +++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java @@ -322,9 +322,6 @@ public class BatteryMeterView extends LinearLayout implements 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 @@ public class BatteryMeterView extends LinearLayout implements 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 @@ public class BatteryMeterView extends LinearLayout implements 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 000000000000..528db5acc86e --- /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 390a9e65c1b4..4aaf85adfce6 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; 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.fragments.FragmentHostManager.FragmentListener; 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 @@ import java.util.List; * 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 class ScreenDecorations extends SystemUI implements Tunable { 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 @@ public class ScreenDecorations extends SystemUI implements Tunable { 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 @@ public class ScreenDecorations extends SystemUI implements Tunable { 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 @@ public class ScreenDecorations extends SystemUI implements Tunable { 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 @@ public class ScreenDecorations extends SystemUI implements Tunable { .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 @@ public class ScreenDecorations extends SystemUI implements Tunable { 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 @@ public class ScreenDecorations extends SystemUI implements Tunable { } 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 @@ public class ScreenDecorations extends SystemUI implements Tunable { } 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 @@ public class ScreenDecorations extends SystemUI implements Tunable { 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 @@ public class ScreenDecorations extends SystemUI implements Tunable { 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/AssistHandleBehavior.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehavior.java index e2c731314246..2eda3d784382 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehavior.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehavior.java @@ -16,31 +16,10 @@ package com.android.systemui.assist; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.assist.AssistHandleBehaviorController.BehaviorController; - public enum AssistHandleBehavior { - TEST(new AssistHandleOffBehavior()), - OFF(new AssistHandleOffBehavior()), - LIKE_HOME(new AssistHandleLikeHomeBehavior()), - REMINDER_EXP(new AssistHandleReminderExpBehavior()); - - private BehaviorController mController; - - AssistHandleBehavior(BehaviorController controller) { - mController = controller; - } - - BehaviorController getController() { - return mController; - } - - @VisibleForTesting - void setTestController(BehaviorController controller) { - if (this.equals(TEST)) { - mController = controller; - } - } + TEST, + OFF, + LIKE_HOME, + REMINDER_EXP; } diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java index 0203d9ed2376..01deb03c466b 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java @@ -16,24 +16,27 @@ package com.android.systemui.assist; -import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.Build; 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.app.AssistUtils; +import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; +import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.Dependency; import com.android.systemui.ScreenDecorations; import com.android.systemui.SysUiServiceProvider; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.phone.NavigationModeController; -import java.util.Locale; +import java.util.EnumMap; +import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; @@ -46,60 +49,76 @@ import java.util.function.Supplier; 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"); - private static final String SHOWN_FREQUENCY_THRESHOLD_KEY = - "ASSIST_HANDLES_SHOWN_FREQUENCY_THRESHOLD_MS"; private static final long DEFAULT_SHOWN_FREQUENCY_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(10); - private static final String SHOW_AND_GO_DURATION_KEY = "ASSIST_HANDLES_SHOW_AND_GO_DURATION_MS"; private static final long DEFAULT_SHOW_AND_GO_DURATION_MS = TimeUnit.SECONDS.toMillis(3); - private static final String BEHAVIOR_KEY = "behavior"; - private static final String SET_BEHAVIOR_ACTION = - "com.android.systemui.SET_ASSIST_HANDLE_BEHAVIOR"; + + /** + * This is the default behavior that will be used once the system is up. It will be set once the + * behavior dependencies are available. This ensures proper behavior lifecycle. + */ + private static final AssistHandleBehavior DEFAULT_BEHAVIOR = AssistHandleBehavior.REMINDER_EXP; private final Context mContext; + private final AssistUtils mAssistUtils; private final Handler mHandler; private final Runnable mHideHandles = this::hideHandles; private final Supplier<ScreenDecorations> mScreenDecorationsSupplier; + private final Map<AssistHandleBehavior, BehaviorController> mBehaviorMap = + new EnumMap<>(AssistHandleBehavior.class); private boolean mHandlesShowing = false; private long mHandlesLastHiddenAt; + /** + * This should always be initialized as {@link AssistHandleBehavior#OFF} to ensure proper + * behavior lifecycle. + */ private AssistHandleBehavior mCurrentBehavior = AssistHandleBehavior.OFF; private boolean mInGesturalMode; - AssistHandleBehaviorController(Context context, Handler handler) { - this(context, handler, () -> - SysUiServiceProvider.getComponent(context, ScreenDecorations.class)); + AssistHandleBehaviorController(Context context, AssistUtils assistUtils, Handler handler) { + this( + context, + assistUtils, + handler, () -> SysUiServiceProvider.getComponent(context, ScreenDecorations.class), + /* testBehavior = */ null); } @VisibleForTesting AssistHandleBehaviorController( Context context, + AssistUtils assistUtils, Handler handler, - Supplier<ScreenDecorations> screenDecorationsSupplier) { + Supplier<ScreenDecorations> screenDecorationsSupplier, + @Nullable BehaviorController testBehavior) { mContext = context; + mAssistUtils = assistUtils; mHandler = handler; mScreenDecorationsSupplier = screenDecorationsSupplier; + mBehaviorMap.put(AssistHandleBehavior.OFF, new AssistHandleOffBehavior()); + mBehaviorMap.put(AssistHandleBehavior.LIKE_HOME, new AssistHandleLikeHomeBehavior()); + mBehaviorMap.put(AssistHandleBehavior.REMINDER_EXP, new AssistHandleReminderExpBehavior()); + if (testBehavior != null) { + mBehaviorMap.put(AssistHandleBehavior.TEST, testBehavior); + } + mInGesturalMode = QuickStepContract.isGesturalMode( Dependency.get(NavigationModeController.class) .addListener(this::handleNavigationModeChange)); - if (IS_DEBUG_DEVICE) { - context.registerReceiver(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String behaviorString = intent.getExtras().getString(BEHAVIOR_KEY); - try { - setBehavior(AssistHandleBehavior.valueOf(behaviorString)); - } catch (IllegalArgumentException e) { - Log.e(TAG, "Invalid behavior identifier: " + behaviorString); + setBehavior(DeviceConfig.getString( + DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.ASSIST_HANDLES_BEHAVIOR_MODE, + DEFAULT_BEHAVIOR.toString())); + DeviceConfig.addOnPropertyChangedListener( + DeviceConfig.NAMESPACE_SYSTEMUI, + mHandler::post, + (namespace, name, value) -> { + if (SystemUiDeviceConfigFlags.ASSIST_HANDLES_BEHAVIOR_MODE.equals(name)) { + setBehavior(value); } - } - }, new IntentFilter(SET_BEHAVIOR_ACTION)); - } + }); } @Override @@ -123,26 +142,56 @@ public final class AssistHandleBehaviorController implements AssistHandleCallbac mHandler.post(() -> maybeShowHandles(/* ignoreThreshold = */ true)); } + void onAssistantGesturePerformed() { + mBehaviorMap.get(mCurrentBehavior).onAssistantGesturePerformed(); + } + void setBehavior(AssistHandleBehavior behavior) { if (mCurrentBehavior == behavior) { return; } + if (!mBehaviorMap.containsKey(behavior)) { + Log.e(TAG, "Unsupported behavior requested: " + behavior.toString()); + return; + } + if (mInGesturalMode) { - mCurrentBehavior.getController().onModeDeactivated(); - behavior.getController().onModeActivated(mContext, this); + mBehaviorMap.get(mCurrentBehavior).onModeDeactivated(); + mBehaviorMap.get(behavior).onModeActivated(mContext, /* callbacks = */ this); } 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 boolean handlesUnblocked(boolean ignoreThreshold) { + long timeSinceHidden = SystemClock.elapsedRealtime() - mHandlesLastHiddenAt; + boolean notThrottled = ignoreThreshold || timeSinceHidden > getShownFrequencyThreshold(); + ComponentName assistantComponent = + mAssistUtils.getAssistComponentForUser(KeyguardUpdateMonitor.getCurrentUser()); + return notThrottled && assistantComponent != null; + } + + private long getShownFrequencyThreshold() { + return DeviceConfig.getLong( + DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOWN_FREQUENCY_THRESHOLD_MS, + DEFAULT_SHOWN_FREQUENCY_THRESHOLD_MS); } - private static long getShowAndGoDuration() { - return SystemProperties.getLong(SHOW_AND_GO_DURATION_KEY, DEFAULT_SHOW_AND_GO_DURATION_MS); + private long getShowAndGoDuration() { + return DeviceConfig.getLong( + DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOW_AND_GO_DURATION_MS, + DEFAULT_SHOW_AND_GO_DURATION_MS); } private void maybeShowHandles(boolean ignoreThreshold) { @@ -150,8 +199,7 @@ public final class AssistHandleBehaviorController implements AssistHandleCallbac return; } - long timeSinceHidden = SystemClock.elapsedRealtime() - mHandlesLastHiddenAt; - if (ignoreThreshold || timeSinceHidden > getShownFrequencyThreshold()) { + if (handlesUnblocked(ignoreThreshold)) { mHandlesShowing = true; ScreenDecorations screenDecorations = mScreenDecorationsSupplier.get(); if (screenDecorations == null) { @@ -185,9 +233,9 @@ public final class AssistHandleBehaviorController implements AssistHandleCallbac mInGesturalMode = inGesturalMode; if (mInGesturalMode) { - mCurrentBehavior.getController().onModeActivated(mContext, this); + mBehaviorMap.get(mCurrentBehavior).onModeActivated(mContext, /* callbacks = */ this); } else { - mCurrentBehavior.getController().onModeDeactivated(); + mBehaviorMap.get(mCurrentBehavior).onModeDeactivated(); hide(); } } @@ -199,6 +247,7 @@ public final class AssistHandleBehaviorController implements AssistHandleCallbac interface BehaviorController { void onModeActivated(Context context, AssistHandleCallbacks callbacks); - void onModeDeactivated(); + default void onModeDeactivated() {} + default void onAssistantGesturePerformed() {} } } diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleLikeHomeBehavior.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleLikeHomeBehavior.java index e89e93c0adf0..05e504c93b82 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 @@ import com.android.systemui.statusbar.phone.NavigationBarFragment; */ 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 static boolean isHomeHandleHiding(int sysuiStateFlags) { + return (sysuiStateFlags & QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN) != 0; + } + + private void handleDozingChanged(boolean isDozing) { + if (mIsDozing == isDozing) { + return; + } + + mIsDozing = isDozing; + callbackForCurrentState(); } - private void handleWindowStateChanged(int state) { - boolean newVisibility = state == StatusBarManager.WINDOW_STATE_SHOWING; - if (mIsNavBarWindowVisible == newVisibility) { + private void handleSystemUiStateChange(int sysuiStateFlags) { + boolean isHomeHandleHiding = isHomeHandleHiding(sysuiStateFlags); + if (mIsHomeHandleHiding == isHomeHandleHiding) { return; } - mIsNavBarWindowVisible = newVisibility; + mIsHomeHandleHiding = isHomeHandleHiding; callbackForCurrentState(); } @@ -83,10 +102,10 @@ final class AssistHandleLikeHomeBehavior implements BehaviorController { return; } - if (mIsNavBarWindowVisible) { - mAssistHandleCallbacks.showAndStay(); - } else { + if (mIsHomeHandleHiding || mIsDozing) { mAssistHandleCallbacks.hide(); + } else { + mAssistHandleCallbacks.showAndStay(); } } } diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleOffBehavior.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleOffBehavior.java index ebf15ae8755c..f4130aeb1d08 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleOffBehavior.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleOffBehavior.java @@ -27,9 +27,4 @@ final class AssistHandleOffBehavior implements BehaviorController { public void onModeActivated(Context context, AssistHandleCallbacks callbacks) { callbacks.hide(); } - - @Override - public void onModeDeactivated() { - // Do nothing - } } diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java index 4a5e0e85107e..4b6a6dcee91d 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java @@ -16,24 +16,27 @@ 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 android.os.SystemClock; +import android.provider.DeviceConfig; +import android.provider.Settings; import androidx.annotation.Nullable; +import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; 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; +import java.util.concurrent.TimeUnit; + /** * Assistant handle behavior that hides the handles when the phone is dozing or in immersive mode, * shows the handles when on lockscreen, and shows the handles temporarily when changing tasks or @@ -41,6 +44,11 @@ import com.android.systemui.statusbar.StatusBarState; */ final class AssistHandleReminderExpBehavior implements BehaviorController { + private static final String LEARNING_TIME_ELAPSED_KEY = "reminder_exp_learning_time_elapsed"; + private static final String LEARNING_EVENT_COUNT_KEY = "reminder_exp_learning_event_count"; + private static final long DEFAULT_LEARNING_TIME_MS = TimeUnit.DAYS.toMillis(3); + private static final int DEFAULT_LEARNING_COUNT = 3; + private final StatusBarStateController.StateListener mStatusBarStateListener = new StatusBarStateController.StateListener() { @Override @@ -65,66 +73,95 @@ final class AssistHandleReminderExpBehavior implements BehaviorController { 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; + /** Whether user has learned the gesture. */ + private boolean mIsLearned; + private long mLastLearningTimestamp; + /** Uptime while in this behavior. */ + private long mLearningTimeElapsed; + /** Number of successful Assistant invocations while in this behavior. */ + private int mLearningCount; + + @Nullable private Context mContext; @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) { + mContext = context; 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(); + + mLearningTimeElapsed = Settings.Secure.getLong( + context.getContentResolver(), LEARNING_TIME_ELAPSED_KEY, /* default = */ 0); + mLearningCount = Settings.Secure.getInt( + context.getContentResolver(), LEARNING_EVENT_COUNT_KEY, /* default = */ 0); + mLastLearningTimestamp = SystemClock.uptimeMillis(); + + callbackForCurrentState(/* justUnlocked = */ false); } @Override public void onModeDeactivated() { mAssistHandleCallbacks = null; + if (mContext != null) { + Settings.Secure.putLong( + mContext.getContentResolver(), LEARNING_TIME_ELAPSED_KEY, mLearningTimeElapsed); + Settings.Secure.putInt( + mContext.getContentResolver(), LEARNING_EVENT_COUNT_KEY, mLearningCount); + mContext = null; + } mStatusBarStateController.removeCallback(mStatusBarStateListener); mActivityManagerWrapper.unregisterTaskStackListener(mTaskStackChangeListener); - mCommandQueue.removeCallback(mCallbacks); mOverviewProxyService.removeCallback(mOverviewProxyListener); } + @Override + public void onAssistantGesturePerformed() { + if (mContext == null) { + return; + } + + Settings.Secure.putLong( + mContext.getContentResolver(), LEARNING_EVENT_COUNT_KEY, ++mLearningCount); + } + + 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) { @@ -132,7 +169,7 @@ final class AssistHandleReminderExpBehavior implements BehaviorController { } mOnLockscreen = onLockscreen; - callbackForCurrentState(); + callbackForCurrentState(!onLockscreen); } private void handleDozingChanged(boolean isDozing) { @@ -141,7 +178,7 @@ final class AssistHandleReminderExpBehavior implements BehaviorController { } mIsDozing = isDozing; - callbackForCurrentState(); + callbackForCurrentState(/* justUnlocked = */ false); } private void handleTaskStackTopChanged(int taskId) { @@ -150,21 +187,21 @@ final class AssistHandleReminderExpBehavior implements BehaviorController { } mRunningTaskId = taskId; - callbackForCurrentState(); + callbackForCurrentState(/* justUnlocked = */ false); } - 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; - callbackForCurrentState(); + mIsNavBarHidden = isNavBarHidden; + callbackForCurrentState(/* justUnlocked = */ false); } private void handleOverviewShown() { - callbackForCurrentState(); + callbackForCurrentState(/* justUnlocked = */ false); } private boolean onLockscreen(int statusBarState) { @@ -172,17 +209,34 @@ final class AssistHandleReminderExpBehavior implements BehaviorController { || 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(boolean justUnlocked) { + updateLearningStatus(); + + if (mIsLearned) { + callbackForLearnedState(justUnlocked); + } else { + callbackForUnlearnedState(); + } + } + + private void callbackForLearnedState(boolean justUnlocked) { + if (mAssistHandleCallbacks == null) { + return; + } + + if (mIsDozing || mIsNavBarHidden || mOnLockscreen) { + mAssistHandleCallbacks.hide(); + } else if (justUnlocked) { + mAssistHandleCallbacks.showAndGo(); + } } - private void callbackForCurrentState() { + private void callbackForUnlearnedState() { if (mAssistHandleCallbacks == null) { return; } - if (mIsDozing || mIsImmersive) { + if (mIsDozing || mIsNavBarHidden) { mAssistHandleCallbacks.hide(); } else if (mOnLockscreen) { mAssistHandleCallbacks.showAndStay(); @@ -190,4 +244,33 @@ final class AssistHandleReminderExpBehavior implements BehaviorController { mAssistHandleCallbacks.showAndGo(); } } + + private void updateLearningStatus() { + if (mContext == null) { + return; + } + + long currentTimestamp = SystemClock.uptimeMillis(); + mLearningTimeElapsed += currentTimestamp - mLastLearningTimestamp; + mLastLearningTimestamp = currentTimestamp; + Settings.Secure.putLong( + mContext.getContentResolver(), LEARNING_TIME_ELAPSED_KEY, mLearningTimeElapsed); + + mIsLearned = + mLearningCount >= getLearningCount() || mLearningTimeElapsed >= getLearningTimeMs(); + } + + private long getLearningTimeMs() { + return DeviceConfig.getLong( + DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.ASSIST_HANDLES_LEARN_TIME_MS, + DEFAULT_LEARNING_TIME_MS); + } + + private int getLearningCount() { + return DeviceConfig.getInt( + DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.ASSIST_HANDLES_LEARN_COUNT, + DEFAULT_LEARNING_COUNT); + } } diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java index 2c38e513d7de..2fc79d618546 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java @@ -40,8 +40,11 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.settingslib.applications.InterestingConfigChanges; import com.android.systemui.ConfigurationChangedReceiver; +import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.SysUiServiceProvider; +import com.android.systemui.assist.ui.DefaultUiController; +import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.policy.DeviceProvisionedController; @@ -50,6 +53,40 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController; */ public class AssistManager implements ConfigurationChangedReceiver { + /** + * Controls the UI for showing Assistant invocation progress. + */ + public interface UiController { + /** + * Updates the invocation progress. + * + * @param type one of INVOCATION_TYPE_GESTURE, INVOCATION_TYPE_ACTIVE_EDGE, + * INVOCATION_TYPE_VOICE, INVOCATION_TYPE_QUICK_SEARCH_BAR, + * INVOCATION_HOME_BUTTON_LONG_PRESS + * @param progress a float between 0 and 1 inclusive. 0 represents the beginning of the + * gesture; 1 represents the end. + */ + void onInvocationProgress(int type, float progress); + + /** + * Called when an invocation gesture completes. + * + * @param velocity the speed of the invocation gesture, in pixels per millisecond. For + * drags, this is 0. + */ + void onGestureCompletion(float velocity); + + /** + * Called with the Bundle from VoiceInteractionSessionListener.onSetUiHints. + */ + void processBundle(Bundle hints); + + /** + * Hides the UI. + */ + void hide(); + } + private static final String TAG = "AssistManager"; // Note that VERBOSE logging may leak PII (e.g. transcription contents). @@ -76,6 +113,7 @@ public class AssistManager implements ConfigurationChangedReceiver { private final InterestingConfigChanges mInterestingConfigChanges; private final PhoneStateMonitor mPhoneStateMonitor; private final AssistHandleBehaviorController mHandleController; + private final UiController mUiController; private AssistOrbContainer mView; private final DeviceProvisionedController mDeviceProvisionedController; @@ -85,16 +123,16 @@ public class AssistManager implements ConfigurationChangedReceiver { private IVoiceInteractionSessionShowCallback mShowCallback = new IVoiceInteractionSessionShowCallback.Stub() { - @Override - public void onFailed() throws RemoteException { - mView.post(mHideRunnable); - } + @Override + public void onFailed() throws RemoteException { + mView.post(mHideRunnable); + } - @Override - public void onShown() throws RemoteException { - mView.post(mHideRunnable); - } - }; + @Override + public void onShown() throws RemoteException { + mView.post(mHideRunnable); + } + }; private Runnable mHideRunnable = new Runnable() { @Override @@ -111,7 +149,8 @@ public class AssistManager implements ConfigurationChangedReceiver { mAssistUtils = new AssistUtils(context); mAssistDisclosure = new AssistDisclosure(context, new Handler()); mPhoneStateMonitor = new PhoneStateMonitor(context); - mHandleController = new AssistHandleBehaviorController(context, new Handler()); + mHandleController = + new AssistHandleBehaviorController(context, mAssistUtils, new Handler()); registerVoiceInteractionSessionListener(); mInterestingConfigChanges = new InterestingConfigChanges(ActivityInfo.CONFIG_ORIENTATION @@ -119,6 +158,23 @@ public class AssistManager implements ConfigurationChangedReceiver { | ActivityInfo.CONFIG_SCREEN_LAYOUT | ActivityInfo.CONFIG_ASSETS_PATHS); onConfigurationChanged(context.getResources().getConfiguration()); mShouldEnableOrb = !ActivityManager.isLowRamDeviceStatic(); + + mUiController = new DefaultUiController(mContext); + + OverviewProxyService overviewProxy = Dependency.get(OverviewProxyService.class); + overviewProxy.addCallback(new OverviewProxyService.OverviewProxyListener() { + @Override + public void onAssistantProgress(float progress) { + // Progress goes from 0 to 1 to indicate how close the assist gesture is to + // completion. + onInvocationProgress(INVOCATION_TYPE_GESTURE, progress); + } + + @Override + public void onAssistantGestureCompletion(float velocity) { + onGestureCompletion(velocity); + } + }); } protected void registerVoiceInteractionSessionListener() { @@ -191,26 +247,32 @@ public class AssistManager implements ConfigurationChangedReceiver { if (args == null) { args = new Bundle(); } + int invocationType = args.getInt(INVOCATION_TYPE_KEY, 0); + if (invocationType == INVOCATION_TYPE_GESTURE) { + mHandleController.onAssistantGesturePerformed(); + } args.putInt(INVOCATION_PHONE_STATE_KEY, mPhoneStateMonitor.getPhoneState()); args.putLong(INVOCATION_TIME_MS_KEY, SystemClock.uptimeMillis()); // Logs assistant start with invocation type. MetricsLogger.action( new LogMaker(MetricsEvent.ASSISTANT) - .setType(MetricsEvent.TYPE_OPEN).setSubtype(args.getInt(INVOCATION_TYPE_KEY))); + .setType(MetricsEvent.TYPE_OPEN).setSubtype( + invocationType)); startAssistInternal(args, assistComponent, isService); } /** Called when the user is performing an assistant invocation action (e.g. Active Edge) */ public void onInvocationProgress(int type, float progress) { - // intentional no-op, vendor's AssistManager implementation should override if needed. + mUiController.onInvocationProgress(type, progress); } - /** Called when the user has invoked the assistant with the incoming velocity, in pixels per + /** + * Called when the user has invoked the assistant with the incoming velocity, in pixels per * millisecond. For invocations without a velocity (e.g. slow drag), the velocity is set to * zero. */ - public void onAssistantGestureCompletion(float velocity) { - // intentional no-op, vendor's AssistManager implementation should override if needed. + public void onGestureCompletion(float velocity) { + mUiController.onGestureCompletion(velocity); } public void hideAssist() { @@ -264,7 +326,7 @@ public class AssistManager implements ConfigurationChangedReceiver { Settings.Secure.ASSIST_STRUCTURE_ENABLED, 1, UserHandle.USER_CURRENT) != 0; final SearchManager searchManager = - (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE); + (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE); if (searchManager == null) { return; } @@ -329,7 +391,7 @@ public class AssistManager implements ConfigurationChangedReceiver { // Look for the search icon specified in the activity meta-data Bundle metaData = isService ? packageManager.getServiceInfo( - component, PackageManager.GET_META_DATA).metaData + component, PackageManager.GET_META_DATA).metaData : packageManager.getActivityInfo( component, PackageManager.GET_META_DATA).metaData; if (metaData != null) { diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/CircularCornerPathRenderer.java b/packages/SystemUI/src/com/android/systemui/assist/ui/CircularCornerPathRenderer.java new file mode 100644 index 000000000000..162e09e4d23d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/assist/ui/CircularCornerPathRenderer.java @@ -0,0 +1,65 @@ +/* + * 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.assist.ui; + +import android.graphics.Path; + +/** + * Describes paths for circular rounded device corners. + */ +public final class CircularCornerPathRenderer extends CornerPathRenderer { + + private final int mCornerRadiusBottom; + private final int mCornerRadiusTop; + private final int mHeight; + private final int mWidth; + private final Path mPath = new Path(); + + public CircularCornerPathRenderer(int cornerRadiusBottom, int cornerRadiusTop, + int width, int height) { + mCornerRadiusBottom = cornerRadiusBottom; + mCornerRadiusTop = cornerRadiusTop; + mHeight = height; + mWidth = width; + } + + @Override // CornerPathRenderer + public Path getCornerPath(Corner corner) { + mPath.reset(); + switch (corner) { + case BOTTOM_LEFT: + mPath.moveTo(0, mHeight - mCornerRadiusBottom); + mPath.arcTo(0, mHeight - mCornerRadiusBottom * 2, mCornerRadiusBottom * 2, mHeight, + 180, -90, true); + break; + case BOTTOM_RIGHT: + mPath.moveTo(mWidth - mCornerRadiusBottom, mHeight); + mPath.arcTo(mWidth - mCornerRadiusBottom * 2, mHeight - mCornerRadiusBottom * 2, + mWidth, mHeight, 90, -90, true); + break; + case TOP_RIGHT: + mPath.moveTo(mWidth, mCornerRadiusTop); + mPath.arcTo(mWidth - mCornerRadiusTop, 0, mWidth, mCornerRadiusTop, 0, -90, true); + break; + case TOP_LEFT: + mPath.moveTo(mCornerRadiusTop, 0); + mPath.arcTo(0, 0, mCornerRadiusTop, mCornerRadiusTop, 270, -90, true); + break; + } + return mPath; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/CornerPathRenderer.java b/packages/SystemUI/src/com/android/systemui/assist/ui/CornerPathRenderer.java new file mode 100644 index 000000000000..2b40e6501fdd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/assist/ui/CornerPathRenderer.java @@ -0,0 +1,140 @@ +/* + * 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.assist.ui; + +import android.graphics.Path; +import android.graphics.PointF; + +import java.util.ArrayList; +import java.util.List; + +/** + * Handles paths along device corners. + */ +public abstract class CornerPathRenderer { + + // The maximum delta between the corner curve and points approximating the corner curve. + private static final float ACCEPTABLE_ERROR = 0.1f; + + /** + * For convenience, labels the four device corners. + * + * Corners must be listed in CCW order, otherwise we'll break rotation. + */ + public enum Corner { + BOTTOM_LEFT, + BOTTOM_RIGHT, + TOP_RIGHT, + TOP_LEFT + } + + /** + * Returns the path along the inside of a corner (centered insetAmountPx from the corner's + * edge). + */ + public Path getInsetPath(Corner corner, float insetAmountPx) { + return approximateInnerPath(getCornerPath(corner), -insetAmountPx); + } + + /** + * Returns the path of a corner (centered on the exact corner). Must be implemented by extending + * classes, based on the device-specific rounded corners. A default implementation for circular + * corners is provided by CircularCornerPathRenderer. + */ + public abstract Path getCornerPath(Corner corner); + + private Path approximateInnerPath(Path input, float delta) { + List<PointF> points = shiftBy(getApproximatePoints(input), delta); + return toPath(points); + } + + private ArrayList<PointF> getApproximatePoints(Path path) { + float[] rawInput = path.approximate(ACCEPTABLE_ERROR); + + ArrayList<PointF> output = new ArrayList<>(); + + for (int i = 0; i < rawInput.length; i = i + 3) { + output.add(new PointF(rawInput[i + 1], rawInput[i + 2])); + } + + return output; + } + + private ArrayList<PointF> shiftBy(ArrayList<PointF> input, float delta) { + ArrayList<PointF> output = new ArrayList<>(); + + for (int i = 0; i < input.size(); i++) { + PointF point = input.get(i); + PointF normal = normalAt(input, i); + PointF shifted = + new PointF(point.x + (normal.x * delta), point.y + (normal.y * delta)); + output.add(shifted); + } + return output; + } + + private Path toPath(List<PointF> points) { + Path path = new Path(); + if (points.size() > 0) { + path.moveTo(points.get(0).x, points.get(0).y); + for (PointF point : points.subList(1, points.size())) { + path.lineTo(point.x, point.y); + } + } + return path; + } + + private PointF normalAt(List<PointF> points, int index) { + PointF d1; + if (index == 0) { + d1 = new PointF(0, 0); + } else { + PointF point = points.get(index); + PointF previousPoint = points.get(index - 1); + d1 = new PointF((point.x - previousPoint.x), (point.y - previousPoint.y)); + } + + PointF d2; + if (index == (points.size() - 1)) { + d2 = new PointF(0, 0); + } else { + PointF point = points.get(index); + PointF nextPoint = points.get(index + 1); + d2 = new PointF((nextPoint.x - point.x), (nextPoint.y - point.y)); + } + + return rotate90Ccw(normalize(new PointF(d1.x + d2.x, d1.y + d2.y))); + } + + private PointF rotate90Ccw(PointF input) { + return new PointF(-input.y, input.x); + } + + private float magnitude(PointF point) { + return (float) Math.sqrt((point.x * point.x) + (point.y * point.y)); + } + + private PointF normalize(PointF point) { + float magnitude = magnitude(point); + if (magnitude == 0.f) { + return point; + } + + float normal = 1 / magnitude; + return new PointF((point.x * normal), (point.y * normal)); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java new file mode 100644 index 000000000000..b1be811be9cf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java @@ -0,0 +1,187 @@ +/* + * 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.assist.ui; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.annotation.ColorInt; +import android.content.Context; +import android.graphics.PixelFormat; +import android.metrics.LogMaker; +import android.os.Bundle; +import android.util.Log; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.WindowManager; +import android.view.animation.PathInterpolator; +import android.widget.FrameLayout; + +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +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; + +/** + * Default UiController implementation. Shows white edge lights along the bottom of the phone, + * expanding from the corners to meet in the center. + */ +public class DefaultUiController implements AssistManager.UiController { + + private static final String TAG = "DefaultUiController"; + + private static final long ANIM_DURATION_MS = 200; + + protected final FrameLayout mRoot; + + private final WindowManager mWindowManager; + private final WindowManager.LayoutParams mLayoutParams; + private final PathInterpolator mProgressInterpolator = new PathInterpolator(.83f, 0, .84f, 1); + + private boolean mAttached = false; + private boolean mInvocationInProgress = false; + private float mLastInvocationProgress = 0; + + private ValueAnimator mInvocationAnimator = new ValueAnimator(); + private InvocationLightsView mInvocationLightsView; + + public DefaultUiController(Context context) { + mRoot = new FrameLayout(context); + mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + + mLayoutParams = new WindowManager.LayoutParams( + WindowManager.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.WRAP_CONTENT, 0, 0, + WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, + WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + PixelFormat.TRANSLUCENT); + mLayoutParams.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; + mLayoutParams.gravity = Gravity.BOTTOM; + mLayoutParams.setTitle("Assist"); + + mInvocationLightsView = (InvocationLightsView) + LayoutInflater.from(context).inflate(R.layout.invocation_lights, mRoot, false); + mRoot.addView(mInvocationLightsView); + } + + @Override // AssistManager.UiController + public void processBundle(Bundle bundle) { + Log.e(TAG, "Bundle received but handling is not implemented; ignoring"); + } + + @Override // AssistManager.UiController + public void onInvocationProgress(int type, float progress) { + if (progress == 1) { + animateInvocationCompletion(type, 0); + } else if (progress == 0) { + hide(); + } else { + if (!mInvocationInProgress) { + attach(); + mInvocationInProgress = true; + updateAssistHandleVisibility(); + } + setProgressInternal(type, progress); + } + mLastInvocationProgress = progress; + + // Logs assistant invocation start. + if (!mInvocationInProgress && progress > 0.f) { + MetricsLogger.action(new LogMaker(MetricsEvent.ASSISTANT) + .setType(MetricsEvent.TYPE_ACTION)); + } + // Logs assistant invocation cancelled. + if (mInvocationInProgress && progress == 0f) { + MetricsLogger.action(new LogMaker(MetricsEvent.ASSISTANT) + .setType(MetricsEvent.TYPE_DISMISS).setSubtype(0)); + } + } + + @Override // AssistManager.UiController + public void onGestureCompletion(float velocity) { + animateInvocationCompletion(AssistManager.INVOCATION_TYPE_GESTURE, velocity); + } + + @Override // AssistManager.UiController + public void hide() { + Dependency.get(AssistManager.class).hideAssist(); + detach(); + if (mInvocationAnimator.isRunning()) { + mInvocationAnimator.cancel(); + } + mInvocationLightsView.hide(); + mInvocationInProgress = false; + updateAssistHandleVisibility(); + } + + /** + * Sets the colors of the four invocation lights, from left to right. + */ + public void setInvocationColors(@ColorInt int color1, @ColorInt int color2, + @ColorInt int color3, @ColorInt int color4) { + mInvocationLightsView.setColors(color1, color2, color3, color4); + } + + private void updateAssistHandleVisibility() { + ScreenDecorations decorations = SysUiServiceProvider.getComponent(mRoot.getContext(), + ScreenDecorations.class); + decorations.setAssistHintBlocked(mInvocationInProgress); + } + + private void attach() { + if (!mAttached) { + mWindowManager.addView(mRoot, mLayoutParams); + mAttached = true; + } + } + + private void detach() { + if (mAttached) { + mWindowManager.removeViewImmediate(mRoot); + mAttached = false; + } + } + + private void setProgressInternal(int type, float progress) { + mInvocationLightsView.onInvocationProgress( + mProgressInterpolator.getInterpolation(progress)); + } + + private void animateInvocationCompletion(int type, float velocity) { + mInvocationAnimator = ValueAnimator.ofFloat(mLastInvocationProgress, 1); + mInvocationAnimator.setStartDelay(1); + mInvocationAnimator.setDuration(ANIM_DURATION_MS); + mInvocationAnimator.addUpdateListener( + animation -> setProgressInternal(type, (float) animation.getAnimatedValue())); + mInvocationAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + mInvocationInProgress = false; + mLastInvocationProgress = 0; + hide(); + } + }); + mInvocationAnimator.start(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/DisplayUtils.java b/packages/SystemUI/src/com/android/systemui/assist/ui/DisplayUtils.java new file mode 100644 index 000000000000..251229f42da3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/assist/ui/DisplayUtils.java @@ -0,0 +1,128 @@ +/* + * 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.assist.ui; + +import android.content.Context; +import android.util.DisplayMetrics; +import android.view.Display; +import android.view.Surface; + +/** + * Utility class for determining screen and corner dimensions. + */ +public class DisplayUtils { + /** + * Converts given distance from dp to pixels. + */ + public static int convertDpToPx(float dp, Context context) { + Display d = context.getDisplay(); + + DisplayMetrics dm = new DisplayMetrics(); + d.getRealMetrics(dm); + + return (int) Math.ceil(dp * dm.density); + } + + /** + * The width of the display. + * + * - Not affected by rotation. + * - Includes system decor. + */ + public static int getWidth(Context context) { + Display d = context.getDisplay(); + + DisplayMetrics dm = new DisplayMetrics(); + d.getRealMetrics(dm); + + int rotation = d.getRotation(); + if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) { + return dm.widthPixels; + } else { + return dm.heightPixels; + } + } + + /** + * The height of the display. + * + * - Not affected by rotation. + * - Includes system decor. + */ + public static int getHeight(Context context) { + Display d = context.getDisplay(); + + DisplayMetrics dm = new DisplayMetrics(); + d.getRealMetrics(dm); + + int rotation = d.getRotation(); + if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) { + return dm.heightPixels; + } else { + return dm.widthPixels; + } + } + + /** + * Returns the radius of the bottom corners (the distance from the true corner to the point + * where the curve ends), in pixels. + */ + public static int getCornerRadiusBottom(Context context) { + int radius = 0; + + int resourceId = context.getResources().getIdentifier("rounded_corner_radius_bottom", + "dimen", "android"); + if (resourceId > 0) { + radius = context.getResources().getDimensionPixelSize(resourceId); + } + + if (radius == 0) { + radius = getCornerRadiusDefault(context); + } + return radius; + } + + /** + * Returns the radius of the top corners (the distance from the true corner to the point where + * the curve ends), in pixels. + */ + public static int getCornerRadiusTop(Context context) { + int radius = 0; + + int resourceId = context.getResources().getIdentifier("rounded_corner_radius_top", + "dimen", "android"); + if (resourceId > 0) { + radius = context.getResources().getDimensionPixelSize(resourceId); + } + + if (radius == 0) { + radius = getCornerRadiusDefault(context); + } + return radius; + } + + private static int getCornerRadiusDefault(Context context) { + int radius = 0; + + int resourceId = context.getResources().getIdentifier("rounded_corner_radius", "dimen", + "android"); + if (resourceId > 0) { + radius = context.getResources().getDimensionPixelSize(resourceId); + } + return radius; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/EdgeLight.java b/packages/SystemUI/src/com/android/systemui/assist/ui/EdgeLight.java new file mode 100644 index 000000000000..9ae02c5e3104 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/assist/ui/EdgeLight.java @@ -0,0 +1,99 @@ +/* + * 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.assist.ui; + +import androidx.annotation.ColorInt; + +/** + * Represents a line drawn on the perimeter of the display. + * + * Offsets and lengths are both normalized to the perimeter of the display – ex. a length of 1 + * is equal to the perimeter of the display. Positions move counter-clockwise as values increase. + * + * If there is no bottom corner radius, the origin is the bottom-left corner. + * If there is a bottom corner radius, the origin is immediately after the bottom corner radius, + * counter-clockwise. + */ +public final class EdgeLight { + @ColorInt + private int mColor; + private float mOffset; + private float mLength; + + /** Copies a list of EdgeLights. */ + public static EdgeLight[] copy(EdgeLight[] array) { + EdgeLight[] copy = new EdgeLight[array.length]; + for (int i = 0; i < array.length; i++) { + copy[i] = new EdgeLight(array[i]); + } + return copy; + } + + public EdgeLight(@ColorInt int color, float offset, float length) { + mColor = color; + mOffset = offset; + mLength = length; + } + + public EdgeLight(EdgeLight sourceLight) { + mColor = sourceLight.getColor(); + mOffset = sourceLight.getOffset(); + mLength = sourceLight.getLength(); + } + + /** Returns the current edge light color. */ + @ColorInt + public int getColor() { + return mColor; + } + + /** Sets the edge light color. */ + public void setColor(@ColorInt int color) { + mColor = color; + } + + /** Returns the edge light length, in units of the total device perimeter. */ + public float getLength() { + return mLength; + } + + /** Sets the edge light length, in units of the total device perimeter. */ + public void setLength(float length) { + mLength = length; + } + + /** + * Returns the current offset, in units of the total device perimeter and measured from the + * bottom-left corner (see class description). + */ + public float getOffset() { + return mOffset; + } + + /** + * Sets the current offset, in units of the total device perimeter and measured from the + * bottom-left corner (see class description). + */ + public void setOffset(float offset) { + mOffset = offset; + } + + /** Returns the center, measured from the bottom-left corner (see class description). */ + public float getCenter() { + return mOffset + (mLength / 2.f); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java b/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java new file mode 100644 index 000000000000..de1d7c8c0a04 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java @@ -0,0 +1,194 @@ +/* + * 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.assist.ui; + +import android.annotation.ColorInt; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Path; +import android.util.AttributeSet; +import android.util.Log; +import android.util.MathUtils; +import android.view.View; + +import com.android.systemui.R; + +import java.util.ArrayList; + +/** + * Shows lights at the bottom of the phone, marking the invocation progress. + */ +public class InvocationLightsView extends View { + + private static final String TAG = "InvocationLightsView"; + + private static final int LIGHT_HEIGHT_DP = 3; + // minimum light length as a fraction of the corner length + private static final float MINIMUM_CORNER_RATIO = .6f; + + protected final ArrayList<EdgeLight> mAssistInvocationLights = new ArrayList<>(); + protected final PerimeterPathGuide mGuide; + + private final Paint mPaint = new Paint(); + // Path used to render lights. One instance is used to draw all lights and is cached to avoid + // allocation on each frame. + private final Path mPath = new Path(); + private final int mViewHeight; + + // Allocate variable for screen location lookup to avoid memory alloc onDraw() + private int[] mScreenLocation = new int[2]; + + public InvocationLightsView(Context context) { + this(context, null); + } + + public InvocationLightsView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public InvocationLightsView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public InvocationLightsView(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + int strokeWidth = DisplayUtils.convertDpToPx(LIGHT_HEIGHT_DP, context); + mPaint.setStrokeWidth(strokeWidth); + mPaint.setStyle(Paint.Style.STROKE); + mPaint.setStrokeJoin(Paint.Join.MITER); + mPaint.setAntiAlias(true); + + int cornerRadiusBottom = DisplayUtils.getCornerRadiusBottom(context); + int cornerRadiusTop = DisplayUtils.getCornerRadiusTop(context); + int displayWidth = DisplayUtils.getWidth(context); + int displayHeight = DisplayUtils.getHeight(context); + CircularCornerPathRenderer cornerPathRenderer = new CircularCornerPathRenderer( + cornerRadiusBottom, cornerRadiusTop, displayWidth, displayHeight); + mGuide = new PerimeterPathGuide(context, cornerPathRenderer, + strokeWidth / 2, displayWidth, displayHeight); + + mViewHeight = Math.max(cornerRadiusBottom, cornerRadiusTop); + + @ColorInt int lightColor = getResources().getColor(R.color.default_invocation_lights_color); + for (int i = 0; i < 4; i++) { + mAssistInvocationLights.add(new EdgeLight(lightColor, 0, 0)); + } + } + + /** + * Updates positions of the invocation lights based on the progress (a float between 0 and 1). + * The lights begin at the device corners and expand inward until they meet at the center. + */ + public void onInvocationProgress(float progress) { + if (progress == 0) { + setVisibility(View.GONE); + } else { + float cornerLengthNormalized = + mGuide.getRegionWidth(PerimeterPathGuide.Region.BOTTOM_LEFT); + float arcLengthNormalized = cornerLengthNormalized * MINIMUM_CORNER_RATIO; + float arcOffsetNormalized = (cornerLengthNormalized - arcLengthNormalized) / 2f; + + float minLightLength = arcLengthNormalized / 2; + float maxLightLength = mGuide.getRegionWidth(PerimeterPathGuide.Region.BOTTOM) / 4f; + + float lightLength = MathUtils.lerp(minLightLength, maxLightLength, progress); + + float leftStart = (-cornerLengthNormalized + arcOffsetNormalized) * (1 - progress); + float rightStart = mGuide.getRegionWidth(PerimeterPathGuide.Region.BOTTOM) + + (cornerLengthNormalized - arcOffsetNormalized) * (1 - progress); + + setLight(0, leftStart, lightLength); + setLight(1, leftStart + lightLength, lightLength); + setLight(2, rightStart - (lightLength * 2), lightLength); + setLight(3, rightStart - lightLength, lightLength); + setVisibility(View.VISIBLE); + } + invalidate(); + } + + /** + * Hides and resets the invocation lights. + */ + public void hide() { + setVisibility(GONE); + for (EdgeLight light : mAssistInvocationLights) { + light.setLength(0); + } + } + + /** + * Sets the invocation light colors, from left to right. + */ + public void setColors(@ColorInt int color1, @ColorInt int color2, + @ColorInt int color3, @ColorInt int color4) { + mAssistInvocationLights.get(0).setColor(color1); + mAssistInvocationLights.get(1).setColor(color2); + mAssistInvocationLights.get(2).setColor(color3); + mAssistInvocationLights.get(3).setColor(color4); + } + + @Override + protected void onFinishInflate() { + getLayoutParams().height = mViewHeight; + requestLayout(); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + + int rotation = getContext().getDisplay().getRotation(); + mGuide.setRotation(rotation); + } + + @Override + protected void onDraw(Canvas canvas) { + // If the view doesn't take up the whole screen, offset the canvas by its translation + // distance such that PerimeterPathGuide's paths are drawn properly based upon the actual + // screen edges. + getLocationOnScreen(mScreenLocation); + canvas.translate(-mScreenLocation[0], -mScreenLocation[1]); + + // if the lights are different colors, the inner ones need to be drawn last and with a + // square cap so that the join between lights is straight + mPaint.setStrokeCap(Paint.Cap.ROUND); + renderLight(mAssistInvocationLights.get(0), canvas); + renderLight(mAssistInvocationLights.get(3), canvas); + + mPaint.setStrokeCap(Paint.Cap.SQUARE); + renderLight(mAssistInvocationLights.get(1), canvas); + renderLight(mAssistInvocationLights.get(2), canvas); + } + + protected void setLight(int index, float offset, float length) { + if (index < 0 || index >= 4) { + Log.w(TAG, "invalid invocation light index: " + index); + } + mAssistInvocationLights.get(index).setOffset(offset); + mAssistInvocationLights.get(index).setLength(length); + } + + private void renderLight(EdgeLight light, Canvas canvas) { + mGuide.strokeSegment(mPath, light.getOffset(), light.getOffset() + light.getLength()); + mPaint.setColor(light.getColor()); + canvas.drawPath(mPath, mPaint); + } + +} diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/PerimeterPathGuide.java b/packages/SystemUI/src/com/android/systemui/assist/ui/PerimeterPathGuide.java new file mode 100644 index 000000000000..8eea36892aa7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/assist/ui/PerimeterPathGuide.java @@ -0,0 +1,389 @@ +/* + * 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.assist.ui; + +import static android.view.Surface.ROTATION_0; +import static android.view.Surface.ROTATION_180; +import static android.view.Surface.ROTATION_270; +import static android.view.Surface.ROTATION_90; + +import android.content.Context; +import android.graphics.Matrix; +import android.graphics.Path; +import android.graphics.PathMeasure; +import android.util.Log; +import android.util.Pair; +import android.view.Surface; + +import androidx.core.math.MathUtils; + +/** + * PerimeterPathGuide establishes a coordinate system for drawing paths along the perimeter of the + * screen. All positions around the perimeter have a coordinate [0, 1). The origin is the bottom + * left corner of the screen, to the right of the curved corner, if any. Coordinates increase + * counter-clockwise around the screen. + * + * Non-square screens require PerimeterPathGuide to be notified when the rotation changes, such that + * it can recompute the edge lengths for the coordinate system. + */ +public class PerimeterPathGuide { + + private static final String TAG = "PerimeterPathGuide"; + + /** + * For convenience, labels sections of the device perimeter. + * + * Must be listed in CCW order. + */ + public enum Region { + BOTTOM, + BOTTOM_RIGHT, + RIGHT, + TOP_RIGHT, + TOP, + TOP_LEFT, + LEFT, + BOTTOM_LEFT + } + + private final int mDeviceWidthPx; + private final int mDeviceHeightPx; + private final int mTopCornerRadiusPx; + private final int mBottomCornerRadiusPx; + + private class RegionAttributes { + public float absoluteLength; + public float normalizedLength; + public float endCoordinate; + public Path path; + } + + // Allocate a Path and PathMeasure for use by intermediate operations that would otherwise have + // to allocate. reset() must be called before using this path, this ensures state from previous + // operations is cleared. + private final Path mScratchPath = new Path(); + private final CornerPathRenderer mCornerPathRenderer; + private final PathMeasure mScratchPathMeasure = new PathMeasure(mScratchPath, false); + private RegionAttributes[] mRegions; + private final int mEdgeInset; + private int mRotation = ROTATION_0; + + public PerimeterPathGuide(Context context, CornerPathRenderer cornerPathRenderer, + int edgeInset, int screenWidth, int screenHeight) { + mCornerPathRenderer = cornerPathRenderer; + mDeviceWidthPx = screenWidth; + mDeviceHeightPx = screenHeight; + mTopCornerRadiusPx = DisplayUtils.getCornerRadiusTop(context); + mBottomCornerRadiusPx = DisplayUtils.getCornerRadiusBottom(context); + mEdgeInset = edgeInset; + + mRegions = new RegionAttributes[8]; + for (int i = 0; i < mRegions.length; i++) { + mRegions[i] = new RegionAttributes(); + } + computeRegions(); + } + + /** + * Sets the rotation. + * + * @param rotation one of Surface.ROTATION_0, Surface.ROTATION_90, Surface.ROTATION_180, + * Surface.ROTATION_270 + */ + public void setRotation(int rotation) { + if (rotation != mRotation) { + switch (rotation) { + case ROTATION_0: + case ROTATION_90: + case ROTATION_180: + case ROTATION_270: + mRotation = rotation; + computeRegions(); + break; + default: + Log.e(TAG, "Invalid rotation provided: " + rotation); + } + } + } + + /** + * Sets path to the section of the perimeter between startCoord and endCoord (measured + * counter-clockwise from the bottom left). + */ + public void strokeSegment(Path path, float startCoord, float endCoord) { + path.reset(); + + startCoord = ((startCoord % 1) + 1) % 1; // Wrap to the range [0, 1). + endCoord = ((endCoord % 1) + 1) % 1; // Wrap to the range [0, 1). + boolean outOfOrder = startCoord > endCoord; + + if (outOfOrder) { + strokeSegmentInternal(path, startCoord, 1f); + startCoord = 0; + } + strokeSegmentInternal(path, startCoord, endCoord); + } + + /** + * Returns the device perimeter in pixels. + */ + public float getPerimeterPx() { + float total = 0; + for (RegionAttributes region : mRegions) { + total += region.absoluteLength; + } + return total; + } + + /** + * Returns the bottom corner radius in pixels. + */ + public float getBottomCornerRadiusPx() { + return mBottomCornerRadiusPx; + } + + /** + * Given a region and a progress value [0,1] indicating the counter-clockwise progress within + * that region, compute the global [0,1) coordinate. + */ + public float getCoord(Region region, float progress) { + RegionAttributes regionAttributes = mRegions[region.ordinal()]; + progress = MathUtils.clamp(progress, 0, 1); + return regionAttributes.endCoordinate - (1 - progress) * regionAttributes.normalizedLength; + } + + /** + * Returns the center of the provided region, relative to the entire perimeter. + */ + public float getRegionCenter(Region region) { + return getCoord(region, 0.5f); + } + + /** + * Returns the width of the provided region, in units relative to the entire perimeter. + */ + public float getRegionWidth(Region region) { + return mRegions[region.ordinal()].normalizedLength; + } + + /** + * Points are expressed in terms of their relative position on the perimeter of the display, + * moving counter-clockwise. This method converts a point to clockwise, assisting use cases + * such as animating to a point clockwise instead of counter-clockwise. + * + * @param point A point in the range from 0 to 1. + * @return A point in the range of -1 to 0 that represents the same location as {@code point}. + */ + public static float makeClockwise(float point) { + return point - 1; + } + + private int getPhysicalCornerRadius(CircularCornerPathRenderer.Corner corner) { + if (corner == CircularCornerPathRenderer.Corner.BOTTOM_LEFT + || corner == CircularCornerPathRenderer.Corner.BOTTOM_RIGHT) { + return mBottomCornerRadiusPx; + } + return mTopCornerRadiusPx; + } + + // Populate mRegions based upon the current rotation value. + private void computeRegions() { + int screenWidth = mDeviceWidthPx; + int screenHeight = mDeviceHeightPx; + + int rotateMatrix = 0; + + switch (mRotation) { + case ROTATION_90: + rotateMatrix = -90; + break; + case ROTATION_180: + rotateMatrix = -180; + break; + case Surface.ROTATION_270: + rotateMatrix = -270; + break; + } + + Matrix matrix = new Matrix(); + matrix.postRotate(rotateMatrix, mDeviceWidthPx / 2, mDeviceHeightPx / 2); + + if (mRotation == ROTATION_90 || mRotation == Surface.ROTATION_270) { + screenHeight = mDeviceWidthPx; + screenWidth = mDeviceHeightPx; + matrix.postTranslate((mDeviceHeightPx + - mDeviceWidthPx) / 2, (mDeviceWidthPx - mDeviceHeightPx) / 2); + } + + CircularCornerPathRenderer.Corner screenBottomLeft = getRotatedCorner( + CircularCornerPathRenderer.Corner.BOTTOM_LEFT); + CircularCornerPathRenderer.Corner screenBottomRight = getRotatedCorner( + CircularCornerPathRenderer.Corner.BOTTOM_RIGHT); + CircularCornerPathRenderer.Corner screenTopLeft = getRotatedCorner( + CircularCornerPathRenderer.Corner.TOP_LEFT); + CircularCornerPathRenderer.Corner screenTopRight = getRotatedCorner( + CircularCornerPathRenderer.Corner.TOP_RIGHT); + + mRegions[Region.BOTTOM_LEFT.ordinal()].path = + mCornerPathRenderer.getInsetPath(screenBottomLeft, mEdgeInset); + mRegions[Region.BOTTOM_RIGHT.ordinal()].path = + mCornerPathRenderer.getInsetPath(screenBottomRight, mEdgeInset); + mRegions[Region.TOP_RIGHT.ordinal()].path = + mCornerPathRenderer.getInsetPath(screenTopRight, mEdgeInset); + mRegions[Region.TOP_LEFT.ordinal()].path = + mCornerPathRenderer.getInsetPath(screenTopLeft, mEdgeInset); + + mRegions[Region.BOTTOM_LEFT.ordinal()].path.transform(matrix); + mRegions[Region.BOTTOM_RIGHT.ordinal()].path.transform(matrix); + mRegions[Region.TOP_RIGHT.ordinal()].path.transform(matrix); + mRegions[Region.TOP_LEFT.ordinal()].path.transform(matrix); + + + Path bottomPath = new Path(); + bottomPath.moveTo(getPhysicalCornerRadius(screenBottomLeft), screenHeight - mEdgeInset); + bottomPath.lineTo(screenWidth - getPhysicalCornerRadius(screenBottomRight), + screenHeight - mEdgeInset); + mRegions[Region.BOTTOM.ordinal()].path = bottomPath; + + Path topPath = new Path(); + topPath.moveTo(screenWidth - getPhysicalCornerRadius(screenTopRight), mEdgeInset); + topPath.lineTo(getPhysicalCornerRadius(screenTopLeft), mEdgeInset); + mRegions[Region.TOP.ordinal()].path = topPath; + + Path rightPath = new Path(); + rightPath.moveTo(screenWidth - mEdgeInset, + screenHeight - getPhysicalCornerRadius(screenBottomRight)); + rightPath.lineTo(screenWidth - mEdgeInset, getPhysicalCornerRadius(screenTopRight)); + mRegions[Region.RIGHT.ordinal()].path = rightPath; + + Path leftPath = new Path(); + leftPath.moveTo(mEdgeInset, + getPhysicalCornerRadius(screenTopLeft)); + leftPath.lineTo(mEdgeInset, screenHeight - getPhysicalCornerRadius(screenBottomLeft)); + mRegions[Region.LEFT.ordinal()].path = leftPath; + + float perimeterLength = 0; + PathMeasure pathMeasure = new PathMeasure(); + for (int i = 0; i < mRegions.length; i++) { + pathMeasure.setPath(mRegions[i].path, false); + mRegions[i].absoluteLength = pathMeasure.getLength(); + perimeterLength += mRegions[i].absoluteLength; + } + + float accum = 0; + for (int i = 0; i < mRegions.length; i++) { + mRegions[i].normalizedLength = mRegions[i].absoluteLength / perimeterLength; + accum += mRegions[i].normalizedLength; + mRegions[i].endCoordinate = accum; + } + } + + private CircularCornerPathRenderer.Corner getRotatedCorner( + CircularCornerPathRenderer.Corner screenCorner) { + int corner = screenCorner.ordinal(); + switch (mRotation) { + case ROTATION_90: + corner += 3; + break; + case ROTATION_180: + corner += 2; + break; + case Surface.ROTATION_270: + corner += 1; + break; + } + return CircularCornerPathRenderer.Corner.values()[corner % 4]; + } + + private void strokeSegmentInternal(Path path, float startCoord, float endCoord) { + Pair<Region, Float> startPoint = placePoint(startCoord); + Pair<Region, Float> endPoint = placePoint(endCoord); + + if (startPoint.first.equals(endPoint.first)) { + strokeRegion(path, startPoint.first, startPoint.second, endPoint.second); + } else { + strokeRegion(path, startPoint.first, startPoint.second, 1f); + boolean hitStart = false; + for (Region r : Region.values()) { + if (r.equals(startPoint.first)) { + hitStart = true; + continue; + } + if (hitStart) { + if (!r.equals(endPoint.first)) { + strokeRegion(path, r, 0f, 1f); + } else { + strokeRegion(path, r, 0f, endPoint.second); + break; + } + } + } + } + } + + private void strokeRegion(Path path, Region r, float relativeStart, float relativeEnd) { + if (relativeStart == relativeEnd) { + return; + } + + mScratchPathMeasure.setPath(mRegions[r.ordinal()].path, false); + mScratchPathMeasure.getSegment(relativeStart * mScratchPathMeasure.getLength(), + relativeEnd * mScratchPathMeasure.getLength(), path, true); + } + + /** + * Return the Region where the point is located, and its relative position within that region + * (from 0 to 1). + * Note that we move counterclockwise around the perimeter; for example, a relative position of + * 0 in + * the BOTTOM region is on the left side of the screen, but in the TOP region it’s on the + * right. + */ + private Pair<Region, Float> placePoint(float coord) { + if (0 > coord || coord > 1) { + coord = ((coord % 1) + 1) + % 1; // Wrap to the range [0, 1). Inputs of exactly 1 are preserved. + } + + Region r = getRegionForPoint(coord); + if (r.equals(Region.BOTTOM)) { + return Pair.create(r, coord / mRegions[r.ordinal()].normalizedLength); + } else { + float coordOffsetInRegion = coord - mRegions[r.ordinal() - 1].endCoordinate; + float coordRelativeToRegion = + coordOffsetInRegion / mRegions[r.ordinal()].normalizedLength; + return Pair.create(r, coordRelativeToRegion); + } + } + + private Region getRegionForPoint(float coord) { + // If coord is outside of [0,1], wrap to [0,1). + if (coord < 0 || coord > 1) { + coord = ((coord % 1) + 1) % 1; + } + + for (Region region : Region.values()) { + if (coord <= mRegions[region.ordinal()].endCoordinate) { + return region; + } + } + + // Should never happen. + Log.e(TAG, "Fell out of getRegionForPoint"); + return Region.BOTTOM; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index 4ec79a64b2a3..392183b554a9 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -16,6 +16,7 @@ package com.android.systemui.bubbles; +import static android.app.Notification.FLAG_BUBBLE; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; @@ -40,8 +41,6 @@ import static java.lang.annotation.RetentionPolicy.SOURCE; import android.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; -import android.app.ActivityTaskManager; -import android.app.IActivityTaskManager; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; @@ -53,6 +52,7 @@ import android.graphics.Rect; import android.os.RemoteException; import android.os.ServiceManager; import android.provider.Settings; +import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.StatusBarNotification; import android.service.notification.ZenModeConfig; import android.util.Log; @@ -138,7 +138,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi private final Context mContext; private final NotificationEntryManager mNotificationEntryManager; - private final IActivityTaskManager mActivityTaskManager; private final BubbleTaskStackListener mTaskStackListener; private BubbleStateChangeListener mStateChangeListener; private BubbleExpandListener mExpandListener; @@ -250,7 +249,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi mStatusBarStateListener = new StatusBarStateListener(); Dependency.get(StatusBarStateController.class).addCallback(mStatusBarStateListener); - mActivityTaskManager = ActivityTaskManager.getService(); mTaskStackListener = new BubbleTaskStackListener(); ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener); @@ -509,6 +507,12 @@ public class BubbleController implements ConfigurationController.ConfigurationLi updateBubble(entry); } } + + @Override + public void onNotificationRankingUpdated(RankingMap rankingMap) { + // Forward to BubbleData to block any bubbles which should no longer be shown + mBubbleData.notificationRankingUpdated(rankingMap); + } }; @SuppressWarnings("FieldCanBeLocal") @@ -547,8 +551,11 @@ public class BubbleController implements ConfigurationController.ConfigurationLi mNotificationEntryManager.performRemoveNotification(bubble.entry.notification, UNDEFINED_DISMISS_REASON); } else { - // The notification is still in the shade but we've removed the bubble so - // lets make sure NoMan knows it's not a bubble anymore + // Update the flag for SysUI + bubble.entry.notification.getNotification().flags &= ~FLAG_BUBBLE; + + // Make sure NoMan knows it's not a bubble anymore so anyone querying it will + // get right result back try { mBarService.onNotificationBubbleChanged(bubble.getKey(), false /* isBubble */); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java index 6ab973eb3065..5575b35a12ae 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java @@ -22,6 +22,8 @@ import static java.util.stream.Collectors.toList; import android.app.Notification; import android.app.PendingIntent; import android.content.Context; +import android.service.notification.NotificationListenerService; +import android.service.notification.NotificationListenerService.RankingMap; import android.util.Log; import android.util.Pair; @@ -114,6 +116,8 @@ public class BubbleData { // State tracked during an operation -- keeps track of what listener events to dispatch. private Update mStateChange; + private NotificationListenerService.Ranking mTmpRanking; + private TimeSource mTimeSource = System::currentTimeMillis; @Nullable @@ -193,6 +197,31 @@ public class BubbleData { dispatchPendingChanges(); } + /** + * Called when NotificationListener has received adjusted notification rank and reapplied + * filtering and sorting. This is used to dismiss any bubbles which should no longer be shown + * due to changes in permissions on the notification channel or the global setting. + * + * @param rankingMap the updated ranking map from NotificationListenerService + */ + public void notificationRankingUpdated(RankingMap rankingMap) { + if (mTmpRanking == null) { + mTmpRanking = new NotificationListenerService.Ranking(); + } + + String[] orderedKeys = rankingMap.getOrderedKeys(); + for (int i = 0; i < orderedKeys.length; i++) { + String key = orderedKeys[i]; + if (hasBubbleWithKey(key)) { + rankingMap.getRanking(key, mTmpRanking); + if (!mTmpRanking.canBubble()) { + doRemove(key, BubbleController.DISMISS_BLOCKED); + } + } + } + dispatchPendingChanges(); + } + private void doAdd(Bubble bubble) { if (DEBUG) { Log.d(TAG, "doAdd: " + bubble); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index 6de0fb581e90..771df2d86110 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -330,9 +330,7 @@ public class BubbleStackView extends FrameLayout { mSurfaceSynchronizer = synchronizer != null ? synchronizer : DEFAULT_SURFACE_SYNCHRONIZER; mBubbleContainer = new PhysicsAnimationLayout(context); - mBubbleContainer.setMaxRenderedChildren( - getResources().getInteger(R.integer.bubbles_max_rendered)); - mBubbleContainer.setController(mStackAnimationController); + mBubbleContainer.setActiveController(mStackAnimationController); mBubbleContainer.setElevation(elevation); mBubbleContainer.setClipChildren(false); addView(mBubbleContainer, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)); @@ -728,11 +726,10 @@ public class BubbleStackView extends FrameLayout { public void updateBubbleOrder(List<Bubble> bubbles) { for (int i = 0; i < bubbles.size(); i++) { Bubble bubble = bubbles.get(i); - mBubbleContainer.moveViewTo(bubble.iconView, i); + mBubbleContainer.reorderView(bubble.iconView, i); } } - /** * Changes the currently selected bubble. If the stack is already expanded, the newly selected * bubble will be shown immediately. This does not change the expanded state or change the @@ -909,19 +906,17 @@ public class BubbleStackView extends FrameLayout { }; if (shouldExpand) { - mBubbleContainer.setController(mExpandedAnimationController); - mExpandedAnimationController.expandFromStack( - mStackAnimationController.getStackPositionAlongNearestHorizontalEdge() - /* collapseTo */, - () -> { - updatePointerPosition(); - updateAfter.run(); - } /* after */); + mBubbleContainer.setActiveController(mExpandedAnimationController); + mExpandedAnimationController.expandFromStack(() -> { + updatePointerPosition(); + updateAfter.run(); + } /* after */); } else { mBubbleContainer.cancelAllAnimations(); mExpandedAnimationController.collapseBackToStack( + mStackAnimationController.getStackPositionAlongNearestHorizontalEdge(), () -> { - mBubbleContainer.setController(mStackAnimationController); + mBubbleContainer.setActiveController(mStackAnimationController); updateAfter.run(); }); } @@ -1014,7 +1009,7 @@ public class BubbleStackView extends FrameLayout { } mStackAnimationController.cancelStackPositionAnimations(); - mBubbleContainer.setController(mStackAnimationController); + mBubbleContainer.setActiveController(mStackAnimationController); hideFlyoutImmediate(); mDraggingInDismissTarget = false; @@ -1112,6 +1107,10 @@ public class BubbleStackView extends FrameLayout { /** Called when a gesture is completed or cancelled. */ void onGestureFinished() { mIsGestureInProgress = false; + + if (mIsExpanded) { + mExpandedAnimationController.onGestureFinished(); + } } /** Prepares and starts the desaturate/darken animation on the bubble stack. */ @@ -1202,6 +1201,7 @@ public class BubbleStackView extends FrameLayout { */ void magnetToStackIfNeededThenAnimateDismissal( View touchedView, float velX, float velY, Runnable after) { + final View draggedOutBubble = mExpandedAnimationController.getDraggedOutBubble(); final Runnable animateDismissal = () -> { mAfterMagnet = null; @@ -1219,7 +1219,7 @@ public class BubbleStackView extends FrameLayout { resetDesaturationAndDarken(); }); } else { - mExpandedAnimationController.dismissDraggedOutBubble(() -> { + mExpandedAnimationController.dismissDraggedOutBubble(draggedOutBubble, () -> { mAnimatingMagnet = false; mShowingDismiss = false; mDraggingInDismissTarget = false; @@ -1386,10 +1386,18 @@ public class BubbleStackView extends FrameLayout { }; // Post in case layout isn't complete and getWidth returns 0. - post(() -> mFlyout.showFlyout( - updateMessage, mStackAnimationController.getStackPosition(), getWidth(), - mStackAnimationController.isStackOnLeftSide(), - bubble.iconView.getBadgeColor(), mAfterFlyoutHides)); + post(() -> { + // An auto-expanding bubble could have been posted during the time it takes to + // layout. + if (isExpanded()) { + return; + } + + mFlyout.showFlyout( + updateMessage, mStackAnimationController.getStackPosition(), getWidth(), + mStackAnimationController.isStackOnLeftSide(), + bubble.iconView.getBadgeColor(), mAfterFlyoutHides); + }); } mFlyout.removeCallbacks(mHideFlyout); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java index 24337a3c3312..1fa0e12452e1 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java @@ -22,6 +22,7 @@ import android.graphics.PointF; import android.view.View; import android.view.WindowInsets; +import androidx.annotation.Nullable; import androidx.dynamicanimation.animation.DynamicAnimation; import androidx.dynamicanimation.animation.SpringForce; @@ -63,12 +64,16 @@ public class ExpandedAnimationController private Point mDisplaySize; /** Size of dismiss target at bottom of screen. */ private float mPipDismissHeight; - /** Max number of bubbles shown in row above expanded view.*/ - private int mBubblesMaxRendered; /** Whether the dragged-out bubble is in the dismiss target. */ private boolean mIndividualBubbleWithinDismissTarget = false; + private boolean mAnimatingExpand = false; + private boolean mAnimatingCollapse = false; + private Runnable mAfterExpand; + private Runnable mAfterCollapse; + private PointF mCollapsePoint; + /** * Whether the dragged out bubble is springing towards the touch point, rather than using the * default behavior of moving directly to the touch point. @@ -97,56 +102,60 @@ public class ExpandedAnimationController private View mBubbleDraggingOut; /** - * Drag velocities for the dragging-out bubble when the drag finished. These are used by - * {@link #onChildRemoved} to animate out the bubble while respecting touch velocity. + * Animates expanding the bubbles into a row along the top of the screen. */ - private float mBubbleDraggingOutVelX; - private float mBubbleDraggingOutVelY; + public void expandFromStack(Runnable after) { + mAnimatingCollapse = false; + mAnimatingExpand = true; + mAfterExpand = after; - @Override - protected void setLayout(PhysicsAnimationLayout layout) { - super.setLayout(layout); + startOrUpdateExpandAnimation(); + } - final Resources res = layout.getResources(); - mStackOffsetPx = res.getDimensionPixelSize(R.dimen.bubble_stack_offset); - mBubblePaddingPx = res.getDimensionPixelSize(R.dimen.bubble_padding); - mBubbleSizePx = res.getDimensionPixelSize(R.dimen.individual_bubble_size); - mStatusBarHeight = - res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); - mPipDismissHeight = res.getDimensionPixelSize(R.dimen.pip_dismiss_gradient_height); - mBubblesMaxRendered = res.getInteger(R.integer.bubbles_max_rendered); + /** Animate collapsing the bubbles back to their stacked position. */ + public void collapseBackToStack(PointF collapsePoint, Runnable after) { + mAnimatingExpand = false; + mAnimatingCollapse = true; + mAfterCollapse = after; + mCollapsePoint = collapsePoint; + + startOrUpdateCollapseAnimation(); } - /** - * Animates expanding the bubbles into a row along the top of the screen. - */ - public void expandFromStack(PointF collapseTo, Runnable after) { + private void startOrUpdateExpandAnimation() { animationsForChildrenFromIndex( 0, /* startIndex */ - new ChildAnimationConfigurator() { - @Override - public void configureAnimationForChildAtIndex( - int index, PhysicsAnimationLayout.PhysicsPropertyAnimator animation) { - animation.position(getBubbleLeft(index), getExpandedY()); + (index, animation) -> animation.position(getBubbleLeft(index), getExpandedY())) + .startAll(() -> { + mAnimatingExpand = false; + + if (mAfterExpand != null) { + mAfterExpand.run(); } - }) - .startAll(after); - mCollapseToPoint = collapseTo; + mAfterExpand = null; + }); } - /** Animate collapsing the bubbles back to their stacked position. */ - public void collapseBackToStack(Runnable after) { + private void startOrUpdateCollapseAnimation() { // Stack to the left if we're going to the left, or right if not. - final float sideMultiplier = mLayout.isFirstChildXLeftOfCenter(mCollapseToPoint.x) ? -1 : 1; - + final float sideMultiplier = mLayout.isFirstChildXLeftOfCenter(mCollapsePoint.x) ? -1 : 1; animationsForChildrenFromIndex( 0, /* startIndex */ - (index, animation) -> + (index, animation) -> { animation.position( - mCollapseToPoint.x + (sideMultiplier * index * mStackOffsetPx), - mCollapseToPoint.y)) - .startAll(after /* endAction */); + mCollapsePoint.x + (sideMultiplier * index * mStackOffsetPx), + mCollapsePoint.y); + }) + .startAll(() -> { + mAnimatingCollapse = false; + + if (mAfterCollapse != null) { + mAfterCollapse.run(); + } + + mAfterCollapse = null; + }); } /** Prepares the given bubble to be dragged out. */ @@ -190,10 +199,10 @@ public class ExpandedAnimationController } /** Plays a dismiss animation on the dragged out bubble. */ - public void dismissDraggedOutBubble(Runnable after) { + public void dismissDraggedOutBubble(View bubble, Runnable after) { mIndividualBubbleWithinDismissTarget = false; - animationForChild(mBubbleDraggingOut) + animationForChild(bubble) .withStiffness(SpringForce.STIFFNESS_HIGH) .scaleX(1.1f) .scaleY(1.1f) @@ -203,6 +212,10 @@ public class ExpandedAnimationController updateBubblePositions(); } + @Nullable public View getDraggedOutBubble() { + return mBubbleDraggingOut; + } + /** Magnets the given bubble to the dismiss target. */ public void magnetBubbleToDismiss( View bubbleView, float velX, float velY, float destY, Runnable after) { @@ -241,24 +254,17 @@ public class ExpandedAnimationController final int index = mLayout.indexOfChild(bubbleView); animationForChildAtIndex(index) - .position(getBubbleLeft(index), getExpandedY()) - .withPositionStartVelocities(velX, velY) - .start(() -> bubbleView.setTranslationZ(0f) /* after */); + .position(getBubbleLeft(index), getExpandedY()) + .withPositionStartVelocities(velX, velY) + .start(() -> bubbleView.setTranslationZ(0f) /* after */); - mBubbleDraggingOut = null; - mBubbleDraggedOutEnough = false; updateBubblePositions(); } - /** - * Sets configuration variables so that when the given bubble is removed, the animations are - * started with the given velocities. - */ - public void prepareForDismissalWithVelocity(View bubbleView, float velX, float velY) { - mBubbleDraggingOut = bubbleView; - mBubbleDraggingOutVelX = velX; - mBubbleDraggingOutVelY = velY; + /** Resets bubble drag out gesture flags. */ + public void onGestureFinished() { mBubbleDraggedOutEnough = false; + mBubbleDraggingOut = null; } /** @@ -297,6 +303,23 @@ public class ExpandedAnimationController } @Override + void onActiveControllerForLayout(PhysicsAnimationLayout layout) { + final Resources res = layout.getResources(); + mStackOffsetPx = res.getDimensionPixelSize(R.dimen.bubble_stack_offset); + mBubblePaddingPx = res.getDimensionPixelSize(R.dimen.bubble_padding); + mBubbleSizePx = res.getDimensionPixelSize(R.dimen.individual_bubble_size); + mStatusBarHeight = + res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); + mPipDismissHeight = res.getDimensionPixelSize(R.dimen.pip_dismiss_gradient_height); + + // Ensure that all child views are at 1x scale, and visible, in case they were animating + // in. + mLayout.setVisibility(View.VISIBLE); + animationsForChildrenFromIndex(0 /* startIndex */, (index, animation) -> + animation.scaleX(1f).scaleY(1f).alpha(1f)).startAll(); + } + + @Override Set<DynamicAnimation.ViewProperty> getAnimatedProperties() { return Sets.newHashSet( DynamicAnimation.TRANSLATION_X, @@ -325,14 +348,21 @@ public class ExpandedAnimationController @Override void onChildAdded(View child, int index) { - child.setTranslationX(getXForChildAtIndex(index)); - - animationForChild(child) - .translationY( - getExpandedY() - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR, /* from */ - getExpandedY() /* to */) - .start(); - updateBubblePositions(); + // If a bubble is added while the expand/collapse animations are playing, update the + // animation to include the new bubble. + if (mAnimatingExpand) { + startOrUpdateExpandAnimation(); + } else if (mAnimatingCollapse) { + startOrUpdateCollapseAnimation(); + } else { + child.setTranslationX(getXForChildAtIndex(index)); + animationForChild(child) + .translationY( + getExpandedY() - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR, /* from */ + getExpandedY() /* to */) + .start(); + updateBubblePositions(); + } } @Override @@ -357,19 +387,15 @@ public class ExpandedAnimationController } @Override - protected void setChildVisibility(View child, int index, int visibility) { - if (visibility == View.VISIBLE) { - // Set alpha to 0 but then become visible immediately so the animation is visible. - child.setAlpha(0f); - child.setVisibility(View.VISIBLE); - } - - animationForChild(child) - .alpha(visibility == View.GONE ? 0f : 1f) - .start(() -> super.setChildVisibility(child, index, visibility) /* after */); + void onChildReordered(View child, int oldIndex, int newIndex) { + updateBubblePositions(); } private void updateBubblePositions() { + if (mAnimatingExpand || mAnimatingCollapse) { + return; + } + for (int i = 0; i < mLayout.getChildCount(); i++) { final View bubble = mLayout.getChildAt(i); @@ -378,6 +404,7 @@ public class ExpandedAnimationController if (bubble.equals(mBubbleDraggingOut)) { return; } + animationForChild(bubble) .translationX(getBubbleLeft(i)) .start(); @@ -403,10 +430,7 @@ public class ExpandedAnimationController return 0; } int bubbleCount = mLayout.getChildCount(); - if (bubbleCount > mBubblesMaxRendered) { - // Only shown bubbles are relevant for calculating position. - bubbleCount = mBubblesMaxRendered; - } + // Width calculations. double bubble = bubbleCount * mBubbleSizePx; float gap = (bubbleCount - 1) * mBubblePaddingPx; diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java index 997d2c4627d8..3a3339249d5b 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java @@ -17,10 +17,12 @@ package com.android.systemui.bubbles.animation; import android.content.Context; +import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; +import androidx.annotation.Nullable; import androidx.dynamicanimation.animation.DynamicAnimation; import androidx.dynamicanimation.animation.SpringAnimation; import androidx.dynamicanimation.animation.SpringForce; @@ -137,12 +139,33 @@ public class PhysicsAnimationLayout extends FrameLayout { */ abstract void onChildRemoved(View child, int index, Runnable finishRemoval); + /** Called when a child view has been reordered in the view hierachy. */ + abstract void onChildReordered(View child, int oldIndex, int newIndex); + + /** + * Called when the controller is set as the active animation controller for the given + * layout. Once active, the controller can start animations using the animator instances + * returned by {@link #animationForChild}. + * + * While all animations started by the previous controller will be cancelled, the new + * controller should not make any assumptions about the state of the layout or its children. + * Their translation, alpha, scale, etc. values may have been changed by the previous + * controller and should be reset here if relevant. + */ + abstract void onActiveControllerForLayout(PhysicsAnimationLayout layout); + protected PhysicsAnimationLayout mLayout; PhysicsAnimationController() { } + /** Whether this controller is the currently active controller for its associated layout. */ + protected boolean isActiveController() { + return this == mLayout.mController; + } + protected void setLayout(PhysicsAnimationLayout layout) { this.mLayout = layout; + onActiveControllerForLayout(layout); } protected PhysicsAnimationLayout getLayout() { @@ -150,15 +173,6 @@ public class PhysicsAnimationLayout extends FrameLayout { } /** - * Sets the child's visibility when it moves beyond or within the limits set by a call to - * {@link PhysicsAnimationLayout#setMaxRenderedChildren}. This can be overridden to animate - * this transition. - */ - protected void setChildVisibility(View child, int index, int visibility) { - child.setVisibility(visibility); - } - - /** * Returns a {@link PhysicsPropertyAnimator} instance for the given child view. */ protected PhysicsPropertyAnimator animationForChild(View child) { @@ -170,6 +184,9 @@ public class PhysicsAnimationLayout extends FrameLayout { child.setTag(R.id.physics_animator_tag, animator); } + animator.clearAnimator(); + animator.setAssociatedController(this); + return animator; } @@ -235,32 +252,17 @@ public class PhysicsAnimationLayout extends FrameLayout { new HashMap<>(); /** The currently active animation controller. */ - private PhysicsAnimationController mController; - - /** - * The maximum number of children to render and animate at a time. See - * {@link #setMaxRenderedChildren}. - */ - private int mMaxRenderedChildren = 5; + @Nullable protected PhysicsAnimationController mController; public PhysicsAnimationLayout(Context context) { super(context); } /** - * The maximum number of children to render and animate at a time. Any child views added beyond - * this limit will be set to {@link View#GONE}. If any animations attempt to run on the view, - * the corresponding property will be set with no animation. - */ - public void setMaxRenderedChildren(int max) { - this.mMaxRenderedChildren = max; - } - - /** * Sets the animation controller and constructs or reconfigures the layout's physics animations * to meet the controller's specifications. */ - public void setController(PhysicsAnimationController controller) { + public void setActiveController(PhysicsAnimationController controller) { cancelAllAnimations(); mEndActionForProperty.clear(); @@ -312,42 +314,11 @@ public class PhysicsAnimationLayout extends FrameLayout { @Override public void addView(View child, int index, ViewGroup.LayoutParams params) { - super.addView(child, index, params); - - // Set up animations for the new view, if the controller is set. If it isn't set, we'll be - // setting up animations for all children when setController is called. - if (mController != null) { - for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) { - setUpAnimationForChild(property, child, index); - } - - mController.onChildAdded(child, index); - } - - setChildrenVisibility(); + addViewInternal(child, index, params, false /* isReorder */); } @Override public void removeView(View view) { - removeViewAndThen(view, /* callback */ null); - } - - @Override - public void removeViewAt(int index) { - removeView(getChildAt(index)); - } - - /** Immediately moves the view from wherever it currently is, to the given index. */ - public void moveViewTo(View view, int index) { - super.removeView(view); - addView(view, index); - } - - /** - * Let the controller know that this view should be removed, and then call the callback once the - * controller has finished any removal animations and the view has actually been removed. - */ - public void removeViewAndThen(View view, Runnable callback) { if (mController != null) { final int index = indexOfChild(view); @@ -355,8 +326,6 @@ public class PhysicsAnimationLayout extends FrameLayout { super.removeView(view); addTransientView(view, index); - setChildrenVisibility(); - // Tell the controller to animate this view out, and call the callback when it's // finished. mController.onChildRemoved(view, index, () -> { @@ -364,19 +333,28 @@ public class PhysicsAnimationLayout extends FrameLayout { // any are still running and then remove it. cancelAnimationsOnView(view); removeTransientView(view); - - if (callback != null) { - callback.run(); - } }); } else { // Without a controller, nobody will animate this view out, so it gets an unceremonious // departure. super.removeView(view); + } + } - if (callback != null) { - callback.run(); - } + @Override + public void removeViewAt(int index) { + removeView(getChildAt(index)); + } + + /** Immediately re-orders the view to the given index. */ + public void reorderView(View view, int index) { + final int oldIndex = indexOfChild(view); + + super.removeView(view); + addViewInternal(view, index, view.getLayoutParams(), true /* isReorder */); + + if (mController != null) { + mController.onChildReordered(view, oldIndex, index); } } @@ -427,6 +405,10 @@ public class PhysicsAnimationLayout extends FrameLayout { } } + protected boolean isActiveController(PhysicsAnimationController controller) { + return mController == controller; + } + /** Whether the first child would be left of center if translated to the given x value. */ protected boolean isFirstChildXLeftOfCenter(float x) { if (getChildCount() > 0) { @@ -454,6 +436,26 @@ public class PhysicsAnimationLayout extends FrameLayout { } /** + * Adds a view to the layout. If this addition is not the result of a call to + * {@link #reorderView}, this will also notify the controller via + * {@link PhysicsAnimationController#onChildAdded} and set up animations for the view. + */ + private void addViewInternal( + View child, int index, ViewGroup.LayoutParams params, boolean isReorder) { + super.addView(child, index, params); + + // Set up animations for the new view, if the controller is set. If it isn't set, we'll be + // setting up animations for all children when setActiveController is called. + if (mController != null && !isReorder) { + for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) { + setUpAnimationForChild(property, child, index); + } + + mController.onChildAdded(child, index); + } + } + + /** * Retrieves the animation of the given property from the view at the given index via the view * tag system. */ @@ -481,33 +483,16 @@ public class PhysicsAnimationLayout extends FrameLayout { SpringAnimation newAnim = new SpringAnimation(child, property); newAnim.addUpdateListener((animation, value, velocity) -> { final int indexOfChild = indexOfChild(child); - final int nextAnimInChain = - mController.getNextAnimationInChain(property, indexOfChild); + final int nextAnimInChain = mController.getNextAnimationInChain(property, indexOfChild); if (nextAnimInChain == PhysicsAnimationController.NONE || indexOfChild < 0) { return; } - final int animIndex = indexOfChild(child); - final float offset = - mController.getOffsetForChainedPropertyAnimation(property); - - // If this property's animations should be chained, then check to see if there is a - // subsequent animation within the rendering limit, and if so, tell it to animate to - // this animation's new value (plus the offset). - if (nextAnimInChain < Math.min(getChildCount(), mMaxRenderedChildren)) { - getAnimationAtIndex(property, animIndex + 1) + final float offset = mController.getOffsetForChainedPropertyAnimation(property); + if (nextAnimInChain < getChildCount()) { + getAnimationAtIndex(property, nextAnimInChain) .animateToFinalPosition(value + offset); - } else if (nextAnimInChain < getChildCount()) { - // If the next child view is not rendered, update the property directly without - // animating it, so that the view is still in the correct state if it later - // becomes visible. - for (int i = nextAnimInChain; i < getChildCount(); i++) { - // 'value' here is the value of the last child within the rendering limit, - // not the first child's value - so we want to subtract the last child's - // index when calculating the offset. - property.setValue(getChildAt(i), value + offset * (i - animIndex)); - } } }); @@ -516,22 +501,6 @@ public class PhysicsAnimationLayout extends FrameLayout { child.setTag(getTagIdForProperty(property), newAnim); } - /** Hides children beyond the max rendering count. */ - private void setChildrenVisibility() { - for (int i = 0; i < getChildCount(); i++) { - final int targetVisibility = i < mMaxRenderedChildren ? View.VISIBLE : View.GONE; - final View targetView = getChildAt(i); - - if (targetView.getVisibility() != targetVisibility) { - if (mController != null) { - mController.setChildVisibility(targetView, i, targetVisibility); - } else { - targetView.setVisibility(targetVisibility); - } - } - } - } - /** Return a stable ID to use as a tag key for the given property's animations. */ private int getTagIdForProperty(DynamicAnimation.ViewProperty property) { if (property.equals(DynamicAnimation.TRANSLATION_X)) { @@ -592,7 +561,7 @@ public class PhysicsAnimationLayout extends FrameLayout { private View mView; /** Start velocity to use for all property animations. */ - private float mDefaultStartVelocity = 0f; + private float mDefaultStartVelocity = -Float.MAX_VALUE; /** Start delay to use when start is called. */ private long mStartDelay = 0; @@ -625,6 +594,15 @@ public class PhysicsAnimationLayout extends FrameLayout { */ private Map<DynamicAnimation.ViewProperty, Float> mAnimatedProperties = new HashMap<>(); + /** + * All of the initial property values that have been set. These values will be instantly set + * when {@link #start} is called, just before the animation begins. + */ + private Map<DynamicAnimation.ViewProperty, Float> mInitialPropertyValues = new HashMap<>(); + + /** The animation controller that last retrieved this animator instance. */ + private PhysicsAnimationController mAssociatedController; + protected PhysicsPropertyAnimator(View view) { this.mView = view; } @@ -644,7 +622,7 @@ public class PhysicsAnimationLayout extends FrameLayout { /** Set the view's alpha value to 'from', then animate it to the given value. */ public PhysicsPropertyAnimator alpha(float from, float to, Runnable... endActions) { - mView.setAlpha(from); + mInitialPropertyValues.put(DynamicAnimation.ALPHA, from); return alpha(to, endActions); } @@ -656,7 +634,7 @@ public class PhysicsAnimationLayout extends FrameLayout { /** Set the view's translationX value to 'from', then animate it to the given value. */ public PhysicsPropertyAnimator translationX( float from, float to, Runnable... endActions) { - mView.setTranslationX(from); + mInitialPropertyValues.put(DynamicAnimation.TRANSLATION_X, from); return translationX(to, endActions); } @@ -668,7 +646,7 @@ public class PhysicsAnimationLayout extends FrameLayout { /** Set the view's translationY value to 'from', then animate it to the given value. */ public PhysicsPropertyAnimator translationY( float from, float to, Runnable... endActions) { - mView.setTranslationY(from); + mInitialPropertyValues.put(DynamicAnimation.TRANSLATION_Y, from); return translationY(to, endActions); } @@ -690,7 +668,7 @@ public class PhysicsAnimationLayout extends FrameLayout { /** Set the view's scaleX value to 'from', then animate it to the given value. */ public PhysicsPropertyAnimator scaleX(float from, float to, Runnable... endActions) { - mView.setScaleX(from); + mInitialPropertyValues.put(DynamicAnimation.SCALE_X, from); return scaleX(to, endActions); } @@ -701,7 +679,7 @@ public class PhysicsAnimationLayout extends FrameLayout { /** Set the view's scaleY value to 'from', then animate it to the given value. */ public PhysicsPropertyAnimator scaleY(float from, float to, Runnable... endActions) { - mView.setScaleY(from); + mInitialPropertyValues.put(DynamicAnimation.SCALE_Y, from); return scaleY(to, endActions); } @@ -750,6 +728,13 @@ public class PhysicsAnimationLayout extends FrameLayout { * animated property on every child (including chained animations) have ended. */ public void start(Runnable... after) { + if (!isActiveController(mAssociatedController)) { + Log.w(TAG, "Only the active animation controller is allowed to start animations. " + + "Use PhysicsAnimationLayout#setActiveController to set the active " + + "animation controller."); + return; + } + final Set<DynamicAnimation.ViewProperty> properties = getAnimatedProperties(); // If there are end actions, set an end listener on the layout for all the properties @@ -791,6 +776,10 @@ public class PhysicsAnimationLayout extends FrameLayout { // Actually start the animations. for (DynamicAnimation.ViewProperty property : properties) { + if (mInitialPropertyValues.containsKey(property)) { + property.setValue(mView, mInitialPropertyValues.get(property)); + } + final SpringForce defaultSpringForce = mController.getSpringForce(property, mView); animateValueForChild( property, @@ -803,14 +792,7 @@ public class PhysicsAnimationLayout extends FrameLayout { mEndActionsForProperty.get(property)); } - // Clear out the animator. - mAnimatedProperties.clear(); - mPositionStartVelocities.clear(); - mDefaultStartVelocity = 0; - mStartDelay = 0; - mStiffness = -1; - mDampingRatio = -1; - mEndActionsForProperty.clear(); + clearAnimator(); } /** Returns the set of properties that will animate once {@link #start} is called. */ @@ -847,20 +829,50 @@ public class PhysicsAnimationLayout extends FrameLayout { }); } - animation.getSpring().setStiffness(stiffness); - animation.getSpring().setDampingRatio(dampingRatio); + final SpringForce animationSpring = animation.getSpring(); - if (startVel > 0) { - animation.setStartVelocity(startVel); + if (animationSpring == null) { + return; } + final Runnable configureAndStartAnimation = () -> { + animationSpring.setStiffness(stiffness); + animationSpring.setDampingRatio(dampingRatio); + + if (startVel > -Float.MAX_VALUE) { + animation.setStartVelocity(startVel); + } + + animationSpring.setFinalPosition(value); + animation.start(); + }; + if (startDelay > 0) { - postDelayed(() -> animation.animateToFinalPosition(value), startDelay); + postDelayed(configureAndStartAnimation, startDelay); } else { - animation.animateToFinalPosition(value); + configureAndStartAnimation.run(); } } } + + private void clearAnimator() { + mInitialPropertyValues.clear(); + mAnimatedProperties.clear(); + mPositionStartVelocities.clear(); + mDefaultStartVelocity = -Float.MAX_VALUE; + mStartDelay = 0; + mStiffness = -1; + mDampingRatio = -1; + mEndActionsForProperty.clear(); + } + + /** + * Sets the controller that last retrieved this animator instance, so that we can prevent + * {@link #start} from actually starting animations if called by a non-active controller. + */ + private void setAssociatedController(PhysicsAnimationController controller) { + mAssociatedController = controller; + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java index b9cdc844eef9..ab8752e4195f 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java @@ -154,21 +154,6 @@ public class StackAnimationController extends /** Height of the status bar. */ private float mStatusBarHeight; - @Override - protected void setLayout(PhysicsAnimationLayout layout) { - super.setLayout(layout); - - Resources res = layout.getResources(); - mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset); - mIndividualBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size); - mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding); - mBubbleOffscreen = res.getDimensionPixelSize(R.dimen.bubble_stack_offscreen); - mStackStartingVerticalOffset = - res.getDimensionPixelSize(R.dimen.bubble_stack_starting_offset_y); - mStatusBarHeight = - res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); - } - /** * Instantly move the first bubble to the given point, and animate the rest of the stack behind * it with the 'following' effect. @@ -286,6 +271,8 @@ public class StackAnimationController extends }, DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); + // If we're flinging now, there's no more touch event to catch up to. + mFirstBubbleSpringingToTouch = false; mIsMovingFromFlinging = true; return destinationRelativeX; } @@ -656,19 +643,38 @@ public class StackAnimationController extends if (mLayout.getChildCount() > 0) { animationForChildAtIndex(0).translationX(mStackPosition.x).start(); + } else { + // Set the start position back to the default since we're out of bubbles. New bubbles + // will then animate in from the start position. + mStackPosition = getDefaultStartPosition(); } } + @Override + void onChildReordered(View child, int oldIndex, int newIndex) {} + + @Override + void onActiveControllerForLayout(PhysicsAnimationLayout layout) { + Resources res = layout.getResources(); + mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset); + mIndividualBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size); + mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding); + mBubbleOffscreen = res.getDimensionPixelSize(R.dimen.bubble_stack_offscreen); + mStackStartingVerticalOffset = + res.getDimensionPixelSize(R.dimen.bubble_stack_starting_offset_y); + mStatusBarHeight = + res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); + } + /** Moves the stack, without any animation, to the starting position. */ private void moveStackToStartPosition() { // Post to ensure that the layout's width and height have been calculated. mLayout.setVisibility(View.INVISIBLE); mLayout.post(() -> { + setStackPosition(mRestingStackPosition == null + ? getDefaultStartPosition() + : mRestingStackPosition); mStackMovedToStartPosition = true; - setStackPosition( - mRestingStackPosition == null - ? getDefaultStartPosition() - : mRestingStackPosition); mLayout.setVisibility(View.VISIBLE); // Animate in the top bubble now that we're visible. @@ -707,15 +713,20 @@ public class StackAnimationController extends Log.d(TAG, String.format("Setting position to (%f, %f).", pos.x, pos.y)); mStackPosition.set(pos.x, pos.y); - mLayout.cancelAllAnimations(); - cancelStackPositionAnimations(); - - // Since we're not using the chained animations, apply the offsets manually. - final float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X); - final float yOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_Y); - for (int i = 0; i < mLayout.getChildCount(); i++) { - mLayout.getChildAt(i).setTranslationX(pos.x + (i * xOffset)); - mLayout.getChildAt(i).setTranslationY(pos.y + (i * yOffset)); + // If we're not the active controller, we don't want to physically move the bubble views. + if (isActiveController()) { + mLayout.cancelAllAnimations(); + cancelStackPositionAnimations(); + + // Since we're not using the chained animations, apply the offsets manually. + final float xOffset = getOffsetForChainedPropertyAnimation( + DynamicAnimation.TRANSLATION_X); + final float yOffset = getOffsetForChainedPropertyAnimation( + DynamicAnimation.TRANSLATION_Y); + for (int i = 0; i < mLayout.getChildCount(); i++) { + mLayout.getChildAt(i).setTranslationX(pos.x + (i * xOffset)); + mLayout.getChildAt(i).setTranslationY(pos.y + (i * yOffset)); + } } } @@ -732,6 +743,10 @@ public class StackAnimationController extends /** Animates in the given bubble. */ private void animateInBubble(View child) { + if (!isActiveController()) { + return; + } + child.setTranslationY(mStackPosition.y); float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X); diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index ff16ed0f1477..86472008688f 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -1588,17 +1588,13 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, // Disable rotation suggestions, if enabled setRotationSuggestionsEnabled(false); - FrameLayout panelContainer = new FrameLayout(mContext); + FrameLayout panelContainer = + findViewById(com.android.systemui.R.id.global_actions_panel_container); FrameLayout.LayoutParams panelParams = new FrameLayout.LayoutParams( FrameLayout.LayoutParams.MATCH_PARENT, - FrameLayout.LayoutParams.WRAP_CONTENT); + FrameLayout.LayoutParams.MATCH_PARENT); panelContainer.addView(mPanelController.getPanelContent(), panelParams); - addContentView( - panelContainer, - new ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT)); mBackgroundDrawable = mPanelController.getBackgroundDrawable(); mScrimAlpha = 1f; } @@ -1606,8 +1602,10 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private void initializeLayout() { setContentView(getGlobalActionsLayoutId(mContext)); + fixNavBarClipping(); mGlobalActionsLayout = findViewById(com.android.systemui.R.id.global_actions_view); mGlobalActionsLayout.setOutsideTouchListener(view -> dismiss()); + ((View) mGlobalActionsLayout.getParent()).setOnClickListener(view -> dismiss()); mGlobalActionsLayout.setListViewAccessibilityDelegate(new View.AccessibilityDelegate() { @Override public boolean dispatchPopulateAccessibilityEvent( @@ -1630,6 +1628,15 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, getWindow().setBackgroundDrawable(mBackgroundDrawable); } + private void fixNavBarClipping() { + ViewGroup content = findViewById(android.R.id.content); + content.setClipChildren(false); + content.setClipToPadding(false); + ViewGroup contentParent = (ViewGroup) content.getParent(); + contentParent.setClipChildren(false); + contentParent.setClipToPadding(false); + } + private int getGlobalActionsLayoutId(Context context) { int rotation = RotationUtils.getRotation(context); boolean useGridLayout = isForceGridEnabled(context) diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java index 03165f47c472..e1462d15c887 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java @@ -42,8 +42,6 @@ public class GlobalActionsGridLayout extends GlobalActionsLayout { listView.setReverseSublists(shouldReverseSublists()); listView.setReverseItems(shouldReverseListItems()); listView.setSwapRowsAndColumns(shouldSwapRowsAndColumns()); - - fixNavBarClipping(); } @Override @@ -75,19 +73,6 @@ public class GlobalActionsGridLayout extends GlobalActionsLayout { } } - /** - * Allows the dialog to clip over the navbar, which prevents shadows and animations from being - * cut off. - */ - private void fixNavBarClipping() { - ViewGroup parent = (ViewGroup) this.getParent(); - ViewGroup parentParent = (ViewGroup) parent.getParent(); - parent.setClipChildren(false); - parent.setClipToPadding(false); - parentParent.setClipChildren(false); - parentParent.setClipToPadding(false); - } - @Override protected ListGridLayout getListView() { return (ListGridLayout) super.getListView(); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java index 35b8d203cbe2..d4c73668d0ed 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.NextAlarmController; 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 @@ public class KeyguardSliceProvider extends SliceProvider implements 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 @@ public class KeyguardSliceProvider extends SliceProvider implements 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 @@ public class KeyguardSliceProvider extends SliceProvider implements } 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 @@ public class KeyguardSliceProvider extends SliceProvider implements 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 class KeyguardSliceProvider extends SliceProvider implements 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/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java index 9d5871eb595c..c375574f023d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java @@ -169,6 +169,5 @@ public class NotificationListener extends NotificationListenerWithPlugins { public interface NotificationSettingsListener { default void onStatusBarIconsBehaviorChanged(boolean hideSilentStatusIcons) { } - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java index a5a6d87c2030..1aa6bc9ae5f9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification; import android.annotation.Nullable; +import android.service.notification.NotificationListenerService; +import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.StatusBarNotification; import com.android.internal.statusbar.NotificationVisibility; @@ -98,4 +100,14 @@ public interface NotificationEntryListener { @Nullable NotificationVisibility visibility, boolean removedByUser) { } + + /** + * Called whenever notification ranking changes, in response to + * {@link NotificationListenerService#onNotificationRankingUpdate}. This is called after + * NotificationData has processed the update and notifications have been re-sorted and filtered. + * + * @param rankingMap provides access to ranking information on currently active notifications + */ + default void onNotificationRankingUpdated(RankingMap rankingMap) { + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index 3ac5768f091a..e8388ceded04 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -483,6 +483,10 @@ public class NotificationEntryManager implements } updateNotifications(); + + for (NotificationEntryListener listener : mNotificationEntryListeners) { + listener.onNotificationRankingUpdated(rankingMap); + } } private void updateRankingOfPendingNotifications( 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 c65e90ea7bea..7c193b19654b 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 @@ class NotificationWakeUpCoordinator @Inject constructor( 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 @@ class NotificationWakeUpCoordinator @Inject constructor( // 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/collection/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java index 04ff58b36c94..64b2f048ce2e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java @@ -25,6 +25,7 @@ import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.SnoozeCriterion; import android.service.notification.StatusBarNotification; import android.util.ArrayMap; +import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.Dependency; @@ -69,7 +70,8 @@ public class NotificationData { mHeadsUpManager = headsUpManager; } - private final Comparator<NotificationEntry> mRankingComparator = + @VisibleForTesting + protected final Comparator<NotificationEntry> mRankingComparator = new Comparator<NotificationEntry>() { private final Ranking mRankingA = new Ranking(); private final Ranking mRankingB = new Ranking(); @@ -120,6 +122,8 @@ public class NotificationData { } else if (aSystemMax != bSystemMax) { // Upsort PRIORITY_MAX system notifications return aSystemMax ? -1 : 1; + } else if (a.isHighPriority() != b.isHighPriority()) { + return -1 * Boolean.compare(a.isHighPriority(), b.isHighPriority()); } else if (aRank != bRank) { return aRank - bRank; } else { @@ -231,17 +235,14 @@ public class NotificationData { /** * Returns true if this notification should be displayed in the high-priority notifications - * section (and on the lockscreen and status bar). + * section */ public boolean isHighPriority(StatusBarNotification statusBarNotification) { if (mRankingMap != null) { getRanking(statusBarNotification.getKey(), mTmpRanking); if (mTmpRanking.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT - || isImportantOngoing(statusBarNotification.getNotification()) - || statusBarNotification.getNotification().hasMediaSession() - || hasPerson(statusBarNotification.getNotification()) - || hasStyle(statusBarNotification.getNotification(), - Notification.MessagingStyle.class)) { + || hasHighPriorityCharacteristics( + mTmpRanking.getChannel(), statusBarNotification)) { return true; } if (mGroupManager.isSummaryOfGroup(statusBarNotification)) { @@ -257,6 +258,25 @@ public class NotificationData { return false; } + private boolean hasHighPriorityCharacteristics(NotificationChannel channel, + StatusBarNotification statusBarNotification) { + + if (isImportantOngoing(statusBarNotification.getNotification()) + || statusBarNotification.getNotification().hasMediaSession() + || hasPerson(statusBarNotification.getNotification()) + || hasStyle(statusBarNotification.getNotification(), + Notification.MessagingStyle.class)) { + // Users who have long pressed and demoted to silent should not see the notification + // in the top section + if (channel != null && channel.hasUserSetImportance()) { + return false; + } + return true; + } + + return false; + } + private boolean isImportantOngoing(Notification notification) { return notification.isForegroundService() && mTmpRanking.getImportance() >= NotificationManager.IMPORTANCE_LOW; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ButtonLinearLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ButtonLinearLayout.java new file mode 100644 index 000000000000..94bdd81401bb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ButtonLinearLayout.java @@ -0,0 +1,32 @@ +/* + * 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; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.Button; +import android.widget.LinearLayout; + +public class ButtonLinearLayout extends LinearLayout { + + public ButtonLinearLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public CharSequence getAccessibilityClassName() { + return Button.class.getName(); + } +} 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 dbc9a8b9c21d..d625b318487d 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 @@ public class ExpandableNotificationRow extends ActivatableNotificationView 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 ecbb2161a0cf..d911e1a05029 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.Point; 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 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl 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 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl } 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 91c43a142db5..ee2dacd67f46 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.MediaMetadata; 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.SeekBar; 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 @@ public class NotificationMediaTemplateViewWrapper extends NotificationTemplateVi 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 class NotificationMediaTemplateViewWrapper extends NotificationTemplateVi public void onStopTrackingTouch(SeekBar seekBar) { if (mMediaController != null && canSeekMedia()) { mMediaController.getTransportControls().seekTo(mSeekBar.getProgress()); + mMetricsLogger.write(newLog(MetricsEvent.TYPE_UPDATE)); } } }; @@ -93,7 +101,8 @@ public class NotificationMediaTemplateViewWrapper extends NotificationTemplateVi // 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 @@ public class NotificationMediaTemplateViewWrapper extends NotificationTemplateVi super(ctx, view, row); mContext = ctx; mMediaManager = Dependency.get(NotificationMediaManager.class); + mMetricsLogger = Dependency.get(MetricsLogger.class); } private void resolveViews() { @@ -121,11 +131,13 @@ public class NotificationMediaTemplateViewWrapper extends NotificationTemplateVi } // 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 @@ public class NotificationMediaTemplateViewWrapper extends NotificationTemplateVi 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 @@ public class NotificationMediaTemplateViewWrapper extends NotificationTemplateVi 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 @@ public class NotificationMediaTemplateViewWrapper extends NotificationTemplateVi 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 @@ public class NotificationMediaTemplateViewWrapper extends NotificationTemplateVi 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 @@ public class NotificationMediaTemplateViewWrapper extends NotificationTemplateVi 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 class NotificationMediaTemplateViewWrapper extends NotificationTemplateVi 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 e5fbf6396667..78dc9a0a731c 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 class AmbientState { } 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 dccf404f0483..a517e760c8b4 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd view.setTranslationY(wakeUplocation); } } + mDimmedNeedsAnimation = true; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java index f2218651d7c7..05a86fa9d7ea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java @@ -33,13 +33,14 @@ import android.os.RemoteException; import android.os.SystemClock; import android.util.Log; import android.util.MathUtils; -import android.view.Choreographer; import android.view.Gravity; import android.view.IPinnedStackController; import android.view.IPinnedStackListener; import android.view.ISystemGestureExclusionListener; +import android.view.InputChannel; import android.view.InputDevice; import android.view.InputEvent; +import android.view.InputEventReceiver; import android.view.InputMonitor; import android.view.KeyCharacterMap; import android.view.KeyEvent; @@ -53,7 +54,6 @@ import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.bubbles.BubbleController; import com.android.systemui.recents.OverviewProxyService; -import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.WindowManagerWrapper; @@ -165,12 +165,14 @@ public class EdgeBackGestureHandler implements DisplayListener { mEdgeWidth = res.getDimensionPixelSize( com.android.internal.R.dimen.config_backGestureInset); - mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + // Reduce the default touch slop to ensure that we can intercept the gesture + // before the app starts to react to it. + // TODO(b/130352502) Tune this value and extract into a constant + mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop() * 0.75f; mLongPressTimeout = ViewConfiguration.getLongPressTimeout(); mNavBarHeight = res.getDimensionPixelSize(R.dimen.navigation_bar_frame_height); - mMinArrowPosition = res.getDimensionPixelSize( - R.dimen.navigation_edge_arrow_min_y); + mMinArrowPosition = res.getDimensionPixelSize(R.dimen.navigation_edge_arrow_min_y); mFingerOffset = res.getDimensionPixelSize(R.dimen.navigation_edge_finger_offset); } @@ -250,9 +252,8 @@ public class EdgeBackGestureHandler implements DisplayListener { // Register input event receiver mInputMonitor = InputManager.getInstance().monitorGestureInput( "edge-swipe", mDisplayId); - mInputEventReceiver = new InputEventReceiver(mInputMonitor.getInputChannel(), - Looper.getMainLooper(), Choreographer.getMainThreadInstance(), - this::onInputEvent); + mInputEventReceiver = new SysUiInputEventReceiver( + mInputMonitor.getInputChannel(), Looper.getMainLooper()); // Add a nav bar panel window mEdgePanel = new NavigationBarEdgePanel(mContext); @@ -440,4 +441,15 @@ public class EdgeBackGestureHandler implements DisplayListener { } InputManager.getInstance().injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); } + + class SysUiInputEventReceiver extends InputEventReceiver { + SysUiInputEventReceiver(InputChannel channel, Looper looper) { + super(channel, looper); + } + + public void onInputEvent(InputEvent event) { + EdgeBackGestureHandler.this.onInputEvent(event); + finishInputEvent(event, true); + } + } } 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 215aa77920d9..a79b6251a3d0 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 @@ public class FloatingRotationButton implements RotationButton { 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 @@ public class FloatingRotationButton implements RotationButton { 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 @@ public class FloatingRotationButton implements RotationButton { 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 @@ public class FloatingRotationButton implements RotationButton { @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/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java index b7a154d67c10..a0cda693822f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -22,8 +22,6 @@ import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Rect; -import android.graphics.Region; -import android.graphics.Region.Op; import android.util.Log; import android.util.Pools; import android.view.DisplayCutout; @@ -78,6 +76,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, private int[] mTmpTwoArray = new int[2]; private boolean mHeadsUpGoingAway; private int mStatusBarState; + private Rect mTouchableRegion = new Rect(); private AnimationStateHandler mAnimationStateHandler; @@ -297,10 +296,13 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, @Nullable public void updateTouchableRegion(ViewTreeObserver.InternalInsetsInfo info) { info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); + info.touchableRegion.set(calculateTouchableRegion()); + } + public Rect calculateTouchableRegion() { if (!hasPinnedHeadsUp()) { - info.touchableRegion.set(0, 0, mStatusBarWindowView.getWidth(), mStatusBarHeight); - updateRegionForNotch(info.touchableRegion); + mTouchableRegion.set(0, 0, mStatusBarWindowView.getWidth(), mStatusBarHeight); + updateRegionForNotch(mTouchableRegion); } else { NotificationEntry topEntry = getTopEntry(); if (topEntry.isChildInGroup()) { @@ -315,11 +317,12 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, int minX = mTmpTwoArray[0]; int maxX = mTmpTwoArray[0] + topRow.getWidth(); int height = topRow.getIntrinsicHeight(); - info.touchableRegion.set(minX, 0, maxX, mHeadsUpInset + height); + mTouchableRegion.set(minX, 0, maxX, mHeadsUpInset + height); } + return mTouchableRegion; } - private void updateRegionForNotch(Region region) { + private void updateRegionForNotch(Rect region) { DisplayCutout cutout = mStatusBarWindowView.getRootWindowInsets().getDisplayCutout(); if (cutout == null) { return; @@ -330,7 +333,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, Rect bounds = new Rect(); ScreenDecorations.DisplayCutoutView.boundsFromDirection(cutout, Gravity.TOP, bounds); bounds.offset(0, mDisplayCutoutTouchableRegionSize); - region.op(bounds, Op.UNION); + region.union(bounds); } @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 337c6b167cdf..d94a33556a43 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.logging.nano.MetricsProto.MetricsEvent; 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 class NavigationBarFragment extends LifecycleFragment implements Callback public int mDisplayId; private boolean mIsOnDefaultDisplay; public boolean mHomeBlockedThisTouch; + private ScreenDecorations mScreenDecorations; private Handler mHandler = Dependency.get(Dependency.MAIN_HANDLER); @@ -348,12 +350,17 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback 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 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback 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 8a28c6fbff29..2b5a28e60082 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.internal.statusbar.IStatusBarService; 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 @@ public final class NavigationBarTransitions extends BarTransitions implements 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 @@ public final class NavigationBarTransitions extends BarTransitions implements 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 @@ public final class NavigationBarTransitions extends BarTransitions implements 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 final class NavigationBarTransitions extends BarTransitions implements 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 7682e8a594ce..8a895e187b31 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 @@ public class NavigationBarView extends FrameLayout implements return mTintController; } - public BarTransitions getBarTransitions() { + public NavigationBarTransitions getBarTransitions() { return mBarTransitions; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index 7623dee10c39..92aa884b14d2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -100,6 +100,7 @@ import com.android.systemui.util.InjectionInflationController; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.function.Consumer; @@ -141,6 +142,7 @@ public class NotificationPanelView extends PanelView implements private static final String COUNTER_PANEL_OPEN_PEEK = "panel_open_peek"; private static final Rect mDummyDirtyRect = new Rect(0, 0, 1, 1); + private static final Rect mEmptyRect = new Rect(); private static final AnimationProperties CLOCK_ANIMATION_PROPERTIES = new AnimationProperties() .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); @@ -596,6 +598,25 @@ public class NotificationPanelView extends PanelView implements mQs.setHeightOverride(mQs.getDesiredHeight()); } updateMaxHeadsUpTranslation(); + updateGestureExclusionRect(); + } + + private void updateGestureExclusionRect() { + Rect exclusionRect = calculateGestureExclusionRect(); + setSystemGestureExclusionRects(exclusionRect.isEmpty() + ? Collections.EMPTY_LIST + : Collections.singletonList(exclusionRect)); + } + + private Rect calculateGestureExclusionRect() { + Rect exclusionRect = null; + if (isFullyCollapsed()) { + // Note: The heads up manager also calculates the non-pinned touchable region + exclusionRect = mHeadsUpManager.calculateTouchableRegion(); + } + return exclusionRect != null + ? exclusionRect + : mEmptyRect; } private void setIsFullWidth(boolean isFullWidth) { @@ -1798,6 +1819,7 @@ public class NotificationPanelView extends PanelView implements updateHeader(); updateNotificationTranslucency(); updatePanelExpanded(); + updateGestureExclusionRect(); if (DEBUG) { invalidate(); } @@ -2568,6 +2590,7 @@ public class NotificationPanelView extends PanelView implements mNotificationStackScroller.runAfterAnimationFinished( mHeadsUpExistenceChangedRunnable); } + updateGestureExclusionRect(); } public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) { @@ -2992,6 +3015,7 @@ public class NotificationPanelView extends PanelView implements @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { super.dump(fd, pw, args); + pw.println(" gestureExclusionRect: " + calculateGestureExclusionRect()); if (mKeyguardStatusBar != null) { mKeyguardStatusBar.dump(fd, pw, args); } 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 f1460a62903d..b117dec44cb4 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 @@ public class RotationContextButton extends ContextualButton implements @Override public void onDestroy() { - mRotationButtonController.cleanUp(); + if (mRotationButtonController != null) { + mRotationButtonController.cleanUp(); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java index c08390fa53a2..2afe485eff8e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java @@ -38,6 +38,7 @@ import com.android.internal.telephony.TelephonyIntents; import com.android.internal.telephony.cdma.EriInfo; import com.android.settingslib.Utils; import com.android.settingslib.graph.SignalDrawable; +import com.android.settingslib.net.SignalStrengthUtil; import com.android.systemui.R; import com.android.systemui.statusbar.policy.NetworkController.IconState; import com.android.systemui.statusbar.policy.NetworkController.SignalCallback; @@ -248,9 +249,8 @@ public class MobileSignalController extends SignalController< } private void updateInflateSignalStrength() { - mInflateSignalStrengths = SubscriptionManager.getResourcesForSubId(mContext, - mSubscriptionInfo.getSubscriptionId()) - .getBoolean(R.bool.config_inflateSignalStrength); + mInflateSignalStrengths = SignalStrengthUtil.shouldInflateSignalStrength(mContext, + mSubscriptionInfo.getSubscriptionId()); } private int getNumLevels() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index b8a14efa4935..b2972fcd1286 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -1140,7 +1140,8 @@ public class NetworkControllerImpl extends BroadcastReceiver res.getBoolean(com.android.internal.R.bool.config_alwaysUseCdmaRssi); config.hspaDataDistinguishable = res.getBoolean(R.bool.config_hspa_data_distinguishable); - config.inflateSignalStrengths = res.getBoolean(R.bool.config_inflateSignalStrength); + config.inflateSignalStrengths = res.getBoolean( + com.android.internal.R.bool.config_inflateSignalStrength); CarrierConfigManager configMgr = (CarrierConfigManager) context.getSystemService(Context.CARRIER_CONFIG_SERVICE); diff --git a/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java index 13c92b605c03..18f114a71a8d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java @@ -18,21 +18,27 @@ package com.android.systemui.assist; import static org.mockito.AdditionalAnswers.answerVoid; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; +import android.content.ComponentName; import android.os.Handler; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; import androidx.test.filters.SmallTest; +import com.android.internal.app.AssistUtils; import com.android.systemui.ScreenDecorations; import com.android.systemui.SysuiTestCase; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.recents.OverviewProxyService; import org.junit.Before; import org.junit.Test; @@ -46,29 +52,35 @@ import org.mockito.MockitoAnnotations; @RunWithLooper public class AssistHandleBehaviorControllerTest extends SysuiTestCase { - private final AssistHandleBehavior mTestBehavior = AssistHandleBehavior.TEST; + private static final ComponentName COMPONENT_NAME = new ComponentName("", ""); private AssistHandleBehaviorController mAssistHandleBehaviorController; @Mock private ScreenDecorations mMockScreenDecorations; + @Mock private AssistUtils mMockAssistUtils; @Mock private Handler mMockHandler; @Mock private AssistHandleBehaviorController.BehaviorController mMockBehaviorController; @Before public void setup() { MockitoAnnotations.initMocks(this); + mDependency.injectMockDependency(StatusBarStateController.class); + mDependency.injectMockDependency(OverviewProxyService.class); doAnswer(answerVoid(Runnable::run)).when(mMockHandler).post(any(Runnable.class)); doAnswer(answerVoid(Runnable::run)).when(mMockHandler) .postDelayed(any(Runnable.class), anyLong()); - mTestBehavior.setTestController(mMockBehaviorController); mAssistHandleBehaviorController = new AssistHandleBehaviorController( - mContext, mMockHandler, () -> mMockScreenDecorations); + mContext, + mMockAssistUtils, + mMockHandler, () -> mMockScreenDecorations, + mMockBehaviorController); } @Test public void hide_hidesHandlesWhenShowing() { // Arrange + when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(COMPONENT_NAME); mAssistHandleBehaviorController.showAndStay(); reset(mMockScreenDecorations); @@ -83,6 +95,7 @@ public class AssistHandleBehaviorControllerTest extends SysuiTestCase { @Test public void hide_doesNothingWhenHiding() { // Arrange + when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(COMPONENT_NAME); mAssistHandleBehaviorController.hide(); reset(mMockScreenDecorations); @@ -96,6 +109,7 @@ public class AssistHandleBehaviorControllerTest extends SysuiTestCase { @Test public void showAndStay_showsHandlesWhenHiding() { // Arrange + when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(COMPONENT_NAME); mAssistHandleBehaviorController.hide(); reset(mMockScreenDecorations); @@ -110,6 +124,7 @@ public class AssistHandleBehaviorControllerTest extends SysuiTestCase { @Test public void showAndStay_doesNothingWhenShowing() { // Arrange + when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(COMPONENT_NAME); mAssistHandleBehaviorController.showAndStay(); reset(mMockScreenDecorations); @@ -121,8 +136,23 @@ public class AssistHandleBehaviorControllerTest extends SysuiTestCase { } @Test + public void showAndStay_doesNothingWhenThereIsNoAssistant() { + // Arrange + when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(null); + mAssistHandleBehaviorController.hide(); + reset(mMockScreenDecorations); + + // Act + mAssistHandleBehaviorController.showAndStay(); + + // Assert + verifyNoMoreInteractions(mMockScreenDecorations); + } + + @Test public void showAndGo_showsThenHidesHandlesWhenHiding() { // Arrange + when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(COMPONENT_NAME); mAssistHandleBehaviorController.hide(); reset(mMockScreenDecorations); @@ -139,6 +169,7 @@ public class AssistHandleBehaviorControllerTest extends SysuiTestCase { @Test public void showAndGo_hidesHandlesAfterTimeoutWhenShowing() { // Arrange + when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(COMPONENT_NAME); mAssistHandleBehaviorController.showAndStay(); reset(mMockScreenDecorations); @@ -153,6 +184,7 @@ public class AssistHandleBehaviorControllerTest extends SysuiTestCase { @Test public void showAndGo_doesNothingIfRecentlyHidden() { // Arrange + when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(COMPONENT_NAME); mAssistHandleBehaviorController.showAndGo(); reset(mMockScreenDecorations); @@ -164,12 +196,27 @@ public class AssistHandleBehaviorControllerTest extends SysuiTestCase { } @Test + public void showAndGo_doesNothingWhenThereIsNoAssistant() { + // Arrange + when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(null); + mAssistHandleBehaviorController.hide(); + reset(mMockScreenDecorations); + + // Act + mAssistHandleBehaviorController.showAndGo(); + + // Assert + verifyNoMoreInteractions(mMockScreenDecorations); + } + + @Test public void setBehavior_activatesTheBehaviorWhenInGesturalMode() { // Arrange + when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(COMPONENT_NAME); mAssistHandleBehaviorController.setInGesturalModeForTest(true); // Act - mAssistHandleBehaviorController.setBehavior(mTestBehavior); + mAssistHandleBehaviorController.setBehavior(AssistHandleBehavior.TEST); // Assert verify(mMockBehaviorController).onModeActivated(mContext, mAssistHandleBehaviorController); @@ -179,8 +226,10 @@ public class AssistHandleBehaviorControllerTest extends SysuiTestCase { @Test public void setBehavior_deactivatesThePreviousBehaviorWhenInGesturalMode() { // Arrange - mAssistHandleBehaviorController.setBehavior(mTestBehavior); + when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(COMPONENT_NAME); + mAssistHandleBehaviorController.setBehavior(AssistHandleBehavior.TEST); mAssistHandleBehaviorController.setInGesturalModeForTest(true); + reset(mMockBehaviorController); // Act mAssistHandleBehaviorController.setBehavior(AssistHandleBehavior.OFF); @@ -193,10 +242,11 @@ public class AssistHandleBehaviorControllerTest extends SysuiTestCase { @Test public void setBehavior_doesNothingWhenNotInGesturalMode() { // Arrange + when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(COMPONENT_NAME); mAssistHandleBehaviorController.setInGesturalModeForTest(false); // Act - mAssistHandleBehaviorController.setBehavior(mTestBehavior); + mAssistHandleBehaviorController.setBehavior(AssistHandleBehavior.TEST); // Assert verifyNoMoreInteractions(mMockBehaviorController); diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java index 756cf3e138f6..b324235106c2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java @@ -60,8 +60,8 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC @Before public void setUp() throws Exception { super.setUp(); - addOneMoreThanRenderLimitBubbles(); - mLayout.setController(mExpandedController); + addOneMoreThanBubbleLimitBubbles(); + mLayout.setActiveController(mExpandedController); Resources res = mLayout.getResources(); mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset); @@ -73,14 +73,14 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC @Test public void testExpansionAndCollapse() throws InterruptedException { Runnable afterExpand = Mockito.mock(Runnable.class); - mExpandedController.expandFromStack(mExpansionPoint, afterExpand); + mExpandedController.expandFromStack(afterExpand); waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); testBubblesInCorrectExpandedPositions(); verify(afterExpand).run(); Runnable afterCollapse = Mockito.mock(Runnable.class); - mExpandedController.collapseBackToStack(afterCollapse); + mExpandedController.collapseBackToStack(mExpansionPoint, afterCollapse); waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); testStackedAtPosition(mExpansionPoint.x, mExpansionPoint.y, -1); @@ -139,7 +139,6 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC assertEquals(500f, draggedBubble.getTranslationY(), 1f); // Snap it back and make sure it made it back correctly. - mExpandedController.prepareForDismissalWithVelocity(draggedBubble, 0f, 0f); mLayout.removeView(draggedBubble); waitForLayoutMessageQueue(); waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); @@ -169,7 +168,7 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC // Dismiss the now-magneted bubble, verify that the callback was called. final Runnable afterDismiss = Mockito.mock(Runnable.class); - mExpandedController.dismissDraggedOutBubble(afterDismiss); + mExpandedController.dismissDraggedOutBubble(draggedOutView, afterDismiss); waitForPropertyAnimations(DynamicAnimation.ALPHA); verify(after).run(); @@ -224,7 +223,7 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC /** Expand the stack and wait for animations to finish. */ private void expand() throws InterruptedException { - mExpandedController.expandFromStack(mExpansionPoint, Mockito.mock(Runnable.class)); + mExpandedController.expandFromStack(Mockito.mock(Runnable.class)); waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y); } @@ -236,26 +235,19 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC assertEquals(x + i * offsetMultiplier * mStackOffset, mLayout.getChildAt(i).getTranslationX(), 2f); assertEquals(y, mLayout.getChildAt(i).getTranslationY(), 2f); - - if (i < mMaxRenderedBubbles) { - assertEquals(1f, mLayout.getChildAt(i).getAlpha(), .01f); - } + assertEquals(1f, mLayout.getChildAt(i).getAlpha(), .01f); } } /** Check that children are in the correct positions for being expanded. */ private void testBubblesInCorrectExpandedPositions() { // Check all the visible bubbles to see if they're in the right place. - for (int i = 0; i < Math.min(mLayout.getChildCount(), mMaxRenderedBubbles); i++) { + for (int i = 0; i < mLayout.getChildCount(); i++) { assertEquals(getBubbleLeft(i), mLayout.getChildAt(i).getTranslationX(), 2f); assertEquals(mExpandedController.getExpandedY(), mLayout.getChildAt(i).getTranslationY(), 2f); - - if (i < mMaxRenderedBubbles) { - assertEquals(1f, mLayout.getChildAt(i).getAlpha(), .01f); - } } } @@ -273,9 +265,7 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC return 0; } int bubbleCount = mLayout.getChildCount(); - if (bubbleCount > mMaxRenderedBubbles) { - bubbleCount = mMaxRenderedBubbles; - } + // Width calculations. double bubble = bubbleCount * mBubbleSize; float gap = (bubbleCount - 1) * mBubblePadding; diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java index eef6ddcf6d6b..f8b32c213109 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java @@ -23,7 +23,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -77,21 +76,9 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { } @Test - public void testRenderVisibility() throws InterruptedException { - mLayout.setController(mTestableController); - addOneMoreThanRenderLimitBubbles(); - - // The last child should be GONE, the rest VISIBLE. - for (int i = 0; i < mMaxRenderedBubbles + 1; i++) { - assertEquals(i == mMaxRenderedBubbles ? View.GONE : View.VISIBLE, - mLayout.getChildAt(i).getVisibility()); - } - } - - @Test public void testHierarchyChanges() throws InterruptedException { - mLayout.setController(mTestableController); - addOneMoreThanRenderLimitBubbles(); + mLayout.setActiveController(mTestableController); + addOneMoreThanBubbleLimitBubbles(); // Make sure the controller was notified of all the views we added. for (View mView : mViews) { @@ -115,8 +102,8 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { @Test public void testUpdateValueNotChained() throws InterruptedException { - mLayout.setController(mTestableController); - addOneMoreThanRenderLimitBubbles(); + mLayout.setActiveController(mTestableController); + addOneMoreThanBubbleLimitBubbles(); // Don't chain any values. mTestableController.setChainedProperties(Sets.newHashSet()); @@ -146,8 +133,8 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { @Test public void testSetEndActions() throws InterruptedException { - mLayout.setController(mTestableController); - addOneMoreThanRenderLimitBubbles(); + mLayout.setActiveController(mTestableController); + addOneMoreThanBubbleLimitBubbles(); mTestableController.setChainedProperties(Sets.newHashSet()); final CountDownLatch xLatch = new CountDownLatch(1); @@ -189,8 +176,8 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { @Test public void testRemoveEndListeners() throws InterruptedException { - mLayout.setController(mTestableController); - addOneMoreThanRenderLimitBubbles(); + mLayout.setActiveController(mTestableController); + addOneMoreThanBubbleLimitBubbles(); mTestableController.setChainedProperties(Sets.newHashSet()); final CountDownLatch xLatch = new CountDownLatch(1); @@ -229,8 +216,8 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { public void testSetController() throws InterruptedException { // Add the bubbles, then set the controller, to make sure that a controller added to an // already-initialized view works correctly. - addOneMoreThanRenderLimitBubbles(); - mLayout.setController(mTestableController); + addOneMoreThanBubbleLimitBubbles(); + mLayout.setActiveController(mTestableController); testChainedTranslationAnimations(); TestableAnimationController secondController = @@ -243,7 +230,7 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { DynamicAnimation.SCALE_X, 10f); secondController.setRemoveImmediately(true); - mLayout.setController(secondController); + mLayout.setActiveController(secondController); mTestableController.animationForChildAtIndex(0) .scaleX(1.5f) .start(); @@ -266,7 +253,7 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { Mockito.verify(secondController, Mockito.atLeastOnce()) .getOffsetForChainedPropertyAnimation(eq(DynamicAnimation.SCALE_X)); - mLayout.setController(mTestableController); + mLayout.setActiveController(mTestableController); mTestableController.animationForChildAtIndex(0) .translationX(100f) .start(); @@ -283,8 +270,8 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { @Test public void testArePropertiesAnimating() throws InterruptedException { - mLayout.setController(mTestableController); - addOneMoreThanRenderLimitBubbles(); + mLayout.setActiveController(mTestableController); + addOneMoreThanBubbleLimitBubbles(); assertFalse(mLayout.arePropertiesAnimating( DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y)); @@ -307,8 +294,8 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { @Test public void testCancelAllAnimations() throws InterruptedException { - mLayout.setController(mTestableController); - addOneMoreThanRenderLimitBubbles(); + mLayout.setActiveController(mTestableController); + addOneMoreThanBubbleLimitBubbles(); mTestableController.animationForChildAtIndex(0) .position(1000, 1000) @@ -321,29 +308,10 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { assertTrue(mViews.get(0).getTranslationY() < 1000); } - @Test - public void testSetChildVisibility() throws InterruptedException { - mLayout.setController(mTestableController); - addOneMoreThanRenderLimitBubbles(); - - // The last view should have been set to GONE by the controller, since we added one more - // than the limit and it got pushed off. None of the first children should have been set - // VISIBLE, since they would have been animated in by onChildAdded. - Mockito.verify(mTestableController).setChildVisibility( - mViews.get(mViews.size() - 1), 5, View.GONE); - Mockito.verify(mTestableController, never()).setChildVisibility( - any(View.class), anyInt(), eq(View.VISIBLE)); - - // Remove the first view, which should cause the last view to become visible again. - mLayout.removeView(mViews.get(0)); - Mockito.verify(mTestableController).setChildVisibility( - mViews.get(mViews.size() - 1), 4, View.VISIBLE); - } - /** Standard test of chained translation animations. */ private void testChainedTranslationAnimations() throws InterruptedException { - mLayout.setController(mTestableController); - addOneMoreThanRenderLimitBubbles(); + mLayout.setActiveController(mTestableController); + addOneMoreThanBubbleLimitBubbles(); assertEquals(0, mLayout.getChildAt(0).getTranslationX(), .1f); assertEquals(0, mLayout.getChildAt(1).getTranslationX(), .1f); @@ -354,11 +322,7 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X); - // Since we enabled chaining, animating the first view to 100 should animate the second to - // 115 (since we set the offset to 15) and the third to 130, etc. Despite the sixth bubble - // not being visible, or animated, make sure that it has the appropriate chained - // translation. - for (int i = 0; i < mMaxRenderedBubbles + 1; i++) { + for (int i = 0; i < mLayout.getChildCount(); i++) { assertEquals( 100 + i * TEST_TRANSLATION_X_OFFSET, mLayout.getChildAt(i).getTranslationX(), .1f); @@ -383,8 +347,8 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { @Test public void testPhysicsAnimator() throws InterruptedException { - mLayout.setController(mTestableController); - addOneMoreThanRenderLimitBubbles(); + mLayout.setActiveController(mTestableController); + addOneMoreThanBubbleLimitBubbles(); Runnable afterAll = Mockito.mock(Runnable.class); Runnable after = Mockito.spy(new Runnable() { @@ -430,9 +394,9 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { // Don't chain since we're going to invoke each animation independently. mTestableController.setChainedProperties(new HashSet<>()); - mLayout.setController(mTestableController); + mLayout.setActiveController(mTestableController); - addOneMoreThanRenderLimitBubbles(); + addOneMoreThanBubbleLimitBubbles(); Runnable allEnd = Mockito.mock(Runnable.class); @@ -452,7 +416,7 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { @Test public void testAnimationsForChildrenFromIndex_noChildren() { - mLayout.setController(mTestableController); + mLayout.setActiveController(mTestableController); final Runnable after = Mockito.mock(Runnable.class); mTestableController @@ -523,8 +487,9 @@ public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase { } @Override - protected void setChildVisibility(View child, int index, int visibility) { - super.setChildVisibility(child, index, visibility); - } + void onChildReordered(View child, int oldIndex, int newIndex) {} + + @Override + void onActiveControllerForLayout(PhysicsAnimationLayout layout) {} } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java index c6acef5d4907..f633f3996d13 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java @@ -56,7 +56,6 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase { Handler mMainThreadHandler; - int mMaxRenderedBubbles; int mSystemWindowInsetSize = 50; int mCutoutInsetSize = 100; @@ -69,6 +68,8 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase { @Mock private DisplayCutout mCutout; + private int mMaxBubbles; + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); @@ -79,7 +80,7 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase { mLayout.setTop(0); mLayout.setBottom(mHeight); - mMaxRenderedBubbles = + mMaxBubbles = getContext().getResources().getInteger(R.integer.bubbles_max_rendered); mMainThreadHandler = new Handler(Looper.getMainLooper()); @@ -96,8 +97,8 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase { } /** Add one extra bubble over the limit, so we can make sure it's gone/chains appropriately. */ - void addOneMoreThanRenderLimitBubbles() throws InterruptedException { - for (int i = 0; i < mMaxRenderedBubbles + 1; i++) { + void addOneMoreThanBubbleLimitBubbles() throws InterruptedException { + for (int i = 0; i < mMaxBubbles + 1; i++) { final View newView = new FrameLayout(mContext); mLayout.addView(newView, 0); mViews.add(0, newView); @@ -138,6 +139,13 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase { } @Override + protected boolean isActiveController(PhysicsAnimationController controller) { + // Return true since otherwise all test controllers will be seen as inactive since they + // are wrapped by MainThreadAnimationControllerWrapper. + return true; + } + + @Override public boolean post(Runnable action) { return mMainThreadHandler.post(action); } @@ -148,9 +156,9 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase { } @Override - public void setController(PhysicsAnimationController controller) { + public void setActiveController(PhysicsAnimationController controller) { runOnMainThreadAndBlock( - () -> super.setController( + () -> super.setActiveController( new MainThreadAnimationControllerWrapper(controller))); } @@ -267,8 +275,15 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase { } @Override - protected void setChildVisibility(View child, int index, int visibility) { - mWrappedController.setChildVisibility(child, index, visibility); + void onChildReordered(View child, int oldIndex, int newIndex) { + runOnMainThreadAndBlock( + () -> mWrappedController.onChildReordered(child, oldIndex, newIndex)); + } + + @Override + void onActiveControllerForLayout(PhysicsAnimationLayout layout) { + runOnMainThreadAndBlock( + () -> mWrappedController.onActiveControllerForLayout(layout)); } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java index 9218a8b93f66..31a7d5a45b68 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java @@ -54,8 +54,8 @@ public class StackAnimationControllerTest extends PhysicsAnimationLayoutTestCase @Before public void setUp() throws Exception { super.setUp(); - mLayout.setController(mStackController); - addOneMoreThanRenderLimitBubbles(); + mLayout.setActiveController(mStackController); + addOneMoreThanBubbleLimitBubbles(); mStackOffset = mLayout.getResources().getDimensionPixelSize(R.dimen.bubble_stack_offset); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsGridLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsGridLayoutTest.java index a396f3e8bf46..ea478597ef77 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsGridLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsGridLayoutTest.java @@ -48,8 +48,9 @@ public class GlobalActionsGridLayoutTest extends SysuiTestCase { @Before public void setUp() throws Exception { - mGridLayout = spy((GlobalActionsGridLayout) - LayoutInflater.from(mContext).inflate(R.layout.global_actions_grid, null)); + mGridLayout = spy(LayoutInflater.from(mContext) + .inflate(R.layout.global_actions_grid, null) + .requireViewById(R.id.global_actions_view)); mListGrid = spy(mGridLayout.getListView()); doReturn(mListGrid).when(mGridLayout).getListView(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/ListGridLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/ListGridLayoutTest.java index 746140fc725d..74e8cc280979 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/ListGridLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/ListGridLayoutTest.java @@ -43,8 +43,9 @@ public class ListGridLayoutTest extends SysuiTestCase { @Before public void setUp() throws Exception { - GlobalActionsGridLayout globalActions = (GlobalActionsGridLayout) - LayoutInflater.from(mContext).inflate(R.layout.global_actions_grid, null); + GlobalActionsGridLayout globalActions = LayoutInflater.from(mContext) + .inflate(R.layout.global_actions_grid, null) + .requireViewById(R.id.global_actions_view); mListGridLayout = globalActions.getListView(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java index 4d593c101706..72f3a62f30a7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java @@ -402,6 +402,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { verify(mRow).setEntry(eq(mEntry)); assertEquals(1, mEntry.systemGeneratedSmartActions.size()); assertEquals("action", mEntry.systemGeneratedSmartActions.get(0).title); + verify(mEntryListener).onNotificationRankingUpdated(mRankingMap); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java index 79a6ac4cb0c7..6e0ddbf0cc46 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java @@ -28,6 +28,7 @@ import static android.app.NotificationManager.IMPORTANCE_MIN; import static com.android.systemui.statusbar.notification.collection.NotificationDataTest.TestableNotificationData.OVERRIDE_CHANNEL; import static com.android.systemui.statusbar.notification.collection.NotificationDataTest.TestableNotificationData.OVERRIDE_IMPORTANCE; +import static com.android.systemui.statusbar.notification.collection.NotificationDataTest.TestableNotificationData.OVERRIDE_RANK; import static com.android.systemui.statusbar.notification.collection.NotificationDataTest.TestableNotificationData.OVERRIDE_VIS_EFFECTS; import static junit.framework.Assert.assertEquals; @@ -61,16 +62,12 @@ import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.util.ArraySet; -import androidx.test.filters.SmallTest; - import com.android.systemui.Dependency; import com.android.systemui.ForegroundServiceController; import com.android.systemui.InitController; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.NotificationTestHelper; -import com.android.systemui.statusbar.notification.collection.NotificationData; import com.android.systemui.statusbar.notification.collection.NotificationData.KeyguardEnvironment; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.ShadeController; @@ -83,7 +80,11 @@ import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; + +import androidx.test.filters.SmallTest; @SmallTest @RunWith(AndroidTestingRunner.class) @@ -137,7 +138,9 @@ public class NotificationDataTest extends SysuiTestCase { @Test public void testChannelSetWhenAdded() { - mNotificationData.rankingOverrides.putParcelable(OVERRIDE_CHANNEL, NOTIFICATION_CHANNEL); + Bundle override = new Bundle(); + override.putParcelable(OVERRIDE_CHANNEL, NOTIFICATION_CHANNEL); + mNotificationData.rankingOverrides.put(mRow.getEntry().key, override); mNotificationData.add(mRow.getEntry()); assertEquals(NOTIFICATION_CHANNEL, mRow.getEntry().channel); } @@ -229,7 +232,9 @@ public class NotificationDataTest extends SysuiTestCase { n.flags = Notification.FLAG_FOREGROUND_SERVICE; NotificationEntry entry = new NotificationEntry(mMockStatusBarNotification); mNotificationData.add(entry); - mNotificationData.rankingOverrides.putInt(OVERRIDE_VIS_EFFECTS, 255); + Bundle override = new Bundle(); + override.putInt(OVERRIDE_VIS_EFFECTS, 255); + mNotificationData.rankingOverrides.put(entry.key, override); assertTrue(entry.isExemptFromDndVisualSuppression()); assertFalse(entry.shouldSuppressAmbient()); @@ -245,7 +250,9 @@ public class NotificationDataTest extends SysuiTestCase { when(mMockStatusBarNotification.getNotification()).thenReturn(n); NotificationEntry entry = new NotificationEntry(mMockStatusBarNotification); mNotificationData.add(entry); - mNotificationData.rankingOverrides.putInt(OVERRIDE_VIS_EFFECTS, 255); + Bundle override = new Bundle(); + override.putInt(OVERRIDE_VIS_EFFECTS, 255); + mNotificationData.rankingOverrides.put(entry.key, override); assertTrue(entry.isExemptFromDndVisualSuppression()); assertFalse(entry.shouldSuppressAmbient()); @@ -257,7 +264,9 @@ public class NotificationDataTest extends SysuiTestCase { NotificationEntry entry = new NotificationEntry(mMockStatusBarNotification); entry.mIsSystemNotification = true; mNotificationData.add(entry); - mNotificationData.rankingOverrides.putInt(OVERRIDE_VIS_EFFECTS, 255); + Bundle override = new Bundle(); + override.putInt(OVERRIDE_VIS_EFFECTS, 255); + mNotificationData.rankingOverrides.put(entry.key, override); assertTrue(entry.isExemptFromDndVisualSuppression()); assertFalse(entry.shouldSuppressAmbient()); @@ -268,8 +277,9 @@ public class NotificationDataTest extends SysuiTestCase { initStatusBarNotification(false); NotificationEntry entry = new NotificationEntry(mMockStatusBarNotification); entry.mIsSystemNotification = true; - mNotificationData.rankingOverrides.putInt(OVERRIDE_VIS_EFFECTS, - NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT); + Bundle override = new Bundle(); + override.putInt(OVERRIDE_VIS_EFFECTS, NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT); + mNotificationData.rankingOverrides.put(entry.key, override); mNotificationData.add(entry); when(mMockStatusBarNotification.getNotification()).thenReturn( @@ -395,11 +405,13 @@ public class NotificationDataTest extends SysuiTestCase { Notification notification = mock(Notification.class); when(notification.isForegroundService()).thenReturn(true); - mNotificationData.rankingOverrides.putInt(OVERRIDE_IMPORTANCE, IMPORTANCE_MIN); - StatusBarNotification sbn = new StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0, notification, mContext.getUser(), "", 0); + Bundle override = new Bundle(); + override.putInt(OVERRIDE_IMPORTANCE, IMPORTANCE_MIN); + mNotificationData.rankingOverrides.put(sbn.getKey(), override); + assertFalse(mNotificationData.isHighPriority(sbn)); } @@ -408,14 +420,114 @@ public class NotificationDataTest extends SysuiTestCase { Notification notification = mock(Notification.class); when(notification.isForegroundService()).thenReturn(true); - mNotificationData.rankingOverrides.putInt(OVERRIDE_IMPORTANCE, IMPORTANCE_LOW); - StatusBarNotification sbn = new StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0, notification, mContext.getUser(), "", 0); + Bundle override = new Bundle(); + override.putInt(OVERRIDE_IMPORTANCE, IMPORTANCE_LOW); + mNotificationData.rankingOverrides.put(sbn.getKey(), override); + assertTrue(mNotificationData.isHighPriority(sbn)); } + @Test + public void userChangeTrumpsHighPriorityCharacteristics() { + Person person = new Person.Builder() + .setName("name") + .setKey("abc") + .setUri("uri") + .setBot(true) + .build(); + + Notification notification = new Notification.Builder(mContext, "test") + .addPerson(person) + .setStyle(new Notification.MessagingStyle("")) + .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true) + .build(); + + StatusBarNotification sbn = new StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0, + notification, mContext.getUser(), "", 0); + + NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_LOW); + channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE); + + Bundle override = new Bundle(); + override.putParcelable(OVERRIDE_CHANNEL, channel); + mNotificationData.rankingOverrides.put(sbn.getKey(), override); + + assertFalse(mNotificationData.isHighPriority(sbn)); + } + + @Test + public void testSort_highPriorityTrumpsNMSRank() { + // NMS rank says A and then B. But A is not high priority and B is, so B should sort in + // front + Notification aN = new Notification.Builder(mContext, "test") + .setStyle(new Notification.MessagingStyle("")) + .build(); + StatusBarNotification aSbn = new StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0, + aN, mContext.getUser(), "", 0); + NotificationEntry a = new NotificationEntry(aSbn); + a.setRow(mock(ExpandableNotificationRow.class)); + a.setIsHighPriority(false); + + Bundle override = new Bundle(); + override.putInt(OVERRIDE_IMPORTANCE, IMPORTANCE_LOW); + override.putInt(OVERRIDE_RANK, 1); + mNotificationData.rankingOverrides.put(a.key, override); + + Notification bN = new Notification.Builder(mContext, "test") + .setStyle(new Notification.MessagingStyle("")) + .build(); + StatusBarNotification bSbn = new StatusBarNotification("pkg2", "pkg2", 0, "tag", 0, 0, + bN, mContext.getUser(), "", 0); + NotificationEntry b = new NotificationEntry(bSbn); + b.setIsHighPriority(true); + b.setRow(mock(ExpandableNotificationRow.class)); + + Bundle bOverride = new Bundle(); + bOverride.putInt(OVERRIDE_IMPORTANCE, IMPORTANCE_LOW); + bOverride.putInt(OVERRIDE_RANK, 2); + mNotificationData.rankingOverrides.put(b.key, bOverride); + + assertEquals(1, mNotificationData.mRankingComparator.compare(a, b)); + } + + @Test + public void testSort_samePriorityUsesNMSRank() { + // NMS rank says A and then B. But A is not high priority and B is, so B should sort in + // front + Notification aN = new Notification.Builder(mContext, "test") + .setStyle(new Notification.MessagingStyle("")) + .build(); + StatusBarNotification aSbn = new StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0, + aN, mContext.getUser(), "", 0); + NotificationEntry a = new NotificationEntry(aSbn); + a.setRow(mock(ExpandableNotificationRow.class)); + a.setIsHighPriority(false); + + Bundle override = new Bundle(); + override.putInt(OVERRIDE_IMPORTANCE, IMPORTANCE_LOW); + override.putInt(OVERRIDE_RANK, 1); + mNotificationData.rankingOverrides.put(a.key, override); + + Notification bN = new Notification.Builder(mContext, "test") + .setStyle(new Notification.MessagingStyle("")) + .build(); + StatusBarNotification bSbn = new StatusBarNotification("pkg2", "pkg2", 0, "tag", 0, 0, + bN, mContext.getUser(), "", 0); + NotificationEntry b = new NotificationEntry(bSbn); + b.setRow(mock(ExpandableNotificationRow.class)); + b.setIsHighPriority(false); + + Bundle bOverride = new Bundle(); + bOverride.putInt(OVERRIDE_IMPORTANCE, IMPORTANCE_LOW); + bOverride.putInt(OVERRIDE_RANK, 2); + mNotificationData.rankingOverrides.put(b.key, bOverride); + + assertEquals(-1, mNotificationData.mRankingComparator.compare(a, b)); + } + private void initStatusBarNotification(boolean allowDuringSetup) { Bundle bundle = new Bundle(); bundle.putBoolean(Notification.EXTRA_ALLOW_DURING_SETUP, allowDuringSetup); @@ -449,7 +561,7 @@ public class NotificationDataTest extends SysuiTestCase { public static final String OVERRIDE_SMART_REPLIES = "sr"; public static final String OVERRIDE_BUBBLE = "cb"; - public Bundle rankingOverrides = new Bundle(); + public Map<String, Bundle> rankingOverrides = new HashMap<>(); @Override protected boolean getRanking(String key, Ranking outRanking) { @@ -475,40 +587,41 @@ public class NotificationDataTest extends SysuiTestCase { currentReplies.addAll(outRanking.getSmartReplies()); } - outRanking.populate(key, - rankingOverrides.getInt(OVERRIDE_RANK, outRanking.getRank()), - rankingOverrides.getBoolean(OVERRIDE_DND, - outRanking.matchesInterruptionFilter()), - rankingOverrides.getInt(OVERRIDE_VIS_OVERRIDE, - outRanking.getVisibilityOverride()), - rankingOverrides.getInt(OVERRIDE_VIS_EFFECTS, - outRanking.getSuppressedVisualEffects()), - rankingOverrides.getInt(OVERRIDE_IMPORTANCE, outRanking.getImportance()), - rankingOverrides.getCharSequence(OVERRIDE_IMP_EXP, - outRanking.getImportanceExplanation()), - rankingOverrides.getString(OVERRIDE_GROUP, outRanking.getOverrideGroupKey()), - rankingOverrides.containsKey(OVERRIDE_CHANNEL) - ? (NotificationChannel) rankingOverrides.getParcelable(OVERRIDE_CHANNEL) - : outRanking.getChannel(), - rankingOverrides.containsKey(OVERRIDE_PEOPLE) - ? rankingOverrides.getStringArrayList(OVERRIDE_PEOPLE) - : currentAdditionalPeople, - rankingOverrides.containsKey(OVERRIDE_SNOOZE_CRITERIA) - ? rankingOverrides.getParcelableArrayList(OVERRIDE_SNOOZE_CRITERIA) - : currentSnooze, - rankingOverrides.getBoolean(OVERRIDE_BADGE, outRanking.canShowBadge()), - rankingOverrides.getInt(OVERRIDE_USER_SENTIMENT, outRanking.getUserSentiment()), - rankingOverrides.getBoolean(OVERRIDE_HIDDEN, outRanking.isSuspended()), - rankingOverrides.getLong(OVERRIDE_LAST_ALERTED, - outRanking.getLastAudiblyAlertedMillis()), - rankingOverrides.getBoolean(OVERRIDE_NOISY, outRanking.isNoisy()), - rankingOverrides.containsKey(OVERRIDE_SMART_ACTIONS) - ? rankingOverrides.getParcelableArrayList(OVERRIDE_SMART_ACTIONS) - : currentActions, - rankingOverrides.containsKey(OVERRIDE_SMART_REPLIES) - ? rankingOverrides.getCharSequenceArrayList(OVERRIDE_SMART_REPLIES) - : currentReplies, - rankingOverrides.getBoolean(OVERRIDE_BUBBLE, outRanking.canBubble())); + if (rankingOverrides.get(key) != null) { + Bundle overrides = rankingOverrides.get(key); + outRanking.populate(key, + overrides.getInt(OVERRIDE_RANK, outRanking.getRank()), + overrides.getBoolean(OVERRIDE_DND, outRanking.matchesInterruptionFilter()), + overrides.getInt(OVERRIDE_VIS_OVERRIDE, outRanking.getVisibilityOverride()), + overrides.getInt(OVERRIDE_VIS_EFFECTS, + outRanking.getSuppressedVisualEffects()), + overrides.getInt(OVERRIDE_IMPORTANCE, outRanking.getImportance()), + overrides.getCharSequence(OVERRIDE_IMP_EXP, + outRanking.getImportanceExplanation()), + overrides.getString(OVERRIDE_GROUP, outRanking.getOverrideGroupKey()), + overrides.containsKey(OVERRIDE_CHANNEL) + ? (NotificationChannel) overrides.getParcelable(OVERRIDE_CHANNEL) + : outRanking.getChannel(), + overrides.containsKey(OVERRIDE_PEOPLE) + ? overrides.getStringArrayList(OVERRIDE_PEOPLE) + : currentAdditionalPeople, + overrides.containsKey(OVERRIDE_SNOOZE_CRITERIA) + ? overrides.getParcelableArrayList(OVERRIDE_SNOOZE_CRITERIA) + : currentSnooze, + overrides.getBoolean(OVERRIDE_BADGE, outRanking.canShowBadge()), + overrides.getInt(OVERRIDE_USER_SENTIMENT, outRanking.getUserSentiment()), + overrides.getBoolean(OVERRIDE_HIDDEN, outRanking.isSuspended()), + overrides.getLong(OVERRIDE_LAST_ALERTED, + outRanking.getLastAudiblyAlertedMillis()), + overrides.getBoolean(OVERRIDE_NOISY, outRanking.isNoisy()), + overrides.containsKey(OVERRIDE_SMART_ACTIONS) + ? overrides.getParcelableArrayList(OVERRIDE_SMART_ACTIONS) + : currentActions, + overrides.containsKey(OVERRIDE_SMART_REPLIES) + ? overrides.getCharSequenceArrayList(OVERRIDE_SMART_REPLIES) + : currentReplies, + overrides.getBoolean(OVERRIDE_BUBBLE, outRanking.canBubble())); + } return true; } } 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 43ea92f13c8d..13b9d56e5a14 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 @@ public class NotificationMenuRowTest extends LeakCheckedTest { @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 000000000000..df41a8424fda --- /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 4181d38ad2c5..faf5a9706735 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 @@ public class NavigationBarFragmentTest extends SysuiBaseFragmentTest { 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 30619810a6cb..0f0e6f9fb446 100644 --- a/proto/src/metrics_constants/metrics_constants.proto +++ b/proto/src/metrics_constants/metrics_constants.proto @@ -7379,6 +7379,15 @@ message MetricsEvent { // 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/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java index 430abf5c479d..10ba9a5e98d9 100644 --- a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java +++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java @@ -28,13 +28,17 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ParceledListSlice; import android.content.pm.ServiceInfo; +import android.os.IBinder; import android.os.RemoteException; import android.service.appprediction.AppPredictionService; +import android.util.ArrayMap; import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.server.infra.AbstractPerUserSystemService; +import java.util.ArrayList; + /** * Per-user instance of {@link AppPredictionManagerService}. */ @@ -48,6 +52,17 @@ public class AppPredictionPerUserService extends @GuardedBy("mLock") private RemoteAppPredictionService mRemoteService; + /** + * When {@code true}, remote service died but service state is kept so it's restored after + * the system re-binds to it. + */ + @GuardedBy("mLock") + private boolean mZombie; + + @GuardedBy("mLock") + private final ArrayMap<AppPredictionSessionId, AppPredictionSessionInfo> mSessionInfos = + new ArrayMap<>(); + protected AppPredictionPerUserService(AppPredictionManagerService master, Object lock, int userId) { super(master, lock, userId); @@ -92,6 +107,16 @@ public class AppPredictionPerUserService extends final RemoteAppPredictionService service = getRemoteServiceLocked(); if (service != null) { service.onCreatePredictionSession(context, sessionId); + + mSessionInfos.put(sessionId, new AppPredictionSessionInfo(sessionId, context, () -> { + synchronized (mLock) { + AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId); + if (sessionInfo != null) { + sessionInfo.removeAllCallbacksLocked(); + mSessionInfos.remove(sessionId); + } + } + })); } } @@ -140,6 +165,11 @@ public class AppPredictionPerUserService extends final RemoteAppPredictionService service = getRemoteServiceLocked(); if (service != null) { service.registerPredictionUpdates(sessionId, callback); + + AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId); + if (sessionInfo != null) { + sessionInfo.addCallbackLocked(callback); + } } } @@ -152,6 +182,11 @@ public class AppPredictionPerUserService extends final RemoteAppPredictionService service = getRemoteServiceLocked(); if (service != null) { service.unregisterPredictionUpdates(sessionId, callback); + + AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId); + if (sessionInfo != null) { + sessionInfo.removeCallbackLocked(callback); + } } } @@ -174,6 +209,12 @@ public class AppPredictionPerUserService extends final RemoteAppPredictionService service = getRemoteServiceLocked(); if (service != null) { service.onDestroyPredictionSession(sessionId); + + AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId); + if (sessionInfo != null) { + sessionInfo.removeAllCallbacksLocked(); + mSessionInfos.remove(sessionId); + } } } @@ -182,17 +223,54 @@ public class AppPredictionPerUserService extends if (isDebug()) { Slog.d(TAG, "onFailureOrTimeout(): timed out=" + timedOut); } - // Do nothing, we are just proxying to the prediction service } @Override + public void onConnectedStateChanged(boolean connected) { + if (isDebug()) { + Slog.d(TAG, "onConnectedStateChanged(): connected=" + connected); + } + if (connected) { + synchronized (mLock) { + if (mZombie) { + // Sanity check - shouldn't happen + if (mRemoteService == null) { + Slog.w(TAG, "Cannot resurrect sessions because remote service is null"); + return; + } + mZombie = false; + resurrectSessionsLocked(); + } + } + } + } + + @Override public void onServiceDied(RemoteAppPredictionService service) { if (isDebug()) { - Slog.d(TAG, "onServiceDied():"); + Slog.w(TAG, "onServiceDied(): service=" + service); + } + synchronized (mLock) { + mZombie = true; + } + // Do nothing, eventually the system will bind to the remote service again... + } + + /** + * Called after the remote service connected, it's used to restore state from a 'zombie' + * service (i.e., after it died). + */ + private void resurrectSessionsLocked() { + final int numSessions = mSessionInfos.size(); + if (isDebug()) { + Slog.d(TAG, "Resurrecting remote service (" + mRemoteService + ") on " + + numSessions + " sessions."); } - // Do nothing, we are just proxying to the prediction service + for (AppPredictionSessionInfo sessionInfo : mSessionInfos.values()) { + sessionInfo.resurrectSessionLocked(this); + } } @GuardedBy("mLock") @@ -215,4 +293,57 @@ public class AppPredictionPerUserService extends return mRemoteService; } + + private static final class AppPredictionSessionInfo { + private final AppPredictionSessionId mSessionId; + private final AppPredictionContext mContext; + private final ArrayList<IPredictionCallback> mCallbacks = new ArrayList<>(); + private final IBinder.DeathRecipient mBinderDeathHandler; + + AppPredictionSessionInfo(AppPredictionSessionId id, AppPredictionContext context, + IBinder.DeathRecipient binderDeathHandler) { + mSessionId = id; + mContext = context; + mBinderDeathHandler = binderDeathHandler; + } + + void addCallbackLocked(IPredictionCallback callback) { + if (mBinderDeathHandler != null) { + try { + callback.asBinder().linkToDeath(mBinderDeathHandler, 0); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to link to death: " + e); + } + } + mCallbacks.add(callback); + } + + void removeCallbackLocked(IPredictionCallback callback) { + if (mBinderDeathHandler != null) { + callback.asBinder().unlinkToDeath(mBinderDeathHandler, 0); + } + mCallbacks.remove(callback); + } + + void removeAllCallbacksLocked() { + if (mBinderDeathHandler != null) { + for (IPredictionCallback callback : mCallbacks) { + callback.asBinder().unlinkToDeath(mBinderDeathHandler, 0); + } + } + mCallbacks.clear(); + } + + void resurrectSessionLocked(AppPredictionPerUserService service) { + if (service.isDebug()) { + Slog.d(TAG, "Resurrecting remote service (" + service.getRemoteServiceLocked() + + ") for session Id=" + mSessionId + " and " + + mCallbacks.size() + " callbacks."); + } + service.onCreatePredictionSessionLocked(mContext, mSessionId); + for (IPredictionCallback callback : mCallbacks) { + service.registerPredictionUpdatesLocked(mSessionId, callback); + } + } + } } diff --git a/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java b/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java index 19226be2e1ca..c82e7a012fff 100644 --- a/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java +++ b/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java @@ -42,6 +42,8 @@ public class RemoteAppPredictionService extends private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.SECOND_IN_MILLIS; + private final RemoteAppPredictionServiceCallbacks mCallback; + public RemoteAppPredictionService(Context context, String serviceInterface, ComponentName componentName, int userId, RemoteAppPredictionServiceCallbacks callback, boolean bindInstantServiceAllowed, @@ -50,6 +52,7 @@ public class RemoteAppPredictionService extends context.getMainThreadHandler(), bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0, verbose, /* initialCapacity= */ 1); + mCallback = callback; } @Override @@ -141,5 +144,17 @@ public class RemoteAppPredictionService extends * Notifies a the failure or timeout of a remote call. */ void onFailureOrTimeout(boolean timedOut); + + /** + * Notifies change in connected state of the remote service. + */ + void onConnectedStateChanged(boolean connected); + } + + @Override // from AbstractRemoteService + protected void handleOnConnectedStateChanged(boolean connected) { + if (mCallback != null) { + mCallback.onConnectedStateChanged(connected); + } } } diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java index dfbb55a02ec7..d0edaaaf7619 100644 --- a/services/core/java/com/android/server/AlarmManagerService.java +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -1763,7 +1763,7 @@ class AlarmManagerService extends SystemService { + ", 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 0a1dbff38bf3..45f7360a2570 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -4363,7 +4363,7 @@ public class ConnectivityService extends IConnectivityManager.Stub /** * @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 @@ public class ConnectivityService extends IConnectivityManager.Stub // 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(); + NetworkAgentInfo defaultNai = getDefaultNetwork(); + if (defaultNai != null) { + underlyingNetworks = new Network[] { defaultNai.network }; } - } else if (underlyingNetworks.length > 0) { - LinkProperties linkProperties = getLinkProperties(underlyingNetworks[0]); - if (linkProperties != null) { - info.primaryUnderlyingIface = linkProperties.getInterfaceName(); + } + 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.primaryUnderlyingIface == null ? null : info; + return info.underlyingIfaces == null ? null : info; } /** diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index d52fe8170358..ae04f76b95b4 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -82,6 +82,7 @@ import android.os.UserManager; import android.os.WorkSource; import android.os.WorkSource.WorkChain; import android.provider.Settings; +import android.stats.location.LocationStatsEnums; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; @@ -269,10 +270,14 @@ public class LocationManagerService extends ILocationManager.Stub { @PowerManager.LocationPowerSaveMode private int mBatterySaverMode; + @GuardedBy("mLock") + private final LocationUsageLogger mLocationUsageLogger; + public LocationManagerService(Context context) { super(); mContext = context; mHandler = FgThread.getHandler(); + mLocationUsageLogger = new LocationUsageLogger(); // Let the package manager query which are the default location // providers as they get certain permissions granted by default. @@ -2346,7 +2351,18 @@ public class LocationManagerService extends ILocationManager.Stub { * Method to be called when a record will no longer be used. */ private void disposeLocked(boolean removeReceiver) { - mRequestStatistics.stopRequesting(mReceiver.mCallerIdentity.mPackageName, mProvider); + String packageName = mReceiver.mCallerIdentity.mPackageName; + mRequestStatistics.stopRequesting(packageName, mProvider); + + mLocationUsageLogger.logLocationApiUsage( + LocationStatsEnums.USAGE_ENDED, + LocationStatsEnums.API_REQUEST_LOCATION_UPDATES, + packageName, + mRealRequest, + mReceiver.isListener(), + mReceiver.isPendingIntent(), + /* radius= */ 0, + mActivityManager.getPackageImportance(packageName)); // remove from mRecordsByProvider ArrayList<UpdateRecord> globalRecords = mRecordsByProvider.get(this.mProvider); @@ -2521,6 +2537,13 @@ public class LocationManagerService extends ILocationManager.Stub { "cannot register both listener and intent"); } + mLocationUsageLogger.logLocationApiUsage( + LocationStatsEnums.USAGE_STARTED, + LocationStatsEnums.API_REQUEST_LOCATION_UPDATES, + packageName, request, listener != null, intent != null, + /* radius= */ 0, + mActivityManager.getPackageImportance(packageName)); + Receiver receiver; if (intent != null) { receiver = getReceiverLocked(intent, pid, uid, packageName, workSource, @@ -2813,6 +2836,18 @@ public class LocationManagerService extends ILocationManager.Stub { } long identity = Binder.clearCallingIdentity(); try { + synchronized (mLock) { + mLocationUsageLogger.logLocationApiUsage( + LocationStatsEnums.USAGE_STARTED, + LocationStatsEnums.API_REQUEST_GEOFENCE, + packageName, + request, + /* hasListener= */ false, + intent != null, + geofence.getRadius(), + mActivityManager.getPackageImportance(packageName)); + } + mGeofenceManager.addFence(sanitizedRequest, geofence, intent, allowedResolutionLevel, uid, packageName); @@ -2833,6 +2868,17 @@ public class LocationManagerService extends ILocationManager.Stub { // geo-fence manager uses the public location API, need to clear identity long identity = Binder.clearCallingIdentity(); try { + synchronized (mLock) { + mLocationUsageLogger.logLocationApiUsage( + LocationStatsEnums.USAGE_ENDED, + LocationStatsEnums.API_REQUEST_GEOFENCE, + packageName, + /* LocationRequest= */ null, + /* hasListener= */ false, + intent != null, + geofence.getRadius(), + mActivityManager.getPackageImportance(packageName)); + } mGeofenceManager.removeFence(geofence, intent); } finally { Binder.restoreCallingIdentity(identity); @@ -2916,6 +2962,20 @@ public class LocationManagerService extends ILocationManager.Stub { gnssDataListeners.put(binder, linkedListener); long identity = Binder.clearCallingIdentity(); try { + if (gnssDataProvider == mGnssNavigationMessageProvider + || gnssDataProvider == mGnssStatusProvider) { + mLocationUsageLogger.logLocationApiUsage( + LocationStatsEnums.USAGE_STARTED, + gnssDataProvider == mGnssNavigationMessageProvider + ? LocationStatsEnums.API_ADD_GNSS_MEASUREMENTS_LISTENER + : LocationStatsEnums.API_REGISTER_GNSS_STATUS_CALLBACK, + packageName, + /* LocationRequest= */ null, + /* hasListener= */ true, + /* hasIntent= */ false, + /* radius */ 0, + mActivityManager.getPackageImportance(packageName)); + } if (isThrottlingExemptLocked(callerIdentity) || isImportanceForeground( mActivityManager.getPackageImportance(packageName))) { @@ -2941,6 +3001,26 @@ public class LocationManagerService extends ILocationManager.Stub { if (linkedListener == null) { return; } + long identity = Binder.clearCallingIdentity(); + try { + if (gnssDataProvider == mGnssNavigationMessageProvider + || gnssDataProvider == mGnssStatusProvider) { + mLocationUsageLogger.logLocationApiUsage( + LocationStatsEnums.USAGE_ENDED, + gnssDataProvider == mGnssNavigationMessageProvider + ? LocationStatsEnums.API_ADD_GNSS_MEASUREMENTS_LISTENER + : LocationStatsEnums.API_REGISTER_GNSS_STATUS_CALLBACK, + linkedListener.mCallerIdentity.mPackageName, + /* LocationRequest= */ null, + /* hasListener= */ true, + /* hasIntent= */ false, + /* radius= */ 0, + mActivityManager.getPackageImportance( + linkedListener.mCallerIdentity.mPackageName)); + } + } finally { + Binder.restoreCallingIdentity(identity); + } unlinkFromListenerDeathNotificationLocked(binder, linkedListener); gnssDataProvider.removeListener(listener); } @@ -3026,6 +3106,11 @@ public class LocationManagerService extends ILocationManager.Stub { checkResolutionLevelIsSufficientForProviderUseLocked(getCallerAllowedResolutionLevel(), providerName); + mLocationUsageLogger.logLocationApiUsage( + LocationStatsEnums.USAGE_STARTED, + LocationStatsEnums.API_SEND_EXTRA_COMMAND, + providerName); + // and check for ACCESS_LOCATION_EXTRA_COMMANDS if ((mContext.checkCallingOrSelfPermission(ACCESS_LOCATION_EXTRA_COMMANDS) != PERMISSION_GRANTED)) { @@ -3037,6 +3122,11 @@ public class LocationManagerService extends ILocationManager.Stub { provider.sendExtraCommandLocked(command, extras); } + mLocationUsageLogger.logLocationApiUsage( + LocationStatsEnums.USAGE_ENDED, + LocationStatsEnums.API_SEND_EXTRA_COMMAND, + providerName); + return true; } } diff --git a/services/core/java/com/android/server/LocationUsageLogger.java b/services/core/java/com/android/server/LocationUsageLogger.java new file mode 100644 index 000000000000..c5030351f69d --- /dev/null +++ b/services/core/java/com/android/server/LocationUsageLogger.java @@ -0,0 +1,265 @@ +/* + * 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.server; + +import android.app.ActivityManager; +import android.location.LocationManager; +import android.location.LocationRequest; +import android.os.SystemClock; +import android.stats.location.LocationStatsEnums; +import android.util.Log; +import android.util.StatsLog; + +import java.time.Instant; + +/** + * Logger for Location API usage logging. + */ +class LocationUsageLogger { + private static final String TAG = "LocationUsageLogger"; + private static final boolean D = Log.isLoggable(TAG, Log.DEBUG); + + private static final int ONE_SEC_IN_MILLIS = 1000; + private static final int ONE_MINUTE_IN_MILLIS = 60000; + private static final int ONE_HOUR_IN_MILLIS = 3600000; + + private long mLastApiUsageLogHour = 0; + + private int mApiUsageLogHourlyCount = 0; + + private static final int API_USAGE_LOG_HOURLY_CAP = 60; + + private static int providerNameToStatsdEnum(String provider) { + if (LocationManager.NETWORK_PROVIDER.equals(provider)) { + return LocationStatsEnums.PROVIDER_NETWORK; + } else if (LocationManager.GPS_PROVIDER.equals(provider)) { + return LocationStatsEnums.PROVIDER_GPS; + } else if (LocationManager.PASSIVE_PROVIDER.equals(provider)) { + return LocationStatsEnums.PROVIDER_PASSIVE; + } else if (LocationManager.FUSED_PROVIDER.equals(provider)) { + return LocationStatsEnums.PROVIDER_FUSED; + } else { + return LocationStatsEnums.PROVIDER_UNKNOWN; + } + } + + private static int bucketizeIntervalToStatsdEnum(long interval) { + // LocationManager already converts negative values to 0. + if (interval < ONE_SEC_IN_MILLIS) { + return LocationStatsEnums.INTERVAL_BETWEEN_0_SEC_AND_1_SEC; + } else if (interval < ONE_SEC_IN_MILLIS * 5) { + return LocationStatsEnums.INTERVAL_BETWEEN_1_SEC_AND_5_SEC; + } else if (interval < ONE_MINUTE_IN_MILLIS) { + return LocationStatsEnums.INTERVAL_BETWEEN_5_SEC_AND_1_MIN; + } else if (interval < ONE_MINUTE_IN_MILLIS * 10) { + return LocationStatsEnums.INTERVAL_BETWEEN_1_MIN_AND_10_MIN; + } else if (interval < ONE_HOUR_IN_MILLIS) { + return LocationStatsEnums.INTERVAL_BETWEEN_10_MIN_AND_1_HOUR; + } else { + return LocationStatsEnums.INTERVAL_LARGER_THAN_1_HOUR; + } + } + + private static int bucketizeSmallestDisplacementToStatsdEnum(float smallestDisplacement) { + // LocationManager already converts negative values to 0. + if (smallestDisplacement == 0) { + return LocationStatsEnums.DISTANCE_ZERO; + } else if (smallestDisplacement > 0 && smallestDisplacement <= 100) { + return LocationStatsEnums.DISTANCE_BETWEEN_0_AND_100; + } else { + return LocationStatsEnums.DISTANCE_LARGER_THAN_100; + } + } + + private static int bucketizeRadiusToStatsdEnum(float radius) { + if (radius < 0) { + return LocationStatsEnums.RADIUS_NEGATIVE; + } else if (radius < 100) { + return LocationStatsEnums.RADIUS_BETWEEN_0_AND_100; + } else if (radius < 200) { + return LocationStatsEnums.RADIUS_BETWEEN_100_AND_200; + } else if (radius < 300) { + return LocationStatsEnums.RADIUS_BETWEEN_200_AND_300; + } else if (radius < 1000) { + return LocationStatsEnums.RADIUS_BETWEEN_300_AND_1000; + } else if (radius < 10000) { + return LocationStatsEnums.RADIUS_BETWEEN_1000_AND_10000; + } else { + return LocationStatsEnums.RADIUS_LARGER_THAN_100000; + } + } + + private static int getBucketizedExpireIn(long expireAt) { + if (expireAt == Long.MAX_VALUE) { + return LocationStatsEnums.EXPIRATION_NO_EXPIRY; + } + + long elapsedRealtime = SystemClock.elapsedRealtime(); + long expireIn = Math.max(0, expireAt - elapsedRealtime); + + if (expireIn < 20 * ONE_SEC_IN_MILLIS) { + return LocationStatsEnums.EXPIRATION_BETWEEN_0_AND_20_SEC; + } else if (expireIn < ONE_MINUTE_IN_MILLIS) { + return LocationStatsEnums.EXPIRATION_BETWEEN_20_SEC_AND_1_MIN; + } else if (expireIn < ONE_MINUTE_IN_MILLIS * 10) { + return LocationStatsEnums.EXPIRATION_BETWEEN_1_MIN_AND_10_MIN; + } else if (expireIn < ONE_HOUR_IN_MILLIS) { + return LocationStatsEnums.EXPIRATION_BETWEEN_10_MIN_AND_1_HOUR; + } else { + return LocationStatsEnums.EXPIRATION_LARGER_THAN_1_HOUR; + } + } + + private static int categorizeActivityImportance(int importance) { + if (importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { + return LocationStatsEnums.IMPORTANCE_TOP; + } else if (importance == ActivityManager + .RunningAppProcessInfo + .IMPORTANCE_FOREGROUND_SERVICE) { + return LocationStatsEnums.IMPORTANCE_FORGROUND_SERVICE; + } else { + return LocationStatsEnums.IMPORTANCE_BACKGROUND; + } + } + + private static int getCallbackType( + int apiType, boolean hasListener, boolean hasIntent) { + if (apiType == LocationStatsEnums.API_SEND_EXTRA_COMMAND) { + return LocationStatsEnums.CALLBACK_NOT_APPLICABLE; + } + + // Listener and PendingIntent will not be set at + // the same time. + if (hasIntent) { + return LocationStatsEnums.CALLBACK_PENDING_INTENT; + } else if (hasListener) { + return LocationStatsEnums.CALLBACK_LISTENER; + } else { + return LocationStatsEnums.CALLBACK_UNKNOWN; + } + } + + // Update the hourly count of APIUsage log event. + // Returns false if hit the hourly log cap. + private boolean checkApiUsageLogCap() { + if (D) { + Log.d(TAG, "checking APIUsage log cap."); + } + + long currentHour = Instant.now().toEpochMilli() / ONE_HOUR_IN_MILLIS; + if (currentHour > mLastApiUsageLogHour) { + mLastApiUsageLogHour = currentHour; + mApiUsageLogHourlyCount = 0; + return true; + } else { + mApiUsageLogHourlyCount = Math.min( + mApiUsageLogHourlyCount + 1, API_USAGE_LOG_HOURLY_CAP); + return mApiUsageLogHourlyCount < API_USAGE_LOG_HOURLY_CAP; + } + } + + /** + * Log a Location API usage event to Statsd. + * Logging event is capped at 60 per hour. Usage events exceeding + * the cap will be dropped by LocationUsageLogger. + */ + public void logLocationApiUsage(int usageType, int apiInUse, + String packageName, LocationRequest locationRequest, + boolean hasListener, boolean hasIntent, + float radius, int activityImportance) { + try { + if (!checkApiUsageLogCap()) { + return; + } + + boolean isLocationRequestNull = locationRequest == null; + if (D) { + Log.d(TAG, "log API Usage to statsd. usageType: " + usageType + ", apiInUse: " + + apiInUse + ", packageName: " + (packageName == null ? "" : packageName) + + ", locationRequest: " + + (isLocationRequestNull ? "" : locationRequest.toString()) + + ", hasListener: " + hasListener + + ", hasIntent: " + hasIntent + + ", radius: " + radius + + ", importance: " + activityImportance); + } + + StatsLog.write(StatsLog.LOCATION_MANAGER_API_USAGE_REPORTED, usageType, + apiInUse, packageName, + isLocationRequestNull + ? LocationStatsEnums.PROVIDER_UNKNOWN + : providerNameToStatsdEnum(locationRequest.getProvider()), + isLocationRequestNull + ? LocationStatsEnums.QUALITY_UNKNOWN + : locationRequest.getQuality(), + isLocationRequestNull + ? LocationStatsEnums.INTERVAL_UNKNOWN + : bucketizeIntervalToStatsdEnum(locationRequest.getInterval()), + isLocationRequestNull + ? LocationStatsEnums.DISTANCE_UNKNOWN + : bucketizeSmallestDisplacementToStatsdEnum( + locationRequest.getSmallestDisplacement()), + isLocationRequestNull ? 0 : locationRequest.getNumUpdates(), + // only log expireIn for USAGE_STARTED + isLocationRequestNull || usageType == LocationStatsEnums.USAGE_ENDED + ? LocationStatsEnums.EXPIRATION_UNKNOWN + : getBucketizedExpireIn(locationRequest.getExpireAt()), + getCallbackType(apiInUse, hasListener, hasIntent), + bucketizeRadiusToStatsdEnum(radius), + categorizeActivityImportance(activityImportance)); + } catch (Exception e) { + // Swallow exceptions to avoid crashing LMS. + Log.w(TAG, "Failed to log API usage to statsd.", e); + } + } + + /** + * Log a Location API usage event to Statsd. + * Logging event is capped at 60 per hour. Usage events exceeding + * the cap will be dropped by LocationUsageLogger. + */ + public void logLocationApiUsage(int usageType, int apiInUse, String providerName) { + try { + if (!checkApiUsageLogCap()) { + return; + } + + if (D) { + Log.d(TAG, "log API Usage to statsd. usageType: " + usageType + ", apiInUse: " + + apiInUse + ", providerName: " + providerName); + } + + StatsLog.write(StatsLog.LOCATION_MANAGER_API_USAGE_REPORTED, usageType, apiInUse, + /* package_name= */ null, + providerNameToStatsdEnum(providerName), + LocationStatsEnums.QUALITY_UNKNOWN, + LocationStatsEnums.INTERVAL_UNKNOWN, + LocationStatsEnums.DISTANCE_UNKNOWN, + /* numUpdates= */ 0, + LocationStatsEnums.EXPIRATION_UNKNOWN, + getCallbackType( + apiInUse, + /* isListenerNull= */ true, + /* isIntentNull= */ true), + /* bucketizedRadius= */ 0, + LocationStatsEnums.IMPORTANCE_UNKNOWN); + } catch (Exception e) { + // Swallow exceptions to avoid crashing LMS. + Log.w(TAG, "Failed to log API usage to statsd.", e); + } + } +} diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java index 32671144aee3..dee89e5d5c4d 100644 --- a/services/core/java/com/android/server/PackageWatchdog.java +++ b/services/core/java/com/android/server/PackageWatchdog.java @@ -25,6 +25,7 @@ import android.annotation.Nullable; import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.VersionedPackage; +import android.net.NetworkStackClient; import android.os.Environment; import android.os.Handler; import android.os.Looper; @@ -115,6 +116,7 @@ public class PackageWatchdog { // File containing the XML data of monitored packages /data/system/package-watchdog.xml private final AtomicFile mPolicyFile; private final ExplicitHealthCheckController mHealthCheckController; + private final NetworkStackClient mNetworkStackClient; @GuardedBy("mLock") private boolean mIsPackagesReady; // Flag to control whether explicit health checks are supported or not @@ -135,7 +137,8 @@ public class PackageWatchdog { new File(new File(Environment.getDataDirectory(), "system"), "package-watchdog.xml")), new Handler(Looper.myLooper()), BackgroundThread.getHandler(), - new ExplicitHealthCheckController(context)); + new ExplicitHealthCheckController(context), + NetworkStackClient.getInstance()); } /** @@ -143,12 +146,14 @@ public class PackageWatchdog { */ @VisibleForTesting PackageWatchdog(Context context, AtomicFile policyFile, Handler shortTaskHandler, - Handler longTaskHandler, ExplicitHealthCheckController controller) { + Handler longTaskHandler, ExplicitHealthCheckController controller, + NetworkStackClient networkStackClient) { mContext = context; mPolicyFile = policyFile; mShortTaskHandler = shortTaskHandler; mLongTaskHandler = longTaskHandler; mHealthCheckController = controller; + mNetworkStackClient = networkStackClient; loadFromFile(); } @@ -174,6 +179,7 @@ public class PackageWatchdog { () -> syncRequestsAsync()); setPropertyChangedListenerLocked(); updateConfigs(); + registerNetworkStackHealthListener(); } } @@ -630,29 +636,40 @@ public class PackageWatchdog { synchronized (mLock) { PackageHealthObserver registeredObserver = observer.mRegisteredObserver; if (registeredObserver != null) { - PackageManager pm = mContext.getPackageManager(); Iterator<MonitoredPackage> it = failedPackages.iterator(); while (it.hasNext()) { String failedPackage = it.next().getName(); - long versionCode = 0; Slog.i(TAG, "Explicit health check failed for package " + failedPackage); - try { - versionCode = pm.getPackageInfo( - failedPackage, 0 /* flags */).getLongVersionCode(); - } catch (PackageManager.NameNotFoundException e) { + VersionedPackage versionedPkg = getVersionedPackage(failedPackage); + if (versionedPkg == null) { Slog.w(TAG, "Explicit health check failed but could not find package " + failedPackage); // TODO(b/120598832): Skip. We only continue to pass tests for now since // the tests don't install any packages + versionedPkg = new VersionedPackage(failedPackage, 0L); } - registeredObserver.execute( - new VersionedPackage(failedPackage, versionCode)); + registeredObserver.execute(versionedPkg); } } } }); } + @Nullable + private VersionedPackage getVersionedPackage(String packageName) { + final PackageManager pm = mContext.getPackageManager(); + if (pm == null) { + return null; + } + try { + final long versionCode = pm.getPackageInfo( + packageName, 0 /* flags */).getLongVersionCode(); + return new VersionedPackage(packageName, versionCode); + } catch (PackageManager.NameNotFoundException e) { + return null; + } + } + /** * Loads mAllObservers from file. * @@ -726,6 +743,27 @@ public class PackageWatchdog { } } + private void registerNetworkStackHealthListener() { + // TODO: have an internal method to trigger a rollback by reporting high severity errors, + // and rely on ActivityManager to inform the watchdog of severe network stack crashes + // instead of having this listener in parallel. + mNetworkStackClient.registerHealthListener( + packageName -> { + final VersionedPackage pkg = getVersionedPackage(packageName); + if (pkg == null) { + Slog.wtf(TAG, "NetworkStack failed but could not find its package"); + return; + } + // This is a severe failure and recovery should be attempted immediately. + // TODO: have a better way to handle such failures. + final List<VersionedPackage> pkgList = Collections.singletonList(pkg); + final long failureCount = getTriggerFailureCount(); + for (int i = 0; i < failureCount; i++) { + onPackageFailure(pkgList); + } + }); + } + /** * Persists mAllObservers to file. Threshold information is ignored. */ diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index bbbec665020b..d2b992bad462 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -2842,8 +2842,9 @@ class StorageManagerService extends IStorageManager.Stub } } + /** 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 @@ class StorageManagerService extends IStorageManager.Stub @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 @@ class StorageManagerService extends IStorageManager.Stub @Override public void close() throws Exception { - if (opened) { + if (mMounted) { mVold.unmountAppFuse(uid, mountId); - opened = false; + mMounted = false; } } } @@ -3582,6 +3584,10 @@ class StorageManagerService extends IStorageManager.Stub } 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 bdbff3db943d..4b48ef917744 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 @@ public class AdbDebuggingManager { 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 @@ public class AdbDebuggingManager { 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 @@ public class AdbDebuggingManager { return; } - if (!keyFile.exists()) { - createKeyFile(keyFile); - } - atomicKeyFile = new AtomicFile(keyFile); fo = atomicKeyFile.startWrite(); for (String key : keys) { @@ -709,6 +698,9 @@ public class AdbDebuggingManager { 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/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index a0522e3971e7..fcd6a0aacd92 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -241,15 +241,6 @@ import com.android.internal.annotations.GuardedBy; sendLMsgNoDelay(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT, SENDMSG_QUEUE, info); } - /*package*/ void postBluetoothA2dpDeviceConfigChangeExt( - @NonNull BluetoothDevice device, - @AudioService.BtProfileConnectionState int state, int profile, - boolean suppressNoisyIntent, int a2dpVolume) { - final BtDeviceConnectionInfo info = new BtDeviceConnectionInfo(device, state, profile, - suppressNoisyIntent, a2dpVolume); - sendLMsgNoDelay(MSG_L_A2DP_ACTIVE_DEVICE_CHANGE_EXT, SENDMSG_QUEUE, info); - } - private static final class HearingAidDeviceConnectionInfo { final @NonNull BluetoothDevice mDevice; final @AudioService.BtProfileConnectionState int mState; @@ -862,22 +853,6 @@ import com.android.internal.annotations.GuardedBy; info.mDevice, info.mState, info.mSupprNoisy, info.mMusicDevice); } } break; - case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE_EXT: { - final BtDeviceConnectionInfo info = (BtDeviceConnectionInfo) msg.obj; - AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent( - "handleBluetoothA2dpActiveDeviceChangeExt " - + " state=" + info.mState - // only querying address as this is the only readily available - // field on the device - + " addr=" + info.mDevice.getAddress() - + " prof=" + info.mProfile + " supprNoisy=" + info.mSupprNoisy - + " vol=" + info.mVolume)).printLog(TAG)); - synchronized (mDeviceStateLock) { - mDeviceInventory.handleBluetoothA2dpActiveDeviceChangeExt( - info.mDevice, info.mState, info.mProfile, - info.mSupprNoisy, info.mVolume); - } - } break; default: Log.wtf(TAG, "Invalid message " + msg.what); } @@ -925,10 +900,8 @@ import com.android.internal.annotations.GuardedBy; private static final int MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT = 27; // process external command to (dis)connect a hearing aid device private static final int MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT = 28; - // process external command to (dis)connect or change active A2DP device - private static final int MSG_L_A2DP_ACTIVE_DEVICE_CHANGE_EXT = 29; // a ScoClient died in BtHelper - private static final int MSG_L_SCOCLIENT_DIED = 30; + private static final int MSG_L_SCOCLIENT_DIED = 29; private static boolean isMessageHandledUnderWakelock(int msgId) { @@ -943,7 +916,6 @@ import com.android.internal.annotations.GuardedBy; case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE: case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT: case MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT: - case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE_EXT: return true; default: return false; diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index 887c90873bdd..99b97cbf7dbc 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -570,49 +570,6 @@ public final class AudioDeviceInventory { } } - /*package*/ void handleBluetoothA2dpActiveDeviceChangeExt( - @NonNull BluetoothDevice device, - @AudioService.BtProfileConnectionState int state, int profile, - boolean suppressNoisyIntent, int a2dpVolume) { - if (state == BluetoothProfile.STATE_DISCONNECTED) { - mDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent( - device, state, profile, suppressNoisyIntent, a2dpVolume); - return; - } - // state == BluetoothProfile.STATE_CONNECTED - synchronized (mConnectedDevices) { - final String address = device.getAddress(); - final int a2dpCodec = mDeviceBroker.getA2dpCodec(device); - final String deviceKey = DeviceInfo.makeDeviceListKey( - AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address); - DeviceInfo deviceInfo = mConnectedDevices.get(deviceKey); - if (deviceInfo != null) { - // Device config change for matching A2DP device - mDeviceBroker.postBluetoothA2dpDeviceConfigChange(device); - return; - } - for (int i = 0; i < mConnectedDevices.size(); i++) { - deviceInfo = mConnectedDevices.valueAt(i); - if (deviceInfo.mDeviceType != AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) { - continue; - } - // A2DP device exists, handle active device change - final String existingDevicekey = mConnectedDevices.keyAt(i); - mConnectedDevices.remove(existingDevicekey); - mConnectedDevices.put(deviceKey, new DeviceInfo( - AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, BtHelper.getName(device), - address, a2dpCodec)); - mDeviceBroker.postA2dpActiveDeviceChange( - new BtHelper.BluetoothA2dpDeviceInfo( - device, a2dpVolume, a2dpCodec)); - return; - } - } - // New A2DP device connection - mDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent( - device, state, profile, suppressNoisyIntent, a2dpVolume); - } - /*package*/ int setWiredDeviceConnectionState(int type, @AudioService.ConnectionState int state, String address, String name, String caller) { synchronized (mConnectedDevices) { diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 15e8851a6a2b..30035c8c365b 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -4382,27 +4382,6 @@ public class AudioService extends IAudioService.Stub mDeviceBroker.postBluetoothA2dpDeviceConfigChange(device); } - /** - * @see AudioManager#handleBluetoothA2dpActiveDeviceChange(BluetoothDevice, int, int, - * boolean, int) - */ - public void handleBluetoothA2dpActiveDeviceChange( - BluetoothDevice device, int state, int profile, boolean suppressNoisyIntent, - int a2dpVolume) { - if (device == null) { - throw new IllegalArgumentException("Illegal null device"); - } - if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK) { - throw new IllegalArgumentException("invalid profile " + profile); - } - if (state != BluetoothProfile.STATE_CONNECTED - && state != BluetoothProfile.STATE_DISCONNECTED) { - throw new IllegalArgumentException("Invalid state " + state); - } - mDeviceBroker.postBluetoothA2dpDeviceConfigChangeExt(device, state, profile, - suppressNoisyIntent, a2dpVolume); - } - private static final int DEVICE_MEDIA_UNMUTED_ON_PLUG = AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE | AudioSystem.DEVICE_OUT_LINE | diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java index af25ad56a2a7..b279b370c611 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 @@ public class ClipboardService extends SystemService { // 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 @@ public class ClipboardService extends SystemService { // 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/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java index 0910dac27337..f6735d983466 100644 --- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java +++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java @@ -237,9 +237,15 @@ public class NetworkNotificationManager { + getTransportName(transportType)); return; } - - final String channelId = highPriority ? SystemNotificationChannels.NETWORK_ALERTS : - SystemNotificationChannels.NETWORK_STATUS; + // When replacing an existing notification for a given network, don't alert, just silently + // update the existing notification. Note that setOnlyAlertOnce() will only work for the + // same id, and the id used here is the NotificationType which is different in every type of + // notification. This is required because the notification metrics only track the ID but not + // the tag. + final boolean hasPreviousNotification = previousNotifyType != null; + final String channelId = (highPriority && !hasPreviousNotification) + ? SystemNotificationChannels.NETWORK_ALERTS + : SystemNotificationChannels.NETWORK_STATUS; Notification.Builder builder = new Notification.Builder(mContext, channelId) .setWhen(System.currentTimeMillis()) .setShowWhen(notifyType == NotificationType.NETWORK_SWITCH) diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 0271d3bcb57c..40a4820355ad 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -953,30 +953,21 @@ public class Vpn { return false; } - LinkProperties lp = makeLinkProperties(); - final boolean hadInternetCapability = mNetworkCapabilities.hasCapability( - NetworkCapabilities.NET_CAPABILITY_INTERNET); - final boolean willHaveInternetCapability = providesRoutesToMostDestinations(lp); - if (hadInternetCapability != willHaveInternetCapability) { - // A seamless handover would have led to a change to INTERNET capability, which - // is supposed to be immutable for a given network. In this case bail out and do not - // perform handover. - Log.i(TAG, "Handover not possible due to changes to INTERNET capability"); - return false; - } - - agent.sendLinkProperties(lp); + agent.sendLinkProperties(makeLinkProperties()); return true; } private void agentConnect() { LinkProperties lp = makeLinkProperties(); - if (providesRoutesToMostDestinations(lp)) { - mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); - } else { - mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); - } + // VPN either provide a default route (IPv4 or IPv6 or both), or they are a split tunnel + // that falls back to the default network, which by definition provides INTERNET (unless + // there is no default network, in which case none of this matters in any sense). + // Also, it guarantees that when a VPN applies to an app, the VPN will always be reported + // as the network by getDefaultNetwork and registerDefaultNetworkCallback. This in turn + // protects the invariant that apps calling CM#bindProcessToNetwork(getDefaultNetwork()) + // the same as if they use the default network. + mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); mNetworkInfo.setDetailedState(DetailedState.CONNECTING, null, null); @@ -1846,10 +1837,11 @@ public class Vpn { if (!profile.searchDomains.isEmpty()) { config.searchDomains = Arrays.asList(profile.searchDomains.split(" +")); } - startLegacyVpn(config, racoon, mtpd); + startLegacyVpn(config, racoon, mtpd, profile); } - private synchronized void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd) { + private synchronized void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd, + VpnProfile profile) { stopLegacyVpnPrivileged(); // Prepare for the new request. @@ -1857,7 +1849,7 @@ public class Vpn { updateState(DetailedState.CONNECTING, "startLegacyVpn"); // Start a new LegacyVpnRunner and we are done! - mLegacyVpnRunner = new LegacyVpnRunner(config, racoon, mtpd); + mLegacyVpnRunner = new LegacyVpnRunner(config, racoon, mtpd, profile); mLegacyVpnRunner.start(); } @@ -1923,6 +1915,7 @@ public class Vpn { private final String mOuterInterface; private final AtomicInteger mOuterConnection = new AtomicInteger(ConnectivityManager.TYPE_NONE); + private final VpnProfile mProfile; private long mBringupStartTime = -1; @@ -1949,7 +1942,7 @@ public class Vpn { } }; - public LegacyVpnRunner(VpnConfig config, String[] racoon, String[] mtpd) { + LegacyVpnRunner(VpnConfig config, String[] racoon, String[] mtpd, VpnProfile profile) { super(TAG); mConfig = config; mDaemons = new String[] {"racoon", "mtpd"}; @@ -1965,6 +1958,8 @@ public class Vpn { // registering mOuterInterface = mConfig.interfaze; + mProfile = profile; + if (!TextUtils.isEmpty(mOuterInterface)) { final ConnectivityManager cm = ConnectivityManager.from(mContext); for (Network network : cm.getAllNetworks()) { @@ -2177,7 +2172,7 @@ public class Vpn { } // Add a throw route for the VPN server endpoint, if one was specified. - String endpoint = parameters[5]; + String endpoint = parameters[5].isEmpty() ? mProfile.server : parameters[5]; if (!endpoint.isEmpty()) { try { InetAddress addr = InetAddress.parseNumericAddress(endpoint); 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 f560d69310a3..ef6944e180de 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 @@ import java.util.function.Predicate; /** * 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 @@ public final class QuotaController extends StateController { 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 final class QuotaController extends StateController { 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 final class QuotaController extends StateController { 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 @@ public final class QuotaController extends StateController { 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 @@ public final class QuotaController extends StateController { 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 @@ public final class QuotaController extends StateController { 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 @@ public final class QuotaController extends StateController { */ 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 @@ public final class QuotaController extends StateController { * 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 @@ public final class QuotaController extends StateController { * * @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 @@ public final class QuotaController extends StateController { 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 @@ public final class QuotaController extends StateController { 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 @@ public final class QuotaController extends StateController { 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 @@ public final class QuotaController extends StateController { } 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 @@ public final class QuotaController extends StateController { 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 @@ public final class QuotaController extends StateController { // 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 @@ public final class QuotaController extends StateController { TimingSession session = sessions.get(i); // Window management. - if (startWindowElapsed < session.startTimeElapsed) { - stats.executionTimeInWindowMs += session.endTimeElapsed - session.startTimeElapsed; - 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); - } - if (i == loopStart - || (sessions.get(i + 1).startTimeElapsed - session.endTimeElapsed) - > mTimingSessionCoalescingDurationMs) { - // Coalesce sessions if they are very close to each other in time - sessionCountInWindow++; + 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; } - } 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.executionTimeInWindowMs += session.endTimeElapsed - start; stats.bgJobCountInWindow += session.bgJobCount; - emptyTimeMs = 0; if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) { - stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed, - startWindowElapsed + 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++; + + if (sessionCountInWindow >= stats.sessionCountLimit) { + stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, + session.endTimeElapsed + stats.windowSizeMs); + } } } @@ -1025,9 +1057,9 @@ public final class QuotaController extends StateController { 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 @@ public final class QuotaController extends StateController { 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 @@ public final class QuotaController extends StateController { 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 @@ public final class QuotaController extends StateController { 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 @@ public final class QuotaController extends StateController { } // 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 @@ public final class QuotaController extends StateController { 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 @@ public final class QuotaController extends StateController { 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 @@ public final class QuotaController extends StateController { 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 @@ public final class QuotaController extends StateController { 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 @@ public final class QuotaController extends StateController { 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 final class QuotaController extends StateController { */ 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 @@ public final class QuotaController extends StateController { * 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 final class QuotaController extends StateController { 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 @@ public final class QuotaController extends StateController { 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 @@ public final class QuotaController extends StateController { 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 @@ public final class QuotaController extends StateController { 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 @@ public final class QuotaController extends StateController { 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 @@ public final class QuotaController extends StateController { 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 @@ public final class QuotaController extends StateController { 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 @@ public final class QuotaController extends StateController { 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,18 +2502,23 @@ public final class QuotaController extends StateController { } @VisibleForTesting - int getMaxJobCountPerAllowedTime() { - return mMaxJobCountPerAllowedTime; + int getMaxJobCountPerRateLimitingWindow() { + return mMaxJobCountPerRateLimitingWindow; } @VisibleForTesting - long getTimingSessionCoalescingDurationMs() { - return mTimingSessionCoalescingDurationMs; + int getMaxSessionCountPerRateLimitingWindow() { + return mMaxSessionCountPerRateLimitingWindow; } @VisibleForTesting - int getMaxSessionCountPerAllowedTime() { - return mMaxSessionCountPerAllowedTime; + long getRateLimitingWindowMs() { + return mRateLimitingWindowMs; + } + + @VisibleForTesting + long getTimingSessionCoalescingDurationMs() { + return mTimingSessionCoalescingDurationMs; } @VisibleForTesting @@ -2659,6 +2735,12 @@ public final class QuotaController extends StateController { 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 @@ public final class QuotaController extends StateController { 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 cebc47217831..7c1c1c7ce403 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 @@ public final class NetworkStatsAccess { 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 69efd02dea9c..473cc97b7edc 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 @@ public class NetworkStatsFactory { 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 a2e7e0cae96b..bdff50053fae 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.net.VpnInfo; 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 @@ public class NetworkStatsRecorder { 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 f34ace55a72e..a13368ff9de4 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { /** 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // 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/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 1160e33aca25..c4eb661c55d8 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1036,19 +1036,12 @@ public class NotificationManagerService extends SystemService { final StatusBarNotification n = r.sbn; final int callingUid = n.getUid(); final String pkg = n.getPackageName(); - final boolean wasBubble = r.getNotification().isBubbleNotification(); if (isBubble && isNotificationAppropriateToBubble(r, pkg, callingUid, null /* oldEntry */)) { r.getNotification().flags |= FLAG_BUBBLE; } else { r.getNotification().flags &= ~FLAG_BUBBLE; } - if (wasBubble != r.getNotification().isBubbleNotification()) { - // Add the "alert only once" flag so that the notification won't HUN - // unnecessarily just because the bubble flag was changed. - r.getNotification().flags |= FLAG_ONLY_ALERT_ONCE; - mListeners.notifyPostedLocked(r, r); - } } } } @@ -2643,18 +2636,25 @@ public class NotificationManagerService extends SystemService { ParceledListSlice channelsList) { List<NotificationChannel> channels = channelsList.getList(); final int channelsSize = channels.size(); + boolean needsPolicyFileChange = false; for (int i = 0; i < channelsSize; i++) { final NotificationChannel channel = channels.get(i); Preconditions.checkNotNull(channel, "channel in list is null"); - mPreferencesHelper.createNotificationChannel(pkg, uid, channel, - true /* fromTargetApp */, mConditionProviders.isPackageOrComponentAllowed( + needsPolicyFileChange = mPreferencesHelper.createNotificationChannel(pkg, uid, + channel, true /* fromTargetApp */, + mConditionProviders.isPackageOrComponentAllowed( pkg, UserHandle.getUserId(uid))); - mListeners.notifyNotificationChannelChanged(pkg, - UserHandle.getUserHandleForUid(uid), - mPreferencesHelper.getNotificationChannel(pkg, uid, channel.getId(), false), - NOTIFICATION_CHANNEL_OR_GROUP_ADDED); + if (needsPolicyFileChange) { + mListeners.notifyNotificationChannelChanged(pkg, + UserHandle.getUserHandleForUid(uid), + mPreferencesHelper.getNotificationChannel(pkg, uid, channel.getId(), + false), + NOTIFICATION_CHANNEL_OR_GROUP_ADDED); + } + } + if (needsPolicyFileChange) { + handleSavePolicyFile(); } - handleSavePolicyFile(); } @Override diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 5956c65defef..1c0ac167c26b 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -615,12 +615,13 @@ public class PreferencesHelper implements RankingConfig { } @Override - public void createNotificationChannel(String pkg, int uid, NotificationChannel channel, + public boolean createNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromTargetApp, boolean hasDndAccess) { Preconditions.checkNotNull(pkg); Preconditions.checkNotNull(channel); Preconditions.checkNotNull(channel.getId()); Preconditions.checkArgument(!TextUtils.isEmpty(channel.getName())); + boolean needsPolicyFileChange = false; synchronized (mPackagePreferences) { PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid); if (r == null) { @@ -637,17 +638,28 @@ public class PreferencesHelper implements RankingConfig { if (existing != null && fromTargetApp) { if (existing.isDeleted()) { existing.setDeleted(false); + needsPolicyFileChange = true; // log a resurrected channel as if it's new again MetricsLogger.action(getChannelLog(channel, pkg).setType( com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN)); } - existing.setName(channel.getName().toString()); - existing.setDescription(channel.getDescription()); - existing.setBlockableSystem(channel.isBlockableSystem()); - if (existing.getGroup() == null) { + if (!Objects.equals(channel.getName().toString(), existing.getName().toString())) { + existing.setName(channel.getName().toString()); + needsPolicyFileChange = true; + } + if (!Objects.equals(channel.getDescription(), existing.getDescription())) { + existing.setDescription(channel.getDescription()); + needsPolicyFileChange = true; + } + if (channel.isBlockableSystem() != existing.isBlockableSystem()) { + existing.setBlockableSystem(channel.isBlockableSystem()); + needsPolicyFileChange = true; + } + if (channel.getGroup() != null && existing.getGroup() == null) { existing.setGroup(channel.getGroup()); + needsPolicyFileChange = true; } // Apps are allowed to downgrade channel importance if the user has not changed any @@ -656,23 +668,30 @@ public class PreferencesHelper implements RankingConfig { if (existing.getUserLockedFields() == 0 && channel.getImportance() < existing.getImportance()) { existing.setImportance(channel.getImportance()); + needsPolicyFileChange = true; } // system apps and dnd access apps can bypass dnd if the user hasn't changed any // fields on the channel yet if (existing.getUserLockedFields() == 0 && hasDndAccess) { boolean bypassDnd = channel.canBypassDnd(); - existing.setBypassDnd(bypassDnd); + if (bypassDnd != existing.canBypassDnd()) { + existing.setBypassDnd(bypassDnd); + needsPolicyFileChange = true; - if (bypassDnd != mAreChannelsBypassingDnd - || previousExistingImportance != existing.getImportance()) { - updateChannelsBypassingDnd(mContext.getUserId()); + if (bypassDnd != mAreChannelsBypassingDnd + || previousExistingImportance != existing.getImportance()) { + updateChannelsBypassingDnd(mContext.getUserId()); + } } } updateConfig(); - return; + return needsPolicyFileChange; } + + needsPolicyFileChange = true; + if (channel.getImportance() < IMPORTANCE_NONE || channel.getImportance() > NotificationManager.IMPORTANCE_MAX) { throw new IllegalArgumentException("Invalid importance level"); @@ -708,6 +727,8 @@ public class PreferencesHelper implements RankingConfig { MetricsLogger.action(getChannelLog(channel, pkg).setType( com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN)); } + + return needsPolicyFileChange; } void clearLockedFieldsLocked(NotificationChannel channel) { diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java index 72502acd6560..5de00e43a05d 100644 --- a/services/core/java/com/android/server/notification/RankingConfig.java +++ b/services/core/java/com/android/server/notification/RankingConfig.java @@ -39,7 +39,7 @@ public interface RankingConfig { boolean fromTargetApp); ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg, int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty); - void createNotificationChannel(String pkg, int uid, NotificationChannel channel, + boolean createNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromTargetApp, boolean hasDndAccess); void updateNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromUser); NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, boolean includeDeleted); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 2a61feea6349..2f866327782d 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -15032,24 +15032,26 @@ public class PackageManagerService extends IPackageManager.Stub void tryProcessInstallRequest(InstallArgs args, int currentStatus) { mCurrentState.put(args, currentStatus); - boolean success = true; if (mCurrentState.size() != mChildParams.size()) { return; } + int completeStatus = PackageManager.INSTALL_SUCCEEDED; for (Integer status : mCurrentState.values()) { if (status == PackageManager.INSTALL_UNKNOWN) { return; } else if (status != PackageManager.INSTALL_SUCCEEDED) { - success = false; + completeStatus = status; break; } } final List<InstallRequest> installRequests = new ArrayList<>(mCurrentState.size()); for (Map.Entry<InstallArgs, Integer> entry : mCurrentState.entrySet()) { installRequests.add(new InstallRequest(entry.getKey(), - createPackageInstalledInfo(entry.getValue()))); + createPackageInstalledInfo(completeStatus))); } - processInstallRequestsAsync(success, installRequests); + processInstallRequestsAsync( + completeStatus == PackageManager.INSTALL_SUCCEEDED, + installRequests); } } @@ -17080,6 +17082,13 @@ public class PackageManagerService extends IPackageManager.Stub 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 f5c8049dcb25..81de8e263299 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 @@ class PackageManagerShellCommand extends ShellCommand { 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 eec4b70880a5..d6e87aab35fe 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 @@ class ShortcutPackage extends ShortcutPackageItem { 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/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 24bf18de13c8..950450cdbeb5 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -36,7 +36,6 @@ import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion; import android.content.pm.ParceledListSlice; import android.content.pm.Signature; import android.content.rollback.IRollbackManager; -import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -116,12 +115,9 @@ public class StagingManager { final PackageInfo packageInfo = mApexManager.getPackageInfoForApexName(packageName); if (packageInfo == null) { - // Only allow installing new apexes if on a debuggable build. - if (!Build.IS_DEBUGGABLE) { - Slog.w(TAG, "Attempted to install new apex " + packageName + " on user build"); - return false; - } - return true; + // Don't allow installation of new APEX. + Slog.e(TAG, "Attempted to install a new apex " + packageName + ". Rejecting"); + return false; } final SigningDetails existingSigningDetails; diff --git a/services/core/java/com/android/server/power/AttentionDetector.java b/services/core/java/com/android/server/power/AttentionDetector.java index 14f1196ab3a2..ed11fd45ec39 100644 --- a/services/core/java/com/android/server/power/AttentionDetector.java +++ b/services/core/java/com/android/server/power/AttentionDetector.java @@ -19,6 +19,8 @@ package com.android.server.power; import static android.provider.Settings.System.ADAPTIVE_SLEEP; import android.Manifest; +import android.app.ActivityManager; +import android.app.SynchronousUserSwitchObserver; import android.attention.AttentionManagerInternal; import android.attention.AttentionManagerInternal.AttentionCallbackInternal; import android.content.ContentResolver; @@ -28,6 +30,7 @@ import android.database.ContentObserver; import android.os.Handler; import android.os.PowerManager; import android.os.PowerManagerInternal; +import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; @@ -54,6 +57,8 @@ public class AttentionDetector { private static final String TAG = "AttentionDetector"; private static final boolean DEBUG = false; + private Context mContext; + private boolean mIsSettingEnabled; /** @@ -132,6 +137,7 @@ public class AttentionDetector { } public void systemReady(Context context) { + mContext = context; updateEnabledFromSettings(context); mPackageManager = context.getPackageManager(); mContentResolver = context.getContentResolver(); @@ -141,6 +147,13 @@ public class AttentionDetector { mMaxAttentionApiTimeoutMillis = context.getResources().getInteger( com.android.internal.R.integer.config_attentionApiTimeout); + try { + final UserSwitchObserver observer = new UserSwitchObserver(); + ActivityManager.getService().registerUserSwitchObserver(observer, TAG); + } catch (RemoteException e) { + // Shouldn't happen since in-process. + } + context.getContentResolver().registerContentObserver(Settings.System.getUriFor( Settings.System.ADAPTIVE_SLEEP), false, new ContentObserver(new Handler()) { @@ -326,4 +339,11 @@ public class AttentionDetector { mRequested.set(false); } } + + private final class UserSwitchObserver extends SynchronousUserSwitchObserver { + @Override + public void onUserSwitching(int newUserId) throws RemoteException { + updateEnabledFromSettings(mContext); + } + } } diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index e2bbb2dc2dc5..cfd3ae6ef594 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 @@ public final class PowerManagerService extends SystemService 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/telecom/TelecomLoaderService.java b/services/core/java/com/android/server/telecom/TelecomLoaderService.java index e65eae0ab156..54369ca5c367 100644 --- a/services/core/java/com/android/server/telecom/TelecomLoaderService.java +++ b/services/core/java/com/android/server/telecom/TelecomLoaderService.java @@ -16,21 +16,17 @@ package com.android.server.telecom; +import android.app.role.RoleManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; -import android.database.ContentObserver; -import android.net.Uri; -import android.os.Handler; import android.os.IBinder; -import android.os.Looper; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; -import android.provider.Settings; import android.telecom.DefaultDialerManager; import android.telecom.PhoneAccountHandle; import android.telecom.TelecomManager; @@ -221,19 +217,10 @@ public class TelecomLoaderService extends SystemService { private void registerDefaultAppNotifier() { final DefaultPermissionGrantPolicy permissionPolicy = getDefaultPermissionGrantPolicy(); // Notify the package manager on default app changes - final Uri defaultDialerAppUri = Settings.Secure.getUriFor( - Settings.Secure.DIALER_DEFAULT_APPLICATION); - ContentObserver contentObserver = new ContentObserver( - new Handler(Looper.getMainLooper())) { - @Override - public void onChange(boolean selfChange, Uri uri, int userId) { - if (defaultDialerAppUri.equals(uri)) { - updateSimCallManagerPermissions(permissionPolicy, userId); - } - } - }; - mContext.getContentResolver().registerContentObserver(defaultDialerAppUri, - false, contentObserver, UserHandle.USER_ALL); + final RoleManager roleManager = mContext.getSystemService(RoleManager.class); + roleManager.addOnRoleHoldersChangedListenerAsUser(mContext.getMainExecutor(), + (roleName, user) -> updateSimCallManagerPermissions(permissionPolicy, + user.getIdentifier()), UserHandle.ALL); } diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index 3a390536b438..a2eb40b0c6fa 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -518,8 +518,11 @@ public class TrustManagerService extends SystemService { agentInfo = mActiveAgents.valueAt(index); } - boolean directUnlock = resolveInfo.serviceInfo.directBootAware - && agentInfo.settings.canUnlockProfile; + boolean directUnlock = false; + if (agentInfo.settings != null) { + directUnlock = resolveInfo.serviceInfo.directBootAware + && agentInfo.settings.canUnlockProfile; + } if (directUnlock) { if (DEBUG) Slog.d(TAG, "refreshAgentList: trustagent " + name diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java index 476a27309f7e..165055ac828c 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.Context; 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 @@ public class WebViewUpdateServiceImpl { 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/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index 3d7e50d91b08..cae7612e0fcc 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -1733,17 +1733,13 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree return; } - if (mThumbnail == null && getTask() != null) { - final TaskSnapshotController snapshotCtrl = mWmService.mTaskSnapshotController; - final ArraySet<Task> tasks = new ArraySet<>(); - tasks.add(getTask()); - snapshotCtrl.snapshotTasks(tasks); - snapshotCtrl.addSkipClosingAppSnapshotTasks(tasks); - final ActivityManager.TaskSnapshot snapshot = snapshotCtrl.getSnapshot( - getTask().mTaskId, getTask().mUserId, false /* restoreFromDisk */, - false /* reducedResolution */); + Task task = getTask(); + if (mThumbnail == null && task != null && !hasCommittedReparentToAnimationLeash()) { + SurfaceControl.ScreenshotGraphicBuffer snapshot = + mWmService.mTaskSnapshotController.createTaskSnapshot( + task, 1 /* scaleFraction */); if (snapshot != null) { - mThumbnail = new AppWindowThumbnail(t, this, snapshot.getSnapshot(), + mThumbnail = new AppWindowThumbnail(t, this, snapshot.getGraphicBuffer(), true /* relative */); } } @@ -2858,7 +2854,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree } } t.hide(mTransitChangeLeash); - t.reparent(mTransitChangeLeash, null); + t.remove(mTransitChangeLeash); mTransitChangeLeash = null; if (cancel) { onAnimationLeashLost(t); diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java index c5b25664f762..434239f6ecf7 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 @@ class RecentsAnimation implements RecentsAnimationCallbacks, 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 854537b4618f..fb781b06f05f 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 @@ class SystemGesturesPointerEventListener implements PointerEventListener { // 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/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java index 432ca3387ce7..181521850369 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotController.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java @@ -21,6 +21,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManager.TaskSnapshot; @@ -241,16 +242,32 @@ class TaskSnapshotController { return null; } - @Nullable private TaskSnapshot snapshotTask(Task task) { - if (!mService.mPolicy.isScreenOn()) { + @Nullable + SurfaceControl.ScreenshotGraphicBuffer createTaskSnapshot(@NonNull Task task, + float scaleFraction) { + if (task.getSurfaceControl() == null) { if (DEBUG_SCREENSHOT) { - Slog.i(TAG_WM, "Attempted to take screenshot while display was off."); + Slog.w(TAG_WM, "Failed to take screenshot. No surface control for " + task); } return null; } - if (task.getSurfaceControl() == null) { + task.getBounds(mTmpRect); + mTmpRect.offsetTo(0, 0); + final SurfaceControl.ScreenshotGraphicBuffer screenshotBuffer = + SurfaceControl.captureLayers( + task.getSurfaceControl().getHandle(), mTmpRect, scaleFraction); + final GraphicBuffer buffer = screenshotBuffer != null ? screenshotBuffer.getGraphicBuffer() + : null; + if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) { + return null; + } + return screenshotBuffer; + } + + @Nullable private TaskSnapshot snapshotTask(Task task) { + if (!mService.mPolicy.isScreenOn()) { if (DEBUG_SCREENSHOT) { - Slog.w(TAG_WM, "Failed to take screenshot. No surface control for " + task); + Slog.i(TAG_WM, "Attempted to take screenshot while display was off."); } return null; } @@ -271,8 +288,6 @@ class TaskSnapshotController { final boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic(); final float scaleFraction = isLowRamDevice ? mPersister.getReducedScale() : 1f; - task.getBounds(mTmpRect); - mTmpRect.offsetTo(0, 0); final WindowState mainWindow = appWindowToken.findMainWindow(); if (mainWindow == null) { @@ -280,18 +295,17 @@ class TaskSnapshotController { return null; } final SurfaceControl.ScreenshotGraphicBuffer screenshotBuffer = - SurfaceControl.captureLayers( - task.getSurfaceControl().getHandle(), mTmpRect, scaleFraction); - final GraphicBuffer buffer = screenshotBuffer != null ? screenshotBuffer.getGraphicBuffer() - : null; - if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) { + createTaskSnapshot(task, scaleFraction); + + if (screenshotBuffer == null) { if (DEBUG_SCREENSHOT) { Slog.w(TAG_WM, "Failed to take screenshot for " + task); } return null; } final boolean isWindowTranslucent = mainWindow.getAttrs().format != PixelFormat.OPAQUE; - return new TaskSnapshot(appWindowToken.mActivityComponent, buffer, + return new TaskSnapshot( + appWindowToken.mActivityComponent, screenshotBuffer.getGraphicBuffer(), screenshotBuffer.getColorSpace(), appWindowToken.getConfiguration().orientation, getInsets(mainWindow), isLowRamDevice /* reduced */, scaleFraction /* scale */, true /* isRealSnapshot */, task.getWindowingMode(), getSystemUiVisibility(task), diff --git a/services/net/java/android/net/NetworkStackClient.java b/services/net/java/android/net/NetworkStackClient.java index 6b5842ff9065..09c9b6d360a9 100644 --- a/services/net/java/android/net/NetworkStackClient.java +++ b/services/net/java/android/net/NetworkStackClient.java @@ -26,22 +26,27 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.net.dhcp.DhcpServingParamsParcel; import android.net.dhcp.IDhcpServerCallbacks; import android.net.ip.IIpClientCallbacks; import android.net.util.SharedLog; import android.os.Binder; -import android.os.Build; +import android.os.Environment; import android.os.IBinder; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.SystemClock; import android.os.UserHandle; +import android.provider.DeviceConfig; +import android.util.ArraySet; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import java.io.File; import java.io.PrintWriter; import java.util.ArrayList; @@ -54,6 +59,15 @@ public class NetworkStackClient { private static final int NETWORKSTACK_TIMEOUT_MS = 10_000; private static final String IN_PROCESS_SUFFIX = ".InProcess"; + private static final String PREFS_FILE = "NetworkStackClientPrefs.xml"; + private static final String PREF_KEY_LAST_CRASH_UPTIME = "lastcrash"; + private static final String CONFIG_MIN_CRASH_INTERVAL_MS = "min_crash_interval"; + + // Even if the network stack is lost, do not crash the system more often than this. + // Connectivity would be broken, but if the user needs the device for something urgent + // (like calling emergency services) we should not bootloop the device. + // This is the default value: the actual value can be adjusted via device config. + private static final long DEFAULT_MIN_CRASH_INTERVAL_MS = 6 * 3_600_000L; private static NetworkStackClient sInstance; @@ -67,12 +81,34 @@ public class NetworkStackClient { @GuardedBy("mLog") private final SharedLog mLog = new SharedLog(TAG); - private volatile boolean mNetworkStackStartRequested = false; + private volatile boolean mWasSystemServerInitialized = false; + + /** + * If non-zero, indicates that the last framework start happened after a crash of the + * NetworkStack which was at the specified uptime. + */ + private volatile long mLastCrashUptime = 0L; + + @GuardedBy("mHealthListeners") + private final ArraySet<NetworkStackHealthListener> mHealthListeners = new ArraySet<>(); private interface NetworkStackCallback { void onNetworkStackConnected(INetworkStackConnector connector); } + /** + * Callback interface for severe failures of the NetworkStack. + * + * <p>Useful for health monitors such as PackageWatchdog. + */ + public interface NetworkStackHealthListener { + /** + * Called when there is a severe failure of the network stack. + * @param packageName Package name of the network stack. + */ + void onNetworkStackFailure(@NonNull String packageName); + } + private NetworkStackClient() { } /** @@ -86,6 +122,15 @@ public class NetworkStackClient { } /** + * Add a {@link NetworkStackHealthListener} to listen to network stack health events. + */ + public void registerHealthListener(@NonNull NetworkStackHealthListener listener) { + synchronized (mHealthListeners) { + mHealthListeners.add(listener); + } + } + + /** * Create a DHCP server according to the specified parameters. * * <p>The server will be returned asynchronously through the provided callbacks. @@ -147,6 +192,16 @@ public class NetworkStackClient { } private class NetworkStackConnection implements ServiceConnection { + @NonNull + private final Context mContext; + @NonNull + private final String mPackageName; + + private NetworkStackConnection(@NonNull Context context, @NonNull String packageName) { + mContext = context; + mPackageName = packageName; + } + @Override public void onServiceConnected(ComponentName name, IBinder service) { logi("Network stack service connected"); @@ -155,14 +210,14 @@ public class NetworkStackClient { @Override public void onServiceDisconnected(ComponentName name) { - // The system has lost its network stack (probably due to a crash in the - // network stack process): better crash rather than stay in a bad state where all - // networking is broken. // onServiceDisconnected is not being called on device shutdown, so this method being // called always indicates a bad state for the system server. - maybeCrashWithTerribleFailure("Lost network stack"); + // This code path is only run by the system server: only the system server binds + // to the NetworkStack as a service. Other processes get the NetworkStack from + // the ServiceManager. + maybeCrashWithTerribleFailure("Lost network stack", mContext, mPackageName); } - }; + } private void registerNetworkStackService(@NonNull IBinder service) { final INetworkStackConnector connector = INetworkStackConnector.Stub.asInterface(service); @@ -189,7 +244,7 @@ public class NetworkStackClient { */ public void init() { log("Network stack init"); - mNetworkStackStartRequested = true; + mWasSystemServerInitialized = true; } /** @@ -202,6 +257,13 @@ public class NetworkStackClient { */ public void start(Context context) { log("Starting network stack"); + + final SharedPreferences prefs = getSharedPreferences(context); + mLastCrashUptime = prefs.getLong(PREF_KEY_LAST_CRASH_UPTIME, 0L); + // Remove the preference after getting the last crash uptime, so mLastCrashUptime always + // indicates this is the first start since the last crash. + prefs.edit().remove(PREF_KEY_LAST_CRASH_UPTIME).commit(); + final PackageManager pm = context.getPackageManager(); // Try to bind in-process if the device was shipped with an in-process version @@ -216,16 +278,19 @@ public class NetworkStackClient { } if (intent == null) { - maybeCrashWithTerribleFailure("Could not resolve the network stack"); + maybeCrashWithTerribleFailure("Could not resolve the network stack", context, null); return; } + final String packageName = intent.getComponent().getPackageName(); + // Start the network stack. The service will be added to the service manager in // NetworkStackConnection.onServiceConnected(). - if (!context.bindServiceAsUser(intent, new NetworkStackConnection(), + if (!context.bindServiceAsUser(intent, new NetworkStackConnection(context, packageName), Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, UserHandle.SYSTEM)) { maybeCrashWithTerribleFailure( - "Could not bind to network stack in-process, or in app with " + intent); + "Could not bind to network stack in-process, or in app with " + intent, + context, packageName); return; } @@ -274,11 +339,47 @@ public class NetworkStackClient { } } - private void maybeCrashWithTerribleFailure(@NonNull String message) { + private void maybeCrashWithTerribleFailure(@NonNull String message, + @NonNull Context context, @Nullable String packageName) { logWtf(message, null); - if (Build.IS_DEBUGGABLE) { + // uptime is monotonic even after a framework restart + final long uptime = SystemClock.elapsedRealtime(); + final long minCrashIntervalMs = DeviceConfig.getLong(DeviceConfig.NAMESPACE_CONNECTIVITY, + CONFIG_MIN_CRASH_INTERVAL_MS, DEFAULT_MIN_CRASH_INTERVAL_MS); + + // Either the framework was not restarted after a crash of the NetworkStack, or the min + // crash interval has passed since then. + if (mLastCrashUptime == 0L || uptime - mLastCrashUptime > minCrashIntervalMs) { + // The system is not bound to its network stack (for example due to a crash in the + // network stack process): better crash rather than stay in a bad state where all + // networking is broken. + // Using device-encrypted SharedPreferences as DeviceConfig does not have a synchronous + // API to persist settings before a crash. + final SharedPreferences prefs = getSharedPreferences(context); + if (!prefs.edit().putLong(PREF_KEY_LAST_CRASH_UPTIME, uptime).commit()) { + logWtf("Could not persist last crash uptime", null); + } throw new IllegalStateException(message); } + + // Here the system crashed recently already. Inform listeners that something is + // definitely wrong. + if (packageName != null) { + final ArraySet<NetworkStackHealthListener> listeners; + synchronized (mHealthListeners) { + listeners = new ArraySet<>(mHealthListeners); + } + for (NetworkStackHealthListener listener : listeners) { + listener.onNetworkStackFailure(packageName); + } + } + } + + private SharedPreferences getSharedPreferences(Context context) { + final File prefsFile = new File( + Environment.getDataSystemDeDirectory(UserHandle.USER_SYSTEM), PREFS_FILE); + return context.createDeviceProtectedStorageContext() + .getSharedPreferences(prefsFile, Context.MODE_PRIVATE); } /** @@ -350,7 +451,7 @@ public class NetworkStackClient { "Only the system server should try to bind to the network stack."); } - if (!mNetworkStackStartRequested) { + if (!mWasSystemServerInitialized) { // The network stack is not being started in this process, e.g. this process is not // the system server. Get a remote connector registered by the system server. final INetworkStackConnector connector = getRemoteConnector(); 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 4e893575e9d4..1db6b8e58165 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 @@ public class QuotaControllerTest { 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 @@ public class QuotaControllerTest { 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 @@ public class QuotaControllerTest { 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 @@ public class QuotaControllerTest { 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 @@ public class QuotaControllerTest { 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 @@ public class QuotaControllerTest { 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 @@ public class QuotaControllerTest { 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 @@ public class QuotaControllerTest { 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 @@ public class QuotaControllerTest { .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 @@ public class QuotaControllerTest { 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 @@ public class QuotaControllerTest { 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 @@ public class QuotaControllerTest { // 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 @@ public class QuotaControllerTest { // 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 @@ public class QuotaControllerTest { 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 @@ public class QuotaControllerTest { 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 class QuotaControllerTest { 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 class QuotaControllerTest { 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 @@ public class QuotaControllerTest { 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 @@ public class QuotaControllerTest { 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 @@ public class QuotaControllerTest { } @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 @@ public class QuotaControllerTest { 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 @@ public class QuotaControllerTest { 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 @@ public class QuotaControllerTest { 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 @@ public class QuotaControllerTest { 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 @@ public class QuotaControllerTest { 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 @@ public class QuotaControllerTest { 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 @@ public class QuotaControllerTest { 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 @@ public class QuotaControllerTest { 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 @@ public class QuotaControllerTest { 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 @@ public class QuotaControllerTest { advanceElapsedClock(10 * SECOND_IN_MILLIS); mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false); - assertEquals(2, stats.jobCountInAllowedTime); + assertEquals(2, stats.jobCountInRateLimitingWindow); } /** @@ -2421,10 +2508,10 @@ public class QuotaControllerTest { /** * 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 @@ public class QuotaControllerTest { // 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 @@ public class QuotaControllerTest { 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 @@ public class QuotaControllerTest { 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 @@ public class QuotaControllerTest { // 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 @@ public class QuotaControllerTest { 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 @@ public class QuotaControllerTest { // 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 @@ public class QuotaControllerTest { 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 fa1bcaccd786..fd3678dae0c4 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 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { 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 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { 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/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 87f221a18161..e75a30b12f68 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -22,7 +22,6 @@ import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIB import static android.app.Notification.CATEGORY_CALL; import static android.app.Notification.FLAG_BUBBLE; import static android.app.Notification.FLAG_FOREGROUND_SERVICE; -import static android.app.Notification.FLAG_ONLY_ALERT_ONCE; import static android.app.NotificationManager.EXTRA_BLOCKED_STATE; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.app.NotificationManager.IMPORTANCE_HIGH; @@ -1660,11 +1659,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(), eq(channel2.getId()), anyBoolean())) .thenReturn(channel2); + when(mPreferencesHelper.createNotificationChannel(eq(PKG), anyInt(), + eq(channel2), anyBoolean(), anyBoolean())) + .thenReturn(true); reset(mListeners); mBinderService.createNotificationChannels(PKG, new ParceledListSlice(Arrays.asList(mTestNotificationChannel, channel2))); - verify(mListeners, times(1)).notifyNotificationChannelChanged(eq(PKG), + verify(mListeners, never()).notifyNotificationChannelChanged(eq(PKG), eq(Process.myUserHandle()), eq(mTestNotificationChannel), eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_ADDED)); verify(mListeners, times(1)).notifyNotificationChannelChanged(eq(PKG), @@ -4963,13 +4965,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.mNotificationDelegate.onNotificationBubbleChanged(nr.getKey(), false); waitForIdle(); - // Make sure we are not a bubble / reported as such to listeners - ArgumentCaptor<NotificationRecord> captor = - ArgumentCaptor.forClass(NotificationRecord.class); - verify(mListeners, times(1)).notifyPostedLocked(captor.capture(), any()); - - assertEquals((captor.getValue().getNotification().flags & FLAG_BUBBLE), 0); - assertTrue((captor.getValue().getNotification().flags & FLAG_ONLY_ALERT_ONCE) != 0); + // Make sure we are not a bubble + StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG); + assertEquals(1, notifsAfter.length); + assertEquals((notifsAfter[0].getNotification().flags & FLAG_BUBBLE), 0); } @Test @@ -5000,13 +4999,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.mNotificationDelegate.onNotificationBubbleChanged(nr.getKey(), true); waitForIdle(); - // Make sure we are a bubble / reported as such to listeners - ArgumentCaptor<NotificationRecord> captor = - ArgumentCaptor.forClass(NotificationRecord.class); - verify(mListeners, times(1)).notifyPostedLocked(captor.capture(), any()); - - assertTrue((captor.getValue().getNotification().flags & FLAG_BUBBLE) != 0); - assertTrue((captor.getValue().getNotification().flags & FLAG_ONLY_ALERT_ONCE) != 0); + // Make sure we are a bubble + StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG); + assertEquals(1, notifsAfter.length); + assertTrue((notifsAfter[0].getNotification().flags & FLAG_BUBBLE) != 0); } @Test @@ -5037,7 +5033,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG); assertEquals(1, notifsAfter.length); assertEquals((notifsAfter[0].getNotification().flags & FLAG_BUBBLE), 0); - verify(mListeners, times(0)).notifyPostedLocked(any(), any()); } @Test diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index a0660c493605..8f8b746b59d4 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -262,13 +262,13 @@ public class PreferencesHelperTest extends UiServiceTestCase { int uid0 = 1001; setUpPackageWithUid(package0, uid0); NotificationChannel channel0 = new NotificationChannel("id0", "name0", IMPORTANCE_HIGH); - mHelper.createNotificationChannel(package0, uid0, channel0, true, false); + assertTrue(mHelper.createNotificationChannel(package0, uid0, channel0, true, false)); String package10 = "test.package.user10"; int uid10 = 1001001; setUpPackageWithUid(package10, uid10); NotificationChannel channel10 = new NotificationChannel("id10", "name10", IMPORTANCE_HIGH); - mHelper.createNotificationChannel(package10, uid10, channel10, true, false); + assertTrue(mHelper.createNotificationChannel(package10, uid10, channel10, true, false)); ByteArrayOutputStream baos = writeXmlAndPurge(package10, uid10, true, 10); @@ -293,7 +293,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { int uid0 = 1001; setUpPackageWithUid(package0, uid0); NotificationChannel channel0 = new NotificationChannel("id0", "name0", IMPORTANCE_HIGH); - mHelper.createNotificationChannel(package0, uid0, channel0, true, false); + assertTrue(mHelper.createNotificationChannel(package0, uid0, channel0, true, false)); ByteArrayOutputStream baos = writeXmlAndPurge(package0, uid0, true, 0); @@ -334,8 +334,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg, true); mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg2, true); - mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false); - mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel2, false, false); + assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false)); + assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel2, false, false)); mHelper.setShowBadge(PKG_N_MR1, UID_N_MR1, true); mHelper.setAppImportanceLocked(PKG_N_MR1, UID_N_MR1); @@ -716,8 +716,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testCreateChannel_blocked() throws Exception { mHelper.setImportance(PKG_N_MR1, UID_N_MR1, IMPORTANCE_NONE); - mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, - new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true, false); + assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, + new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true, false)); } @Test @@ -746,10 +746,10 @@ public class PreferencesHelperTest extends UiServiceTestCase { } catch (IllegalArgumentException e) { // yay } - mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, - new NotificationChannel("bananas", "bananas", IMPORTANCE_NONE), true, false); - mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, - new NotificationChannel("bananas", "bananas", IMPORTANCE_MAX), true, false); + assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, + new NotificationChannel("bananas", "bananas", IMPORTANCE_NONE), true, false)); + assertFalse(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, + new NotificationChannel("bananas", "bananas", IMPORTANCE_MAX), true, false)); } @@ -763,7 +763,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { channel.setBypassDnd(true); channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET); - mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, false, false); + assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, false, false)); // same id, try to update all fields final NotificationChannel channel2 = @@ -776,7 +776,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, channel2, true); // all fields should be changed - assertEquals(channel2, mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel.getId(), false)); + assertEquals(channel2, + mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel.getId(), false)); verify(mHandler, times(1)).requestSort(); } @@ -894,7 +895,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { } channel.lockFields(lockMask); - mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false); + assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false)); NotificationChannel savedChannel = mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel.getId(), false); @@ -1469,13 +1470,18 @@ public class PreferencesHelperTest extends UiServiceTestCase { new NotificationChannel("id2", "name2", IMPORTANCE_LOW); channel.setVibrationPattern(vibration); - mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false); + assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false)); NotificationChannel newChannel = new NotificationChannel( channel.getId(), channel.getName(), NotificationManager.IMPORTANCE_HIGH); newChannel.setVibrationPattern(new long[]{100}); + newChannel.setAllowBubbles(!channel.canBubble()); + newChannel.setLightColor(Color.BLUE); + newChannel.setSound(Uri.EMPTY, null); + newChannel.setShowBadge(!channel.canShowBadge()); - mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, newChannel, true, false); + assertFalse(mHelper.createNotificationChannel( + PKG_N_MR1, UID_N_MR1, newChannel, true, false)); // Old settings not overridden compareChannels(channel, @@ -1588,16 +1594,17 @@ public class PreferencesHelperTest extends UiServiceTestCase { new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH); mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false); - mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG_N_MR1}, new int[]{ - UID_N_MR1}); + assertTrue(mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG_N_MR1}, + new int[]{UID_N_MR1})); - assertEquals(0, mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, true).getList().size()); + assertEquals(0, mHelper.getNotificationChannels( + PKG_N_MR1, UID_N_MR1, true).getList().size()); // Not deleted mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false); - mHelper.onPackagesChanged(false, UserHandle.USER_SYSTEM, new String[]{PKG_N_MR1}, new int[]{ - UID_N_MR1}); + assertFalse(mHelper.onPackagesChanged(false, UserHandle.USER_SYSTEM, + new String[]{PKG_N_MR1}, new int[]{UID_N_MR1})); assertEquals(2, mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false).getList().size()); } @@ -1825,13 +1832,13 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testCreateChannel_updateName() { NotificationChannel nc = new NotificationChannel("id", "hello", IMPORTANCE_DEFAULT); - mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, nc, true, false); + assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, nc, true, false)); NotificationChannel actual = mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, "id", false); assertEquals("hello", actual.getName()); nc = new NotificationChannel("id", "goodbye", IMPORTANCE_HIGH); - mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, nc, true, false); + assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, nc, true, false)); actual = mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, "id", false); assertEquals("goodbye", actual.getName()); @@ -1845,14 +1852,14 @@ public class PreferencesHelperTest extends UiServiceTestCase { NotificationChannelGroup group = new NotificationChannelGroup("group", ""); mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, group, true); NotificationChannel nc = new NotificationChannel("id", "hello", IMPORTANCE_DEFAULT); - mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, nc, true, false); + assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, nc, true, false)); NotificationChannel actual = mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, "id", false); assertNull(actual.getGroup()); nc = new NotificationChannel("id", "hello", IMPORTANCE_HIGH); nc.setGroup(group.getId()); - mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, nc, true, false); + assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, nc, true, false)); actual = mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, "id", false); assertNotNull(actual.getGroup()); @@ -2109,7 +2116,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { NotificationChannel update = new NotificationChannel("A", "a", IMPORTANCE_LOW); update.setBypassDnd(true); - mHelper.createNotificationChannel(SYSTEM_PKG, SYSTEM_UID, update, true, false); + assertFalse(mHelper.createNotificationChannel(SYSTEM_PKG, SYSTEM_UID, update, true, false)); assertFalse(mHelper.getNotificationChannel(SYSTEM_PKG, SYSTEM_UID, "A", false) .canBypassDnd()); @@ -2122,7 +2129,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { NotificationChannel update = new NotificationChannel("A", "a", IMPORTANCE_LOW); update.setBypassDnd(true); - mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, update, true, true); + assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, update, true, true)); assertTrue(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, "A", false).canBypassDnd()); } diff --git a/services/usb/java/com/android/server/usb/UsbSerialReader.java b/services/usb/java/com/android/server/usb/UsbSerialReader.java index 32fc796e7e05..8ca77f0c63dc 100644 --- a/services/usb/java/com/android/server/usb/UsbSerialReader.java +++ b/services/usb/java/com/android/server/usb/UsbSerialReader.java @@ -85,22 +85,22 @@ class UsbSerialReader extends IUsbSerialReader.Stub { throw new RemoteException("package " + packageName + " cannot be found"); } packageTargetSdkVersion = pkg.applicationInfo.targetSdkVersion; - } finally { - Binder.restoreCallingIdentity(token); - } - if (packageTargetSdkVersion >= Build.VERSION_CODES.Q) { - if (mContext.checkPermission(android.Manifest.permission.MANAGE_USB, pid, uid) - == PackageManager.PERMISSION_DENIED) { - UsbUserSettingsManager settings = mSettingsManager.getSettingsForUser( - UserHandle.getUserId(uid)); + if (packageTargetSdkVersion >= Build.VERSION_CODES.Q) { + if (mContext.checkPermission(android.Manifest.permission.MANAGE_USB, pid, uid) + == PackageManager.PERMISSION_DENIED) { + UsbUserSettingsManager settings = mSettingsManager.getSettingsForUser( + UserHandle.getUserId(uid)); - if (mDevice instanceof UsbDevice) { - settings.checkPermission((UsbDevice) mDevice, packageName, uid); - } else { - settings.checkPermission((UsbAccessory) mDevice, uid); + if (mDevice instanceof UsbDevice) { + settings.checkPermission((UsbDevice) mDevice, packageName, uid); + } else { + settings.checkPermission((UsbAccessory) mDevice, uid); + } } } + } finally { + Binder.restoreCallingIdentity(token); } } diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 5175c1daf3c0..a125e319d35f 100755 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -1973,26 +1973,6 @@ public class CarrierConfigManager { public static final String KEY_CARRIER_WIFI_STRING_ARRAY = "carrier_wifi_string_array"; /** - * Base64 Encoding method the carrier will use for encoding encrypted IMSI and SSID. - * The value set as below: - * 2045 - RFC2045 (default value) - * 4648 - RFC4648 - * - * @hide - */ - public static final String KEY_IMSI_ENCODING_METHOD_INT = "imsi_encoding_method_int"; - - /** - * Defines the sequence of sending an encrypted IMSI identity for EAP-SIM/AKA authentication. - * The value set as below: - * 1 - encrypted IMSI as EAP-RESPONSE/IDENTITY (default one). - * 2 - anonymous as EAP-RESPONSE/IDENTITY -> encrypted IMSI as EAP-RESPONSE/AKA|SIM-IDENTITY. - * - * @hide - */ - public static final String KEY_EAP_IDENTITY_SEQUENCE_INT = "imsi_eap_identity_sequence_int"; - - /** * Time delay (in ms) after which we show the notification to switch the preferred * network. * @hide @@ -3265,8 +3245,6 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_STK_DISABLE_LAUNCH_BROWSER_BOOL, false); sDefaults.putBoolean(KEY_ALLOW_METERED_NETWORK_FOR_CERT_DOWNLOAD_BOOL, false); sDefaults.putStringArray(KEY_CARRIER_WIFI_STRING_ARRAY, null); - sDefaults.putInt(KEY_IMSI_ENCODING_METHOD_INT, 2045); - sDefaults.putInt(KEY_EAP_IDENTITY_SEQUENCE_INT, 1); sDefaults.putInt(KEY_PREF_NETWORK_NOTIFICATION_DELAY_INT, -1); sDefaults.putInt(KEY_EMERGENCY_NOTIFICATION_DELAY_INT, -1); sDefaults.putBoolean(KEY_ALLOW_USSD_REQUESTS_VIA_TELEPHONY_MANAGER_BOOL, true); diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index 984d8f705a65..5d39a2cc194a 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -341,6 +341,7 @@ public class ServiceState implements Parcelable { private String mOperatorAlphaLongRaw; private String mOperatorAlphaShortRaw; + private boolean mIsIwlanPreferred; /** * get String description of roaming type @@ -427,6 +428,7 @@ public class ServiceState implements Parcelable { mNrFrequencyRange = s.mNrFrequencyRange; mOperatorAlphaLongRaw = s.mOperatorAlphaLongRaw; mOperatorAlphaShortRaw = s.mOperatorAlphaShortRaw; + mIsIwlanPreferred = s.mIsIwlanPreferred; } /** @@ -463,6 +465,7 @@ public class ServiceState implements Parcelable { mNrFrequencyRange = in.readInt(); mOperatorAlphaLongRaw = in.readString(); mOperatorAlphaShortRaw = in.readString(); + mIsIwlanPreferred = in.readBoolean(); } public void writeToParcel(Parcel out, int flags) { @@ -492,6 +495,7 @@ public class ServiceState implements Parcelable { out.writeInt(mNrFrequencyRange); out.writeString(mOperatorAlphaLongRaw); out.writeString(mOperatorAlphaShortRaw); + out.writeBoolean(mIsIwlanPreferred); } public int describeContents() { @@ -853,7 +857,8 @@ public class ServiceState implements Parcelable { mNetworkRegistrationInfos, mNrFrequencyRange, mOperatorAlphaLongRaw, - mOperatorAlphaShortRaw); + mOperatorAlphaShortRaw, + mIsIwlanPreferred); } } @@ -885,7 +890,8 @@ public class ServiceState implements Parcelable { && 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 @@ public class ServiceState implements Parcelable { .append(", mNrFrequencyRange=").append(mNrFrequencyRange) .append(", mOperatorAlphaLongRaw=").append(mOperatorAlphaLongRaw) .append(", mOperatorAlphaShortRaw=").append(mOperatorAlphaShortRaw) + .append(", mIsIwlanPreferred=").append(mIsIwlanPreferred) .append("}").toString(); } } @@ -1085,6 +1092,7 @@ public class ServiceState implements Parcelable { } mOperatorAlphaLongRaw = null; mOperatorAlphaShortRaw = null; + mIsIwlanPreferred = false; } public void setStateOutOfService() { @@ -1459,20 +1467,9 @@ public class ServiceState implements Parcelable { /** @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 @@ public class ServiceState implements Parcelable { } } - /** @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 class ServiceState implements Parcelable { 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 d63c37accb37..2ba34c6c3453 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -5356,18 +5356,30 @@ public class TelephonyManager { * 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/android/telephony/emergency/EmergencyNumber.java b/telephony/java/android/telephony/emergency/EmergencyNumber.java index 7b5145740b08..511adf6ad65b 100644 --- a/telephony/java/android/telephony/emergency/EmergencyNumber.java +++ b/telephony/java/android/telephony/emergency/EmergencyNumber.java @@ -299,7 +299,7 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu * Get the dialing number of the emergency number. * * The character in the number string is only the dial pad - * character('0'-'9', '*', or '#'). For example: 911. + * character('0'-'9', '*', '+', or '#'). For example: 911. * * @return the dialing number. */ diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 63aded1c4b20..cf1323a328fe 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -1986,4 +1986,14 @@ interface ITelephony { * 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/PackageWatchdog/Android.bp b/tests/PackageWatchdog/Android.bp index b07996568839..88d92c4d94fe 100644 --- a/tests/PackageWatchdog/Android.bp +++ b/tests/PackageWatchdog/Android.bp @@ -18,11 +18,18 @@ android_test { srcs: ["src/**/*.java"], static_libs: [ "junit", + "mockito-target-extended-minus-junit4", "frameworks-base-testutils", "androidx.test.rules", "services.core", + "services.net", ], libs: ["android.test.runner"], + jni_libs: [ + // mockito-target-extended dependencies + "libdexmakerjvmtiagent", + "libstaticjvmtiagent", + ], platform_apis: true, test_suites: ["device-tests"], } diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java index eb19361d86a3..97716377ea2e 100644 --- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java +++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java @@ -27,6 +27,7 @@ import static org.junit.Assert.fail; import android.Manifest; import android.content.Context; import android.content.pm.VersionedPackage; +import android.net.NetworkStackClient; import android.os.Handler; import android.os.test.TestLooper; import android.provider.DeviceConfig; @@ -41,6 +42,8 @@ import com.android.server.PackageWatchdog.PackageHealthObserverImpact; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import java.io.File; import java.util.ArrayList; @@ -70,9 +73,12 @@ public class PackageWatchdogTest { private static final long SHORT_DURATION = TimeUnit.SECONDS.toMillis(1); private static final long LONG_DURATION = TimeUnit.SECONDS.toMillis(5); private TestLooper mTestLooper; + @Mock + private NetworkStackClient mNetworkStackClient; @Before public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); new File(InstrumentationRegistry.getContext().getFilesDir(), "package-watchdog.xml").delete(); adoptShellPermissions(Manifest.permission.READ_DEVICE_CONFIG); @@ -732,7 +738,8 @@ public class PackageWatchdogTest { new AtomicFile(new File(context.getFilesDir(), "package-watchdog.xml")); Handler handler = new Handler(mTestLooper.getLooper()); PackageWatchdog watchdog = - new PackageWatchdog(context, policyFile, handler, handler, controller); + new PackageWatchdog(context, policyFile, handler, handler, controller, + mNetworkStackClient); // Verify controller is not automatically started assertFalse(controller.mIsEnabled); if (withPackagesReady) { diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp index 1cf3d364a8d5..aec40558ad51 100644 --- a/tests/RollbackTest/Android.bp +++ b/tests/RollbackTest/Android.bp @@ -95,7 +95,7 @@ android_test { ":RollbackTestAppASplitV2", ], test_config: "RollbackTest.xml", - sdk_version: "test_current", + // TODO: sdk_version: "test_current" when Intent#resolveSystemservice is TestApi } java_test_host { diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java index 81629aaaec76..a9e20cdb191b 100644 --- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java @@ -28,6 +28,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; @@ -82,13 +83,31 @@ class RollbackTestUtils { * Returns -1 if the package is not currently installed. */ static long getInstalledVersion(String packageName) { + PackageInfo pi = getPackageInfo(packageName); + if (pi == null) { + return -1; + } else { + return pi.getLongVersionCode(); + } + } + + private static boolean isSystemAppWithoutUpdate(String packageName) { + PackageInfo pi = getPackageInfo(packageName); + if (pi == null) { + return false; + } else { + return ((pi.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) + && ((pi.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0); + } + } + + private static PackageInfo getPackageInfo(String packageName) { Context context = InstrumentationRegistry.getContext(); PackageManager pm = context.getPackageManager(); try { - PackageInfo info = pm.getPackageInfo(packageName, PackageManager.MATCH_APEX); - return info.getLongVersionCode(); + return pm.getPackageInfo(packageName, PackageManager.MATCH_APEX); } catch (PackageManager.NameNotFoundException e) { - return -1; + return null; } } @@ -109,8 +128,8 @@ class RollbackTestUtils { * @throws AssertionError if package can't be uninstalled. */ static void uninstall(String packageName) throws InterruptedException, IOException { - // No need to uninstall if the package isn't installed. - if (getInstalledVersion(packageName) == -1) { + // No need to uninstall if the package isn't installed or is installed on /system. + if (getInstalledVersion(packageName) == -1 || isSystemAppWithoutUpdate(packageName)) { return; } diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java index 1ddfa6ee54ce..1a29c4c11457 100644 --- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java @@ -21,7 +21,9 @@ import static com.android.tests.rollback.RollbackTestUtils.getUniqueRollbackInfo import android.Manifest; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.pm.VersionedPackage; import android.content.rollback.RollbackInfo; import android.content.rollback.RollbackManager; @@ -30,6 +32,8 @@ import androidx.test.InstrumentationRegistry; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -54,6 +58,8 @@ public class StagedRollbackTest { private static final String TEST_APP_A = "com.android.tests.rollback.testapp.A"; private static final String TEST_APP_A_V1 = "RollbackTestAppAv1.apk"; private static final String TEST_APP_A_CRASHING_V2 = "RollbackTestAppACrashingV2.apk"; + private static final String NETWORK_STACK_CONNECTOR_CLASS = + "android.net.INetworkStackConnector"; /** * Adopts common shell permissions needed for rollback tests. @@ -157,4 +163,44 @@ public class StagedRollbackTest { assertTrue(rollback.isStaged()); assertNotEquals(-1, rollback.getCommittedSessionId()); } + + @Test + public void resetNetworkStack() throws Exception { + RollbackManager rm = RollbackTestUtils.getRollbackManager(); + String networkStack = getNetworkStackPackageName(); + + rm.expireRollbackForPackage(networkStack); + RollbackTestUtils.uninstall(networkStack); + + assertNull(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), + networkStack)); + } + + @Test + public void assertNetworkStackRollbackAvailable() throws Exception { + RollbackManager rm = RollbackTestUtils.getRollbackManager(); + assertNotNull(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), + getNetworkStackPackageName())); + } + + @Test + public void assertNetworkStackRollbackCommitted() throws Exception { + RollbackManager rm = RollbackTestUtils.getRollbackManager(); + assertNotNull(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(), + getNetworkStackPackageName())); + } + + @Test + public void assertNoNetworkStackRollbackCommitted() throws Exception { + RollbackManager rm = RollbackTestUtils.getRollbackManager(); + assertNull(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(), + getNetworkStackPackageName())); + } + + private String getNetworkStackPackageName() { + Intent intent = new Intent(NETWORK_STACK_CONNECTOR_CLASS); + ComponentName comp = intent.resolveSystemService( + InstrumentationRegistry.getContext().getPackageManager(), 0); + return comp.getPackageName(); + } } diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java index 75a95adf460a..bad294794337 100644 --- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java +++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java @@ -23,6 +23,8 @@ import com.android.tradefed.log.LogUtil.CLog; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -43,6 +45,20 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { phase)); } + @Before + public void setUp() throws Exception { + // Disconnect internet so we can test network health triggered rollbacks + getDevice().executeShellCommand("svc wifi disable"); + getDevice().executeShellCommand("svc data disable"); + } + + @After + public void tearDown() throws Exception { + // Reconnect internet after testing network health triggered rollbacks + getDevice().executeShellCommand("svc wifi enable"); + getDevice().executeShellCommand("svc data enable"); + } + /** * Tests watchdog triggered staged rollbacks involving only apks. */ @@ -63,6 +79,90 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { } getDevice().waitForDeviceAvailable(); + runPhase("testBadApkOnlyConfirmRollback"); } + + /** + * Tests failed network health check triggers watchdog staged rollbacks. + */ + @Test + public void testNetworkFailedRollback() throws Exception { + // Remove available rollbacks and uninstall NetworkStack on /data/ + runPhase("resetNetworkStack"); + // Reduce health check deadline + getDevice().executeShellCommand("device_config put rollback " + + "watchdog_request_timeout_millis 300000"); + // Simulate re-installation of new NetworkStack with rollbacks enabled + getDevice().executeShellCommand("pm install -r --staged --enable-rollback " + + "/system/priv-app/NetworkStack/NetworkStack.apk"); + + // Sleep to allow writes to disk before reboot + Thread.sleep(5000); + // Reboot device to activate staged package + getDevice().reboot(); + getDevice().waitForDeviceAvailable(); + + // Verify rollback was enabled + runPhase("assertNetworkStackRollbackAvailable"); + + // Sleep for < health check deadline + Thread.sleep(5000); + // Verify rollback was not executed before health check deadline + runPhase("assertNoNetworkStackRollbackCommitted"); + try { + // This is expected to fail due to the device being rebooted out + // from underneath the test. If this fails for reasons other than + // the device reboot, those failures should result in failure of + // the assertNetworkStackExecutedRollback phase. + CLog.logAndDisplay(LogLevel.INFO, "Sleep and expect to fail while sleeping"); + // Sleep for > health check deadline + Thread.sleep(260000); + } catch (AssertionError e) { + // AssertionError is expected. + } + + getDevice().waitForDeviceAvailable(); + // Verify rollback was executed after health check deadline + runPhase("assertNetworkStackRollbackCommitted"); + } + + /** + * Tests passed network health check does not trigger watchdog staged rollbacks. + */ + @Test + public void testNetworkPassedDoesNotRollback() throws Exception { + // Remove available rollbacks and uninstall NetworkStack on /data/ + runPhase("resetNetworkStack"); + // Reduce health check deadline, here unlike the network failed case, we use + // a longer deadline because joining a network can take a much longer time for + // reasons external to the device than 'not joining' + getDevice().executeShellCommand("device_config put rollback " + + "watchdog_request_timeout_millis 300000"); + // Simulate re-installation of new NetworkStack with rollbacks enabled + getDevice().executeShellCommand("pm install -r --staged --enable-rollback " + + "/system/priv-app/NetworkStack/NetworkStack.apk"); + + // Sleep to allow writes to disk before reboot + Thread.sleep(5000); + // Reboot device to activate staged package + getDevice().reboot(); + getDevice().waitForDeviceAvailable(); + + // Verify rollback was enabled + runPhase("assertNetworkStackRollbackAvailable"); + + // Connect to internet so network health check passes + getDevice().executeShellCommand("svc wifi enable"); + getDevice().executeShellCommand("svc data enable"); + + // Wait for device available because emulator device may restart after turning + // on mobile data + getDevice().waitForDeviceAvailable(); + + // Sleep for > health check deadline + Thread.sleep(310000); + // Verify rollback was not executed after health check deadline + runPhase("assertNoNetworkStackRollbackCommitted"); + } } diff --git a/tests/net/java/android/net/NetworkStatsTest.java b/tests/net/java/android/net/NetworkStatsTest.java index b5b0384ca599..c16a0f446651 100644 --- a/tests/net/java/android/net/NetworkStatsTest.java +++ b/tests/net/java/android/net/NetworkStatsTest.java @@ -569,7 +569,7 @@ public class NetworkStatsTest { .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 @@ public class NetworkStatsTest { .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 @@ public class NetworkStatsTest { } @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/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java index 2cae2509026c..ce50bef53d75 100644 --- a/tests/net/java/com/android/server/connectivity/VpnTest.java +++ b/tests/net/java/com/android/server/connectivity/VpnTest.java @@ -727,94 +727,4 @@ public class VpnTest { "::/1", "8000::/2", "c000::/3", "e000::/4", "f000::/5", "f800::/6", "fe00::/8", "2605:ef80:e:af1d::/64"); } - - @Test - public void testProvidesRoutesToMostDestinations() { - final LinkProperties lp = new LinkProperties(); - - // Default route provides routes to all IPv4 destinations. - lp.addRoute(new RouteInfo(new IpPrefix("0.0.0.0/0"))); - assertTrue(Vpn.providesRoutesToMostDestinations(lp)); - - // Empty LP provides routes to no destination - lp.clear(); - assertFalse(Vpn.providesRoutesToMostDestinations(lp)); - - // All IPv4 routes except for local networks. This is the case most relevant - // to this function. It provides routes to almost the entire space. - // (clone the stream so that we can reuse it later) - publicIpV4Routes().forEach(s -> lp.addRoute(new RouteInfo(new IpPrefix(s)))); - assertTrue(Vpn.providesRoutesToMostDestinations(lp)); - - // Removing a 16-bit prefix, which is 65536 addresses. This is still enough to - // provide routes to "most" destinations. - lp.removeRoute(new RouteInfo(new IpPrefix("192.169.0.0/16"))); - assertTrue(Vpn.providesRoutesToMostDestinations(lp)); - - // Remove the /2 route, which represent a quarter of the available routing space. - // This LP does not provides routes to "most" destinations any more. - lp.removeRoute(new RouteInfo(new IpPrefix("64.0.0.0/2"))); - assertFalse(Vpn.providesRoutesToMostDestinations(lp)); - - lp.clear(); - publicIpV6Routes().forEach(s -> lp.addRoute(new RouteInfo(new IpPrefix(s)))); - assertTrue(Vpn.providesRoutesToMostDestinations(lp)); - - lp.removeRoute(new RouteInfo(new IpPrefix("::/1"))); - assertFalse(Vpn.providesRoutesToMostDestinations(lp)); - - // V6 does not provide sufficient coverage but v4 does - publicIpV4Routes().forEach(s -> lp.addRoute(new RouteInfo(new IpPrefix(s)))); - assertTrue(Vpn.providesRoutesToMostDestinations(lp)); - - // V4 still does - lp.removeRoute(new RouteInfo(new IpPrefix("192.169.0.0/16"))); - assertTrue(Vpn.providesRoutesToMostDestinations(lp)); - - // V4 does not any more - lp.removeRoute(new RouteInfo(new IpPrefix("64.0.0.0/2"))); - assertFalse(Vpn.providesRoutesToMostDestinations(lp)); - - // V4 does not, but V6 has sufficient coverage again - lp.addRoute(new RouteInfo(new IpPrefix("::/1"))); - assertTrue(Vpn.providesRoutesToMostDestinations(lp)); - - lp.clear(); - // V4-unreachable route should not be treated as sufficient coverage - lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE)); - assertFalse(Vpn.providesRoutesToMostDestinations(lp)); - - lp.clear(); - // V6-unreachable route should not be treated as sufficient coverage - lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE)); - assertFalse(Vpn.providesRoutesToMostDestinations(lp)); - } - - @Test - public void testDoesNotLockUpWithTooManyRoutes() { - final LinkProperties lp = new LinkProperties(); - final byte[] ad = new byte[4]; - // Actually evaluating this many routes under 1500ms is impossible on - // current hardware and for some time, as the algorithm is O(n²). - // Make sure the system has a safeguard against this and does not - // lock up. - final int MAX_ROUTES = 4000; - final long MAX_ALLOWED_TIME_MS = 1500; - for (int i = 0; i < MAX_ROUTES; ++i) { - ad[0] = (byte)((i >> 24) & 0xFF); - ad[1] = (byte)((i >> 16) & 0xFF); - ad[2] = (byte)((i >> 8) & 0xFF); - ad[3] = (byte)(i & 0xFF); - try { - lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.getByAddress(ad), 32))); - } catch (UnknownHostException e) { - // UnknownHostException is only thrown for an address of illegal length, - // which can't happen in the case above. - } - } - final long start = SystemClock.currentThreadTimeMillis(); - assertTrue(Vpn.providesRoutesToMostDestinations(lp)); - final long end = SystemClock.currentThreadTimeMillis(); - assertTrue(end - start < MAX_ALLOWED_TIME_MS); - } } diff --git a/tests/net/java/com/android/server/net/NetworkStatsAccessTest.java b/tests/net/java/com/android/server/net/NetworkStatsAccessTest.java index 6e725dd69cb7..858358c74f80 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 @@ public class NetworkStatsAccessTest { } 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 bce526d3ae29..d9f2c201fe37 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 com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_PO 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 @@ public class NetworkStatsServiceTest { 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 @@ public class NetworkStatsServiceTest { 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,70 @@ public class NetworkStatsServiceTest { } @Test + 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(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 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(), 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(); + + // 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(TEST_IFACE)}; + VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})}; expectNetworkStatsUidDetail(buildEmptyStats()); expectBandwidthControlCheck(); @@ -938,23 +1004,74 @@ public class NetworkStatsServiceTest { 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 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, 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)); + .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, 1000L, 100L, 1000L, 100L, 1); - assertUidTotal(sTemplateWifi, UID_BLUE, 500L, 50L, 500L, 50L, 1); - assertUidTotal(sTemplateWifi, UID_VPN, 150L, 0L, 150L, 0L, 2); + 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 @@ public class NetworkStatsServiceTest { // 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 @@ public class NetworkStatsServiceTest { } @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 @@ public class NetworkStatsServiceTest { 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 @@ public class NetworkStatsServiceTest { } @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 @@ public class NetworkStatsServiceTest { 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; } |