Merge "[Media ML] Let MCS manage MediaSession2" into sc-dev
diff --git a/Android.bp b/Android.bp
index e4c5c37..908280e 100644
--- a/Android.bp
+++ b/Android.bp
@@ -371,7 +371,6 @@
srcs: [
// Java/AIDL sources under frameworks/base
":framework-blobstore-sources",
- ":framework-connectivity-sources", // framework-connectivity is not yet a module
":framework-core-sources",
":framework-drm-sources",
":framework-graphics-nonupdatable-sources",
@@ -437,6 +436,7 @@
name: "framework-updatable-sources",
srcs: [
":framework-appsearch-sources",
+ ":framework-connectivity-sources",
":framework-graphics-srcs",
":framework-mediaprovider-sources",
":framework-permission-sources",
@@ -639,6 +639,7 @@
defaults: ["framework-aidl-export-defaults"],
srcs: [
":framework-non-updatable-sources",
+ ":framework-connectivity-sources",
"core/java/**/*.logtags",
],
// See comment on framework-atb-backward-compatibility module below
@@ -700,6 +701,8 @@
apex_available: ["//apex_available:platform"],
visibility: [
"//frameworks/base",
+ // TODO: remove when framework-connectivity can build against API
+ "//frameworks/base/packages/Connectivity/framework",
// TODO(b/147128803) remove the below lines
"//frameworks/base/apex/appsearch/framework",
"//frameworks/base/apex/blobstore/framework",
@@ -1452,6 +1455,7 @@
],
libs: [
"framework-annotations-lib",
+ "framework-connectivity",
"unsupportedappusage",
],
visibility: [
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 4bd524f..bff222e 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -58,6 +58,9 @@
local_include_dirs: [
"apex/media/aidl/stable",
"media/aidl",
+ // TODO: move to include-dirs for packages/modules/Connectivity when this moves out of
+ // frameworks/base
+ "packages/Connectivity/framework/aidl-export",
"telephony/java",
],
include_dirs: ["frameworks/av/aidl"],
@@ -310,6 +313,7 @@
"art.module.public.api.stubs",
"conscrypt.module.public.api.stubs",
"framework-appsearch.stubs",
+ "framework-connectivity.stubs",
"framework-graphics.stubs",
"framework-media.stubs",
"framework-mediaprovider.stubs",
@@ -334,6 +338,7 @@
"art.module.public.api.stubs",
"conscrypt.module.public.api.stubs",
"framework-appsearch.stubs.system",
+ "framework-connectivity.stubs.system",
"framework-graphics.stubs.system",
"framework-media.stubs.system",
"framework-mediaprovider.stubs.system",
@@ -374,6 +379,7 @@
"art.module.public.api.stubs",
"conscrypt.module.public.api.stubs",
"framework-appsearch.stubs.system",
+ "framework-connectivity.stubs.system",
"framework-graphics.stubs.system",
"framework-media.stubs.system",
"framework-mediaprovider.stubs.system",
diff --git a/api/Android.bp b/api/Android.bp
index 15c1dfc..1d4698e 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -40,6 +40,7 @@
":art.module.public.api{.public.api.txt}",
":conscrypt.module.public.api{.public.api.txt}",
":framework-appsearch{.public.api.txt}",
+ ":framework-connectivity{.public.api.txt}",
":framework-graphics{.public.api.txt}",
":framework-media{.public.api.txt}",
":framework-mediaprovider{.public.api.txt}",
@@ -95,6 +96,7 @@
":art.module.public.api{.public.stubs.source}",
":conscrypt.module.public.api{.public.stubs.source}",
":framework-appsearch{.public.stubs.source}",
+ ":framework-connectivity{.public.stubs.source}",
":framework-graphics{.public.stubs.source}",
":framework-media{.public.stubs.source}",
":framework-mediaprovider{.public.stubs.source}",
@@ -120,6 +122,7 @@
":art.module.public.api{.public.removed-api.txt}",
":conscrypt.module.public.api{.public.removed-api.txt}",
":framework-appsearch{.public.removed-api.txt}",
+ ":framework-connectivity{.public.removed-api.txt}",
":framework-graphics{.public.removed-api.txt}",
":framework-media{.public.removed-api.txt}",
":framework-mediaprovider{.public.removed-api.txt}",
@@ -155,6 +158,7 @@
srcs: [
":android.net.ipsec.ike{.system.api.txt}",
":framework-appsearch{.system.api.txt}",
+ ":framework-connectivity{.system.api.txt}",
":framework-graphics{.system.api.txt}",
":framework-media{.system.api.txt}",
":framework-mediaprovider{.system.api.txt}",
@@ -208,6 +212,7 @@
srcs: [
":android.net.ipsec.ike{.system.removed-api.txt}",
":framework-appsearch{.system.removed-api.txt}",
+ ":framework-connectivity{.system.removed-api.txt}",
":framework-graphics{.system.removed-api.txt}",
":framework-media{.system.removed-api.txt}",
":framework-mediaprovider{.system.removed-api.txt}",
@@ -243,6 +248,7 @@
srcs: [
":android.net.ipsec.ike{.module-lib.api.txt}",
":framework-appsearch{.module-lib.api.txt}",
+ ":framework-connectivity{.module-lib.api.txt}",
":framework-graphics{.module-lib.api.txt}",
":framework-media{.module-lib.api.txt}",
":framework-mediaprovider{.module-lib.api.txt}",
@@ -298,6 +304,7 @@
srcs: [
":android.net.ipsec.ike{.module-lib.removed-api.txt}",
":framework-appsearch{.module-lib.removed-api.txt}",
+ ":framework-connectivity{.module-lib.removed-api.txt}",
":framework-graphics{.module-lib.removed-api.txt}",
":framework-media{.module-lib.removed-api.txt}",
":framework-mediaprovider{.module-lib.removed-api.txt}",
diff --git a/core/api/current.txt b/core/api/current.txt
index 6bff005..481a712 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -21,7 +21,6 @@
field public static final String ACTIVITY_RECOGNITION = "android.permission.ACTIVITY_RECOGNITION";
field public static final String ADD_VOICEMAIL = "com.android.voicemail.permission.ADD_VOICEMAIL";
field public static final String ANSWER_PHONE_CALLS = "android.permission.ANSWER_PHONE_CALLS";
- field public static final String BACKGROUND_CAMERA = "android.permission.BACKGROUND_CAMERA";
field public static final String BATTERY_STATS = "android.permission.BATTERY_STATS";
field public static final String BIND_ACCESSIBILITY_SERVICE = "android.permission.BIND_ACCESSIBILITY_SERVICE";
field public static final String BIND_APPWIDGET = "android.permission.BIND_APPWIDGET";
@@ -134,7 +133,6 @@
field public static final String RECEIVE_SMS = "android.permission.RECEIVE_SMS";
field public static final String RECEIVE_WAP_PUSH = "android.permission.RECEIVE_WAP_PUSH";
field public static final String RECORD_AUDIO = "android.permission.RECORD_AUDIO";
- field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO";
field public static final String REORDER_TASKS = "android.permission.REORDER_TASKS";
field public static final String REQUEST_COMPANION_PROFILE_WATCH = "android.permission.REQUEST_COMPANION_PROFILE_WATCH";
field public static final String REQUEST_COMPANION_RUN_IN_BACKGROUND = "android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND";
@@ -12563,7 +12561,6 @@
field public static final String FEATURE_WIFI_DIRECT = "android.hardware.wifi.direct";
field public static final String FEATURE_WIFI_PASSPOINT = "android.hardware.wifi.passpoint";
field public static final String FEATURE_WIFI_RTT = "android.hardware.wifi.rtt";
- field public static final int FLAG_PERMISSION_ALLOWLIST_ROLE = 8; // 0x8
field public static final int FLAG_PERMISSION_WHITELIST_INSTALLER = 2; // 0x2
field public static final int FLAG_PERMISSION_WHITELIST_SYSTEM = 1; // 0x1
field public static final int FLAG_PERMISSION_WHITELIST_UPGRADE = 4; // 0x4
@@ -12702,7 +12699,6 @@
field public static final int FLAG_HARD_RESTRICTED = 4; // 0x4
field public static final int FLAG_IMMUTABLY_RESTRICTED = 16; // 0x10
field public static final int FLAG_INSTALLED = 1073741824; // 0x40000000
- field public static final int FLAG_INSTALLER_EXEMPT_IGNORED = 32; // 0x20
field public static final int FLAG_SOFT_RESTRICTED = 8; // 0x8
field public static final int PROTECTION_DANGEROUS = 1; // 0x1
field public static final int PROTECTION_FLAG_APPOP = 64; // 0x40
@@ -12902,6 +12898,7 @@
method public void reportShortcutUsed(String);
method public boolean requestPinShortcut(@NonNull android.content.pm.ShortcutInfo, @Nullable android.content.IntentSender);
method public boolean setDynamicShortcuts(@NonNull java.util.List<android.content.pm.ShortcutInfo>);
+ method public void updateShortcutVisibility(@NonNull String, @Nullable byte[], boolean);
method public boolean updateShortcuts(@NonNull java.util.List<android.content.pm.ShortcutInfo>);
field public static final int FLAG_MATCH_CACHED = 8; // 0x8
field public static final int FLAG_MATCH_DYNAMIC = 2; // 0x2
@@ -19293,8 +19290,8 @@
method @NonNull public android.location.GnssAntennaInfo.Builder setSignalGainCorrections(@Nullable android.location.GnssAntennaInfo.SphericalCorrections);
}
- @Deprecated public static interface GnssAntennaInfo.Listener {
- method @Deprecated public void onGnssAntennaInfoReceived(@NonNull java.util.List<android.location.GnssAntennaInfo>);
+ public static interface GnssAntennaInfo.Listener {
+ method public void onGnssAntennaInfoReceived(@NonNull java.util.List<android.location.GnssAntennaInfo>);
}
public static final class GnssAntennaInfo.PhaseCenterOffset implements android.os.Parcelable {
@@ -19683,7 +19680,7 @@
method public boolean hasProvider(@NonNull String);
method public boolean isLocationEnabled();
method public boolean isProviderEnabled(@NonNull String);
- method @Deprecated public boolean registerAntennaInfoListener(@NonNull java.util.concurrent.Executor, @NonNull android.location.GnssAntennaInfo.Listener);
+ method public boolean registerAntennaInfoListener(@NonNull java.util.concurrent.Executor, @NonNull android.location.GnssAntennaInfo.Listener);
method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerGnssMeasurementsCallback(@NonNull android.location.GnssMeasurementsEvent.Callback);
method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerGnssMeasurementsCallback(@NonNull android.location.GnssMeasurementsEvent.Callback, @Nullable android.os.Handler);
method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerGnssMeasurementsCallback(@NonNull java.util.concurrent.Executor, @NonNull android.location.GnssMeasurementsEvent.Callback);
@@ -19720,13 +19717,11 @@
method public void setTestProviderEnabled(@NonNull String, boolean);
method public void setTestProviderLocation(@NonNull String, @NonNull android.location.Location);
method @Deprecated public void setTestProviderStatus(@NonNull String, int, @Nullable android.os.Bundle, long);
- method @Deprecated public void unregisterAntennaInfoListener(@NonNull android.location.GnssAntennaInfo.Listener);
+ method public void unregisterAntennaInfoListener(@NonNull android.location.GnssAntennaInfo.Listener);
method public void unregisterGnssMeasurementsCallback(@NonNull android.location.GnssMeasurementsEvent.Callback);
method public void unregisterGnssNavigationMessageCallback(@NonNull android.location.GnssNavigationMessage.Callback);
method public void unregisterGnssStatusCallback(@NonNull android.location.GnssStatus.Callback);
- field public static final String ACTION_GNSS_ANTENNA_INFOS_CHANGED = "android.location.action.GNSS_ANTENNA_INFOS_CHANGED";
field public static final String ACTION_GNSS_CAPABILITIES_CHANGED = "android.location.action.GNSS_CAPABILITIES_CHANGED";
- field public static final String EXTRA_GNSS_ANTENNA_INFOS = "android.location.extra.GNSS_ANTENNA_INFOS";
field public static final String EXTRA_GNSS_CAPABILITIES = "android.location.extra.GNSS_CAPABILITIES";
field public static final String EXTRA_LOCATION_ENABLED = "android.location.extra.LOCATION_ENABLED";
field public static final String EXTRA_PROVIDER_ENABLED = "android.location.extra.PROVIDER_ENABLED";
@@ -19869,7 +19864,6 @@
method public int getFlags();
method public int getUsage();
method public int getVolumeControlStream();
- method @NonNull public static String usageToString(int);
method public void writeToParcel(android.os.Parcel, int);
field public static final int ALLOW_CAPTURE_BY_ALL = 1; // 0x1
field public static final int ALLOW_CAPTURE_BY_NONE = 3; // 0x3
@@ -21302,8 +21296,8 @@
public static final class MediaCodecInfo.AudioCapabilities {
method public android.util.Range<java.lang.Integer> getBitrateRange();
method @NonNull public android.util.Range<java.lang.Integer>[] getInputChannelCountRanges();
- method public int getMaxInputChannelCount();
- method public int getMinInputChannelCount();
+ method @IntRange(from=1, to=255) public int getMaxInputChannelCount();
+ method @IntRange(from=1, to=255) public int getMinInputChannelCount();
method public android.util.Range<java.lang.Integer>[] getSupportedSampleRateRanges();
method public int[] getSupportedSampleRates();
method public boolean isSampleRateSupported(int);
@@ -25827,161 +25821,6 @@
package android.net {
- public class CaptivePortal implements android.os.Parcelable {
- method public int describeContents();
- method public void ignoreNetwork();
- method public void reportCaptivePortalDismissed();
- method public void writeToParcel(android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.net.CaptivePortal> CREATOR;
- }
-
- public class ConnectivityDiagnosticsManager {
- method public void registerConnectivityDiagnosticsCallback(@NonNull android.net.NetworkRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback);
- method public void unregisterConnectivityDiagnosticsCallback(@NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback);
- }
-
- public abstract static class ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback {
- ctor public ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback();
- method public void onConnectivityReportAvailable(@NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityReport);
- method public void onDataStallSuspected(@NonNull android.net.ConnectivityDiagnosticsManager.DataStallReport);
- method public void onNetworkConnectivityReported(@NonNull android.net.Network, boolean);
- }
-
- public static final class ConnectivityDiagnosticsManager.ConnectivityReport implements android.os.Parcelable {
- ctor public ConnectivityDiagnosticsManager.ConnectivityReport(@NonNull android.net.Network, long, @NonNull android.net.LinkProperties, @NonNull android.net.NetworkCapabilities, @NonNull android.os.PersistableBundle);
- method public int describeContents();
- method @NonNull public android.os.PersistableBundle getAdditionalInfo();
- method @NonNull public android.net.LinkProperties getLinkProperties();
- method @NonNull public android.net.Network getNetwork();
- method @NonNull public android.net.NetworkCapabilities getNetworkCapabilities();
- method public long getReportTimestamp();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.net.ConnectivityDiagnosticsManager.ConnectivityReport> CREATOR;
- field public static final String KEY_NETWORK_PROBES_ATTEMPTED_BITMASK = "networkProbesAttempted";
- field public static final String KEY_NETWORK_PROBES_SUCCEEDED_BITMASK = "networkProbesSucceeded";
- field public static final String KEY_NETWORK_VALIDATION_RESULT = "networkValidationResult";
- field public static final int NETWORK_PROBE_DNS = 4; // 0x4
- field public static final int NETWORK_PROBE_FALLBACK = 32; // 0x20
- field public static final int NETWORK_PROBE_HTTP = 8; // 0x8
- field public static final int NETWORK_PROBE_HTTPS = 16; // 0x10
- field public static final int NETWORK_PROBE_PRIVATE_DNS = 64; // 0x40
- field public static final int NETWORK_VALIDATION_RESULT_INVALID = 0; // 0x0
- field public static final int NETWORK_VALIDATION_RESULT_PARTIALLY_VALID = 2; // 0x2
- field public static final int NETWORK_VALIDATION_RESULT_SKIPPED = 3; // 0x3
- field public static final int NETWORK_VALIDATION_RESULT_VALID = 1; // 0x1
- }
-
- public static final class ConnectivityDiagnosticsManager.DataStallReport implements android.os.Parcelable {
- ctor public ConnectivityDiagnosticsManager.DataStallReport(@NonNull android.net.Network, long, int, @NonNull android.net.LinkProperties, @NonNull android.net.NetworkCapabilities, @NonNull android.os.PersistableBundle);
- method public int describeContents();
- method public int getDetectionMethod();
- method @NonNull public android.net.LinkProperties getLinkProperties();
- method @NonNull public android.net.Network getNetwork();
- method @NonNull public android.net.NetworkCapabilities getNetworkCapabilities();
- method public long getReportTimestamp();
- method @NonNull public android.os.PersistableBundle getStallDetails();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.net.ConnectivityDiagnosticsManager.DataStallReport> CREATOR;
- field public static final int DETECTION_METHOD_DNS_EVENTS = 1; // 0x1
- field public static final int DETECTION_METHOD_TCP_METRICS = 2; // 0x2
- field public static final String KEY_DNS_CONSECUTIVE_TIMEOUTS = "dnsConsecutiveTimeouts";
- field public static final String KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS = "tcpMetricsCollectionPeriodMillis";
- field public static final String KEY_TCP_PACKET_FAIL_RATE = "tcpPacketFailRate";
- }
-
- public class ConnectivityManager {
- method public void addDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener);
- method public boolean bindProcessToNetwork(@Nullable android.net.Network);
- method @NonNull public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull android.net.IpSecManager.UdpEncapsulationSocket, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback);
- method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.Network getActiveNetwork();
- method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getActiveNetworkInfo();
- method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo[] getAllNetworkInfo();
- method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.Network[] getAllNetworks();
- method @Deprecated public boolean getBackgroundDataSetting();
- method @Nullable public android.net.Network getBoundNetworkForProcess();
- method public int getConnectionOwnerUid(int, @NonNull java.net.InetSocketAddress, @NonNull java.net.InetSocketAddress);
- method @Nullable public android.net.ProxyInfo getDefaultProxy();
- method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.LinkProperties getLinkProperties(@Nullable android.net.Network);
- method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public int getMultipathPreference(@Nullable android.net.Network);
- method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkCapabilities getNetworkCapabilities(@Nullable android.net.Network);
- method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getNetworkInfo(int);
- method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getNetworkInfo(@Nullable android.net.Network);
- method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public int getNetworkPreference();
- method @Nullable public byte[] getNetworkWatchlistConfigHash();
- method @Deprecated @Nullable public static android.net.Network getProcessDefaultNetwork();
- method public int getRestrictBackgroundStatus();
- method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public boolean isActiveNetworkMetered();
- method public boolean isDefaultNetworkActive();
- method @Deprecated public static boolean isNetworkTypeValid(int);
- method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback);
- method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
- method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback);
- method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
- method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.app.PendingIntent);
- method public void releaseNetworkRequest(@NonNull android.app.PendingIntent);
- method public void removeDefaultNetworkActiveListener(@NonNull android.net.ConnectivityManager.OnNetworkActiveListener);
- method @Deprecated public void reportBadNetwork(@Nullable android.net.Network);
- method public void reportNetworkConnectivity(@Nullable android.net.Network, boolean);
- method public boolean requestBandwidthUpdate(@NonNull android.net.Network);
- method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback);
- method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
- method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, int);
- method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler, int);
- method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.app.PendingIntent);
- method @Deprecated public void setNetworkPreference(int);
- method @Deprecated public static boolean setProcessDefaultNetwork(@Nullable android.net.Network);
- method public void unregisterNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback);
- method public void unregisterNetworkCallback(@NonNull android.app.PendingIntent);
- field @Deprecated public static final String ACTION_BACKGROUND_DATA_SETTING_CHANGED = "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED";
- field public static final String ACTION_CAPTIVE_PORTAL_SIGN_IN = "android.net.conn.CAPTIVE_PORTAL";
- field public static final String ACTION_RESTRICT_BACKGROUND_CHANGED = "android.net.conn.RESTRICT_BACKGROUND_CHANGED";
- field @Deprecated public static final String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
- field @Deprecated public static final int DEFAULT_NETWORK_PREFERENCE = 1; // 0x1
- field public static final String EXTRA_CAPTIVE_PORTAL = "android.net.extra.CAPTIVE_PORTAL";
- field public static final String EXTRA_CAPTIVE_PORTAL_URL = "android.net.extra.CAPTIVE_PORTAL_URL";
- field @Deprecated public static final String EXTRA_EXTRA_INFO = "extraInfo";
- field @Deprecated public static final String EXTRA_IS_FAILOVER = "isFailover";
- field public static final String EXTRA_NETWORK = "android.net.extra.NETWORK";
- field @Deprecated public static final String EXTRA_NETWORK_INFO = "networkInfo";
- field public static final String EXTRA_NETWORK_REQUEST = "android.net.extra.NETWORK_REQUEST";
- field @Deprecated public static final String EXTRA_NETWORK_TYPE = "networkType";
- field public static final String EXTRA_NO_CONNECTIVITY = "noConnectivity";
- field @Deprecated public static final String EXTRA_OTHER_NETWORK_INFO = "otherNetwork";
- field public static final String EXTRA_REASON = "reason";
- field public static final int MULTIPATH_PREFERENCE_HANDOVER = 1; // 0x1
- field public static final int MULTIPATH_PREFERENCE_PERFORMANCE = 4; // 0x4
- field public static final int MULTIPATH_PREFERENCE_RELIABILITY = 2; // 0x2
- field public static final int RESTRICT_BACKGROUND_STATUS_DISABLED = 1; // 0x1
- field public static final int RESTRICT_BACKGROUND_STATUS_ENABLED = 3; // 0x3
- field public static final int RESTRICT_BACKGROUND_STATUS_WHITELISTED = 2; // 0x2
- field @Deprecated public static final int TYPE_BLUETOOTH = 7; // 0x7
- field @Deprecated public static final int TYPE_DUMMY = 8; // 0x8
- field @Deprecated public static final int TYPE_ETHERNET = 9; // 0x9
- field @Deprecated public static final int TYPE_MOBILE = 0; // 0x0
- field @Deprecated public static final int TYPE_MOBILE_DUN = 4; // 0x4
- field @Deprecated public static final int TYPE_MOBILE_HIPRI = 5; // 0x5
- field @Deprecated public static final int TYPE_MOBILE_MMS = 2; // 0x2
- field @Deprecated public static final int TYPE_MOBILE_SUPL = 3; // 0x3
- field @Deprecated public static final int TYPE_VPN = 17; // 0x11
- field @Deprecated public static final int TYPE_WIFI = 1; // 0x1
- field @Deprecated public static final int TYPE_WIMAX = 6; // 0x6
- }
-
- public static class ConnectivityManager.NetworkCallback {
- ctor public ConnectivityManager.NetworkCallback();
- method public void onAvailable(@NonNull android.net.Network);
- method public void onBlockedStatusChanged(@NonNull android.net.Network, boolean);
- method public void onCapabilitiesChanged(@NonNull android.net.Network, @NonNull android.net.NetworkCapabilities);
- method public void onLinkPropertiesChanged(@NonNull android.net.Network, @NonNull android.net.LinkProperties);
- method public void onLosing(@NonNull android.net.Network, int);
- method public void onLost(@NonNull android.net.Network);
- method public void onUnavailable();
- }
-
- public static interface ConnectivityManager.OnNetworkActiveListener {
- method public void onNetworkActive();
- }
-
public class Credentials {
ctor public Credentials(int, int, int);
method public int getGid();
@@ -25989,46 +25828,6 @@
method public int getUid();
}
- public class DhcpInfo implements android.os.Parcelable {
- ctor public DhcpInfo();
- method public int describeContents();
- method public void writeToParcel(android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.net.DhcpInfo> CREATOR;
- field public int dns1;
- field public int dns2;
- field public int gateway;
- field public int ipAddress;
- field public int leaseDuration;
- field public int netmask;
- field public int serverAddress;
- }
-
- public final class DnsResolver {
- method @NonNull public static android.net.DnsResolver getInstance();
- method public void query(@Nullable android.net.Network, @NonNull String, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super java.util.List<java.net.InetAddress>>);
- method public void query(@Nullable android.net.Network, @NonNull String, int, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super java.util.List<java.net.InetAddress>>);
- method public void rawQuery(@Nullable android.net.Network, @NonNull byte[], int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super byte[]>);
- method public void rawQuery(@Nullable android.net.Network, @NonNull String, int, int, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super byte[]>);
- field public static final int CLASS_IN = 1; // 0x1
- field public static final int ERROR_PARSE = 0; // 0x0
- field public static final int ERROR_SYSTEM = 1; // 0x1
- field public static final int FLAG_EMPTY = 0; // 0x0
- field public static final int FLAG_NO_CACHE_LOOKUP = 4; // 0x4
- field public static final int FLAG_NO_CACHE_STORE = 2; // 0x2
- field public static final int FLAG_NO_RETRY = 1; // 0x1
- field public static final int TYPE_A = 1; // 0x1
- field public static final int TYPE_AAAA = 28; // 0x1c
- }
-
- public static interface DnsResolver.Callback<T> {
- method public void onAnswer(@NonNull T, int);
- method public void onError(@NonNull android.net.DnsResolver.DnsException);
- }
-
- public static class DnsResolver.DnsException extends java.lang.Exception {
- field public final int code;
- }
-
public final class Ikev2VpnProfile extends android.net.PlatformVpnProfile {
method @NonNull public java.util.List<java.lang.String> getAllowedAlgorithms();
method public int getMaxMtu();
@@ -26058,21 +25857,6 @@
method @NonNull public android.net.Ikev2VpnProfile.Builder setProxy(@Nullable android.net.ProxyInfo);
}
- public class InetAddresses {
- method public static boolean isNumericAddress(@NonNull String);
- method @NonNull public static java.net.InetAddress parseNumericAddress(@NonNull String);
- }
-
- public final class IpPrefix implements android.os.Parcelable {
- method public boolean contains(@NonNull java.net.InetAddress);
- method public int describeContents();
- method @NonNull public java.net.InetAddress getAddress();
- method @IntRange(from=0, to=128) public int getPrefixLength();
- method @NonNull public byte[] getRawAddress();
- method public void writeToParcel(android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.net.IpPrefix> CREATOR;
- }
-
public final class IpSecAlgorithm implements android.os.Parcelable {
ctor public IpSecAlgorithm(@NonNull String, @NonNull byte[]);
ctor public IpSecAlgorithm(@NonNull String, @NonNull byte[], int);
@@ -26141,45 +25925,6 @@
method @NonNull public android.net.IpSecTransform.Builder setIpv4Encapsulation(@NonNull android.net.IpSecManager.UdpEncapsulationSocket, int);
}
- public class LinkAddress implements android.os.Parcelable {
- method public int describeContents();
- method public java.net.InetAddress getAddress();
- method public int getFlags();
- method @IntRange(from=0, to=128) public int getPrefixLength();
- method public int getScope();
- method public void writeToParcel(android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.net.LinkAddress> CREATOR;
- }
-
- public final class LinkProperties implements android.os.Parcelable {
- ctor public LinkProperties();
- method public boolean addRoute(@NonNull android.net.RouteInfo);
- method public void clear();
- method public int describeContents();
- method @Nullable public java.net.Inet4Address getDhcpServerAddress();
- method @NonNull public java.util.List<java.net.InetAddress> getDnsServers();
- method @Nullable public String getDomains();
- method @Nullable public android.net.ProxyInfo getHttpProxy();
- method @Nullable public String getInterfaceName();
- method @NonNull public java.util.List<android.net.LinkAddress> getLinkAddresses();
- method public int getMtu();
- method @Nullable public android.net.IpPrefix getNat64Prefix();
- method @Nullable public String getPrivateDnsServerName();
- method @NonNull public java.util.List<android.net.RouteInfo> getRoutes();
- method public boolean isPrivateDnsActive();
- method public boolean isWakeOnLanSupported();
- method public void setDhcpServerAddress(@Nullable java.net.Inet4Address);
- method public void setDnsServers(@NonNull java.util.Collection<java.net.InetAddress>);
- method public void setDomains(@Nullable String);
- method public void setHttpProxy(@Nullable android.net.ProxyInfo);
- method public void setInterfaceName(@Nullable String);
- method public void setLinkAddresses(@NonNull java.util.Collection<android.net.LinkAddress>);
- method public void setMtu(int);
- method public void setNat64Prefix(@Nullable android.net.IpPrefix);
- method public void writeToParcel(android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.net.LinkProperties> CREATOR;
- }
-
public class LocalServerSocket implements java.io.Closeable {
ctor public LocalServerSocket(String) throws java.io.IOException;
ctor public LocalServerSocket(java.io.FileDescriptor) throws java.io.IOException;
@@ -26235,24 +25980,6 @@
enum_constant public static final android.net.LocalSocketAddress.Namespace RESERVED;
}
- public final class MacAddress implements android.os.Parcelable {
- method public int describeContents();
- method @NonNull public static android.net.MacAddress fromBytes(@NonNull byte[]);
- method @NonNull public static android.net.MacAddress fromString(@NonNull String);
- method public int getAddressType();
- method @Nullable public java.net.Inet6Address getLinkLocalIpv6FromEui48Mac();
- method public boolean isLocallyAssigned();
- method public boolean matches(@NonNull android.net.MacAddress, @NonNull android.net.MacAddress);
- method @NonNull public byte[] toByteArray();
- method @NonNull public String toOuiString();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.net.MacAddress BROADCAST_ADDRESS;
- field @NonNull public static final android.os.Parcelable.Creator<android.net.MacAddress> CREATOR;
- field public static final int TYPE_BROADCAST = 3; // 0x3
- field public static final int TYPE_MULTICAST = 2; // 0x2
- field public static final int TYPE_UNICAST = 1; // 0x1
- }
-
public class MailTo {
method public String getBody();
method public String getCc();
@@ -26264,139 +25991,6 @@
field public static final String MAILTO_SCHEME = "mailto:";
}
- public class Network implements android.os.Parcelable {
- method public void bindSocket(java.net.DatagramSocket) throws java.io.IOException;
- method public void bindSocket(java.net.Socket) throws java.io.IOException;
- method public void bindSocket(java.io.FileDescriptor) throws java.io.IOException;
- method public int describeContents();
- method public static android.net.Network fromNetworkHandle(long);
- method public java.net.InetAddress[] getAllByName(String) throws java.net.UnknownHostException;
- method public java.net.InetAddress getByName(String) throws java.net.UnknownHostException;
- method public long getNetworkHandle();
- method public javax.net.SocketFactory getSocketFactory();
- method public java.net.URLConnection openConnection(java.net.URL) throws java.io.IOException;
- method public java.net.URLConnection openConnection(java.net.URL, java.net.Proxy) throws java.io.IOException;
- method public void writeToParcel(android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.net.Network> CREATOR;
- }
-
- public final class NetworkCapabilities implements android.os.Parcelable {
- ctor public NetworkCapabilities();
- ctor public NetworkCapabilities(android.net.NetworkCapabilities);
- method public int describeContents();
- method public int getLinkDownstreamBandwidthKbps();
- method public int getLinkUpstreamBandwidthKbps();
- method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier();
- method public int getOwnerUid();
- method public int getSignalStrength();
- method @Nullable public android.net.TransportInfo getTransportInfo();
- method public boolean hasCapability(int);
- method public boolean hasTransport(int);
- method public void writeToParcel(android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkCapabilities> CREATOR;
- field public static final int NET_CAPABILITY_CAPTIVE_PORTAL = 17; // 0x11
- field public static final int NET_CAPABILITY_CBS = 5; // 0x5
- field public static final int NET_CAPABILITY_DUN = 2; // 0x2
- field public static final int NET_CAPABILITY_EIMS = 10; // 0xa
- field public static final int NET_CAPABILITY_ENTERPRISE = 29; // 0x1d
- field public static final int NET_CAPABILITY_FOREGROUND = 19; // 0x13
- field public static final int NET_CAPABILITY_FOTA = 3; // 0x3
- field public static final int NET_CAPABILITY_IA = 7; // 0x7
- field public static final int NET_CAPABILITY_IMS = 4; // 0x4
- field public static final int NET_CAPABILITY_INTERNET = 12; // 0xc
- field public static final int NET_CAPABILITY_MCX = 23; // 0x17
- field public static final int NET_CAPABILITY_MMS = 0; // 0x0
- field public static final int NET_CAPABILITY_NOT_CONGESTED = 20; // 0x14
- field public static final int NET_CAPABILITY_NOT_METERED = 11; // 0xb
- field public static final int NET_CAPABILITY_NOT_RESTRICTED = 13; // 0xd
- field public static final int NET_CAPABILITY_NOT_ROAMING = 18; // 0x12
- field public static final int NET_CAPABILITY_NOT_SUSPENDED = 21; // 0x15
- field public static final int NET_CAPABILITY_NOT_VPN = 15; // 0xf
- field public static final int NET_CAPABILITY_RCS = 8; // 0x8
- field public static final int NET_CAPABILITY_SUPL = 1; // 0x1
- field public static final int NET_CAPABILITY_TEMPORARILY_NOT_METERED = 25; // 0x19
- field public static final int NET_CAPABILITY_TRUSTED = 14; // 0xe
- field public static final int NET_CAPABILITY_VALIDATED = 16; // 0x10
- field public static final int NET_CAPABILITY_WIFI_P2P = 6; // 0x6
- field public static final int NET_CAPABILITY_XCAP = 9; // 0x9
- field public static final int SIGNAL_STRENGTH_UNSPECIFIED = -2147483648; // 0x80000000
- field public static final int TRANSPORT_BLUETOOTH = 2; // 0x2
- field public static final int TRANSPORT_CELLULAR = 0; // 0x0
- field public static final int TRANSPORT_ETHERNET = 3; // 0x3
- field public static final int TRANSPORT_LOWPAN = 6; // 0x6
- field public static final int TRANSPORT_VPN = 4; // 0x4
- field public static final int TRANSPORT_WIFI = 1; // 0x1
- field public static final int TRANSPORT_WIFI_AWARE = 5; // 0x5
- }
-
- @Deprecated public class NetworkInfo implements android.os.Parcelable {
- ctor @Deprecated public NetworkInfo(int, int, @Nullable String, @Nullable String);
- method @Deprecated public int describeContents();
- method @Deprecated @NonNull public android.net.NetworkInfo.DetailedState getDetailedState();
- method @Deprecated public String getExtraInfo();
- method @Deprecated public String getReason();
- method @Deprecated public android.net.NetworkInfo.State getState();
- method @Deprecated public int getSubtype();
- method @Deprecated public String getSubtypeName();
- method @Deprecated public int getType();
- method @Deprecated public String getTypeName();
- method @Deprecated public boolean isAvailable();
- method @Deprecated public boolean isConnected();
- method @Deprecated public boolean isConnectedOrConnecting();
- method @Deprecated public boolean isFailover();
- method @Deprecated public boolean isRoaming();
- method @Deprecated public void setDetailedState(@NonNull android.net.NetworkInfo.DetailedState, @Nullable String, @Nullable String);
- method @Deprecated public void writeToParcel(android.os.Parcel, int);
- field @Deprecated @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkInfo> CREATOR;
- }
-
- @Deprecated public enum NetworkInfo.DetailedState {
- enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState AUTHENTICATING;
- enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState BLOCKED;
- enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState CAPTIVE_PORTAL_CHECK;
- enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState CONNECTED;
- enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState CONNECTING;
- enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState DISCONNECTED;
- enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState DISCONNECTING;
- enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState FAILED;
- enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState IDLE;
- enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState OBTAINING_IPADDR;
- enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState SCANNING;
- enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState SUSPENDED;
- enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState VERIFYING_POOR_LINK;
- }
-
- @Deprecated public enum NetworkInfo.State {
- enum_constant @Deprecated public static final android.net.NetworkInfo.State CONNECTED;
- enum_constant @Deprecated public static final android.net.NetworkInfo.State CONNECTING;
- enum_constant @Deprecated public static final android.net.NetworkInfo.State DISCONNECTED;
- enum_constant @Deprecated public static final android.net.NetworkInfo.State DISCONNECTING;
- enum_constant @Deprecated public static final android.net.NetworkInfo.State SUSPENDED;
- enum_constant @Deprecated public static final android.net.NetworkInfo.State UNKNOWN;
- }
-
- public class NetworkRequest implements android.os.Parcelable {
- method public boolean canBeSatisfiedBy(@Nullable android.net.NetworkCapabilities);
- method public int describeContents();
- method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier();
- method public boolean hasCapability(int);
- method public boolean hasTransport(int);
- method public void writeToParcel(android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkRequest> CREATOR;
- }
-
- public static class NetworkRequest.Builder {
- ctor public NetworkRequest.Builder();
- method public android.net.NetworkRequest.Builder addCapability(int);
- method public android.net.NetworkRequest.Builder addTransportType(int);
- method public android.net.NetworkRequest build();
- method @NonNull public android.net.NetworkRequest.Builder clearCapabilities();
- method public android.net.NetworkRequest.Builder removeCapability(int);
- method public android.net.NetworkRequest.Builder removeTransportType(int);
- method @Deprecated public android.net.NetworkRequest.Builder setNetworkSpecifier(String);
- method public android.net.NetworkRequest.Builder setNetworkSpecifier(android.net.NetworkSpecifier);
- }
-
public abstract class NetworkSpecifier {
ctor public NetworkSpecifier();
}
@@ -26413,44 +26007,6 @@
field public static final int TYPE_IKEV2_IPSEC_USER_PASS = 6; // 0x6
}
- public final class Proxy {
- ctor public Proxy();
- method @Deprecated public static String getDefaultHost();
- method @Deprecated public static int getDefaultPort();
- method @Deprecated public static String getHost(android.content.Context);
- method @Deprecated public static int getPort(android.content.Context);
- field @Deprecated public static final String EXTRA_PROXY_INFO = "android.intent.extra.PROXY_INFO";
- field public static final String PROXY_CHANGE_ACTION = "android.intent.action.PROXY_CHANGE";
- }
-
- public class ProxyInfo implements android.os.Parcelable {
- ctor public ProxyInfo(@Nullable android.net.ProxyInfo);
- method public static android.net.ProxyInfo buildDirectProxy(String, int);
- method public static android.net.ProxyInfo buildDirectProxy(String, int, java.util.List<java.lang.String>);
- method public static android.net.ProxyInfo buildPacProxy(android.net.Uri);
- method @NonNull public static android.net.ProxyInfo buildPacProxy(@NonNull android.net.Uri, int);
- method public int describeContents();
- method public String[] getExclusionList();
- method public String getHost();
- method public android.net.Uri getPacFileUrl();
- method public int getPort();
- method public boolean isValid();
- method public void writeToParcel(android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.net.ProxyInfo> CREATOR;
- }
-
- public final class RouteInfo implements android.os.Parcelable {
- method public int describeContents();
- method @NonNull public android.net.IpPrefix getDestination();
- method @Nullable public java.net.InetAddress getGateway();
- method @Nullable public String getInterface();
- method public boolean hasGateway();
- method public boolean isDefaultRoute();
- method public boolean matches(java.net.InetAddress);
- method public void writeToParcel(android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.net.RouteInfo> CREATOR;
- }
-
@Deprecated public class SSLCertificateSocketFactory extends javax.net.ssl.SSLSocketFactory {
ctor @Deprecated public SSLCertificateSocketFactory(int);
method @Deprecated public java.net.Socket createSocket(java.net.Socket, String, int, boolean) throws java.io.IOException;
@@ -26476,30 +26032,6 @@
ctor public SSLSessionCache(android.content.Context);
}
- public abstract class SocketKeepalive implements java.lang.AutoCloseable {
- method public final void close();
- method public final void start(@IntRange(from=0xa, to=0xe10) int);
- method public final void stop();
- field public static final int ERROR_HARDWARE_ERROR = -31; // 0xffffffe1
- field public static final int ERROR_INSUFFICIENT_RESOURCES = -32; // 0xffffffe0
- field public static final int ERROR_INVALID_INTERVAL = -24; // 0xffffffe8
- field public static final int ERROR_INVALID_IP_ADDRESS = -21; // 0xffffffeb
- field public static final int ERROR_INVALID_LENGTH = -23; // 0xffffffe9
- field public static final int ERROR_INVALID_NETWORK = -20; // 0xffffffec
- field public static final int ERROR_INVALID_PORT = -22; // 0xffffffea
- field public static final int ERROR_INVALID_SOCKET = -25; // 0xffffffe7
- field public static final int ERROR_SOCKET_NOT_IDLE = -26; // 0xffffffe6
- field public static final int ERROR_UNSUPPORTED = -30; // 0xffffffe2
- }
-
- public static class SocketKeepalive.Callback {
- ctor public SocketKeepalive.Callback();
- method public void onDataReceived();
- method public void onError(int);
- method public void onStarted();
- method public void onStopped();
- }
-
public final class TelephonyNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
method public int describeContents();
method public int getSubscriptionId();
@@ -26557,9 +26089,6 @@
field public static final int UNSUPPORTED = -1; // 0xffffffff
}
- public interface TransportInfo {
- }
-
public abstract class Uri implements java.lang.Comparable<android.net.Uri> android.os.Parcelable {
method public abstract android.net.Uri.Builder buildUpon();
method public int compareTo(android.net.Uri);
@@ -31932,10 +31461,10 @@
method public static android.content.Intent createUserCreationIntent(@Nullable String, @Nullable String, @Nullable String, @Nullable android.os.PersistableBundle);
method @WorkerThread public android.os.Bundle getApplicationRestrictions(String);
method public long getSerialNumberForUser(android.os.UserHandle);
- method @RequiresPermission("android.permission.MANAGE_USERS") public int getUserCount();
+ method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.CREATE_USERS"}) public int getUserCount();
method public long getUserCreationTime(android.os.UserHandle);
method public android.os.UserHandle getUserForSerialNumber(long);
- method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}, conditional=true) public String getUserName();
+ method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED, "android.permission.CREATE_USERS"}, conditional=true) public String getUserName();
method public java.util.List<android.os.UserHandle> getUserProfiles();
method public android.os.Bundle getUserRestrictions();
method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.os.Bundle getUserRestrictions(android.os.UserHandle);
@@ -40257,7 +39786,7 @@
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getVoiceMailNumber(android.telecom.PhoneAccountHandle);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handleMmi(String);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handleMmi(String, android.telecom.PhoneAccountHandle);
- method public boolean hasCompanionInCallServiceAccess();
+ method public boolean hasManageOngoingCallsPermission();
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isInCall();
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isInManagedCall();
method public boolean isIncomingCallPermitted(android.telecom.PhoneAccountHandle);
@@ -40909,7 +40438,7 @@
}
public static final class CarrierConfigManager.ImsServiceEntitlement {
- field public static final String KEY_AES_URL_STRING = "imsserviceentitlement.aes_url_string";
+ field public static final String KEY_ENTITLEMENT_SERVER_URL_STRING = "imsserviceentitlement.entitlement_server_url_string";
field public static final String KEY_PREFIX = "imsserviceentitlement.";
}
@@ -42137,7 +41666,6 @@
public final class SignalStrengthUpdateRequest implements android.os.Parcelable {
method public int describeContents();
- method @NonNull public android.os.IBinder getLiveToken();
method @NonNull public java.util.Collection<android.telephony.SignalThresholdInfo> getSignalThresholdInfos();
method public boolean isReportingRequestedWhileIdle();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -48038,6 +47566,32 @@
method public void onScaleEnd(android.view.ScaleGestureDetector);
}
+ @UiThread public interface ScrollCaptureCallback {
+ method public void onScrollCaptureEnd(@NonNull Runnable);
+ method public void onScrollCaptureImageRequest(@NonNull android.view.ScrollCaptureSession, @NonNull android.os.CancellationSignal, @NonNull android.graphics.Rect, @NonNull java.util.function.Consumer<android.graphics.Rect>);
+ method public void onScrollCaptureSearch(@NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.graphics.Rect>);
+ method public void onScrollCaptureStart(@NonNull android.view.ScrollCaptureSession, @NonNull android.os.CancellationSignal, @NonNull Runnable);
+ }
+
+ public class ScrollCaptureSession {
+ ctor public ScrollCaptureSession(@NonNull android.view.Surface, @NonNull android.graphics.Rect, @NonNull android.graphics.Point);
+ method @NonNull public android.graphics.Point getPositionInWindow();
+ method @NonNull public android.graphics.Rect getScrollBounds();
+ method @NonNull public android.view.Surface getSurface();
+ }
+
+ public final class ScrollCaptureTarget {
+ ctor public ScrollCaptureTarget(@NonNull android.view.View, @NonNull android.graphics.Rect, @NonNull android.graphics.Point, @NonNull android.view.ScrollCaptureCallback);
+ method @NonNull public android.view.ScrollCaptureCallback getCallback();
+ method @NonNull public android.view.View getContainingView();
+ method public int getHint();
+ method @NonNull public android.graphics.Rect getLocalVisibleRect();
+ method @NonNull public android.graphics.Point getPositionInWindow();
+ method @Nullable public android.graphics.Rect getScrollBounds();
+ method public void setScrollBounds(@Nullable android.graphics.Rect);
+ method @UiThread public void updatePositionInWindow();
+ }
+
public class SearchEvent {
ctor public SearchEvent(android.view.InputDevice);
method public android.view.InputDevice getInputDevice();
@@ -48364,6 +47918,7 @@
method public void dispatchProvideStructure(android.view.ViewStructure);
method protected void dispatchRestoreInstanceState(android.util.SparseArray<android.os.Parcelable>);
method protected void dispatchSaveInstanceState(android.util.SparseArray<android.os.Parcelable>);
+ method public void dispatchScrollCaptureSearch(@NonNull android.graphics.Rect, @NonNull android.graphics.Point, @NonNull java.util.function.Consumer<android.view.ScrollCaptureTarget>);
method protected void dispatchSetActivated(boolean);
method protected void dispatchSetPressed(boolean);
method protected void dispatchSetSelected(boolean);
@@ -48523,6 +48078,7 @@
method public int getScrollBarFadeDuration();
method public int getScrollBarSize();
method public int getScrollBarStyle();
+ method public int getScrollCaptureHint();
method public int getScrollIndicators();
method public final int getScrollX();
method public final int getScrollY();
@@ -48691,6 +48247,7 @@
method public void onRtlPropertiesChanged(int);
method @CallSuper @Nullable protected android.os.Parcelable onSaveInstanceState();
method public void onScreenStateChanged(int);
+ method public void onScrollCaptureSearch(@NonNull android.graphics.Rect, @NonNull android.graphics.Point, @NonNull java.util.function.Consumer<android.view.ScrollCaptureTarget>);
method protected void onScrollChanged(int, int, int, int);
method protected boolean onSetAlpha(int);
method protected void onSizeChanged(int, int, int, int);
@@ -48873,6 +48430,8 @@
method public void setScrollBarFadeDuration(int);
method public void setScrollBarSize(int);
method public void setScrollBarStyle(int);
+ method public final void setScrollCaptureCallback(@Nullable android.view.ScrollCaptureCallback);
+ method public void setScrollCaptureHint(int);
method public void setScrollContainer(boolean);
method public void setScrollIndicators(int);
method public void setScrollIndicators(int, int);
@@ -49051,6 +48610,10 @@
field public static final int SCROLL_AXIS_HORIZONTAL = 1; // 0x1
field public static final int SCROLL_AXIS_NONE = 0; // 0x0
field public static final int SCROLL_AXIS_VERTICAL = 2; // 0x2
+ field public static final int SCROLL_CAPTURE_HINT_AUTO = 0; // 0x0
+ field public static final int SCROLL_CAPTURE_HINT_EXCLUDE = 1; // 0x1
+ field public static final int SCROLL_CAPTURE_HINT_EXCLUDE_DESCENDANTS = 4; // 0x4
+ field public static final int SCROLL_CAPTURE_HINT_INCLUDE = 2; // 0x2
field public static final int SCROLL_INDICATOR_BOTTOM = 2; // 0x2
field public static final int SCROLL_INDICATOR_END = 32; // 0x20
field public static final int SCROLL_INDICATOR_LEFT = 4; // 0x4
@@ -49835,6 +49398,7 @@
method public abstract boolean performContextMenuIdentifierAction(int, int);
method public abstract boolean performPanelIdentifierAction(int, int, int);
method public abstract boolean performPanelShortcut(int, int, android.view.KeyEvent, int);
+ method public void registerScrollCaptureCallback(@NonNull android.view.ScrollCaptureCallback);
method public final void removeOnFrameMetricsAvailableListener(android.view.Window.OnFrameMetricsAvailableListener);
method public boolean requestFeature(int);
method @NonNull public final <T extends android.view.View> T requireViewById(@IdRes int);
@@ -49914,6 +49478,7 @@
method public abstract void takeKeyEvents(boolean);
method public abstract void takeSurface(android.view.SurfaceHolder.Callback2);
method public abstract void togglePanel(int, android.view.KeyEvent);
+ method public void unregisterScrollCaptureCallback(@NonNull android.view.ScrollCaptureCallback);
field public static final int DECOR_CAPTION_SHADE_AUTO = 0; // 0x0
field public static final int DECOR_CAPTION_SHADE_DARK = 2; // 0x2
field public static final int DECOR_CAPTION_SHADE_LIGHT = 1; // 0x1
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index dd9582f..e6f0e48 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -163,67 +163,14 @@
package android.net {
- public final class ConnectivityFrameworkInitializer {
- method public static void registerServiceWrappers();
- }
-
- public class ConnectivityManager {
- method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
- method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @Nullable android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback);
- method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle);
- }
-
public static final class IpSecManager.UdpEncapsulationSocket implements java.lang.AutoCloseable {
method public int getResourceId();
}
- public final class NetworkAgentConfig implements android.os.Parcelable {
- method @Nullable public String getSubscriberId();
- }
-
- public static final class NetworkAgentConfig.Builder {
- method @NonNull public android.net.NetworkAgentConfig.Builder setSubscriberId(@Nullable String);
- }
-
- public final class NetworkCapabilities implements android.os.Parcelable {
- field public static final int TRANSPORT_TEST = 7; // 0x7
- }
-
public class NetworkWatchlistManager {
method @Nullable public byte[] getWatchlistConfigHash();
}
- public final class Proxy {
- method public static void setHttpProxyConfiguration(@Nullable android.net.ProxyInfo);
- }
-
- public final class TcpRepairWindow {
- ctor public TcpRepairWindow(int, int, int, int, int, int);
- field public final int maxWindow;
- field public final int rcvWnd;
- field public final int rcvWndScale;
- field public final int rcvWup;
- field public final int sndWl1;
- field public final int sndWnd;
- }
-
- public final class TestNetworkInterface implements android.os.Parcelable {
- ctor public TestNetworkInterface(@NonNull android.os.ParcelFileDescriptor, @NonNull String);
- method public int describeContents();
- method @NonNull public android.os.ParcelFileDescriptor getFileDescriptor();
- method @NonNull public String getInterfaceName();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.net.TestNetworkInterface> CREATOR;
- }
-
- public class TestNetworkManager {
- method @NonNull public android.net.TestNetworkInterface createTapInterface();
- method @NonNull public android.net.TestNetworkInterface createTunInterface(@NonNull java.util.Collection<android.net.LinkAddress>);
- method public void setupTestNetwork(@NonNull String, @NonNull android.os.IBinder);
- method public void teardownTestNetwork(@NonNull android.net.Network);
- field public static final String TEST_TAP_PREFIX = "testtap";
- }
-
public final class UnderlyingNetworkInfo implements android.os.Parcelable {
ctor public UnderlyingNetworkInfo(int, @NonNull String, @NonNull java.util.List<java.lang.String>);
method public int describeContents();
@@ -234,14 +181,6 @@
field @NonNull public final java.util.List<java.lang.String> underlyingIfaces;
}
- public final class VpnTransportInfo implements android.os.Parcelable android.net.TransportInfo {
- ctor public VpnTransportInfo(int);
- method public int describeContents();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.net.VpnTransportInfo> CREATOR;
- field public final int type;
- }
-
}
package android.os {
diff --git a/core/api/removed.txt b/core/api/removed.txt
index 990388a..a99178d 100644
--- a/core/api/removed.txt
+++ b/core/api/removed.txt
@@ -241,12 +241,6 @@
package android.net {
- public class ConnectivityManager {
- method @Deprecated public boolean requestRouteToHost(int, int);
- method @Deprecated public int startUsingNetworkFeature(int, String);
- method @Deprecated public int stopUsingNetworkFeature(int, String);
- }
-
@Deprecated public class NetworkBadging {
method @NonNull public static android.graphics.drawable.Drawable getWifiIcon(@IntRange(from=0, to=4) int, int, @Nullable android.content.res.Resources.Theme);
field public static final int BADGING_4K = 30; // 0x1e
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index c62b808..c3d5d5a 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -27,6 +27,7 @@
field public static final String ALLOW_ANY_CODEC_FOR_PLAYBACK = "android.permission.ALLOW_ANY_CODEC_FOR_PLAYBACK";
field public static final String AMBIENT_WALLPAPER = "android.permission.AMBIENT_WALLPAPER";
field public static final String APPROVE_INCIDENT_REPORTS = "android.permission.APPROVE_INCIDENT_REPORTS";
+ field public static final String BACKGROUND_CAMERA = "android.permission.BACKGROUND_CAMERA";
field public static final String BACKUP = "android.permission.BACKUP";
field public static final String BATTERY_PREDICTION = "android.permission.BATTERY_PREDICTION";
field public static final String BIND_ATTENTION_SERVICE = "android.permission.BIND_ATTENTION_SERVICE";
@@ -215,6 +216,7 @@
field public static final String RECEIVE_DEVICE_CUSTOMIZATION_READY = "android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY";
field public static final String RECEIVE_EMERGENCY_BROADCAST = "android.permission.RECEIVE_EMERGENCY_BROADCAST";
field public static final String RECEIVE_WIFI_CREDENTIAL_CHANGE = "android.permission.RECEIVE_WIFI_CREDENTIAL_CHANGE";
+ field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO";
field public static final String RECOVERY = "android.permission.RECOVERY";
field public static final String RECOVER_KEYSTORE = "android.permission.RECOVER_KEYSTORE";
field public static final String REGISTER_CALL_PROVIDER = "android.permission.REGISTER_CALL_PROVIDER";
@@ -2464,10 +2466,10 @@
}
public static class PackageInstaller.Session implements java.io.Closeable {
- method @RequiresPermission("com.android.permission.USE_INSTALLER_V2") public void addFile(int, @NonNull String, long, @NonNull byte[], @Nullable byte[]);
+ method public void addFile(int, @NonNull String, long, @NonNull byte[], @Nullable byte[]);
method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void commitTransferred(@NonNull android.content.IntentSender);
- method @Nullable @RequiresPermission("com.android.permission.USE_INSTALLER_V2") public android.content.pm.DataLoaderParams getDataLoaderParams();
- method @RequiresPermission("com.android.permission.USE_INSTALLER_V2") public void removeFile(int, @NonNull String);
+ method @Nullable public android.content.pm.DataLoaderParams getDataLoaderParams();
+ method public void removeFile(int, @NonNull String);
}
public static class PackageInstaller.SessionInfo implements android.os.Parcelable {
@@ -2488,7 +2490,7 @@
public static class PackageInstaller.SessionParams implements android.os.Parcelable {
method @RequiresPermission(android.Manifest.permission.ALLOCATE_AGGRESSIVE) public void setAllocateAggressive(boolean);
method @Deprecated public void setAllowDowngrade(boolean);
- method @RequiresPermission(allOf={android.Manifest.permission.INSTALL_PACKAGES, "com.android.permission.USE_INSTALLER_V2"}) public void setDataLoaderParams(@NonNull android.content.pm.DataLoaderParams);
+ method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setDataLoaderParams(@NonNull android.content.pm.DataLoaderParams);
method public void setDontKillApp(boolean);
method public void setEnableRollback(boolean);
method public void setEnableRollback(boolean, int);
@@ -2570,7 +2572,6 @@
field public static final int FLAG_PERMISSION_ONE_TIME = 65536; // 0x10000
field public static final int FLAG_PERMISSION_POLICY_FIXED = 4; // 0x4
field public static final int FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT = 2048; // 0x800
- field public static final int FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT = 262144; // 0x40000
field public static final int FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT = 4096; // 0x1000
field public static final int FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT = 8192; // 0x2000
field public static final int FLAG_PERMISSION_REVIEW_REQUIRED = 64; // 0x40
@@ -2756,6 +2757,15 @@
package android.content.pm.verify.domain {
+ public final class DomainOwner implements android.os.Parcelable {
+ ctor public DomainOwner(@NonNull String, boolean);
+ method public int describeContents();
+ method @NonNull public String getPackageName();
+ method public boolean isOverrideable();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.domain.DomainOwner> CREATOR;
+ }
+
public final class DomainVerificationInfo implements android.os.Parcelable {
method public int describeContents();
method @NonNull public java.util.Map<java.lang.String,java.lang.Integer> getHostToStateMap();
@@ -2768,6 +2778,7 @@
public interface DomainVerificationManager {
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.DOMAIN_VERIFICATION_AGENT, android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION}) public android.content.pm.verify.domain.DomainVerificationInfo getDomainVerificationInfo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
method @Nullable @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public android.content.pm.verify.domain.DomainVerificationUserSelection getDomainVerificationUserSelection(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public java.util.List<android.content.pm.verify.domain.DomainOwner> getOwnersForDomain(@NonNull String);
method @NonNull @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public java.util.List<java.lang.String> getValidVerificationPackageNames();
method public static boolean isStateModifiable(int);
method public static boolean isStateVerified(int);
@@ -2789,13 +2800,16 @@
public final class DomainVerificationUserSelection implements android.os.Parcelable {
method public int describeContents();
- method @NonNull public java.util.Map<java.lang.String,java.lang.Boolean> getHostToUserSelectionMap();
+ method @NonNull public java.util.Map<java.lang.String,java.lang.Integer> getHostToStateMap();
method @NonNull public java.util.UUID getIdentifier();
method @NonNull public String getPackageName();
method @NonNull public android.os.UserHandle getUser();
method @NonNull public boolean isLinkHandlingAllowed();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.domain.DomainVerificationUserSelection> CREATOR;
+ field public static final int DOMAIN_STATE_NONE = 0; // 0x0
+ field public static final int DOMAIN_STATE_SELECTED = 1; // 0x1
+ field public static final int DOMAIN_STATE_VERIFIED = 2; // 0x2
}
}
@@ -7084,102 +7098,6 @@
package android.net {
- public class CaptivePortal implements android.os.Parcelable {
- method public void logEvent(int, @NonNull String);
- method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void reevaluateNetwork();
- method public void useNetwork();
- field public static final int APP_REQUEST_REEVALUATION_REQUIRED = 100; // 0x64
- field public static final int APP_RETURN_DISMISSED = 0; // 0x0
- field public static final int APP_RETURN_UNWANTED = 1; // 0x1
- field public static final int APP_RETURN_WANTED_AS_IS = 2; // 0x2
- }
-
- public final class CaptivePortalData implements android.os.Parcelable {
- method public int describeContents();
- method public long getByteLimit();
- method public long getExpiryTimeMillis();
- method public long getRefreshTimeMillis();
- method @Nullable public android.net.Uri getUserPortalUrl();
- method public int getUserPortalUrlSource();
- method @Nullable public String getVenueFriendlyName();
- method @Nullable public android.net.Uri getVenueInfoUrl();
- method public int getVenueInfoUrlSource();
- method public boolean isCaptive();
- method public boolean isSessionExtendable();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field public static final int CAPTIVE_PORTAL_DATA_SOURCE_OTHER = 0; // 0x0
- field public static final int CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT = 1; // 0x1
- field @NonNull public static final android.os.Parcelable.Creator<android.net.CaptivePortalData> CREATOR;
- }
-
- public static class CaptivePortalData.Builder {
- ctor public CaptivePortalData.Builder();
- ctor public CaptivePortalData.Builder(@Nullable android.net.CaptivePortalData);
- method @NonNull public android.net.CaptivePortalData build();
- method @NonNull public android.net.CaptivePortalData.Builder setBytesRemaining(long);
- method @NonNull public android.net.CaptivePortalData.Builder setCaptive(boolean);
- method @NonNull public android.net.CaptivePortalData.Builder setExpiryTime(long);
- method @NonNull public android.net.CaptivePortalData.Builder setRefreshTime(long);
- method @NonNull public android.net.CaptivePortalData.Builder setSessionExtendable(boolean);
- method @NonNull public android.net.CaptivePortalData.Builder setUserPortalUrl(@Nullable android.net.Uri);
- method @NonNull public android.net.CaptivePortalData.Builder setUserPortalUrl(@Nullable android.net.Uri, int);
- method @NonNull public android.net.CaptivePortalData.Builder setVenueFriendlyName(@Nullable String);
- method @NonNull public android.net.CaptivePortalData.Builder setVenueInfoUrl(@Nullable android.net.Uri);
- method @NonNull public android.net.CaptivePortalData.Builder setVenueInfoUrl(@Nullable android.net.Uri, int);
- }
-
- public class ConnectivityManager {
- method @NonNull @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) public android.net.SocketKeepalive createNattKeepalive(@NonNull android.net.Network, @NonNull android.os.ParcelFileDescriptor, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback);
- method @NonNull @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull java.net.Socket, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback);
- method @Deprecated @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public String getCaptivePortalServerUrl();
- method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void getLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEntitlementResultListener);
- method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported();
- method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_FACTORY}) public int registerNetworkProvider(@NonNull android.net.NetworkProvider);
- method public void registerQosCallback(@NonNull android.net.QosSocketInfo, @NonNull android.net.QosCallback, @NonNull java.util.concurrent.Executor);
- method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEventCallback);
- method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void requestNetwork(@NonNull android.net.NetworkRequest, int, int, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback);
- method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_AIRPLANE_MODE, android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void setAirplaneMode(boolean);
- method @RequiresPermission(android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE) public void setOemNetworkPreference(@NonNull android.net.OemNetworkPreferences, @Nullable java.util.concurrent.Executor, @Nullable android.net.ConnectivityManager.OnSetOemNetworkPreferenceListener);
- method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public boolean shouldAvoidBadWifi();
- method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void startCaptivePortalApp(@NonNull android.net.Network, @NonNull android.os.Bundle);
- method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback);
- method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback, android.os.Handler);
- method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void stopTethering(int);
- method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_FACTORY}) public void unregisterNetworkProvider(@NonNull android.net.NetworkProvider);
- method public void unregisterQosCallback(@NonNull android.net.QosCallback);
- method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void unregisterTetheringEventCallback(@NonNull android.net.ConnectivityManager.OnTetheringEventCallback);
- field public static final String EXTRA_CAPTIVE_PORTAL_PROBE_SPEC = "android.net.extra.CAPTIVE_PORTAL_PROBE_SPEC";
- field public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT = "android.net.extra.CAPTIVE_PORTAL_USER_AGENT";
- field public static final int TETHERING_BLUETOOTH = 2; // 0x2
- field public static final int TETHERING_USB = 1; // 0x1
- field public static final int TETHERING_WIFI = 0; // 0x0
- field @Deprecated public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = 13; // 0xd
- field @Deprecated public static final int TETHER_ERROR_NO_ERROR = 0; // 0x0
- field @Deprecated public static final int TETHER_ERROR_PROVISION_FAILED = 11; // 0xb
- field public static final int TYPE_NONE = -1; // 0xffffffff
- field @Deprecated public static final int TYPE_PROXY = 16; // 0x10
- field @Deprecated public static final int TYPE_WIFI_P2P = 13; // 0xd
- }
-
- public static interface ConnectivityManager.OnSetOemNetworkPreferenceListener {
- method public void onComplete();
- }
-
- @Deprecated public abstract static class ConnectivityManager.OnStartTetheringCallback {
- ctor @Deprecated public ConnectivityManager.OnStartTetheringCallback();
- method @Deprecated public void onTetheringFailed();
- method @Deprecated public void onTetheringStarted();
- }
-
- @Deprecated public static interface ConnectivityManager.OnTetheringEntitlementResultListener {
- method @Deprecated public void onTetheringEntitlementResult(int);
- }
-
- @Deprecated public abstract static class ConnectivityManager.OnTetheringEventCallback {
- ctor @Deprecated public ConnectivityManager.OnTetheringEventCallback();
- method @Deprecated public void onUpstreamChanged(@Nullable android.net.Network);
- }
-
public class DnsResolverServiceManager {
method @NonNull @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public static android.os.IBinder getService(@NonNull android.content.Context);
}
@@ -7197,48 +7115,6 @@
method public void release();
}
- public final class InvalidPacketException extends java.lang.Exception {
- ctor public InvalidPacketException(int);
- method public int getError();
- field public static final int ERROR_INVALID_IP_ADDRESS = -21; // 0xffffffeb
- field public static final int ERROR_INVALID_LENGTH = -23; // 0xffffffe9
- field public static final int ERROR_INVALID_PORT = -22; // 0xffffffea
- }
-
- public final class IpConfiguration implements android.os.Parcelable {
- ctor public IpConfiguration();
- ctor public IpConfiguration(@NonNull android.net.IpConfiguration);
- method public int describeContents();
- method @Nullable public android.net.ProxyInfo getHttpProxy();
- method @NonNull public android.net.IpConfiguration.IpAssignment getIpAssignment();
- method @NonNull public android.net.IpConfiguration.ProxySettings getProxySettings();
- method @Nullable public android.net.StaticIpConfiguration getStaticIpConfiguration();
- method public void setHttpProxy(@Nullable android.net.ProxyInfo);
- method public void setIpAssignment(@NonNull android.net.IpConfiguration.IpAssignment);
- method public void setProxySettings(@NonNull android.net.IpConfiguration.ProxySettings);
- method public void setStaticIpConfiguration(@Nullable android.net.StaticIpConfiguration);
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.net.IpConfiguration> CREATOR;
- }
-
- public enum IpConfiguration.IpAssignment {
- enum_constant public static final android.net.IpConfiguration.IpAssignment DHCP;
- enum_constant public static final android.net.IpConfiguration.IpAssignment STATIC;
- enum_constant public static final android.net.IpConfiguration.IpAssignment UNASSIGNED;
- }
-
- public enum IpConfiguration.ProxySettings {
- enum_constant public static final android.net.IpConfiguration.ProxySettings NONE;
- enum_constant public static final android.net.IpConfiguration.ProxySettings PAC;
- enum_constant public static final android.net.IpConfiguration.ProxySettings STATIC;
- enum_constant public static final android.net.IpConfiguration.ProxySettings UNASSIGNED;
- }
-
- public final class IpPrefix implements android.os.Parcelable {
- ctor public IpPrefix(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int);
- ctor public IpPrefix(@NonNull String);
- }
-
public final class IpSecManager {
method @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void applyTunnelModeTransform(@NonNull android.net.IpSecManager.IpSecTunnelInterface, int, @NonNull android.net.IpSecTransform) throws java.io.IOException;
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public android.net.IpSecManager.IpSecTunnelInterface createIpSecTunnelInterface(@NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull android.net.Network) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
@@ -7256,68 +7132,6 @@
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public android.net.IpSecTransform buildTunnelModeTransform(@NonNull java.net.InetAddress, @NonNull android.net.IpSecManager.SecurityParameterIndex) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
}
- public class KeepalivePacketData {
- ctor protected KeepalivePacketData(@NonNull java.net.InetAddress, @IntRange(from=0, to=65535) int, @NonNull java.net.InetAddress, @IntRange(from=0, to=65535) int, @NonNull byte[]) throws android.net.InvalidPacketException;
- method @NonNull public java.net.InetAddress getDstAddress();
- method public int getDstPort();
- method @NonNull public byte[] getPacket();
- method @NonNull public java.net.InetAddress getSrcAddress();
- method public int getSrcPort();
- }
-
- public class LinkAddress implements android.os.Parcelable {
- ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int, int, int);
- ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int, int, int, long, long);
- ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int);
- ctor public LinkAddress(@NonNull String);
- ctor public LinkAddress(@NonNull String, int, int);
- method public long getDeprecationTime();
- method public long getExpirationTime();
- method public boolean isGlobalPreferred();
- method public boolean isIpv4();
- method public boolean isIpv6();
- method public boolean isSameAddressAs(@Nullable android.net.LinkAddress);
- field public static final long LIFETIME_PERMANENT = 9223372036854775807L; // 0x7fffffffffffffffL
- field public static final long LIFETIME_UNKNOWN = -1L; // 0xffffffffffffffffL
- }
-
- public final class LinkProperties implements android.os.Parcelable {
- ctor public LinkProperties(@Nullable android.net.LinkProperties);
- ctor public LinkProperties(@Nullable android.net.LinkProperties, boolean);
- method public boolean addDnsServer(@NonNull java.net.InetAddress);
- method public boolean addLinkAddress(@NonNull android.net.LinkAddress);
- method public boolean addPcscfServer(@NonNull java.net.InetAddress);
- method @NonNull public java.util.List<java.net.InetAddress> getAddresses();
- method @NonNull public java.util.List<java.lang.String> getAllInterfaceNames();
- method @NonNull public java.util.List<android.net.LinkAddress> getAllLinkAddresses();
- method @NonNull public java.util.List<android.net.RouteInfo> getAllRoutes();
- method @Nullable public android.net.Uri getCaptivePortalApiUrl();
- method @Nullable public android.net.CaptivePortalData getCaptivePortalData();
- method @NonNull public java.util.List<java.net.InetAddress> getPcscfServers();
- method @Nullable public String getTcpBufferSizes();
- method @NonNull public java.util.List<java.net.InetAddress> getValidatedPrivateDnsServers();
- method public boolean hasGlobalIpv6Address();
- method public boolean hasIpv4Address();
- method public boolean hasIpv4DefaultRoute();
- method public boolean hasIpv4DnsServer();
- method public boolean hasIpv6DefaultRoute();
- method public boolean hasIpv6DnsServer();
- method public boolean isIpv4Provisioned();
- method public boolean isIpv6Provisioned();
- method public boolean isProvisioned();
- method public boolean isReachable(@NonNull java.net.InetAddress);
- method public boolean removeDnsServer(@NonNull java.net.InetAddress);
- method public boolean removeLinkAddress(@NonNull android.net.LinkAddress);
- method public boolean removeRoute(@NonNull android.net.RouteInfo);
- method public void setCaptivePortalApiUrl(@Nullable android.net.Uri);
- method public void setCaptivePortalData(@Nullable android.net.CaptivePortalData);
- method public void setPcscfServers(@NonNull java.util.Collection<java.net.InetAddress>);
- method public void setPrivateDnsServerName(@Nullable String);
- method public void setTcpBufferSizes(@Nullable String);
- method public void setUsePrivateDns(boolean);
- method public void setValidatedPrivateDnsServers(@NonNull java.util.Collection<java.net.InetAddress>);
- }
-
public final class MatchAllNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
ctor public MatchAllNetworkSpecifier();
method public int describeContents();
@@ -7325,104 +7139,6 @@
field @NonNull public static final android.os.Parcelable.Creator<android.net.MatchAllNetworkSpecifier> CREATOR;
}
- public final class NattKeepalivePacketData extends android.net.KeepalivePacketData implements android.os.Parcelable {
- ctor public NattKeepalivePacketData(@NonNull java.net.InetAddress, int, @NonNull java.net.InetAddress, int, @NonNull byte[]) throws android.net.InvalidPacketException;
- method public int describeContents();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.net.NattKeepalivePacketData> CREATOR;
- }
-
- public class Network implements android.os.Parcelable {
- ctor public Network(@NonNull android.net.Network);
- method public int getNetId();
- method @NonNull public android.net.Network getPrivateDnsBypassingCopy();
- }
-
- public abstract class NetworkAgent {
- ctor public NetworkAgent(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String, @NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties, int, @NonNull android.net.NetworkAgentConfig, @Nullable android.net.NetworkProvider);
- method @Nullable public android.net.Network getNetwork();
- method public void markConnected();
- method public void onAddKeepalivePacketFilter(int, @NonNull android.net.KeepalivePacketData);
- method public void onAutomaticReconnectDisabled();
- method public void onNetworkUnwanted();
- method public void onQosCallbackRegistered(int, @NonNull android.net.QosFilter);
- method public void onQosCallbackUnregistered(int);
- method public void onRemoveKeepalivePacketFilter(int);
- method public void onSaveAcceptUnvalidated(boolean);
- method public void onSignalStrengthThresholdsUpdated(@NonNull int[]);
- method public void onStartSocketKeepalive(int, @NonNull java.time.Duration, @NonNull android.net.KeepalivePacketData);
- method public void onStopSocketKeepalive(int);
- method public void onValidationStatus(int, @Nullable android.net.Uri);
- method @NonNull public android.net.Network register();
- method public final void sendLinkProperties(@NonNull android.net.LinkProperties);
- method public final void sendNetworkCapabilities(@NonNull android.net.NetworkCapabilities);
- method public final void sendNetworkScore(@IntRange(from=0, to=99) int);
- method public final void sendQosCallbackError(int, int);
- method public final void sendQosSessionAvailable(int, int, @NonNull android.telephony.data.EpsBearerQosSessionAttributes);
- method public final void sendQosSessionLost(int, int);
- method public final void sendSocketKeepaliveEvent(int, int);
- method public final void setUnderlyingNetworks(@Nullable java.util.List<android.net.Network>);
- method public void unregister();
- field public static final int VALIDATION_STATUS_NOT_VALID = 2; // 0x2
- field public static final int VALIDATION_STATUS_VALID = 1; // 0x1
- }
-
- public final class NetworkAgentConfig implements android.os.Parcelable {
- method public int describeContents();
- method public int getLegacyType();
- method @NonNull public String getLegacyTypeName();
- method public boolean isExplicitlySelected();
- method public boolean isPartialConnectivityAcceptable();
- method public boolean isUnvalidatedConnectivityAcceptable();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkAgentConfig> CREATOR;
- }
-
- public static final class NetworkAgentConfig.Builder {
- ctor public NetworkAgentConfig.Builder();
- method @NonNull public android.net.NetworkAgentConfig build();
- method @NonNull public android.net.NetworkAgentConfig.Builder setExplicitlySelected(boolean);
- method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyType(int);
- method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyTypeName(@NonNull String);
- method @NonNull public android.net.NetworkAgentConfig.Builder setPartialConnectivityAcceptable(boolean);
- method @NonNull public android.net.NetworkAgentConfig.Builder setUnvalidatedConnectivityAcceptable(boolean);
- }
-
- public final class NetworkCapabilities implements android.os.Parcelable {
- ctor public NetworkCapabilities(@Nullable android.net.NetworkCapabilities, boolean);
- method @NonNull public int[] getAdministratorUids();
- method @Nullable public String getSsid();
- method @NonNull public int[] getTransportTypes();
- method public boolean isPrivateDnsBroken();
- method public boolean satisfiedByNetworkCapabilities(@Nullable android.net.NetworkCapabilities);
- field public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28; // 0x1c
- field public static final int NET_CAPABILITY_OEM_PAID = 22; // 0x16
- field public static final int NET_CAPABILITY_OEM_PRIVATE = 26; // 0x1a
- field public static final int NET_CAPABILITY_PARTIAL_CONNECTIVITY = 24; // 0x18
- field public static final int NET_CAPABILITY_VEHICLE_INTERNAL = 27; // 0x1b
- }
-
- public static final class NetworkCapabilities.Builder {
- ctor public NetworkCapabilities.Builder();
- ctor public NetworkCapabilities.Builder(@NonNull android.net.NetworkCapabilities);
- method @NonNull public android.net.NetworkCapabilities.Builder addCapability(int);
- method @NonNull public android.net.NetworkCapabilities.Builder addTransportType(int);
- method @NonNull public android.net.NetworkCapabilities build();
- method @NonNull public android.net.NetworkCapabilities.Builder clearAll();
- method @NonNull public android.net.NetworkCapabilities.Builder removeCapability(int);
- method @NonNull public android.net.NetworkCapabilities.Builder removeTransportType(int);
- method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setAdministratorUids(@NonNull int[]);
- method @NonNull public android.net.NetworkCapabilities.Builder setLinkDownstreamBandwidthKbps(int);
- method @NonNull public android.net.NetworkCapabilities.Builder setLinkUpstreamBandwidthKbps(int);
- method @NonNull public android.net.NetworkCapabilities.Builder setNetworkSpecifier(@Nullable android.net.NetworkSpecifier);
- method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setOwnerUid(int);
- method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setRequestorPackageName(@Nullable String);
- method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setRequestorUid(int);
- method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkCapabilities.Builder setSignalStrength(int);
- method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setSsid(@Nullable String);
- method @NonNull public android.net.NetworkCapabilities.Builder setTransportInfo(@Nullable android.net.TransportInfo);
- }
-
public class NetworkKey implements android.os.Parcelable {
ctor public NetworkKey(android.net.WifiKey);
method @Nullable public static android.net.NetworkKey createFromScanResult(@NonNull android.net.wifi.ScanResult);
@@ -7434,15 +7150,6 @@
field public final android.net.WifiKey wifiKey;
}
- public class NetworkProvider {
- ctor public NetworkProvider(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String);
- method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void declareNetworkRequestUnfulfillable(@NonNull android.net.NetworkRequest);
- method public int getProviderId();
- method public void onNetworkRequestWithdrawn(@NonNull android.net.NetworkRequest);
- method public void onNetworkRequested(@NonNull android.net.NetworkRequest, @IntRange(from=0, to=99) int, int);
- field public static final int ID_NONE = -1; // 0xffffffff
- }
-
public abstract class NetworkRecommendationProvider {
ctor public NetworkRecommendationProvider(android.content.Context, java.util.concurrent.Executor);
method public final android.os.IBinder getBinder();
@@ -7452,15 +7159,6 @@
public class NetworkReleasedException extends java.lang.Exception {
}
- public class NetworkRequest implements android.os.Parcelable {
- method @Nullable public String getRequestorPackageName();
- method public int getRequestorUid();
- }
-
- public static class NetworkRequest.Builder {
- method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkRequest.Builder setSignalStrength(int);
- }
-
public class NetworkScoreManager {
method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, android.Manifest.permission.REQUEST_NETWORK_SCORES}) public boolean clearScores() throws java.lang.SecurityException;
method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, android.Manifest.permission.REQUEST_NETWORK_SCORES}) public void disableScoring() throws java.lang.SecurityException;
@@ -7585,16 +7283,6 @@
field @NonNull public static final android.os.Parcelable.Creator<android.net.QosSocketInfo> CREATOR;
}
- public final class RouteInfo implements android.os.Parcelable {
- ctor public RouteInfo(@Nullable android.net.IpPrefix, @Nullable java.net.InetAddress, @Nullable String, int);
- ctor public RouteInfo(@Nullable android.net.IpPrefix, @Nullable java.net.InetAddress, @Nullable String, int, int);
- method public int getMtu();
- method public int getType();
- field public static final int RTN_THROW = 9; // 0x9
- field public static final int RTN_UNICAST = 1; // 0x1
- field public static final int RTN_UNREACHABLE = 7; // 0x7
- }
-
public class RssiCurve implements android.os.Parcelable {
ctor public RssiCurve(int, int, byte[]);
ctor public RssiCurve(int, int, byte[], int);
@@ -7626,53 +7314,12 @@
field public final android.net.RssiCurve rssiCurve;
}
- public abstract class SocketKeepalive implements java.lang.AutoCloseable {
- field public static final int SUCCESS = 0; // 0x0
- }
-
public class SocketLocalAddressChangedException extends java.lang.Exception {
}
public class SocketNotBoundException extends java.lang.Exception {
}
- public final class StaticIpConfiguration implements android.os.Parcelable {
- ctor public StaticIpConfiguration();
- ctor public StaticIpConfiguration(@Nullable android.net.StaticIpConfiguration);
- method public void addDnsServer(@NonNull java.net.InetAddress);
- method public void clear();
- method public int describeContents();
- method @NonNull public java.util.List<java.net.InetAddress> getDnsServers();
- method @Nullable public String getDomains();
- method @Nullable public java.net.InetAddress getGateway();
- method @Nullable public android.net.LinkAddress getIpAddress();
- method @NonNull public java.util.List<android.net.RouteInfo> getRoutes(@Nullable String);
- method public void writeToParcel(android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.net.StaticIpConfiguration> CREATOR;
- }
-
- public static final class StaticIpConfiguration.Builder {
- ctor public StaticIpConfiguration.Builder();
- method @NonNull public android.net.StaticIpConfiguration build();
- method @NonNull public android.net.StaticIpConfiguration.Builder setDnsServers(@NonNull Iterable<java.net.InetAddress>);
- method @NonNull public android.net.StaticIpConfiguration.Builder setDomains(@Nullable String);
- method @NonNull public android.net.StaticIpConfiguration.Builder setGateway(@Nullable java.net.InetAddress);
- method @NonNull public android.net.StaticIpConfiguration.Builder setIpAddress(@Nullable android.net.LinkAddress);
- }
-
- public final class TcpKeepalivePacketData extends android.net.KeepalivePacketData implements android.os.Parcelable {
- ctor public TcpKeepalivePacketData(@NonNull java.net.InetAddress, int, @NonNull java.net.InetAddress, int, @NonNull byte[], int, int, int, int, int, int) throws android.net.InvalidPacketException;
- method public int describeContents();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.net.TcpKeepalivePacketData> CREATOR;
- field public final int ipTos;
- field public final int ipTtl;
- field public final int tcpAck;
- field public final int tcpSeq;
- field public final int tcpWindow;
- field public final int tcpWindowScale;
- }
-
public class TrafficStats {
method public static void setThreadStatsTagApp();
method public static void setThreadStatsTagBackup();
@@ -7685,11 +7332,6 @@
field public static final int TAG_SYSTEM_IMPERSONATION_RANGE_START = -256; // 0xffffff00
}
- public interface TransportInfo {
- method public default boolean hasLocationSensitiveFields();
- method @NonNull public default android.net.TransportInfo makeCopy(boolean);
- }
-
public abstract class Uri implements java.lang.Comparable<android.net.Uri> android.os.Parcelable {
method @NonNull public String toSafeString();
}
@@ -7713,23 +7355,6 @@
}
-package android.net.apf {
-
- public final class ApfCapabilities implements android.os.Parcelable {
- ctor public ApfCapabilities(int, int, int);
- method public int describeContents();
- method public static boolean getApfDrop8023Frames();
- method @NonNull public static int[] getApfEtherTypeBlackList();
- method public boolean hasDataAccess();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.net.apf.ApfCapabilities> CREATOR;
- field public final int apfPacketFormat;
- field public final int apfVersionSupported;
- field public final int maximumApfProgramSize;
- }
-
-}
-
package android.net.metrics {
@Deprecated public final class ApfProgramEvent implements android.net.metrics.IpConnectivityLog.Event {
@@ -7924,19 +7549,6 @@
}
-package android.net.util {
-
- public final class SocketUtils {
- method public static void bindSocketToInterface(@NonNull java.io.FileDescriptor, @NonNull String) throws android.system.ErrnoException;
- method public static void closeSocket(@Nullable java.io.FileDescriptor) throws java.io.IOException;
- method @NonNull public static java.net.SocketAddress makeNetlinkSocketAddress(int, int);
- method @NonNull public static java.net.SocketAddress makePacketSocketAddress(int, int);
- method @Deprecated @NonNull public static java.net.SocketAddress makePacketSocketAddress(int, @NonNull byte[]);
- method @NonNull public static java.net.SocketAddress makePacketSocketAddress(int, int, @NonNull byte[]);
- }
-
-}
-
package android.net.vcn {
public class VcnManager {
@@ -8788,13 +8400,13 @@
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.os.UserHandle createProfile(@NonNull String, @NonNull String, @NonNull java.util.Set<java.lang.String>) throws android.os.UserManager.UserOperationException;
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}, conditional=true) public java.util.List<android.os.UserHandle> getAllProfiles();
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}, conditional=true) public java.util.List<android.os.UserHandle> getEnabledProfiles();
- method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.os.UserHandle getProfileParent(@NonNull android.os.UserHandle);
+ method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public android.os.UserHandle getProfileParent(@NonNull android.os.UserHandle);
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.os.UserHandle getRestrictedProfileParent();
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getSeedAccountName();
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.os.PersistableBundle getSeedAccountOptions();
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getSeedAccountType();
- method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public long[] getSerialNumbersOfUsers(boolean);
- method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public java.util.List<android.os.UserHandle> getUserHandles(boolean);
+ method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public long[] getSerialNumbersOfUsers(boolean);
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.os.UserHandle> getUserHandles(boolean);
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public android.graphics.Bitmap getUserIcon();
method @Deprecated @android.os.UserManager.UserRestrictionSource @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public int getUserRestrictionSource(String, android.os.UserHandle);
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public java.util.List<android.os.UserManager.EnforcingUser> getUserRestrictionSources(String, android.os.UserHandle);
@@ -9232,6 +8844,7 @@
field public static final String NAMESPACE_BIOMETRICS = "biometrics";
field public static final String NAMESPACE_BLOBSTORE = "blobstore";
field public static final String NAMESPACE_BLUETOOTH = "bluetooth";
+ field public static final String NAMESPACE_CLIPBOARD = "clipboard";
field public static final String NAMESPACE_CONNECTIVITY = "connectivity";
field public static final String NAMESPACE_CONTENT_CAPTURE = "content_capture";
field @Deprecated public static final String NAMESPACE_DEX_BOOT = "dex_boot";
@@ -11159,6 +10772,10 @@
field public static final String KEY_SUPPORT_CDMA_1X_VOICE_CALLS_BOOL = "support_cdma_1x_voice_calls_bool";
}
+ public static final class CarrierConfigManager.Ims {
+ field public static final String KEY_PUBLISH_SERVICE_DESC_FEATURE_TAG_MAP_OVERRIDE_STRING_ARRAY = "ims.publish_service_desc_feature_tag_map_override_string_array";
+ }
+
public static final class CarrierConfigManager.Wifi {
field public static final String KEY_AVOID_5GHZ_SOFTAP_FOR_LAA_BOOL = "wifi.avoid_5ghz_softap_for_laa_bool";
field public static final String KEY_AVOID_5GHZ_WIFI_DIRECT_FOR_LAA_BOOL = "wifi.avoid_5ghz_wifi_direct_for_laa_bool";
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index de34068..ca261cd 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -5,6 +5,7 @@
field public static final String ACCESS_NOTIFICATIONS = "android.permission.ACCESS_NOTIFICATIONS";
field public static final String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING";
field public static final String APPROVE_INCIDENT_REPORTS = "android.permission.APPROVE_INCIDENT_REPORTS";
+ field public static final String BACKGROUND_CAMERA = "android.permission.BACKGROUND_CAMERA";
field public static final String BIND_CELL_BROADCAST_SERVICE = "android.permission.BIND_CELL_BROADCAST_SERVICE";
field public static final String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE";
field public static final String BROADCAST_CLOSE_SYSTEM_DIALOGS = "android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS";
@@ -30,6 +31,7 @@
field public static final String QUERY_USERS = "android.permission.QUERY_USERS";
field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
field public static final String READ_PRIVILEGED_PHONE_STATE = "android.permission.READ_PRIVILEGED_PHONE_STATE";
+ field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO";
field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
field public static final String RESET_APP_ERRORS = "android.permission.RESET_APP_ERRORS";
field public static final String START_TASKS_FROM_RECENTS = "android.permission.START_TASKS_FROM_RECENTS";
@@ -748,6 +750,65 @@
ctor public ShortcutManager(android.content.Context);
}
+ public class UserInfo implements android.os.Parcelable {
+ ctor public UserInfo(int, String, int);
+ ctor public UserInfo(int, String, String, int);
+ ctor public UserInfo(int, String, String, int, String);
+ ctor @Deprecated public UserInfo();
+ ctor public UserInfo(android.content.pm.UserInfo);
+ method public boolean canHaveProfile();
+ method public int describeContents();
+ method public android.os.UserHandle getUserHandle();
+ method public boolean isAdmin();
+ method public boolean isDemo();
+ method public boolean isEnabled();
+ method public boolean isEphemeral();
+ method public boolean isFull();
+ method public boolean isGuest();
+ method public boolean isInitialized();
+ method public boolean isManagedProfile();
+ method public boolean isPrimary();
+ method public boolean isProfile();
+ method public boolean isQuietModeEnabled();
+ method public boolean isRestricted();
+ method public boolean isSystemOnly();
+ method public static boolean isSystemOnly(int);
+ method public boolean supportsSwitchTo();
+ method public boolean supportsSwitchToByUser();
+ method public void writeToParcel(android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.UserInfo> CREATOR;
+ field public static final int FLAG_ADMIN = 2; // 0x2
+ field @Deprecated public static final int FLAG_DEMO = 512; // 0x200
+ field public static final int FLAG_DISABLED = 64; // 0x40
+ field public static final int FLAG_EPHEMERAL = 256; // 0x100
+ field public static final int FLAG_FULL = 1024; // 0x400
+ field @Deprecated public static final int FLAG_GUEST = 4; // 0x4
+ field public static final int FLAG_INITIALIZED = 16; // 0x10
+ field @Deprecated public static final int FLAG_MANAGED_PROFILE = 32; // 0x20
+ field public static final int FLAG_PRIMARY = 1; // 0x1
+ field public static final int FLAG_PROFILE = 4096; // 0x1000
+ field public static final int FLAG_QUIET_MODE = 128; // 0x80
+ field @Deprecated public static final int FLAG_RESTRICTED = 8; // 0x8
+ field public static final int FLAG_SYSTEM = 2048; // 0x800
+ field public static final int NO_PROFILE_GROUP_ID = -10000; // 0xffffd8f0
+ field public boolean convertedFromPreCreated;
+ field public long creationTime;
+ field public int flags;
+ field public boolean guestToRemove;
+ field public String iconPath;
+ field public int id;
+ field public String lastLoggedInFingerprint;
+ field public long lastLoggedInTime;
+ field public String name;
+ field public boolean partial;
+ field public boolean preCreated;
+ field public int profileBadge;
+ field public int profileGroupId;
+ field public int restrictedProfileParentId;
+ field public int serialNumber;
+ field public String userType;
+ }
+
}
package android.content.res {
@@ -1511,8 +1572,15 @@
}
public class UserManager {
+ method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createProfileForUser(@Nullable String, @NonNull String, int, int, @Nullable String[]);
+ method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createRestrictedProfile(@Nullable String);
+ method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createUser(@Nullable String, @NonNull String, int);
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public String getUserType();
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers(boolean, boolean, boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean hasBaseUserRestriction(@NonNull String, @NonNull android.os.UserHandle);
+ method public static boolean isGuestUserEphemeral();
method public static boolean isSplitSystemUser();
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo preCreateUser(@NonNull String) throws android.os.UserManager.UserOperationException;
}
public final class VibrationAttributes implements android.os.Parcelable {
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index 87fb5b1..a536efb 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -468,7 +468,7 @@
GetterSetterNames: android.location.GnssMeasurement#setCarrierFrequencyHz(float):
GetterSetterNames: android.location.GnssMeasurement#setCodeType(String):
-
+
GetterSetterNames: android.location.GnssMeasurement#setCorrelationVectors(java.util.Collection<android.location.CorrelationVector>):
GetterSetterNames: android.location.GnssMeasurement#setFullInterSignalBiasNanos(double):
@@ -480,7 +480,7 @@
GetterSetterNames: android.location.GnssMeasurement#setSatelliteInterSignalBiasUncertaintyNanos(double):
GetterSetterNames: android.location.GnssMeasurement#setSatellitePvt(android.location.SatellitePvt):
-
+
GetterSetterNames: android.location.GnssMeasurement#setSnrInDb(double):
GetterSetterNames: android.location.LocationRequest#isLocationSettingsIgnored():
@@ -953,6 +953,32 @@
MissingNullability: android.content.pm.ShortcutManager#ShortcutManager(android.content.Context) parameter #0:
+MissingNullability: android.content.pm.UserInfo#UserInfo(android.content.pm.UserInfo) parameter #0:
+ Missing nullability on parameter `orig` in method `UserInfo`
+MissingNullability: android.content.pm.UserInfo#UserInfo(int, String, String, int) parameter #1:
+ Missing nullability on parameter `name` in method `UserInfo`
+MissingNullability: android.content.pm.UserInfo#UserInfo(int, String, String, int) parameter #2:
+ Missing nullability on parameter `iconPath` in method `UserInfo`
+MissingNullability: android.content.pm.UserInfo#UserInfo(int, String, String, int, String) parameter #1:
+ Missing nullability on parameter `name` in method `UserInfo`
+MissingNullability: android.content.pm.UserInfo#UserInfo(int, String, String, int, String) parameter #2:
+ Missing nullability on parameter `iconPath` in method `UserInfo`
+MissingNullability: android.content.pm.UserInfo#UserInfo(int, String, String, int, String) parameter #4:
+ Missing nullability on parameter `userType` in method `UserInfo`
+MissingNullability: android.content.pm.UserInfo#UserInfo(int, String, int) parameter #1:
+ Missing nullability on parameter `name` in method `UserInfo`
+MissingNullability: android.content.pm.UserInfo#getUserHandle():
+ Missing nullability on method `getUserHandle` return
+MissingNullability: android.content.pm.UserInfo#iconPath:
+ Missing nullability on field `iconPath` in class `class android.content.pm.UserInfo`
+MissingNullability: android.content.pm.UserInfo#lastLoggedInFingerprint:
+ Missing nullability on field `lastLoggedInFingerprint` in class `class android.content.pm.UserInfo`
+MissingNullability: android.content.pm.UserInfo#name:
+ Missing nullability on field `name` in class `class android.content.pm.UserInfo`
+MissingNullability: android.content.pm.UserInfo#userType:
+ Missing nullability on field `userType` in class `class android.content.pm.UserInfo`
+MissingNullability: android.content.pm.UserInfo#writeToParcel(android.os.Parcel, int) parameter #0:
+ Missing nullability on parameter `dest` in method `writeToParcel`
MissingNullability: android.content.res.AssetManager#getOverlayablesToString(String) parameter #0:
MissingNullability: android.content.res.Configuration#windowConfiguration:
@@ -2433,6 +2459,38 @@
MutableBareField: android.content.AutofillOptions#whitelistedActivitiesForAugmentedAutofill:
+MutableBareField: android.content.pm.UserInfo#convertedFromPreCreated:
+ Bare field convertedFromPreCreated must be marked final, or moved behind accessors if mutable
+MutableBareField: android.content.pm.UserInfo#creationTime:
+ Bare field creationTime must be marked final, or moved behind accessors if mutable
+MutableBareField: android.content.pm.UserInfo#flags:
+ Bare field flags must be marked final, or moved behind accessors if mutable
+MutableBareField: android.content.pm.UserInfo#guestToRemove:
+ Bare field guestToRemove must be marked final, or moved behind accessors if mutable
+MutableBareField: android.content.pm.UserInfo#iconPath:
+ Bare field iconPath must be marked final, or moved behind accessors if mutable
+MutableBareField: android.content.pm.UserInfo#id:
+ Bare field id must be marked final, or moved behind accessors if mutable
+MutableBareField: android.content.pm.UserInfo#lastLoggedInFingerprint:
+ Bare field lastLoggedInFingerprint must be marked final, or moved behind accessors if mutable
+MutableBareField: android.content.pm.UserInfo#lastLoggedInTime:
+ Bare field lastLoggedInTime must be marked final, or moved behind accessors if mutable
+MutableBareField: android.content.pm.UserInfo#name:
+ Bare field name must be marked final, or moved behind accessors if mutable
+MutableBareField: android.content.pm.UserInfo#partial:
+ Bare field partial must be marked final, or moved behind accessors if mutable
+MutableBareField: android.content.pm.UserInfo#preCreated:
+ Bare field preCreated must be marked final, or moved behind accessors if mutable
+MutableBareField: android.content.pm.UserInfo#profileBadge:
+ Bare field profileBadge must be marked final, or moved behind accessors if mutable
+MutableBareField: android.content.pm.UserInfo#profileGroupId:
+ Bare field profileGroupId must be marked final, or moved behind accessors if mutable
+MutableBareField: android.content.pm.UserInfo#restrictedProfileParentId:
+ Bare field restrictedProfileParentId must be marked final, or moved behind accessors if mutable
+MutableBareField: android.content.pm.UserInfo#serialNumber:
+ Bare field serialNumber must be marked final, or moved behind accessors if mutable
+MutableBareField: android.content.pm.UserInfo#userType:
+ Bare field userType must be marked final, or moved behind accessors if mutable
MutableBareField: android.database.sqlite.SQLiteDebug.DbStats#cache:
MutableBareField: android.database.sqlite.SQLiteDebug.DbStats#dbName:
@@ -2548,15 +2606,15 @@
NoSettingsProvider: android.provider.Settings.Global#USE_OPEN_WIFI_PACKAGE:
NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED:
-
+
NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_MAGNIFICATION_CAPABILITY:
-
+
NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_MAGNIFICATION_MODE:
-
+
NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_MAGNIFICATION_MODE_ALL:
-
+
NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN:
-
+
NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW:
NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_SHORTCUT_TARGET_SERVICE:
@@ -2619,6 +2677,10 @@
+NullableCollection: android.os.UserManager#createProfileForUser(String, String, int, int, String[]) parameter #4:
+ Type of parameter disallowedPackages in android.os.UserManager.createProfileForUser(String name, String userType, int flags, int userId, String[] disallowedPackages) is a nullable collection (`java.lang.String[]`); must be non-null
+
+
OnNameExpected: android.service.autofill.augmented.AugmentedAutofillService#dump(java.io.PrintWriter, String[]):
OnNameExpected: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]):
@@ -2729,6 +2791,8 @@
ParcelNotFinal: android.app.WindowConfiguration:
+ParcelNotFinal: android.content.pm.UserInfo:
+ Parcelable classes must be final: android.content.pm.UserInfo is not final
ParcelNotFinal: android.net.metrics.IpConnectivityLog.Event:
ParcelNotFinal: android.os.IncidentManager.IncidentReport:
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 71f164f..c812e8e 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -113,10 +113,6 @@
IBinder allowlistToken, long duration, @TempAllowListType int type,
@ReasonCode int reasonCode, @Nullable String reason);
- @Deprecated
- public abstract void setPendingIntentWhitelistDuration(IIntentSender target,
- IBinder allowlistToken, long duration, @TempAllowListType int type);
-
/**
* Returns the flags set for a {@link PendingIntent}.
*/
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index f7304fb..7222e86 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5013,6 +5013,7 @@
bindNotificationHeader(contentView, p);
bindLargeIconAndApplyMargin(contentView, p, result);
boolean showProgress = handleProgressBar(contentView, ex, p);
+ boolean hasSecondLine = showProgress;
if (p.hasTitle()) {
contentView.setViewVisibility(R.id.title, View.VISIBLE);
contentView.setTextViewText(R.id.title, processTextSpans(p.title));
@@ -5028,11 +5029,27 @@
contentView.setTextViewText(textId, processTextSpans(p.text));
setTextViewColorSecondary(contentView, textId, p);
contentView.setViewVisibility(textId, View.VISIBLE);
+ hasSecondLine = true;
}
+ setHeaderlessVerticalMargins(contentView, p, hasSecondLine);
return contentView;
}
+ private static void setHeaderlessVerticalMargins(RemoteViews contentView,
+ StandardTemplateParams p, boolean hasSecondLine) {
+ if (!p.mHeaderless) {
+ return;
+ }
+ int marginDimen = hasSecondLine
+ ? R.dimen.notification_headerless_margin_twoline
+ : R.dimen.notification_headerless_margin_oneline;
+ contentView.setViewLayoutMarginDimen(R.id.notification_headerless_view_column,
+ RemoteViews.MARGIN_TOP, marginDimen);
+ contentView.setViewLayoutMarginDimen(R.id.notification_headerless_view_column,
+ RemoteViews.MARGIN_BOTTOM, marginDimen);
+ }
+
private CharSequence processTextSpans(CharSequence text) {
if (hasForegroundColor() || mInNightMode) {
return ContrastColorUtil.clearColorSpans(text);
@@ -5494,12 +5511,28 @@
big.setViewVisibility(R.id.notification_material_reply_text_3, View.GONE);
big.setTextViewText(R.id.notification_material_reply_text_3, null);
- final boolean snoozeEnabled = mContext.getContentResolver() != null
- && (Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.SHOW_NOTIFICATION_SNOOZE, 0) == 1);
- int bottomMarginDimen = snoozeEnabled ? 0 : R.dimen.notification_content_margin;
+ // This may get erased by bindSnoozeAction
big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
- RemoteViews.MARGIN_BOTTOM, bottomMarginDimen);
+ RemoteViews.MARGIN_BOTTOM, R.dimen.notification_content_margin);
+ }
+
+ private void bindSnoozeAction(RemoteViews big, StandardTemplateParams p) {
+ boolean hideSnoozeButton = mN.isForegroundService() || mN.fullScreenIntent != null
+ || isColorized(p) || p.mViewType == StandardTemplateParams.VIEW_TYPE_HEADS_UP;
+ big.setBoolean(R.id.snooze_button, "setEnabled", !hideSnoozeButton);
+ if (hideSnoozeButton) {
+ // Only hide; NotificationContentView will show it when it adds the click listener
+ big.setViewVisibility(R.id.snooze_button, View.GONE);
+ }
+
+ final boolean snoozeEnabled = !hideSnoozeButton
+ && mContext.getContentResolver() != null
+ && (Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.SHOW_NOTIFICATION_SNOOZE, 0) == 1);
+ if (snoozeEnabled) {
+ big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
+ RemoteViews.MARGIN_BOTTOM, 0);
+ }
}
/**
@@ -5521,6 +5554,7 @@
RemoteViews big = applyStandardTemplate(layoutId, p, result);
resetStandardTemplateWithActions(big);
+ bindSnoozeAction(big, p);
boolean validRemoteInput = false;
@@ -6834,26 +6868,13 @@
if (decorationType <= DevFlags.DECORATION_PARTIAL) {
template.removeFromParent(R.id.notification_top_line);
}
- if (decorationType != DevFlags.DECORATION_FULL_COMPATIBLE) {
- // Change the max content size from 60dp (the compatible size) to 48dp
- // (the constrained size). This is done by increasing the minimum margin
- // (implemented as top/bottom margins) and decreasing the extra margin
- // (implemented as the height of shrinkable top/bottom views in the column).
- template.setViewLayoutMarginDimen(
- R.id.notification_headerless_view_column,
- RemoteViews.MARGIN_TOP,
- R.dimen.notification_headerless_margin_constrained_minimum);
- template.setViewLayoutMarginDimen(
- R.id.notification_headerless_view_column,
- RemoteViews.MARGIN_BOTTOM,
- R.dimen.notification_headerless_margin_constrained_minimum);
- template.setViewLayoutHeightDimen(
- R.id.notification_headerless_margin_extra_top,
- R.dimen.notification_headerless_margin_constrained_extra);
- template.setViewLayoutHeightDimen(
- R.id.notification_headerless_margin_extra_bottom,
- R.dimen.notification_headerless_margin_constrained_extra);
- }
+ // The vertical margins are bigger in the "two-line" scenario than the "one-line"
+ // scenario, but the 'compatible' decoration state is intended to have 3 lines,
+ // (1 for the top line views and 2 for the custom views), so in that one case we
+ // use the smaller 1-line margins. This gives the compatible case 88-16*2=56 dp of
+ // height, 24dp of which goes to the top line, leaving 32dp for the custom view.
+ boolean hasSecondLine = decorationType != DevFlags.DECORATION_FULL_COMPATIBLE;
+ Builder.setHeaderlessVerticalMargins(template, p, hasSecondLine);
} else {
// also update the end margin to account for the large icon or expander
Resources resources = context.getResources();
@@ -9358,6 +9379,7 @@
// Bind actions.
mBuilder.resetStandardTemplateWithActions(contentView);
+ mBuilder.bindSnoozeAction(contentView, p);
bindCallActions(contentView, p);
// Bind some extra conversation-specific header fields.
@@ -11998,6 +12020,7 @@
boolean mHideTitle;
boolean mHideActions;
boolean mHideProgress;
+ boolean mHideSnoozeButton;
boolean mPromotePicture;
boolean mAllowActionIcons;
CharSequence title;
@@ -12015,6 +12038,7 @@
mHideTitle = false;
mHideActions = false;
mHideProgress = false;
+ mHideSnoozeButton = false;
mPromotePicture = false;
mAllowActionIcons = false;
title = null;
@@ -12061,6 +12085,11 @@
return this;
}
+ final StandardTemplateParams hideSnoozeButton(boolean hideSnoozeButton) {
+ this.mHideSnoozeButton = hideSnoozeButton;
+ return this;
+ }
+
final StandardTemplateParams promotePicture(boolean promotePicture) {
this.mPromotePicture = promotePicture;
return this;
diff --git a/core/java/android/app/people/PeopleSpaceTile.java b/core/java/android/app/people/PeopleSpaceTile.java
index 132af4b..dd2ba7d 100644
--- a/core/java/android/app/people/PeopleSpaceTile.java
+++ b/core/java/android/app/people/PeopleSpaceTile.java
@@ -29,6 +29,7 @@
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.UserHandle;
import java.util.ArrayList;
import java.util.List;
@@ -45,7 +46,7 @@
private String mId;
private CharSequence mUserName;
private Icon mUserIcon;
- private int mUid;
+ private UserHandle mUserHandle;
private Uri mContactUri;
private String mPackageName;
private String mBirthdayText;
@@ -64,7 +65,7 @@
mUserName = b.mUserName;
mUserIcon = b.mUserIcon;
mContactUri = b.mContactUri;
- mUid = b.mUid;
+ mUserHandle = b.mUserHandle;
mPackageName = b.mPackageName;
mBirthdayText = b.mBirthdayText;
mLastInteractionTimestamp = b.mLastInteractionTimestamp;
@@ -95,8 +96,8 @@
return mContactUri;
}
- public int getUid() {
- return mUid;
+ public UserHandle getUserHandle() {
+ return mUserHandle;
}
public String getPackageName() {
@@ -165,7 +166,7 @@
Builder builder =
new Builder(mId, mUserName.toString(), mUserIcon, mIntent);
builder.setContactUri(mContactUri);
- builder.setUid(mUid);
+ builder.setUserHandle(mUserHandle);
builder.setPackageName(mPackageName);
builder.setBirthdayText(mBirthdayText);
builder.setLastInteractionTimestamp(mLastInteractionTimestamp);
@@ -186,7 +187,7 @@
private CharSequence mUserName;
private Icon mUserIcon;
private Uri mContactUri;
- private int mUid;
+ private UserHandle mUserHandle;
private String mPackageName;
private String mBirthdayText;
private long mLastInteractionTimestamp;
@@ -212,7 +213,7 @@
mId = info.getId();
mUserName = info.getLabel();
mUserIcon = convertDrawableToIcon(launcherApps.getShortcutIconDrawable(info, 0));
- mUid = info.getUserId();
+ mUserHandle = info.getUserHandle();
mPackageName = info.getPackage();
mContactUri = getContactUri(info);
}
@@ -222,7 +223,7 @@
mId = info.getId();
mUserName = info.getLabel();
mUserIcon = convertDrawableToIcon(launcherApps.getShortcutIconDrawable(info, 0));
- mUid = info.getUserId();
+ mUserHandle = info.getUserHandle();
mPackageName = info.getPackage();
mContactUri = getContactUri(info);
mStatuses = channel.getStatuses();
@@ -265,9 +266,9 @@
return this;
}
- /** Sets the associated uid. */
- public Builder setUid(int uid) {
- mUid = uid;
+ /** Sets the associated {@code userHandle}. */
+ public Builder setUserHandle(UserHandle userHandle) {
+ mUserHandle = userHandle;
return this;
}
@@ -349,7 +350,7 @@
mUserName = in.readCharSequence();
mUserIcon = in.readParcelable(Icon.class.getClassLoader());
mContactUri = in.readParcelable(Uri.class.getClassLoader());
- mUid = in.readInt();
+ mUserHandle = in.readParcelable(UserHandle.class.getClassLoader());
mPackageName = in.readString();
mBirthdayText = in.readString();
mLastInteractionTimestamp = in.readLong();
@@ -375,7 +376,7 @@
dest.writeCharSequence(mUserName);
dest.writeParcelable(mUserIcon, flags);
dest.writeParcelable(mContactUri, flags);
- dest.writeInt(mUid);
+ dest.writeParcelable(mUserHandle, flags);
dest.writeString(mPackageName);
dest.writeString(mBirthdayText);
dest.writeLong(mLastInteractionTimestamp);
diff --git a/core/java/android/content/pm/IShortcutService.aidl b/core/java/android/content/pm/IShortcutService.aidl
index 29a55b7..b345748 100644
--- a/core/java/android/content/pm/IShortcutService.aidl
+++ b/core/java/android/content/pm/IShortcutService.aidl
@@ -78,4 +78,7 @@
ParceledListSlice getShortcuts(String packageName, int matchFlags, int userId);
void pushDynamicShortcut(String packageName, in ShortcutInfo shortcut, int userId);
+
+ void updateShortcutVisibility(String callingPkg, String packageName, in byte[] certificate,
+ in boolean visible, int userId);
}
\ No newline at end of file
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 567501c..42cbe35 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -1173,7 +1173,6 @@
* {@hide}
*/
@SystemApi
- @RequiresPermission(android.Manifest.permission.USE_INSTALLER_V2)
public @Nullable DataLoaderParams getDataLoaderParams() {
try {
DataLoaderParamsParcel data = mSession.getDataLoaderParams();
@@ -1213,7 +1212,6 @@
* {@hide}
*/
@SystemApi
- @RequiresPermission(android.Manifest.permission.USE_INSTALLER_V2)
public void addFile(@FileLocation int location, @NonNull String name, long lengthBytes,
@NonNull byte[] metadata, @Nullable byte[] signature) {
try {
@@ -1237,7 +1235,6 @@
* {@hide}
*/
@SystemApi
- @RequiresPermission(android.Manifest.permission.USE_INSTALLER_V2)
public void removeFile(@FileLocation int location, @NonNull String name) {
try {
mSession.removeFile(location, name);
@@ -2050,9 +2047,7 @@
* {@hide}
*/
@SystemApi
- @RequiresPermission(allOf = {
- Manifest.permission.INSTALL_PACKAGES,
- Manifest.permission.USE_INSTALLER_V2})
+ @RequiresPermission(Manifest.permission.INSTALL_PACKAGES)
public void setDataLoaderParams(@NonNull DataLoaderParams dataLoaderParams) {
this.dataLoaderParams = dataLoaderParams;
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index ca88241..29dea6b 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4075,16 +4075,6 @@
public static final int FLAG_PERMISSION_AUTO_REVOKED = 1 << 17;
/**
- * Permission flag: The permission is restricted but the app is exempt
- * from the restriction and is allowed to hold this permission in its
- * full form and the exemption is provided by the held roles.
- *
- * @hide
- */
- @SystemApi
- public static final int FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT = 1 << 18;
-
- /**
* Permission flag: This location permission is selected as the level of granularity of
* location accuracy.
* Example: If this flag is set for ACCESS_FINE_LOCATION, FINE location is the selected location
@@ -4113,8 +4103,7 @@
public static final int FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT =
FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT
| FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT
- | FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT
- | FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT;
+ | FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
/**
* Mask for all permission flags.
@@ -4199,20 +4188,11 @@
*/
public static final int FLAG_PERMISSION_WHITELIST_UPGRADE = 1 << 2;
- /**
- * Permission allowlist flag: permissions exempted by the system
- * when being granted a role.
- * Permissions can also be exempted by the installer, the system, or on
- * upgrade.
- */
- public static final int FLAG_PERMISSION_ALLOWLIST_ROLE = 1 << 3;
-
/** @hide */
@IntDef(flag = true, prefix = {"FLAG_PERMISSION_WHITELIST_"}, value = {
FLAG_PERMISSION_WHITELIST_SYSTEM,
FLAG_PERMISSION_WHITELIST_INSTALLER,
- FLAG_PERMISSION_WHITELIST_UPGRADE,
- FLAG_PERMISSION_ALLOWLIST_ROLE
+ FLAG_PERMISSION_WHITELIST_UPGRADE
})
@Retention(RetentionPolicy.SOURCE)
public @interface PermissionWhitelistFlags {}
@@ -5244,10 +5224,6 @@
* This list corresponds to the {@link #FLAG_PERMISSION_WHITELIST_INSTALLER} flag.
* Can be accessed by pre-installed holders of a dedicated permission or the
* installer on record.
- *
- * <li>one for cases where the system exempts the permission when granting a role.
- * This list corresponds to the {@link #FLAG_PERMISSION_ALLOWLIST_ROLE} flag. Can
- * be accessed by pre-installed holders of a dedicated permission.
* </ol>
*
* <p>
@@ -5266,7 +5242,6 @@
* @see #FLAG_PERMISSION_WHITELIST_SYSTEM
* @see #FLAG_PERMISSION_WHITELIST_UPGRADE
* @see #FLAG_PERMISSION_WHITELIST_INSTALLER
- * @see #FLAG_PERMISSION_ALLOWLIST_ROLE
*
* @throws SecurityException if you try to access a whitelist that you have no access to.
*/
@@ -5306,10 +5281,6 @@
* This list corresponds to the {@link #FLAG_PERMISSION_WHITELIST_INSTALLER} flag.
* Can be modified by pre-installed holders of a dedicated permission or the installer
* on record.
- *
- * <li>one for cases where the system exempts the permission when permission when
- * granting a role. This list corresponds to the {@link #FLAG_PERMISSION_ALLOWLIST_ROLE}
- * flag. Can be modified by pre-installed holders of a dedicated permission.
* </ol>
*
* <p>You need to specify the whitelists for which to set the whitelisted permissions
@@ -5333,7 +5304,6 @@
* @see #FLAG_PERMISSION_WHITELIST_SYSTEM
* @see #FLAG_PERMISSION_WHITELIST_UPGRADE
* @see #FLAG_PERMISSION_WHITELIST_INSTALLER
- * @see #FLAG_PERMISSION_ALLOWLIST_ROLE
*
* @throws SecurityException if you try to modify a whitelist that you have no access to.
*/
@@ -5403,7 +5373,6 @@
* @see #FLAG_PERMISSION_WHITELIST_SYSTEM
* @see #FLAG_PERMISSION_WHITELIST_UPGRADE
* @see #FLAG_PERMISSION_WHITELIST_INSTALLER
- * @see #FLAG_PERMISSION_ALLOWLIST_ROLE
*
* @throws SecurityException if you try to modify a whitelist that you have no access to.
*/
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index 0e70a3e..83baca6 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -411,14 +411,6 @@
public static final int FLAG_IMMUTABLY_RESTRICTED = 1<<4;
/**
- * Flag for {@link #flags}, corresponding to <code>installerExemptIgnored</code>
- * value of {@link android.R.attr#permissionFlags}.
- *
- * <p> Modifier for permission restriction. This permission cannot be exempted by the installer.
- */
- public static final int FLAG_INSTALLER_EXEMPT_IGNORED = 1 << 5;
-
- /**
* Flag for {@link #flags}, indicating that this permission has been
* installed into the system's globally defined permissions.
*/
@@ -724,11 +716,6 @@
}
/** @hide */
- public boolean isInstallerExemptIgnored() {
- return (flags & PermissionInfo.FLAG_INSTALLER_EXEMPT_IGNORED) != 0;
- }
-
- /** @hide */
public boolean isAppOp() {
return (protectionLevel & PermissionInfo.PROTECTION_FLAG_APPOP) != 0;
}
diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java
index 35c99a1..d3bac79 100644
--- a/core/java/android/content/pm/ShortcutManager.java
+++ b/core/java/android/content/pm/ShortcutManager.java
@@ -39,6 +39,7 @@
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.UserHandle;
import com.android.internal.annotations.VisibleForTesting;
@@ -771,4 +772,20 @@
}
}
+ /**
+ * Granting another app the access to the shortcuts you own. You must provide the package name
+ * and their SHA256 certificate digest in order to granting the access.
+ *
+ * Once granted, the other app can retain a copy of all the shortcuts you own when calling
+ * {@link LauncherApps#getShortcuts(LauncherApps.ShortcutQuery, UserHandle)}.
+ */
+ public void updateShortcutVisibility(@NonNull final String packageName,
+ @Nullable final byte[] certificate, final boolean visible) {
+ try {
+ mService.updateShortcutVisibility(mContext.getPackageName(), packageName, certificate,
+ visible, injectMyUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index d81dff8..cfb6e1b 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -18,6 +18,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.TestApi;
import android.annotation.UserIdInt;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
@@ -47,6 +48,7 @@
*
* @hide
*/
+@TestApi
public class UserInfo implements Parcelable {
/**
diff --git a/core/java/android/content/pm/verify/domain/DomainOwner.aidl b/core/java/android/content/pm/verify/domain/DomainOwner.aidl
new file mode 100644
index 0000000..41366d1
--- /dev/null
+++ b/core/java/android/content/pm/verify/domain/DomainOwner.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.verify.domain;
+
+parcelable DomainOwner;
diff --git a/core/java/android/content/pm/verify/domain/DomainOwner.java b/core/java/android/content/pm/verify/domain/DomainOwner.java
new file mode 100644
index 0000000..b050f5d
--- /dev/null
+++ b/core/java/android/content/pm/verify/domain/DomainOwner.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * @hide
+ */
+@SystemApi
+@DataClass(genParcelable = true, genEqualsHashCode = true, genAidl = true, genToString = true)
+public final class DomainOwner implements Parcelable {
+
+ /**
+ * Package name of that owns the domain.
+ */
+ @NonNull
+ private final String mPackageName;
+
+ /**
+ * Whether or not this owner can be automatically overridden.
+ *
+ * @see DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, boolean)
+ */
+ private final boolean mOverrideable;
+
+
+
+ // Code below generated by codegen v1.0.22.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain/DomainOwner.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new DomainOwner.
+ *
+ * @param packageName
+ * Package name of that owns the domain.
+ * @param overrideable
+ * Whether or not this owner can be automatically overridden. If all owners for a domain are
+ * overrideable, then calling
+ * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID,
+ * Set, boolean)} to enable the domain will disable all other owners. On the other hand, if any
+ * of the owners are non-overrideable, then
+ * {@link DomainVerificationManager#setDomainVerificationLinkHandlingAllowed(String,
+ * boolean)} must be called with false to disable all of the other owners before this domain can
+ * be taken by a new owner through
+ * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID,
+ * Set, boolean)}.
+ */
+ @DataClass.Generated.Member
+ public DomainOwner(
+ @NonNull String packageName,
+ boolean overrideable) {
+ this.mPackageName = packageName;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mPackageName);
+ this.mOverrideable = overrideable;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * Package name of that owns the domain.
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * Whether or not this owner can be automatically overridden. If all owners for a domain are
+ * overrideable, then calling
+ * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID,
+ * Set, boolean)} to enable the domain will disable all other owners. On the other hand, if any
+ * of the owners are non-overrideable, then
+ * {@link DomainVerificationManager#setDomainVerificationLinkHandlingAllowed(String,
+ * boolean)} must be called with false to disable all of the other owners before this domain can
+ * be taken by a new owner through
+ * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID,
+ * Set, boolean)}.
+ */
+ @DataClass.Generated.Member
+ public boolean isOverrideable() {
+ return mOverrideable;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "DomainOwner { " +
+ "packageName = " + mPackageName + ", " +
+ "overrideable = " + mOverrideable +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@android.annotation.Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(DomainOwner other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ DomainOwner that = (DomainOwner) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mPackageName, that.mPackageName)
+ && mOverrideable == that.mOverrideable;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mPackageName);
+ _hash = 31 * _hash + Boolean.hashCode(mOverrideable);
+ return _hash;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mOverrideable) flg |= 0x2;
+ dest.writeByte(flg);
+ dest.writeString(mPackageName);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ DomainOwner(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ boolean overrideable = (flg & 0x2) != 0;
+ String packageName = in.readString();
+
+ this.mPackageName = packageName;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mPackageName);
+ this.mOverrideable = overrideable;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<DomainOwner> CREATOR
+ = new Parcelable.Creator<DomainOwner>() {
+ @Override
+ public DomainOwner[] newArray(int size) {
+ return new DomainOwner[size];
+ }
+
+ @Override
+ public DomainOwner createFromParcel(@NonNull android.os.Parcel in) {
+ return new DomainOwner(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1614119379978L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainOwner.java",
+ inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final boolean mOverrideable\nclass DomainOwner extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genEqualsHashCode=true, genAidl=true, genToString=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/content/pm/verify/domain/DomainSet.aidl b/core/java/android/content/pm/verify/domain/DomainSet.aidl
new file mode 100644
index 0000000..fab131d
--- /dev/null
+++ b/core/java/android/content/pm/verify/domain/DomainSet.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.verify.domain;
+
+parcelable DomainSet;
diff --git a/core/java/android/content/pm/verify/domain/DomainSet.java b/core/java/android/content/pm/verify/domain/DomainSet.java
new file mode 100644
index 0000000..243ff08
--- /dev/null
+++ b/core/java/android/content/pm/verify/domain/DomainSet.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.verify.domain;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+import java.util.Set;
+
+/**
+ * Wraps an input set of domains from the client process, to be sent to the server. Handles cases
+ * where the data size is too large by writing data using {@link Parcel#writeBlob(byte[])}.
+ *
+ * @hide
+ */
+@DataClass(genParcelable = true, genAidl = true, genEqualsHashCode = true)
+public class DomainSet implements Parcelable {
+
+ @NonNull
+ private final Set<String> mDomains;
+
+ private void parcelDomains(@NonNull Parcel dest, @SuppressWarnings("unused") int flags) {
+ DomainVerificationUtils.writeHostSet(dest, mDomains);
+ }
+
+ private Set<String> unparcelDomains(@NonNull Parcel in) {
+ return DomainVerificationUtils.readHostSet(in);
+ }
+
+
+
+ // Code below generated by codegen v1.0.22.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain
+ // /DomainSet.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ @DataClass.Generated.Member
+ public DomainSet(
+ @NonNull Set<String> domains) {
+ this.mDomains = domains;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mDomains);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull Set<String> getDomains() {
+ return mDomains;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@android.annotation.Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(DomainSet other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ DomainSet that = (DomainSet) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mDomains, that.mDomains);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mDomains);
+ return _hash;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ parcelDomains(dest, flags);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ protected DomainSet(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ Set<String> domains = unparcelDomains(in);
+
+ this.mDomains = domains;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mDomains);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<DomainSet> CREATOR
+ = new Parcelable.Creator<DomainSet>() {
+ @Override
+ public DomainSet[] newArray(int size) {
+ return new DomainSet[size];
+ }
+
+ @Override
+ public DomainSet createFromParcel(@NonNull Parcel in) {
+ return new DomainSet(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1613169242020L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainSet.java",
+ inputSignatures = "private final @android.annotation.NonNull java.util.Set<java.lang.String> mDomains\nprivate void parcelDomains(android.os.Parcel,int)\nprivate java.util.Set<java.lang.String> unparcelDomains(android.os.Parcel)\nclass DomainSet extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java b/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java
index 7afbe1f..8095875 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java
@@ -19,7 +19,9 @@
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.content.pm.PackageManager;
+import android.os.Parcel;
import android.os.Parcelable;
+import android.util.ArrayMap;
import com.android.internal.util.DataClass;
import com.android.internal.util.Parcelling;
@@ -34,12 +36,12 @@
* against the digital asset links response from the server hosting that domain.
* <p>
* These values for each domain can be modified through
- * {@link DomainVerificationManager#setDomainVerificationStatus(UUID, Set, int)}.
+ * {@link DomainVerificationManager#setDomainVerificationStatus(UUID,
+ * Set, int)}.
*
* @hide
*/
@SystemApi
-@SuppressWarnings("DefaultAnnotationParam")
@DataClass(genAidl = true, genHiddenConstructor = true, genParcelable = true, genToString = true,
genEqualsHashCode = true)
public final class DomainVerificationInfo implements Parcelable {
@@ -71,22 +73,30 @@
private final String mPackageName;
/**
- * Map of host names to their current state. State is an integer, which defaults to
- * {@link DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the
- * domain verification agent (the intended consumer of this API), which can be equal
- * to {@link DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or
- * greater than {@link DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for
- * any unsuccessful response.
+ * Map of host names to their current state. State is an integer, which defaults to {@link
+ * DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the domain
+ * verification agent (the intended consumer of this API), which can be equal to {@link
+ * DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or greater than {@link
+ * DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for any unsuccessful response.
* <p>
- * Any value non-inclusive between those 2 values are reserved for use by the system.
- * The domain verification agent may be able to act on these reserved values, and this
- * ability can be queried using {@link DomainVerificationManager#isStateModifiable(int)}.
- * It is expected that the agent attempt to verify all domains that it can modify the
- * state of, even if it does not understand the meaning of those values.
+ * Any value non-inclusive between those 2 values are reserved for use by the system. The domain
+ * verification agent may be able to act on these reserved values, and this ability can be
+ * queried using {@link DomainVerificationManager#isStateModifiable(int)}. It is expected that
+ * the agent attempt to verify all domains that it can modify the state of, even if it does not
+ * understand the meaning of those values.
*/
@NonNull
private final Map<String, Integer> mHostToStateMap;
+ private void parcelHostToStateMap(Parcel dest, @SuppressWarnings("unused") int flags) {
+ DomainVerificationUtils.writeHostMap(dest, mHostToStateMap);
+ }
+
+ private Map<String, Integer> unparcelHostToStateMap(Parcel in) {
+ return DomainVerificationUtils.readHostMap(in, new ArrayMap<>(),
+ DomainVerificationUserSelection.class.getClassLoader());
+ }
+
// Code below generated by codegen v1.0.22.
@@ -95,7 +105,8 @@
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
- // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain
+ // /DomainVerificationInfo.java
//
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
// Settings > Editor > Code Style > Formatter Control
@@ -123,18 +134,17 @@
* @param packageName
* The package name that this data corresponds to.
* @param hostToStateMap
- * Map of host names to their current state. State is an integer, which defaults to
- * {@link DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the
- * domain verification agent (the intended consumer of this API), which can be equal
- * to {@link DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or
- * greater than {@link DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for
- * any unsuccessful response.
+ * Map of host names to their current state. State is an integer, which defaults to {@link
+ * DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the domain
+ * verification agent (the intended consumer of this API), which can be equal to {@link
+ * DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or greater than {@link
+ * DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for any unsuccessful response.
* <p>
- * Any value non-inclusive between those 2 values are reserved for use by the system.
- * The domain verification agent may be able to act on these reserved values, and this
- * ability can be queried using {@link DomainVerificationManager#isStateModifiable(int)}.
- * It is expected that the agent attempt to verify all domains that it can modify the
- * state of, even if it does not understand the meaning of those values.
+ * Any value non-inclusive between those 2 values are reserved for use by the system. The domain
+ * verification agent may be able to act on these reserved values, and this ability can be
+ * queried using {@link DomainVerificationManager#isStateModifiable(int)}. It is expected that
+ * the agent attempt to verify all domains that it can modify the state of, even if it does not
+ * understand the meaning of those values.
* @hide
*/
@DataClass.Generated.Member
@@ -185,18 +195,17 @@
}
/**
- * Map of host names to their current state. State is an integer, which defaults to
- * {@link DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the
- * domain verification agent (the intended consumer of this API), which can be equal
- * to {@link DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or
- * greater than {@link DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for
- * any unsuccessful response.
+ * Map of host names to their current state. State is an integer, which defaults to {@link
+ * DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the domain
+ * verification agent (the intended consumer of this API), which can be equal to {@link
+ * DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or greater than {@link
+ * DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for any unsuccessful response.
* <p>
- * Any value non-inclusive between those 2 values are reserved for use by the system.
- * The domain verification agent may be able to act on these reserved values, and this
- * ability can be queried using {@link DomainVerificationManager#isStateModifiable(int)}.
- * It is expected that the agent attempt to verify all domains that it can modify the
- * state of, even if it does not understand the meaning of those values.
+ * Any value non-inclusive between those 2 values are reserved for use by the system. The domain
+ * verification agent may be able to act on these reserved values, and this ability can be
+ * queried using {@link DomainVerificationManager#isStateModifiable(int)}. It is expected that
+ * the agent attempt to verify all domains that it can modify the state of, even if it does not
+ * understand the meaning of those values.
*/
@DataClass.Generated.Member
public @NonNull Map<String,Integer> getHostToStateMap() {
@@ -260,13 +269,13 @@
@Override
@DataClass.Generated.Member
- public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
sParcellingForIdentifier.parcel(mIdentifier, dest, flags);
dest.writeString(mPackageName);
- dest.writeMap(mHostToStateMap);
+ parcelHostToStateMap(dest, flags);
}
@Override
@@ -276,14 +285,13 @@
/** @hide */
@SuppressWarnings({"unchecked", "RedundantCast"})
@DataClass.Generated.Member
- /* package-private */ DomainVerificationInfo(@NonNull android.os.Parcel in) {
+ /* package-private */ DomainVerificationInfo(@NonNull Parcel in) {
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
UUID identifier = sParcellingForIdentifier.unparcel(in);
String packageName = in.readString();
- Map<String,Integer> hostToStateMap = new java.util.LinkedHashMap<>();
- in.readMap(hostToStateMap, Integer.class.getClassLoader());
+ Map<String,Integer> hostToStateMap = unparcelHostToStateMap(in);
this.mIdentifier = identifier;
com.android.internal.util.AnnotationValidations.validate(
@@ -307,16 +315,16 @@
}
@Override
- public DomainVerificationInfo createFromParcel(@NonNull android.os.Parcel in) {
+ public DomainVerificationInfo createFromParcel(@NonNull Parcel in) {
return new DomainVerificationInfo(in);
}
};
@DataClass.Generated(
- time = 1611862790369L,
+ time = 1613002530369L,
codegenVersion = "1.0.22",
sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java",
- inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> mHostToStateMap\nclass DomainVerificationInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true)")
+ inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> mHostToStateMap\nprivate void parcelHostToStateMap(android.os.Parcel,int)\nprivate java.util.Map<java.lang.String,java.lang.Integer> unparcelHostToStateMap(android.os.Parcel)\nclass DomainVerificationInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
index cbb3baa..11402af 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
@@ -239,7 +239,15 @@
* {@link Context#createPackageContextAsUser(String, int, UserHandle)} should be used.
*
* Enabling an unverified domain will allow an application to open it, but this can only occur
- * if no other app on the device is approved for the domain.
+ * if no other app on the device is approved for a higher approval level. This can queried
+ * using {@link #getOwnersForDomain(String)}.
+ *
+ * If all owners for a domain are {@link DomainOwner#isOverrideable()}, then calling this to
+ * enable that domain will disable all other owners.
+ *
+ * On the other hand, if any of the owners are non-overrideable, then this must be called with
+ * false for all of the other owners to disable them before the domain can be taken by a new
+ * owner.
*
* @param domainSetId See {@link DomainVerificationInfo#getIdentifier()}.
* @param domains The domains to toggle the state of.
@@ -276,6 +284,19 @@
throws NameNotFoundException;
/**
+ * For the given domain, return all apps which are approved to open it in a
+ * greater than 0 priority. This does not mean that all apps can actually open
+ * an Intent with that domain. That will be decided by the set of apps which
+ * are the highest priority level, ignoring all lower priority levels.
+ *
+ * By default the list will be returned ordered from lowest to highest
+ * priority.
+ */
+ @NonNull
+ @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION)
+ List<DomainOwner> getOwnersForDomain(@NonNull String domain);
+
+ /**
* Thrown if a {@link DomainVerificationInfo#getIdentifier()}} or an associated set of domains
* provided by the caller is no longer valid. This may be recoverable, and the caller should
* re-query the package name associated with the ID using
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java b/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java
index 5938def..8b9865c 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java
@@ -21,11 +21,9 @@
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.verify.domain.IDomainVerificationManager;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
-import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
@@ -89,7 +87,7 @@
int state) throws IllegalArgumentException, NameNotFoundException {
try {
mDomainVerificationManager.setDomainVerificationStatus(domainSetId.toString(),
- new ArrayList<>(domains), state);
+ new DomainSet(domains), state);
} catch (Exception e) {
Exception converted = rethrow(e, domainSetId);
if (converted instanceof NameNotFoundException) {
@@ -126,7 +124,7 @@
throws IllegalArgumentException, NameNotFoundException {
try {
mDomainVerificationManager.setDomainVerificationUserSelection(domainSetId.toString(),
- new ArrayList<>(domains), enabled, mContext.getUserId());
+ new DomainSet(domains), enabled, mContext.getUserId());
} catch (Exception e) {
Exception converted = rethrow(e, domainSetId);
if (converted instanceof NameNotFoundException) {
@@ -158,6 +156,16 @@
}
}
+ @NonNull
+ @Override
+ public List<DomainOwner> getOwnersForDomain(@NonNull String domain) {
+ try {
+ return mDomainVerificationManager.getOwnersForDomain(domain, mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
private Exception rethrow(Exception exception, @Nullable UUID domainSetId) {
return rethrow(exception, domainSetId, null);
}
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationRequest.java b/core/java/android/content/pm/verify/domain/DomainVerificationRequest.java
index 473abce..65f6d7c 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationRequest.java
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationRequest.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.content.Intent;
+import android.os.Parcel;
import android.os.Parcelable;
import com.android.internal.util.DataClass;
@@ -27,11 +28,11 @@
import java.util.Set;
/**
- * Request object sent in the {@link Intent} that's broadcast to the domain verification
- * agent, retrieved through {@link DomainVerificationManager#EXTRA_VERIFICATION_REQUEST}.
+ * Request object sent in the {@link Intent} that's broadcast to the domain verification agent,
+ * retrieved through {@link DomainVerificationManager#EXTRA_VERIFICATION_REQUEST}.
* <p>
- * This contains the set of packages which have been invalidated and will require
- * re-verification. The exact domains can be retrieved with
+ * This contains the set of packages which have been invalidated and will require re-verification.
+ * The exact domains can be retrieved with
* {@link DomainVerificationManager#getDomainVerificationInfo(String)}
*
* @hide
@@ -42,14 +43,22 @@
public final class DomainVerificationRequest implements Parcelable {
/**
- * The package names of the apps that need to be verified. The receiver should call
- * {@link DomainVerificationManager#getDomainVerificationInfo(String)} with each of
- * these values to get the actual set of domains that need to be acted on.
+ * The package names of the apps that need to be verified. The receiver should call {@link
+ * DomainVerificationManager#getDomainVerificationInfo(String)} with each of these values to get
+ * the actual set of domains that need to be acted on.
*/
@NonNull
@DataClass.ParcelWith(Parcelling.BuiltIn.ForStringSet.class)
private final Set<String> mPackageNames;
+ private void parcelPackageNames(@NonNull Parcel dest, @SuppressWarnings("unused") int flags) {
+ DomainVerificationUtils.writeHostSet(dest, mPackageNames);
+ }
+
+ private Set<String> unparcelPackageNames(@NonNull Parcel in) {
+ return DomainVerificationUtils.readHostSet(in);
+ }
+
// Code below generated by codegen v1.0.22.
@@ -58,7 +67,8 @@
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
- // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationRequest.java
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain
+ // /DomainVerificationRequest.java
//
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
// Settings > Editor > Code Style > Formatter Control
@@ -69,9 +79,9 @@
* Creates a new DomainVerificationRequest.
*
* @param packageNames
- * The package names of the apps that need to be verified. The receiver should call
- * {@link DomainVerificationManager#getDomainVerificationInfo(String)} with each of
- * these values to get the actual set of domains that need to be acted on.
+ * The package names of the apps that need to be verified. The receiver should call {@link
+ * DomainVerificationManager#getDomainVerificationInfo(String)} with each of these values to get
+ * the actual set of domains that need to be acted on.
* @hide
*/
@DataClass.Generated.Member
@@ -85,9 +95,9 @@
}
/**
- * The package names of the apps that need to be verified. The receiver should call
- * {@link DomainVerificationManager#getDomainVerificationInfo(String)} with each of
- * these values to get the actual set of domains that need to be acted on.
+ * The package names of the apps that need to be verified. The receiver should call {@link
+ * DomainVerificationManager#getDomainVerificationInfo(String)} with each of these values to get
+ * the actual set of domains that need to be acted on.
*/
@DataClass.Generated.Member
public @NonNull Set<String> getPackageNames() {
@@ -134,11 +144,11 @@
@Override
@DataClass.Generated.Member
- public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
- sParcellingForPackageNames.parcel(mPackageNames, dest, flags);
+ parcelPackageNames(dest, flags);
}
@Override
@@ -148,11 +158,11 @@
/** @hide */
@SuppressWarnings({"unchecked", "RedundantCast"})
@DataClass.Generated.Member
- /* package-private */ DomainVerificationRequest(@NonNull android.os.Parcel in) {
+ /* package-private */ DomainVerificationRequest(@NonNull Parcel in) {
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
- Set<String> packageNames = sParcellingForPackageNames.unparcel(in);
+ Set<String> packageNames = unparcelPackageNames(in);
this.mPackageNames = packageNames;
com.android.internal.util.AnnotationValidations.validate(
@@ -170,16 +180,16 @@
}
@Override
- public DomainVerificationRequest createFromParcel(@NonNull android.os.Parcel in) {
+ public DomainVerificationRequest createFromParcel(@NonNull Parcel in) {
return new DomainVerificationRequest(in);
}
};
@DataClass.Generated(
- time = 1611862814990L,
+ time = 1613169505495L,
codegenVersion = "1.0.22",
sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationRequest.java",
- inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForStringSet.class) java.util.Set<java.lang.String> mPackageNames\nclass DomainVerificationRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true, genAidl=false, genEqualsHashCode=true)")
+ inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForStringSet.class) java.util.Set<java.lang.String> mPackageNames\nprivate void parcelPackageNames(android.os.Parcel,int)\nprivate java.util.Set<java.lang.String> unparcelPackageNames(android.os.Parcel)\nclass DomainVerificationRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true, genAidl=false, genEqualsHashCode=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java b/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java
index 73346ef..d23f5f1 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java
@@ -20,8 +20,10 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.content.Context;
+import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
+import android.util.ArrayMap;
import com.android.internal.util.DataClass;
import com.android.internal.util.Parcelling;
@@ -40,29 +42,46 @@
* toggle affects <b>all</b> links and is not based on the verification state of the domains.
* <p>
* Assuming the toggle is enabled, the user can also select additional unverified domains to grant
- * to the application to open, which is reflected in {@link #getHostToUserSelectionMap()}. But only
- * a single application can be approved for a domain unless the applications are both approved. If
- * another application is approved, the user will not be allowed to enable the domain.
+ * to the application to open, which is reflected in {@link #getHostToStateMap()}. But only a single
+ * application can be approved for a domain unless the applications are both approved. If another
+ * application is approved, the user will not be allowed to enable the domain.
* <p>
* These values can be changed through the
* {@link DomainVerificationManager#setDomainVerificationLinkHandlingAllowed(String,
- * boolean)} and
- * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set,
+ * boolean)} and {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set,
* boolean)} APIs.
* <p>
- * Note that because state is per user, if a different user needs to be changed, one will
- * need to use {@link Context#createContextAsUser(UserHandle, int)} and hold the
- * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission.
+ * Note that because state is per user, if a different user needs to be changed, one will need to
+ * use {@link Context#createContextAsUser(UserHandle, int)} and hold the {@link
+ * android.Manifest.permission#INTERACT_ACROSS_USERS} permission.
*
* @hide
*/
@SystemApi
@SuppressWarnings("DefaultAnnotationParam")
@DataClass(genAidl = true, genHiddenConstructor = true, genParcelable = true, genToString = true,
- genEqualsHashCode = true)
+ genEqualsHashCode = true, genHiddenConstDefs = true)
public final class DomainVerificationUserSelection implements Parcelable {
/**
+ * The domain is unverified and unselected, and the application is unable to open web links
+ * that resolve to the domain.
+ */
+ public static final int DOMAIN_STATE_NONE = 0;
+
+ /**
+ * The domain has been selected through the
+ * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, boolean)}
+ * API, under the assumption it has not been reset by the system.
+ */
+ public static final int DOMAIN_STATE_SELECTED = 1;
+
+ /**
+ * The domain has been previously verified by the domain verification agent.
+ */
+ public static final int DOMAIN_STATE_VERIFIED = 2;
+
+ /**
* @see DomainVerificationInfo#getIdentifier
*/
@NonNull
@@ -88,15 +107,20 @@
private final boolean mLinkHandlingAllowed;
/**
- * Retrieve the existing user selection state for the matching
- * {@link #getPackageName()}, as was previously set by
- * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set,
- * boolean)}.
- *
- * @return Map of hosts to enabled state for the given package and user.
+ * Mapping of domain host to state, as defined by {@link DomainState}.
*/
@NonNull
- private final Map<String, Boolean> mHostToUserSelectionMap;
+ private final Map<String, Integer> mHostToStateMap;
+
+ private void parcelHostToStateMap(Parcel dest, @SuppressWarnings("unused") int flags) {
+ DomainVerificationUtils.writeHostMap(dest, mHostToStateMap);
+ }
+
+ @NonNull
+ private Map<String, Integer> unparcelHostToStateMap(Parcel in) {
+ return DomainVerificationUtils.readHostMap(in, new ArrayMap<>(),
+ DomainVerificationUserSelection.class.getClassLoader());
+ }
@@ -106,14 +130,37 @@
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
- // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain
- // /DomainVerificationUserSelection.java
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java
//
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
// Settings > Editor > Code Style > Formatter Control
//@formatter:off
+ /** @hide */
+ @android.annotation.IntDef(prefix = "DOMAIN_STATE_", value = {
+ DOMAIN_STATE_NONE,
+ DOMAIN_STATE_SELECTED,
+ DOMAIN_STATE_VERIFIED
+ })
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+ @DataClass.Generated.Member
+ public @interface DomainState {}
+
+ /** @hide */
+ @DataClass.Generated.Member
+ public static String domainStateToString(@DomainState int value) {
+ switch (value) {
+ case DOMAIN_STATE_NONE:
+ return "DOMAIN_STATE_NONE";
+ case DOMAIN_STATE_SELECTED:
+ return "DOMAIN_STATE_SELECTED";
+ case DOMAIN_STATE_VERIFIED:
+ return "DOMAIN_STATE_VERIFIED";
+ default: return Integer.toHexString(value);
+ }
+ }
+
/**
* Creates a new DomainVerificationUserSelection.
*
@@ -123,11 +170,8 @@
* The user that this data corresponds to.
* @param linkHandlingAllowed
* Whether or not this package is allowed to open links.
- * @param hostToUserSelectionMap
- * Retrieve the existing user selection state for the matching
- * {@link #getPackageName()}, as was previously set by
- * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set,
- * boolean)}.
+ * @param hostToStateMap
+ * Mapping of domain host to state, as defined by {@link DomainState}.
* @hide
*/
@DataClass.Generated.Member
@@ -136,7 +180,7 @@
@NonNull String packageName,
@NonNull UserHandle user,
@NonNull boolean linkHandlingAllowed,
- @NonNull Map<String,Boolean> hostToUserSelectionMap) {
+ @NonNull Map<String,Integer> hostToStateMap) {
this.mIdentifier = identifier;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mIdentifier);
@@ -149,9 +193,9 @@
this.mLinkHandlingAllowed = linkHandlingAllowed;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mLinkHandlingAllowed);
- this.mHostToUserSelectionMap = hostToUserSelectionMap;
+ this.mHostToStateMap = hostToStateMap;
com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mHostToUserSelectionMap);
+ NonNull.class, null, mHostToStateMap);
// onConstructed(); // You can define this method to get a callback
}
@@ -189,16 +233,11 @@
}
/**
- * Retrieve the existing user selection state for the matching
- * {@link #getPackageName()}, as was previously set by
- * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set,
- * boolean)}.
- *
- * @return Map of hosts to enabled state for the given package and user.
+ * Mapping of domain host to state, as defined by {@link DomainState}.
*/
@DataClass.Generated.Member
- public @NonNull Map<String,Boolean> getHostToUserSelectionMap() {
- return mHostToUserSelectionMap;
+ public @NonNull Map<String,Integer> getHostToStateMap() {
+ return mHostToStateMap;
}
@Override
@@ -212,7 +251,7 @@
"packageName = " + mPackageName + ", " +
"user = " + mUser + ", " +
"linkHandlingAllowed = " + mLinkHandlingAllowed + ", " +
- "hostToUserSelectionMap = " + mHostToUserSelectionMap +
+ "hostToStateMap = " + mHostToStateMap +
" }";
}
@@ -233,7 +272,7 @@
&& java.util.Objects.equals(mPackageName, that.mPackageName)
&& java.util.Objects.equals(mUser, that.mUser)
&& mLinkHandlingAllowed == that.mLinkHandlingAllowed
- && java.util.Objects.equals(mHostToUserSelectionMap, that.mHostToUserSelectionMap);
+ && java.util.Objects.equals(mHostToStateMap, that.mHostToStateMap);
}
@Override
@@ -247,7 +286,7 @@
_hash = 31 * _hash + java.util.Objects.hashCode(mPackageName);
_hash = 31 * _hash + java.util.Objects.hashCode(mUser);
_hash = 31 * _hash + Boolean.hashCode(mLinkHandlingAllowed);
- _hash = 31 * _hash + java.util.Objects.hashCode(mHostToUserSelectionMap);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mHostToStateMap);
return _hash;
}
@@ -264,7 +303,7 @@
@Override
@DataClass.Generated.Member
- public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
@@ -274,7 +313,7 @@
sParcellingForIdentifier.parcel(mIdentifier, dest, flags);
dest.writeString(mPackageName);
dest.writeTypedObject(mUser, flags);
- dest.writeMap(mHostToUserSelectionMap);
+ parcelHostToStateMap(dest, flags);
}
@Override
@@ -284,7 +323,7 @@
/** @hide */
@SuppressWarnings({"unchecked", "RedundantCast"})
@DataClass.Generated.Member
- /* package-private */ DomainVerificationUserSelection(@NonNull android.os.Parcel in) {
+ /* package-private */ DomainVerificationUserSelection(@NonNull Parcel in) {
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
@@ -293,8 +332,7 @@
UUID identifier = sParcellingForIdentifier.unparcel(in);
String packageName = in.readString();
UserHandle user = (UserHandle) in.readTypedObject(UserHandle.CREATOR);
- Map<String,Boolean> hostToUserSelectionMap = new java.util.LinkedHashMap<>();
- in.readMap(hostToUserSelectionMap, Boolean.class.getClassLoader());
+ Map<String,Integer> hostToStateMap = unparcelHostToStateMap(in);
this.mIdentifier = identifier;
com.android.internal.util.AnnotationValidations.validate(
@@ -308,9 +346,9 @@
this.mLinkHandlingAllowed = linkHandlingAllowed;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mLinkHandlingAllowed);
- this.mHostToUserSelectionMap = hostToUserSelectionMap;
+ this.mHostToStateMap = hostToStateMap;
com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mHostToUserSelectionMap);
+ NonNull.class, null, mHostToStateMap);
// onConstructed(); // You can define this method to get a callback
}
@@ -324,16 +362,16 @@
}
@Override
- public DomainVerificationUserSelection createFromParcel(@NonNull android.os.Parcel in) {
+ public DomainVerificationUserSelection createFromParcel(@NonNull Parcel in) {
return new DomainVerificationUserSelection(in);
}
};
@DataClass.Generated(
- time = 1612829797220L,
+ time = 1613683603297L,
codegenVersion = "1.0.22",
sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java",
- inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull android.os.UserHandle mUser\nprivate final @android.annotation.NonNull boolean mLinkHandlingAllowed\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Boolean> mHostToUserSelectionMap\nclass DomainVerificationUserSelection extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true)")
+ inputSignatures = "public static final int DOMAIN_STATE_NONE\npublic static final int DOMAIN_STATE_SELECTED\npublic static final int DOMAIN_STATE_VERIFIED\nprivate final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull android.os.UserHandle mUser\nprivate final @android.annotation.NonNull boolean mLinkHandlingAllowed\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> mHostToStateMap\nprivate void parcelHostToStateMap(android.os.Parcel,int)\nprivate @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> unparcelHostToStateMap(android.os.Parcel)\nclass DomainVerificationUserSelection extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true, genHiddenConstDefs=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationUtils.java b/core/java/android/content/pm/verify/domain/DomainVerificationUtils.java
new file mode 100644
index 0000000..93005fa
--- /dev/null
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationUtils.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.util.ArraySet;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @hide
+ */
+public class DomainVerificationUtils {
+
+ private static final int STRINGS_TARGET_BYTE_SIZE = IBinder.getSuggestedMaxIpcSizeBytes() / 2;
+
+ /**
+ * Write a map containing web hosts to the given parcel, using {@link Parcel#writeBlob(byte[])}
+ * if the limit exceeds {@link IBinder#getSuggestedMaxIpcSizeBytes()} / 2. This assumes that the
+ * written map is the only data structure in the caller that varies based on the host data set.
+ * Other data that will be written to the parcel after this method will not be considered in the
+ * calculation.
+ */
+ public static void writeHostMap(@NonNull Parcel dest, @NonNull Map<String, ?> map) {
+ boolean targetSizeExceeded = false;
+ int totalSize = dest.dataSize();
+ for (String host : map.keySet()) {
+ totalSize += estimatedByteSizeOf(host);
+ if (totalSize > STRINGS_TARGET_BYTE_SIZE) {
+ targetSizeExceeded = true;
+ break;
+ }
+ }
+
+ dest.writeBoolean(targetSizeExceeded);
+
+ if (!targetSizeExceeded) {
+ dest.writeMap(map);
+ return;
+ }
+
+ Parcel data = Parcel.obtain();
+ try {
+ data.writeMap(map);
+ dest.writeBlob(data.marshall());
+ } finally {
+ data.recycle();
+ }
+ }
+
+ /**
+ * Retrieve a map previously written by {@link #writeHostMap(Parcel, Map)}.
+ */
+ @NonNull
+ @SuppressWarnings("rawtypes")
+ public static <T extends Map> T readHostMap(@NonNull Parcel in, @NonNull T map,
+ @NonNull ClassLoader classLoader) {
+ boolean targetSizeExceeded = in.readBoolean();
+
+ if (!targetSizeExceeded) {
+ in.readMap(map, classLoader);
+ return map;
+ }
+
+ Parcel data = Parcel.obtain();
+ try {
+ byte[] blob = in.readBlob();
+ data.unmarshall(blob, 0, blob.length);
+ data.setDataPosition(0);
+ data.readMap(map, classLoader);
+ } finally {
+ data.recycle();
+ }
+
+ return map;
+ }
+
+ /**
+ * {@link ArraySet} variant of {@link #writeHostMap(Parcel, Map)}.
+ */
+ public static void writeHostSet(@NonNull Parcel dest, @NonNull Set<String> set) {
+ boolean targetSizeExceeded = false;
+ int totalSize = dest.dataSize();
+ for (String host : set) {
+ totalSize += estimatedByteSizeOf(host);
+ if (totalSize > STRINGS_TARGET_BYTE_SIZE) {
+ targetSizeExceeded = true;
+ break;
+ }
+ }
+
+ dest.writeBoolean(targetSizeExceeded);
+
+ if (!targetSizeExceeded) {
+ writeSet(dest, set);
+ return;
+ }
+
+ Parcel data = Parcel.obtain();
+ try {
+ writeSet(data, set);
+ dest.writeBlob(data.marshall());
+ } finally {
+ data.recycle();
+ }
+ }
+
+ /**
+ * {@link ArraySet} variant of {@link #readHostMap(Parcel, Map, ClassLoader)}.
+ */
+ @NonNull
+ public static Set<String> readHostSet(@NonNull Parcel in) {
+ boolean targetSizeExceeded = in.readBoolean();
+
+ if (!targetSizeExceeded) {
+ return readSet(in);
+ }
+
+ Parcel data = Parcel.obtain();
+ try {
+ byte[] blob = in.readBlob();
+ data.unmarshall(blob, 0, blob.length);
+ data.setDataPosition(0);
+ return readSet(data);
+ } finally {
+ data.recycle();
+ }
+ }
+
+ private static void writeSet(@NonNull Parcel dest, @Nullable Set<String> set) {
+ if (set == null) {
+ dest.writeInt(-1);
+ return;
+ }
+ dest.writeInt(set.size());
+ for (String string : set) {
+ dest.writeString(string);
+ }
+ }
+
+ @NonNull
+ private static Set<String> readSet(@NonNull Parcel in) {
+ int size = in.readInt();
+ if (size == -1) {
+ return Collections.emptySet();
+ }
+
+ ArraySet<String> set = new ArraySet<>(size);
+ for (int count = 0; count < size; count++) {
+ set.add(in.readString());
+ }
+ return set;
+ }
+
+ /**
+ * Ballpark the size of domains to avoid unnecessary allocation of ashmem when sending domains
+ * across the client-server API.
+ */
+ public static int estimatedByteSizeOf(String string) {
+ return string.length() * 2 + 12;
+ }
+}
diff --git a/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl b/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl
index 21dd623b..701af32 100644
--- a/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl
+++ b/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl
@@ -16,6 +16,8 @@
package android.content.pm.verify.domain;
+import android.content.pm.verify.domain.DomainOwner;
+import android.content.pm.verify.domain.DomainSet;
import android.content.pm.verify.domain.DomainVerificationInfo;
import android.content.pm.verify.domain.DomainVerificationUserSelection;
import java.util.List;
@@ -35,10 +37,13 @@
DomainVerificationUserSelection getDomainVerificationUserSelection(String packageName,
int userId);
- void setDomainVerificationStatus(String domainSetId, in List<String> domains, int state);
+ @nullable
+ List<DomainOwner> getOwnersForDomain(String domain, int userId);
+
+ void setDomainVerificationStatus(String domainSetId, in DomainSet domains, int state);
void setDomainVerificationLinkHandlingAllowed(String packageName, boolean allowed, int userId);
- void setDomainVerificationUserSelection(String domainSetId, in List<String> domains,
+ void setDomainVerificationUserSelection(String domainSetId, in DomainSet domains,
boolean enabled, int userId);
}
diff --git a/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java b/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java
index 663a704..51addc9 100644
--- a/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java
+++ b/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java
@@ -85,8 +85,8 @@
boolean resetLockoutRequiresHardwareAuthToken) {
// TODO(b/179175438): Value should be provided from the HAL
this(sensorId, strength, maxEnrollmentsPerUser, sensorType,
- resetLockoutRequiresHardwareAuthToken, 0 /* sensorLocationX */,
- 0 /* sensorLocationY */, 0 /* sensorRadius */);
+ resetLockoutRequiresHardwareAuthToken, 540 /* sensorLocationX */,
+ 1636 /* sensorLocationY */, 130 /* sensorRadius */);
}
/**
diff --git a/core/java/android/hardware/soundtrigger/OWNERS b/core/java/android/hardware/soundtrigger/OWNERS
index 816bc6b..e5d0370 100644
--- a/core/java/android/hardware/soundtrigger/OWNERS
+++ b/core/java/android/hardware/soundtrigger/OWNERS
@@ -1 +1,2 @@
-include /core/java/android/media/soundtrigger/OWNERS
+ytai@google.com
+elaurent@google.com
diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING
index 1bdc82a..97e03e9 100644
--- a/core/java/android/os/TEST_MAPPING
+++ b/core/java/android/os/TEST_MAPPING
@@ -38,6 +38,23 @@
"include-filter": "com.android.server.pm.parsing.PackageInfoUserFieldsTest"
}
]
+ },
+ {
+ "file_patterns": ["BatteryStats.java"],
+ "name": "FrameworksCoreTests",
+ "options": [
+ { "include-filter": "com.android.internal.os.BatteryStatsTests" },
+ { "exclude-annotation": "com.android.internal.os.SkipPresubmit" }
+ ]
+ },
+ {
+ "file_patterns": ["BatteryStats.java"],
+ "name": "FrameworksServicesTests",
+ "options": [
+ { "include-filter": "com.android.server.am.BatteryStatsServiceTest" },
+ { "include-filter": "com.android.server.am.MeasuredEnergySnapshotTest" },
+ { "include-filter": "com.android.server.am.BatteryExternalStatsWorkerTest" }
+ ]
}
],
"postsubmit": [
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 8bdfd3d..682754e 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1688,6 +1688,7 @@
* @return Whether guest user is always ephemeral
* @hide
*/
+ @TestApi
public static boolean isGuestUserEphemeral() {
return Resources.getSystem()
.getBoolean(com.android.internal.R.bool.config_guestUserEphemeral);
@@ -1802,6 +1803,20 @@
}
/**
+ * @return the user type of the context user.
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS
+ })
+ @UserHandleAware
+ public @NonNull String getUserType() {
+ UserInfo userInfo = getUserInfo(mUserId);
+ return userInfo == null ? "" : userInfo.userType;
+ }
+
+ /**
* Returns the user name of the context user. This call is only available to applications on
* the system image; it requires the {@code android.permission.MANAGE_USERS} or {@code
* android.permission.GET_ACCOUNTS_PRIVILEGED} permissions.
@@ -1809,7 +1824,8 @@
* @return the user name
*/
@RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
- android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}, conditional = true)
+ android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED,
+ android.Manifest.permission.CREATE_USERS}, conditional = true)
@UserHandleAware
public @NonNull String getUserName() {
if (UserHandle.myUserId() == mUserId) {
@@ -2792,6 +2808,7 @@
*/
@RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
Manifest.permission.CREATE_USERS})
+ @TestApi
public @Nullable UserInfo createUser(@Nullable String name, @NonNull String userType,
@UserInfoFlag int flags) {
try {
@@ -2828,6 +2845,7 @@
* @throws UserOperationException if the user could not be created.
* @hide
*/
+ @TestApi
@RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
Manifest.permission.CREATE_USERS})
public @NonNull UserInfo preCreateUser(@NonNull String userType)
@@ -2976,10 +2994,11 @@
*
* @hide
*/
+ @TestApi
@RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
Manifest.permission.CREATE_USERS})
- public UserInfo createProfileForUser(String name, @NonNull String userType,
- @UserInfoFlag int flags, @UserIdInt int userId, String[] disallowedPackages) {
+ public @Nullable UserInfo createProfileForUser(@Nullable String name, @NonNull String userType,
+ @UserInfoFlag int flags, @UserIdInt int userId, @Nullable String[] disallowedPackages) {
try {
return mService.createProfileForUserWithThrow(name, userType, flags, userId,
disallowedPackages);
@@ -3022,9 +3041,10 @@
*
* @hide
*/
+ @TestApi
@RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
Manifest.permission.CREATE_USERS})
- public UserInfo createRestrictedProfile(String name) {
+ public @Nullable UserInfo createRestrictedProfile(@Nullable String name) {
try {
UserHandle parentUserHandle = Process.myUserHandle();
UserInfo user = mService.createRestrictedProfileWithThrow(name,
@@ -3248,10 +3268,11 @@
/**
* Return the number of users currently created on the device.
- * <p>This API is not for use by third-party apps. It requires the {@code MANAGE_USERS}
- * permission.</p>
*/
- @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS
+ })
public int getUserCount() {
List<UserInfo> users = getUsers();
return users != null ? users.size() : 1;
@@ -3274,7 +3295,10 @@
* @hide
*/
@UnsupportedAppUsage
- @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS
+ })
public List<UserInfo> getUsers() {
return getUsers(/*excludePartial= */ true, /* excludeDying= */ false,
/* excludePreCreated= */ true);
@@ -3292,7 +3316,10 @@
* @return the list of users that were created.
* @hide
*/
- @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS
+ })
public @NonNull List<UserInfo> getAliveUsers() {
return getUsers(/*excludePartial= */ true, /* excludeDying= */ true,
/* excludePreCreated= */ true);
@@ -3306,7 +3333,10 @@
*/
@Deprecated
@UnsupportedAppUsage
- @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS
+ })
public @NonNull List<UserInfo> getUsers(boolean excludeDying) {
return getUsers(/*excludePartial= */ true, excludeDying,
/* excludePreCreated= */ true);
@@ -3317,8 +3347,12 @@
*
* @hide
*/
- @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
- public List<UserInfo> getUsers(boolean excludePartial, boolean excludeDying,
+ @TestApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS
+ })
+ public @NonNull List<UserInfo> getUsers(boolean excludePartial, boolean excludeDying,
boolean excludePreCreated) {
try {
return mService.getUsers(excludePartial, excludeDying, excludePreCreated);
@@ -3335,7 +3369,10 @@
* @hide
*/
@SystemApi
- @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS
+ })
public @NonNull List<UserHandle> getUserHandles(boolean excludeDying) {
List<UserInfo> users = getUsers(/* excludePartial= */ true, excludeDying,
/* excludePreCreated= */ true);
@@ -3354,7 +3391,10 @@
* @hide
*/
@SystemApi
- @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS
+ })
public long[] getSerialNumbersOfUsers(boolean excludeDying) {
List<UserInfo> users = getUsers(/* excludePartial= */ true, excludeDying,
/* excludePreCreated= */ true);
@@ -3678,7 +3718,10 @@
* @hide
*/
@UnsupportedAppUsage
- @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS
+ })
public UserInfo getProfileParent(@UserIdInt int userId) {
try {
return mService.getProfileParent(userId);
@@ -3697,7 +3740,10 @@
* @hide
*/
@SystemApi
- @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS
+ })
public @Nullable UserHandle getProfileParent(@NonNull UserHandle user) {
UserInfo info = getProfileParent(user.getIdentifier());
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 06203ff..9ffc5aa0 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -426,7 +426,7 @@
// avoid writing a partial response to the zygote.
for (String arg : args) {
// Making two indexOf calls here is faster than running a manually fused loop due
- // to the fact that indexOf is a optimized intrinsic.
+ // to the fact that indexOf is an optimized intrinsic.
if (arg.indexOf('\n') >= 0) {
throw new ZygoteStartFailedEx("Embedded newlines not allowed");
} else if (arg.indexOf('\r') >= 0) {
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index ff01011..bae36b29 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -485,10 +485,6 @@
* One for cases where the installer of the package allowlists a permission. This list
* corresponds to the {@link PackageManager#FLAG_PERMISSION_WHITELIST_INSTALLER} flag. Can be
* accessed by pre-installed holders of a dedicated permission or the installer on record.
- * <li>
- * One for cases where the system exempts the permission when granting a role. This list
- * corresponds to the {@link PackageManager#FLAG_PERMISSION_ALLOWLIST_ROLE} flag. Can be
- * accessed by pre-installed holders of a dedicated permission.
* </ol>
*
* @param packageName the app for which to get allowlisted permissions
@@ -502,7 +498,6 @@
* @see PackageManager#FLAG_PERMISSION_WHITELIST_SYSTEM
* @see PackageManager#FLAG_PERMISSION_WHITELIST_UPGRADE
* @see PackageManager#FLAG_PERMISSION_WHITELIST_INSTALLER
- * @see PackageManager#FLAG_PERMISSION_ALLOWLIST_ROLE
*
* @hide Pending API
*/
@@ -549,10 +544,6 @@
* One for cases where the installer of the package allowlists a permission. This list
* corresponds to the {@link PackageManager#FLAG_PERMISSION_WHITELIST_INSTALLER} flag. Can be
* accessed by pre-installed holders of a dedicated permission or the installer on record.
- * <li>
- * One for cases where the system exempts the permission when granting a role. This list
- * corresponds to the {@link PackageManager#FLAG_PERMISSION_ALLOWLIST_ROLE} flag. Can be
- * accessed by pre-installed holders of a dedicated permission.
* </ol>
* <p>
* You need to specify the allowlists for which to set the allowlisted permissions which will
@@ -570,7 +561,6 @@
* @see PackageManager#FLAG_PERMISSION_WHITELIST_SYSTEM
* @see PackageManager#FLAG_PERMISSION_WHITELIST_UPGRADE
* @see PackageManager#FLAG_PERMISSION_WHITELIST_INSTALLER
- * @see PackageManager#FLAG_PERMISSION_ALLOWLIST_ROLE
*
* @hide Pending API
*/
@@ -613,10 +603,6 @@
* One for cases where the installer of the package allowlists a permission. This list
* corresponds to the {@link PackageManager#FLAG_PERMISSION_WHITELIST_INSTALLER} flag. Can be
* accessed by pre-installed holders of a dedicated permission or the installer on record.
- * <li>
- * One for cases where the system exempts the permission when granting a role. This list
- * corresponds to the {@link PackageManager#FLAG_PERMISSION_ALLOWLIST_ROLE} flag. Can be
- * accessed by pre-installed holders of a dedicated permission.
* </ol>
* <p>
* You need to specify the allowlists for which to set the allowlisted permissions which will
@@ -634,7 +620,6 @@
* @see PackageManager#FLAG_PERMISSION_WHITELIST_SYSTEM
* @see PackageManager#FLAG_PERMISSION_WHITELIST_UPGRADE
* @see PackageManager#FLAG_PERMISSION_WHITELIST_INSTALLER
- * @see PackageManager#FLAG_PERMISSION_ALLOWLIST_ROLE
*
* @hide Pending API
*/
diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java
index 0e35ef9..4c9e77c 100644
--- a/core/java/android/permission/PermissionUsageHelper.java
+++ b/core/java/android/permission/PermissionUsageHelper.java
@@ -57,6 +57,8 @@
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
+import com.android.internal.R;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -186,6 +188,15 @@
== PackageManager.PERMISSION_GRANTED;
}
+ private boolean isSpeechRecognizerUsage(String op, String packageName) {
+ if (!OPSTR_RECORD_AUDIO.equals(op)) {
+ return false;
+ }
+
+ return packageName.equals(
+ mContext.getString(R.string.config_systemSpeechRecognizer));
+ }
+
/**
* @see PermissionManager.getIndicatorAppOpUsageData
*/
@@ -317,7 +328,8 @@
if (packageName.equals(SYSTEM_PKG)
|| (!isUserSensitive(packageName, user, op)
&& !isLocationProvider(packageName, user)
- && !isAppPredictor(packageName, user))) {
+ && !isAppPredictor(packageName, user))
+ && !isSpeechRecognizerUsage(op, packageName)) {
continue;
}
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 4354920..6e89faf 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -157,6 +157,14 @@
public static final String NAMESPACE_BLUETOOTH = "bluetooth";
/**
+ * Namespace for features relating to clipboard.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String NAMESPACE_CLIPBOARD = "clipboard";
+
+ /**
* Namespace for all networking connectivity related features.
*
* @hide
diff --git a/core/java/android/view/IScrollCaptureCallbacks.aidl b/core/java/android/view/IScrollCaptureCallbacks.aidl
index d97e3c6..26eaac0 100644
--- a/core/java/android/view/IScrollCaptureCallbacks.aidl
+++ b/core/java/android/view/IScrollCaptureCallbacks.aidl
@@ -16,12 +16,10 @@
package android.view;
-import android.graphics.Point;
import android.graphics.Rect;
+import android.view.ScrollCaptureResponse;
import android.view.Surface;
-import android.view.IScrollCaptureConnection;
-
/**
* Asynchronous callback channel for responses to scroll capture requests.
*
@@ -29,34 +27,30 @@
*/
interface IScrollCaptureCallbacks {
/**
- * Scroll capture is available, and a connection has been provided.
+ * Provides the result of WindowManagerService#requestScrollCapture
*
- * @param connection a connection to a window process and scrollable content
- * @param scrollAreaInWindow the location of scrolling in global (window) coordinate space
+ * @param response the response which describes the result
*/
- oneway void onConnected(in IScrollCaptureConnection connection, in Rect scrollBounds,
- in Point positionInWindow);
+ oneway void onScrollCaptureResponse(in ScrollCaptureResponse response);
/**
- * The window does not support scroll capture.
- */
- oneway void onUnavailable();
-
- /**
- * Called when the remote end has confirmed the request and is ready to begin providing image
- * requests.
+ * Called in reply to IScrollCaptureConnection#startCapture, when the remote end has confirmed
+ * the request and is ready to begin capturing images.
*/
oneway void onCaptureStarted();
/**
- * Received a response from a capture request.
+ * Received a response from a capture request. The provided rectangle indicates the portion
+ * of the requested rectangle which was captured. An empty rectangle indicates that the request
+ * could not be satisfied (most commonly due to the available scrolling range).
+ *
+ * @param flags flags describing additional status of the result
+ * @param capturedArea the actual area of the image captured
*/
- oneway void onCaptureBufferSent(long frameNumber, in Rect capturedArea);
+ oneway void onImageRequestCompleted(int flags, in Rect capturedArea);
/**
- * Signals that the capture session has completed and the target window may be returned to
- * normal interactive use. This may be due to normal shutdown, or after a timeout or other
- * unrecoverable state change such as activity lifecycle, window visibility or focus.
+ * Signals that the capture session has completed and the target window is ready for normal use.
*/
- oneway void onConnectionClosed();
+ oneway void onCaptureEnded();
}
diff --git a/core/java/android/view/IScrollCaptureConnection.aidl b/core/java/android/view/IScrollCaptureConnection.aidl
index 63a4f48..c55e888 100644
--- a/core/java/android/view/IScrollCaptureConnection.aidl
+++ b/core/java/android/view/IScrollCaptureConnection.aidl
@@ -17,33 +17,45 @@
package android.view;
import android.graphics.Rect;
+import android.os.ICancellationSignal;
import android.view.Surface;
/**
- * Interface implemented by a client of the Scroll Capture framework to receive requests
- * to start, capture images and end the session.
+ * A remote connection to a scroll capture target.
*
* {@hide}
*/
interface IScrollCaptureConnection {
/**
- * Informs the client that it has been selected for scroll capture and should prepare to
- * to begin handling capture requests.
- */
- oneway void startCapture(in Surface surface);
-
- /**
- * Request the client capture an image within the provided rectangle.
+ * Informs the target that it has been selected for scroll capture.
*
- * @see android.view.ScrollCaptureCallback#onScrollCaptureRequest
+ * @param surface a return channel for image buffers
+ *
+ * @return a cancallation signal which is used cancel the request
*/
- oneway void requestImage(in Rect captureArea);
+ ICancellationSignal startCapture(in Surface surface);
/**
- * Inform the client that capture has ended. The client should shut down and release all
- * local resources in use and prepare for return to normal interactive usage.
+ * Request the target capture an image within the provided rectangle.
+ *
+ * @param surface a return channel for image buffers
+ * @param signal a cancallation signal which can interrupt the request
+ *
+ * @return a cancallation signal which is used cancel the request
*/
- oneway void endCapture();
+ ICancellationSignal requestImage(in Rect captureArea);
+
+ /**
+ * Inform the target that capture has ended.
+ *
+ * @return a cancallation signal which is used cancel the request
+ */
+ ICancellationSignal endCapture();
+
+ /**
+ * Closes the connection.
+ */
+ oneway void close();
}
diff --git a/core/java/android/view/ScrollCaptureCallback.java b/core/java/android/view/ScrollCaptureCallback.java
index d3aad2c..1688616 100644
--- a/core/java/android/view/ScrollCaptureCallback.java
+++ b/core/java/android/view/ScrollCaptureCallback.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.UiThread;
import android.graphics.Rect;
+import android.os.CancellationSignal;
import java.util.function.Consumer;
@@ -58,8 +59,6 @@
* @see View#setScrollCaptureHint(int)
* @see View#setScrollCaptureCallback(ScrollCaptureCallback)
* @see Window#registerScrollCaptureCallback(ScrollCaptureCallback)
- *
- * @hide
*/
@UiThread
public interface ScrollCaptureCallback {
@@ -68,80 +67,84 @@
* The system is searching for the appropriate scrolling container to capture and would like to
* know the size and position of scrolling content handled by this callback.
* <p>
- * Implementations should inset {@code containingViewBounds} to cover only the area within the
- * containing view where scrolling content may be positioned. This should cover only the content
- * which tracks with scrolling movement.
+ * To determine scroll bounds, an implementation should inset the visible bounds of the
+ * containing view to cover only the area where scrolling content may be positioned. This
+ * should cover only the content which tracks with scrolling movement.
* <p>
- * Return the updated rectangle to {@code resultConsumer}. If for any reason the scrolling
- * content is not available to capture, a {@code null} rectangle may be returned, and this view
- * will be excluded as the target for this request.
+ * Return the updated rectangle to {@link Consumer#accept onReady.accept}. If for any reason the
+ * scrolling content is not available to capture, a empty rectangle may be returned which will
+ * exclude this view from consideration.
* <p>
- * Responses received after XXXms will be discarded.
- * <p>
- * TODO: finalize timeout
+ * This request may be cancelled via the provided {@link CancellationSignal}. When this happens,
+ * any future call to {@link Consumer#accept onReady.accept} will have no effect and this
+ * content will be omitted from the search results.
*
- * @param onReady consumer for the updated rectangle
+ * @param signal signal to cancel the operation in progress
+ * @param onReady consumer for the updated rectangle
*/
- void onScrollCaptureSearch(@NonNull Consumer<Rect> onReady);
+ void onScrollCaptureSearch(@NonNull CancellationSignal signal, @NonNull Consumer<Rect> onReady);
/**
* Scroll Capture has selected this callback to provide the scrolling image content.
* <p>
- * The onReady signal should be called when ready to begin handling image requests.
+ * {@link Runnable#run onReady.run} should be called when ready to begin handling image
+ * requests.
+ * <p>
+ * This request may be cancelled via the provided {@link CancellationSignal}. When this happens,
+ * any future call to {@link Runnable#run onReady.run} will have no effect and provided session
+ * will not be activated.
+ *
+ * @param session the current session, resources provided by it are valid for use until the
+ * {@link #onScrollCaptureEnd(Runnable) session ends}
+ * @param signal signal to cancel the operation in progress
+ * @param onReady signal used to report completion of the request
*/
- void onScrollCaptureStart(@NonNull ScrollCaptureSession session, @NonNull Runnable onReady);
+ void onScrollCaptureStart(@NonNull ScrollCaptureSession session,
+ @NonNull CancellationSignal signal, @NonNull Runnable onReady);
/**
* An image capture has been requested from the scrolling content.
* <p>
- * <code>captureArea</code> contains the bounds of the image requested, relative to the
- * rectangle provided by {@link ScrollCaptureCallback#onScrollCaptureSearch}, referred to as
- * {@code scrollBounds}.
- * here.
+ * The requested rectangle describes an area inside the target view, relative to
+ * <code>scrollBounds</code>. The content may be offscreen, above or below the current visible
+ * portion of the target view. To handle the request, render the available portion of this
+ * rectangle to a buffer and return it via the Surface available from {@link
+ * ScrollCaptureSession#getSurface()}.
* <p>
- * A series of requests will step by a constant vertical amount relative to {@code
- * scrollBounds}, moving through the scrolling range of content, above and below the current
- * visible area. The rectangle's vertical position will not account for any scrolling movement
- * since capture started. Implementations therefore must track any scroll position changes and
- * subtract this distance from requests.
+ * Note: Implementations are only required to render the requested content, and may do so into
+ * off-screen buffers without scrolling if they are able.
* <p>
- * To handle a request, the content should be scrolled to maximize the visible area of the
- * requested rectangle. Offset {@code captureArea} again to account for any further scrolling.
+ * The resulting available portion of the request must be computed as a portion of {@code
+ * captureArea}, and sent to signal the operation is complete, using {@link Consumer#accept
+ * onComplete.accept}. If the requested rectangle is partially or fully out of bounds the
+ * resulting portion should be returned. If no portion is available (outside of available
+ * content), then skip sending any buffer and report an empty Rect as result.
* <p>
- * Finally, clip this rectangle against scrollBounds to determine what portion, if any is
- * visible content to capture. If the rectangle is completely clipped, set it to {@link
- * Rect#setEmpty() empty} and skip the next step.
- * <p>
- * Make a copy of {@code captureArea}, transform to window coordinates and draw the window,
- * clipped to this rectangle, into the {@link ScrollCaptureSession#getSurface() surface} at
- * offset (0,0).
- * <p>
- * Finally, return the resulting {@code captureArea} using
- * {@link ScrollCaptureSession#notifyBufferSent}.
- * <p>
- * If the response is not supplied within XXXms, the session will end with a call to {@link
- * #onScrollCaptureEnd}, after which {@code session} is invalid and should be discarded.
- * <p>
- * TODO: finalize timeout
- * <p>
+ * This request may be cancelled via the provided {@link CancellationSignal}. When this happens,
+ * any future call to {@link Consumer#accept onComplete.accept} will be ignored until the next
+ * request.
*
+ * @param session the current session, resources provided by it are valid for use until the
+ * {@link #onScrollCaptureEnd(Runnable) session ends}
+ * @param signal signal to cancel the operation in progress
* @param captureArea the area to capture, a rectangle within {@code scrollBounds}
+ * @param onComplete a consumer for the captured area
*/
- void onScrollCaptureImageRequest(
- @NonNull ScrollCaptureSession session, @NonNull Rect captureArea);
+ void onScrollCaptureImageRequest(@NonNull ScrollCaptureSession session,
+ @NonNull CancellationSignal signal, @NonNull Rect captureArea,
+ @NonNull Consumer<Rect> onComplete);
/**
* Signals that capture has ended. Implementations should release any temporary resources or
* references to objects in use during the capture. Any resources obtained from the session are
* now invalid and attempts to use them after this point may throw an exception.
* <p>
- * The window should be returned as much as possible to its original state when capture started.
- * At a minimum, the content should be scrolled to its original position.
+ * The window should be returned to its original state when capture started. At a minimum, the
+ * content should be scrolled to its original position.
* <p>
- * <code>onReady</code> should be called when the window should be made visible and
- * interactive. The system will wait up to XXXms for this call before proceeding.
- * <p>
- * TODO: finalize timeout
+ * {@link Runnable#run onReady.run} should be called as soon as possible after the window is
+ * ready for normal interactive use. After the callback (or after a timeout, if not called) the
+ * screenshot tool will be dismissed and the window may become visible to the user at any time.
*
* @param onReady a callback to inform the system that the application has completed any
* cleanup and is ready to become visible
diff --git a/core/java/android/view/ScrollCaptureConnection.java b/core/java/android/view/ScrollCaptureConnection.java
index 0e6cdd1d..3456e01 100644
--- a/core/java/android/view/ScrollCaptureConnection.java
+++ b/core/java/android/view/ScrollCaptureConnection.java
@@ -18,18 +18,23 @@
import static java.util.Objects.requireNonNull;
+import android.annotation.BinderThread;
import android.annotation.NonNull;
import android.annotation.UiThread;
-import android.annotation.WorkerThread;
import android.graphics.Point;
import android.graphics.Rect;
-import android.os.Handler;
+import android.os.CancellationSignal;
+import android.os.ICancellationSignal;
import android.os.RemoteException;
+import android.os.Trace;
import android.util.CloseGuard;
+import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
-import java.util.concurrent.atomic.AtomicBoolean;
+import java.lang.ref.WeakReference;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* Mediator between a selected scroll capture target view and a remote process.
@@ -41,270 +46,276 @@
public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub {
private static final String TAG = "ScrollCaptureConnection";
- private static final int DEFAULT_TIMEOUT = 1000;
- private final Handler mHandler;
- private ScrollCaptureTarget mSelectedTarget;
- private int mTimeoutMillis = DEFAULT_TIMEOUT;
-
- protected Surface mSurface;
- private IScrollCaptureCallbacks mCallbacks;
-
+ private final Object mLock = new Object();
private final Rect mScrollBounds;
private final Point mPositionInWindow;
private final CloseGuard mCloseGuard;
+ private final Executor mUiThread;
- // The current session instance in use by the callback.
+ private ScrollCaptureCallback mLocal;
+ private IScrollCaptureCallbacks mRemote;
+
private ScrollCaptureSession mSession;
- // Helps manage timeout callbacks registered to handler and aids testing.
- private DelayedAction mTimeoutAction;
+ private CancellationSignal mCancellation;
+
+ private volatile boolean mStarted;
+ private volatile boolean mConnected;
/**
* Constructs a ScrollCaptureConnection.
*
* @param selectedTarget the target the client is controlling
- * @param callbacks the callbacks to reply to system requests
+ * @param remote the callbacks to reply to system requests
*
* @hide
*/
public ScrollCaptureConnection(
+ @NonNull Executor uiThread,
@NonNull ScrollCaptureTarget selectedTarget,
- @NonNull IScrollCaptureCallbacks callbacks) {
+ @NonNull IScrollCaptureCallbacks remote) {
+ mUiThread = requireNonNull(uiThread, "<uiThread> must non-null");
requireNonNull(selectedTarget, "<selectedTarget> must non-null");
- requireNonNull(callbacks, "<callbacks> must non-null");
- final Rect scrollBounds = requireNonNull(selectedTarget.getScrollBounds(),
+ mRemote = requireNonNull(remote, "<callbacks> must non-null");
+ mScrollBounds = requireNonNull(Rect.copyOrNull(selectedTarget.getScrollBounds()),
"target.getScrollBounds() must be non-null to construct a client");
- mSelectedTarget = selectedTarget;
- mHandler = selectedTarget.getContainingView().getHandler();
- mScrollBounds = new Rect(scrollBounds);
+ mLocal = selectedTarget.getCallback();
mPositionInWindow = new Point(selectedTarget.getPositionInWindow());
- mCallbacks = callbacks;
mCloseGuard = new CloseGuard();
mCloseGuard.open("close");
-
- selectedTarget.getContainingView().addOnAttachStateChangeListener(
- new View.OnAttachStateChangeListener() {
- @Override
- public void onViewAttachedToWindow(View v) {
-
- }
-
- @Override
- public void onViewDetachedFromWindow(View v) {
- selectedTarget.getContainingView().removeOnAttachStateChangeListener(this);
- endCapture();
- }
- });
+ mConnected = true;
}
- @VisibleForTesting
- public void setTimeoutMillis(int timeoutMillis) {
- mTimeoutMillis = timeoutMillis;
- }
-
- @VisibleForTesting
- public DelayedAction getTimeoutAction() {
- return mTimeoutAction;
- }
-
- private void checkConnected() {
- if (mSelectedTarget == null || mCallbacks == null) {
- throw new IllegalStateException("This client has been disconnected.");
- }
- }
-
- private void checkStarted() {
- if (mSession == null) {
- throw new IllegalStateException("Capture session has not been started!");
- }
- }
-
- @WorkerThread // IScrollCaptureConnection
+ @BinderThread
@Override
- public void startCapture(Surface surface) throws RemoteException {
+ public ICancellationSignal startCapture(Surface surface) throws RemoteException {
checkConnected();
- mSurface = surface;
- scheduleTimeout(mTimeoutMillis, this::onStartCaptureTimeout);
- mSession = new ScrollCaptureSession(mSurface, mScrollBounds, mPositionInWindow, this);
- mHandler.post(() -> mSelectedTarget.getCallback().onScrollCaptureStart(mSession,
- this::onStartCaptureCompleted));
+ if (!surface.isValid()) {
+ throw new RemoteException(new IllegalArgumentException("surface must be valid"));
+ }
+
+ ICancellationSignal cancellation = CancellationSignal.createTransport();
+ mCancellation = CancellationSignal.fromTransport(cancellation);
+ mSession = new ScrollCaptureSession(surface, mScrollBounds, mPositionInWindow);
+
+ Runnable listener =
+ SafeCallback.create(mCancellation, mUiThread, this::onStartCaptureCompleted);
+ // -> UiThread
+ mUiThread.execute(() -> mLocal.onScrollCaptureStart(mSession, mCancellation, listener));
+ return cancellation;
}
@UiThread
private void onStartCaptureCompleted() {
- if (cancelTimeout()) {
- mHandler.post(() -> {
- try {
- mCallbacks.onCaptureStarted();
- } catch (RemoteException e) {
- doShutdown();
- }
- });
+ mStarted = true;
+ try {
+ mRemote.onCaptureStarted();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Shutting down due to error: ", e);
+ close();
}
}
- @UiThread
- private void onStartCaptureTimeout() {
- endCapture();
- }
- @WorkerThread // IScrollCaptureConnection
+ @BinderThread
@Override
- public void requestImage(Rect requestRect) {
+ public ICancellationSignal requestImage(Rect requestRect) throws RemoteException {
+ Trace.beginSection("requestImage");
checkConnected();
checkStarted();
- scheduleTimeout(mTimeoutMillis, this::onRequestImageTimeout);
- // Response is dispatched via ScrollCaptureSession, to onRequestImageCompleted
- mHandler.post(() -> mSelectedTarget.getCallback().onScrollCaptureImageRequest(
- mSession, new Rect(requestRect)));
+
+ ICancellationSignal cancellation = CancellationSignal.createTransport();
+ mCancellation = CancellationSignal.fromTransport(cancellation);
+
+ Consumer<Rect> listener =
+ SafeCallback.create(mCancellation, mUiThread, this::onImageRequestCompleted);
+ // -> UiThread
+ mUiThread.execute(() -> mLocal.onScrollCaptureImageRequest(
+ mSession, mCancellation, new Rect(requestRect), listener));
+ Trace.endSection();
+ return cancellation;
}
@UiThread
- void onRequestImageCompleted(long frameNumber, Rect capturedArea) {
- final Rect finalCapturedArea = new Rect(capturedArea);
- if (cancelTimeout()) {
- mHandler.post(() -> {
- try {
- mCallbacks.onCaptureBufferSent(frameNumber, finalCapturedArea);
- } catch (RemoteException e) {
- doShutdown();
- }
- });
- }
- }
-
- @UiThread
- private void onRequestImageTimeout() {
- endCapture();
- }
-
- @WorkerThread // IScrollCaptureConnection
- @Override
- public void endCapture() {
- if (isStarted()) {
- scheduleTimeout(mTimeoutMillis, this::onEndCaptureTimeout);
- mHandler.post(() ->
- mSelectedTarget.getCallback().onScrollCaptureEnd(this::onEndCaptureCompleted));
- } else {
- disconnect();
- }
- }
-
- private boolean isStarted() {
- return mCallbacks != null && mSelectedTarget != null;
- }
-
- @UiThread
- private void onEndCaptureCompleted() { // onEndCaptureCompleted
- if (cancelTimeout()) {
- doShutdown();
- }
- }
-
- @UiThread
- private void onEndCaptureTimeout() {
- doShutdown();
- }
-
-
- private void doShutdown() {
+ void onImageRequestCompleted(Rect capturedArea) {
try {
- if (mCallbacks != null) {
- mCallbacks.onConnectionClosed();
- }
+ mRemote.onImageRequestCompleted(0, capturedArea);
} catch (RemoteException e) {
- // Ignore
- } finally {
- disconnect();
+ Log.w(TAG, "Shutting down due to error: ", e);
+ close();
}
}
+ @BinderThread
+ @Override
+ public ICancellationSignal endCapture() throws RemoteException {
+ checkConnected();
+ checkStarted();
+
+ ICancellationSignal cancellation = CancellationSignal.createTransport();
+ mCancellation = CancellationSignal.fromTransport(cancellation);
+
+ Runnable listener =
+ SafeCallback.create(mCancellation, mUiThread, this::onEndCaptureCompleted);
+ // -> UiThread
+ mUiThread.execute(() -> mLocal.onScrollCaptureEnd(listener));
+ return cancellation;
+ }
+
+ @UiThread
+ private void onEndCaptureCompleted() {
+ synchronized (mLock) {
+ mStarted = false;
+ try {
+ mRemote.onCaptureEnded();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Shutting down due to error: ", e);
+ close();
+ }
+ }
+ }
+
+ @BinderThread
+ @Override
+ public void close() {
+ if (mStarted) {
+ Log.w(TAG, "close(): capture is still started?! Ending now.");
+
+ // -> UiThread
+ mUiThread.execute(() -> mLocal.onScrollCaptureEnd(() -> { /* ignore */ }));
+ mStarted = false;
+ }
+ disconnect();
+ }
+
/**
* Shuts down this client and releases references to dependent objects. No attempt is made
* to notify the controller, use with caution!
*/
- public void disconnect() {
- if (mSession != null) {
- mSession.disconnect();
+ private void disconnect() {
+ synchronized (mLock) {
mSession = null;
+ mConnected = false;
+ mStarted = false;
+ mRemote = null;
+ mLocal = null;
+ mCloseGuard.close();
}
+ }
- mSelectedTarget = null;
- mCallbacks = null;
+ public boolean isConnected() {
+ return mConnected;
+ }
+
+ public boolean isStarted() {
+ return mStarted;
+ }
+
+ private synchronized void checkConnected() throws RemoteException {
+ synchronized (mLock) {
+ if (!mConnected) {
+ throw new RemoteException(new IllegalStateException("Not connected"));
+ }
+ }
+ }
+
+ private void checkStarted() throws RemoteException {
+ synchronized (mLock) {
+ if (!mStarted) {
+ throw new RemoteException(new IllegalStateException("Not started!"));
+ }
+ }
}
/** @return a string representation of the state of this client */
public String toString() {
return "ScrollCaptureConnection{"
+ + "connected=" + mConnected
+ + ", started=" + mStarted
+ ", session=" + mSession
- + ", selectedTarget=" + mSelectedTarget
- + ", clientCallbacks=" + mCallbacks
+ + ", remote=" + mRemote
+ + ", local=" + mLocal
+ "}";
}
- private boolean cancelTimeout() {
- if (mTimeoutAction != null) {
- return mTimeoutAction.cancel();
- }
- return false;
- }
-
- private void scheduleTimeout(long timeoutMillis, Runnable action) {
- if (mTimeoutAction != null) {
- mTimeoutAction.cancel();
- }
- mTimeoutAction = new DelayedAction(mHandler, timeoutMillis, action);
- }
-
- /** @hide */
@VisibleForTesting
- public static class DelayedAction {
- private final AtomicBoolean mCompleted = new AtomicBoolean();
- private final Object mToken = new Object();
- private final Handler mHandler;
- private final Runnable mAction;
+ public CancellationSignal getCancellation() {
+ return mCancellation;
+ }
- @VisibleForTesting
- public DelayedAction(Handler handler, long timeoutMillis, Runnable action) {
- mHandler = handler;
- mAction = action;
- mHandler.postDelayed(this::onTimeout, mToken, timeoutMillis);
- }
-
- private boolean onTimeout() {
- if (mCompleted.compareAndSet(false, true)) {
- mAction.run();
- return true;
+ protected void finalize() throws Throwable {
+ try {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
}
- return false;
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private static class SafeCallback<T> {
+ private final CancellationSignal mSignal;
+ private final WeakReference<T> mTargetRef;
+ private final Executor mExecutor;
+ private boolean mExecuted;
+
+ protected SafeCallback(CancellationSignal signal, Executor executor, T target) {
+ mSignal = signal;
+ mTargetRef = new WeakReference<>(target);
+ mExecutor = executor;
}
- /**
- * Cause the timeout action to run immediately and mark as timed out.
- *
- * @return true if the timeout was run, false if the timeout had already been canceled
- */
- @VisibleForTesting
- public boolean timeoutNow() {
- return onTimeout();
- }
-
- /**
- * Attempt to cancel the timeout action (such as after a callback is made)
- *
- * @return true if the timeout was canceled and will not run, false if time has expired and
- * the timeout action has or will run momentarily
- */
- public boolean cancel() {
- if (!mCompleted.compareAndSet(false, true)) {
- // Whoops, too late!
- return false;
+ // Provide the target to the consumer to invoke, forward on handler thread ONCE,
+ // and only if noy cancelled, and the target is still available (not collected)
+ protected final void maybeAccept(Consumer<T> targetConsumer) {
+ if (mExecuted) {
+ return;
}
- mHandler.removeCallbacksAndMessages(mToken);
- return true;
+ mExecuted = true;
+ if (mSignal.isCanceled()) {
+ return;
+ }
+ T target = mTargetRef.get();
+ if (target == null) {
+ return;
+ }
+ mExecutor.execute(() -> targetConsumer.accept(target));
+ }
+
+ static Runnable create(CancellationSignal signal, Executor executor, Runnable target) {
+ return new RunnableCallback(signal, executor, target);
+ }
+
+ static <T> Consumer<T> create(CancellationSignal signal, Executor executor,
+ Consumer<T> target) {
+ return new ConsumerCallback<T>(signal, executor, target);
+ }
+ }
+
+ private static final class RunnableCallback extends SafeCallback<Runnable> implements Runnable {
+ RunnableCallback(CancellationSignal signal, Executor executor, Runnable target) {
+ super(signal, executor, target);
+ }
+
+ @Override
+ public void run() {
+ maybeAccept(Runnable::run);
+ }
+ }
+
+ private static final class ConsumerCallback<T> extends SafeCallback<Consumer<T>>
+ implements Consumer<T> {
+ ConsumerCallback(CancellationSignal signal, Executor executor, Consumer<T> target) {
+ super(signal, executor, target);
+ }
+
+ @Override
+ public void accept(T value) {
+ maybeAccept((target) -> target.accept(value));
}
}
}
diff --git a/core/java/android/view/ScrollCaptureResponse.aidl b/core/java/android/view/ScrollCaptureResponse.aidl
new file mode 100644
index 0000000..3de2b80
--- /dev/null
+++ b/core/java/android/view/ScrollCaptureResponse.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+parcelable ScrollCaptureResponse;
diff --git a/core/java/android/view/ScrollCaptureResponse.java b/core/java/android/view/ScrollCaptureResponse.java
new file mode 100644
index 0000000..564113e
--- /dev/null
+++ b/core/java/android/view/ScrollCaptureResponse.java
@@ -0,0 +1,381 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Rect;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+import java.util.ArrayList;
+
+/** @hide */
+@DataClass(genToString = true, genGetters = true)
+public class ScrollCaptureResponse implements Parcelable {
+
+ /** Developer-facing human readable description of the result. */
+ @NonNull
+ private String mDescription = "";
+
+ // Remaining fields are non-null when isConnected() == true
+
+ /** The active connection for a successful result. */
+ @Nullable
+ @DataClass.MaySetToNull
+ private IScrollCaptureConnection mConnection = null;
+
+ /** The bounds of the window within the display */
+ @Nullable
+ private Rect mWindowBounds = null;
+
+ /** The bounds of the scrolling content, in window space. */
+ @Nullable
+ private Rect mBoundsInWindow = null;
+
+ /** The current window title. */
+ @Nullable
+ private String mWindowTitle = null;
+
+ /** Carries additional logging and debugging information when enabled. */
+ @NonNull
+ @DataClass.PluralOf("message")
+ private ArrayList<String> mMessages = new ArrayList<>();
+
+ /** Whether a connection has been returned. */
+ public boolean isConnected() {
+ return mConnection != null;
+ }
+
+
+
+
+ // Code below generated by codegen v1.0.22.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/ScrollCaptureResponse.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ @DataClass.Generated.Member
+ /* package-private */ ScrollCaptureResponse(
+ @NonNull String description,
+ @Nullable IScrollCaptureConnection connection,
+ @Nullable Rect windowBounds,
+ @Nullable Rect boundsInWindow,
+ @Nullable String windowTitle,
+ @NonNull ArrayList<String> messages) {
+ this.mDescription = description;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mDescription);
+ this.mConnection = connection;
+ this.mWindowBounds = windowBounds;
+ this.mBoundsInWindow = boundsInWindow;
+ this.mWindowTitle = windowTitle;
+ this.mMessages = messages;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mMessages);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * Developer-facing human readable description of the result.
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getDescription() {
+ return mDescription;
+ }
+
+ /**
+ * The active connection for a successful result.
+ */
+ @DataClass.Generated.Member
+ public @Nullable IScrollCaptureConnection getConnection() {
+ return mConnection;
+ }
+
+ /**
+ * The bounds of the window within the display
+ */
+ @DataClass.Generated.Member
+ public @Nullable Rect getWindowBounds() {
+ return mWindowBounds;
+ }
+
+ /**
+ * The bounds of the scrolling content, in window space.
+ */
+ @DataClass.Generated.Member
+ public @Nullable Rect getBoundsInWindow() {
+ return mBoundsInWindow;
+ }
+
+ /**
+ * The current window title.
+ */
+ @DataClass.Generated.Member
+ public @Nullable String getWindowTitle() {
+ return mWindowTitle;
+ }
+
+ /**
+ * Carries additional logging and debugging information when enabled.
+ */
+ @DataClass.Generated.Member
+ public @NonNull ArrayList<String> getMessages() {
+ return mMessages;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "ScrollCaptureResponse { " +
+ "description = " + mDescription + ", " +
+ "connection = " + mConnection + ", " +
+ "windowBounds = " + mWindowBounds + ", " +
+ "boundsInWindow = " + mBoundsInWindow + ", " +
+ "windowTitle = " + mWindowTitle + ", " +
+ "messages = " + mMessages +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mConnection != null) flg |= 0x2;
+ if (mWindowBounds != null) flg |= 0x4;
+ if (mBoundsInWindow != null) flg |= 0x8;
+ if (mWindowTitle != null) flg |= 0x10;
+ dest.writeByte(flg);
+ dest.writeString(mDescription);
+ if (mConnection != null) dest.writeStrongInterface(mConnection);
+ if (mWindowBounds != null) dest.writeTypedObject(mWindowBounds, flags);
+ if (mBoundsInWindow != null) dest.writeTypedObject(mBoundsInWindow, flags);
+ if (mWindowTitle != null) dest.writeString(mWindowTitle);
+ dest.writeStringList(mMessages);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ protected ScrollCaptureResponse(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ String description = in.readString();
+ IScrollCaptureConnection connection = (flg & 0x2) == 0 ? null : IScrollCaptureConnection.Stub.asInterface(in.readStrongBinder());
+ Rect windowBounds = (flg & 0x4) == 0 ? null : (Rect) in.readTypedObject(Rect.CREATOR);
+ Rect boundsInWindow = (flg & 0x8) == 0 ? null : (Rect) in.readTypedObject(Rect.CREATOR);
+ String windowTitle = (flg & 0x10) == 0 ? null : in.readString();
+ ArrayList<String> messages = new ArrayList<>();
+ in.readStringList(messages);
+
+ this.mDescription = description;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mDescription);
+ this.mConnection = connection;
+ this.mWindowBounds = windowBounds;
+ this.mBoundsInWindow = boundsInWindow;
+ this.mWindowTitle = windowTitle;
+ this.mMessages = messages;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mMessages);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<ScrollCaptureResponse> CREATOR
+ = new Parcelable.Creator<ScrollCaptureResponse>() {
+ @Override
+ public ScrollCaptureResponse[] newArray(int size) {
+ return new ScrollCaptureResponse[size];
+ }
+
+ @Override
+ public ScrollCaptureResponse createFromParcel(@NonNull android.os.Parcel in) {
+ return new ScrollCaptureResponse(in);
+ }
+ };
+
+ /**
+ * A builder for {@link ScrollCaptureResponse}
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static class Builder {
+
+ private @NonNull String mDescription;
+ private @Nullable IScrollCaptureConnection mConnection;
+ private @Nullable Rect mWindowBounds;
+ private @Nullable Rect mBoundsInWindow;
+ private @Nullable String mWindowTitle;
+ private @NonNull ArrayList<String> mMessages;
+
+ private long mBuilderFieldsSet = 0L;
+
+ public Builder() {
+ }
+
+ /**
+ * Developer-facing human readable description of the result.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setDescription(@NonNull String value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mDescription = value;
+ return this;
+ }
+
+ /**
+ * The active connection for a successful result.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setConnection(@Nullable IScrollCaptureConnection value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ mConnection = value;
+ return this;
+ }
+
+ /**
+ * The bounds of the window within the display
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setWindowBounds(@NonNull Rect value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4;
+ mWindowBounds = value;
+ return this;
+ }
+
+ /**
+ * The bounds of the scrolling content, in window space.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setBoundsInWindow(@NonNull Rect value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x8;
+ mBoundsInWindow = value;
+ return this;
+ }
+
+ /**
+ * The current window title.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setWindowTitle(@NonNull String value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x10;
+ mWindowTitle = value;
+ return this;
+ }
+
+ /**
+ * Carries additional logging and debugging information when enabled.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setMessages(@NonNull ArrayList<String> value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x20;
+ mMessages = value;
+ return this;
+ }
+
+ /** @see #setMessages */
+ @DataClass.Generated.Member
+ public @NonNull Builder addMessage(@NonNull String value) {
+ if (mMessages == null) setMessages(new ArrayList<>());
+ mMessages.add(value);
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull ScrollCaptureResponse build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x40; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x1) == 0) {
+ mDescription = "";
+ }
+ if ((mBuilderFieldsSet & 0x2) == 0) {
+ mConnection = null;
+ }
+ if ((mBuilderFieldsSet & 0x4) == 0) {
+ mWindowBounds = null;
+ }
+ if ((mBuilderFieldsSet & 0x8) == 0) {
+ mBoundsInWindow = null;
+ }
+ if ((mBuilderFieldsSet & 0x10) == 0) {
+ mWindowTitle = null;
+ }
+ if ((mBuilderFieldsSet & 0x20) == 0) {
+ mMessages = new ArrayList<>();
+ }
+ ScrollCaptureResponse o = new ScrollCaptureResponse(
+ mDescription,
+ mConnection,
+ mWindowBounds,
+ mBoundsInWindow,
+ mWindowTitle,
+ mMessages);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x40) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+
+ @DataClass.Generated(
+ time = 1612282689462L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/core/java/android/view/ScrollCaptureResponse.java",
+ inputSignatures = "private @android.annotation.NonNull java.lang.String mDescription\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.MaySetToNull android.view.IScrollCaptureConnection mConnection\nprivate @android.annotation.Nullable android.graphics.Rect mWindowBounds\nprivate @android.annotation.Nullable android.graphics.Rect mBoundsInWindow\nprivate @android.annotation.Nullable java.lang.String mWindowTitle\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"message\") java.util.ArrayList<java.lang.String> mMessages\npublic boolean isConnected()\nclass ScrollCaptureResponse extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genGetters=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/view/ScrollCaptureSearchResults.java b/core/java/android/view/ScrollCaptureSearchResults.java
new file mode 100644
index 0000000..3469b9d
--- /dev/null
+++ b/core/java/android/view/ScrollCaptureSearchResults.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.annotation.UiThread;
+import android.graphics.Rect;
+import android.os.CancellationSignal;
+import android.util.IndentingPrintWriter;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Collects nodes in the view hierarchy which have been identified as scrollable content.
+ *
+ * @hide
+ */
+@UiThread
+public final class ScrollCaptureSearchResults {
+ private final Executor mExecutor;
+ private final List<ScrollCaptureTarget> mTargets;
+ private final CancellationSignal mCancel;
+
+ private Runnable mOnCompleteListener;
+ private int mCompleted;
+ private boolean mComplete = true;
+
+ public ScrollCaptureSearchResults(Executor executor) {
+ mExecutor = executor;
+ mTargets = new ArrayList<>();
+ mCancel = new CancellationSignal();
+ }
+
+ // Public
+
+ /**
+ * Add the given target to the results.
+ *
+ * @param target the target to consider
+ */
+ public void addTarget(@NonNull ScrollCaptureTarget target) {
+ requireNonNull(target);
+
+ mTargets.add(target);
+ mComplete = false;
+ final ScrollCaptureCallback callback = target.getCallback();
+ final Consumer<Rect> consumer = new SearchRequest(target);
+
+ // Defer so the view hierarchy scan completes first
+ mExecutor.execute(
+ () -> callback.onScrollCaptureSearch(mCancel, consumer));
+ }
+
+ public boolean isComplete() {
+ return mComplete;
+ }
+
+ /**
+ * Provides a callback to be invoked as soon as all responses have been received from all
+ * targets to this point.
+ *
+ * @param onComplete listener to add
+ */
+ public void setOnCompleteListener(Runnable onComplete) {
+ if (mComplete) {
+ onComplete.run();
+ } else {
+ mOnCompleteListener = onComplete;
+ }
+ }
+
+ /**
+ * Indicates whether the search results are empty.
+ *
+ * @return true if no targets have been added
+ */
+ public boolean isEmpty() {
+ return mTargets.isEmpty();
+ }
+
+ /**
+ * Force the results to complete now, cancelling any pending requests and calling a complete
+ * listener if provided.
+ */
+ public void finish() {
+ if (!mComplete) {
+ mCancel.cancel();
+ signalComplete();
+ }
+ }
+
+ private void signalComplete() {
+ mComplete = true;
+ mTargets.sort(PRIORITY_ORDER);
+ if (mOnCompleteListener != null) {
+ mOnCompleteListener.run();
+ mOnCompleteListener = null;
+ }
+ }
+
+ @VisibleForTesting
+ public List<ScrollCaptureTarget> getTargets() {
+ return new ArrayList<>(mTargets);
+ }
+
+ /**
+ * Get the top ranked result out of all completed requests.
+ *
+ * @return the top ranked result
+ */
+ public ScrollCaptureTarget getTopResult() {
+ ScrollCaptureTarget target = mTargets.isEmpty() ? null : mTargets.get(0);
+ return target != null && target.getScrollBounds() != null ? target : null;
+ }
+
+ private class SearchRequest implements Consumer<Rect> {
+ private ScrollCaptureTarget mTarget;
+
+ SearchRequest(ScrollCaptureTarget target) {
+ mTarget = target;
+ }
+
+ @Override
+ public void accept(Rect scrollBounds) {
+ if (mTarget == null || mCancel.isCanceled()) {
+ return;
+ }
+ mExecutor.execute(() -> consume(scrollBounds));
+ }
+
+ private void consume(Rect scrollBounds) {
+ if (mTarget == null || mCancel.isCanceled()) {
+ return;
+ }
+ if (!nullOrEmpty(scrollBounds)) {
+ mTarget.setScrollBounds(scrollBounds);
+ mTarget.updatePositionInWindow();
+ }
+ mCompleted++;
+ mTarget = null;
+
+ // All done?
+ if (mCompleted == mTargets.size()) {
+ signalComplete();
+ }
+ }
+ }
+
+ private static final int AFTER = 1;
+ private static final int BEFORE = -1;
+ private static final int EQUAL = 0;
+
+ static final Comparator<ScrollCaptureTarget> PRIORITY_ORDER = (a, b) -> {
+ if (a == null && b == null) {
+ return 0;
+ } else if (a == null || b == null) {
+ return (a == null) ? 1 : -1;
+ }
+
+ boolean emptyScrollBoundsA = nullOrEmpty(a.getScrollBounds());
+ boolean emptyScrollBoundsB = nullOrEmpty(b.getScrollBounds());
+ if (emptyScrollBoundsA || emptyScrollBoundsB) {
+ if (emptyScrollBoundsA && emptyScrollBoundsB) {
+ return EQUAL;
+ }
+ // Prefer the one with a non-empty scroll bounds
+ if (emptyScrollBoundsA) {
+ return AFTER;
+ }
+ return BEFORE;
+ }
+
+ final View viewA = a.getContainingView();
+ final View viewB = b.getContainingView();
+
+ // Prefer any view with scrollCaptureHint="INCLUDE", over one without
+ // This is an escape hatch for the next rule (descendants first)
+ boolean hintIncludeA = hasIncludeHint(viewA);
+ boolean hintIncludeB = hasIncludeHint(viewB);
+ if (hintIncludeA != hintIncludeB) {
+ return (hintIncludeA) ? BEFORE : AFTER;
+ }
+ // If the views are relatives, prefer the descendant. This allows implementations to
+ // leverage nested scrolling APIs by interacting with the innermost scrollable view (as
+ // would happen with touch input).
+ if (isDescendant(viewA, viewB)) {
+ return BEFORE;
+ }
+ if (isDescendant(viewB, viewA)) {
+ return AFTER;
+ }
+
+ // finally, prefer one with larger scroll bounds
+ int scrollAreaA = area(a.getScrollBounds());
+ int scrollAreaB = area(b.getScrollBounds());
+ return (scrollAreaA >= scrollAreaB) ? BEFORE : AFTER;
+ };
+
+ private static int area(Rect r) {
+ return r.width() * r.height();
+ }
+
+ private static boolean nullOrEmpty(Rect r) {
+ return r == null || r.isEmpty();
+ }
+
+ private static boolean hasIncludeHint(View view) {
+ return (view.getScrollCaptureHint() & View.SCROLL_CAPTURE_HINT_INCLUDE) != 0;
+ }
+
+ /**
+ * Determines if {@code otherView} is a descendant of {@code view}.
+ *
+ * @param view a view
+ * @param otherView another view
+ * @return true if {@code view} is an ancestor of {@code otherView}
+ */
+ private static boolean isDescendant(@NonNull View view, @NonNull View otherView) {
+ if (view == otherView) {
+ return false;
+ }
+ ViewParent otherParent = otherView.getParent();
+ while (otherParent != view && otherParent != null) {
+ otherParent = otherParent.getParent();
+ }
+ return otherParent == view;
+ }
+
+ void dump(IndentingPrintWriter writer) {
+ writer.println("results:");
+ writer.increaseIndent();
+ writer.println("complete: " + isComplete());
+ writer.println("cancelled: " + mCancel.isCanceled());
+ writer.println("targets:");
+ writer.increaseIndent();
+ if (isEmpty()) {
+ writer.println("None");
+ } else {
+ for (int i = 0; i < mTargets.size(); i++) {
+ writer.println("[" + i + "]");
+ writer.increaseIndent();
+ mTargets.get(i).dump(writer);
+ writer.decreaseIndent();
+ }
+ writer.decreaseIndent();
+ }
+ writer.decreaseIndent();
+ }
+}
diff --git a/core/java/android/view/ScrollCaptureSession.java b/core/java/android/view/ScrollCaptureSession.java
index 92617a3..748e7ea 100644
--- a/core/java/android/view/ScrollCaptureSession.java
+++ b/core/java/android/view/ScrollCaptureSession.java
@@ -16,18 +16,15 @@
package android.view;
+import static java.util.Objects.requireNonNull;
+
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.graphics.Point;
import android.graphics.Rect;
/**
* A session represents the scope of interaction between a {@link ScrollCaptureCallback} and the
- * system during an active scroll capture operation. During the scope of a session, a callback
- * will receive a series of requests for image data. Resources provided here are valid for use
- * until {@link ScrollCaptureCallback#onScrollCaptureEnd(Runnable)}.
- *
- * @hide
+ * system during an active scroll capture operation.
*/
public class ScrollCaptureSession {
@@ -35,22 +32,27 @@
private final Rect mScrollBounds;
private final Point mPositionInWindow;
- @Nullable
- private ScrollCaptureConnection mConnection;
-
- /** @hide */
- public ScrollCaptureSession(Surface surface, Rect scrollBounds, Point positionInWindow,
- ScrollCaptureConnection connection) {
- mSurface = surface;
- mScrollBounds = scrollBounds;
- mPositionInWindow = positionInWindow;
- mConnection = connection;
+ /**
+ * Constructs a new session instance.
+ *
+ * @param surface the surface to consume generated images
+ * @param scrollBounds the bounds of the capture area within the containing view
+ * @param positionInWindow the offset of scrollBounds within the window
+ */
+ public ScrollCaptureSession(@NonNull Surface surface, @NonNull Rect scrollBounds,
+ @NonNull Point positionInWindow) {
+ mSurface = requireNonNull(surface);
+ mScrollBounds = requireNonNull(scrollBounds);
+ mPositionInWindow = requireNonNull(positionInWindow);
}
/**
* Returns a
* <a href="https://source.android.com/devices/graphics/arch-bq-gralloc">BufferQueue</a> in the
* form of a {@link Surface} for transfer of image buffers.
+ * <p>
+ * The surface is guaranteed to remain {@link Surface#isValid() valid} until the session
+ * {@link ScrollCaptureCallback#onScrollCaptureEnd(Runnable) ends}.
*
* @return the surface for transferring image buffers
* @throws IllegalStateException if the session has been closed
@@ -80,26 +82,4 @@
public Point getPositionInWindow() {
return mPositionInWindow;
}
-
- /**
- * Notify the system that an a buffer has been posted via the getSurface() channel.
- *
- * @param frameNumber the frame number of the queued buffer
- * @param capturedArea the area captured, relative to scroll bounds
- */
- public void notifyBufferSent(long frameNumber, @NonNull Rect capturedArea) {
- if (mConnection != null) {
- mConnection.onRequestImageCompleted(frameNumber, capturedArea);
- }
- }
-
- /**
- * @hide
- */
- public void disconnect() {
- mConnection = null;
- if (mSurface.isValid()) {
- mSurface.release();
- }
- }
}
diff --git a/core/java/android/view/ScrollCaptureTarget.java b/core/java/android/view/ScrollCaptureTarget.java
index f3fcabb..4fd4889 100644
--- a/core/java/android/view/ScrollCaptureTarget.java
+++ b/core/java/android/view/ScrollCaptureTarget.java
@@ -22,14 +22,16 @@
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.Rect;
+import android.os.CancellationSignal;
import com.android.internal.util.FastMath;
+import java.io.PrintWriter;
+import java.util.function.Consumer;
+
/**
* A target collects the set of contextual information for a ScrollCaptureHandler discovered during
* a {@link View#dispatchScrollCaptureSearch scroll capture search}.
- *
- * @hide
*/
public final class ScrollCaptureTarget {
private final View mContainingView;
@@ -41,7 +43,6 @@
private final float[] mTmpFloatArr = new float[2];
private final Matrix mMatrixViewLocalToWindow = new Matrix();
- private final Rect mTmpRect = new Rect();
public ScrollCaptureTarget(@NonNull View scrollTarget, @NonNull Rect localVisibleRect,
@NonNull Point positionInWindow, @NonNull ScrollCaptureCallback callback) {
@@ -52,7 +53,10 @@
mPositionInWindow = positionInWindow;
}
- /** @return the hint that the {@code containing view} had during the scroll capture search */
+ /**
+ * @return the hint that the {@code containing view} had during the scroll capture search
+ * @see View#getScrollCaptureHint()
+ */
@View.ScrollCaptureHint
public int getHint() {
return mHint;
@@ -71,8 +75,7 @@
}
/**
- * Returns the un-clipped, visible bounds of the containing view during the scroll capture
- * search. This is used to determine on-screen area to assist in selecting the primary target.
+ * Returns the visible bounds of the containing view.
*
* @return the visible bounds of the {@code containing view} in view-local coordinates
*/
@@ -81,13 +84,17 @@
return mLocalVisibleRect;
}
- /** @return the position of the {@code containing view} within the window */
+ /** @return the position of the visible bounds of the containing view within the window */
@NonNull
public Point getPositionInWindow() {
return mPositionInWindow;
}
- /** @return the {@code scroll bounds} for this {@link ScrollCaptureCallback callback} */
+ /**
+ * @return the {@code scroll bounds} for this {@link ScrollCaptureCallback callback}
+ *
+ * @see ScrollCaptureCallback#onScrollCaptureSearch(CancellationSignal, Consumer)
+ */
@Nullable
public Rect getScrollBounds() {
return mScrollBounds;
@@ -119,8 +126,8 @@
}
/**
- * Refresh the value of {@link #mLocalVisibleRect} and {@link #mPositionInWindow} based on the
- * current state of the {@code containing view}.
+ * Refresh the local visible bounds and it's offset within the window, based on the current
+ * state of the {@code containing view}.
*/
@UiThread
public void updatePositionInWindow() {
@@ -132,4 +139,27 @@
roundIntoPoint(mPositionInWindow, mTmpFloatArr);
}
+ public String toString() {
+ return "ScrollCaptureTarget{" + "view=" + mContainingView
+ + ", callback=" + mCallback
+ + ", scrollBounds=" + mScrollBounds
+ + ", localVisibleRect=" + mLocalVisibleRect
+ + ", positionInWindow=" + mPositionInWindow
+ + "}";
+ }
+
+ void dump(@NonNull PrintWriter writer) {
+ View view = getContainingView();
+ writer.println("view: " + view);
+ writer.println("hint: " + mHint);
+ writer.println("callback: " + mCallback);
+ writer.println("scrollBounds: "
+ + (mScrollBounds == null ? "null" : mScrollBounds.toShortString()));
+ Point inWindow = getPositionInWindow();
+ writer.println("positionInWindow: "
+ + ((inWindow == null) ? "null" : "[" + inWindow.x + "," + inWindow.y + "]"));
+ Rect localVisible = getLocalVisibleRect();
+ writer.println("localVisibleRect: "
+ + (localVisible == null ? "null" : localVisible.toShortString()));
+ }
}
diff --git a/core/java/android/view/ScrollCaptureTargetResolver.java b/core/java/android/view/ScrollCaptureTargetResolver.java
deleted file mode 100644
index e4316bb..0000000
--- a/core/java/android/view/ScrollCaptureTargetResolver.java
+++ /dev/null
@@ -1,337 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import android.annotation.AnyThread;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.UiThread;
-import android.graphics.Rect;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.SystemClock;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-
-import java.util.Queue;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Consumer;
-
-/**
- * Queries additional state from a list of {@link ScrollCaptureTarget targets} via asynchronous
- * callbacks, then aggregates and reduces the target list to a single target, or null if no target
- * is suitable.
- * <p>
- * The rules for selection are (in order):
- * <ul>
- * <li>prefer getScrollBounds(): non-empty
- * <li>prefer View.getScrollCaptureHint == SCROLL_CAPTURE_HINT_INCLUDE
- * <li>prefer descendants before parents
- * <li>prefer larger area for getScrollBounds() (clipped to view bounds)
- * </ul>
- *
- * <p>
- * All calls to {@link ScrollCaptureCallback#onScrollCaptureSearch} are made on the main thread,
- * with results are queued and consumed to the main thread as well.
- *
- * @see #start(Handler, long, Consumer)
- *
- * @hide
- */
-@UiThread
-public class ScrollCaptureTargetResolver {
- private static final String TAG = "ScrollCaptureTargetRes";
-
- private final Object mLock = new Object();
-
- private final Queue<ScrollCaptureTarget> mTargets;
- private Handler mHandler;
- private long mTimeLimitMillis;
-
- private Consumer<ScrollCaptureTarget> mWhenComplete;
- private int mPendingBoundsRequests;
- private long mDeadlineMillis;
-
- private ScrollCaptureTarget mResult;
- private boolean mFinished;
-
- private boolean mStarted;
-
- private static int area(Rect r) {
- return r.width() * r.height();
- }
-
- private static boolean nullOrEmpty(Rect r) {
- return r == null || r.isEmpty();
- }
-
- /**
- * Binary operator which selects the best {@link ScrollCaptureTarget}.
- */
- private static ScrollCaptureTarget chooseTarget(ScrollCaptureTarget a, ScrollCaptureTarget b) {
- if (a == null && b == null) {
- return null;
- } else if (a == null || b == null) {
- ScrollCaptureTarget c = (a == null) ? b : a;
- return c;
- }
-
- boolean emptyScrollBoundsA = nullOrEmpty(a.getScrollBounds());
- boolean emptyScrollBoundsB = nullOrEmpty(b.getScrollBounds());
- if (emptyScrollBoundsA || emptyScrollBoundsB) {
- if (emptyScrollBoundsA && emptyScrollBoundsB) {
- // Both have an empty or null scrollBounds
- Log.d(TAG, "chooseTarget: (both have empty or null bounds) return " + null);
- return null;
- }
- // Prefer the one with a non-empty scroll bounds
- if (emptyScrollBoundsA) {
- Log.d(TAG, "chooseTarget: (a has empty or null bounds) return " + b);
- return b;
- }
- Log.d(TAG, "chooseTarget: (b has empty or null bounds) return " + a);
- return a;
- }
-
- final View viewA = a.getContainingView();
- final View viewB = b.getContainingView();
-
- // Prefer any view with scrollCaptureHint="INCLUDE", over one without
- // This is an escape hatch for the next rule (descendants first)
- boolean hintIncludeA = hasIncludeHint(viewA);
- boolean hintIncludeB = hasIncludeHint(viewB);
- if (hintIncludeA != hintIncludeB) {
- ScrollCaptureTarget c = (hintIncludeA) ? a : b;
- Log.d(TAG, "chooseTarget: (has hint=INCLUDE) return " + c);
- return c;
- }
-
- // If the views are relatives, prefer the descendant. This allows implementations to
- // leverage nested scrolling APIs by interacting with the innermost scrollable view (as
- // would happen with touch input).
- if (isDescendant(viewA, viewB)) {
- Log.d(TAG, "chooseTarget: (b is descendant of a) return " + b);
- return b;
- }
- if (isDescendant(viewB, viewA)) {
- Log.d(TAG, "chooseTarget: (a is descendant of b) return " + a);
- return a;
- }
-
- // finally, prefer one with larger scroll bounds
- int scrollAreaA = area(a.getScrollBounds());
- int scrollAreaB = area(b.getScrollBounds());
- ScrollCaptureTarget c = (scrollAreaA >= scrollAreaB) ? a : b;
- Log.d(TAG, "chooseTarget: return " + c);
- return c;
- }
-
- /**
- * Creates an instance to query and filter {@code target}.
- *
- * @param targets a list of {@link ScrollCaptureTarget} as collected by {@link
- * View#dispatchScrollCaptureSearch}.
- * @see #start(Handler, long, Consumer)
- */
- public ScrollCaptureTargetResolver(Queue<ScrollCaptureTarget> targets) {
- mTargets = targets;
- }
-
- void checkThread() {
- if (mHandler.getLooper() != Looper.myLooper()) {
- throw new IllegalStateException("Called from wrong thread! ("
- + Thread.currentThread().getName() + ")");
- }
- }
-
- /**
- * Blocks until a result is returned (after completion or timeout).
- * <p>
- * For testing only. Normal usage should receive a callback after calling {@link #start}.
- */
- @VisibleForTesting
- public ScrollCaptureTarget waitForResult() throws InterruptedException {
- synchronized (mLock) {
- while (!mFinished) {
- mLock.wait();
- }
- }
- return mResult;
- }
-
- private void supplyResult(ScrollCaptureTarget target) {
- checkThread();
- if (mFinished) {
- return;
- }
- mResult = chooseTarget(mResult, target);
- boolean finish = mPendingBoundsRequests == 0
- || SystemClock.uptimeMillis() >= mDeadlineMillis;
- if (finish) {
- mPendingBoundsRequests = 0;
- mWhenComplete.accept(mResult);
- synchronized (mLock) {
- mFinished = true;
- mLock.notify();
- }
- mWhenComplete = null;
- }
- }
-
- /**
- * Asks all targets for {@link ScrollCaptureCallback#onScrollCaptureSearch(Consumer)
- * scrollBounds}, and selects the primary target according to the {@link
- * #chooseTarget} function.
- *
- * @param timeLimitMillis the amount of time to wait for all responses before delivering the top
- * result
- * @param resultConsumer the consumer to receive the primary target
- */
- @AnyThread
- public void start(Handler uiHandler, long timeLimitMillis,
- Consumer<ScrollCaptureTarget> resultConsumer) {
- synchronized (mLock) {
- if (mStarted) {
- throw new IllegalStateException("already started!");
- }
- if (timeLimitMillis < 0) {
- throw new IllegalArgumentException("Time limit must be positive");
- }
- mHandler = uiHandler;
- mTimeLimitMillis = timeLimitMillis;
- mWhenComplete = resultConsumer;
- if (mTargets.isEmpty()) {
- mHandler.post(() -> supplyResult(null));
- return;
- }
- mStarted = true;
- uiHandler.post(this::run);
- }
- }
-
- private void run() {
- checkThread();
-
- mPendingBoundsRequests = mTargets.size();
- for (ScrollCaptureTarget target : mTargets) {
- queryTarget(target);
- }
- mDeadlineMillis = SystemClock.uptimeMillis() + mTimeLimitMillis;
- mHandler.postAtTime(mTimeoutRunnable, mDeadlineMillis);
- }
-
- private final Runnable mTimeoutRunnable = () -> {
- checkThread();
- supplyResult(null);
- };
-
- /**
- * Adds a target to the list and requests {@link ScrollCaptureCallback#onScrollCaptureSearch}
- * scrollBounds} from it. Results are returned by a call to {@link #onScrollBoundsProvided}.
- *
- * @param target the target to add
- */
- @UiThread
- private void queryTarget(@NonNull ScrollCaptureTarget target) {
- checkThread();
- final ScrollCaptureCallback callback = target.getCallback();
- // from the UI thread, request scroll bounds
- callback.onScrollCaptureSearch(
- // allow only one callback to onReady.accept():
- new SingletonConsumer<Rect>(
- // Queue and consume on the UI thread
- ((scrollBounds) -> mHandler.post(
- () -> onScrollBoundsProvided(target, scrollBounds)))));
- }
-
- @UiThread
- private void onScrollBoundsProvided(ScrollCaptureTarget target, @Nullable Rect scrollBounds) {
- checkThread();
- if (mFinished) {
- return;
- }
-
- // Record progress.
- mPendingBoundsRequests--;
-
- // Remove the timeout.
- mHandler.removeCallbacks(mTimeoutRunnable);
-
- boolean doneOrTimedOut = mPendingBoundsRequests == 0
- || SystemClock.uptimeMillis() >= mDeadlineMillis;
-
- final View containingView = target.getContainingView();
- if (!nullOrEmpty(scrollBounds) && containingView.isAggregatedVisible()) {
- target.updatePositionInWindow();
- target.setScrollBounds(scrollBounds);
- supplyResult(target);
- }
-
- if (!mFinished) {
- // Reschedule the timeout.
- mHandler.postAtTime(mTimeoutRunnable, mDeadlineMillis);
- }
- }
-
- private static boolean hasIncludeHint(View view) {
- return (view.getScrollCaptureHint() & View.SCROLL_CAPTURE_HINT_INCLUDE) != 0;
- }
-
- /**
- * Determines if {@code otherView} is a descendant of {@code view}.
- *
- * @param view a view
- * @param otherView another view
- * @return true if {@code view} is an ancestor of {@code otherView}
- */
- private static boolean isDescendant(@NonNull View view, @NonNull View otherView) {
- if (view == otherView) {
- return false;
- }
- ViewParent otherParent = otherView.getParent();
- while (otherParent != view && otherParent != null) {
- otherParent = otherParent.getParent();
- }
- return otherParent == view;
- }
-
- /**
- * A safe wrapper for a consumer callbacks intended to accept a single value. It ensures
- * that the receiver of the consumer does not retain a reference to {@code target} after use nor
- * cause race conditions by invoking {@link Consumer#accept accept} more than once.
- */
- static class SingletonConsumer<T> implements Consumer<T> {
- final AtomicReference<Consumer<T>> mAtomicRef;
-
- /**
- * @param target the target consumer
- **/
- SingletonConsumer(Consumer<T> target) {
- mAtomicRef = new AtomicReference<>(target);
- }
-
- @Override
- public void accept(T t) {
- final Consumer<T> consumer = mAtomicRef.getAndSet(null);
- if (consumer != null) {
- consumer.accept(t);
- }
- }
- }
-}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 0832578..03dd100 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -3504,64 +3504,4 @@
public static Transaction getGlobalTransaction() {
return sGlobalTransaction;
}
-
- /**
- * Wrapper for sending blur data to SurfaceFlinger.
- * @hide
- */
- public static final class BlurRegion {
- public int blurRadius;
- public float cornerRadiusTL;
- public float cornerRadiusTR;
- public float cornerRadiusBL;
- public float cornerRadiusBR;
- public float alpha = 1;
- public boolean visible = true;
- public final Rect rect = new Rect();
-
- private final float[] mFloatArray = new float[10];
-
- public BlurRegion() {
- }
-
- public BlurRegion(BlurRegion other) {
- rect.set(other.rect);
- blurRadius = other.blurRadius;
- alpha = other.alpha;
- cornerRadiusTL = other.cornerRadiusTL;
- cornerRadiusTR = other.cornerRadiusTR;
- cornerRadiusBL = other.cornerRadiusBL;
- cornerRadiusBR = other.cornerRadiusBR;
- }
-
- /**
- * Serializes this class into a float array that's more JNI friendly.
- */
- public float[] toFloatArray() {
- mFloatArray[0] = blurRadius;
- mFloatArray[1] = alpha;
- mFloatArray[2] = rect.left;
- mFloatArray[3] = rect.top;
- mFloatArray[4] = rect.right;
- mFloatArray[5] = rect.bottom;
- mFloatArray[6] = cornerRadiusTL;
- mFloatArray[7] = cornerRadiusTR;
- mFloatArray[8] = cornerRadiusBL;
- mFloatArray[9] = cornerRadiusBR;
- return mFloatArray;
- }
-
- @Override
- public String toString() {
- return "BlurRegion{"
- + "blurRadius=" + blurRadius
- + ", corners={" + cornerRadiusTL
- + "," + cornerRadiusTR
- + "," + cornerRadiusBL
- + "," + cornerRadiusBR
- + "}, alpha=" + alpha
- + ", rect=" + rect
- + "}";
- }
- }
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 3789324..ebef464 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -184,10 +184,10 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
-import java.util.Queue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
import java.util.function.Predicate;
/**
@@ -1484,7 +1484,6 @@
*
* @see #getScrollCaptureHint()
* @see #setScrollCaptureHint(int)
- * @hide
*/
public static final int SCROLL_CAPTURE_HINT_AUTO = 0;
@@ -1495,7 +1494,6 @@
*
* @see #getScrollCaptureHint()
* @see #setScrollCaptureHint(int)
- * @hide
*/
public static final int SCROLL_CAPTURE_HINT_EXCLUDE = 0x1;
@@ -1506,7 +1504,6 @@
*
* @see #getScrollCaptureHint()
* @see #setScrollCaptureHint(int)
- * @hide
*/
public static final int SCROLL_CAPTURE_HINT_INCLUDE = 0x2;
@@ -1517,7 +1514,6 @@
*
* @see #getScrollCaptureHint()
* @see #setScrollCaptureHint(int)
- * @hide
*/
public static final int SCROLL_CAPTURE_HINT_EXCLUDE_DESCENDANTS = 0x4;
@@ -30053,8 +30049,6 @@
* Returns the current scroll capture hint for this view.
*
* @return the current scroll capture hint
- *
- * @hide
*/
@ScrollCaptureHint
public int getScrollCaptureHint() {
@@ -30067,8 +30061,6 @@
* scroll capture targets.
*
* @param hint the scrollCaptureHint flags value to set
- *
- * @hide
*/
public void setScrollCaptureHint(@ScrollCaptureHint int hint) {
mPrivateFlags4 &= ~PFLAG4_SCROLL_CAPTURE_HINT_MASK;
@@ -30088,10 +30080,8 @@
* setting a custom callback to help ensure it is selected as the target.
*
* @param callback the new callback to assign
- *
- * @hide
*/
- public void setScrollCaptureCallback(@Nullable ScrollCaptureCallback callback) {
+ public final void setScrollCaptureCallback(@Nullable ScrollCaptureCallback callback) {
getListenerInfo().mScrollCaptureCallback = callback;
}
@@ -30110,29 +30100,54 @@
}
/**
+ * Dispatch a scroll capture search request down the view hierarchy.
+ *
+ * @param localVisibleRect the visible area of this ViewGroup in local coordinates, according to
+ * the parent
+ * @param windowOffset the offset of this view within the window
+ * @param targets accepts potential scroll capture targets; {@link Consumer#accept
+ * results.accept} may be called zero or more times on the calling
+ * thread before onScrollCaptureSearch returns
+ */
+ public void dispatchScrollCaptureSearch(
+ @NonNull Rect localVisibleRect, @NonNull Point windowOffset,
+ @NonNull Consumer<ScrollCaptureTarget> targets) {
+ onScrollCaptureSearch(localVisibleRect, windowOffset, targets);
+ }
+
+ /**
* Called when scroll capture is requested, to search for appropriate content to scroll. If
* applicable, this view adds itself to the provided list for consideration, subject to the
* flags set by {@link #setScrollCaptureHint}.
*
* @param localVisibleRect the local visible rect of this view
* @param windowOffset the offset of localVisibleRect within the window
- * @param targets a queue which collects potential targets
- *
+ * @param targets accepts potential scroll capture targets; {@link Consumer#accept
+ * results.accept} may be called zero or more times on the calling
+ * thread before onScrollCaptureSearch returns
* @throws IllegalStateException if this view is not attached to a window
- * @hide
*/
- public void dispatchScrollCaptureSearch(@NonNull Rect localVisibleRect,
- @NonNull Point windowOffset, @NonNull Queue<ScrollCaptureTarget> targets) {
+ public void onScrollCaptureSearch(@NonNull Rect localVisibleRect,
+ @NonNull Point windowOffset, @NonNull Consumer<ScrollCaptureTarget> targets) {
int hint = getScrollCaptureHint();
if ((hint & SCROLL_CAPTURE_HINT_EXCLUDE) != 0) {
return;
}
+ boolean rectIsVisible = true;
+
+ // Apply clipBounds if present.
+ if (mClipBounds != null) {
+ rectIsVisible = localVisibleRect.intersect(mClipBounds);
+ }
+ if (!rectIsVisible) {
+ return;
+ }
// Get a callback provided by the framework, library or application.
ScrollCaptureCallback callback =
(mListenerInfo == null) ? null : mListenerInfo.mScrollCaptureCallback;
- // Try internal support for standard scrolling containers.
+ // Try framework support for standard scrolling containers.
if (callback == null) {
callback = createScrollCaptureCallbackInternal(localVisibleRect, windowOffset);
}
@@ -30142,7 +30157,7 @@
// Add to the list for consideration
Point offset = new Point(windowOffset.x, windowOffset.y);
Rect rect = new Rect(localVisibleRect);
- targets.add(new ScrollCaptureTarget(this, rect, offset, callback));
+ targets.accept(new ScrollCaptureTarget(this, rect, offset, callback));
}
}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 37bea58..38a5937 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -77,7 +77,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Queue;
+import java.util.function.Consumer;
import java.util.function.Predicate;
/**
@@ -7463,92 +7463,73 @@
}
/**
- * Offsets the given rectangle in parent's local coordinates into child's coordinate space
- * and clips the result to the child View's bounds, padding and clipRect if appropriate. If the
- * resulting rectangle is not empty, the request is forwarded to the child.
- * <p>
- * Note: This method does not account for any static View transformations which may be
- * applied to the child view.
- *
- * @param child the child to dispatch to
- * @param localVisibleRect the visible (clipped) area of this ViewGroup, in local coordinates
- * @param windowOffset the offset of localVisibleRect within the window
- * @param targets a queue to collect located targets
- */
- private void dispatchTransformedScrollCaptureSearch(View child, Rect localVisibleRect,
- Point windowOffset, Queue<ScrollCaptureTarget> targets) {
-
- // copy local visible rect for modification and dispatch
- final Rect childVisibleRect = getTempRect();
- childVisibleRect.set(localVisibleRect);
-
- // transform to child coords
- final Point childWindowOffset = getTempPoint();
- childWindowOffset.set(windowOffset.x, windowOffset.y);
-
- final int dx = child.mLeft - mScrollX;
- final int dy = child.mTop - mScrollY;
-
- childVisibleRect.offset(-dx, -dy);
- childWindowOffset.offset(dx, dy);
-
- boolean rectIsVisible = true;
- final int width = mRight - mLeft;
- final int height = mBottom - mTop;
-
- // Clip to child bounds
- if (getClipChildren()) {
- rectIsVisible = childVisibleRect.intersect(0, 0, child.getWidth(), child.getHeight());
- }
-
- // Clip to child padding.
- if (rectIsVisible && (child instanceof ViewGroup)
- && ((ViewGroup) child).getClipToPadding()) {
- rectIsVisible = childVisibleRect.intersect(
- child.mPaddingLeft, child.mPaddingTop,
- child.getWidth() - child.mPaddingRight,
- child.getHeight() - child.mPaddingBottom);
- }
- // Clip to child clipBounds.
- if (rectIsVisible && child.mClipBounds != null) {
- rectIsVisible = childVisibleRect.intersect(child.mClipBounds);
- }
- if (rectIsVisible) {
- child.dispatchScrollCaptureSearch(childVisibleRect, childWindowOffset, targets);
- }
- }
-
- /**
* Handle the scroll capture search request by checking this view if applicable, then to each
* child view.
*
* @param localVisibleRect the visible area of this ViewGroup in local coordinates, according to
* the parent
* @param windowOffset the offset of this view within the window
- * @param targets the collected list of scroll capture targets
- *
- * @hide
+ * @param targets accepts potential scroll capture targets; {@link Consumer#accept
+ * results.accept} may be called zero or more times on the calling
+ * thread before onScrollCaptureSearch returns
*/
@Override
public void dispatchScrollCaptureSearch(
@NonNull Rect localVisibleRect, @NonNull Point windowOffset,
- @NonNull Queue<ScrollCaptureTarget> targets) {
+ @NonNull Consumer<ScrollCaptureTarget> targets) {
+
+ // copy local visible rect for modification and dispatch
+ final Rect rect = getTempRect();
+ rect.set(localVisibleRect);
+
+ if (getClipToPadding()) {
+ rect.inset(mPaddingLeft, mPaddingTop, mPaddingRight, mPaddingBottom);
+ }
// Dispatch to self first.
super.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, targets);
- // Then dispatch to children, if not excluding descendants.
- if ((getScrollCaptureHint() & SCROLL_CAPTURE_HINT_EXCLUDE_DESCENDANTS) == 0) {
- final int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- // Only visible views can be captured.
- if (child.getVisibility() != View.VISIBLE) {
- continue;
- }
- // Transform to child coords and dispatch
- dispatchTransformedScrollCaptureSearch(child, localVisibleRect, windowOffset,
- targets);
+ // Skip children if descendants excluded.
+ if ((getScrollCaptureHint() & SCROLL_CAPTURE_HINT_EXCLUDE_DESCENDANTS) != 0) {
+ return;
+ }
+
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ // Only visible views can be captured.
+ if (child.getVisibility() != View.VISIBLE) {
+ continue;
+ }
+ // Offset the given rectangle (in parent's local coordinates) into child's coordinate
+ // space and clip the result to the child View's bounds, padding and clipRect as needed.
+ // If the resulting rectangle is not empty, the request is forwarded to the child.
+
+ // copy local visible rect for modification and dispatch
+ final Rect childVisibleRect = getTempRect();
+ childVisibleRect.set(localVisibleRect);
+
+ // transform to child coords
+ final Point childWindowOffset = getTempPoint();
+ childWindowOffset.set(windowOffset.x, windowOffset.y);
+
+ final int dx = child.mLeft - mScrollX;
+ final int dy = child.mTop - mScrollY;
+
+ childVisibleRect.offset(-dx, -dy);
+ childWindowOffset.offset(dx, dy);
+
+ boolean rectIsVisible = true;
+
+ // Clip to child bounds
+ if (getClipChildren()) {
+ rectIsVisible = childVisibleRect.intersect(0, 0, child.getWidth(),
+ child.getHeight());
+ }
+
+ // Clip to child padding.
+ if (rectIsVisible) {
+ child.dispatchScrollCaptureSearch(childVisibleRect, childWindowOffset, targets);
}
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1abcb15..f8e65bd 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -141,6 +141,7 @@
import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.EventLog;
+import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.LongArray;
import android.util.MergedConfiguration;
@@ -202,6 +203,7 @@
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
+import java.io.StringWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashSet;
@@ -274,6 +276,11 @@
*/
private static final int CONTENT_CAPTURE_ENABLED_FALSE = 2;
+ /**
+ * Maximum time to wait for {@link View#dispatchScrollCaptureSearch} to complete.
+ */
+ private static final int SCROLL_CAPTURE_REQUEST_TIMEOUT_MILLIS = 2500;
+
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
static final ThreadLocal<HandlerActionQueue> sRunQueues = new ThreadLocal<HandlerActionQueue>();
@@ -668,8 +675,6 @@
private final InsetsController mInsetsController;
private final ImeFocusController mImeFocusController;
- private ScrollCaptureConnection mScrollCaptureConnection;
-
private boolean mIsSurfaceOpaque;
private final BackgroundBlurDrawable.Aggregator mBlurRegionAggregator =
@@ -683,12 +688,6 @@
return mImeFocusController;
}
- /** @return The current {@link ScrollCaptureConnection} for this instance, if any is active. */
- @Nullable
- public ScrollCaptureConnection getScrollCaptureConnection() {
- return mScrollCaptureConnection;
- }
-
private final GestureExclusionTracker mGestureExclusionTracker = new GestureExclusionTracker();
private IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection;
@@ -730,6 +729,8 @@
private HashSet<ScrollCaptureCallback> mRootScrollCaptureCallbacks;
+ private long mScrollCaptureRequestTimeout = SCROLL_CAPTURE_REQUEST_TIMEOUT_MILLIS;
+
/**
* Increment this value when the surface has been replaced.
*/
@@ -817,6 +818,8 @@
mImeFocusController = new ImeFocusController(this);
AudioManager audioManager = mContext.getSystemService(AudioManager.class);
mFastScrollSoundEffectsEnabled = audioManager.areNavigationRepeatSoundEffectsEnabled();
+
+ mScrollCaptureRequestTimeout = SCROLL_CAPTURE_REQUEST_TIMEOUT_MILLIS;
}
public static void addFirstDrawHandler(Runnable callback) {
@@ -3966,11 +3969,12 @@
}
private void addFrameCallbackIfNeeded() {
- boolean nextDrawUseBlastSync = mNextDrawUseBlastSync;
- boolean hasBlur = mBlurRegionAggregator.hasRegions();
- boolean reportNextDraw = mReportNextDraw;
+ final boolean nextDrawUseBlastSync = mNextDrawUseBlastSync;
+ final boolean reportNextDraw = mReportNextDraw;
+ final boolean hasBlurUpdates = mBlurRegionAggregator.hasUpdates();
+ final boolean needsCallbackForBlur = hasBlurUpdates || mBlurRegionAggregator.hasRegions();
- if (!nextDrawUseBlastSync && !reportNextDraw && !hasBlur) {
+ if (!nextDrawUseBlastSync && !reportNextDraw && !needsCallbackForBlur) {
return;
}
@@ -3978,18 +3982,22 @@
Log.d(mTag, "Creating frameDrawingCallback"
+ " nextDrawUseBlastSync=" + nextDrawUseBlastSync
+ " reportNextDraw=" + reportNextDraw
- + " hasBlur=" + hasBlur);
+ + " hasBlurUpdates=" + hasBlurUpdates);
}
- // The callback will run on a worker thread pool from the render thread.
+ final BackgroundBlurDrawable.BlurRegion[] blurRegionsForFrame =
+ needsCallbackForBlur ? mBlurRegionAggregator.getBlurRegionsCopyForRT() : null;
+
+ // The callback will run on the render thread.
HardwareRenderer.FrameDrawingCallback frameDrawingCallback = frame -> {
if (DEBUG_BLAST) {
Log.d(mTag, "Received frameDrawingCallback frameNum=" + frame + "."
+ " Creating transactionCompleteCallback=" + nextDrawUseBlastSync);
}
- if (hasBlur) {
- mBlurRegionAggregator.dispatchBlurTransactionIfNeeded(frame);
+ if (needsCallbackForBlur) {
+ mBlurRegionAggregator
+ .dispatchBlurTransactionIfNeeded(frame, blurRegionsForFrame, hasBlurUpdates);
}
if (mBlastBufferQueue == null) {
@@ -9223,9 +9231,9 @@
* Collect and include any ScrollCaptureCallback instances registered with the window.
*
* @see #addScrollCaptureCallback(ScrollCaptureCallback)
- * @param targets the search queue for targets
+ * @param results an object to collect the results of the search
*/
- private void collectRootScrollCaptureTargets(Queue<ScrollCaptureTarget> targets) {
+ private void collectRootScrollCaptureTargets(ScrollCaptureSearchResults results) {
if (mRootScrollCaptureCallbacks == null) {
return;
}
@@ -9233,26 +9241,45 @@
// Add to the list for consideration
Point offset = new Point(mView.getLeft(), mView.getTop());
Rect rect = new Rect(0, 0, mView.getWidth(), mView.getHeight());
- targets.add(new ScrollCaptureTarget(mView, rect, offset, cb));
+ results.addTarget(new ScrollCaptureTarget(mView, rect, offset, cb));
}
}
/**
- * Handles an inbound request for scroll capture from the system. If a client is not already
- * active, a search will be dispatched through the view tree to locate scrolling content.
+ * Update the timeout for scroll capture requests. Only affects this view root.
+ * The default value is {@link #SCROLL_CAPTURE_REQUEST_TIMEOUT_MILLIS}.
+ *
+ * @param timeMillis the new timeout in milliseconds
+ */
+ public void setScrollCaptureRequestTimeout(int timeMillis) {
+ mScrollCaptureRequestTimeout = timeMillis;
+ }
+
+ /**
+ * Get the current timeout for scroll capture requests.
+ *
+ * @return the timeout in milliseconds
+ */
+ public long getScrollCaptureRequestTimeout() {
+ return mScrollCaptureRequestTimeout;
+ }
+
+ /**
+ * Handles an inbound request for scroll capture from the system. A search will be
+ * dispatched through the view tree to locate scrolling content.
* <p>
- * Either {@link IScrollCaptureCallbacks#onConnected(IScrollCaptureConnection, Rect,
- * Point)} or {@link IScrollCaptureCallbacks#onUnavailable()} will be returned
- * depending on the results of the search.
+ * A call to {@link IScrollCaptureCallbacks#onScrollCaptureResponse(ScrollCaptureResponse)}
+ * will follow.
*
* @param callbacks to receive responses
- * @see ScrollCaptureTargetResolver
+ * @see ScrollCaptureTargetSelector
*/
public void handleScrollCaptureRequest(@NonNull IScrollCaptureCallbacks callbacks) {
- LinkedList<ScrollCaptureTarget> targetList = new LinkedList<>();
+ ScrollCaptureSearchResults results =
+ new ScrollCaptureSearchResults(mContext.getMainExecutor());
// Window (root) level callbacks
- collectRootScrollCaptureTargets(targetList);
+ collectRootScrollCaptureTargets(results);
// Search through View-tree
View rootView = getView();
@@ -9260,58 +9287,70 @@
Point point = new Point();
Rect rect = new Rect(0, 0, rootView.getWidth(), rootView.getHeight());
getChildVisibleRect(rootView, rect, point);
- rootView.dispatchScrollCaptureSearch(rect, point, targetList);
+ rootView.dispatchScrollCaptureSearch(rect, point, results::addTarget);
}
-
- // No-op path. Scroll capture not offered for this window.
- if (targetList.isEmpty()) {
- dispatchScrollCaptureSearchResult(callbacks, null);
- return;
+ Runnable onComplete = () -> dispatchScrollCaptureSearchResult(callbacks, results);
+ results.setOnCompleteListener(onComplete);
+ if (!results.isComplete()) {
+ mHandler.postDelayed(results::finish, getScrollCaptureRequestTimeout());
}
-
- // Request scrollBounds from each of the targets.
- // Continues with the consumer once all responses are consumed, or the timeout expires.
- ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(targetList);
- resolver.start(mHandler, 1000,
- (selected) -> dispatchScrollCaptureSearchResult(callbacks, selected));
}
/** Called by {@link #handleScrollCaptureRequest} when a result is returned */
private void dispatchScrollCaptureSearchResult(
@NonNull IScrollCaptureCallbacks callbacks,
- @Nullable ScrollCaptureTarget selectedTarget) {
+ @NonNull ScrollCaptureSearchResults results) {
- // If timeout or no eligible targets found.
+ ScrollCaptureTarget selectedTarget = results.getTopResult();
+
+ ScrollCaptureResponse.Builder response = new ScrollCaptureResponse.Builder();
+ response.setWindowTitle(getTitle().toString());
+
+ StringWriter writer = new StringWriter();
+ IndentingPrintWriter pw = new IndentingPrintWriter(writer);
+ results.dump(pw);
+ pw.flush();
+ response.addMessage(writer.toString());
+
if (selectedTarget == null) {
+ response.setDescription("No scrollable targets found in window");
try {
- if (DEBUG_SCROLL_CAPTURE) {
- Log.d(TAG, "scrollCaptureSearch returned no targets available.");
- }
- callbacks.onUnavailable();
+ callbacks.onScrollCaptureResponse(response.build());
} catch (RemoteException e) {
- if (DEBUG_SCROLL_CAPTURE) {
- Log.w(TAG, "Failed to send scroll capture search result.", e);
- }
+ Log.e(TAG, "Failed to send scroll capture search result", e);
}
return;
}
- // Create a client instance and return it to the caller
- mScrollCaptureConnection = new ScrollCaptureConnection(selectedTarget, callbacks);
+ response.setDescription("Connected");
+
+ // Compute area covered by scrolling content within window
+ Rect boundsInWindow = new Rect();
+ View containingView = selectedTarget.getContainingView();
+ containingView.getLocationInWindow(mAttachInfo.mTmpLocation);
+ boundsInWindow.set(selectedTarget.getScrollBounds());
+ boundsInWindow.offset(mAttachInfo.mTmpLocation[0], mAttachInfo.mTmpLocation[1]);
+ response.setBoundsInWindow(boundsInWindow);
+
+ // Compute the area on screen covered by the window
+ Rect boundsOnScreen = new Rect();
+ mView.getLocationOnScreen(mAttachInfo.mTmpLocation);
+ boundsOnScreen.set(0, 0, mView.getWidth(), mView.getHeight());
+ boundsOnScreen.offset(mAttachInfo.mTmpLocation[0], mAttachInfo.mTmpLocation[1]);
+ response.setWindowBounds(boundsOnScreen);
+
+ // Create a connection and return it to the caller
+ ScrollCaptureConnection connection = new ScrollCaptureConnection(
+ mView.getContext().getMainExecutor(), selectedTarget, callbacks);
+ response.setConnection(connection);
+
try {
- if (DEBUG_SCROLL_CAPTURE) {
- Log.d(TAG, "scrollCaptureSearch returning client: " + getScrollCaptureConnection());
- }
- callbacks.onConnected(
- mScrollCaptureConnection,
- selectedTarget.getScrollBounds(),
- selectedTarget.getPositionInWindow());
+ callbacks.onScrollCaptureResponse(response.build());
} catch (RemoteException e) {
if (DEBUG_SCROLL_CAPTURE) {
- Log.w(TAG, "Failed to send scroll capture search result.", e);
+ Log.w(TAG, "Failed to send scroll capture search response.", e);
}
- mScrollCaptureConnection.disconnect();
- mScrollCaptureConnection = null;
+ connection.close();
}
}
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 4ecdd78..221b334 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -2621,7 +2621,6 @@
* callback with the root view of the window.
*
* @param callback the callback to add
- * @hide
*/
public void registerScrollCaptureCallback(@NonNull ScrollCaptureCallback callback) {
}
@@ -2630,7 +2629,6 @@
* Unregisters a {@link ScrollCaptureCallback} previously registered with this window.
*
* @param callback the callback to remove
- * @hide
*/
public void unregisterScrollCaptureCallback(@NonNull ScrollCaptureCallback callback) {
}
diff --git a/core/java/com/android/internal/graphics/drawable/BackgroundBlurDrawable.java b/core/java/com/android/internal/graphics/drawable/BackgroundBlurDrawable.java
index 96dac56..402d7fe 100644
--- a/core/java/com/android/internal/graphics/drawable/BackgroundBlurDrawable.java
+++ b/core/java/com/android/internal/graphics/drawable/BackgroundBlurDrawable.java
@@ -19,6 +19,7 @@
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UiThread;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -31,12 +32,14 @@
import android.graphics.Rect;
import android.graphics.RenderNode;
import android.graphics.drawable.Drawable;
-import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
-import android.view.SurfaceControl;
+import android.util.LongSparseArray;
import android.view.ViewRootImpl;
import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
/**
* A drawable that keeps track of a blur region, pokes a hole under it, and propagates its state
@@ -52,26 +55,40 @@
private final Paint mPaint = new Paint();
private final Path mRectPath = new Path();
private final float[] mTmpRadii = new float[8];
- private final SurfaceControl.BlurRegion mBlurRegion = new SurfaceControl.BlurRegion();
- // This will be called from a thread pool.
- private final RenderNode.PositionUpdateListener mPositionUpdateListener =
+ private boolean mVisible = true;
+
+ // Confined to UiThread. The values are copied into a BlurRegion, which lives on
+ // RenderThread to avoid interference with UiThread updates.
+ private int mBlurRadius;
+ private float mCornerRadiusTL;
+ private float mCornerRadiusTR;
+ private float mCornerRadiusBL;
+ private float mCornerRadiusBR;
+ private float mAlpha = 1;
+
+ // Do not update from UiThread. This holds the latest position for this drawable. It is used
+ // by the Aggregator from RenderThread to get the final position of the blur region sent to SF
+ private final Rect mRect = new Rect();
+ // This is called from a thread pool. The callbacks might come out of order w.r.t. the frame
+ // number, so we send a Runnable holding the actual update to the Aggregator. The Aggregator
+ // can apply the update on RenderThread when processing that same frame.
+ @VisibleForTesting
+ public final RenderNode.PositionUpdateListener mPositionUpdateListener =
new RenderNode.PositionUpdateListener() {
@Override
public void positionChanged(long frameNumber, int left, int top, int right,
int bottom) {
- synchronized (mAggregator) {
- mBlurRegion.rect.set(left, top, right, bottom);
- mAggregator.onBlurRegionUpdated(BackgroundBlurDrawable.this, mBlurRegion);
- }
+ mAggregator.onRenderNodePositionChanged(frameNumber, () -> {
+ mRect.set(left, top, right, bottom);
+ });
}
@Override
public void positionLost(long frameNumber) {
- synchronized (mAggregator) {
- mBlurRegion.rect.setEmpty();
- mAggregator.onBlurRegionUpdated(BackgroundBlurDrawable.this, mBlurRegion);
- }
+ mAggregator.onRenderNodePositionChanged(frameNumber, () -> {
+ mRect.setEmpty();
+ });
}
};
@@ -79,6 +96,7 @@
mAggregator = aggregator;
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
mPaint.setColor(Color.TRANSPARENT);
+ mPaint.setAntiAlias(true);
mRenderNode = new RenderNode("BackgroundBlurDrawable");
mRenderNode.addPositionUpdateListener(mPositionUpdateListener);
}
@@ -104,23 +122,30 @@
public boolean setVisible(boolean visible, boolean restart) {
boolean changed = super.setVisible(visible, restart);
if (changed) {
- mBlurRegion.visible = visible;
+ mVisible = visible;
+ mAggregator.onBlurDrawableUpdated(this);
}
return changed;
}
@Override
public void setAlpha(int alpha) {
- mBlurRegion.alpha = alpha / 255f;
- invalidateSelf();
+ if (mAlpha != alpha / 255f) {
+ mAlpha = alpha / 255f;
+ invalidateSelf();
+ mAggregator.onBlurDrawableUpdated(this);
+ }
}
/**
* Blur radius in pixels.
*/
public void setBlurRadius(int blurRadius) {
- mBlurRegion.blurRadius = blurRadius;
- invalidateSelf();
+ if (mBlurRadius != blurRadius) {
+ mBlurRadius = blurRadius;
+ invalidateSelf();
+ mAggregator.onBlurDrawableUpdated(this);
+ }
}
/**
@@ -139,14 +164,18 @@
*/
public void setCornerRadius(float cornerRadiusTL, float cornerRadiusTR, float cornerRadiusBL,
float cornerRadiusBR) {
- synchronized (mAggregator) {
- mBlurRegion.cornerRadiusTL = cornerRadiusTL;
- mBlurRegion.cornerRadiusTR = cornerRadiusTR;
- mBlurRegion.cornerRadiusBL = cornerRadiusBL;
- mBlurRegion.cornerRadiusBR = cornerRadiusBR;
+ if (mCornerRadiusTL != cornerRadiusTL
+ || mCornerRadiusTR != cornerRadiusTR
+ || mCornerRadiusBL != cornerRadiusBL
+ || mCornerRadiusBR != cornerRadiusBR) {
+ mCornerRadiusTL = cornerRadiusTL;
+ mCornerRadiusTR = cornerRadiusTR;
+ mCornerRadiusBL = cornerRadiusBL;
+ mCornerRadiusBR = cornerRadiusBR;
+ updatePath();
+ invalidateSelf();
+ mAggregator.onBlurDrawableUpdated(this);
}
- updatePath();
- invalidateSelf();
}
@Override
@@ -157,12 +186,10 @@
}
private void updatePath() {
- synchronized (mAggregator) {
- mTmpRadii[0] = mTmpRadii[1] = mBlurRegion.cornerRadiusTL;
- mTmpRadii[2] = mTmpRadii[3] = mBlurRegion.cornerRadiusTR;
- mTmpRadii[4] = mTmpRadii[5] = mBlurRegion.cornerRadiusBL;
- mTmpRadii[6] = mTmpRadii[7] = mBlurRegion.cornerRadiusBR;
- }
+ mTmpRadii[0] = mTmpRadii[1] = mCornerRadiusTL;
+ mTmpRadii[2] = mTmpRadii[3] = mCornerRadiusTR;
+ mTmpRadii[4] = mTmpRadii[5] = mCornerRadiusBL;
+ mTmpRadii[6] = mTmpRadii[7] = mCornerRadiusBR;
mRectPath.reset();
if (getAlpha() == 0 || !isVisible()) {
return;
@@ -182,17 +209,32 @@
return PixelFormat.TRANSLUCENT;
}
+ @Override
+ public String toString() {
+ return "BackgroundBlurDrawable{"
+ + "blurRadius=" + mBlurRadius
+ + ", corners={" + mCornerRadiusTL
+ + "," + mCornerRadiusTR
+ + "," + mCornerRadiusBL
+ + "," + mCornerRadiusBR
+ + "}, alpha=" + mAlpha
+ + ", visible=" + mVisible
+ + "}";
+ }
+
/**
* Responsible for keeping track of all blur regions of a {@link ViewRootImpl} and posting a
* message when it's time to propagate them.
*/
public static final class Aggregator {
-
- private final ArrayMap<BackgroundBlurDrawable, SurfaceControl.BlurRegion> mBlurRegions =
- new ArrayMap<>();
+ private final Object mRtLock = new Object();
+ // Maintains a list of all *visible* blur drawables. Confined to UI thread
+ private final ArraySet<BackgroundBlurDrawable> mDrawables = new ArraySet();
+ @GuardedBy("mRtLock")
+ private final LongSparseArray<ArraySet<Runnable>> mFrameRtUpdates = new LongSparseArray();
private final ViewRootImpl mViewRoot;
- private float[][] mTmpBlurRegionsArray;
- private boolean mNeedsUpdate;
+ private BlurRegion[] mTmpBlurRegionsForFrame = new BlurRegion[0];
+ private boolean mHasUiUpdates;
public Aggregator(ViewRootImpl viewRoot) {
mViewRoot = viewRoot;
@@ -209,60 +251,191 @@
}
/**
- * Called from RenderThread only, already locked.
- * @param drawable
- * @param blurRegion
+ * Called when a BackgroundBlurDrawable has been updated
*/
- void onBlurRegionUpdated(BackgroundBlurDrawable drawable,
- SurfaceControl.BlurRegion blurRegion) {
- if (blurRegion.rect.isEmpty() || blurRegion.alpha == 0 || blurRegion.blurRadius == 0
- || !blurRegion.visible) {
- mBlurRegions.remove(drawable);
- mNeedsUpdate = true;
- if (DEBUG) {
- Log.d(TAG, "Remove " + blurRegion);
+ @UiThread
+ void onBlurDrawableUpdated(BackgroundBlurDrawable drawable) {
+ final boolean shouldBeDrawn =
+ drawable.mAlpha != 0 && drawable.mBlurRadius > 0 && drawable.mVisible;
+ final boolean isDrawn = mDrawables.contains(drawable);
+ if (shouldBeDrawn) {
+ mHasUiUpdates = true;
+ if (!isDrawn) {
+ mDrawables.add(drawable);
+ if (DEBUG) {
+ Log.d(TAG, "Add " + drawable);
+ }
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "Update " + drawable);
+ }
}
- } else {
- mBlurRegions.put(drawable, blurRegion);
- mNeedsUpdate = true;
+ } else if (!shouldBeDrawn && isDrawn) {
+ mHasUiUpdates = true;
+ mDrawables.remove(drawable);
if (DEBUG) {
- Log.d(TAG, "Update " + blurRegion);
+ Log.d(TAG, "Remove " + drawable);
}
}
}
+ // Called from a thread pool
+ void onRenderNodePositionChanged(long frameNumber, Runnable update) {
+ // One of the blur region's position has changed, so we have to send an updated list
+ // of blur regions to SurfaceFlinger for this frame.
+ synchronized (mRtLock) {
+ ArraySet<Runnable> frameRtUpdates = mFrameRtUpdates.get(frameNumber);
+ if (frameRtUpdates == null) {
+ frameRtUpdates = new ArraySet<>();
+ mFrameRtUpdates.put(frameNumber, frameRtUpdates);
+ }
+ frameRtUpdates.add(update);
+ }
+ }
+
/**
- * If there are any blur regions visible on the screen at the moment.
+ * @return true if there are any updates that need to be sent to SF
*/
+ @UiThread
+ public boolean hasUpdates() {
+ return mHasUiUpdates;
+ }
+
+ /**
+ * @return true if there are any visible blur regions
+ */
+ @UiThread
public boolean hasRegions() {
- return mBlurRegions.size() > 0;
+ return mDrawables.size() > 0;
}
/**
- * Dispatch blur updates, if there were any.
- * @param frameNumber Frame where the update should happen.
+ * @return an array of BlurRegions, which are holding a copy of the information in
+ * all the currently visible BackgroundBlurDrawables
*/
- public void dispatchBlurTransactionIfNeeded(long frameNumber) {
- synchronized (this) {
- if (!mNeedsUpdate) {
- return;
+ @UiThread
+ public BlurRegion[] getBlurRegionsCopyForRT() {
+ if (mHasUiUpdates) {
+ mTmpBlurRegionsForFrame = new BlurRegion[mDrawables.size()];
+ for (int i = 0; i < mDrawables.size(); i++) {
+ mTmpBlurRegionsForFrame[i] = new BlurRegion(mDrawables.valueAt(i));
}
- mNeedsUpdate = false;
-
- if (mTmpBlurRegionsArray == null
- || mTmpBlurRegionsArray.length != mBlurRegions.size()) {
- mTmpBlurRegionsArray = new float[mBlurRegions.size()][];
- }
- if (DEBUG) {
- Log.d(TAG, "onBlurRegionUpdated will dispatch " + mTmpBlurRegionsArray.length
- + " regions for frame " + frameNumber);
- }
- for (int i = 0; i < mTmpBlurRegionsArray.length; i++) {
- mTmpBlurRegionsArray[i] = mBlurRegions.valueAt(i).toFloatArray();
- }
-
- mViewRoot.dispatchBlurRegions(mTmpBlurRegionsArray, frameNumber);
+ mHasUiUpdates = false;
}
+
+ return mTmpBlurRegionsForFrame;
+ }
+
+ /**
+ * Called on RenderThread.
+ *
+ * @return all blur regions if there are any ui or position updates for this frame,
+ * null otherwise
+ */
+ @VisibleForTesting
+ public float[][] getBlurRegionsToDispatchToSf(long frameNumber,
+ BlurRegion[] blurRegionsForFrame, boolean hasUiUpdatesForFrame) {
+ synchronized (mRtLock) {
+ if (!hasUiUpdatesForFrame && (mFrameRtUpdates.size() == 0
+ || mFrameRtUpdates.keyAt(0) > frameNumber)) {
+ return null;
+ }
+
+ // mFrameRtUpdates holds position updates coming from a thread pool span from
+ // RenderThread. At this point, all position updates for frame frameNumber should
+ // have been added to mFrameRtUpdates.
+ // Here, we apply all updates for frames <= frameNumber in case some previous update
+ // has been missed. This also protects mFrameRtUpdates from memory leaks.
+ while (mFrameRtUpdates.size() != 0 && mFrameRtUpdates.keyAt(0) <= frameNumber) {
+ final ArraySet<Runnable> frameUpdates = mFrameRtUpdates.valueAt(0);
+ mFrameRtUpdates.removeAt(0);
+ for (int i = 0; i < frameUpdates.size(); i++) {
+ frameUpdates.valueAt(i).run();
+ }
+ }
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "Dispatching " + blurRegionsForFrame.length + " blur regions:");
+ }
+
+ final float[][] blurRegionsArray = new float[blurRegionsForFrame.length][];
+ for (int i = 0; i < blurRegionsArray.length; i++) {
+ blurRegionsArray[i] = blurRegionsForFrame[i].toFloatArray();
+ if (DEBUG) {
+ Log.d(TAG, blurRegionsForFrame[i].toString());
+ }
+ }
+ return blurRegionsArray;
+ }
+
+ /**
+ * Called on RenderThread in FrameDrawingCallback.
+ * Dispatch all blur regions if there are any ui or position updates.
+ */
+ public void dispatchBlurTransactionIfNeeded(long frameNumber,
+ BlurRegion[] blurRegionsForFrame, boolean hasUiUpdatesForFrame) {
+ final float[][] blurRegionsArray = getBlurRegionsToDispatchToSf(frameNumber,
+ blurRegionsForFrame, hasUiUpdatesForFrame);
+ if (blurRegionsArray != null) {
+ mViewRoot.dispatchBlurRegions(blurRegionsArray, frameNumber);
+ }
+ }
+
+ }
+
+ /**
+ * Wrapper for sending blur data to SurfaceFlinger
+ * Confined to RenderThread.
+ */
+ public static final class BlurRegion {
+ public final int blurRadius;
+ public final float cornerRadiusTL;
+ public final float cornerRadiusTR;
+ public final float cornerRadiusBL;
+ public final float cornerRadiusBR;
+ public final float alpha;
+ public final Rect rect;
+
+ BlurRegion(BackgroundBlurDrawable drawable) {
+ alpha = drawable.mAlpha;
+ blurRadius = drawable.mBlurRadius;
+ cornerRadiusTL = drawable.mCornerRadiusTL;
+ cornerRadiusTR = drawable.mCornerRadiusTR;
+ cornerRadiusBL = drawable.mCornerRadiusBL;
+ cornerRadiusBR = drawable.mCornerRadiusBR;
+ rect = drawable.mRect;
+ }
+
+ /**
+ * Serializes this class into a float array that's more JNI friendly.
+ */
+ float[] toFloatArray() {
+ final float[] floatArray = new float[10];
+ floatArray[0] = blurRadius;
+ floatArray[1] = alpha;
+ floatArray[2] = rect.left;
+ floatArray[3] = rect.top;
+ floatArray[4] = rect.right;
+ floatArray[5] = rect.bottom;
+ floatArray[6] = cornerRadiusTL;
+ floatArray[7] = cornerRadiusTR;
+ floatArray[8] = cornerRadiusBL;
+ floatArray[9] = cornerRadiusBR;
+ return floatArray;
+ }
+
+ @Override
+ public String toString() {
+ return "BlurRegion{"
+ + "blurRadius=" + blurRadius
+ + ", corners={" + cornerRadiusTL
+ + "," + cornerRadiusTR
+ + "," + cornerRadiusBL
+ + "," + cornerRadiusBR
+ + "}, alpha=" + alpha
+ + ", rect=" + rect
+ + "}";
}
}
}
diff --git a/core/java/com/android/internal/os/TEST_MAPPING b/core/java/com/android/internal/os/TEST_MAPPING
index 9698f19..791e9ad 100644
--- a/core/java/com/android/internal/os/TEST_MAPPING
+++ b/core/java/com/android/internal/os/TEST_MAPPING
@@ -1,6 +1,23 @@
{
"presubmit": [
{
+ "file_patterns": ["Battery[^/]*\\.java"],
+ "name": "FrameworksCoreTests",
+ "options": [
+ { "include-filter": "com.android.internal.os.BatteryStatsTests" },
+ { "exclude-annotation": "com.android.internal.os.SkipPresubmit" }
+ ]
+ },
+ {
+ "file_patterns": ["Battery[^/]*\\.java"],
+ "name": "FrameworksServicesTests",
+ "options": [
+ { "include-filter": "com.android.server.am.BatteryStatsServiceTest" },
+ { "include-filter": "com.android.server.am.MeasuredEnergySnapshotTest" },
+ { "include-filter": "com.android.server.am.BatteryExternalStatsWorkerTest" }
+ ]
+ },
+ {
"name": "FrameworksCoreTests",
"options": [
{
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index b99c953..6b1d408b 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -18,8 +18,8 @@
import static android.system.OsConstants.O_CLOEXEC;
-import static com.android.internal.os.ZygoteConnectionConstants.MAX_ZYGOTE_ARGC;
-
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.pm.ApplicationInfo;
import android.net.Credentials;
import android.net.LocalServerSocket;
@@ -36,17 +36,16 @@
import com.android.internal.net.NetworkUtilsInternal;
+import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
import dalvik.system.ZygoteHooks;
import libcore.io.IoUtils;
-import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.FileDescriptor;
import java.io.IOException;
-import java.io.InputStreamReader;
/** @hide */
public final class Zygote {
@@ -238,6 +237,8 @@
*/
public static final String CHILD_ZYGOTE_UID_RANGE_END = "--uid-range-end=";
+ private static final String TAG = "Zygote";
+
/** Prefix prepended to socket names created by init */
private static final String ANDROID_SOCKET_PREFIX = "ANDROID_SOCKET_";
@@ -408,6 +409,10 @@
// Note that this event ends at the end of handleChildProc.
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork");
+ if (gids != null && gids.length > 0) {
+ NetworkUtilsInternal.setAllowNetworkingForProcess(containsInetGid(gids));
+ }
+
// Set the Java Language thread priority to the default value for new apps.
Thread.currentThread().setPriority(Thread.NORM_PRIORITY);
@@ -580,114 +585,163 @@
private static native int nativeGetUsapPoolEventFD();
/**
- * Fork a new unspecialized app process from the zygote
+ * Fork a new unspecialized app process from the zygote. Adds the Usap to the native
+ * Usap table.
*
* @param usapPoolSocket The server socket the USAP will call accept on
- * @param sessionSocketRawFDs Anonymous session sockets that are currently open
- * @param isPriorityFork Value controlling the process priority level until accept is called
- * @return In the Zygote process this function will always return null; in unspecialized app
- * processes this function will return a Runnable object representing the new
- * application that is passed up from usapMain.
+ * @param sessionSocketRawFDs Anonymous session sockets that are currently open.
+ * These are closed in the child.
+ * @param isPriorityFork Raise the initial process priority level because this is on the
+ * critical path for application startup.
+ * @return In the child process, this returns a Runnable that waits for specialization
+ * info to start an app process. In the sygote/parent process this returns null.
*/
- static Runnable forkUsap(LocalServerSocket usapPoolSocket,
- int[] sessionSocketRawFDs,
- boolean isPriorityFork) {
- FileDescriptor[] pipeFDs;
+ static @Nullable Runnable forkUsap(LocalServerSocket usapPoolSocket,
+ int[] sessionSocketRawFDs,
+ boolean isPriorityFork) {
+ FileDescriptor readFD;
+ FileDescriptor writeFD;
try {
- pipeFDs = Os.pipe2(O_CLOEXEC);
+ FileDescriptor[] pipeFDs = Os.pipe2(O_CLOEXEC);
+ readFD = pipeFDs[0];
+ writeFD = pipeFDs[1];
} catch (ErrnoException errnoEx) {
throw new IllegalStateException("Unable to create USAP pipe.", errnoEx);
}
- int pid =
- nativeForkUsap(pipeFDs[0].getInt$(), pipeFDs[1].getInt$(),
- sessionSocketRawFDs, isPriorityFork);
-
+ int pid = nativeForkApp(readFD.getInt$(), writeFD.getInt$(),
+ sessionSocketRawFDs, /*argsKnown=*/ false, isPriorityFork);
if (pid == 0) {
- IoUtils.closeQuietly(pipeFDs[0]);
- return usapMain(usapPoolSocket, pipeFDs[1]);
+ IoUtils.closeQuietly(readFD);
+ return childMain(null, usapPoolSocket, writeFD);
+ } else if (pid == -1) {
+ // Fork failed.
+ return null;
} else {
- // The read-end of the pipe will be closed by the native code.
- // See removeUsapTableEntry();
- IoUtils.closeQuietly(pipeFDs[1]);
+ // readFD will be closed by the native code. See removeUsapTableEntry();
+ IoUtils.closeQuietly(writeFD);
+ nativeAddUsapTableEntry(pid, readFD.getInt$());
return null;
}
}
- private static native int nativeForkUsap(int readPipeFD,
- int writePipeFD,
- int[] sessionSocketRawFDs,
- boolean isPriorityFork);
+ private static native int nativeForkApp(int readPipeFD,
+ int writePipeFD,
+ int[] sessionSocketRawFDs,
+ boolean argsKnown,
+ boolean isPriorityFork);
/**
- * This function is used by unspecialized app processes to wait for specialization requests from
- * the system server.
+ * Add an entry for a new Usap to the table maintained in native code.
+ */
+ @CriticalNative
+ private static native void nativeAddUsapTableEntry(int pid, int readPipeFD);
+
+ /**
+ * Fork a new app process from the zygote. argBuffer contains a fork command that
+ * request neither a child zygote, nor a wrapped process. Continue to accept connections
+ * on the specified socket, use those to refill argBuffer, and continue to process
+ * sufficiently simple fork requests. We presume that the only open file descriptors
+ * requiring special treatment are the session socket embedded in argBuffer, and
+ * zygoteSocket.
+ * @param argBuffer containing initial command and the connected socket from which to
+ * read more
+ * @param zygoteSocket socket from which to obtain new connections when current argBuffer
+ * one is disconnected
+ * @param expectedUId Uid of peer for initial requests. Subsequent requests from a different
+ * peer will cause us to return rather than perform the requested fork.
+ * @param minUid Minimum Uid enforced for all but first fork request. The caller checks
+ * the Uid policy for the initial request.
+ * @param firstNiceName name of first created process. Used for error reporting only.
+ * @return A Runnable in each child process, null in the parent.
+ * If this returns in then argBuffer still contains a command needing to be executed.
+ */
+ static @Nullable Runnable forkSimpleApps(@NonNull ZygoteCommandBuffer argBuffer,
+ @NonNull FileDescriptor zygoteSocket,
+ int expectedUid,
+ int minUid,
+ @Nullable String firstNiceName) {
+ boolean in_child =
+ argBuffer.forkRepeatedly(zygoteSocket, expectedUid, minUid, firstNiceName);
+ if (in_child) {
+ return childMain(argBuffer, /*usapPoolSocket=*/null, /*writePipe=*/null);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Specialize the current process into one described by argBuffer or the command read from
+ * usapPoolSocket. Exactly one of those must be null. If we are given an argBuffer, we close
+ * it. Used both for a specializing a USAP process, and for process creation without USAPs.
+ * In both cases, we specialize the process after first returning to Java code.
*
* @param writePipe The write end of the reporting pipe used to communicate with the poll loop
* of the ZygoteServer.
* @return A runnable oject representing the new application.
*/
- private static Runnable usapMain(LocalServerSocket usapPoolSocket,
- FileDescriptor writePipe) {
+ private static Runnable childMain(@Nullable ZygoteCommandBuffer argBuffer,
+ @Nullable LocalServerSocket usapPoolSocket,
+ FileDescriptor writePipe) {
final int pid = Process.myPid();
- Process.setArgV0(Process.is64Bit() ? "usap64" : "usap32");
- LocalSocket sessionSocket = null;
DataOutputStream usapOutputStream = null;
- Credentials peerCredentials = null;
ZygoteArguments args = null;
- // Change the priority to max before calling accept so we can respond to new specialization
- // requests as quickly as possible. This will be reverted to the default priority in the
- // native specialization code.
- boostUsapPriority();
+ // Block SIGTERM so we won't be killed if the Zygote flushes the USAP pool.
+ blockSigTerm();
- while (true) {
- try {
- sessionSocket = usapPoolSocket.accept();
+ LocalSocket sessionSocket = null;
+ if (argBuffer == null) {
+ // Read arguments from usapPoolSocket instead.
- // Block SIGTERM so we won't be killed if the Zygote flushes the USAP pool.
- blockSigTerm();
+ Process.setArgV0(Process.is64Bit() ? "usap64" : "usap32");
- BufferedReader usapReader =
- new BufferedReader(new InputStreamReader(sessionSocket.getInputStream()));
- usapOutputStream =
- new DataOutputStream(sessionSocket.getOutputStream());
+ // Change the priority to max before calling accept so we can respond to new
+ // specialization requests as quickly as possible. This will be reverted to the
+ // default priority in the native specialization code.
+ boostUsapPriority();
- peerCredentials = sessionSocket.getPeerCredentials();
+ while (true) {
+ ZygoteCommandBuffer tmpArgBuffer = null;
+ try {
+ sessionSocket = usapPoolSocket.accept();
- String[] argStrings = readArgumentList(usapReader);
-
- if (argStrings != null) {
- args = new ZygoteArguments(argStrings);
-
+ usapOutputStream =
+ new DataOutputStream(sessionSocket.getOutputStream());
+ Credentials peerCredentials = sessionSocket.getPeerCredentials();
+ tmpArgBuffer = new ZygoteCommandBuffer(sessionSocket);
+ args = ZygoteArguments.getInstance(argBuffer);
+ applyUidSecurityPolicy(args, peerCredentials);
// TODO (chriswailes): Should this only be run for debug builds?
validateUsapCommand(args);
break;
- } else {
- Log.e("USAP", "Truncated command received.");
- IoUtils.closeQuietly(sessionSocket);
-
- // Re-enable SIGTERM so the USAP can be flushed from the pool if necessary.
- unblockSigTerm();
+ } catch (Exception ex) {
+ Log.e("USAP", ex.getMessage());
}
- } catch (Exception ex) {
- Log.e("USAP", ex.getMessage());
- IoUtils.closeQuietly(sessionSocket);
-
// Re-enable SIGTERM so the USAP can be flushed from the pool if necessary.
unblockSigTerm();
+ IoUtils.closeQuietly(sessionSocket);
+ IoUtils.closeQuietly(tmpArgBuffer);
+ blockSigTerm();
}
+ } else {
+ try {
+ args = ZygoteArguments.getInstance(argBuffer);
+ } catch (Exception ex) {
+ Log.e("AppStartup", ex.getMessage());
+ throw new AssertionError("Failed to parse application start command", ex);
+ }
+ // peerCredentials were checked in parent.
}
-
+ if (args == null) {
+ throw new AssertionError("Empty command line");
+ }
try {
- // SIGTERM is blocked on loop exit. This prevents a USAP that is specializing from
- // being killed during a pool flush.
+ // SIGTERM is blocked here. This prevents a USAP that is specializing from being
+ // killed during a pool flush.
- setAppProcessName(args, "USAP");
-
- applyUidSecurityPolicy(args, peerCredentials);
applyDebuggerSystemProperty(args);
int[][] rlimits = null;
@@ -696,53 +750,57 @@
rlimits = args.mRLimits.toArray(INT_ARRAY_2D);
}
- // This must happen before the SELinux policy for this process is
- // changed when specializing.
- try {
- // Used by ZygoteProcess.zygoteSendArgsAndGetResult to fill in a
- // Process.ProcessStartResult object.
- usapOutputStream.writeInt(pid);
- } catch (IOException ioEx) {
- Log.e("USAP", "Failed to write response to session socket: "
- + ioEx.getMessage());
- throw new RuntimeException(ioEx);
- } finally {
- IoUtils.closeQuietly(sessionSocket);
-
+ if (argBuffer == null) {
+ // This must happen before the SELinux policy for this process is
+ // changed when specializing.
try {
- // This socket is closed using Os.close due to an issue with the implementation
- // of LocalSocketImp.close(). Because the raw FD is created by init and then
- // loaded from an environment variable (as opposed to being created by the
- // LocalSocketImpl itself) the current implementation will not actually close
- // the underlying FD.
- //
- // See b/130309968 for discussion of this issue.
- Os.close(usapPoolSocket.getFileDescriptor());
- } catch (ErrnoException ex) {
- Log.e("USAP", "Failed to close USAP pool socket");
- throw new RuntimeException(ex);
+ // Used by ZygoteProcess.zygoteSendArgsAndGetResult to fill in a
+ // Process.ProcessStartResult object.
+ usapOutputStream.writeInt(pid);
+ } catch (IOException ioEx) {
+ Log.e("USAP", "Failed to write response to session socket: "
+ + ioEx.getMessage());
+ throw new RuntimeException(ioEx);
+ } finally {
+ try {
+ // Since the raw FD is created by init and then loaded from an environment
+ // variable (as opposed to being created by the LocalSocketImpl itself),
+ // the LocalSocket/LocalSocketImpl does not own the Os-level socket. See
+ // the spec for LocalSocket.createConnectedLocalSocket(FileDescriptor fd).
+ // Thus closing the LocalSocket does not suffice. See b/130309968 for more
+ // discussion.
+ FileDescriptor fd = usapPoolSocket.getFileDescriptor();
+ usapPoolSocket.close();
+ Os.close(fd);
+ } catch (ErrnoException | IOException ex) {
+ Log.e("USAP", "Failed to close USAP pool socket");
+ throw new RuntimeException(ex);
+ }
}
}
- try {
- ByteArrayOutputStream buffer =
- new ByteArrayOutputStream(Zygote.USAP_MANAGEMENT_MESSAGE_BYTES);
- DataOutputStream outputStream = new DataOutputStream(buffer);
+ if (writePipe != null) {
+ try {
+ ByteArrayOutputStream buffer =
+ new ByteArrayOutputStream(Zygote.USAP_MANAGEMENT_MESSAGE_BYTES);
+ DataOutputStream outputStream = new DataOutputStream(buffer);
- // This is written as a long so that the USAP reporting pipe and USAP pool event FD
- // handlers in ZygoteServer.runSelectLoop can be unified. These two cases should
- // both send/receive 8 bytes.
- outputStream.writeLong(pid);
- outputStream.flush();
-
- Os.write(writePipe, buffer.toByteArray(), 0, buffer.size());
- } catch (Exception ex) {
- Log.e("USAP",
- String.format("Failed to write PID (%d) to pipe (%d): %s",
- pid, writePipe.getInt$(), ex.getMessage()));
- throw new RuntimeException(ex);
- } finally {
- IoUtils.closeQuietly(writePipe);
+ // This is written as a long so that the USAP reporting pipe and USAP pool
+ // event FD handlers in ZygoteServer.runSelectLoop can be unified. These two
+ // cases should both send/receive 8 bytes.
+ // TODO: Needs tweaking to handle the non-Usap invoke-with case, which expects
+ // a different format.
+ outputStream.writeLong(pid);
+ outputStream.flush();
+ Os.write(writePipe, buffer.toByteArray(), 0, buffer.size());
+ } catch (Exception ex) {
+ Log.e("USAP",
+ String.format("Failed to write PID (%d) to pipe (%d): %s",
+ pid, writePipe.getInt$(), ex.getMessage()));
+ throw new RuntimeException(ex);
+ } finally {
+ IoUtils.closeQuietly(writePipe);
+ }
}
specializeAppProcess(args.mUid, args.mGid, args.mGids,
@@ -849,13 +907,29 @@
return nativeRemoveUsapTableEntry(usapPID);
}
+ @CriticalNative
private static native boolean nativeRemoveUsapTableEntry(int usapPID);
/**
- * uid 1000 (Process.SYSTEM_UID) may specify any uid > 1000 in normal
+ * Return the minimum child uid that the given peer is allowed to create.
+ * uid 1000 (Process.SYSTEM_UID) may specify any uid ≥ 1000 in normal
* operation. It may also specify any gid and setgroups() list it chooses.
* In factory test mode, it may specify any UID.
- *
+ */
+ static int minChildUid(Credentials peer) {
+ if (peer.getUid() == Process.SYSTEM_UID
+ && FactoryTest.getMode() == FactoryTest.FACTORY_TEST_OFF) {
+ /* In normal operation, SYSTEM_UID can only specify a restricted
+ * set of UIDs. In factory test mode, SYSTEM_UID may specify any uid.
+ */
+ return Process.SYSTEM_UID;
+ } else {
+ return 0;
+ }
+ }
+
+ /*
+ * Adjust uid and gid arguments, ensuring that the security policy is satisfied.
* @param args non-null; zygote spawner arguments
* @param peer non-null; peer credentials
* @throws ZygoteSecurityException Indicates a security issue when applying the UID based
@@ -864,17 +938,10 @@
static void applyUidSecurityPolicy(ZygoteArguments args, Credentials peer)
throws ZygoteSecurityException {
- if (peer.getUid() == Process.SYSTEM_UID) {
- /* In normal operation, SYSTEM_UID can only specify a restricted
- * set of UIDs. In factory test mode, SYSTEM_UID may specify any uid.
- */
- boolean uidRestricted = FactoryTest.getMode() == FactoryTest.FACTORY_TEST_OFF;
-
- if (uidRestricted && args.mUidSpecified && (args.mUid < Process.SYSTEM_UID)) {
- throw new ZygoteSecurityException(
- "System UID may not launch process with UID < "
- + Process.SYSTEM_UID);
- }
+ if (args.mUidSpecified && (args.mUid < minChildUid(peer))) {
+ throw new ZygoteSecurityException(
+ "System UID may not launch process with UID < "
+ + Process.SYSTEM_UID);
}
// If not otherwise specified, uid and gid are inherited from peer
@@ -960,45 +1027,6 @@
}
/**
- * Reads an argument list from the provided socket
- * @return Argument list or null if EOF is reached
- * @throws IOException passed straight through
- */
- static String[] readArgumentList(BufferedReader socketReader) throws IOException {
- int argc;
-
- try {
- String argc_string = socketReader.readLine();
-
- if (argc_string == null) {
- // EOF reached.
- return null;
- }
- argc = Integer.parseInt(argc_string);
-
- } catch (NumberFormatException ex) {
- Log.e("Zygote", "Invalid Zygote wire format: non-int at argc");
- throw new IOException("Invalid wire format");
- }
-
- // See bug 1092107: large argc can be used for a DOS attack
- if (argc > MAX_ZYGOTE_ARGC) {
- throw new IOException("Max arg count exceeded");
- }
-
- String[] args = new String[argc];
- for (int arg_index = 0; arg_index < argc; arg_index++) {
- args[arg_index] = socketReader.readLine();
- if (args[arg_index] == null) {
- // We got an unexpected EOF.
- throw new IOException("Truncated request");
- }
- }
-
- return args;
- }
-
- /**
* Creates a managed LocalServerSocket object using a file descriptor
* created by an init.rc script. The init scripts that specify the
* sockets name can be found in system/core/rootdir. The socket is bound
diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java
index 32b808a..65b454d 100644
--- a/core/java/com/android/internal/os/ZygoteArguments.java
+++ b/core/java/com/android/internal/os/ZygoteArguments.java
@@ -16,8 +16,8 @@
package com.android.internal.os;
+import java.io.EOFException;
import java.util.ArrayList;
-import java.util.Arrays;
/**
* Handles argument parsing for args related to the zygote spawner.
@@ -245,20 +245,34 @@
/**
* Constructs instance and parses args
*
- * @param args zygote command-line args
+ * @param args zygote command-line args as ZygoteCommandBuffer, positioned after argument count.
*/
- ZygoteArguments(String[] args) throws IllegalArgumentException {
- parseArgs(args);
+ private ZygoteArguments(ZygoteCommandBuffer args, int argCount)
+ throws IllegalArgumentException, EOFException {
+ parseArgs(args, argCount);
+ }
+
+ /**
+ * Return a new ZygoteArguments reflecting the contents of the given ZygoteCommandBuffer. Return
+ * null if the ZygoteCommandBuffer was positioned at EOF. Assumes the buffer is initially
+ * positioned at the beginning of the command.
+ */
+ public static ZygoteArguments getInstance(ZygoteCommandBuffer args)
+ throws IllegalArgumentException, EOFException {
+ int argCount = args.getCount();
+ return argCount == 0 ? null : new ZygoteArguments(args, argCount);
}
/**
* Parses the commandline arguments intended for the Zygote spawner (such as "--setuid=" and
- * "--setgid=") and creates an array containing the remaining args.
+ * "--setgid=") and creates an array containing the remaining args. Return false if we were
+ * at EOF.
*
* Per security review bug #1112214, duplicate args are disallowed in critical cases to make
* injection harder.
*/
- private void parseArgs(String[] args) throws IllegalArgumentException {
+ private void parseArgs(ZygoteCommandBuffer args, int argCount)
+ throws IllegalArgumentException, EOFException {
/*
* See android.os.ZygoteProcess.zygoteSendArgsAndGetResult()
* Presently the wire format to the zygote process is:
@@ -269,13 +283,13 @@
* the child or -1 on failure.
*/
- int curArg = 0;
-
+ String unprocessedArg = null;
+ int curArg = 0; // Index of arg
boolean seenRuntimeArgs = false;
-
boolean expectRuntimeArgs = true;
- for ( /* curArg */ ; curArg < args.length; curArg++) {
- String arg = args[curArg];
+
+ for ( /* curArg */ ; curArg < argCount; ++curArg) {
+ String arg = args.nextArg();
if (arg.equals("--")) {
curArg++;
@@ -367,7 +381,8 @@
"Duplicate arg specified");
}
try {
- mInvokeWith = args[++curArg];
+ ++curArg;
+ mInvokeWith = args.nextArg();
} catch (IndexOutOfBoundsException ex) {
throw new IllegalArgumentException(
"--invoke-with requires argument");
@@ -397,12 +412,14 @@
} else if (arg.startsWith("--app-data-dir=")) {
mAppDataDir = getAssignmentValue(arg);
} else if (arg.equals("--preload-app")) {
- mPreloadApp = args[++curArg];
+ ++curArg;
+ mPreloadApp = args.nextArg();
} else if (arg.equals("--preload-package")) {
- mPreloadPackage = args[++curArg];
- mPreloadPackageLibs = args[++curArg];
- mPreloadPackageLibFileName = args[++curArg];
- mPreloadPackageCacheKey = args[++curArg];
+ curArg += 4;
+ mPreloadPackage = args.nextArg();
+ mPreloadPackageLibs = args.nextArg();
+ mPreloadPackageLibFileName = args.nextArg();
+ mPreloadPackageCacheKey = args.nextArg();
} else if (arg.equals("--preload-default")) {
mPreloadDefault = true;
expectRuntimeArgs = false;
@@ -411,8 +428,11 @@
} else if (arg.equals("--set-api-denylist-exemptions")) {
// consume all remaining args; this is a stand-alone command, never included
// with the regular fork command.
- mApiDenylistExemptions = Arrays.copyOfRange(args, curArg + 1, args.length);
- curArg = args.length;
+ mApiDenylistExemptions = new String[argCount - curArg - 1];
+ ++curArg;
+ for (int i = 0; curArg < argCount; ++curArg, ++i) {
+ mApiDenylistExemptions[i] = args.nextArg();
+ }
expectRuntimeArgs = false;
} else if (arg.startsWith("--hidden-api-log-sampling-rate=")) {
String rateStr = getAssignmentValue(arg);
@@ -462,35 +482,46 @@
} else if (arg.equals(Zygote.BIND_MOUNT_APP_DATA_DIRS)) {
mBindMountAppDataDirs = true;
} else {
+ unprocessedArg = arg;
break;
}
}
+ // curArg is the index of the first unprocessed argument. That argument is either referenced
+ // by unprocessedArg or not read yet.
if (mBootCompleted) {
- if (args.length - curArg > 0) {
+ if (argCount > curArg) {
throw new IllegalArgumentException("Unexpected arguments after --boot-completed");
}
} else if (mAbiListQuery || mPidQuery) {
- if (args.length - curArg > 0) {
+ if (argCount > curArg) {
throw new IllegalArgumentException("Unexpected arguments after --query-abi-list.");
}
} else if (mPreloadPackage != null) {
- if (args.length - curArg > 0) {
+ if (argCount > curArg) {
throw new IllegalArgumentException(
"Unexpected arguments after --preload-package.");
}
} else if (mPreloadApp != null) {
- if (args.length - curArg > 0) {
+ if (argCount > curArg) {
throw new IllegalArgumentException(
"Unexpected arguments after --preload-app.");
}
} else if (expectRuntimeArgs) {
if (!seenRuntimeArgs) {
- throw new IllegalArgumentException("Unexpected argument : " + args[curArg]);
+ throw new IllegalArgumentException("Unexpected argument : "
+ + (unprocessedArg == null ? args.nextArg() : unprocessedArg));
}
- mRemainingArgs = new String[args.length - curArg];
- System.arraycopy(args, curArg, mRemainingArgs, 0, mRemainingArgs.length);
+ mRemainingArgs = new String[argCount - curArg];
+ int i = 0;
+ if (unprocessedArg != null) {
+ mRemainingArgs[0] = unprocessedArg;
+ ++i;
+ }
+ for (; i < argCount - curArg; ++i) {
+ mRemainingArgs[i] = args.nextArg();
+ }
}
if (mStartChildZygote) {
diff --git a/core/java/com/android/internal/os/ZygoteCommandBuffer.java b/core/java/com/android/internal/os/ZygoteCommandBuffer.java
new file mode 100644
index 0000000..b61ae7a
--- /dev/null
+++ b/core/java/com/android/internal/os/ZygoteCommandBuffer.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2020 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.internal.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.LocalSocket;
+
+import java.io.FileDescriptor;
+import java.lang.ref.Reference; // For reachabilityFence.
+
+/**
+ * A native-accessible buffer for Zygote commands. Designed to support repeated forking
+ * of applications without intervening memory allocation, thus keeping zygote memory
+ * as stable as possible.
+ * A ZygoteCommandBuffer may have an associated socket from which it can be refilled.
+ * Otherwise the contents are explicitly set by getInstance().
+ *
+ * NOT THREAD-SAFE. No methods may be called concurrently from multiple threads.
+ *
+ * Only one ZygoteCommandBuffer can exist at a time.
+ * Must be explicitly closed before being dropped.
+ * @hide
+ */
+class ZygoteCommandBuffer implements AutoCloseable {
+ private long mNativeBuffer; // Not final so that we can clear it in close().
+
+ /**
+ * The command socket.
+ *
+ * mSocket is retained in the child process in "peer wait" mode, so
+ * that it closes when the child process terminates. In other cases,
+ * it is closed in the peer.
+ */
+ private final LocalSocket mSocket;
+ private final int mNativeSocket;
+
+ /**
+ * Constructs instance from file descriptor from which the command will be read.
+ * Only a single instance may be live in a given process. The native code checks.
+ *
+ * @param fd file descriptor to read from. The setCommand() method may be used if and only if
+ * fd is null.
+ */
+ ZygoteCommandBuffer(@Nullable LocalSocket socket) {
+ mSocket = socket;
+ if (socket == null) {
+ mNativeSocket = -1;
+ } else {
+ mNativeSocket = mSocket.getFileDescriptor().getInt$();
+ }
+ mNativeBuffer = getNativeBuffer(mNativeSocket);
+ }
+
+ /**
+ * Constructs an instance with explicitly supplied arguments and an invalid
+ * file descriptor. Can only be used for a single command.
+ */
+ ZygoteCommandBuffer(@NonNull String[] args) {
+ this((LocalSocket) null);
+ setCommand(args);
+ }
+
+
+ private static native long getNativeBuffer(int fd);
+
+ /**
+ * Deallocate native resources associated with the one and only command buffer, and prevent
+ * reuse. Subsequent calls to getInstance() will yield a new buffer.
+ * We do not close the associated socket, if any.
+ */
+ @Override
+ public void close() {
+ freeNativeBuffer(mNativeBuffer);
+ mNativeBuffer = 0;
+ }
+
+ private static native void freeNativeBuffer(long /* NativeCommandBuffer* */ nbuffer);
+
+ /**
+ * Read at least the first line of the next command into the buffer, return the argument count
+ * from that line. Assumes we are initially positioned at the beginning of the first line of
+ * the command. Leave the buffer positioned at the beginning of the second command line, i.e.
+ * the first argument. If the buffer has no associated file descriptor, we just reposition to
+ * the beginning of the buffer, and reread existing contents. Returns zero if we started out
+ * at EOF.
+ */
+ int getCount() {
+ try {
+ return nativeGetCount(mNativeBuffer);
+ } finally {
+ // Make sure the mNativeSocket doesn't get closed due to early finalization.
+ Reference.reachabilityFence(mSocket);
+ }
+ }
+
+ private static native int nativeGetCount(long /* NativeCommandBuffer* */ nbuffer);
+
+
+ /*
+ * Set the buffer to contain the supplied sequence of arguments.
+ */
+ private void setCommand(String[] command) {
+ int nArgs = command.length;
+ insert(mNativeBuffer, Integer.toString(nArgs));
+ for (String s: command) {
+ insert(mNativeBuffer, s);
+ }
+ // Native code checks there is no socket; hence no reachabilityFence.
+ }
+
+ private static native void insert(long /* NativeCommandBuffer* */ nbuffer, String s);
+
+ /**
+ * Retrieve the next argument/line from the buffer, filling the buffer as necessary.
+ */
+ String nextArg() {
+ try {
+ return nativeNextArg(mNativeBuffer);
+ } finally {
+ Reference.reachabilityFence(mSocket);
+ }
+ }
+
+ private static native String nativeNextArg(long /* NativeCommandBuffer* */ nbuffer);
+
+ void readFullyAndReset() {
+ try {
+ nativeReadFullyAndReset(mNativeBuffer);
+ } finally {
+ Reference.reachabilityFence(mSocket);
+ }
+ }
+
+ private static native void nativeReadFullyAndReset(long /* NativeCommandBuffer* */ nbuffer);
+
+ /**
+ * Fork a child as specified by the current command in the buffer, and repeat this process
+ * after refilling the buffer, so long as the buffer clearly contains another fork command.
+ *
+ * @param zygoteSocket socket from which to obtain new connections when current one is
+ * disconnected
+ * @param expectedUid Peer UID for current connection. We refuse to deal with requests from
+ * a different UID.
+ * @param minUid the smallest uid that may be request for the child process.
+ * @param firstNiceName The name for the initial process to be forked. Used only for error
+ * reporting.
+ *
+ * @return true in the child, false in the parent. In the parent case, the buffer is positioned
+ * at the beginning of a command that still needs to be processed.
+ */
+ boolean forkRepeatedly(FileDescriptor zygoteSocket, int expectedUid, int minUid,
+ String firstNiceName) {
+ try {
+ return nativeForkRepeatedly(mNativeBuffer, zygoteSocket.getInt$(),
+ expectedUid, minUid, firstNiceName);
+ } finally {
+ Reference.reachabilityFence(mSocket);
+ Reference.reachabilityFence(zygoteSocket);
+ }
+ }
+
+ /*
+ * Repeatedly fork children as above. It commonly does not return in the parent, but it may.
+ * @return true in the chaild, false in the parent if we encounter a command we couldn't handle.
+ */
+ private static native boolean nativeForkRepeatedly(long /* NativeCommandBuffer* */ nbuffer,
+ int zygoteSocketRawFd,
+ int expectedUid,
+ int minUid,
+ String firstNiceName);
+
+}
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 5a576ebb..37c7590 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -36,16 +36,15 @@
import android.util.Log;
import dalvik.system.VMRuntime;
+import dalvik.system.ZygoteHooks;
import libcore.io.IoUtils;
-import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileDescriptor;
import java.io.IOException;
-import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.concurrent.TimeUnit;
@@ -67,7 +66,6 @@
private final LocalSocket mSocket;
@UnsupportedAppUsage
private final DataOutputStream mSocketOutStream;
- private final BufferedReader mSocketReader;
@UnsupportedAppUsage
private final Credentials peer;
private final String abiList;
@@ -85,9 +83,6 @@
this.abiList = abiList;
mSocketOutStream = new DataOutputStream(socket.getOutputStream());
- mSocketReader =
- new BufferedReader(
- new InputStreamReader(socket.getInputStream()), Zygote.SOCKET_BUFFER_SIZE);
mSocket.setSoTimeout(CONNECTION_TIMEOUT_MILLIS);
@@ -111,178 +106,216 @@
}
/**
- * Reads one start command from the command socket. If successful, a child is forked and a
+ * Reads a command from the command socket. If a child is successfully forked, a
* {@code Runnable} that calls the childs main method (or equivalent) is returned in the child
* process. {@code null} is always returned in the parent process (the zygote).
+ * If multipleOK is set, we may keep processing additional fork commands before returning.
*
* If the client closes the socket, an {@code EOF} condition is set, which callers can test
* for by calling {@code ZygoteConnection.isClosedByPeer}.
*/
- Runnable processOneCommand(ZygoteServer zygoteServer) {
- String[] args;
+ Runnable processCommand(ZygoteServer zygoteServer, boolean multipleOK) {
+ ZygoteArguments parsedArgs;
- try {
- args = Zygote.readArgumentList(mSocketReader);
- } catch (IOException ex) {
- throw new IllegalStateException("IOException on command socket", ex);
+ try (ZygoteCommandBuffer argBuffer = new ZygoteCommandBuffer(mSocket)) {
+ while (true) {
+ try {
+ parsedArgs = ZygoteArguments.getInstance(argBuffer);
+ // Keep argBuffer around, since we need it to fork.
+ } catch (IOException ex) {
+ throw new IllegalStateException("IOException on command socket", ex);
+ }
+ if (parsedArgs == null) {
+ isEof = true;
+ return null;
+ }
+
+ int pid;
+ FileDescriptor childPipeFd = null;
+ FileDescriptor serverPipeFd = null;
+
+ if (parsedArgs.mBootCompleted) {
+ handleBootCompleted();
+ return null;
+ }
+
+ if (parsedArgs.mAbiListQuery) {
+ handleAbiListQuery();
+ return null;
+ }
+
+ if (parsedArgs.mPidQuery) {
+ handlePidQuery();
+ return null;
+ }
+
+ if (parsedArgs.mUsapPoolStatusSpecified) {
+ // Handle this once we've released the argBuffer, to avoid opening a second one.
+ break;
+ }
+
+ if (parsedArgs.mPreloadDefault) {
+ handlePreload();
+ return null;
+ }
+
+ if (parsedArgs.mPreloadPackage != null) {
+ handlePreloadPackage(parsedArgs.mPreloadPackage,
+ parsedArgs.mPreloadPackageLibs,
+ parsedArgs.mPreloadPackageLibFileName,
+ parsedArgs.mPreloadPackageCacheKey);
+ return null;
+ }
+
+ if (canPreloadApp() && parsedArgs.mPreloadApp != null) {
+ byte[] rawParcelData = Base64.getDecoder().decode(parsedArgs.mPreloadApp);
+ Parcel appInfoParcel = Parcel.obtain();
+ appInfoParcel.unmarshall(rawParcelData, 0, rawParcelData.length);
+ appInfoParcel.setDataPosition(0);
+ ApplicationInfo appInfo =
+ ApplicationInfo.CREATOR.createFromParcel(appInfoParcel);
+ appInfoParcel.recycle();
+ if (appInfo != null) {
+ handlePreloadApp(appInfo);
+ } else {
+ throw new IllegalArgumentException("Failed to deserialize --preload-app");
+ }
+ return null;
+ }
+
+ if (parsedArgs.mApiDenylistExemptions != null) {
+ return handleApiDenylistExemptions(zygoteServer,
+ parsedArgs.mApiDenylistExemptions);
+ }
+
+ if (parsedArgs.mHiddenApiAccessLogSampleRate != -1
+ || parsedArgs.mHiddenApiAccessStatslogSampleRate != -1) {
+ return handleHiddenApiAccessLogSampleRate(zygoteServer,
+ parsedArgs.mHiddenApiAccessLogSampleRate,
+ parsedArgs.mHiddenApiAccessStatslogSampleRate);
+ }
+
+ if (parsedArgs.mPermittedCapabilities != 0
+ || parsedArgs.mEffectiveCapabilities != 0) {
+ throw new ZygoteSecurityException("Client may not specify capabilities: "
+ + "permitted=0x" + Long.toHexString(parsedArgs.mPermittedCapabilities)
+ + ", effective=0x"
+ + Long.toHexString(parsedArgs.mEffectiveCapabilities));
+ }
+
+ Zygote.applyUidSecurityPolicy(parsedArgs, peer);
+ Zygote.applyInvokeWithSecurityPolicy(parsedArgs, peer);
+
+ Zygote.applyDebuggerSystemProperty(parsedArgs);
+ Zygote.applyInvokeWithSystemProperty(parsedArgs);
+
+ int[][] rlimits = null;
+
+ if (parsedArgs.mRLimits != null) {
+ rlimits = parsedArgs.mRLimits.toArray(Zygote.INT_ARRAY_2D);
+ }
+
+ int[] fdsToIgnore = null;
+
+ if (parsedArgs.mInvokeWith != null) {
+ try {
+ FileDescriptor[] pipeFds = Os.pipe2(O_CLOEXEC);
+ childPipeFd = pipeFds[1];
+ serverPipeFd = pipeFds[0];
+ Os.fcntlInt(childPipeFd, F_SETFD, 0);
+ fdsToIgnore = new int[]{childPipeFd.getInt$(), serverPipeFd.getInt$()};
+ } catch (ErrnoException errnoEx) {
+ throw new IllegalStateException("Unable to set up pipe for invoke-with",
+ errnoEx);
+ }
+ }
+
+ /*
+ * In order to avoid leaking descriptors to the Zygote child,
+ * the native code must close the two Zygote socket descriptors
+ * in the child process before it switches from Zygote-root to
+ * the UID and privileges of the application being launched.
+ *
+ * In order to avoid "bad file descriptor" errors when the
+ * two LocalSocket objects are closed, the Posix file
+ * descriptors are released via a dup2() call which closes
+ * the socket and substitutes an open descriptor to /dev/null.
+ */
+
+ int [] fdsToClose = { -1, -1 };
+
+ FileDescriptor fd = mSocket.getFileDescriptor();
+
+ if (fd != null) {
+ fdsToClose[0] = fd.getInt$();
+ }
+
+ FileDescriptor zygoteFd = zygoteServer.getZygoteSocketFileDescriptor();
+
+ if (zygoteFd != null) {
+ fdsToClose[1] = zygoteFd.getInt$();
+ }
+
+ if (parsedArgs.mInvokeWith != null || parsedArgs.mStartChildZygote
+ || !multipleOK || peer.getUid() != Process.SYSTEM_UID) {
+ // Continue using old code for now. TODO: Handle these cases in the other path.
+ pid = Zygote.forkAndSpecialize(parsedArgs.mUid, parsedArgs.mGid,
+ parsedArgs.mGids, parsedArgs.mRuntimeFlags, rlimits,
+ parsedArgs.mMountExternal, parsedArgs.mSeInfo, parsedArgs.mNiceName,
+ fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote,
+ parsedArgs.mInstructionSet, parsedArgs.mAppDataDir,
+ parsedArgs.mIsTopApp, parsedArgs.mPkgDataInfoList,
+ parsedArgs.mWhitelistedDataInfoList, parsedArgs.mBindMountAppDataDirs,
+ parsedArgs.mBindMountAppStorageDirs);
+
+ try {
+ if (pid == 0) {
+ // in child
+ zygoteServer.setForkChild();
+
+ zygoteServer.closeServerSocket();
+ IoUtils.closeQuietly(serverPipeFd);
+ serverPipeFd = null;
+
+ return handleChildProc(parsedArgs, childPipeFd,
+ parsedArgs.mStartChildZygote);
+ } else {
+ // In the parent. A pid < 0 indicates a failure and will be handled in
+ // handleParentProc.
+ IoUtils.closeQuietly(childPipeFd);
+ childPipeFd = null;
+ handleParentProc(pid, serverPipeFd);
+ return null;
+ }
+ } finally {
+ IoUtils.closeQuietly(childPipeFd);
+ IoUtils.closeQuietly(serverPipeFd);
+ }
+ } else {
+ ZygoteHooks.preFork();
+ Runnable result = Zygote.forkSimpleApps(argBuffer,
+ zygoteServer.getZygoteSocketFileDescriptor(),
+ peer.getUid(), Zygote.minChildUid(peer), parsedArgs.mNiceName);
+ if (result == null) {
+ // parent; we finished some number of forks. Result is Boolean.
+ // We already did the equivalent of handleParentProc().
+ ZygoteHooks.postForkCommon();
+ // argBuffer contains a command not understood by forksimpleApps.
+ continue;
+ } else {
+ // child; result is a Runnable.
+ zygoteServer.setForkChild();
+ Zygote.setAppProcessName(parsedArgs, TAG); // ??? Necessary?
+ return result;
+ }
+ }
+ }
}
-
- // readArgumentList returns null only when it has reached EOF with no available
- // data to read. This will only happen when the remote socket has disconnected.
- if (args == null) {
- isEof = true;
- return null;
- }
-
- int pid;
- FileDescriptor childPipeFd = null;
- FileDescriptor serverPipeFd = null;
-
- ZygoteArguments parsedArgs = new ZygoteArguments(args);
-
- if (parsedArgs.mBootCompleted) {
- handleBootCompleted();
- return null;
- }
-
- if (parsedArgs.mAbiListQuery) {
- handleAbiListQuery();
- return null;
- }
-
- if (parsedArgs.mPidQuery) {
- handlePidQuery();
- return null;
- }
-
if (parsedArgs.mUsapPoolStatusSpecified) {
+ // Now that we've released argBuffer:
return handleUsapPoolStatusChange(zygoteServer, parsedArgs.mUsapPoolEnabled);
}
-
- if (parsedArgs.mPreloadDefault) {
- handlePreload();
- return null;
- }
-
- if (parsedArgs.mPreloadPackage != null) {
- handlePreloadPackage(parsedArgs.mPreloadPackage, parsedArgs.mPreloadPackageLibs,
- parsedArgs.mPreloadPackageLibFileName, parsedArgs.mPreloadPackageCacheKey);
- return null;
- }
-
- if (canPreloadApp() && parsedArgs.mPreloadApp != null) {
- byte[] rawParcelData = Base64.getDecoder().decode(parsedArgs.mPreloadApp);
- Parcel appInfoParcel = Parcel.obtain();
- appInfoParcel.unmarshall(rawParcelData, 0, rawParcelData.length);
- appInfoParcel.setDataPosition(0);
- ApplicationInfo appInfo = ApplicationInfo.CREATOR.createFromParcel(appInfoParcel);
- appInfoParcel.recycle();
- if (appInfo != null) {
- handlePreloadApp(appInfo);
- } else {
- throw new IllegalArgumentException("Failed to deserialize --preload-app");
- }
- return null;
- }
-
- if (parsedArgs.mApiDenylistExemptions != null) {
- return handleApiDenylistExemptions(zygoteServer, parsedArgs.mApiDenylistExemptions);
- }
-
- if (parsedArgs.mHiddenApiAccessLogSampleRate != -1
- || parsedArgs.mHiddenApiAccessStatslogSampleRate != -1) {
- return handleHiddenApiAccessLogSampleRate(zygoteServer,
- parsedArgs.mHiddenApiAccessLogSampleRate,
- parsedArgs.mHiddenApiAccessStatslogSampleRate);
- }
-
- if (parsedArgs.mPermittedCapabilities != 0 || parsedArgs.mEffectiveCapabilities != 0) {
- throw new ZygoteSecurityException("Client may not specify capabilities: "
- + "permitted=0x" + Long.toHexString(parsedArgs.mPermittedCapabilities)
- + ", effective=0x" + Long.toHexString(parsedArgs.mEffectiveCapabilities));
- }
-
- Zygote.applyUidSecurityPolicy(parsedArgs, peer);
- Zygote.applyInvokeWithSecurityPolicy(parsedArgs, peer);
-
- Zygote.applyDebuggerSystemProperty(parsedArgs);
- Zygote.applyInvokeWithSystemProperty(parsedArgs);
-
- int[][] rlimits = null;
-
- if (parsedArgs.mRLimits != null) {
- rlimits = parsedArgs.mRLimits.toArray(Zygote.INT_ARRAY_2D);
- }
-
- int[] fdsToIgnore = null;
-
- if (parsedArgs.mInvokeWith != null) {
- try {
- FileDescriptor[] pipeFds = Os.pipe2(O_CLOEXEC);
- childPipeFd = pipeFds[1];
- serverPipeFd = pipeFds[0];
- Os.fcntlInt(childPipeFd, F_SETFD, 0);
- fdsToIgnore = new int[]{childPipeFd.getInt$(), serverPipeFd.getInt$()};
- } catch (ErrnoException errnoEx) {
- throw new IllegalStateException("Unable to set up pipe for invoke-with", errnoEx);
- }
- }
-
- /*
- * In order to avoid leaking descriptors to the Zygote child,
- * the native code must close the two Zygote socket descriptors
- * in the child process before it switches from Zygote-root to
- * the UID and privileges of the application being launched.
- *
- * In order to avoid "bad file descriptor" errors when the
- * two LocalSocket objects are closed, the Posix file
- * descriptors are released via a dup2() call which closes
- * the socket and substitutes an open descriptor to /dev/null.
- */
-
- int [] fdsToClose = { -1, -1 };
-
- FileDescriptor fd = mSocket.getFileDescriptor();
-
- if (fd != null) {
- fdsToClose[0] = fd.getInt$();
- }
-
- fd = zygoteServer.getZygoteSocketFileDescriptor();
-
- if (fd != null) {
- fdsToClose[1] = fd.getInt$();
- }
-
- pid = Zygote.forkAndSpecialize(parsedArgs.mUid, parsedArgs.mGid, parsedArgs.mGids,
- parsedArgs.mRuntimeFlags, rlimits, parsedArgs.mMountExternal, parsedArgs.mSeInfo,
- parsedArgs.mNiceName, fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote,
- parsedArgs.mInstructionSet, parsedArgs.mAppDataDir, parsedArgs.mIsTopApp,
- parsedArgs.mPkgDataInfoList, parsedArgs.mWhitelistedDataInfoList,
- parsedArgs.mBindMountAppDataDirs, parsedArgs.mBindMountAppStorageDirs);
-
- try {
- if (pid == 0) {
- // in child
- zygoteServer.setForkChild();
-
- zygoteServer.closeServerSocket();
- IoUtils.closeQuietly(serverPipeFd);
- serverPipeFd = null;
-
- return handleChildProc(parsedArgs, childPipeFd, parsedArgs.mStartChildZygote);
- } else {
- // In the parent. A pid < 0 indicates a failure and will be handled in
- // handleParentProc.
- IoUtils.closeQuietly(childPipeFd);
- childPipeFd = null;
- handleParentProc(pid, serverPipeFd);
- return null;
- }
- } finally {
- IoUtils.closeQuietly(childPipeFd);
- IoUtils.closeQuietly(serverPipeFd);
- }
+ throw new AssertionError("Shouldn't get here");
}
private void handleAbiListQuery() {
@@ -557,7 +590,7 @@
if (res > 0) {
if ((fds[0].revents & POLLIN) != 0) {
- // Only read one byte, so as not to block.
+ // Only read one byte, so as not to block. Really needed?
int readBytes = android.system.Os.read(pipeFd, data, dataIndex, 1);
if (readBytes < 0) {
throw new RuntimeException("Some error");
diff --git a/core/java/com/android/internal/os/ZygoteConnectionConstants.java b/core/java/com/android/internal/os/ZygoteConnectionConstants.java
index 506e39f..0c1cd6d 100644
--- a/core/java/com/android/internal/os/ZygoteConnectionConstants.java
+++ b/core/java/com/android/internal/os/ZygoteConnectionConstants.java
@@ -31,9 +31,6 @@
*/
public static final int CONNECTION_TIMEOUT_MILLIS = 1000;
- /** max number of arguments that a connection can specify */
- public static final int MAX_ZYGOTE_ARGC = 1024;
-
/**
* Wait time for a wrapped app to report back its pid.
*
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index f2ed50e..d5b778e 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -65,6 +65,7 @@
import libcore.io.IoUtils;
import java.io.BufferedReader;
+import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@@ -782,7 +783,13 @@
int pid;
try {
- parsedArgs = new ZygoteArguments(args);
+ ZygoteCommandBuffer commandBuffer = new ZygoteCommandBuffer(args);
+ try {
+ parsedArgs = ZygoteArguments.getInstance(commandBuffer);
+ } catch (EOFException e) {
+ throw new AssertionError("Unexpected argument error for forking system server", e);
+ }
+ commandBuffer.close();
Zygote.applyDebuggerSystemProperty(parsedArgs);
Zygote.applyInvokeWithSystemProperty(parsedArgs);
@@ -861,7 +868,7 @@
* into new processes are required to either set the priority to the default value or terminate
* before executing any non-system code. The native side of this occurs in SpecializeCommon,
* while the Java Language priority is changed in ZygoteInit.handleSystemServerProcess,
- * ZygoteConnection.handleChildProc, and Zygote.usapMain.
+ * ZygoteConnection.handleChildProc, and Zygote.childMain.
*
* @param argv Command line arguments used to specify the Zygote's configuration.
*/
diff --git a/core/java/com/android/internal/os/ZygoteServer.java b/core/java/com/android/internal/os/ZygoteServer.java
index 585ddf6..f71b314 100644
--- a/core/java/com/android/internal/os/ZygoteServer.java
+++ b/core/java/com/android/internal/os/ZygoteServer.java
@@ -337,7 +337,7 @@
* @param sessionSocketRawFDs Anonymous session sockets that are currently open
* @return In the Zygote process this function will always return null; in unspecialized app
* processes this function will return a Runnable object representing the new
- * application that is passed up from usapMain.
+ * application that is passed up from childMain (the usap's main wait loop).
*/
Runnable fillUsapPool(int[] sessionSocketRawFDs, boolean isPriorityRefill) {
@@ -420,6 +420,7 @@
* Runs the zygote process's select loop. Accepts new connections as
* they happen, and reads commands from connections one spawn-request's
* worth at a time.
+ * @param abiList list of ABIs supported by this zygote.
*/
Runnable runSelectLoop(String abiList) {
ArrayList<FileDescriptor> socketFDs = new ArrayList<>();
@@ -537,22 +538,23 @@
if (pollIndex == 0) {
// Zygote server socket
-
ZygoteConnection newPeer = acceptCommandPeer(abiList);
peers.add(newPeer);
socketFDs.add(newPeer.getFileDescriptor());
-
} else if (pollIndex < usapPoolEventFDIndex) {
// Session socket accepted from the Zygote server socket
try {
ZygoteConnection connection = peers.get(pollIndex);
- final Runnable command = connection.processOneCommand(this);
+ boolean multipleForksOK = !isUsapPoolEnabled()
+ && ZygoteHooks.indefiniteThreadSuspensionOK();
+ final Runnable command =
+ connection.processCommand(this, multipleForksOK);
// TODO (chriswailes): Is this extra check necessary?
if (mIsForkChild) {
// We're in the child. We should always have a command to run at
- // this stage if processOneCommand hasn't called "exec".
+ // this stage if processCommand hasn't called "exec".
if (command == null) {
throw new IllegalStateException("command == null");
}
@@ -565,7 +567,7 @@
}
// We don't know whether the remote side of the socket was closed or
- // not until we attempt to read from it from processOneCommand. This
+ // not until we attempt to read from it from processCommand. This
// shows up as a regular POLLIN event in our regular processing
// loop.
if (connection.isClosedByPeer()) {
diff --git a/core/java/com/android/internal/power/TEST_MAPPING b/core/java/com/android/internal/power/TEST_MAPPING
new file mode 100644
index 0000000..96f31bc
--- /dev/null
+++ b/core/java/com/android/internal/power/TEST_MAPPING
@@ -0,0 +1,19 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksCoreTests",
+ "options": [
+ { "include-filter": "com.android.internal.os.BatteryStatsTests" },
+ { "exclude-annotation": "com.android.internal.os.SkipPresubmit" }
+ ]
+ },
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ { "include-filter": "com.android.server.am.BatteryStatsServiceTest" },
+ { "include-filter": "com.android.server.am.MeasuredEnergySnapshotTest" },
+ { "include-filter": "com.android.server.am.BatteryExternalStatsWorkerTest" }
+ ]
+ }
+ ]
+}
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index ef2275d..ab0149f 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -30,6 +30,7 @@
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.PointerIcon;
+import android.view.ScrollCaptureResponse;
import android.view.WindowInsets.Type.InsetsType;
import android.window.ClientWindowFrames;
@@ -160,7 +161,9 @@
@Override
public void requestScrollCapture(IScrollCaptureCallbacks callbacks) {
try {
- callbacks.onUnavailable();
+ callbacks.onScrollCaptureResponse(
+ new ScrollCaptureResponse.Builder().setDescription("Not Implemented").build());
+
} catch (RemoteException ex) {
// ignore
}
diff --git a/core/java/com/android/internal/view/ScrollCaptureViewSupport.java b/core/java/com/android/internal/view/ScrollCaptureViewSupport.java
index 85fa791..a41511b 100644
--- a/core/java/com/android/internal/view/ScrollCaptureViewSupport.java
+++ b/core/java/com/android/internal/view/ScrollCaptureViewSupport.java
@@ -16,15 +16,19 @@
package com.android.internal.view;
+import android.annotation.UiThread;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
import android.graphics.HardwareRenderer;
import android.graphics.Matrix;
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.RenderNode;
-import android.os.Handler;
+import android.os.CancellationSignal;
import android.util.DisplayMetrics;
import android.util.Log;
+import android.view.Display.ColorMode;
import android.view.ScrollCaptureCallback;
import android.view.ScrollCaptureSession;
import android.view.Surface;
@@ -44,32 +48,42 @@
* @param <V> the specific View subclass handled
* @see ScrollCaptureViewHelper
*/
+@UiThread
public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCallback {
- public static final long NO_FRAME_PRODUCED = -1;
-
private static final String TAG = "ScrollCaptureViewSupport";
private static final boolean WAIT_FOR_ANIMATION = true;
private final WeakReference<V> mWeakView;
private final ScrollCaptureViewHelper<V> mViewHelper;
- private ViewRenderer mRenderer;
- private Handler mUiHandler;
+ private final ViewRenderer mRenderer;
private boolean mStarted;
private boolean mEnded;
ScrollCaptureViewSupport(V containingView, ScrollCaptureViewHelper<V> viewHelper) {
mWeakView = new WeakReference<>(containingView);
mRenderer = new ViewRenderer();
- mUiHandler = containingView.getHandler();
+ // TODO(b/177649144): provide access to color space from android.media.Image
mViewHelper = viewHelper;
}
- // Base implementation of ScrollCaptureCallback
+ /** Based on ViewRootImpl#updateColorModeIfNeeded */
+ @ColorMode
+ private static int getColorMode(View containingView) {
+ Context context = containingView.getContext();
+ int colorMode = containingView.getViewRootImpl().mWindowAttributes.getColorMode();
+ if (!context.getResources().getConfiguration().isScreenWideColorGamut()) {
+ colorMode = ActivityInfo.COLOR_MODE_DEFAULT;
+ }
+ return colorMode;
+ }
@Override
- public final void onScrollCaptureSearch(Consumer<Rect> onReady) {
+ public final void onScrollCaptureSearch(CancellationSignal signal, Consumer<Rect> onReady) {
+ if (signal.isCanceled()) {
+ return;
+ }
V view = mWeakView.get();
mStarted = false;
mEnded = false;
@@ -82,7 +96,11 @@
}
@Override
- public final void onScrollCaptureStart(ScrollCaptureSession session, Runnable onReady) {
+ public final void onScrollCaptureStart(ScrollCaptureSession session, CancellationSignal signal,
+ Runnable onReady) {
+ if (signal.isCanceled()) {
+ return;
+ }
V view = mWeakView.get();
mEnded = false;
@@ -99,18 +117,22 @@
}
@Override
- public final void onScrollCaptureImageRequest(ScrollCaptureSession session, Rect requestRect) {
+ public final void onScrollCaptureImageRequest(ScrollCaptureSession session,
+ CancellationSignal signal, Rect requestRect, Consumer<Rect> onComplete) {
+ if (signal.isCanceled()) {
+ return;
+ }
V view = mWeakView.get();
if (view == null || !view.isVisibleToUser()) {
// Signal to the controller that we have a problem and can't continue.
- session.notifyBufferSent(NO_FRAME_PRODUCED, new Rect());
+ onComplete.accept(new Rect());
return;
}
// Ask the view to scroll as needed to bring this area into view.
ScrollResult scrollResult = mViewHelper.onScrollRequested(view, session.getScrollBounds(),
requestRect);
if (scrollResult.availableArea.isEmpty()) {
- session.notifyBufferSent(NO_FRAME_PRODUCED, scrollResult.availableArea);
+ onComplete.accept(scrollResult.availableArea);
return;
}
view.invalidate(); // don't wait for vsync
@@ -121,17 +143,13 @@
viewCaptureArea.offset(0, -scrollResult.scrollDelta);
if (WAIT_FOR_ANIMATION) {
- Log.d(TAG, "render: delaying until animation");
view.postOnAnimation(() -> {
- Log.d(TAG, "postOnAnimation(): rendering now");
- long resultFrame = mRenderer.renderView(view, viewCaptureArea);
- Log.d(TAG, "notifyBufferSent: " + scrollResult.availableArea);
-
- session.notifyBufferSent(resultFrame, new Rect(scrollResult.availableArea));
+ mRenderer.renderView(view, viewCaptureArea);
+ onComplete.accept(new Rect(scrollResult.availableArea));
});
} else {
- long resultFrame = mRenderer.renderView(view, viewCaptureArea);
- session.notifyBufferSent(resultFrame, new Rect(scrollResult.availableArea));
+ mRenderer.renderView(view, viewCaptureArea);
+ onComplete.accept(new Rect(scrollResult.availableArea));
}
}
@@ -239,7 +257,7 @@
mCaptureRenderNode.endRecording();
}
- public long renderView(View view, Rect sourceRect) {
+ public void renderView(View view, Rect sourceRect) {
if (updateForView(view)) {
setupLighting(view);
}
@@ -258,7 +276,7 @@
switch (request.syncAndDraw()) {
case HardwareRenderer.SYNC_OK:
case HardwareRenderer.SYNC_REDRAW_REQUESTED:
- return frameNumber;
+ return;
case HardwareRenderer.SYNC_FRAME_DROPPED:
Log.e(TAG, "syncAndDraw(): SYNC_FRAME_DROPPED !");
@@ -270,7 +288,6 @@
Log.e(TAG, "syncAndDraw(): SYNC_CONTEXT_IS_STOPPED !");
break;
}
- return NO_FRAME_PRODUCED;
}
public void trimMemory() {
@@ -289,5 +306,17 @@
mTempMatrix.mapRect(mTempRectF);
mTempRectF.round(outRect);
}
+
+ public void setColorMode(@ColorMode int colorMode) {
+ mRenderer.setColorMode(colorMode);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "ScrollCaptureViewSupport{"
+ + "view=" + mWeakView.get()
+ + ", helper=" + mViewHelper
+ + '}';
}
}
diff --git a/core/java/com/android/internal/widget/NotificationVanishingFrameLayout.java b/core/java/com/android/internal/widget/NotificationVanishingFrameLayout.java
new file mode 100644
index 0000000..742bdfd
--- /dev/null
+++ b/core/java/com/android/internal/widget/NotificationVanishingFrameLayout.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2021 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.internal.widget;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.RemoteViews;
+
+/**
+ * This view will measure itself as having 0 size if all of its children are {@link #GONE}.
+ * Otherwise it acts like a normal {@link FrameLayout}.
+ */
+@RemoteViews.RemoteView
+public class NotificationVanishingFrameLayout extends FrameLayout {
+ public NotificationVanishingFrameLayout(Context context) {
+ this(context, null, 0, 0);
+ }
+
+ public NotificationVanishingFrameLayout(Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, 0, 0);
+ }
+
+ public NotificationVanishingFrameLayout(Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public NotificationVanishingFrameLayout(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ if (allChildrenGone()) {
+ int zeroSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY);
+ super.onMeasure(zeroSpec, zeroSpec);
+ } else {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+ }
+
+ private boolean allChildrenGone() {
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ if (child != null && child.getVisibility() != GONE) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/core/java/com/android/server/OWNERS b/core/java/com/android/server/OWNERS
new file mode 100644
index 0000000..12629254
--- /dev/null
+++ b/core/java/com/android/server/OWNERS
@@ -0,0 +1 @@
+per-file SystemConfig.java = toddke@google.com
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index b485f0f..2287900 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -211,6 +211,7 @@
"com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp",
"com_android_internal_os_KernelSingleUidTimeReader.cpp",
"com_android_internal_os_Zygote.cpp",
+ "com_android_internal_os_ZygoteCommandBuffer.cpp",
"com_android_internal_os_ZygoteInit.cpp",
"hwbinder/EphemeralStorage.cpp",
"fd_utils.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index dc77bba..ddd8613 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -198,6 +198,7 @@
extern int register_com_android_internal_os_KernelSingleProcessCpuThreadReader(JNIEnv* env);
extern int register_com_android_internal_os_KernelSingleUidTimeReader(JNIEnv *env);
extern int register_com_android_internal_os_Zygote(JNIEnv *env);
+extern int register_com_android_internal_os_ZygoteCommandBuffer(JNIEnv *env);
extern int register_com_android_internal_os_ZygoteInit(JNIEnv *env);
extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env);
@@ -1531,6 +1532,7 @@
REG_JNI(register_com_android_internal_net_NetworkUtilsInternal),
REG_JNI(register_com_android_internal_os_ClassLoaderFactory),
REG_JNI(register_com_android_internal_os_Zygote),
+ REG_JNI(register_com_android_internal_os_ZygoteCommandBuffer),
REG_JNI(register_com_android_internal_os_ZygoteInit),
REG_JNI(register_com_android_internal_util_VirtualRefBasePtr),
REG_JNI(register_android_hardware_Camera),
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index e801725..c9062d8 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -17,6 +17,8 @@
#define LOG_TAG "Zygote"
#define ATRACE_TAG ATRACE_TAG_DALVIK
+#include "com_android_internal_os_Zygote.h"
+
#include <async_safe/log.h>
// sys/mount.h has to come before linux/fs.h due to redefinition of MS_RDONLY, MS_BIND, etc
@@ -91,19 +93,6 @@
#include "nativebridge/native_bridge.h"
-/* Functions in the callchain during the fork shall not be protected with
- Armv8.3-A Pointer Authentication, otherwise child will not be able to return. */
-#ifdef __ARM_FEATURE_PAC_DEFAULT
-#ifdef __ARM_FEATURE_BTI_DEFAULT
-#define NO_PAC_FUNC __attribute__((target("branch-protection=bti")))
-#else
-#define NO_PAC_FUNC __attribute__((target("branch-protection=none")))
-#endif /* __ARM_FEATURE_BTI_DEFAULT */
-#else /* !__ARM_FEATURE_PAC_DEFAULT */
-#define NO_PAC_FUNC
-#endif /* __ARM_FEATURE_PAC_DEFAULT */
-
-
namespace {
// TODO (chriswailes): Add a function to initialize native Zygote data.
@@ -118,8 +107,7 @@
using android::base::WriteStringToFile;
using android::base::GetBoolProperty;
-#define CREATE_ERROR(...) StringPrintf("%s:%d: ", __FILE__, __LINE__). \
- append(StringPrintf(__VA_ARGS__))
+using android::zygote::ZygoteFailure;
// This type is duplicated in fd_utils.h
typedef const std::function<void(std::string)>& fail_fn_t;
@@ -214,7 +202,7 @@
static constexpr EntryStorage INVALID_ENTRY_VALUE = {-1, -1};
std::atomic<EntryStorage> mStorage;
- static_assert(decltype(mStorage)::is_always_lock_free);
+ static_assert(decltype(mStorage)::is_always_lock_free); // Accessed from signal handler.
public:
constexpr UsapTableEntry() : mStorage(INVALID_ENTRY_VALUE) {}
@@ -917,36 +905,6 @@
}
/**
- * A failure function used to report fatal errors to the managed runtime. This
- * function is often curried with the process name information and then passed
- * to called functions.
- *
- * @param env Managed runtime environment
- * @param process_name A native representation of the process name
- * @param managed_process_name A managed representation of the process name
- * @param msg The error message to be reported
- */
-[[noreturn]]
-static void ZygoteFailure(JNIEnv* env,
- const char* process_name,
- jstring managed_process_name,
- const std::string& msg) {
- std::unique_ptr<ScopedUtfChars> scoped_managed_process_name_ptr = nullptr;
- if (managed_process_name != nullptr) {
- scoped_managed_process_name_ptr.reset(new ScopedUtfChars(env, managed_process_name));
- if (scoped_managed_process_name_ptr->c_str() != nullptr) {
- process_name = scoped_managed_process_name_ptr->c_str();
- }
- }
-
- const std::string& error_msg =
- (process_name == nullptr) ? msg : StringPrintf("(%s) %s", process_name, msg.c_str());
-
- env->FatalError(error_msg.c_str());
- __builtin_unreachable();
-}
-
-/**
* A helper method for converting managed strings to native strings. A fatal
* error is generated if a problem is encountered in extracting a non-null
* string.
@@ -1073,86 +1031,6 @@
#endif
}
-// Utility routine to fork a process from the zygote.
-NO_PAC_FUNC
-static pid_t ForkCommon(JNIEnv* env, bool is_system_server,
- const std::vector<int>& fds_to_close,
- const std::vector<int>& fds_to_ignore,
- bool is_priority_fork) {
- SetSignalHandlers();
-
- // Curry a failure function.
- auto fail_fn = std::bind(ZygoteFailure, env, is_system_server ? "system_server" : "zygote",
- nullptr, _1);
-
- // Temporarily block SIGCHLD during forks. The SIGCHLD handler might
- // log, which would result in the logging FDs we close being reopened.
- // This would cause failures because the FDs are not allowlisted.
- //
- // Note that the zygote process is single threaded at this point.
- BlockSignal(SIGCHLD, fail_fn);
-
- // Close any logging related FDs before we start evaluating the list of
- // file descriptors.
- __android_log_close();
- AStatsSocket_close();
-
- // If this is the first fork for this zygote, create the open FD table. If
- // it isn't, we just need to check whether the list of open files has changed
- // (and it shouldn't in the normal case).
- if (gOpenFdTable == nullptr) {
- gOpenFdTable = FileDescriptorTable::Create(fds_to_ignore, fail_fn);
- } else {
- gOpenFdTable->Restat(fds_to_ignore, fail_fn);
- }
-
- android_fdsan_error_level fdsan_error_level = android_fdsan_get_error_level();
-
- // Purge unused native memory in an attempt to reduce the amount of false
- // sharing with the child process. By reducing the size of the libc_malloc
- // region shared with the child process we reduce the number of pages that
- // transition to the private-dirty state when malloc adjusts the meta-data
- // on each of the pages it is managing after the fork.
- mallopt(M_PURGE, 0);
-
- pid_t pid = fork();
-
- if (pid == 0) {
- if (is_priority_fork) {
- setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_MAX);
- } else {
- setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_MIN);
- }
-
- // The child process.
- PAuthKeyChange(env);
- PreApplicationInit();
-
- // Clean up any descriptors which must be closed immediately
- DetachDescriptors(env, fds_to_close, fail_fn);
-
- // Invalidate the entries in the USAP table.
- ClearUsapTable();
-
- // Re-open all remaining open file descriptors so that they aren't shared
- // with the zygote across a fork.
- gOpenFdTable->ReopenOrDetach(fail_fn);
-
- // Turn fdsan back on.
- android_fdsan_set_error_level(fdsan_error_level);
-
- // Reset the fd to the unsolicited zygote socket
- gSystemServerSocketFd = -1;
- } else {
- ALOGD("Forked child process %d", pid);
- }
-
- // We blocked SIGCHLD prior to a fork, we unblock it here.
- UnblockSignal(SIGCHLD, fail_fn);
-
- return pid;
-}
-
// Create an app data directory over tmpfs overlayed CE / DE storage, and bind mount it
// from the actual app data directory in data mirror.
static bool createAndMountAppData(std::string_view package_name,
@@ -1973,9 +1851,10 @@
static int sUsapTableInsertIndex = 0;
int search_index = sUsapTableInsertIndex;
-
do {
if (gUsapTable[search_index].SetIfInvalid(usap_pid, read_pipe_fd)) {
+ ++gUsapPoolCount;
+
// Start our next search right after where we finished this one.
sUsapTableInsertIndex = (search_index + 1) % gUsapTable.size();
@@ -1993,7 +1872,7 @@
/**
* Invalidates the entry in the USAPTable corresponding to the provided
* process ID if it is present. If an entry was removed the USAP pool
- * count is decremented.
+ * count is decremented. May be called from signal handler.
*
* @param usap_pid Process ID of the USAP entry to invalidate
* @return True if an entry was invalidated; false otherwise
@@ -2069,6 +1948,121 @@
namespace android {
+/**
+ * A failure function used to report fatal errors to the managed runtime. This
+ * function is often curried with the process name information and then passed
+ * to called functions.
+ *
+ * @param env Managed runtime environment
+ * @param process_name A native representation of the process name
+ * @param managed_process_name A managed representation of the process name
+ * @param msg The error message to be reported
+ */
+[[noreturn]]
+void zygote::ZygoteFailure(JNIEnv* env,
+ const char* process_name,
+ jstring managed_process_name,
+ const std::string& msg) {
+ std::unique_ptr<ScopedUtfChars> scoped_managed_process_name_ptr = nullptr;
+ if (managed_process_name != nullptr) {
+ scoped_managed_process_name_ptr.reset(new ScopedUtfChars(env, managed_process_name));
+ if (scoped_managed_process_name_ptr->c_str() != nullptr) {
+ process_name = scoped_managed_process_name_ptr->c_str();
+ }
+ }
+
+ const std::string& error_msg =
+ (process_name == nullptr || process_name[0] == '\0') ?
+ msg : StringPrintf("(%s) %s", process_name, msg.c_str());
+
+ env->FatalError(error_msg.c_str());
+ __builtin_unreachable();
+}
+
+// Utility routine to fork a process from the zygote.
+NO_PAC_FUNC
+pid_t zygote::ForkCommon(JNIEnv* env, bool is_system_server,
+ const std::vector<int>& fds_to_close,
+ const std::vector<int>& fds_to_ignore,
+ bool is_priority_fork,
+ bool purge) {
+ SetSignalHandlers();
+
+ // Curry a failure function.
+ auto fail_fn = std::bind(zygote::ZygoteFailure, env,
+ is_system_server ? "system_server" : "zygote",
+ nullptr, _1);
+
+ // Temporarily block SIGCHLD during forks. The SIGCHLD handler might
+ // log, which would result in the logging FDs we close being reopened.
+ // This would cause failures because the FDs are not allowlisted.
+ //
+ // Note that the zygote process is single threaded at this point.
+ BlockSignal(SIGCHLD, fail_fn);
+
+ // Close any logging related FDs before we start evaluating the list of
+ // file descriptors.
+ __android_log_close();
+ AStatsSocket_close();
+
+ // If this is the first fork for this zygote, create the open FD table. If
+ // it isn't, we just need to check whether the list of open files has changed
+ // (and it shouldn't in the normal case).
+ if (gOpenFdTable == nullptr) {
+ gOpenFdTable = FileDescriptorTable::Create(fds_to_ignore, fail_fn);
+ } else {
+ gOpenFdTable->Restat(fds_to_ignore, fail_fn);
+ }
+
+ android_fdsan_error_level fdsan_error_level = android_fdsan_get_error_level();
+
+ if (purge) {
+ // Purge unused native memory in an attempt to reduce the amount of false
+ // sharing with the child process. By reducing the size of the libc_malloc
+ // region shared with the child process we reduce the number of pages that
+ // transition to the private-dirty state when malloc adjusts the meta-data
+ // on each of the pages it is managing after the fork.
+ mallopt(M_PURGE, 0);
+ }
+
+ pid_t pid = fork();
+
+ if (pid == 0) {
+ if (is_priority_fork) {
+ setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_MAX);
+ } else {
+ setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_MIN);
+ }
+
+ // The child process.
+ PAuthKeyChange(env);
+ PreApplicationInit();
+
+ // Clean up any descriptors which must be closed immediately
+ DetachDescriptors(env, fds_to_close, fail_fn);
+
+ // Invalidate the entries in the USAP table.
+ ClearUsapTable();
+
+ // Re-open all remaining open file descriptors so that they aren't shared
+ // with the zygote across a fork.
+ gOpenFdTable->ReopenOrDetach(fail_fn);
+
+ // Turn fdsan back on.
+ android_fdsan_set_error_level(fdsan_error_level);
+
+ // Reset the fd to the unsolicited zygote socket
+ gSystemServerSocketFd = -1;
+ } else {
+ ALOGD("Forked child process %d", pid);
+ }
+
+ // We blocked SIGCHLD prior to a fork, we unblock it here.
+ UnblockSignal(SIGCHLD, fail_fn);
+
+ return pid;
+}
+
static void com_android_internal_os_Zygote_nativePreApplicationInit(JNIEnv*, jclass) {
PreApplicationInit();
}
@@ -2085,7 +2079,8 @@
jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
if (UNLIKELY(managed_fds_to_close == nullptr)) {
- ZygoteFailure(env, "zygote", nice_name, "Zygote received a null fds_to_close vector.");
+ zygote::ZygoteFailure(env, "zygote", nice_name,
+ "Zygote received a null fds_to_close vector.");
}
std::vector<int> fds_to_close =
@@ -2111,7 +2106,7 @@
fds_to_ignore.push_back(gSystemServerSocketFd);
}
- pid_t pid = ForkCommon(env, false, fds_to_close, fds_to_ignore, true);
+ pid_t pid = zygote::ForkCommon(env, false, fds_to_close, fds_to_ignore, true);
if (pid == 0) {
SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
@@ -2146,10 +2141,10 @@
fds_to_ignore.push_back(gSystemServerSocketFd);
}
- pid_t pid = ForkCommon(env, true,
- fds_to_close,
- fds_to_ignore,
- true);
+ pid_t pid = zygote::ForkCommon(env, true,
+ fds_to_close,
+ fds_to_ignore,
+ true);
if (pid == 0) {
// System server prcoess does not need data isolation so no need to
// know pkg_data_info_list.
@@ -2189,58 +2184,74 @@
* ensuring proper file descriptor hygiene.
*
* @param env Managed runtime environment
- * @param read_pipe_fd The read FD for the USAP reporting pipe. Manually closed by blastlas
- * in managed code.
+ * @param read_pipe_fd The read FD for the USAP reporting pipe. Manually closed by the child
+ * in managed code. -1 indicates none.
* @param write_pipe_fd The write FD for the USAP reporting pipe. Manually closed by the
- * zygote in managed code.
+ * zygote in managed code. -1 indicates none.
* @param managed_session_socket_fds A list of anonymous session sockets that must be ignored by
* the FD hygiene code and automatically "closed" in the new USAP.
+ * @param args_known Arguments for specialization are available; no need to read from a socket
* @param is_priority_fork Controls the nice level assigned to the newly created process
- * @return
+ * @return child pid in the parent, 0 in the child
*/
NO_PAC_FUNC
-static jint com_android_internal_os_Zygote_nativeForkUsap(JNIEnv* env,
- jclass,
- jint read_pipe_fd,
- jint write_pipe_fd,
- jintArray managed_session_socket_fds,
- jboolean is_priority_fork) {
- std::vector<int> fds_to_close(MakeUsapPipeReadFDVector()),
- fds_to_ignore(fds_to_close);
-
+static jint com_android_internal_os_Zygote_nativeForkApp(JNIEnv* env,
+ jclass,
+ jint read_pipe_fd,
+ jint write_pipe_fd,
+ jintArray managed_session_socket_fds,
+ jboolean args_known,
+ jboolean is_priority_fork) {
std::vector<int> session_socket_fds =
ExtractJIntArray(env, "USAP", nullptr, managed_session_socket_fds)
.value_or(std::vector<int>());
+ return zygote::forkApp(env, read_pipe_fd, write_pipe_fd, session_socket_fds,
+ args_known == JNI_TRUE, is_priority_fork == JNI_TRUE, true);
+}
- // The USAP Pool Event FD is created during the initialization of the
- // USAP pool and should always be valid here.
+NO_PAC_FUNC
+int zygote::forkApp(JNIEnv* env,
+ int read_pipe_fd,
+ int write_pipe_fd,
+ const std::vector<int>& session_socket_fds,
+ bool args_known,
+ bool is_priority_fork,
+ bool purge) {
+
+ std::vector<int> fds_to_close(MakeUsapPipeReadFDVector()),
+ fds_to_ignore(fds_to_close);
fds_to_close.push_back(gZygoteSocketFD);
- fds_to_close.push_back(gUsapPoolEventFD);
- fds_to_close.insert(fds_to_close.end(), session_socket_fds.begin(), session_socket_fds.end());
if (gSystemServerSocketFd != -1) {
fds_to_close.push_back(gSystemServerSocketFd);
}
+ if (args_known) {
+ fds_to_close.push_back(gUsapPoolSocketFD);
+ }
+ fds_to_close.insert(fds_to_close.end(), session_socket_fds.begin(), session_socket_fds.end());
- fds_to_ignore.push_back(gZygoteSocketFD);
fds_to_ignore.push_back(gUsapPoolSocketFD);
- fds_to_ignore.push_back(gUsapPoolEventFD);
- fds_to_ignore.push_back(read_pipe_fd);
- fds_to_ignore.push_back(write_pipe_fd);
+ fds_to_ignore.push_back(gZygoteSocketFD);
+ if (read_pipe_fd != -1) {
+ fds_to_ignore.push_back(read_pipe_fd);
+ }
+ if (write_pipe_fd != -1) {
+ fds_to_ignore.push_back(write_pipe_fd);
+ }
fds_to_ignore.insert(fds_to_ignore.end(), session_socket_fds.begin(), session_socket_fds.end());
+
+ if (gUsapPoolEventFD != -1) {
+ fds_to_close.push_back(gUsapPoolEventFD);
+ fds_to_ignore.push_back(gUsapPoolEventFD);
+ }
if (gSystemServerSocketFd != -1) {
+ if (args_known) {
+ fds_to_close.push_back(gSystemServerSocketFd);
+ }
fds_to_ignore.push_back(gSystemServerSocketFd);
}
-
- pid_t usap_pid = ForkCommon(env, /* is_system_server= */ false, fds_to_close, fds_to_ignore,
- is_priority_fork == JNI_TRUE);
-
- if (usap_pid != 0) {
- ++gUsapPoolCount;
- AddUsapTableEntry(usap_pid, read_pipe_fd);
- }
-
- return usap_pid;
+ return zygote::ForkCommon(env, /* is_system_server= */ false, fds_to_close,
+ fds_to_ignore, is_priority_fork == JNI_TRUE, purge);
}
static void com_android_internal_os_Zygote_nativeAllowFileAcrossFork(
@@ -2354,7 +2365,7 @@
*/
if (!SetTaskProfiles(0, {})) {
- ZygoteFailure(env, "zygote", nullptr, "Zygote SetTaskProfiles failed");
+ zygote::ZygoteFailure(env, "zygote", nullptr, "Zygote SetTaskProfiles failed");
}
}
@@ -2372,15 +2383,21 @@
return managed_usap_fds;
}
+/*
+ * Add the given pid and file descriptor to the Usap table. CriticalNative method.
+ */
+static void com_android_internal_os_Zygote_nativeAddUsapTableEntry(jint pid, jint read_pipe_fd) {
+ AddUsapTableEntry(pid, read_pipe_fd);
+}
+
/**
- * A JNI wrapper around RemoveUsapTableEntry.
+ * A JNI wrapper around RemoveUsapTableEntry. CriticalNative method.
*
* @param env Managed runtime environment
* @param usap_pid Process ID of the USAP entry to invalidate
* @return True if an entry was invalidated; false otherwise.
*/
-static jboolean com_android_internal_os_Zygote_nativeRemoveUsapTableEntry(JNIEnv* env, jclass,
- jint usap_pid) {
+static jboolean com_android_internal_os_Zygote_nativeRemoveUsapTableEntry(jint usap_pid) {
return RemoveUsapTableEntry(usap_pid);
}
@@ -2395,7 +2412,8 @@
static jint com_android_internal_os_Zygote_nativeGetUsapPoolEventFD(JNIEnv* env, jclass) {
if (gUsapPoolEventFD == -1) {
if ((gUsapPoolEventFD = eventfd(0, 0)) == -1) {
- ZygoteFailure(env, "zygote", nullptr, StringPrintf("Unable to create eventfd: %s", strerror(errno)));
+ zygote::ZygoteFailure(env, "zygote", nullptr,
+ StringPrintf("Unable to create eventfd: %s", strerror(errno)));
}
}
@@ -2438,12 +2456,12 @@
}
static void com_android_internal_os_Zygote_nativeBlockSigTerm(JNIEnv* env, jclass) {
- auto fail_fn = std::bind(ZygoteFailure, env, "usap", nullptr, _1);
+ auto fail_fn = std::bind(zygote::ZygoteFailure, env, "usap", nullptr, _1);
BlockSignal(SIGTERM, fail_fn);
}
static void com_android_internal_os_Zygote_nativeUnblockSigTerm(JNIEnv* env, jclass) {
- auto fail_fn = std::bind(ZygoteFailure, env, "usap", nullptr, _1);
+ auto fail_fn = std::bind(zygote::ZygoteFailure, env, "usap", nullptr, _1);
UnblockSignal(SIGTERM, fail_fn);
}
@@ -2549,7 +2567,10 @@
(void*)com_android_internal_os_Zygote_nativePreApplicationInit},
{"nativeInstallSeccompUidGidFilter", "(II)V",
(void*)com_android_internal_os_Zygote_nativeInstallSeccompUidGidFilter},
- {"nativeForkUsap", "(II[IZ)I", (void*)com_android_internal_os_Zygote_nativeForkUsap},
+ {"nativeForkApp", "(II[IZZ)I", (void*)com_android_internal_os_Zygote_nativeForkApp},
+ // @CriticalNative
+ {"nativeAddUsapTableEntry", "(II)V",
+ (void*)com_android_internal_os_Zygote_nativeAddUsapTableEntry},
{"nativeSpecializeAppProcess",
"(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/"
"String;Z[Ljava/lang/String;[Ljava/lang/String;ZZ)V",
@@ -2558,6 +2579,10 @@
(void*)com_android_internal_os_Zygote_nativeInitNativeState},
{"nativeGetUsapPipeFDs", "()[I",
(void*)com_android_internal_os_Zygote_nativeGetUsapPipeFDs},
+ // @CriticalNative
+ {"nativeAddUsapTableEntry", "(II)V",
+ (void*)com_android_internal_os_Zygote_nativeAddUsapTableEntry},
+ // @CriticalNative
{"nativeRemoveUsapTableEntry", "(I)Z",
(void*)com_android_internal_os_Zygote_nativeRemoveUsapTableEntry},
{"nativeGetUsapPoolEventFD", "()I",
diff --git a/core/jni/com_android_internal_os_Zygote.h b/core/jni/com_android_internal_os_Zygote.h
new file mode 100644
index 0000000..d2da914
--- /dev/null
+++ b/core/jni/com_android_internal_os_Zygote.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2008 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.
+ */
+
+#ifndef _COM_ANDROID_INTERNAL_OS_ZYGOTE_H
+#define _COM_ANDROID_INTERNAL_OS_ZYGOTE_H
+
+#define LOG_TAG "Zygote"
+#define ATRACE_TAG ATRACE_TAG_DALVIK
+
+/* Functions in the callchain during the fork shall not be protected with
+ Armv8.3-A Pointer Authentication, otherwise child will not be able to return. */
+#ifdef __ARM_FEATURE_PAC_DEFAULT
+#ifdef __ARM_FEATURE_BTI_DEFAULT
+#define NO_PAC_FUNC __attribute__((target("branch-protection=bti")))
+#else
+#define NO_PAC_FUNC __attribute__((target("branch-protection=none")))
+#endif /* __ARM_FEATURE_BTI_DEFAULT */
+#else /* !__ARM_FEATURE_PAC_DEFAULT */
+#define NO_PAC_FUNC
+#endif /* __ARM_FEATURE_PAC_DEFAULT */
+
+#include <jni.h>
+#include <vector>
+#include <android-base/stringprintf.h>
+
+#define CREATE_ERROR(...) StringPrintf("%s:%d: ", __FILE__, __LINE__). \
+ append(StringPrintf(__VA_ARGS__))
+
+namespace android {
+namespace zygote {
+
+NO_PAC_FUNC
+pid_t ForkCommon(JNIEnv* env,bool is_system_server,
+ const std::vector<int>& fds_to_close,
+ const std::vector<int>& fds_to_ignore,
+ bool is_priority_fork,
+ bool purge = true);
+
+/**
+ * Fork a process. The pipe fds are used for usap communication, or -1 in
+ * other cases. Session_socket_fds are FDs used for zygote communication that must be dealt
+ * with hygienically, but are not otherwise used here. Args_known indicates that the process
+ * will be immediately specialized with arguments that are already known, so no usap
+ * communication is required. Is_priority_fork should be true if this is on the app startup
+ * critical path. Purge specifies that unused pages should be purged before the fork.
+ */
+NO_PAC_FUNC
+int forkApp(JNIEnv* env,
+ int read_pipe_fd,
+ int write_pipe_fd,
+ const std::vector<int>& session_socket_fds,
+ bool args_known,
+ bool is_priority_fork,
+ bool purge);
+
+[[noreturn]]
+void ZygoteFailure(JNIEnv* env,
+ const char* process_name,
+ jstring managed_process_name,
+ const std::string& msg);
+
+} // namespace zygote
+} // namespace android
+
+#endif // _COM_ANDROID_INTERNAL_OS_ZYGOTE_
diff --git a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
new file mode 100644
index 0000000..011e8f8
--- /dev/null
+++ b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
@@ -0,0 +1,512 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#include "com_android_internal_os_Zygote.h"
+
+#include <algorithm>
+#include <android-base/logging.h>
+#include <async_safe/log.h>
+#include <cctype>
+#include <chrono>
+#include <core_jni_helpers.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <jni.h>
+#include <nativehelper/JNIHelp.h>
+#include <optional>
+#include <poll.h>
+#include <unistd.h>
+#include <utility>
+#include <utils/misc.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <vector>
+
+namespace android {
+
+using namespace std::placeholders;
+using android::base::StringPrintf;
+using android::zygote::ZygoteFailure;
+
+// WARNING: Knows a little about the wire protocol used to communicate with Zygote.
+// TODO: Fix error handling.
+
+constexpr size_t MAX_COMMAND_BYTES = 12200;
+constexpr size_t NICE_NAME_BYTES = 50;
+
+// A buffer optionally bundled with a file descriptor from which we can fill it.
+// Does not own the file descriptor; destroying a NativeCommandBuffer does not
+// close the descriptor.
+class NativeCommandBuffer {
+ public:
+ NativeCommandBuffer(int sourceFd): mEnd(0), mNext(0), mLinesLeft(0), mFd(sourceFd) {}
+
+ // Read mNext line from mFd, filling mBuffer from file descriptor, as needed.
+ // Return a pair of pointers pointing to the first character, and one past the
+ // mEnd of the line, i.e. at the newline. Returns nothing on failure.
+ template<class FailFn>
+ std::optional<std::pair<char*, char*>> readLine(FailFn fail_fn) {
+ char* result = mBuffer + mNext;
+ while (true) {
+ if (mNext == mEnd) {
+ if (mEnd == MAX_COMMAND_BYTES) {
+ return {};
+ }
+ if (mFd == -1) {
+ fail_fn("ZygoteCommandBuffer.readLine attempted to read from mFd -1");
+ }
+ ssize_t nread = TEMP_FAILURE_RETRY(read(mFd, mBuffer + mEnd, MAX_COMMAND_BYTES - mEnd));
+ if (nread <= 0) {
+ if (nread == 0) {
+ return {};
+ }
+ fail_fn(CREATE_ERROR("session socket read failed: %s", strerror(errno)));
+ } else if (nread == MAX_COMMAND_BYTES - mEnd) {
+ // This is pessimistic by one character, but close enough.
+ fail_fn("ZygoteCommandBuffer overflowed: command too long");
+ }
+ mEnd += nread;
+ }
+ // UTF-8 does not allow newline to occur as part of a multibyte character.
+ char* nl = static_cast<char *>(memchr(mBuffer + mNext, '\n', mEnd - mNext));
+ if (nl == nullptr) {
+ mNext = mEnd;
+ } else {
+ mNext = nl - mBuffer + 1;
+ if (--mLinesLeft < 0) {
+ fail_fn("ZygoteCommandBuffer.readLine attempted to read past mEnd of command");
+ }
+ return std::make_pair(result, nl);
+ }
+ }
+ }
+
+ void reset() {
+ mNext = 0;
+ }
+
+ // Make sure the current command is fully buffered, without reading past the current command.
+ template<class FailFn>
+ void readAllLines(FailFn fail_fn) {
+ while (mLinesLeft > 0) {
+ readLine(fail_fn);
+ }
+ }
+
+ void clear() {
+ // Don't bother to actually clear the buffer; it'll be unmapped in the child anyway.
+ reset();
+ mNiceName[0] = '\0';
+ mEnd = 0;
+ }
+
+ // Insert line into the mBuffer. Checks that the mBuffer is not associated with an mFd.
+ // Implicitly adds newline separators. Allows mBuffer contents to be explicitly set.
+ void insert(const char* line, size_t lineLen) {
+ DCHECK(mFd == -1);
+ CHECK(mEnd + lineLen < MAX_COMMAND_BYTES);
+ strncpy(mBuffer + mEnd, line, lineLen);
+ mBuffer[mEnd + lineLen] = '\n';
+ mEnd += lineLen + 1;
+ }
+
+ // Clear mBuffer, start reading new command, return the number of arguments, leaving mBuffer
+ // positioned at the beginning of first argument. Return 0 on EOF.
+ template<class FailFn>
+ int getCount(FailFn fail_fn) {
+ mLinesLeft = 1;
+ auto line = readLine(fail_fn);
+ if (!line.has_value()) {
+ return 0;
+ }
+ char* countString = line.value().first; // Newline terminated.
+ long nArgs = atol(countString);
+ if (nArgs <= 0 || nArgs >= MAX_COMMAND_BYTES / 2) {
+ fail_fn(CREATE_ERROR("Unreasonable argument count %ld", nArgs));
+ }
+ mLinesLeft = nArgs;
+ return static_cast<int>(nArgs);
+ }
+
+ // Is the mBuffer a simple fork command?
+ // We disallow request to wrap the child process, child zygotes, anything that
+ // mentions capabilities or requests uid < minUid.
+ // We insist that --setuid and --setgid arguments are explicitly included and that the
+ // command starts with --runtime-args.
+ // Assumes we are positioned at the beginning of the command after the argument count,
+ // and leaves the position at some indeterminate position in the buffer.
+ // As a side effect, this sets mNiceName to a non-empty string, if possible.
+ template<class FailFn>
+ bool isSimpleForkCommand(int minUid, FailFn fail_fn) {
+ if (mLinesLeft <= 0 || mLinesLeft >= MAX_COMMAND_BYTES / 2) {
+ return false;
+ }
+ static const char* RUNTIME_ARGS = "--runtime-args";
+ static const char* INVOKE_WITH = "--invoke-with";
+ static const char* CHILD_ZYGOTE = "--start-child-zygote";
+ static const char* SETUID = "--setuid=";
+ static const char* SETGID = "--setgid=";
+ static const char* CAPABILITIES = "--capabilities";
+ static const char* NICE_NAME = "--nice-name=";
+ static const size_t RA_LENGTH = strlen(RUNTIME_ARGS);
+ static const size_t IW_LENGTH = strlen(INVOKE_WITH);
+ static const size_t CZ_LENGTH = strlen(CHILD_ZYGOTE);
+ static const size_t SU_LENGTH = strlen(SETUID);
+ static const size_t SG_LENGTH = strlen(SETGID);
+ static const size_t CA_LENGTH = strlen(CAPABILITIES);
+ static const size_t NN_LENGTH = strlen(NICE_NAME);
+
+ bool saw_setuid = false, saw_setgid = false;
+ bool saw_runtime_args = false;
+
+ while (mLinesLeft > 0) {
+ auto read_result = readLine(fail_fn);
+ if (!read_result.has_value()) {
+ return false;
+ }
+ auto [arg_start, arg_end] = read_result.value();
+ if (arg_end - arg_start == RA_LENGTH
+ && strncmp(arg_start, RUNTIME_ARGS, RA_LENGTH) == 0) {
+ saw_runtime_args = true;
+ continue;
+ }
+ if (arg_end - arg_start >= NN_LENGTH
+ && strncmp(arg_start, NICE_NAME, NN_LENGTH) == 0) {
+ size_t name_len = arg_end - (arg_start + NN_LENGTH);
+ size_t copy_len = std::min(name_len, NICE_NAME_BYTES - 1);
+ memcpy(mNiceName, arg_start + NN_LENGTH, copy_len);
+ mNiceName[copy_len] = '\0';
+ continue;
+ }
+ if (arg_end - arg_start == IW_LENGTH
+ && strncmp(arg_start, INVOKE_WITH, IW_LENGTH) == 0) {
+ // This also removes the need for invoke-with security checks here.
+ return false;
+ }
+ if (arg_end - arg_start == CZ_LENGTH
+ && strncmp(arg_start, CHILD_ZYGOTE, CZ_LENGTH) == 0) {
+ return false;
+ }
+ if (arg_end - arg_start >= CA_LENGTH
+ && strncmp(arg_start, CAPABILITIES, CA_LENGTH) == 0) {
+ return false;
+ }
+ if (arg_end - arg_start >= SU_LENGTH
+ && strncmp(arg_start, SETUID, SU_LENGTH) == 0) {
+ int uid = digitsVal(arg_start + SU_LENGTH, arg_end);
+ if (uid < minUid) {
+ return false;
+ }
+ saw_setuid = true;
+ continue;
+ }
+ if (arg_end - arg_start >= SG_LENGTH
+ && strncmp(arg_start, SETGID, SG_LENGTH) == 0) {
+ int gid = digitsVal(arg_start + SG_LENGTH, arg_end);
+ if (gid == -1) {
+ return false;
+ }
+ saw_setgid = true;
+ }
+ }
+ return saw_runtime_args && saw_setuid && saw_setgid;
+ }
+
+ void setFd(int new_fd) {
+ mFd = new_fd;
+ }
+
+ int getFd() const {
+ return mFd;
+ }
+
+ const char* niceNameAddr() const {
+ return mNiceName;
+ }
+
+ // Debug only:
+ void logState() const {
+ ALOGD("mbuffer starts with %c%c, nice name is %s, "
+ "mEnd = %u, mNext = %u, mLinesLeft = %d, mFd = %d",
+ mBuffer[0], (mBuffer[1] == '\n' ? ' ' : mBuffer[1]),
+ niceNameAddr(),
+ static_cast<unsigned>(mEnd), static_cast<unsigned>(mNext),
+ static_cast<int>(mLinesLeft), mFd);
+ }
+
+ private:
+ // Picky version of atoi(). No sign or unexpected characters allowed. Return -1 on failure.
+ static int digitsVal(char* start, char* end) {
+ int result = 0;
+ if (end - start > 6) {
+ return -1;
+ }
+ for (char* dp = start; dp < end; ++dp) {
+ if (*dp < '0' || *dp > '9') {
+ ALOGW("Argument failed integer format check");
+ return -1;
+ }
+ result = 10 * result + (*dp - '0');
+ }
+ return result;
+ }
+
+ uint32_t mEnd; // Index of first empty byte in the mBuffer.
+ uint32_t mNext; // Index of first character past last line returned by readLine.
+ int32_t mLinesLeft; // Lines in current command that haven't yet been read.
+ int mFd; // Open file descriptor from which we can read more. -1 if none.
+ char mNiceName[NICE_NAME_BYTES];
+ char mBuffer[MAX_COMMAND_BYTES];
+};
+
+static_assert(sizeof(NativeCommandBuffer) < 3 * 4096);
+
+static int buffersAllocd(0);
+
+// Get a new NativeCommandBuffer. Can only be called once between freeNativeBuffer calls,
+// so that only one buffer exists at a time.
+jlong com_android_internal_os_ZygoteCommandBuffer_getNativeBuffer(JNIEnv* env, jclass, jint fd) {
+ CHECK(buffersAllocd == 0);
+ ++buffersAllocd;
+ // MMap explicitly to get it page aligned.
+ void *bufferMem = mmap(NULL, sizeof(NativeCommandBuffer), PROT_READ | PROT_WRITE,
+ MAP_ANONYMOUS | MAP_PRIVATE | MAP_POPULATE, -1, 0);
+ // Currently we mmap and unmap one for every request handled by the Java code.
+ // That could be improved, but unclear it matters.
+ if (bufferMem == MAP_FAILED) {
+ ZygoteFailure(env, nullptr, nullptr, "Failed to map argument buffer");
+ }
+ return (jlong) new(bufferMem) NativeCommandBuffer(fd);
+}
+
+// Delete native command buffer.
+void com_android_internal_os_ZygoteCommandBuffer_freeNativeBuffer(JNIEnv* env, jclass,
+ jlong j_buffer) {
+ CHECK(buffersAllocd == 1);
+ NativeCommandBuffer* n_buffer = reinterpret_cast<NativeCommandBuffer*>(j_buffer);
+ n_buffer->~NativeCommandBuffer();
+ if (munmap(n_buffer, sizeof(NativeCommandBuffer)) != 0) {
+ ZygoteFailure(env, nullptr, nullptr, "Failed to unmap argument buffer");
+ }
+ --buffersAllocd;
+}
+
+// Clear the buffer, read the line containing the count, and return the count.
+jint com_android_internal_os_ZygoteCommandBuffer_nativeGetCount(JNIEnv* env, jclass,
+ jlong j_buffer) {
+ NativeCommandBuffer* n_buffer = reinterpret_cast<NativeCommandBuffer*>(j_buffer);
+ auto fail_fn = std::bind(ZygoteFailure, env, nullptr, nullptr, _1);
+ return n_buffer->getCount(fail_fn);
+}
+
+// Explicitly insert a string as the last line (argument) of the buffer.
+void com_android_internal_os_ZygoteCommandBuffer_insert(JNIEnv* env, jclass, jlong j_buffer,
+ jstring line) {
+ NativeCommandBuffer* n_buffer = reinterpret_cast<NativeCommandBuffer*>(j_buffer);
+ size_t lineLen = static_cast<size_t>(env->GetStringUTFLength(line));
+ const char* cstring = env->GetStringUTFChars(line, NULL);
+ n_buffer->insert(cstring, lineLen);
+ env->ReleaseStringUTFChars(line, cstring);
+}
+
+// Read a line from the buffer, refilling as necessary.
+jstring com_android_internal_os_ZygoteCommandBuffer_nativeNextArg(JNIEnv* env, jclass,
+ jlong j_buffer) {
+ NativeCommandBuffer* n_buffer = reinterpret_cast<NativeCommandBuffer*>(j_buffer);
+ auto fail_fn = std::bind(ZygoteFailure, env, n_buffer->niceNameAddr(), nullptr, _1);
+ auto line = n_buffer->readLine(fail_fn);
+ if (!line.has_value()) {
+ fail_fn("Incomplete zygote command");
+ }
+ auto [cresult, endp] = line.value();
+ // OK to temporarily clobber the buffer, since this is not thread safe, and we're modifying
+ // the buffer anyway.
+ *endp = '\0';
+ jstring result = env->NewStringUTF(cresult);
+ *endp = '\n';
+ return result;
+}
+
+// Read all lines from the current command into the buffer, and then reset the buffer, so
+// we will start reading again at the beginning of the command, starting with the argument
+// count. And we don't need access to the fd to do so.
+void com_android_internal_os_ZygoteCommandBuffer_nativeReadFullyAndReset(JNIEnv* env, jclass,
+ jlong j_buffer) {
+ NativeCommandBuffer* n_buffer = reinterpret_cast<NativeCommandBuffer*>(j_buffer);
+ auto fail_fn = std::bind(ZygoteFailure, env, n_buffer->niceNameAddr(), nullptr, _1);
+ n_buffer->readAllLines(fail_fn);
+ n_buffer->reset();
+}
+
+// Fork a child as specified by the current command buffer, and refill the command
+// buffer from the given socket. So long as the result is another simple fork command,
+// repeat this process.
+// It must contain a fork command, which is currently restricted not to fork another
+// zygote or involve a wrapper process.
+// The initial buffer should be partially or entirely read; we read it fully and reset it.
+// When we return, the buffer contains the command we couldn't handle, and has been reset().
+// We return false in the parent when we see a command we didn't understand, and thus the
+// command in the buffer still needs to be executed.
+// We return true in each child.
+// We only process fork commands if the peer uid matches expected_uid.
+// For every fork command after the first, we check that the requested uid is at
+// least minUid.
+NO_PAC_FUNC
+jboolean com_android_internal_os_ZygoteCommandBuffer_nativeForkRepeatedly(
+ JNIEnv* env,
+ jclass,
+ jlong j_buffer,
+ jint zygote_socket_fd,
+ jint expected_uid,
+ jint minUid,
+ jstring managed_nice_name) {
+
+ NativeCommandBuffer* n_buffer = reinterpret_cast<NativeCommandBuffer*>(j_buffer);
+ int session_socket = n_buffer->getFd();
+ std::vector<int> session_socket_fds {session_socket};
+ auto fail_fn_1 = std::bind(ZygoteFailure, env, static_cast<const char*>(nullptr),
+ static_cast<jstring>(managed_nice_name), _1);
+ // This binds to the nice name address; the actual names are updated by isSimpleForkCommand:
+ auto fail_fn_n = std::bind(ZygoteFailure, env, n_buffer->niceNameAddr(),
+ static_cast<jstring>(nullptr), _1);
+ auto fail_fn_z = std::bind(ZygoteFailure, env, "zygote", nullptr, _1);
+
+ struct pollfd fd_structs[2];
+ static const int ZYGOTE_IDX = 0;
+ static const int SESSION_IDX = 1;
+ fd_structs[ZYGOTE_IDX].fd = zygote_socket_fd;
+ fd_structs[ZYGOTE_IDX].events = POLLIN;
+ fd_structs[SESSION_IDX].fd = session_socket;
+ fd_structs[SESSION_IDX].events = POLLIN;
+
+ struct timeval timeout;
+ socklen_t timeout_size = sizeof timeout;
+ if (getsockopt(session_socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, &timeout_size) != 0) {
+ fail_fn_z("Failed to retrieve session socket timeout");
+ }
+
+ struct ucred credentials;
+ socklen_t cred_size = sizeof credentials;
+ if (getsockopt(n_buffer->getFd(), SOL_SOCKET, SO_PEERCRED, &credentials, &cred_size) == -1
+ || cred_size != sizeof credentials) {
+ fail_fn_1("ForkMany failed to get initial credentials, %s", strerror(errno));
+ }
+
+ bool first_time = true;
+ do {
+ if (credentials.uid != expected_uid) {
+ return JNI_FALSE;
+ }
+ n_buffer->readAllLines(first_time ? fail_fn_1 : fail_fn_n);
+ n_buffer->reset();
+ int pid = zygote::forkApp(env, /* no pipe FDs */ -1, -1, session_socket_fds,
+ /*args_known=*/ true, /*is_priority_fork=*/ true,
+ /*purge=*/ first_time);
+ if (pid == 0) {
+ return JNI_TRUE;
+ }
+ // We're in the parent. Write big-endian pid, followed by a boolean.
+ char pid_buf[5];
+ int tmp_pid = pid;
+ for (int i = 3; i >= 0; --i) {
+ pid_buf[i] = tmp_pid & 0xff;
+ tmp_pid >>= 8;
+ }
+ pid_buf[4] = 0; // Process is not wrapped.
+ int res = write(session_socket, pid_buf, 5);
+ if (res != 5) {
+ if (res == -1) {
+ (first_time ? fail_fn_1 : fail_fn_n)
+ (CREATE_ERROR("Pid write error %d: %s", errno, strerror(errno)));
+ } else {
+ (first_time ? fail_fn_1 : fail_fn_n)
+ (CREATE_ERROR("Write unexpectedly returned short: %d < 5", res));
+ }
+ }
+ // Clear buffer and get count from next command.
+ n_buffer->clear();
+ for (;;) {
+ // Poll isn't strictly necessary for now. But without it, disconnect is hard to detect.
+ int poll_res = TEMP_FAILURE_RETRY(poll(fd_structs, 2, -1 /* infinite timeout */));
+ if ((fd_structs[SESSION_IDX].revents & POLLIN) != 0) {
+ if (n_buffer->getCount(fail_fn_z) != 0) {
+ break;
+ } // else disconnected;
+ } else if (poll_res == 0 || (fd_structs[ZYGOTE_IDX].revents & POLLIN) == 0) {
+ fail_fn_z(
+ CREATE_ERROR("Poll returned with no descriptors ready! Poll returned %d", poll_res));
+ }
+ // We've now seen either a disconnect or connect request.
+ close(session_socket);
+ int new_fd = accept(zygote_socket_fd, nullptr, nullptr);
+ if (new_fd == -1) {
+ fail_fn_z("Accept(%d) failed: %s", zygote_socket_fd, strerror(errno));
+ }
+ if (new_fd != session_socket) {
+ // Move new_fd back to the old value, so that we don't have to change Java-level data
+ // structures to reflect a change. This implicitly closes the old one.
+ if (dup2(new_fd, session_socket) != session_socket) {
+ fail_fn_z(CREATE_ERROR("Failed to move fd %d to %d: %s",
+ new_fd, session_socket, strerror(errno)));
+ }
+ close(new_fd);
+ }
+ // If we ever return, we effectively reuse the old Java ZygoteConnection.
+ // None of its state needs to change.
+ if (setsockopt(session_socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, timeout_size) != 0) {
+ fail_fn_z(CREATE_ERROR("Failed to set receive timeout for socket %d: %s",
+ session_socket, strerror(errno)));
+ }
+ if (setsockopt(session_socket, SOL_SOCKET, SO_SNDTIMEO, &timeout, timeout_size) != 0) {
+ fail_fn_z(CREATE_ERROR("Failed to set send timeout for socket %d: %s",
+ session_socket, strerror(errno)));
+ }
+ if (getsockopt(session_socket, SOL_SOCKET, SO_PEERCRED, &credentials, &cred_size) == -1) {
+ fail_fn_z(CREATE_ERROR("ForkMany failed to get credentials: %s", strerror(errno)));
+ }
+ if (cred_size != sizeof credentials) {
+ fail_fn_z(CREATE_ERROR("ForkMany credential size = %d, should be %d",
+ cred_size, static_cast<int>(sizeof credentials)));
+ }
+ }
+ first_time = false;
+ } while (n_buffer->isSimpleForkCommand(minUid, fail_fn_n));
+ ALOGW("forkRepeatedly terminated due to non-simple command");
+ n_buffer->logState();
+ n_buffer->reset();
+ return JNI_FALSE;
+}
+
+#define METHOD_NAME(m) com_android_internal_os_ZygoteCommandBuffer_ ## m
+
+static const JNINativeMethod gMethods[] = {
+ {"getNativeBuffer", "(I)J", (void *) METHOD_NAME(getNativeBuffer)},
+ {"freeNativeBuffer", "(J)V", (void *) METHOD_NAME(freeNativeBuffer)},
+ {"insert", "(JLjava/lang/String;)V", (void *) METHOD_NAME(insert)},
+ {"nativeNextArg", "(J)Ljava/lang/String;", (void *) METHOD_NAME(nativeNextArg)},
+ {"nativeReadFullyAndReset", "(J)V", (void *) METHOD_NAME(nativeReadFullyAndReset)},
+ {"nativeGetCount", "(J)I", (void *) METHOD_NAME(nativeGetCount)},
+ {"nativeForkRepeatedly", "(JIIILjava/lang/String;)Z",
+ (void *) METHOD_NAME(nativeForkRepeatedly)},
+};
+
+int register_com_android_internal_os_ZygoteCommandBuffer(JNIEnv* env) {
+ return RegisterMethodsOrDie(env, "com/android/internal/os/ZygoteCommandBuffer", gMethods,
+ NELEM(gMethods));
+}
+
+} // namespace android
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index a89043d..d5f5d28 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1284,15 +1284,15 @@
android:backgroundPermission="android.permission.RECORD_BACKGROUND_AUDIO"
android:protectionLevel="dangerous|instant" />
- <!-- Allows an application to record audio while in the background.
- <p>Protection level: dangerous
- -->
+ <!-- @SystemApi @TestApi Allows an application to record audio while in the background.
+ This permission is not intended to be held by apps.
+ <p>Protection level: internal
+ @hide -->
<permission android:name="android.permission.RECORD_BACKGROUND_AUDIO"
android:permissionGroup="android.permission-group.UNDEFINED"
android:label="@string/permlab_recordBackgroundAudio"
android:description="@string/permdesc_recordBackgroundAudio"
- android:permissionFlags="hardRestricted|installerExemptIgnored"
- android:protectionLevel="dangerous" />
+ android:protectionLevel="internal" />
<!-- ====================================================================== -->
<!-- Permissions for activity recognition -->
@@ -1368,15 +1368,15 @@
android:backgroundPermission="android.permission.BACKGROUND_CAMERA"
android:protectionLevel="dangerous|instant" />
- <!-- Required to be able to access the camera device in the background.
- <p>Protection level: dangerous
- -->
+ <!-- @SystemApi @TestApi Required to be able to access the camera device in the background.
+ This permission is not intended to be held by apps.
+ <p>Protection level: internal
+ @hide -->
<permission android:name="android.permission.BACKGROUND_CAMERA"
android:permissionGroup="android.permission-group.UNDEFINED"
android:label="@string/permlab_backgroundCamera"
android:description="@string/permdesc_backgroundCamera"
- android:permissionFlags="hardRestricted|installerExemptIgnored"
- android:protectionLevel="dangerous" />
+ android:protectionLevel="internal" />
<!-- @SystemApi Required in addition to android.permission.CAMERA to be able to access
system only camera devices.
@@ -3982,17 +3982,6 @@
<permission android:name="com.android.permission.INSTALL_EXISTING_PACKAGES"
android:protectionLevel="signature|privileged" />
- <!-- Allows an application to use the package installer v2 APIs.
- <p>The package installer v2 APIs are still a work in progress and we're
- currently validating they work in all scenarios.
- <p>Not for use by third-party applications.
- TODO(b/152310230): use this permission to protect only Incremental installations
- once the APIs are confirmed to be sufficient.
- @hide
- -->
- <permission android:name="com.android.permission.USE_INSTALLER_V2"
- android:protectionLevel="signature|verifier" />
-
<!-- Allows an application to use System Data Loaders.
<p>Not for use by third-party applications.
@hide
diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml
index d79cb74..b83611bc 100644
--- a/core/res/res/layout/notification_template_material_base.xml
+++ b/core/res/res/layout/notification_template_material_base.xml
@@ -91,26 +91,11 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
- android:layout_marginBottom="@dimen/notification_headerless_margin_minimum"
- android:layout_marginTop="@dimen/notification_headerless_margin_minimum"
+ android:layout_marginBottom="@dimen/notification_headerless_margin_twoline"
+ android:layout_marginTop="@dimen/notification_headerless_margin_twoline"
android:orientation="vertical"
>
- <!--
- This invisible FrameLayout is here as a collapsible padding. Having a layout_weight=1 is
- what causes this view (and it's counterpart at the opposite end) to collapse before the
- actual content views do.
- This pair of 10dp collapsible paddings (plus the 16dp fixed margins) allow us to support
- headerless notifications of 1-3 lines (where each line is 20dp tall) where the 1-line
- variant is 56dp and the 2- and 3-line variants are both 76dp.
- -->
- <FrameLayout
- android:id="@+id/notification_headerless_margin_extra_top"
- android:layout_width="match_parent"
- android:layout_height="@dimen/notification_headerless_margin_extra"
- android:layout_weight="1"
- />
-
<!-- extends ViewGroup -->
<NotificationTopLineView
android:id="@+id/notification_top_line"
@@ -153,12 +138,20 @@
android:orientation="vertical"
>
- <include
- layout="@layout/notification_template_text"
+ <com.android.internal.widget.NotificationVanishingFrameLayout
android:layout_width="match_parent"
android:layout_height="@dimen/notification_headerless_line_height"
- android:layout_marginTop="0dp"
- />
+ >
+ <!-- This is the simplest way to keep this text vertically centered without using
+ gravity="center_vertical" which causes jumpiness in expansion animations. -->
+ <include
+ layout="@layout/notification_template_text"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_text_height"
+ android:layout_gravity="center_vertical"
+ android:layout_marginTop="0dp"
+ />
+ </com.android.internal.widget.NotificationVanishingFrameLayout>
<include
layout="@layout/notification_template_progress"
@@ -168,21 +161,6 @@
</LinearLayout>
- <!--
- This invisible FrameLayout is here as a collapsible padding. Having a layout_weight=1 is
- what causes this view (and it's counterpart at the opposite end) to collapse before the
- actual content views do.
- This pair of 10dp collapsible paddings (plus the 16dp fixed margins) allow us to support
- headerless notifications of 1-3 lines (where each line is 20dp tall) where the 1-line
- variant is 56dp and the 2- and 3-line variants are both 76dp.
- -->
- <FrameLayout
- android:id="@+id/notification_headerless_margin_extra_bottom"
- android:layout_width="match_parent"
- android:layout_height="@dimen/notification_headerless_margin_extra"
- android:layout_weight="1"
- />
-
</LinearLayout>
</com.android.internal.widget.NotificationMaxHeightFrameLayout>
diff --git a/core/res/res/values-night/colors.xml b/core/res/res/values-night/colors.xml
index 29f2b6f..4410e94 100644
--- a/core/res/res/values-night/colors.xml
+++ b/core/res/res/values-night/colors.xml
@@ -26,6 +26,10 @@
<color name="notification_default_color_dark">#ddffffff</color>
+ <color name="notification_primary_text_color_current">@color/notification_primary_text_color_dark</color>
+ <color name="notification_secondary_text_color_current">@color/notification_secondary_text_color_dark</color>
+ <color name="notification_default_color_current">@color/notification_default_color_dark</color>
+
<color name="chooser_row_divider">@color/list_divider_color_dark</color>
<color name="chooser_gradient_background">@color/loading_gradient_background_color_dark</color>
<color name="chooser_gradient_highlight">@color/loading_gradient_highlight_color_dark</color>
diff --git a/core/res/res/values-night/values.xml b/core/res/res/values-night/values.xml
index b1bcf72..3fd82b8 100644
--- a/core/res/res/values-night/values.xml
+++ b/core/res/res/values-night/values.xml
@@ -28,9 +28,4 @@
</style>
<style name="Theme.DeviceDefault.QuickSettings.Dialog" parent="Theme.DeviceDefault.Dialog" />
-
- <style name="TextAppearance.Material.Notification">
- <item name="textColor">@color/notification_secondary_text_color_dark</item>
- <item name="textSize">@dimen/notification_text_size</item>
- </style>
</resources>
\ No newline at end of file
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 59c260c..d79c01f 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -143,6 +143,10 @@
<color name="notification_default_color_dark">@color/primary_text_default_material_light</color>
<color name="notification_default_color_light">#a3202124</color>
+ <color name="notification_primary_text_color_current">@color/notification_primary_text_color_light</color>
+ <color name="notification_secondary_text_color_current">@color/notification_secondary_text_color_light</color>
+ <color name="notification_default_color_current">@color/notification_default_color_light</color>
+
<color name="notification_default_color">#757575</color> <!-- Gray 600 -->
<color name="notification_action_button_text_color">@color/notification_default_color</color>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 695a831..c2b6b99 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -319,26 +319,22 @@
<!-- The top padding for the notification expand button. -->
<dimen name="notification_expand_button_padding_top">1dp</dimen>
- <!-- minimum vertical margin for the headerless notification content, when cap = 60dp -->
- <dimen name="notification_headerless_margin_minimum">8dp</dimen>
+ <!-- Vertical margin for the headerless notification content, when content has 1 line -->
+ <!-- 16 * 2 (margins) + 24 (1 line) = 56 (notification) -->
+ <dimen name="notification_headerless_margin_oneline">16dp</dimen>
- <!-- extra vertical margin for the headerless notification content, when cap = 60dp -->
- <dimen name="notification_headerless_margin_extra">10dp</dimen>
-
- <!-- minimum vertical margin for the headerless notification content, when cap = 48dp -->
- <dimen name="notification_headerless_margin_constrained_minimum">14dp</dimen>
-
- <!-- extra vertical margin for the headerless notification content, when cap = 48dp -->
- <dimen name="notification_headerless_margin_constrained_extra">4dp</dimen>
+ <!-- Vertical margin for the headerless notification content, when content has 2 lines -->
+ <!-- 20 * 2 (margins) + 24 * 2 (2 lines) = 88 (notification) -->
+ <dimen name="notification_headerless_margin_twoline">20dp</dimen>
<!-- The height of each of the 1 or 2 lines in the headerless notification template -->
- <dimen name="notification_headerless_line_height">20sp</dimen>
+ <dimen name="notification_headerless_line_height">24dp</dimen>
<!-- vertical margin for the headerless notification content -->
<dimen name="notification_headerless_min_height">56dp</dimen>
<!-- Height of a small notification in the status bar -->
- <dimen name="notification_min_height">76dp</dimen>
+ <dimen name="notification_min_height">88dp</dimen>
<!-- The width of the big icons in notifications. -->
<dimen name="notification_large_icon_width">64dp</dimen>
@@ -738,10 +734,11 @@
<!-- The maximum width of a image in a media notification. The images will be reduced to that width in case they are bigger.-->
<dimen name="notification_media_image_max_width">280dp</dimen>
<!-- The size of the right icon -->
- <dimen name="notification_right_icon_size">52dp</dimen>
+ <dimen name="notification_right_icon_size">48dp</dimen>
<!-- The top and bottom margin of the right icon in the normal notification states -->
- <dimen name="notification_right_icon_headerless_margin">12dp</dimen>
+ <dimen name="notification_right_icon_headerless_margin">20dp</dimen>
<!-- The top margin of the right icon in the "big" notification states -->
+ <!-- TODO(b/181048615): Move the large icon below the expander in big states -->
<dimen name="notification_right_icon_big_margin_top">16dp</dimen>
<!-- The size of the left icon -->
<dimen name="notification_left_icon_size">@dimen/notification_icon_circle_size</dimen>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 576c44d..9eb2f14 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4538,7 +4538,7 @@
<string name="color_correction_feature_name">Color Correction</string>
<!-- Title of Reduce Brightness feature, shown in the warning dialog about the accessibility shortcut. [CHAR LIMIT=none] -->
- <string name="reduce_bright_colors_feature_name">Reduce Brightness</string>
+ <string name="reduce_bright_colors_feature_name">Reduce brightness</string>
<!-- Text in toast to alert the user that the accessibility shortcut turned on an accessibility service. [CHAR LIMIT=none] -->
<string name="accessibility_shortcut_enabling_service">Held volume keys. <xliff:g id="service_name" example="TalkBack">%1$s</xliff:g> turned on.</string>
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 158289a..3c4a5d4 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -466,18 +466,20 @@
</style>
<style name="TextAppearance.Material.Notification">
- <item name="textColor">@color/notification_secondary_text_color_light</item>
+ <item name="textColor">@color/notification_secondary_text_color_current</item>
<item name="textSize">@dimen/notification_text_size</item>
</style>
<style name="TextAppearance.Material.Notification.Reply" />
<style name="TextAppearance.Material.Notification.Title">
+ <item name="textColor">@color/notification_primary_text_color_current</item>
<item name="fontFamily">sans-serif-medium</item>
<item name="textSize">@dimen/notification_title_text_size</item>
</style>
<style name="TextAppearance.Material.Notification.BigTitle">
+ <item name="textColor">@color/notification_primary_text_color_current</item>
<item name="fontFamily">sans-serif-medium</item>
<item name="textSize">@dimen/notification_big_title_text_size</item>
</style>
@@ -492,9 +494,8 @@
<style name="TextAppearance.Material.Notification.Time" parent="TextAppearance.Material.Notification.Info" />
- <style name="TextAppearance.Material.Notification.Emphasis">
- <item name="textColor">#66000000</item>
- </style>
+ <!-- unused; keep identical to parent -->
+ <style name="TextAppearance.Material.Notification.Emphasis"/>
<style name="TextAppearance.Material.Notification.Conversation.AppName" parent="TextAppearance.Material.Notification.Title">
<item name="android:textSize">16sp</item>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7ad05de..a4dec22 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2895,8 +2895,6 @@
<java-symbol type="id" name="alternate_expand_target" />
<java-symbol type="id" name="notification_header" />
<java-symbol type="id" name="notification_top_line" />
- <java-symbol type="id" name="notification_headerless_margin_extra_top" />
- <java-symbol type="id" name="notification_headerless_margin_extra_bottom" />
<java-symbol type="id" name="time_divider" />
<java-symbol type="id" name="header_text_divider" />
<java-symbol type="id" name="header_text_secondary_divider" />
@@ -2918,8 +2916,8 @@
<java-symbol type="dimen" name="notification_header_icon_size" />
<java-symbol type="dimen" name="notification_header_app_name_margin_start" />
<java-symbol type="dimen" name="notification_header_separating_margin" />
- <java-symbol type="dimen" name="notification_headerless_margin_constrained_minimum" />
- <java-symbol type="dimen" name="notification_headerless_margin_constrained_extra" />
+ <java-symbol type="dimen" name="notification_headerless_margin_oneline" />
+ <java-symbol type="dimen" name="notification_headerless_margin_twoline" />
<java-symbol type="string" name="default_notification_channel_label" />
<java-symbol type="string" name="importance_from_user" />
<java-symbol type="string" name="importance_from_person" />
diff --git a/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java b/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java
index 1573c19..7cb6804 100644
--- a/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java
+++ b/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java
@@ -37,6 +37,7 @@
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Parcel;
+import android.os.UserHandle;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -124,13 +125,13 @@
}
@Test
- public void testUid() {
+ public void testUserHandle() {
PeopleSpaceTile tile = new PeopleSpaceTile
.Builder(new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps)
- .setUid(42)
+ .setUserHandle(new UserHandle(0))
.build();
- assertThat(tile.getUid()).isEqualTo(42);
+ assertThat(tile.getUserHandle()).isEqualTo(new UserHandle(0));
}
@Test
@@ -227,7 +228,7 @@
.setUserName("name")
.setUserIcon(mIcon)
.setContactUri(Uri.parse("contact"))
- .setUid(42)
+ .setUserHandle(new UserHandle(1))
.setPackageName("package.name")
.setLastInteractionTimestamp(7L)
.setIsImportantConversation(true)
@@ -246,7 +247,7 @@
assertThat(readTile.getUserName()).isEqualTo(tile.getUserName());
assertThat(readTile.getUserIcon().toString()).isEqualTo(tile.getUserIcon().toString());
assertThat(readTile.getContactUri()).isEqualTo(tile.getContactUri());
- assertThat(readTile.getUid()).isEqualTo(tile.getUid());
+ assertThat(readTile.getUserHandle()).isEqualTo(tile.getUserHandle());
assertThat(readTile.getPackageName()).isEqualTo(tile.getPackageName());
assertThat(readTile.getLastInteractionTimestamp()).isEqualTo(
tile.getLastInteractionTimestamp());
diff --git a/core/tests/coretests/src/android/view/BlurAggregatorTest.java b/core/tests/coretests/src/android/view/BlurAggregatorTest.java
new file mode 100644
index 0000000..b01f275
--- /dev/null
+++ b/core/tests/coretests/src/android/view/BlurAggregatorTest.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertNotNull;
+import static junit.framework.TestCase.assertNull;
+import static junit.framework.TestCase.assertTrue;
+
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.internal.graphics.drawable.BackgroundBlurDrawable;
+import com.android.internal.graphics.drawable.BackgroundBlurDrawable.Aggregator;
+import com.android.internal.graphics.drawable.BackgroundBlurDrawable.BlurRegion;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class BlurAggregatorTest {
+ private static final int TEST_BLUR_RADIUS = 30;
+ private static final int TEST_FRAME_NUMBER = 1;
+
+ private Context mContext;
+
+ private Aggregator mAggregator;
+ private BackgroundBlurDrawable mDrawable;
+
+ private ViewRootImpl mViewRoot;
+
+ @Before
+ public void setUp() {
+ mContext = getInstrumentation().getTargetContext();
+ getInstrumentation().runOnMainSync(() -> {
+ mViewRoot = new ViewRootImpl(mContext, mContext.getDisplayNoVerify());
+ });
+ mAggregator = new Aggregator(mViewRoot);
+ mDrawable = createTestBackgroundBlurDrawable();
+ }
+
+ private BackgroundBlurDrawable createTestBackgroundBlurDrawable() {
+ final BackgroundBlurDrawable drawable = mAggregator.createBackgroundBlurDrawable(mContext);
+ drawable.setBlurRadius(TEST_BLUR_RADIUS);
+ final boolean hasUpdates = mAggregator.hasUpdates();
+ final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT();
+ mAggregator.getBlurRegionsToDispatchToSf(TEST_FRAME_NUMBER, blurRegions, hasUpdates);
+ return drawable;
+ }
+
+ @Test
+ public void testBlurRadiusUpdatePropagatesToRenderThreadIfNeeded() {
+ mDrawable.setBlurRadius(TEST_BLUR_RADIUS);
+ assertFalse(mAggregator.hasUpdates());
+
+ mDrawable.setBlurRadius(0);
+ assertTrue(mAggregator.hasUpdates());
+ BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT();
+ assertEquals(0, blurRegions.length);
+ assertFalse(mAggregator.hasUpdates());
+
+ mDrawable.setBlurRadius(TEST_BLUR_RADIUS);
+ assertTrue(mAggregator.hasUpdates());
+ blurRegions = mAggregator.getBlurRegionsCopyForRT();
+ assertEquals(1, blurRegions.length);
+ assertEquals(TEST_BLUR_RADIUS, blurRegions[0].blurRadius);
+ assertFalse(mAggregator.hasUpdates());
+
+ }
+
+ @Test
+ public void testAlphaUpdatePropagatesToRenderThreadIfNeeded() {
+ mDrawable.setAlpha(20);
+ assertTrue(mAggregator.hasUpdates());
+ BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT();
+ assertEquals(1, blurRegions.length);
+ assertEquals(20 / 255f, blurRegions[0].alpha);
+ assertFalse(mAggregator.hasUpdates());
+
+ mDrawable.setAlpha(20);
+ assertFalse(mAggregator.hasUpdates());
+
+ mDrawable.setAlpha(0);
+ assertTrue(mAggregator.hasUpdates());
+ blurRegions = mAggregator.getBlurRegionsCopyForRT();
+ assertEquals(0, blurRegions.length);
+ assertFalse(mAggregator.hasUpdates());
+ }
+
+ @Test
+ public void testCornerRadiusUpdatePropagatesToRenderThreadIfNeeded() {
+ mDrawable.setCornerRadius(1f, 2f, 3f, 4f);
+ assertTrue(mAggregator.hasUpdates());
+ final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT();
+ assertEquals(1, blurRegions.length);
+ assertEquals(1f, blurRegions[0].cornerRadiusTL);
+ assertEquals(2f, blurRegions[0].cornerRadiusTR);
+ assertEquals(3f, blurRegions[0].cornerRadiusBL);
+ assertEquals(4f, blurRegions[0].cornerRadiusBR);
+ assertFalse(mAggregator.hasUpdates());
+ }
+
+ @Test
+ public void testVisibleUpdatePropagatesToRenderThreadIfNeeded() {
+ mDrawable.setVisible(false, /* restart= */false);
+ assertTrue(mAggregator.hasUpdates());
+ BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT();
+ assertEquals(0, blurRegions.length);
+ assertFalse(mAggregator.hasUpdates());
+
+ mDrawable.setVisible(true, /* restart= */ false);
+ assertTrue(mAggregator.hasUpdates());
+ blurRegions = mAggregator.getBlurRegionsCopyForRT();
+ assertEquals(1, blurRegions.length);
+ assertEquals(TEST_BLUR_RADIUS, blurRegions[0].blurRadius);
+ assertFalse(mAggregator.hasUpdates());
+ }
+
+ @Test
+ public void testBlurRegionCopyForRtIsSameIfNoUiUpdates() {
+ mDrawable.setBlurRadius(30);
+ BlurRegion[] blurRegions1 = mAggregator.getBlurRegionsCopyForRT();
+ assertEquals(1, blurRegions1.length);
+ assertEquals(30, blurRegions1[0].blurRadius);
+
+ BlurRegion[] blurRegions2 = mAggregator.getBlurRegionsCopyForRT();
+ assertEquals(blurRegions1, blurRegions2);
+ }
+
+ @Test
+ public void testPositionUpdateAppearsInBlurRegion() {
+ BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT();
+ assertEquals(1, blurRegions.length);
+
+ mDrawable.mPositionUpdateListener.positionChanged(TEST_FRAME_NUMBER, 1, 2, 3, 4);
+ mAggregator.getBlurRegionsToDispatchToSf(TEST_FRAME_NUMBER, blurRegions,
+ mAggregator.hasUpdates());
+ assertEquals(1, blurRegions[0].rect.left);
+ assertEquals(2, blurRegions[0].rect.top);
+ assertEquals(3, blurRegions[0].rect.right);
+ assertEquals(4, blurRegions[0].rect.bottom);
+ }
+
+ @Test
+ public void testNoBlurRegionsDispatchedWhenNoUpdates() {
+ final boolean hasUpdates = mAggregator.hasUpdates();
+ assertFalse(hasUpdates);
+ final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT();
+ assertEquals(1, blurRegions.length);
+
+ float[][] blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf(
+ TEST_FRAME_NUMBER, blurRegions, hasUpdates);
+ assertNull(blurRegionsForSf);
+ }
+
+ @Test
+ public void testBlurRegionDispatchedIfOnlyDrawableUpdated() {
+ mDrawable.setBlurRadius(50);
+ final boolean hasUpdates = mAggregator.hasUpdates();
+ assertTrue(hasUpdates);
+ final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT();
+ assertEquals(1, blurRegions.length);
+
+ float[][] blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf(
+ TEST_FRAME_NUMBER, blurRegions, hasUpdates);
+ assertNotNull(blurRegionsForSf);
+ assertEquals(1, blurRegionsForSf.length);
+ assertEquals(50f, blurRegionsForSf[0][0]);
+ }
+
+ @Test
+ public void testBlurRegionDispatchedIfOnlyPositionUpdated() {
+ final boolean hasUpdates = mAggregator.hasUpdates();
+ assertFalse(hasUpdates);
+ final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT();
+ assertEquals(1, blurRegions.length);
+
+ mDrawable.mPositionUpdateListener.positionChanged(TEST_FRAME_NUMBER, 1, 2, 3, 4);
+ float[][] blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf(
+ TEST_FRAME_NUMBER, blurRegions, hasUpdates);
+ assertNotNull(blurRegionsForSf);
+ assertEquals(1, blurRegionsForSf.length);
+ assertEquals((float) TEST_BLUR_RADIUS, blurRegionsForSf[0][0]);
+ assertEquals(1f, blurRegionsForSf[0][2]);
+ assertEquals(2f, blurRegionsForSf[0][3]);
+ assertEquals(3f, blurRegionsForSf[0][4]);
+ assertEquals(4f, blurRegionsForSf[0][5]);
+ }
+
+ @Test
+ public void testPositionUpdateIsAppliedInNextFrameIfMissed() {
+ final boolean hasUpdates = mAggregator.hasUpdates();
+ assertFalse(hasUpdates);
+ final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT();
+ assertEquals(1, blurRegions.length);
+
+ mDrawable.mPositionUpdateListener.positionChanged(TEST_FRAME_NUMBER, 1, 2, 3, 4);
+ float[][] blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf(
+ TEST_FRAME_NUMBER + 1, blurRegions, hasUpdates);
+ assertNotNull(blurRegionsForSf);
+ assertEquals(1, blurRegionsForSf.length);
+ assertEquals((float) TEST_BLUR_RADIUS, blurRegionsForSf[0][0]);
+ assertEquals(1f, blurRegionsForSf[0][2]);
+ assertEquals(2f, blurRegionsForSf[0][3]);
+ assertEquals(3f, blurRegionsForSf[0][4]);
+ assertEquals(4f, blurRegionsForSf[0][5]);
+ }
+
+ @Test
+ public void testMultipleDrawablesDispatchedToSfIfOneIsUpdated() {
+ final BackgroundBlurDrawable drawable2 = createTestBackgroundBlurDrawable();
+ drawable2.setBlurRadius(50);
+ final boolean hasUpdates = mAggregator.hasUpdates();
+ assertTrue(hasUpdates);
+ final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT();
+ assertEquals(2, blurRegions.length);
+
+ // Check that an update in one of the drawables triggers a dispatch of all blur regions
+ float[][] blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf(
+ TEST_FRAME_NUMBER, blurRegions, hasUpdates);
+ assertNotNull(blurRegionsForSf);
+ assertEquals(2, blurRegionsForSf.length);
+
+ // Check that the Aggregator deleted all position updates for frame TEST_FRAME_NUMBER
+ blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf(
+ TEST_FRAME_NUMBER, blurRegions, /* hasUiUpdates= */ false);
+ assertNull(blurRegionsForSf);
+
+ // Check that a position update triggers a dispatch of all blur regions
+ drawable2.mPositionUpdateListener.positionChanged(TEST_FRAME_NUMBER, 1, 2, 3, 4);
+ blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf(
+ TEST_FRAME_NUMBER + 1, blurRegions, hasUpdates);
+ assertNotNull(blurRegionsForSf);
+ assertEquals(2, blurRegionsForSf.length);
+ }
+
+ @Test
+ public void testUiThreadUpdatesDoNotChangeStateOnRenderThread() {
+ // Updates for frame N
+ mDrawable.setBlurRadius(50);
+ mDrawable.setCornerRadius(1, 2, 3, 4);
+ mDrawable.setAlpha(20);
+
+ final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT();
+ assertEquals(1, blurRegions.length);
+ assertEquals(50, blurRegions[0].blurRadius);
+ assertEquals(20 / 255f, blurRegions[0].alpha);
+ assertEquals(1f, blurRegions[0].cornerRadiusTL);
+ assertEquals(2f, blurRegions[0].cornerRadiusTR);
+ assertEquals(3f, blurRegions[0].cornerRadiusBL);
+ assertEquals(4f, blurRegions[0].cornerRadiusBR);
+
+ // Updates for frame N+1
+ mDrawable.setBlurRadius(60);
+ mDrawable.setCornerRadius(10, 20, 30, 40);
+ mDrawable.setAlpha(40);
+
+ // Assert state for frame N is untouched
+ assertEquals(50, blurRegions[0].blurRadius);
+ assertEquals(20 / 255f, blurRegions[0].alpha);
+ assertEquals(1f, blurRegions[0].cornerRadiusTL);
+ assertEquals(2f, blurRegions[0].cornerRadiusTR);
+ assertEquals(3f, blurRegions[0].cornerRadiusBL);
+ assertEquals(4f, blurRegions[0].cornerRadiusBR);
+ }
+
+ @Test
+ public void testPositionUpdatesForFutureFramesAreNotAppliedForCurrentFrame() {
+ final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT();
+
+ mDrawable.mPositionUpdateListener.positionChanged(TEST_FRAME_NUMBER, 1, 2, 3, 4);
+ mDrawable.mPositionUpdateListener.positionChanged(TEST_FRAME_NUMBER + 1, 5, 6, 7, 8);
+
+ final float[][] blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf(
+ TEST_FRAME_NUMBER, blurRegions, /* hasUiUpdates= */ false);
+ assertNotNull(blurRegionsForSf);
+ assertEquals(1, blurRegionsForSf.length);
+ // Assert state for first frame is not affected by update for second frame
+ assertEquals((float) TEST_BLUR_RADIUS, blurRegionsForSf[0][0]);
+ assertEquals(1f, blurRegionsForSf[0][2]);
+ assertEquals(2f, blurRegionsForSf[0][3]);
+ assertEquals(3f, blurRegionsForSf[0][4]);
+ assertEquals(4f, blurRegionsForSf[0][5]);
+
+ final float[][] blurRegionsForSfForNextFrame = mAggregator.getBlurRegionsToDispatchToSf(
+ TEST_FRAME_NUMBER + 1, blurRegions, /* hasUiUpdates= */ false);
+ assertNotNull(blurRegionsForSfForNextFrame);
+ assertEquals(1, blurRegionsForSfForNextFrame.length);
+ // Assert second frame updates are applied normally
+ assertEquals((float) TEST_BLUR_RADIUS, blurRegionsForSfForNextFrame[0][0]);
+ assertEquals(5f, blurRegionsForSfForNextFrame[0][2]);
+ assertEquals(6f, blurRegionsForSfForNextFrame[0][3]);
+ assertEquals(7f, blurRegionsForSfForNextFrame[0][4]);
+ assertEquals(8f, blurRegionsForSfForNextFrame[0][5]);
+ }
+
+}
diff --git a/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java b/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java
index b9cf1e4..516fb76 100644
--- a/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java
+++ b/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java
@@ -16,16 +16,12 @@
package android.view;
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
import static androidx.test.InstrumentationRegistry.getTargetContext;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -35,6 +31,7 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Handler;
+import android.os.ICancellationSignal;
import androidx.test.runner.AndroidJUnit4;
@@ -42,9 +39,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
-import org.mockito.stubbing.Answer;
/**
* Tests of {@link ScrollCaptureConnection}.
@@ -56,261 +51,138 @@
private final Point mPositionInWindow = new Point(1, 2);
private final Rect mLocalVisibleRect = new Rect(2, 3, 4, 5);
private final Rect mScrollBounds = new Rect(3, 4, 5, 6);
+ private final TestScrollCaptureCallback mCallback = new TestScrollCaptureCallback();
+
+ private ScrollCaptureTarget mTarget;
+ private ScrollCaptureConnection mConnection;
private Handler mHandler;
- private ScrollCaptureTarget mTarget1;
@Mock
private Surface mSurface;
@Mock
- private IScrollCaptureCallbacks mConnectionCallbacks;
+ private IScrollCaptureCallbacks mRemote;
@Mock
- private View mMockView1;
- @Mock
- private ScrollCaptureCallback mCallback1;
+ private View mView;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mHandler = new Handler(getTargetContext().getMainLooper());
+ when(mSurface.isValid()).thenReturn(true);
+ when(mView.getScrollCaptureHint()).thenReturn(View.SCROLL_CAPTURE_HINT_INCLUDE);
- when(mMockView1.getHandler()).thenReturn(mHandler);
- when(mMockView1.getScrollCaptureHint()).thenReturn(View.SCROLL_CAPTURE_HINT_INCLUDE);
-
- mTarget1 = new ScrollCaptureTarget(
- mMockView1, mLocalVisibleRect, mPositionInWindow, mCallback1);
- mTarget1.setScrollBounds(mScrollBounds);
- }
-
- /** Test the DelayedAction timeout helper class works as expected. */
- @Test
- public void testDelayedAction() {
- Runnable action = Mockito.mock(Runnable.class);
- ScrollCaptureConnection.DelayedAction delayed =
- new ScrollCaptureConnection.DelayedAction(mHandler, 100, action);
- try {
- Thread.sleep(200);
- } catch (InterruptedException ex) {
- /* ignore */
- }
- getInstrumentation().waitForIdleSync();
- assertFalse(delayed.cancel());
- assertFalse(delayed.timeoutNow());
- verify(action, times(1)).run();
- }
-
- /** Test the DelayedAction cancel() */
- @Test
- public void testDelayedAction_cancel() {
- Runnable action = Mockito.mock(Runnable.class);
- ScrollCaptureConnection.DelayedAction delayed =
- new ScrollCaptureConnection.DelayedAction(mHandler, 100, action);
- try {
- Thread.sleep(50);
- } catch (InterruptedException ex) {
- /* ignore */
- }
- assertTrue(delayed.cancel());
- assertFalse(delayed.timeoutNow());
- try {
- Thread.sleep(200);
- } catch (InterruptedException ex) {
- /* ignore */
- }
- getInstrumentation().waitForIdleSync();
- verify(action, never()).run();
- }
-
- /** Test the DelayedAction timeoutNow() - for testing only */
- @Test
- public void testDelayedAction_timeoutNow() {
- Runnable action = Mockito.mock(Runnable.class);
- ScrollCaptureConnection.DelayedAction delayed =
- new ScrollCaptureConnection.DelayedAction(mHandler, 100, action);
- try {
- Thread.sleep(50);
- } catch (InterruptedException ex) {
- /* ignore */
- }
- assertTrue(delayed.timeoutNow());
- assertFalse(delayed.cancel());
- getInstrumentation().waitForIdleSync();
- verify(action, times(1)).run();
+ mTarget = new ScrollCaptureTarget(mView, mLocalVisibleRect, mPositionInWindow, mCallback);
+ mTarget.setScrollBounds(mScrollBounds);
+ mConnection = new ScrollCaptureConnection(Runnable::run, mTarget, mRemote);
}
/** Test creating a client with valid info */
@Test
public void testConstruction() {
- new ScrollCaptureConnection(mTarget1, mConnectionCallbacks);
+ ScrollCaptureTarget target = new ScrollCaptureTarget(
+ mView, mLocalVisibleRect, mPositionInWindow, mCallback);
+ target.setScrollBounds(new Rect(1, 2, 3, 4));
+ new ScrollCaptureConnection(Runnable::run, target, mRemote);
}
/** Test creating a client fails if arguments are not valid. */
@Test
public void testConstruction_requiresScrollBounds() {
try {
- mTarget1.setScrollBounds(null);
- new ScrollCaptureConnection(mTarget1, mConnectionCallbacks);
+ mTarget.setScrollBounds(null);
+ new ScrollCaptureConnection(Runnable::run, mTarget, mRemote);
fail("An exception was expected.");
} catch (RuntimeException ex) {
// Ignore, expected.
}
}
- @SuppressWarnings("SameParameterValue")
- private static Answer<Void> runRunnable(int arg) {
- return invocation -> {
- Runnable r = invocation.getArgument(arg);
- r.run();
- return null;
- };
- }
-
- @SuppressWarnings("SameParameterValue")
- private static Answer<Void> reportBufferSent(int sessionArg, long frameNum, Rect capturedArea) {
- return invocation -> {
- ScrollCaptureSession session = invocation.getArgument(sessionArg);
- session.notifyBufferSent(frameNum, capturedArea);
- return null;
- };
- }
-
/** @see ScrollCaptureConnection#startCapture(Surface) */
@Test
public void testStartCapture() throws Exception {
- final ScrollCaptureConnection connection = new ScrollCaptureConnection(mTarget1,
- mConnectionCallbacks);
+ mConnection.startCapture(mSurface);
- // Have the session start accepted immediately
- doAnswer(runRunnable(1)).when(mCallback1)
- .onScrollCaptureStart(any(ScrollCaptureSession.class), any(Runnable.class));
- connection.startCapture(mSurface);
- getInstrumentation().waitForIdleSync();
+ mCallback.completeStartRequest();
+ assertTrue(mConnection.isStarted());
- verify(mCallback1, times(1))
- .onScrollCaptureStart(any(ScrollCaptureSession.class), any(Runnable.class));
- verify(mConnectionCallbacks, times(1)).onCaptureStarted();
- verifyNoMoreInteractions(mConnectionCallbacks);
+ verify(mRemote, times(1)).onCaptureStarted();
+ verifyNoMoreInteractions(mRemote);
}
@Test
- public void testStartCaptureTimeout() throws Exception {
- final ScrollCaptureConnection connection = new ScrollCaptureConnection(mTarget1,
- mConnectionCallbacks);
- connection.startCapture(mSurface);
+ public void testStartCapture_cancellation() throws Exception {
+ ICancellationSignal signal = mConnection.startCapture(mSurface);
+ signal.cancel();
- // Force timeout to fire
- connection.getTimeoutAction().timeoutNow();
+ mCallback.completeStartRequest();
+ assertFalse(mConnection.isStarted());
- getInstrumentation().waitForIdleSync();
- verify(mCallback1, times(1)).onScrollCaptureEnd(any(Runnable.class));
- }
-
- private void startCapture(ScrollCaptureConnection connection) throws Exception {
- doAnswer(runRunnable(1)).when(mCallback1)
- .onScrollCaptureStart(any(ScrollCaptureSession.class), any(Runnable.class));
- connection.startCapture(mSurface);
- getInstrumentation().waitForIdleSync();
- reset(mCallback1, mConnectionCallbacks);
+ verifyNoMoreInteractions(mRemote);
}
/** @see ScrollCaptureConnection#requestImage(Rect) */
@Test
public void testRequestImage() throws Exception {
- final ScrollCaptureConnection connection = new ScrollCaptureConnection(mTarget1,
- mConnectionCallbacks);
- startCapture(connection);
+ mConnection.startCapture(mSurface);
+ mCallback.completeStartRequest();
+ reset(mRemote);
- // Stub the callback to complete the request immediately
- doAnswer(reportBufferSent(/* sessionArg */ 0, /* frameNum */ 1L, new Rect(1, 2, 3, 4)))
- .when(mCallback1)
- .onScrollCaptureImageRequest(any(ScrollCaptureSession.class), any(Rect.class));
+ mConnection.requestImage(new Rect(1, 2, 3, 4));
+ mCallback.completeImageRequest(new Rect(1, 2, 3, 4));
- // Make the inbound binder call
- connection.requestImage(new Rect(1, 2, 3, 4));
-
- // Wait for handler thread dispatch
- getInstrumentation().waitForIdleSync();
- verify(mCallback1, times(1)).onScrollCaptureImageRequest(
- any(ScrollCaptureSession.class), eq(new Rect(1, 2, 3, 4)));
-
- // Wait for binder thread dispatch
- getInstrumentation().waitForIdleSync();
- verify(mConnectionCallbacks, times(1))
- .onCaptureBufferSent(eq(1L), eq(new Rect(1, 2, 3, 4)));
-
- verifyNoMoreInteractions(mCallback1, mConnectionCallbacks);
+ verify(mRemote, times(1))
+ .onImageRequestCompleted(eq(0), eq(new Rect(1, 2, 3, 4)));
+ verifyNoMoreInteractions(mRemote);
}
@Test
- public void testRequestImageTimeout() throws Exception {
- final ScrollCaptureConnection connection = new ScrollCaptureConnection(mTarget1,
- mConnectionCallbacks);
- startCapture(connection);
+ public void testRequestImage_cancellation() throws Exception {
+ mConnection.startCapture(mSurface);
+ mCallback.completeStartRequest();
+ reset(mRemote);
- // Make the inbound binder call
- connection.requestImage(new Rect(1, 2, 3, 4));
+ ICancellationSignal signal = mConnection.requestImage(new Rect(1, 2, 3, 4));
+ signal.cancel();
+ mCallback.completeImageRequest(new Rect(1, 2, 3, 4));
- // Wait for handler thread dispatch
- getInstrumentation().waitForIdleSync();
- verify(mCallback1, times(1)).onScrollCaptureImageRequest(
- any(ScrollCaptureSession.class), eq(new Rect(1, 2, 3, 4)));
-
- // Force timeout to fire
- connection.getTimeoutAction().timeoutNow();
- getInstrumentation().waitForIdleSync();
-
- // (callback not stubbed, does nothing)
- // Timeout triggers request to end capture
- verify(mCallback1, times(1)).onScrollCaptureEnd(any(Runnable.class));
- verifyNoMoreInteractions(mCallback1, mConnectionCallbacks);
+ verifyNoMoreInteractions(mRemote);
}
/** @see ScrollCaptureConnection#endCapture() */
@Test
public void testEndCapture() throws Exception {
- final ScrollCaptureConnection connection = new ScrollCaptureConnection(mTarget1,
- mConnectionCallbacks);
- startCapture(connection);
+ mConnection.startCapture(mSurface);
+ mCallback.completeStartRequest();
+ reset(mRemote);
- // Stub the callback to complete the request immediately
- doAnswer(runRunnable(0))
- .when(mCallback1)
- .onScrollCaptureEnd(any(Runnable.class));
+ mConnection.endCapture();
+ mCallback.completeEndRequest();
- // Make the inbound binder call
- connection.endCapture();
+ // And the reply is sent
+ verify(mRemote, times(1)).onCaptureEnded();
+ verifyNoMoreInteractions(mRemote);
+ }
- // Wait for handler thread dispatch
- getInstrumentation().waitForIdleSync();
- verify(mCallback1, times(1)).onScrollCaptureEnd(any(Runnable.class));
+ /** @see ScrollCaptureConnection#endCapture() */
+ @Test
+ public void testEndCapture_cancellation() throws Exception {
+ mConnection.startCapture(mSurface);
+ mCallback.completeStartRequest();
+ reset(mRemote);
- // Wait for binder thread dispatch
- getInstrumentation().waitForIdleSync();
- verify(mConnectionCallbacks, times(1)).onConnectionClosed();
+ ICancellationSignal signal = mConnection.endCapture();
+ signal.cancel();
+ mCallback.completeEndRequest();
- verifyNoMoreInteractions(mCallback1, mConnectionCallbacks);
+ verifyNoMoreInteractions(mRemote);
}
@Test
- public void testEndCaptureTimeout() throws Exception {
- final ScrollCaptureConnection connection = new ScrollCaptureConnection(mTarget1,
- mConnectionCallbacks);
- startCapture(connection);
-
- // Make the inbound binder call
- connection.endCapture();
-
- // Wait for handler thread dispatch
- getInstrumentation().waitForIdleSync();
- verify(mCallback1, times(1)).onScrollCaptureEnd(any(Runnable.class));
-
- // Force timeout to fire
- connection.getTimeoutAction().timeoutNow();
-
- // Wait for binder thread dispatch
- getInstrumentation().waitForIdleSync();
- verify(mConnectionCallbacks, times(1)).onConnectionClosed();
-
- verifyNoMoreInteractions(mCallback1, mConnectionCallbacks);
+ public void testClose() throws Exception {
+ mConnection.close();
+ assertFalse(mConnection.isConnected());
+ verifyNoMoreInteractions(mRemote);
}
+
}
diff --git a/core/tests/coretests/src/android/view/ScrollCaptureSearchResultsTest.java b/core/tests/coretests/src/android/view/ScrollCaptureSearchResultsTest.java
new file mode 100644
index 0000000..cc229e1
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ScrollCaptureSearchResultsTest.java
@@ -0,0 +1,415 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static androidx.test.InstrumentationRegistry.getTargetContext;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.CancellationSignal;
+import android.os.SystemClock;
+
+import androidx.annotation.NonNull;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+/**
+ * Tests of {@link ScrollCaptureTargetSelector}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class ScrollCaptureSearchResultsTest {
+
+
+ private static final Rect EMPTY_RECT = new Rect();
+ private static final String TAG = "Test";
+
+ private final Executor mDirectExec = Runnable::run;
+ private Executor mBgExec;
+
+ @Before
+ public void setUp() {
+ mBgExec = Executors.newSingleThreadExecutor();
+ }
+
+ @Test
+ public void testNoTargets() {
+ ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(mDirectExec);
+ assertTrue(results.isComplete());
+
+ assertNull("Expected null due to empty queue", results.getTopResult());
+ }
+
+ @Test
+ public void testNoValidTargets() {
+ ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(mDirectExec);
+
+ FakeScrollCaptureCallback callback1 = new FakeScrollCaptureCallback(mDirectExec);
+ callback1.setScrollBounds(EMPTY_RECT);
+ ScrollCaptureTarget target1 = createTarget(callback1, new Rect(20, 30, 40, 50),
+ new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO);
+
+ // Supplies scrollBounds = empty rect
+ FakeScrollCaptureCallback callback2 = new FakeScrollCaptureCallback(mDirectExec);
+ callback2.setScrollBounds(EMPTY_RECT);
+ ScrollCaptureTarget target2 = createTarget(callback2, new Rect(20, 30, 40, 50),
+ new Point(0, 20), View.SCROLL_CAPTURE_HINT_INCLUDE);
+
+ results.addTarget(target1);
+ results.addTarget(target2);
+
+ assertTrue(results.isComplete());
+ assertNull("Expected null due to no valid targets", results.getTopResult());
+ }
+
+ @Test
+ public void testSingleTarget() {
+ ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(mDirectExec);
+ FakeScrollCaptureCallback callback = new FakeScrollCaptureCallback(mDirectExec);
+ ScrollCaptureTarget target = createTarget(callback,
+ new Rect(20, 30, 40, 50), new Point(10, 10),
+ View.SCROLL_CAPTURE_HINT_AUTO);
+ callback.setScrollBounds(new Rect(2, 2, 18, 18));
+
+ results.addTarget(target);
+ assertTrue(results.isComplete());
+
+ ScrollCaptureTarget result = results.getTopResult();
+ assertSame("Excepted the same target as a result", target, result);
+ assertEquals("result has wrong scroll bounds",
+ new Rect(2, 2, 18, 18), result.getScrollBounds());
+ }
+
+ @Test
+ public void testSingleTarget_backgroundThread() throws InterruptedException {
+ FakeScrollCaptureCallback callback1 = new FakeScrollCaptureCallback(mBgExec);
+ ScrollCaptureTarget target1 = createTarget(callback1,
+ new Rect(20, 30, 40, 50), new Point(10, 10),
+ View.SCROLL_CAPTURE_HINT_AUTO);
+ callback1.setDelay(100);
+ callback1.setScrollBounds(new Rect(2, 2, 18, 18));
+
+ ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(mDirectExec);
+ results.addTarget(target1);
+
+ CountDownLatch latch = new CountDownLatch(1);
+ results.setOnCompleteListener(latch::countDown);
+ if (!latch.await(200, TimeUnit.MILLISECONDS)) {
+ fail("onComplete listener was expected");
+ }
+
+ ScrollCaptureTarget result = results.getTopResult();
+ assertSame("Excepted the single target1 as a result", target1, result);
+ assertEquals("Result has wrong scroll bounds",
+ new Rect(2, 2, 18, 18), result.getScrollBounds());
+ }
+
+ @Test
+ public void testRanking() {
+
+ // 1 - Empty
+ FakeScrollCaptureCallback callback1 = new FakeScrollCaptureCallback(mDirectExec);
+ callback1.setScrollBounds(EMPTY_RECT);
+ ViewGroup targetView1 = new FakeView(getTargetContext(), 0, 0, 60, 60, 1);
+ ScrollCaptureTarget target1 = createTargetWithView(targetView1, callback1,
+ new Rect(0, 0, 60, 60), new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO);
+
+ // 2 - 10x10 + HINT_INCLUDE
+ FakeScrollCaptureCallback callback2 = new FakeScrollCaptureCallback(mDirectExec);
+ callback2.setScrollBounds(new Rect(0, 0, 10, 10));
+ ViewGroup targetView2 = new FakeView(getTargetContext(), 0, 0, 60, 60, 2);
+ ScrollCaptureTarget target2 = createTargetWithView(targetView2, callback2,
+ new Rect(0, 0, 60, 60), new Point(0, 0), View.SCROLL_CAPTURE_HINT_INCLUDE);
+
+ // 3 - 20x20 + AUTO
+ FakeScrollCaptureCallback callback3 = new FakeScrollCaptureCallback(mDirectExec);
+ callback3.setScrollBounds(new Rect(0, 0, 20, 20));
+ ViewGroup targetView3 = new FakeView(getTargetContext(), 0, 0, 60, 60, 3);
+ ScrollCaptureTarget target3 = createTargetWithView(targetView3, callback3,
+ new Rect(0, 0, 60, 60), new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO);
+
+ // 4 - 30x30 + AUTO
+ FakeScrollCaptureCallback callback4 = new FakeScrollCaptureCallback(mDirectExec);
+ callback4.setScrollBounds(new Rect(0, 0, 10, 10));
+ ViewGroup targetView4 = new FakeView(getTargetContext(), 0, 0, 60, 60, 4);
+ ScrollCaptureTarget target4 = createTargetWithView(targetView4, callback4,
+ new Rect(0, 0, 60, 60), new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO);
+
+ // 5 - 10x10 + child of #4
+ FakeScrollCaptureCallback callback5 = new FakeScrollCaptureCallback(mDirectExec);
+ callback5.setScrollBounds(new Rect(0, 0, 10, 10));
+ ViewGroup targetView5 = new FakeView(getTargetContext(), 0, 0, 60, 60, 5);
+ ScrollCaptureTarget target5 = createTargetWithView(targetView5, callback5,
+ new Rect(0, 0, 60, 60), new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO);
+ targetView4.addView(targetView5);
+
+ // 6 - 20x20 + child of #4
+ FakeScrollCaptureCallback callback6 = new FakeScrollCaptureCallback(mDirectExec);
+ callback6.setScrollBounds(new Rect(0, 0, 20, 20));
+ ViewGroup targetView6 = new FakeView(getTargetContext(), 0, 0, 60, 60, 6);
+ ScrollCaptureTarget target6 = createTargetWithView(targetView6, callback6,
+ new Rect(0, 0, 60, 60), new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO);
+ targetView4.addView(targetView6);
+
+ ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(mDirectExec);
+ results.addTarget(target1);
+ results.addTarget(target2);
+ results.addTarget(target3);
+ results.addTarget(target4);
+ results.addTarget(target5);
+ results.addTarget(target6);
+ assertTrue(results.isComplete());
+
+ // Verify "top" result
+ assertEquals(target2, results.getTopResult());
+
+ // Verify priority ("best" first)
+ assertThat(results.getTargets())
+ .containsExactly(
+ target2,
+ target6,
+ target5,
+ target4,
+ target3,
+ target1);
+ }
+
+ /**
+ * If a timeout expires, late results are ignored.
+ */
+ @Test
+ public void testTimeout() {
+ ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(mDirectExec);
+
+ // callback 1, 10x10, hint=AUTO, responds after 100ms from bg thread
+ FakeScrollCaptureCallback callback1 = new FakeScrollCaptureCallback(mBgExec);
+ callback1.setScrollBounds(new Rect(5, 5, 15, 15));
+ callback1.setDelay(100);
+ ScrollCaptureTarget target1 = createTarget(
+ callback1, new Rect(20, 30, 40, 50), new Point(10, 10),
+ View.SCROLL_CAPTURE_HINT_AUTO);
+ results.addTarget(target1);
+
+ // callback 2, 20x20, hint=AUTO, responds after 5s from bg thread
+ FakeScrollCaptureCallback callback2 = new FakeScrollCaptureCallback(mBgExec);
+ callback2.setScrollBounds(new Rect(0, 0, 20, 20));
+ callback2.setDelay(1000);
+ ScrollCaptureTarget target2 = createTarget(
+ callback2, new Rect(20, 30, 40, 50), new Point(10, 10),
+ View.SCROLL_CAPTURE_HINT_AUTO);
+ results.addTarget(target2);
+
+ // callback 3, 20x20, hint=INCLUDE, responds after 10s from bg thread
+ FakeScrollCaptureCallback callback3 = new FakeScrollCaptureCallback(mBgExec);
+ callback3.setScrollBounds(new Rect(0, 0, 20, 20));
+ callback3.setDelay(1500);
+ ScrollCaptureTarget target3 = createTarget(
+ callback3, new Rect(20, 30, 40, 50), new Point(10, 10),
+ View.SCROLL_CAPTURE_HINT_INCLUDE);
+ results.addTarget(target3);
+
+ // callback 1 will be received
+ // callback 2 & 3 will be ignored due to timeout
+ SystemClock.sleep(500);
+ results.finish();
+
+ ScrollCaptureTarget result = results.getTopResult();
+ assertSame("Expected target1 as the result, due to timeouts of others", target1, result);
+ assertEquals("callback1 should have been called",
+ 1, callback1.getOnScrollCaptureSearchCount());
+ assertEquals("callback2 should have been called",
+ 1, callback2.getOnScrollCaptureSearchCount());
+ assertEquals("callback3 should have been called",
+ 1, callback3.getOnScrollCaptureSearchCount());
+
+ assertEquals("result has wrong scroll bounds",
+ new Rect(5, 5, 15, 15), result.getScrollBounds());
+ assertNull("target2 should not have been updated",
+ target2.getScrollBounds());
+ assertNull("target3 should not have been updated",
+ target3.getScrollBounds());
+ }
+
+ @Test
+ public void testWithCallbackMultipleReplies() {
+ // Calls response methods 3 times each
+ ScrollCaptureCallback callback1 = new CallbackStub() {
+ @Override
+ public void onScrollCaptureSearch(@NonNull CancellationSignal signal,
+ @NonNull Consumer<Rect> onReady) {
+ onReady.accept(new Rect(1, 2, 3, 4));
+ onReady.accept(new Rect(9, 10, 11, 12));
+ }
+ };
+
+ ScrollCaptureTarget target1 = createTarget(callback1, new Rect(20, 30, 40, 50),
+ new Point(10, 10), View.SCROLL_CAPTURE_HINT_AUTO);
+
+ ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(mDirectExec);
+ results.addTarget(target1);
+ assertTrue(results.isComplete());
+
+ ScrollCaptureTarget result = results.getTopResult();
+ assertSame("Expected target1", target1, result);
+ assertEquals("result has wrong scroll bounds",
+ new Rect(1, 2, 3, 4), result.getScrollBounds());
+ }
+
+ private void setupTargetView(View view, Rect localVisibleRect, int scrollCaptureHint) {
+ view.setScrollCaptureHint(scrollCaptureHint);
+ view.onVisibilityAggregated(true);
+ // Treat any offset as padding, outset localVisibleRect on all sides and use this as
+ // child bounds
+ Rect bounds = new Rect(localVisibleRect);
+ bounds.inset(-bounds.left, -bounds.top, bounds.left, bounds.top);
+ view.layout(bounds.left, bounds.top, bounds.right, bounds.bottom);
+ view.onVisibilityAggregated(true);
+ }
+
+ private ScrollCaptureTarget createTarget(ScrollCaptureCallback callback, Rect localVisibleRect,
+ Point positionInWindow, int scrollCaptureHint) {
+ View mockView = new View(getTargetContext());
+ return createTargetWithView(mockView, callback, localVisibleRect, positionInWindow,
+ scrollCaptureHint);
+ }
+
+ private ScrollCaptureTarget createTargetWithView(View view, ScrollCaptureCallback callback,
+ Rect localVisibleRect, Point positionInWindow, int scrollCaptureHint) {
+ setupTargetView(view, localVisibleRect, scrollCaptureHint);
+ return new ScrollCaptureTarget(view, localVisibleRect, positionInWindow, callback);
+ }
+
+
+ static class FakeView extends ViewGroup implements ViewParent {
+ FakeView(Context context, int l, int t, int r, int b, int id) {
+ super(context);
+ layout(l, t, r, b);
+ setId(id);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ }
+ }
+
+ static class FakeScrollCaptureCallback implements ScrollCaptureCallback {
+ private final Executor mExecutor;
+ private Rect mScrollBounds;
+ private long mDelayMillis;
+ private int mOnScrollCaptureSearchCount;
+ FakeScrollCaptureCallback(Executor executor) {
+ mExecutor = executor;
+ }
+ public int getOnScrollCaptureSearchCount() {
+ return mOnScrollCaptureSearchCount;
+ }
+
+ @Override
+ public void onScrollCaptureSearch(CancellationSignal signal, Consumer<Rect> onReady) {
+ mOnScrollCaptureSearchCount++;
+ run(() -> {
+ Rect b = getScrollBounds();
+ onReady.accept(b);
+ });
+ }
+
+ @Override
+ public void onScrollCaptureStart(ScrollCaptureSession session, CancellationSignal signal,
+ Runnable onReady) {
+ run(onReady);
+ }
+
+ @Override
+ public void onScrollCaptureImageRequest(ScrollCaptureSession session,
+ CancellationSignal signal, Rect captureArea, Consumer<Rect> onReady) {
+ run(() -> onReady.accept(captureArea));
+ }
+
+ @Override
+ public void onScrollCaptureEnd(Runnable onReady) {
+ run(onReady);
+ }
+
+ public void setScrollBounds(@Nullable Rect scrollBounds) {
+ mScrollBounds = scrollBounds;
+ }
+
+ public void setDelay(long delayMillis) {
+ mDelayMillis = delayMillis;
+ }
+
+ protected Rect getScrollBounds() {
+ return mScrollBounds;
+ }
+
+ protected void run(Runnable r) {
+ mExecutor.execute(() -> {
+ delay();
+ r.run();
+ });
+ }
+
+ protected void delay() {
+ if (mDelayMillis > 0) {
+ try {
+ Thread.sleep(mDelayMillis);
+ } catch (InterruptedException e) {
+ // Ignore
+ }
+ }
+ }
+ }
+ static class CallbackStub implements ScrollCaptureCallback {
+ @Override
+ public void onScrollCaptureSearch(@NonNull CancellationSignal signal,
+ @NonNull Consumer<Rect> onReady) {
+ }
+
+ @Override
+ public void onScrollCaptureStart(@NonNull ScrollCaptureSession session,
+ @NonNull CancellationSignal signal, @NonNull Runnable onReady) {
+ }
+
+ @Override
+ public void onScrollCaptureImageRequest(@NonNull ScrollCaptureSession session,
+ @NonNull CancellationSignal signal, @NonNull Rect captureArea,
+ Consumer<Rect> onReady) {
+ }
+
+ @Override
+ public void onScrollCaptureEnd(@NonNull Runnable onReady) {
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/view/ScrollCaptureTargetResolverTest.java b/core/tests/coretests/src/android/view/ScrollCaptureTargetResolverTest.java
deleted file mode 100644
index 8b21b8e..0000000
--- a/core/tests/coretests/src/android/view/ScrollCaptureTargetResolverTest.java
+++ /dev/null
@@ -1,498 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import static androidx.test.InstrumentationRegistry.getTargetContext;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.os.Handler;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.LinkedList;
-import java.util.function.Consumer;
-
-/**
- * Tests of {@link ScrollCaptureTargetResolver}.
- */
-@RunWith(AndroidJUnit4.class)
-public class ScrollCaptureTargetResolverTest {
-
- private static final long TEST_TIMEOUT_MS = 2000;
- private static final long RESOLVER_TIMEOUT_MS = 1000;
-
- private Handler mHandler;
- private TargetConsumer mTargetConsumer;
-
- @Before
- public void setUp() {
- mTargetConsumer = new TargetConsumer();
- mHandler = new Handler(getTargetContext().getMainLooper());
- }
-
- @Test(timeout = TEST_TIMEOUT_MS)
- public void testEmptyQueue() throws InterruptedException {
- ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(new LinkedList<>());
- resolver.start(mHandler, RESOLVER_TIMEOUT_MS, mTargetConsumer);
-
- // Test only
- resolver.waitForResult();
-
- ScrollCaptureTarget result = mTargetConsumer.getLastValue();
- assertNull("Expected null due to empty queue", result);
- }
-
- @Test(timeout = TEST_TIMEOUT_MS)
- public void testNoValidTargets() throws InterruptedException {
- LinkedList<ScrollCaptureTarget> targetQueue = new LinkedList<>();
-
- // Supplies scrollBounds = null
- FakeScrollCaptureCallback callback1 = new FakeScrollCaptureCallback();
- callback1.setScrollBounds(null);
- ScrollCaptureTarget target1 = createTarget(callback1, new Rect(20, 30, 40, 50),
- new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO);
-
- // Supplies scrollBounds = empty rect
- FakeScrollCaptureCallback callback2 = new FakeScrollCaptureCallback();
- callback2.setScrollBounds(new Rect());
- ScrollCaptureTarget target2 = createTarget(callback2, new Rect(20, 30, 40, 50),
- new Point(0, 20), View.SCROLL_CAPTURE_HINT_INCLUDE);
-
- targetQueue.add(target1);
- targetQueue.add(target2);
-
- ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(targetQueue);
- resolver.start(mHandler, RESOLVER_TIMEOUT_MS, mTargetConsumer);
-
- // Test only
- resolver.waitForResult();
-
- ScrollCaptureTarget result = mTargetConsumer.getLastValue();
- assertNull("Expected null due to no valid targets", result);
- }
-
- @Test(timeout = TEST_TIMEOUT_MS)
- public void testSingleTarget() throws InterruptedException {
- FakeScrollCaptureCallback callback = new FakeScrollCaptureCallback();
- ScrollCaptureTarget target = createTarget(callback,
- new Rect(20, 30, 40, 50), new Point(10, 10),
- View.SCROLL_CAPTURE_HINT_AUTO);
- callback.setScrollBounds(new Rect(2, 2, 18, 18));
-
- LinkedList<ScrollCaptureTarget> targetQueue = new LinkedList<>();
- targetQueue.add(target);
- ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(targetQueue);
- resolver.start(mHandler, RESOLVER_TIMEOUT_MS, mTargetConsumer);
-
- // Test only
- resolver.waitForResult();
-
- ScrollCaptureTarget result = mTargetConsumer.getLastValue();
- assertSame("Excepted the same target as a result", target, result);
- assertEquals("result has wrong scroll bounds",
- new Rect(2, 2, 18, 18), result.getScrollBounds());
- }
-
- @Test(timeout = TEST_TIMEOUT_MS)
- public void testSingleTarget_backgroundThread() throws InterruptedException {
- BackgroundTestCallback callback1 = new BackgroundTestCallback();
- ScrollCaptureTarget target1 = createTarget(callback1,
- new Rect(20, 30, 40, 50), new Point(10, 10),
- View.SCROLL_CAPTURE_HINT_AUTO);
- callback1.setDelay(100);
- callback1.setScrollBounds(new Rect(2, 2, 18, 18));
-
- LinkedList<ScrollCaptureTarget> targetQueue = new LinkedList<>();
- targetQueue.add(target1);
-
- ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(targetQueue);
- resolver.start(mHandler, RESOLVER_TIMEOUT_MS, mTargetConsumer);
-
- // Test only
- resolver.waitForResult();
-
- ScrollCaptureTarget result = mTargetConsumer.getLastValue();
- assertSame("Excepted the single target1 as a result", target1, result);
- assertEquals("Result has wrong scroll bounds",
- new Rect(2, 2, 18, 18), result.getScrollBounds());
- }
-
- @Test(timeout = TEST_TIMEOUT_MS)
- public void testPreferNonEmptyBounds() throws InterruptedException {
- LinkedList<ScrollCaptureTarget> targetQueue = new LinkedList<>();
-
- FakeScrollCaptureCallback callback1 = new FakeScrollCaptureCallback();
- callback1.setScrollBounds(new Rect());
- ScrollCaptureTarget target1 = createTarget(callback1, new Rect(20, 30, 40, 50),
- new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO);
-
- FakeScrollCaptureCallback callback2 = new FakeScrollCaptureCallback();
- callback2.setScrollBounds(new Rect(0, 0, 20, 20));
- ScrollCaptureTarget target2 = createTarget(callback2, new Rect(20, 30, 40, 50),
- new Point(0, 20), View.SCROLL_CAPTURE_HINT_INCLUDE);
-
- FakeScrollCaptureCallback callback3 = new FakeScrollCaptureCallback();
- callback3.setScrollBounds(null);
- ScrollCaptureTarget target3 = createTarget(callback3, new Rect(20, 30, 40, 50),
- new Point(0, 40), View.SCROLL_CAPTURE_HINT_AUTO);
-
- targetQueue.add(target1);
- targetQueue.add(target2); // scrollBounds not null or empty()
- targetQueue.add(target3);
-
- ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(targetQueue);
- resolver.start(mHandler, RESOLVER_TIMEOUT_MS, mTargetConsumer);
- resolver.waitForResult();
-
- ScrollCaptureTarget result = mTargetConsumer.getLastValue();
- assertEquals("Expected " + target2 + " as a result", target2, result);
- assertEquals("result has wrong scroll bounds",
- new Rect(0, 0, 20, 20), result.getScrollBounds());
- }
-
- @Test(timeout = TEST_TIMEOUT_MS)
- public void testPreferHintInclude() throws InterruptedException {
- LinkedList<ScrollCaptureTarget> targetQueue = new LinkedList<>();
-
- FakeScrollCaptureCallback callback1 = new FakeScrollCaptureCallback();
- callback1.setScrollBounds(new Rect(0, 0, 20, 20));
- ScrollCaptureTarget target1 = createTarget(callback1, new Rect(20, 30, 40, 50),
- new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO);
-
- FakeScrollCaptureCallback callback2 = new FakeScrollCaptureCallback();
- callback2.setScrollBounds(new Rect(1, 1, 19, 19));
- ScrollCaptureTarget target2 = createTarget(callback2, new Rect(20, 30, 40, 50),
- new Point(0, 20), View.SCROLL_CAPTURE_HINT_INCLUDE);
-
- FakeScrollCaptureCallback callback3 = new FakeScrollCaptureCallback();
- callback3.setScrollBounds(new Rect(2, 2, 18, 18));
- ScrollCaptureTarget target3 = createTarget(callback3, new Rect(20, 30, 40, 50),
- new Point(0, 40), View.SCROLL_CAPTURE_HINT_AUTO);
-
- targetQueue.add(target1);
- targetQueue.add(target2); // * INCLUDE > AUTO
- targetQueue.add(target3);
-
- ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(targetQueue);
- resolver.start(mHandler, RESOLVER_TIMEOUT_MS, mTargetConsumer);
-
- resolver.waitForResult();
-
- ScrollCaptureTarget result = mTargetConsumer.getLastValue();
- assertEquals("input = " + targetQueue + " Expected " + target2
- + " as the result, due to hint=INCLUDE", target2, result);
- assertEquals("result has wrong scroll bounds",
- new Rect(1, 1, 19, 19), result.getScrollBounds());
- }
-
- @Test(timeout = TEST_TIMEOUT_MS)
- public void testDescendantPreferred() throws InterruptedException {
- LinkedList<ScrollCaptureTarget> targetQueue = new LinkedList<>();
-
- ViewGroup targetView1 = new FakeRootView(getTargetContext(), 0, 0, 60, 60); // 60x60
- ViewGroup targetView2 = new FakeRootView(getTargetContext(), 20, 30, 40, 50); // 20x20
- ViewGroup targetView3 = new FakeRootView(getTargetContext(), 5, 5, 15, 15); // 10x10
-
- targetView1.addView(targetView2);
- targetView2.addView(targetView3);
-
- // Create first target with an unrelated parent
- FakeScrollCaptureCallback callback1 = new FakeScrollCaptureCallback();
- callback1.setScrollBounds(new Rect(0, 0, 60, 60));
- ScrollCaptureTarget target1 = createTargetWithView(targetView1, callback1,
- new Rect(0, 0, 60, 60),
- new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO);
-
- // Create second target associated with a view within parent2
- FakeScrollCaptureCallback callback2 = new FakeScrollCaptureCallback();
- callback2.setScrollBounds(new Rect(0, 0, 20, 20));
- ScrollCaptureTarget target2 = createTargetWithView(targetView2, callback2,
- new Rect(0, 0, 20, 20),
- new Point(20, 30), View.SCROLL_CAPTURE_HINT_AUTO);
-
- // Create third target associated with a view within parent3
- FakeScrollCaptureCallback callback3 = new FakeScrollCaptureCallback();
- callback3.setScrollBounds(new Rect(0, 0, 15, 15));
- ScrollCaptureTarget target3 = createTargetWithView(targetView3, callback3,
- new Rect(0, 0, 15, 15),
- new Point(25, 35), View.SCROLL_CAPTURE_HINT_AUTO);
-
- targetQueue.add(target1); // auto, 60x60
- targetQueue.add(target2); // auto, 20x20
- targetQueue.add(target3); // auto, 15x15 <- innermost scrollable
-
- ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(targetQueue);
- resolver.start(mHandler, RESOLVER_TIMEOUT_MS, mTargetConsumer);
-
- // Test only
- resolver.waitForResult();
-
- ScrollCaptureTarget result = mTargetConsumer.getLastValue();
- assertSame("Expected target3 as the result, due to relation", target3, result);
- assertEquals("result has wrong scroll bounds",
- new Rect(0, 0, 15, 15), result.getScrollBounds());
- }
-
- /**
- * If a timeout expires, late results are ignored.
- */
- @Test(timeout = TEST_TIMEOUT_MS)
- public void testTimeout() throws InterruptedException {
- LinkedList<ScrollCaptureTarget> targetQueue = new LinkedList<>();
-
- // callback 1, 10x10, hint=AUTO, responds immediately from bg thread
- BackgroundTestCallback callback1 = new BackgroundTestCallback();
- callback1.setScrollBounds(new Rect(5, 5, 15, 15));
- ScrollCaptureTarget target1 = createTarget(
- callback1, new Rect(20, 30, 40, 50), new Point(10, 10),
- View.SCROLL_CAPTURE_HINT_AUTO);
- targetQueue.add(target1);
-
- // callback 2, 20x20, hint=AUTO, responds after 5s from bg thread
- BackgroundTestCallback callback2 = new BackgroundTestCallback();
- callback2.setScrollBounds(new Rect(0, 0, 20, 20));
- callback2.setDelay(5000);
- ScrollCaptureTarget target2 = createTarget(
- callback2, new Rect(20, 30, 40, 50), new Point(10, 10),
- View.SCROLL_CAPTURE_HINT_AUTO);
- targetQueue.add(target2);
-
- // callback 3, 20x20, hint=INCLUDE, responds after 10s from bg thread
- BackgroundTestCallback callback3 = new BackgroundTestCallback();
- callback3.setScrollBounds(new Rect(0, 0, 20, 20));
- callback3.setDelay(10000);
- ScrollCaptureTarget target3 = createTarget(
- callback3, new Rect(20, 30, 40, 50), new Point(10, 10),
- View.SCROLL_CAPTURE_HINT_INCLUDE);
- targetQueue.add(target3);
-
- // callback 1 will be received
- // callback 2 & 3 will be ignored due to timeout
-
- ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(targetQueue);
- resolver.start(mHandler, RESOLVER_TIMEOUT_MS, mTargetConsumer);
-
- resolver.waitForResult();
-
- ScrollCaptureTarget result = mTargetConsumer.getLastValue();
- assertSame("Expected target1 as the result, due to timeouts of others", target1, result);
- assertEquals("result has wrong scroll bounds",
- new Rect(5, 5, 15, 15), result.getScrollBounds());
- assertEquals("callback1 should have been called",
- 1, callback1.getOnScrollCaptureSearchCount());
- assertEquals("callback2 should have been called",
- 1, callback2.getOnScrollCaptureSearchCount());
- assertEquals("callback3 should have been called",
- 1, callback3.getOnScrollCaptureSearchCount());
- }
-
- @Test(timeout = TEST_TIMEOUT_MS)
- public void testWithCallbackMultipleReplies() throws InterruptedException {
- // Calls response methods 3 times each
- RepeatingCaptureCallback callback1 = new RepeatingCaptureCallback(3);
- callback1.setScrollBounds(new Rect(2, 2, 18, 18));
- ScrollCaptureTarget target1 = createTarget(callback1, new Rect(20, 30, 40, 50),
- new Point(10, 10), View.SCROLL_CAPTURE_HINT_AUTO);
-
- FakeScrollCaptureCallback callback2 = new FakeScrollCaptureCallback();
- callback2.setScrollBounds(new Rect(0, 0, 20, 20));
- ScrollCaptureTarget target2 = createTarget(callback2, new Rect(20, 30, 40, 50),
- new Point(10, 10), View.SCROLL_CAPTURE_HINT_AUTO);
-
- LinkedList<ScrollCaptureTarget> targetQueue = new LinkedList<>();
- targetQueue.add(target1);
- targetQueue.add(target2);
-
- ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(targetQueue);
- resolver.start(mHandler, RESOLVER_TIMEOUT_MS, mTargetConsumer);
-
- resolver.waitForResult();
-
- ScrollCaptureTarget result = mTargetConsumer.getLastValue();
- assertSame("Expected target2 as the result, due to hint=INCLUDE", target2, result);
- assertEquals("result has wrong scroll bounds",
- new Rect(0, 0, 20, 20), result.getScrollBounds());
- assertEquals("callback1 should have been called once",
- 1, callback1.getOnScrollCaptureSearchCount());
- assertEquals("callback2 should have been called once",
- 1, callback2.getOnScrollCaptureSearchCount());
- }
-
- private static class TargetConsumer implements Consumer<ScrollCaptureTarget> {
- volatile ScrollCaptureTarget mResult;
- int mAcceptCount;
-
- ScrollCaptureTarget getLastValue() {
- return mResult;
- }
-
- int acceptCount() {
- return mAcceptCount;
- }
-
- @Override
- public void accept(@Nullable ScrollCaptureTarget t) {
- mAcceptCount++;
- mResult = t;
- }
- }
-
- private void setupTargetView(View view, Rect localVisibleRect, int scrollCaptureHint) {
- view.setScrollCaptureHint(scrollCaptureHint);
- view.onVisibilityAggregated(true);
- // Treat any offset as padding, outset localVisibleRect on all sides and use this as
- // child bounds
- Rect bounds = new Rect(localVisibleRect);
- bounds.inset(-bounds.left, -bounds.top, bounds.left, bounds.top);
- view.layout(bounds.left, bounds.top, bounds.right, bounds.bottom);
- view.onVisibilityAggregated(true);
- }
-
- private ScrollCaptureTarget createTarget(ScrollCaptureCallback callback, Rect localVisibleRect,
- Point positionInWindow, int scrollCaptureHint) {
- View mockView = new View(getTargetContext());
- return createTargetWithView(mockView, callback, localVisibleRect, positionInWindow,
- scrollCaptureHint);
- }
-
- private ScrollCaptureTarget createTargetWithView(View view, ScrollCaptureCallback callback,
- Rect localVisibleRect, Point positionInWindow, int scrollCaptureHint) {
- setupTargetView(view, localVisibleRect, scrollCaptureHint);
- return new ScrollCaptureTarget(view, localVisibleRect, positionInWindow, callback);
- }
-
-
- static class FakeRootView extends ViewGroup implements ViewParent {
- FakeRootView(Context context, int l, int t, int r, int b) {
- super(context);
- layout(l, t, r, b);
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- }
- }
-
- static class FakeScrollCaptureCallback implements ScrollCaptureCallback {
- private Rect mScrollBounds;
- private long mDelayMillis;
- private int mOnScrollCaptureSearchCount;
-
- public int getOnScrollCaptureSearchCount() {
- return mOnScrollCaptureSearchCount;
- }
-
- @Override
- public void onScrollCaptureSearch(Consumer<Rect> onReady) {
- mOnScrollCaptureSearchCount++;
- run(() -> {
- Rect b = getScrollBounds();
- onReady.accept(b);
- });
- }
-
- @Override
- public void onScrollCaptureStart(ScrollCaptureSession session, Runnable onReady) {
- run(onReady);
- }
-
- @Override
- public void onScrollCaptureImageRequest(ScrollCaptureSession session, Rect captureArea) {
- run(() -> session.notifyBufferSent(0, captureArea));
- }
-
- @Override
- public void onScrollCaptureEnd(Runnable onReady) {
- run(onReady);
- }
-
- public void setScrollBounds(@Nullable Rect scrollBounds) {
- mScrollBounds = scrollBounds;
- }
-
- public void setDelay(long delayMillis) {
- mDelayMillis = delayMillis;
- }
-
- protected Rect getScrollBounds() {
- return mScrollBounds;
- }
-
- protected void run(Runnable r) {
- delay();
- r.run();
- }
-
- protected void delay() {
- if (mDelayMillis > 0) {
- try {
- Thread.sleep(mDelayMillis);
- } catch (InterruptedException e) {
- // Ignore
- }
- }
- }
- }
-
- static class RepeatingCaptureCallback extends FakeScrollCaptureCallback {
- private int mRepeatCount;
-
- RepeatingCaptureCallback(int repeatCount) {
- mRepeatCount = repeatCount;
- }
-
- protected void run(Runnable r) {
- delay();
- for (int i = 0; i < mRepeatCount; i++) {
- r.run();
- }
- }
- }
-
- /** Response to async calls on an arbitrary background thread */
- static class BackgroundTestCallback extends FakeScrollCaptureCallback {
- static int sCount = 0;
- private void runOnBackgroundThread(Runnable r) {
- final Runnable target = () -> {
- delay();
- r.run();
- };
- Thread t = new Thread(target);
- synchronized (BackgroundTestCallback.this) {
- sCount++;
- }
- t.setName("Background-Thread-" + sCount);
- t.start();
- }
-
- @Override
- protected void run(Runnable r) {
- runOnBackgroundThread(r);
- }
- }
-}
diff --git a/core/tests/coretests/src/android/view/TestScrollCaptureCallback.java b/core/tests/coretests/src/android/view/TestScrollCaptureCallback.java
new file mode 100644
index 0000000..1520c6e
--- /dev/null
+++ b/core/tests/coretests/src/android/view/TestScrollCaptureCallback.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static org.junit.Assert.*;
+
+import android.graphics.Rect;
+import android.os.CancellationSignal;
+
+import androidx.annotation.NonNull;
+
+import java.util.function.Consumer;
+
+class TestScrollCaptureCallback implements ScrollCaptureCallback {
+ private Consumer<Rect> mSearchConsumer;
+ private Runnable mStartOnReady;
+ private Consumer<Rect> mImageOnComplete;
+ private Runnable mOnEndReady;
+ private volatile int mModCount;
+
+ @Override
+ public void onScrollCaptureSearch(@NonNull CancellationSignal signal,
+ @NonNull Consumer<Rect> onReady) {
+ mSearchConsumer = onReady;
+ mModCount++;
+ }
+
+ @Override
+ public void onScrollCaptureStart(@NonNull ScrollCaptureSession session,
+ @NonNull CancellationSignal signal, @NonNull Runnable onReady) {
+ mStartOnReady = onReady;
+ mModCount++;
+ }
+
+ @Override
+ public void onScrollCaptureImageRequest(@NonNull ScrollCaptureSession session,
+ @NonNull CancellationSignal signal, @NonNull Rect captureArea,
+ @NonNull Consumer<Rect> onComplete) {
+ mImageOnComplete = onComplete;
+ mModCount++;
+ }
+
+ @Override
+ public void onScrollCaptureEnd(@NonNull Runnable onReady) {
+ mOnEndReady = onReady;
+ }
+
+ void completeSearchRequest(Rect scrollBounds) {
+ assertNotNull("Did not receive search request", mSearchConsumer);
+ mSearchConsumer.accept(scrollBounds);
+ mModCount++;
+ }
+
+ void verifyZeroInteractions() {
+ assertEquals("Expected zero interactions", 0, mModCount);
+ }
+
+ void completeStartRequest() {
+ assertNotNull("Did not receive start request", mStartOnReady);
+ mStartOnReady.run();
+ }
+
+ void completeImageRequest(Rect captured) {
+ assertNotNull("Did not receive image request", mImageOnComplete);
+ mImageOnComplete.accept(captured);
+ }
+
+ void completeEndRequest() {
+ assertNotNull("Did not receive end request", mOnEndReady);
+ mOnEndReady.run();
+ }
+}
diff --git a/core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java b/core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java
index 3af0533..41cd4c5 100644
--- a/core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java
+++ b/core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java
@@ -19,7 +19,9 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.testng.AssertJUnit.assertSame;
@@ -27,19 +29,20 @@
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
+import android.os.CancellationSignal;
import android.platform.test.annotations.Presubmit;
+import androidx.annotation.NonNull;
import androidx.test.filters.FlakyTest;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
-import java.util.LinkedList;
-import java.util.Queue;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* Exercises Scroll Capture search in {@link ViewGroup}.
@@ -50,10 +53,7 @@
@RunWith(MockitoJUnitRunner.class)
public class ViewGroupScrollCaptureTest {
- @Mock
- ScrollCaptureCallback mMockCallback;
- @Mock
- ScrollCaptureCallback mMockCallback2;
+ private static final Executor DIRECT_EXECUTOR = Runnable::run;
/** Make sure the hint flags are saved and loaded correctly. */
@Test
@@ -103,25 +103,24 @@
public void testDispatchScrollCaptureSearch_noCallback_hintAuto() throws Exception {
final Context context = getInstrumentation().getContext();
final MockViewGroup viewGroup = new MockViewGroup(context, 0, 0, 200, 200);
+ TestScrollCaptureCallback callback = new TestScrollCaptureCallback();
// When system internal scroll capture is requested, this callback is returned.
- viewGroup.setScrollCaptureCallbackInternalForTest(mMockCallback);
+ viewGroup.setScrollCaptureCallbackInternalForTest(callback);
Rect localVisibleRect = new Rect(0, 0, 200, 200);
Point windowOffset = new Point();
- LinkedList<ScrollCaptureTarget> targetList = new LinkedList<>();
+ ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(DIRECT_EXECUTOR);
// Dispatch
- viewGroup.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, targetList);
-
- // Verify the system checked for fallback support
- viewGroup.assertDispatchScrollCaptureCount(1);
- viewGroup.assertLastDispatchScrollCaptureArgs(localVisibleRect, windowOffset);
+ viewGroup.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, results::addTarget);
+ callback.completeSearchRequest(new Rect(1, 2, 3, 4));
+ assertTrue(results.isComplete());
// Verify the target is as expected.
- assertEquals(1, targetList.size());
- ScrollCaptureTarget target = targetList.get(0);
- assertSame("Target has the wrong callback", mMockCallback, target.getCallback());
+ ScrollCaptureTarget target = results.getTopResult();
+ assertNotNull("Target not found", target);
+ assertSame("Target has the wrong callback", callback, target.getCallback());
assertSame("Target has the wrong View", viewGroup, target.getContainingView());
assertEquals("Target hint is incorrect", View.SCROLL_CAPTURE_HINT_AUTO,
target.getContainingView().getScrollCaptureHint());
@@ -139,18 +138,22 @@
final MockViewGroup viewGroup =
new MockViewGroup(context, 0, 0, 200, 200, View.SCROLL_CAPTURE_HINT_EXCLUDE);
+ TestScrollCaptureCallback callback = new TestScrollCaptureCallback();
+
// When system internal scroll capture is requested, this callback is returned.
- viewGroup.setScrollCaptureCallbackInternalForTest(mMockCallback);
+ viewGroup.setScrollCaptureCallbackInternalForTest(callback);
Rect localVisibleRect = new Rect(0, 0, 200, 200);
Point windowOffset = new Point();
- LinkedList<ScrollCaptureTarget> targetList = new LinkedList<>();
+ ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(DIRECT_EXECUTOR);
+ assertTrue(results.isComplete());
// Dispatch
- viewGroup.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, targetList);
+ viewGroup.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, results::addTarget);
+ callback.verifyZeroInteractions();
// Verify the results.
- assertEquals("Target list size should be zero.", 0, targetList.size());
+ assertTrue("Results should be empty.", results.isEmpty());
}
/**
@@ -164,27 +167,34 @@
final Context context = getInstrumentation().getContext();
MockViewGroup viewGroup = new MockViewGroup(context, 0, 0, 200, 200);
+ TestScrollCaptureCallback callback = new TestScrollCaptureCallback();
+ TestScrollCaptureCallback callback2 = new TestScrollCaptureCallback();
+
// With an already provided scroll capture callback
- viewGroup.setScrollCaptureCallback(mMockCallback);
+ viewGroup.setScrollCaptureCallback(callback);
// When system internal scroll capture is requested, this callback is returned.
- viewGroup.setScrollCaptureCallbackInternalForTest(mMockCallback);
+ viewGroup.setScrollCaptureCallbackInternalForTest(callback2);
Rect localVisibleRect = new Rect(0, 0, 200, 200);
Point windowOffset = new Point();
- LinkedList<ScrollCaptureTarget> targetList = new LinkedList<>();
+ ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(DIRECT_EXECUTOR);
// Dispatch to the ViewGroup
- viewGroup.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, targetList);
-
- // Confirm that framework support was not requested,
- // because this view already had a callback set.
- viewGroup.assertCreateScrollCaptureCallbackInternalCount(0);
+ viewGroup.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, results::addTarget);
+ callback.completeSearchRequest(new Rect(1, 2, 3, 4));
// Verify the target is as expected.
- assertEquals(1, targetList.size());
- ScrollCaptureTarget target = targetList.get(0);
- assertSame("Target has the wrong callback", mMockCallback, target.getCallback());
+ assertFalse(results.isEmpty());
+ assertTrue(results.isComplete());
+
+ // internal framework callback was not requested
+ callback2.verifyZeroInteractions();
+
+ ScrollCaptureTarget target = results.getTopResult();
+
+ assertNotNull("Target not found", target);
+ assertSame("Target has the wrong callback", callback, target.getCallback());
assertSame("Target has the wrong View", viewGroup, target.getContainingView());
assertEquals("Target hint is incorrect", View.SCROLL_CAPTURE_HINT_AUTO,
target.getContainingView().getScrollCaptureHint());
@@ -201,22 +211,22 @@
final Context context = getInstrumentation().getContext();
MockViewGroup viewGroup =
new MockViewGroup(context, 0, 0, 200, 200, View.SCROLL_CAPTURE_HINT_EXCLUDE);
+
+ TestScrollCaptureCallback callback = new TestScrollCaptureCallback();
+
// With an already provided scroll capture callback
- viewGroup.setScrollCaptureCallback(mMockCallback);
+ viewGroup.setScrollCaptureCallback(callback);
Rect localVisibleRect = new Rect(0, 0, 200, 200);
Point windowOffset = new Point();
- LinkedList<ScrollCaptureTarget> targetList = new LinkedList<>();
+ ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(DIRECT_EXECUTOR);
// Dispatch to the ViewGroup itself
- viewGroup.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, targetList);
-
- // Confirm that framework support was not requested, because this view is excluded.
- // (And because this view has a callback set.)
- viewGroup.assertCreateScrollCaptureCallbackInternalCount(0);
+ viewGroup.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, results::addTarget);
+ callback.verifyZeroInteractions();
// Has callback, but hint=excluded, so excluded.
- assertTrue(targetList.isEmpty());
+ assertNull(results.getTopResult());
}
/**
@@ -252,37 +262,43 @@
// | | |
// +---------------+----------+ (200,200)
- // View 1 is clipped and not visible.
+ // View 1 is fully clipped and not visible.
final MockView view1 = new MockView(context, 0, 0, 200, 25);
viewGroup.addView(view1);
- // View 2 is partially visible.
+ // View 2 is partially visible. (75x75)
final MockView view2 = new MockView(context, 0, 25, 150, 100);
viewGroup.addView(view2);
- // View 3 is partially visible.
+ TestScrollCaptureCallback callback1 = new TestScrollCaptureCallback();
+
+ // View 3 is partially visible (175x50)
// Pretend View3 can scroll by having framework provide fallback support
final MockView view3 = new MockView(context, 0, 100, 200, 200);
// When system internal scroll capture is requested for this view, return this callback.
- view3.setScrollCaptureCallbackInternalForTest(mMockCallback);
+ view3.setScrollCaptureCallbackInternalForTest(callback1);
viewGroup.addView(view3);
// View 4 is invisible and should be ignored.
final MockView view4 = new MockView(context, 150, 25, 200, 100, View.INVISIBLE);
viewGroup.addView(view4);
- // View 4 is invisible and should be ignored.
+ TestScrollCaptureCallback callback2 = new TestScrollCaptureCallback();
+
+ // View 5 is partially visible and explicitly included via flag. (25x50)
final MockView view5 = new MockView(context, 150, 100, 200, 200);
- // When system internal scroll capture is requested for this view, return this callback.
- view5.setScrollCaptureCallback(mMockCallback2);
+ view5.setScrollCaptureCallback(callback2);
view5.setScrollCaptureHint(View.SCROLL_CAPTURE_HINT_INCLUDE);
viewGroup.addView(view5);
// Where targets are added
- final LinkedList<ScrollCaptureTarget> targetList = new LinkedList<>();
+ final ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(DIRECT_EXECUTOR);
// Dispatch to the ViewGroup
- viewGroup.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, targetList);
+ viewGroup.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, results::addTarget);
+ callback1.completeSearchRequest(new Rect(0, 0, 200, 100));
+ callback2.completeSearchRequest(new Rect(0, 0, 50, 100));
+ assertTrue(results.isComplete());
// View 1 is entirely clipped by the parent and not visible, dispatch
// skips this view entirely.
@@ -317,18 +333,14 @@
view5.assertCreateScrollCaptureCallbackInternalCount(0);
// 2 views should have been returned, view3 & view5
- assertEquals(2, targetList.size());
+ assertFalse(results.isEmpty());
+ assertTrue(results.isComplete());
- ScrollCaptureTarget target = targetList.get(0);
- assertSame("First target has the wrong View", view3, target.getContainingView());
- assertSame("First target has the wrong callback", mMockCallback, target.getCallback());
- assertEquals("First target hint is incorrect", View.SCROLL_CAPTURE_HINT_AUTO,
- target.getContainingView().getScrollCaptureHint());
-
- target = targetList.get(1);
- assertSame("Second target has the wrong View", view5, target.getContainingView());
- assertSame("Second target has the wrong callback", mMockCallback2, target.getCallback());
- assertEquals("Second target hint is incorrect", View.SCROLL_CAPTURE_HINT_INCLUDE,
+ ScrollCaptureTarget target = results.getTopResult();
+ assertNotNull("Target not found", target);
+ assertSame("Result is the wrong View", view5, target.getContainingView());
+ assertSame("Result is the wrong callback", callback2, target.getCallback());
+ assertEquals("First target hint is incorrect", View.SCROLL_CAPTURE_HINT_INCLUDE,
target.getContainingView().getScrollCaptureHint());
}
@@ -371,7 +383,7 @@
}
void assertCreateScrollCaptureCallbackInternalCount(int count) {
- assertEquals("Unexpected number of calls to createScrollCaptureCallackInternal",
+ assertEquals("Unexpected number of calls to createScrollCaptureCallbackInternal",
count, mCreateScrollCaptureCallbackInternalCount);
}
@@ -385,11 +397,11 @@
@Override
public void dispatchScrollCaptureSearch(Rect localVisibleRect, Point windowOffset,
- Queue<ScrollCaptureTarget> targets) {
+ Consumer<ScrollCaptureTarget> results) {
mDispatchScrollCaptureSearchNumCalls++;
mDispatchScrollCaptureSearchLastLocalVisibleRect = new Rect(localVisibleRect);
mDispatchScrollCaptureSearchLastWindowOffset = new Point(windowOffset);
- super.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, targets);
+ super.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, results);
}
@Override
@@ -401,13 +413,31 @@
}
}
+ static class CallbackStub implements ScrollCaptureCallback {
+
+ @Override
+ public void onScrollCaptureSearch(@NonNull CancellationSignal signal,
+ @NonNull Consumer<Rect> onReady) {
+ }
+
+ @Override
+ public void onScrollCaptureStart(@NonNull ScrollCaptureSession session,
+ @NonNull CancellationSignal signal, @NonNull Runnable onReady) {
+ }
+
+ @Override
+ public void onScrollCaptureImageRequest(@NonNull ScrollCaptureSession session,
+ @NonNull CancellationSignal signal, @NonNull Rect captureArea,
+ Consumer<Rect> onComplete) {
+ }
+
+ @Override
+ public void onScrollCaptureEnd(@NonNull Runnable onReady) {
+ }
+ };
+
public static final class MockViewGroup extends ViewGroup {
private ScrollCaptureCallback mInternalCallback;
- private int mDispatchScrollCaptureSearchNumCalls;
- private Rect mDispatchScrollCaptureSearchLastLocalVisibleRect;
- private Point mDispatchScrollCaptureSearchLastWindowOffset;
- private int mCreateScrollCaptureCallbackInternalCount;
-
MockViewGroup(Context context) {
this(context, /* left */ 0, /* top */0, /* right */ 0, /* bottom */0);
@@ -428,16 +458,10 @@
mInternalCallback = internal;
}
- void assertDispatchScrollCaptureSearchCount(int count) {
- assertEquals("Unexpected number of calls to dispatchScrollCaptureSearch",
- count, mDispatchScrollCaptureSearchNumCalls);
- }
-
@Override
@Nullable
public ScrollCaptureCallback createScrollCaptureCallbackInternal(Rect localVisibleRect,
Point offsetInWindow) {
- mCreateScrollCaptureCallbackInternalCount++;
return mInternalCallback;
}
@@ -445,36 +469,5 @@
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// We don't layout this view.
}
-
- void assertDispatchScrollCaptureCount(int count) {
- assertEquals(count, mDispatchScrollCaptureSearchNumCalls);
- }
-
- void assertLastDispatchScrollCaptureArgs(Rect localVisibleRect, Point windowOffset) {
- assertEquals("arg localVisibleRect to dispatchScrollCaptureCallback was incorrect.",
- localVisibleRect, mDispatchScrollCaptureSearchLastLocalVisibleRect);
- assertEquals("arg windowOffset to dispatchScrollCaptureCallback was incorrect.",
- windowOffset, mDispatchScrollCaptureSearchLastWindowOffset);
- }
- void assertCreateScrollCaptureCallbackInternalCount(int count) {
- assertEquals("Unexpected number of calls to createScrollCaptureCallackInternal",
- count, mCreateScrollCaptureCallbackInternalCount);
- }
-
- void reset() {
- mDispatchScrollCaptureSearchNumCalls = 0;
- mDispatchScrollCaptureSearchLastWindowOffset = null;
- mDispatchScrollCaptureSearchLastLocalVisibleRect = null;
- mCreateScrollCaptureCallbackInternalCount = 0;
- }
-
- @Override
- public void dispatchScrollCaptureSearch(Rect localVisibleRect, Point windowOffset,
- Queue<ScrollCaptureTarget> targets) {
- mDispatchScrollCaptureSearchNumCalls++;
- mDispatchScrollCaptureSearchLastLocalVisibleRect = new Rect(localVisibleRect);
- mDispatchScrollCaptureSearchLastWindowOffset = new Point(windowOffset);
- super.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, targets);
- }
}
}
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index c67174f..7746bc2 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -207,7 +207,7 @@
final CountDownLatch latch = new CountDownLatch(1);
mViewRootImpl.handleScrollCaptureRequest(new IScrollCaptureCallbacks.Default() {
@Override
- public void onUnavailable() {
+ public void onScrollCaptureResponse(ScrollCaptureResponse response) {
latch.countDown();
}
});
@@ -220,6 +220,37 @@
}
/**
+ * Ensure scroll capture request handles a ViewRootImpl with no view tree.
+ */
+ @Test
+ public void requestScrollCapture_timeout() {
+ final View view = new View(mContext);
+ view.setScrollCaptureCallback(new TestScrollCaptureCallback()); // Does nothing
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ WindowManager.LayoutParams wmlp =
+ new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
+ // Set a fake token to bypass 'is your activity running' check
+ wmlp.token = new Binder();
+ view.setLayoutParams(wmlp);
+ mViewRootImpl.setView(view, wmlp, null);
+ });
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ mViewRootImpl.setScrollCaptureRequestTimeout(100);
+ mViewRootImpl.handleScrollCaptureRequest(new IScrollCaptureCallbacks.Default() {
+ @Override
+ public void onScrollCaptureResponse(ScrollCaptureResponse response) {
+ latch.countDown();
+ }
+ });
+ try {
+ if (!latch.await(2500, TimeUnit.MILLISECONDS)) {
+ fail("requestScrollCapture timeout did not occur");
+ }
+ } catch (InterruptedException e) { /* ignore */ }
+ }
+
+ /**
* When window doesn't have focus, keys should be dropped.
*/
@Test
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java
index 9cac7e7..ff728d6 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java
@@ -77,6 +77,7 @@
* bit FrameworksCoreTests:com.android.internal.os.BatteryStatsCpuTimesTest
*/
@SmallTest
+@SkipPresubmit("b/180015146")
@RunWith(AndroidJUnit4.class)
public class BatteryStatsCpuTimesTest {
@Mock
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
index 4b37dd2..24baa93 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
@@ -73,6 +73,7 @@
}
@Test
+ @SkipPresubmit("b/180015146")
public void testUpdateProcStateCpuTimes() {
mBatteryStatsImpl.setOnBatteryInternal(true);
mBatteryStatsImpl.updateTimeBasesLocked(false, Display.STATE_ON, 0, 0);
@@ -230,6 +231,7 @@
}
@Test
+ @SkipPresubmit("b/180015146")
public void testCopyFromAllUidsCpuTimes() {
mBatteryStatsImpl.setOnBatteryInternal(false);
mBatteryStatsImpl.updateTimeBasesLocked(false, Display.STATE_ON, 0, 0);
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
index 6652c64..931611e 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -295,6 +295,7 @@
}
@SmallTest
+ @SkipPresubmit("b/180015146")
public void testAlarmStartAndFinishLocked() throws Exception {
final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
@@ -332,6 +333,7 @@
}
@SmallTest
+ @SkipPresubmit("b/180015146")
public void testAlarmStartAndFinishLocked_workSource() throws Exception {
final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsSamplingTimerTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsSamplingTimerTest.java
index 3b27f18..dd814e6 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsSamplingTimerTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsSamplingTimerTest.java
@@ -56,6 +56,7 @@
}
@SmallTest
+ @SkipPresubmit("b/180015146")
public void testEndSampleAndContinueWhenTimeOrCountDecreases() throws Exception {
final MockClocks clocks = new MockClocks();
final BatteryStatsImpl.TimeBase timeBase = Mockito.mock(BatteryStatsImpl.TimeBase.class);
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index 7b7cf65..d276bc3 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -77,5 +77,4 @@
com.android.internal.power.MeasuredEnergyStatsTest.class
})
public class BatteryStatsTests {
-}
-
+}
\ No newline at end of file
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsUserLifecycleTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsUserLifecycleTests.java
index e7a1bca..e90bcb7 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsUserLifecycleTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsUserLifecycleTests.java
@@ -78,6 +78,7 @@
}
@Test
+ @SkipPresubmit("b/180015146")
public void testNoCpuDataForRemovedUser() throws Exception {
mIam.startUserInBackground(mTestUserId);
waitUntilTrue("No uids for started user " + mTestUserId,
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
index 9ef6288..23ea508 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
@@ -35,6 +35,7 @@
import java.util.List;
@SmallTest
+@SkipPresubmit("b/180015146")
@RunWith(AndroidJUnit4.class)
public class BatteryUsageStatsTest {
diff --git a/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java
index e559471..f6aa08b 100644
--- a/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java
@@ -43,6 +43,7 @@
.setAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_TX, 100.0);
@Test
+ @SkipPresubmit("b/180015146")
public void testTimerBasedModel() {
setDurationsAndPower(mStatsRule.getUidStats(Process.BLUETOOTH_UID)
.getOrCreateBluetoothControllerActivityLocked(),
@@ -73,6 +74,7 @@
}
@Test
+ @SkipPresubmit("b/180015146")
public void testReportedPowerBasedModel() {
setDurationsAndPower(mStatsRule.getUidStats(Process.BLUETOOTH_UID)
.getOrCreateBluetoothControllerActivityLocked(),
diff --git a/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java b/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
index a80f5a0..4fe7d70 100644
--- a/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
@@ -382,6 +382,7 @@
}
@Test
+ @SkipPresubmit("b/180015146 flakey")
public void testCpuFreqTimes_stateFgService() throws Exception {
if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
Log.w(TAG, "Skipping " + testName.getMethodName()
@@ -514,6 +515,7 @@
}
@Test
+ @SkipPresubmit("b/180015146")
public void testCpuFreqTimes_trackingDisabled() throws Exception {
if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
Log.w(TAG, "Skipping " + testName.getMethodName()
diff --git a/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java
index 9cf0d37..e691beb 100644
--- a/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java
@@ -92,6 +92,7 @@
}
@Test
+ @SkipPresubmit("b/180015146")
public void testTimerBasedModel() {
when(mMockUserInfoProvider.exists(anyInt())).thenReturn(true);
diff --git a/core/tests/coretests/src/com/android/internal/os/CustomMeasuredPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/CustomMeasuredPowerCalculatorTest.java
index a4ea892..f298f59 100644
--- a/core/tests/coretests/src/com/android/internal/os/CustomMeasuredPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/CustomMeasuredPowerCalculatorTest.java
@@ -42,6 +42,7 @@
public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule();
@Test
+ @SkipPresubmit("b/180015146")
public void testMeasuredEnergyCopiedIntoBatteryConsumers() {
final BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
SparseLongArray uidEnergies = new SparseLongArray();
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java
index 7dca0cb..177f348 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java
@@ -87,6 +87,7 @@
}
@Test
+ @SkipPresubmit("b/180015146")
public void testThrottler() throws Exception {
mReader = new KernelCpuUidUserSysTimeReader(
new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), true);
diff --git a/core/tests/coretests/src/com/android/internal/os/SkipPresubmit.java b/core/tests/coretests/src/com/android/internal/os/SkipPresubmit.java
new file mode 100644
index 0000000..d03ed66
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/SkipPresubmit.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 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.internal.os;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/** Annotation to skip a test from TEST_MAPPING presubmit. */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.METHOD})
+public @interface SkipPresubmit {
+ /** The optional reason why the test is ignored. */
+ String value() default "";
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java
index dfbf28b..b5282e9 100644
--- a/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java
@@ -78,6 +78,7 @@
}
@Test
+ @SkipPresubmit("b/180015146")
public void testPowerProfileBasedModel() {
when(mMockUserInfoProvider.exists(anyInt())).thenReturn(true);
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 8fd5d80..3900d7e 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -406,8 +406,6 @@
<permission name="android.permission.SET_WALLPAPER" />
<permission name="android.permission.SET_WALLPAPER_COMPONENT" />
<permission name="android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE" />
- <!-- Permissions required for Incremental CTS tests -->
- <permission name="com.android.permission.USE_INSTALLER_V2"/>
<permission name="android.permission.LOADER_USAGE_STATS"/>
<!-- Permission required to test system only camera devices. -->
<permission name="android.permission.SYSTEM_CAMERA" />
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index f1f9a5f..e222570 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -153,6 +153,13 @@
* resource bitmaps often are) the filtering will already have been
* done.</p>
*
+ * <p>On devices running {@link Build.VERSION_CODES#O} and below, hardware
+ * accelerated drawing always uses bilinear sampling on scaled bitmaps,
+ * regardless of this flag. On devices running {@link Build.VERSION_CODES#Q}
+ * and above, this flag defaults to being set on a new {@code Paint}. It can
+ * be cleared with {@link #setFlags} or {@link #setFilterBitmap}.</p>
+ *
+ * @see #Paint()
* @see #Paint(int)
* @see #setFlags(int)
*/
@@ -558,6 +565,12 @@
/**
* Create a new paint with default settings.
+ *
+ * <p>On devices running {@link Build.VERSION_CODES#O} and below, hardware
+ * accelerated drawing always acts as if {@link #FILTER_BITMAP_FLAG} is set.
+ * On devices running {@link Build.VERSION_CODES#Q} and above,
+ * {@code FILTER_BITMAP_FLAG} is set by this constructor, and it can be
+ * cleared with {@link #setFlags} or {@link #setFilterBitmap}.</p>
*/
public Paint() {
this(0);
@@ -567,6 +580,13 @@
* Create a new paint with the specified flags. Use setFlags() to change
* these after the paint is created.
*
+ * <p>On devices running {@link Build.VERSION_CODES#O} and below, hardware
+ * accelerated drawing always acts as if {@link #FILTER_BITMAP_FLAG} is set.
+ * On devices running {@link Build.VERSION_CODES#Q} and above,
+ * {@code FILTER_BITMAP_FLAG} is always set by this constructor, regardless
+ * of the value of {@code flags}. It can be cleared with {@link #setFlags} or
+ * {@link #setFilterBitmap}.</p>
+ *
* @param flags initial flag bits, as if they were passed via setFlags().
*/
public Paint(int flags) {
@@ -991,6 +1011,7 @@
* device pixels. That is dependent on dithering and xfermodes.
*
* @see #setFilterBitmap(boolean) setFilterBitmap()
+ * @see #FILTER_BITMAP_FLAG
*/
public final boolean isFilterBitmap() {
return (getFlags() & FILTER_BITMAP_FLAG) != 0;
@@ -1004,6 +1025,7 @@
*
* @param filter true to set the FILTER_BITMAP_FLAG bit in the paint's
* flags, false to clear it.
+ * @see #FILTER_BITMAP_FLAG
*/
public void setFilterBitmap(boolean filter) {
nSetFilterBitmap(mNativePaint, filter);
diff --git a/keystore/java/android/security/KeyStore2.java b/keystore/java/android/security/KeyStore2.java
index 476e4d7..6ac3821 100644
--- a/keystore/java/android/security/KeyStore2.java
+++ b/keystore/java/android/security/KeyStore2.java
@@ -24,6 +24,7 @@
import android.os.ServiceManager;
import android.os.ServiceSpecificException;
import android.security.keymaster.KeymasterDefs;
+import android.system.keystore2.Domain;
import android.system.keystore2.IKeystoreService;
import android.system.keystore2.KeyDescriptor;
import android.system.keystore2.KeyEntryResponse;
@@ -157,6 +158,50 @@
}
/**
+ * Grant string prefix as used by the keystore boringssl engine. Must be kept in sync
+ * with system/security/keystore-engine. Note: The prefix here includes the 0x which
+ * std::stringstream used in keystore-engine needs to identify the number as hex represented.
+ * Here we include it in the prefix, because Long#parseUnsignedLong does not understand it
+ * and gets the radix as explicit argument.
+ * @hide
+ */
+ private static final String KEYSTORE_ENGINE_GRANT_ALIAS_PREFIX =
+ "ks2_keystore-engine_grant_id:0x";
+
+ /**
+ * This function turns a grant identifier into a specific string that is understood by the
+ * keystore-engine in system/security/keystore-engine. Is only used by VPN and WI-FI components
+ * to allow certain system components like racoon or vendor components like WPA supplicant
+ * to use keystore keys with boring ssl.
+ *
+ * @param grantId the grant id as returned by {@link #grant} in the {@code nspace} filed of
+ * the resulting {@code KeyDescriptor}.
+ * @return The grant descriptor string.
+ * @hide
+ */
+ public static String makeKeystoreEngineGrantString(long grantId) {
+ return String.format("%s%016X", KEYSTORE_ENGINE_GRANT_ALIAS_PREFIX, grantId);
+ }
+
+ /**
+ * Convenience function to turn a keystore engine grant string as returned by
+ * {@link #makeKeystoreEngineGrantString(long)} back into a grant KeyDescriptor.
+ *
+ * @param grantString As string returned by {@link #makeKeystoreEngineGrantString(long)}
+ * @return The grant key descriptor.
+ * @hide
+ */
+ public static KeyDescriptor keystoreEngineGrantString2KeyDescriptor(String grantString) {
+ KeyDescriptor key = new KeyDescriptor();
+ key.domain = Domain.GRANT;
+ key.nspace = Long.parseUnsignedLong(
+ grantString.substring(KEYSTORE_ENGINE_GRANT_ALIAS_PREFIX.length()), 16);
+ key.alias = null;
+ key.blob = null;
+ return key;
+ }
+
+ /**
* Create a grant that allows the grantee identified by {@code granteeUid} to use
* the key specified by {@code descriptor} withint the restrictions given by
* {@code accessVectore}.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
index fb70cbe5..4bb8e9b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
@@ -39,12 +39,12 @@
import android.view.IWindowSessionCallback;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
+import android.view.ScrollCaptureResponse;
import android.view.SurfaceControl;
-import android.view.SurfaceSession;
import android.view.SurfaceControlViewHost;
+import android.view.SurfaceSession;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewRootImpl;
import android.view.WindowManager;
import android.view.WindowlessWindowManager;
import android.window.ClientWindowFrames;
@@ -371,7 +371,11 @@
@Override
public void requestScrollCapture(IScrollCaptureCallbacks callbacks) {
try {
- callbacks.onUnavailable();
+ callbacks.onScrollCaptureResponse(
+ new ScrollCaptureResponse.Builder()
+ .setDescription("Not Implemented")
+ .build());
+
} catch (RemoteException ex) {
// ignore
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt
index eb53783..5f003ba 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt
@@ -71,16 +71,16 @@
}
}
- @FlakyTest
+ @Presubmit
@Test
fun appPairsDividerIsVisible() = testSpec.appPairsDividerIsVisible()
- @FlakyTest
+ @Presubmit
@Test
fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales(Surface.ROTATION_0,
testSpec.config.endRotation)
- @FlakyTest
+ @Presubmit
@Test
fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales(Surface.ROTATION_0,
testSpec.config.endRotation)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt
index 39c9484..d479208 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt
@@ -76,17 +76,8 @@
@Presubmit
@Test
- fun navBarLayerRotatesAndScales() {
- Assume.assumeFalse(isRotated)
- testSpec.navBarLayerRotatesAndScales(Surface.ROTATION_0, testSpec.config.endRotation)
- }
-
- @FlakyTest
- @Test
- fun navBarLayerRotatesAndScales_Flaky() {
- Assume.assumeTrue(isRotated)
- testSpec.navBarLayerRotatesAndScales(Surface.ROTATION_0, testSpec.config.endRotation)
- }
+ fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales(
+ Surface.ROTATION_0, testSpec.config.endRotation)
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
index 41e2864..9011f1a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
@@ -18,7 +18,6 @@
import android.platform.test.annotations.Presubmit
import android.view.Surface
-import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
@@ -125,7 +124,7 @@
}
}
- @FlakyTest
+ @Presubmit
@Test
fun testAppLayerCoversFullScreen() {
testSpec.assertLayersEnd {
@@ -133,11 +132,11 @@
}
}
- @FlakyTest(bugId = 140855415)
+ @Presubmit
@Test
fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible()
- @FlakyTest(bugId = 140855415)
+ @Presubmit
@Test
fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseTransition.kt
similarity index 69%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseTransition.kt
index 06ef79a..3e33176 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseTransition.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 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.
@@ -16,12 +16,9 @@
package com.android.wm.shell.flicker.pip
-import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.dsl.FlickerBuilder
@@ -35,21 +32,10 @@
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import org.junit.FixMethodOrder
import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
-/**
- * Test Pip launch.
- * To run this test: `atest WMShellFlickerTests:PipToHomeTest`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class PipToHomeTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
+abstract class PipCloseTransition(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
get() = buildTransition(eachRun = true) { configuration ->
setup {
@@ -62,30 +48,27 @@
this.setRotation(Surface.ROTATION_0)
}
}
- transitions {
- pipApp.closePipWindow(wmHelper)
- }
}
- @Postsubmit
+ @Presubmit
@Test
- fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible()
+ open fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible()
@Presubmit
@Test
- fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible()
+ open fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible()
@Presubmit
@Test
- fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible()
+ open fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible()
@Presubmit
@Test
- fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
+ open fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
@Presubmit
@Test
- fun pipWindowBecomesInvisible() {
+ open fun pipWindowBecomesInvisible() {
testSpec.assertWm {
this.showsAppWindow(PIP_WINDOW_TITLE)
.then()
@@ -95,7 +78,7 @@
@Presubmit
@Test
- fun pipLayerBecomesInvisible() {
+ open fun pipLayerBecomesInvisible() {
testSpec.assertLayers {
this.isVisible(PIP_WINDOW_TITLE)
.then()
@@ -105,22 +88,22 @@
@Presubmit
@Test
- fun statusBarLayerRotatesScales() =
+ open fun statusBarLayerRotatesScales() =
testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0)
- @Postsubmit
+ @Presubmit
@Test
- fun noUncoveredRegions() =
+ open fun noUncoveredRegions() =
testSpec.noUncoveredRegions(testSpec.config.startRotation, Surface.ROTATION_0)
- @Postsubmit
+ @Presubmit
@Test
- fun navBarLayerRotatesAndScales() =
+ open fun navBarLayerRotatesAndScales() =
testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0)
@FlakyTest(bugId = 151179149)
@Test
- fun focusChanges() = testSpec.focusChanges(pipApp.launcherName, "NexusLauncherActivity")
+ open fun focusChanges() = testSpec.focusChanges(pipApp.launcherName, "NexusLauncherActivity")
companion object {
@Parameterized.Parameters(name = "{0}")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithDismissButtonTest.kt
new file mode 100644
index 0000000..0408421
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithDismissButtonTest.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 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.wm.shell.flicker.pip
+
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test Pip launch.
+ * To run this test: `atest WMShellFlickerTests:PipCloseWithDismissButton`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class PipCloseWithDismissButtonTest(testSpec: FlickerTestParameter) : PipCloseTransition(testSpec) {
+ override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ get() = {
+ super.transition(this, it)
+ transitions {
+ pipApp.closePipWindow(wmHelper)
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithSwipeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithSwipeTest.kt
new file mode 100644
index 0000000..afaf33a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithSwipeTest.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2021 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.wm.shell.flicker.pip
+
+import android.platform.test.annotations.Postsubmit
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.statusBarLayerRotatesScales
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test Pip launch.
+ * To run this test: `atest WMShellFlickerTests:PipCloseWithSwipe`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class PipCloseWithSwipeTest(testSpec: FlickerTestParameter) : PipCloseTransition(testSpec) {
+ override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ get() = {
+ super.transition(this, it)
+ transitions {
+ val pipRegion = wmHelper.getWindowRegion(pipApp.component).bounds
+ val pipCenterX = pipRegion.centerX()
+ val pipCenterY = pipRegion.centerY()
+ val displayCenterX = device.displayWidth / 2
+ device.swipe(pipCenterX, pipCenterY, displayCenterX, device.displayHeight, 5)
+ }
+ }
+
+ @Postsubmit
+ @Test
+ override fun navBarLayerIsAlwaysVisible() = super.navBarLayerIsAlwaysVisible()
+
+ @Postsubmit
+ @Test
+ override fun statusBarLayerIsAlwaysVisible() = super.statusBarLayerIsAlwaysVisible()
+
+ @Postsubmit
+ @Test
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
+
+ @Postsubmit
+ @Test
+ override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
+
+ @Postsubmit
+ @Test
+ override fun pipWindowBecomesInvisible() = super.pipWindowBecomesInvisible()
+
+ @Postsubmit
+ @Test
+ override fun pipLayerBecomesInvisible() = super.pipLayerBecomesInvisible()
+
+ @Postsubmit
+ @Test
+ override fun statusBarLayerRotatesScales() =
+ testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0)
+
+ @Postsubmit
+ @Test
+ override fun noUncoveredRegions() = super.noUncoveredRegions()
+
+ @Postsubmit
+ @Test
+ override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipMovesInAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipMovesInAllApps.kt
new file mode 100644
index 0000000..4c95da2
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipMovesInAllApps.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2021 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.wm.shell.flicker.pip
+
+import android.platform.test.annotations.Postsubmit
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.google.common.truth.Truth
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test Pip launch.
+ * To run this test: `atest WMShellFlickerTests:PipMovesInAllApps`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class PipMovesInAllApps(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
+ private val taplInstrumentation = LauncherInstrumentation()
+
+ override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit
+ get() = buildTransition(eachRun = false) {
+ teardown {
+ eachRun {
+ taplInstrumentation.pressHome()
+ }
+ }
+ transitions {
+ taplInstrumentation.pressHome().switchToAllApps()
+ wmHelper.waitForAppTransitionIdle()
+ }
+ }
+
+ @Postsubmit
+ @Test
+ fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible()
+
+ @Postsubmit
+ @Test
+ fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
+
+ @Postsubmit
+ @Test
+ fun pipAlwaysVisible() = testSpec.assertWm { this.showsAppWindow(pipApp.windowName) }
+
+ @Postsubmit
+ @Test
+ fun pipWindowMovesUp() = testSpec.assertWmEnd {
+ val initialState = this.trace?.first()?.wmState
+ ?: error("Trace should not be empty")
+ val startPos = initialState.pinnedWindows.first().frame
+ val currPos = this.wmState.pinnedWindows.first().frame
+ val subject = Truth.assertWithMessage("Pip should have moved up")
+ subject.that(currPos.top).isGreaterThan(startPos.top)
+ subject.that(currPos.bottom).isGreaterThan(startPos.bottom)
+ subject.that(currPos.left).isEqualTo(startPos.left)
+ subject.that(currPos.right).isEqualTo(startPos.right)
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5)
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
index 37b49c7..df835d2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
@@ -88,11 +88,11 @@
fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation,
testSpec.config.endRotation, allStates = false)
- @FlakyTest(bugId = 140855415)
+ @Presubmit
@Test
fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible()
- @FlakyTest(bugId = 140855415)
+ @Presubmit
@Test
fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt
index 704fd1d..1bb1d28 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt
@@ -66,7 +66,7 @@
}
}
- @FlakyTest(bugId = 140855415)
+ @Presubmit
@Test
fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible()
@@ -107,12 +107,12 @@
}
}
- @FlakyTest
+ @Presubmit
@Test
fun noUncoveredRegions() =
testSpec.noUncoveredRegions(testSpec.config.startRotation, Surface.ROTATION_0)
- @FlakyTest(bugId = 140855415)
+ @Presubmit
@Test
fun navBarLayerRotatesAndScales() =
testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
index 1fcc7d0..7916ce5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
@@ -123,11 +123,11 @@
}
}
- @FlakyTest(bugId = 140855415)
+ @Presubmit
@Test
fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible()
- @FlakyTest(bugId = 140855415)
+ @Presubmit
@Test
fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible()
diff --git a/location/java/android/location/GnssAntennaInfo.java b/location/java/android/location/GnssAntennaInfo.java
index f1eb8fc..6633d24 100644
--- a/location/java/android/location/GnssAntennaInfo.java
+++ b/location/java/android/location/GnssAntennaInfo.java
@@ -39,11 +39,7 @@
/**
* Used for receiving GNSS antenna info from the GNSS engine.
- *
- * @deprecated Prefer to use a broadcast receiver for
- * {@link LocationManager#ACTION_GNSS_ANTENNA_INFOS_CHANGED}.
*/
- @Deprecated
public interface Listener {
/**
* Invoked on a change to GNSS antenna info.
diff --git a/location/java/android/location/IGnssAntennaInfoListener.aidl b/location/java/android/location/IGnssAntennaInfoListener.aidl
new file mode 100644
index 0000000..3cceea3
--- /dev/null
+++ b/location/java/android/location/IGnssAntennaInfoListener.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location;
+
+import android.location.GnssAntennaInfo;
+
+/**
+ * {@hide}
+ */
+oneway interface IGnssAntennaInfoListener {
+ void onGnssAntennaInfoChanged(in List<GnssAntennaInfo> antennaInfos);
+}
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index 38b48e9..6fa6536 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -26,6 +26,7 @@
import android.location.GnssMeasurementCorrections;
import android.location.GnssMeasurementRequest;
import android.location.IGeocodeListener;
+import android.location.IGnssAntennaInfoListener;
import android.location.IGnssMeasurementsListener;
import android.location.IGnssStatusListener;
import android.location.IGnssNavigationMessageListener;
@@ -92,6 +93,9 @@
void addGnssNavigationMessageListener(in IGnssNavigationMessageListener listener, String packageName, @nullable String attributionTag, String listenerId);
void removeGnssNavigationMessageListener(in IGnssNavigationMessageListener listener);
+ void addGnssAntennaInfoListener(in IGnssAntennaInfoListener listener, String packageName, @nullable String attributionTag, String listenerId);
+ void removeGnssAntennaInfoListener(in IGnssAntennaInfoListener listener);
+
void addProviderRequestListener(in IProviderRequestListener listener);
void removeProviderRequestListener(in IProviderRequestListener listener);
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 95bae5a..e73e915 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -43,10 +43,8 @@
import android.compat.Compatibility;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
-import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.location.provider.IProviderRequestListener;
import android.location.provider.ProviderProperties;
@@ -349,28 +347,6 @@
public static final String EXTRA_GNSS_CAPABILITIES = "android.location.extra.GNSS_CAPABILITIES";
/**
- * Broadcast intent action when GNSS antenna infos change. Includes an intent extra,
- * {@link #EXTRA_GNSS_ANTENNA_INFOS}, with an ArrayList of the new {@link GnssAntennaInfo}. This
- * may be read via {@link android.content.Intent#getParcelableArrayListExtra(String)}.
- *
- * @see #EXTRA_GNSS_ANTENNA_INFOS
- * @see #getGnssAntennaInfos()
- */
- @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_GNSS_ANTENNA_INFOS_CHANGED =
- "android.location.action.GNSS_ANTENNA_INFOS_CHANGED";
-
- /**
- * Intent extra included with {@link #ACTION_GNSS_ANTENNA_INFOS_CHANGED} broadcasts, containing
- * the new ArrayList of {@link GnssAntennaInfo}. This may be read via
- * {@link android.content.Intent#getParcelableArrayListExtra(String)}.
- *
- * @see #ACTION_GNSS_ANTENNA_INFOS_CHANGED
- */
- public static final String EXTRA_GNSS_ANTENNA_INFOS =
- "android.location.extra.GNSS_ANTENNA_INFOS";
-
- /**
* Broadcast intent action for Settings app to inject a footer at the bottom of location
* settings. This is for use only by apps that are included in the system image.
*
@@ -1967,7 +1943,7 @@
*
* @param provider the provider name
* @param properties the provider properties
- * @param locationTags the attribution tags for accessing location from the provider
+ * @param extraAttributionTags additional attribution tags associated with this provider
*
* @throws IllegalArgumentException if provider is null
* @throws IllegalArgumentException if properties is null
@@ -1976,13 +1952,14 @@
* allowed} for your app.
*/
public void addTestProvider(@NonNull String provider, @NonNull ProviderProperties properties,
- @NonNull Set<String> locationTags) {
+ @NonNull Set<String> extraAttributionTags) {
Preconditions.checkArgument(provider != null, "invalid null provider");
Preconditions.checkArgument(properties != null, "invalid null properties");
- Preconditions.checkArgument(locationTags != null, "invalid null location tags");
+ Preconditions.checkArgument(extraAttributionTags != null,
+ "invalid null extra attribution tags");
try {
- mService.addTestProvider(provider, properties, new ArrayList<>(locationTags),
+ mService.addTestProvider(provider, properties, new ArrayList<>(extraAttributionTags),
mContext.getOpPackageName(), mContext.getFeatureId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -2659,10 +2636,11 @@
}
/**
- * Registers a GNSS antenna info listener. GNSS antenna info updates will only be received while
- * the {@link #GPS_PROVIDER} is enabled, and while the client app is in the foreground.
+ * Registers a GNSS antenna info listener that will receive all changes to antenna info. Use
+ * {@link #getGnssAntennaInfos()} to get current antenna info.
*
- * <p>Not all GNSS chipsets support antenna info updates, see {@link #getGnssCapabilities()}.
+ * <p>Not all GNSS chipsets support antenna info updates, see {@link #getGnssCapabilities()}. If
+ * unsupported, the listener will never be invoked.
*
* <p>Prior to Android S, this requires the {@link Manifest.permission#ACCESS_FINE_LOCATION}
* permission.
@@ -2673,10 +2651,7 @@
*
* @throws IllegalArgumentException if executor is null
* @throws IllegalArgumentException if listener is null
- *
- * @deprecated Prefer to use a receiver for {@link #ACTION_GNSS_ANTENNA_INFOS_CHANGED}.
*/
- @Deprecated
public boolean registerAntennaInfoListener(
@NonNull @CallbackExecutor Executor executor,
@NonNull GnssAntennaInfo.Listener listener) {
@@ -2686,13 +2661,10 @@
}
/**
- * Unregisters a GNSS Antenna Info listener.
+ * Unregisters a GNSS antenna info listener.
*
* @param listener a {@link GnssAntennaInfo.Listener} object to remove
- *
- * @deprecated Prefer to use a receiver for {@link #ACTION_GNSS_ANTENNA_INFOS_CHANGED}.
*/
- @Deprecated
public void unregisterAntennaInfoListener(@NonNull GnssAntennaInfo.Listener listener) {
GnssLazyLoader.sGnssAntennaInfoListeners.removeListener(listener);
}
@@ -3009,14 +2981,17 @@
}
@Override
- protected void registerTransport(GnssAntennaInfoTransport transport) {
- transport.getContext().registerReceiver(transport,
- new IntentFilter(ACTION_GNSS_ANTENNA_INFOS_CHANGED));
+ protected void registerTransport(GnssAntennaInfoTransport transport)
+ throws RemoteException {
+ getService().addGnssAntennaInfoListener(transport, transport.getPackage(),
+ transport.getAttributionTag(),
+ AppOpsManager.toReceiverId(transport.getListener()));
}
@Override
- protected void unregisterTransport(GnssAntennaInfoTransport transport) {
- transport.getContext().unregisterReceiver(transport);
+ protected void unregisterTransport(GnssAntennaInfoTransport transport)
+ throws RemoteException {
+ getService().removeGnssAntennaInfoListener(transport);
}
}
@@ -3376,11 +3351,12 @@
}
}
- private static class GnssAntennaInfoTransport extends BroadcastReceiver implements
+ private static class GnssAntennaInfoTransport extends IGnssAntennaInfoListener.Stub implements
ListenerTransport<GnssAntennaInfo.Listener> {
private final Executor mExecutor;
- private final Context mContext;
+ private final String mPackageName;
+ private final String mAttributionTag;
private volatile @Nullable GnssAntennaInfo.Listener mListener;
@@ -3389,12 +3365,17 @@
Preconditions.checkArgument(executor != null, "invalid null executor");
Preconditions.checkArgument(listener != null, "invalid null listener");
mExecutor = executor;
- mContext = context;
+ mPackageName = context.getPackageName();
+ mAttributionTag = context.getAttributionTag();
mListener = listener;
}
- public Context getContext() {
- return mContext;
+ public String getPackage() {
+ return mPackageName;
+ }
+
+ public String getAttributionTag() {
+ return mAttributionTag;
}
@Override
@@ -3408,12 +3389,8 @@
}
@Override
- public void onReceive(Context context, Intent intent) {
- ArrayList<GnssAntennaInfo> infos = intent.getParcelableArrayListExtra(
- EXTRA_GNSS_ANTENNA_INFOS);
- if (infos != null) {
- execute(mExecutor, callback -> callback.onGnssAntennaInfoReceived(infos));
- }
+ public void onGnssAntennaInfoChanged(List<GnssAntennaInfo> antennaInfos) {
+ execute(mExecutor, callback -> callback.onGnssAntennaInfoReceived(antennaInfos));
}
}
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index bb1dbd4..7e729d8 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -1264,6 +1264,8 @@
*
* @param usage one of the {@link AudioAttributes} usage constants
* @return string representing the {@link AudioAttributes} usage constant passed as a parameter
+ *
+ * @hide
*/
@NonNull
public static String usageToString(@AttributeSdkUsage int usage) {
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 4d7ed11..06d0eb0 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -19,6 +19,7 @@
import static android.media.Utils.intersectSortedDistinctRanges;
import static android.media.Utils.sortDistinctRanges;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
@@ -1133,6 +1134,7 @@
* in the ranges returned by {@link #getInputChannelCountRanges}
*
*/
+ @IntRange(from = 1, to = 255)
public int getMaxInputChannelCount() {
int overall_max = 0;
for (int i = mInputChannelRanges.length - 1; i >= 0; i--) {
@@ -1151,6 +1153,7 @@
* This returns the lowest channel count in the ranges returned by
* {@link #getInputChannelCountRanges}.
*/
+ @IntRange(from = 1, to = 255)
public int getMinInputChannelCount() {
int overall_min = MAX_INPUT_CHANNEL_COUNT;
for (int i = mInputChannelRanges.length - 1; i >= 0; i--) {
diff --git a/media/java/android/media/soundtrigger/OWNERS b/media/java/android/media/soundtrigger/OWNERS
index 6a351d3..e5d0370 100644
--- a/media/java/android/media/soundtrigger/OWNERS
+++ b/media/java/android/media/soundtrigger/OWNERS
@@ -1 +1,2 @@
+ytai@google.com
elaurent@google.com
diff --git a/media/java/android/mtp/MtpServer.java b/media/java/android/mtp/MtpServer.java
index 042d02f..1d2657a 100644
--- a/media/java/android/mtp/MtpServer.java
+++ b/media/java/android/mtp/MtpServer.java
@@ -144,11 +144,6 @@
native_remove_storage(storage.getStorageId());
}
- public static void configure(boolean usePtp) {
- native_configure(usePtp);
- }
-
- public static native final void native_configure(boolean usePtp);
private native final void native_setup(
MtpDatabase database,
FileDescriptor controlFd,
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioAttributesUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioAttributesUnitTest.java
new file mode 100644
index 0000000..3a4bec0
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioAttributesUnitTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2021 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.mediaframeworktest.unit;
+
+import static org.junit.Assert.assertEquals;
+
+import android.media.AudioAttributes;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class AudioAttributesUnitTest {
+
+ @Test
+ public void testUsageToString_returnCorrectStrings() {
+ assertEquals("USAGE_UNKNOWN", AudioAttributes.usageToString(AudioAttributes.USAGE_UNKNOWN));
+ assertEquals("USAGE_MEDIA", AudioAttributes.usageToString(AudioAttributes.USAGE_MEDIA));
+ assertEquals("USAGE_VOICE_COMMUNICATION",
+ AudioAttributes.usageToString(AudioAttributes.USAGE_VOICE_COMMUNICATION));
+ assertEquals("USAGE_VOICE_COMMUNICATION_SIGNALLING",
+ AudioAttributes.usageToString(
+ AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING));
+ assertEquals("USAGE_ALARM", AudioAttributes.usageToString(AudioAttributes.USAGE_ALARM));
+ assertEquals("USAGE_NOTIFICATION",
+ AudioAttributes.usageToString(AudioAttributes.USAGE_NOTIFICATION));
+ assertEquals("USAGE_NOTIFICATION_RINGTONE",
+ AudioAttributes.usageToString(AudioAttributes.USAGE_NOTIFICATION_RINGTONE));
+ assertEquals("USAGE_NOTIFICATION_COMMUNICATION_REQUEST",
+ AudioAttributes.usageToString(
+ AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST));
+ assertEquals("USAGE_NOTIFICATION_COMMUNICATION_INSTANT",
+ AudioAttributes.usageToString(
+ AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT));
+ assertEquals("USAGE_NOTIFICATION_COMMUNICATION_DELAYED",
+ AudioAttributes.usageToString(
+ AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED));
+ assertEquals("USAGE_NOTIFICATION_EVENT",
+ AudioAttributes.usageToString(AudioAttributes.USAGE_NOTIFICATION_EVENT));
+ assertEquals("USAGE_ASSISTANCE_ACCESSIBILITY",
+ AudioAttributes.usageToString(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY));
+ assertEquals("USAGE_ASSISTANCE_NAVIGATION_GUIDANCE",
+ AudioAttributes.usageToString(
+ AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE));
+ assertEquals("USAGE_ASSISTANCE_SONIFICATION",
+ AudioAttributes.usageToString(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION));
+ assertEquals("USAGE_GAME", AudioAttributes.usageToString(AudioAttributes.USAGE_GAME));
+ assertEquals("USAGE_ASSISTANT",
+ AudioAttributes.usageToString(AudioAttributes.USAGE_ASSISTANT));
+ assertEquals("USAGE_CALL_ASSISTANT",
+ AudioAttributes.usageToString(AudioAttributes.USAGE_CALL_ASSISTANT));
+ assertEquals("USAGE_EMERGENCY",
+ AudioAttributes.usageToString(AudioAttributes.USAGE_EMERGENCY));
+ assertEquals("USAGE_SAFETY", AudioAttributes.usageToString(AudioAttributes.USAGE_SAFETY));
+ assertEquals("USAGE_VEHICLE_STATUS",
+ AudioAttributes.usageToString(AudioAttributes.USAGE_VEHICLE_STATUS));
+ assertEquals("USAGE_ANNOUNCEMENT",
+ AudioAttributes.usageToString(AudioAttributes.USAGE_ANNOUNCEMENT));
+ }
+
+ @Test
+ public void testUsageToString_unknownUsage() {
+ assertEquals("unknown usage -1", AudioAttributes.usageToString(-1));
+ }
+}
diff --git a/packages/Connectivity/framework/Android.bp b/packages/Connectivity/framework/Android.bp
index c7e261c..86b85e83 100644
--- a/packages/Connectivity/framework/Android.bp
+++ b/packages/Connectivity/framework/Android.bp
@@ -58,3 +58,28 @@
"//packages/modules/Connectivity:__subpackages__",
],
}
+
+java_sdk_library {
+ name: "framework-connectivity",
+ api_only: true,
+ defaults: ["framework-module-defaults"],
+ // TODO: build against module API
+ platform_apis: true,
+ srcs: [
+ ":framework-connectivity-sources",
+ ],
+ aidl: {
+ include_dirs: [
+ // Include directories for parcelables that are part of the stable API, and need a
+ // one-line "parcelable X" .aidl declaration to be used in AIDL interfaces.
+ // TODO(b/180293679): remove these dependencies as they should not be necessary once
+ // the module builds against API (the parcelable declarations exist in framework.aidl)
+ "frameworks/base/core/java", // For framework parcelables
+ "frameworks/native/aidl/binder", // For PersistableBundle.aidl
+ ],
+ },
+ libs: [
+ "unsupportedappusage",
+ ],
+ permitted_packages: ["android.net", "com.android.connectivity.aidl"],
+}
diff --git a/packages/Connectivity/framework/api/current.txt b/packages/Connectivity/framework/api/current.txt
new file mode 100644
index 0000000..31b8fc8
--- /dev/null
+++ b/packages/Connectivity/framework/api/current.txt
@@ -0,0 +1,470 @@
+// Signature format: 2.0
+package android.net {
+
+ public class CaptivePortal implements android.os.Parcelable {
+ method public int describeContents();
+ method public void ignoreNetwork();
+ method public void reportCaptivePortalDismissed();
+ method public void writeToParcel(android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.CaptivePortal> CREATOR;
+ }
+
+ public class ConnectivityDiagnosticsManager {
+ method public void registerConnectivityDiagnosticsCallback(@NonNull android.net.NetworkRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback);
+ method public void unregisterConnectivityDiagnosticsCallback(@NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback);
+ }
+
+ public abstract static class ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback {
+ ctor public ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback();
+ method public void onConnectivityReportAvailable(@NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityReport);
+ method public void onDataStallSuspected(@NonNull android.net.ConnectivityDiagnosticsManager.DataStallReport);
+ method public void onNetworkConnectivityReported(@NonNull android.net.Network, boolean);
+ }
+
+ public static final class ConnectivityDiagnosticsManager.ConnectivityReport implements android.os.Parcelable {
+ ctor public ConnectivityDiagnosticsManager.ConnectivityReport(@NonNull android.net.Network, long, @NonNull android.net.LinkProperties, @NonNull android.net.NetworkCapabilities, @NonNull android.os.PersistableBundle);
+ method public int describeContents();
+ method @NonNull public android.os.PersistableBundle getAdditionalInfo();
+ method @NonNull public android.net.LinkProperties getLinkProperties();
+ method @NonNull public android.net.Network getNetwork();
+ method @NonNull public android.net.NetworkCapabilities getNetworkCapabilities();
+ method public long getReportTimestamp();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.ConnectivityDiagnosticsManager.ConnectivityReport> CREATOR;
+ field public static final String KEY_NETWORK_PROBES_ATTEMPTED_BITMASK = "networkProbesAttempted";
+ field public static final String KEY_NETWORK_PROBES_SUCCEEDED_BITMASK = "networkProbesSucceeded";
+ field public static final String KEY_NETWORK_VALIDATION_RESULT = "networkValidationResult";
+ field public static final int NETWORK_PROBE_DNS = 4; // 0x4
+ field public static final int NETWORK_PROBE_FALLBACK = 32; // 0x20
+ field public static final int NETWORK_PROBE_HTTP = 8; // 0x8
+ field public static final int NETWORK_PROBE_HTTPS = 16; // 0x10
+ field public static final int NETWORK_PROBE_PRIVATE_DNS = 64; // 0x40
+ field public static final int NETWORK_VALIDATION_RESULT_INVALID = 0; // 0x0
+ field public static final int NETWORK_VALIDATION_RESULT_PARTIALLY_VALID = 2; // 0x2
+ field public static final int NETWORK_VALIDATION_RESULT_SKIPPED = 3; // 0x3
+ field public static final int NETWORK_VALIDATION_RESULT_VALID = 1; // 0x1
+ }
+
+ public static final class ConnectivityDiagnosticsManager.DataStallReport implements android.os.Parcelable {
+ ctor public ConnectivityDiagnosticsManager.DataStallReport(@NonNull android.net.Network, long, int, @NonNull android.net.LinkProperties, @NonNull android.net.NetworkCapabilities, @NonNull android.os.PersistableBundle);
+ method public int describeContents();
+ method public int getDetectionMethod();
+ method @NonNull public android.net.LinkProperties getLinkProperties();
+ method @NonNull public android.net.Network getNetwork();
+ method @NonNull public android.net.NetworkCapabilities getNetworkCapabilities();
+ method public long getReportTimestamp();
+ method @NonNull public android.os.PersistableBundle getStallDetails();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.ConnectivityDiagnosticsManager.DataStallReport> CREATOR;
+ field public static final int DETECTION_METHOD_DNS_EVENTS = 1; // 0x1
+ field public static final int DETECTION_METHOD_TCP_METRICS = 2; // 0x2
+ field public static final String KEY_DNS_CONSECUTIVE_TIMEOUTS = "dnsConsecutiveTimeouts";
+ field public static final String KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS = "tcpMetricsCollectionPeriodMillis";
+ field public static final String KEY_TCP_PACKET_FAIL_RATE = "tcpPacketFailRate";
+ }
+
+ public class ConnectivityManager {
+ method public void addDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener);
+ method public boolean bindProcessToNetwork(@Nullable android.net.Network);
+ method @NonNull public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull android.net.IpSecManager.UdpEncapsulationSocket, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback);
+ method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.Network getActiveNetwork();
+ method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getActiveNetworkInfo();
+ method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo[] getAllNetworkInfo();
+ method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.Network[] getAllNetworks();
+ method @Deprecated public boolean getBackgroundDataSetting();
+ method @Nullable public android.net.Network getBoundNetworkForProcess();
+ method public int getConnectionOwnerUid(int, @NonNull java.net.InetSocketAddress, @NonNull java.net.InetSocketAddress);
+ method @Nullable public android.net.ProxyInfo getDefaultProxy();
+ method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.LinkProperties getLinkProperties(@Nullable android.net.Network);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public int getMultipathPreference(@Nullable android.net.Network);
+ method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkCapabilities getNetworkCapabilities(@Nullable android.net.Network);
+ method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getNetworkInfo(int);
+ method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getNetworkInfo(@Nullable android.net.Network);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public int getNetworkPreference();
+ method @Nullable public byte[] getNetworkWatchlistConfigHash();
+ method @Deprecated @Nullable public static android.net.Network getProcessDefaultNetwork();
+ method public int getRestrictBackgroundStatus();
+ method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public boolean isActiveNetworkMetered();
+ method public boolean isDefaultNetworkActive();
+ method @Deprecated public static boolean isNetworkTypeValid(int);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.app.PendingIntent);
+ method public void releaseNetworkRequest(@NonNull android.app.PendingIntent);
+ method public void removeDefaultNetworkActiveListener(@NonNull android.net.ConnectivityManager.OnNetworkActiveListener);
+ method @Deprecated public void reportBadNetwork(@Nullable android.net.Network);
+ method public void reportNetworkConnectivity(@Nullable android.net.Network, boolean);
+ method public boolean requestBandwidthUpdate(@NonNull android.net.Network);
+ method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback);
+ method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
+ method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, int);
+ method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler, int);
+ method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.app.PendingIntent);
+ method @Deprecated public void setNetworkPreference(int);
+ method @Deprecated public static boolean setProcessDefaultNetwork(@Nullable android.net.Network);
+ method public void unregisterNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback);
+ method public void unregisterNetworkCallback(@NonNull android.app.PendingIntent);
+ field @Deprecated public static final String ACTION_BACKGROUND_DATA_SETTING_CHANGED = "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED";
+ field public static final String ACTION_CAPTIVE_PORTAL_SIGN_IN = "android.net.conn.CAPTIVE_PORTAL";
+ field public static final String ACTION_RESTRICT_BACKGROUND_CHANGED = "android.net.conn.RESTRICT_BACKGROUND_CHANGED";
+ field @Deprecated public static final String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
+ field @Deprecated public static final int DEFAULT_NETWORK_PREFERENCE = 1; // 0x1
+ field public static final String EXTRA_CAPTIVE_PORTAL = "android.net.extra.CAPTIVE_PORTAL";
+ field public static final String EXTRA_CAPTIVE_PORTAL_URL = "android.net.extra.CAPTIVE_PORTAL_URL";
+ field @Deprecated public static final String EXTRA_EXTRA_INFO = "extraInfo";
+ field @Deprecated public static final String EXTRA_IS_FAILOVER = "isFailover";
+ field public static final String EXTRA_NETWORK = "android.net.extra.NETWORK";
+ field @Deprecated public static final String EXTRA_NETWORK_INFO = "networkInfo";
+ field public static final String EXTRA_NETWORK_REQUEST = "android.net.extra.NETWORK_REQUEST";
+ field @Deprecated public static final String EXTRA_NETWORK_TYPE = "networkType";
+ field public static final String EXTRA_NO_CONNECTIVITY = "noConnectivity";
+ field @Deprecated public static final String EXTRA_OTHER_NETWORK_INFO = "otherNetwork";
+ field public static final String EXTRA_REASON = "reason";
+ field public static final int MULTIPATH_PREFERENCE_HANDOVER = 1; // 0x1
+ field public static final int MULTIPATH_PREFERENCE_PERFORMANCE = 4; // 0x4
+ field public static final int MULTIPATH_PREFERENCE_RELIABILITY = 2; // 0x2
+ field public static final int RESTRICT_BACKGROUND_STATUS_DISABLED = 1; // 0x1
+ field public static final int RESTRICT_BACKGROUND_STATUS_ENABLED = 3; // 0x3
+ field public static final int RESTRICT_BACKGROUND_STATUS_WHITELISTED = 2; // 0x2
+ field @Deprecated public static final int TYPE_BLUETOOTH = 7; // 0x7
+ field @Deprecated public static final int TYPE_DUMMY = 8; // 0x8
+ field @Deprecated public static final int TYPE_ETHERNET = 9; // 0x9
+ field @Deprecated public static final int TYPE_MOBILE = 0; // 0x0
+ field @Deprecated public static final int TYPE_MOBILE_DUN = 4; // 0x4
+ field @Deprecated public static final int TYPE_MOBILE_HIPRI = 5; // 0x5
+ field @Deprecated public static final int TYPE_MOBILE_MMS = 2; // 0x2
+ field @Deprecated public static final int TYPE_MOBILE_SUPL = 3; // 0x3
+ field @Deprecated public static final int TYPE_VPN = 17; // 0x11
+ field @Deprecated public static final int TYPE_WIFI = 1; // 0x1
+ field @Deprecated public static final int TYPE_WIMAX = 6; // 0x6
+ }
+
+ public static class ConnectivityManager.NetworkCallback {
+ ctor public ConnectivityManager.NetworkCallback();
+ method public void onAvailable(@NonNull android.net.Network);
+ method public void onBlockedStatusChanged(@NonNull android.net.Network, boolean);
+ method public void onCapabilitiesChanged(@NonNull android.net.Network, @NonNull android.net.NetworkCapabilities);
+ method public void onLinkPropertiesChanged(@NonNull android.net.Network, @NonNull android.net.LinkProperties);
+ method public void onLosing(@NonNull android.net.Network, int);
+ method public void onLost(@NonNull android.net.Network);
+ method public void onUnavailable();
+ }
+
+ public static interface ConnectivityManager.OnNetworkActiveListener {
+ method public void onNetworkActive();
+ }
+
+ public class DhcpInfo implements android.os.Parcelable {
+ ctor public DhcpInfo();
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.DhcpInfo> CREATOR;
+ field public int dns1;
+ field public int dns2;
+ field public int gateway;
+ field public int ipAddress;
+ field public int leaseDuration;
+ field public int netmask;
+ field public int serverAddress;
+ }
+
+ public final class DnsResolver {
+ method @NonNull public static android.net.DnsResolver getInstance();
+ method public void query(@Nullable android.net.Network, @NonNull String, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super java.util.List<java.net.InetAddress>>);
+ method public void query(@Nullable android.net.Network, @NonNull String, int, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super java.util.List<java.net.InetAddress>>);
+ method public void rawQuery(@Nullable android.net.Network, @NonNull byte[], int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super byte[]>);
+ method public void rawQuery(@Nullable android.net.Network, @NonNull String, int, int, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super byte[]>);
+ field public static final int CLASS_IN = 1; // 0x1
+ field public static final int ERROR_PARSE = 0; // 0x0
+ field public static final int ERROR_SYSTEM = 1; // 0x1
+ field public static final int FLAG_EMPTY = 0; // 0x0
+ field public static final int FLAG_NO_CACHE_LOOKUP = 4; // 0x4
+ field public static final int FLAG_NO_CACHE_STORE = 2; // 0x2
+ field public static final int FLAG_NO_RETRY = 1; // 0x1
+ field public static final int TYPE_A = 1; // 0x1
+ field public static final int TYPE_AAAA = 28; // 0x1c
+ }
+
+ public static interface DnsResolver.Callback<T> {
+ method public void onAnswer(@NonNull T, int);
+ method public void onError(@NonNull android.net.DnsResolver.DnsException);
+ }
+
+ public static class DnsResolver.DnsException extends java.lang.Exception {
+ field public final int code;
+ }
+
+ public class InetAddresses {
+ method public static boolean isNumericAddress(@NonNull String);
+ method @NonNull public static java.net.InetAddress parseNumericAddress(@NonNull String);
+ }
+
+ public final class IpPrefix implements android.os.Parcelable {
+ method public boolean contains(@NonNull java.net.InetAddress);
+ method public int describeContents();
+ method @NonNull public java.net.InetAddress getAddress();
+ method @IntRange(from=0, to=128) public int getPrefixLength();
+ method @NonNull public byte[] getRawAddress();
+ method public void writeToParcel(android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.IpPrefix> CREATOR;
+ }
+
+ public class LinkAddress implements android.os.Parcelable {
+ method public int describeContents();
+ method public java.net.InetAddress getAddress();
+ method public int getFlags();
+ method @IntRange(from=0, to=128) public int getPrefixLength();
+ method public int getScope();
+ method public void writeToParcel(android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.LinkAddress> CREATOR;
+ }
+
+ public final class LinkProperties implements android.os.Parcelable {
+ ctor public LinkProperties();
+ method public boolean addRoute(@NonNull android.net.RouteInfo);
+ method public void clear();
+ method public int describeContents();
+ method @Nullable public java.net.Inet4Address getDhcpServerAddress();
+ method @NonNull public java.util.List<java.net.InetAddress> getDnsServers();
+ method @Nullable public String getDomains();
+ method @Nullable public android.net.ProxyInfo getHttpProxy();
+ method @Nullable public String getInterfaceName();
+ method @NonNull public java.util.List<android.net.LinkAddress> getLinkAddresses();
+ method public int getMtu();
+ method @Nullable public android.net.IpPrefix getNat64Prefix();
+ method @Nullable public String getPrivateDnsServerName();
+ method @NonNull public java.util.List<android.net.RouteInfo> getRoutes();
+ method public boolean isPrivateDnsActive();
+ method public boolean isWakeOnLanSupported();
+ method public void setDhcpServerAddress(@Nullable java.net.Inet4Address);
+ method public void setDnsServers(@NonNull java.util.Collection<java.net.InetAddress>);
+ method public void setDomains(@Nullable String);
+ method public void setHttpProxy(@Nullable android.net.ProxyInfo);
+ method public void setInterfaceName(@Nullable String);
+ method public void setLinkAddresses(@NonNull java.util.Collection<android.net.LinkAddress>);
+ method public void setMtu(int);
+ method public void setNat64Prefix(@Nullable android.net.IpPrefix);
+ method public void writeToParcel(android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.LinkProperties> CREATOR;
+ }
+
+ public final class MacAddress implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public static android.net.MacAddress fromBytes(@NonNull byte[]);
+ method @NonNull public static android.net.MacAddress fromString(@NonNull String);
+ method public int getAddressType();
+ method @Nullable public java.net.Inet6Address getLinkLocalIpv6FromEui48Mac();
+ method public boolean isLocallyAssigned();
+ method public boolean matches(@NonNull android.net.MacAddress, @NonNull android.net.MacAddress);
+ method @NonNull public byte[] toByteArray();
+ method @NonNull public String toOuiString();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.net.MacAddress BROADCAST_ADDRESS;
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.MacAddress> CREATOR;
+ field public static final int TYPE_BROADCAST = 3; // 0x3
+ field public static final int TYPE_MULTICAST = 2; // 0x2
+ field public static final int TYPE_UNICAST = 1; // 0x1
+ }
+
+ public class Network implements android.os.Parcelable {
+ method public void bindSocket(java.net.DatagramSocket) throws java.io.IOException;
+ method public void bindSocket(java.net.Socket) throws java.io.IOException;
+ method public void bindSocket(java.io.FileDescriptor) throws java.io.IOException;
+ method public int describeContents();
+ method public static android.net.Network fromNetworkHandle(long);
+ method public java.net.InetAddress[] getAllByName(String) throws java.net.UnknownHostException;
+ method public java.net.InetAddress getByName(String) throws java.net.UnknownHostException;
+ method public long getNetworkHandle();
+ method public javax.net.SocketFactory getSocketFactory();
+ method public java.net.URLConnection openConnection(java.net.URL) throws java.io.IOException;
+ method public java.net.URLConnection openConnection(java.net.URL, java.net.Proxy) throws java.io.IOException;
+ method public void writeToParcel(android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.Network> CREATOR;
+ }
+
+ public final class NetworkCapabilities implements android.os.Parcelable {
+ ctor public NetworkCapabilities();
+ ctor public NetworkCapabilities(android.net.NetworkCapabilities);
+ method public int describeContents();
+ method public int getLinkDownstreamBandwidthKbps();
+ method public int getLinkUpstreamBandwidthKbps();
+ method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier();
+ method public int getOwnerUid();
+ method public int getSignalStrength();
+ method @Nullable public android.net.TransportInfo getTransportInfo();
+ method public boolean hasCapability(int);
+ method public boolean hasTransport(int);
+ method public void writeToParcel(android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkCapabilities> CREATOR;
+ field public static final int NET_CAPABILITY_CAPTIVE_PORTAL = 17; // 0x11
+ field public static final int NET_CAPABILITY_CBS = 5; // 0x5
+ field public static final int NET_CAPABILITY_DUN = 2; // 0x2
+ field public static final int NET_CAPABILITY_EIMS = 10; // 0xa
+ field public static final int NET_CAPABILITY_ENTERPRISE = 29; // 0x1d
+ field public static final int NET_CAPABILITY_FOREGROUND = 19; // 0x13
+ field public static final int NET_CAPABILITY_FOTA = 3; // 0x3
+ field public static final int NET_CAPABILITY_IA = 7; // 0x7
+ field public static final int NET_CAPABILITY_IMS = 4; // 0x4
+ field public static final int NET_CAPABILITY_INTERNET = 12; // 0xc
+ field public static final int NET_CAPABILITY_MCX = 23; // 0x17
+ field public static final int NET_CAPABILITY_MMS = 0; // 0x0
+ field public static final int NET_CAPABILITY_NOT_CONGESTED = 20; // 0x14
+ field public static final int NET_CAPABILITY_NOT_METERED = 11; // 0xb
+ field public static final int NET_CAPABILITY_NOT_RESTRICTED = 13; // 0xd
+ field public static final int NET_CAPABILITY_NOT_ROAMING = 18; // 0x12
+ field public static final int NET_CAPABILITY_NOT_SUSPENDED = 21; // 0x15
+ field public static final int NET_CAPABILITY_NOT_VPN = 15; // 0xf
+ field public static final int NET_CAPABILITY_RCS = 8; // 0x8
+ field public static final int NET_CAPABILITY_SUPL = 1; // 0x1
+ field public static final int NET_CAPABILITY_TEMPORARILY_NOT_METERED = 25; // 0x19
+ field public static final int NET_CAPABILITY_TRUSTED = 14; // 0xe
+ field public static final int NET_CAPABILITY_VALIDATED = 16; // 0x10
+ field public static final int NET_CAPABILITY_WIFI_P2P = 6; // 0x6
+ field public static final int NET_CAPABILITY_XCAP = 9; // 0x9
+ field public static final int SIGNAL_STRENGTH_UNSPECIFIED = -2147483648; // 0x80000000
+ field public static final int TRANSPORT_BLUETOOTH = 2; // 0x2
+ field public static final int TRANSPORT_CELLULAR = 0; // 0x0
+ field public static final int TRANSPORT_ETHERNET = 3; // 0x3
+ field public static final int TRANSPORT_LOWPAN = 6; // 0x6
+ field public static final int TRANSPORT_VPN = 4; // 0x4
+ field public static final int TRANSPORT_WIFI = 1; // 0x1
+ field public static final int TRANSPORT_WIFI_AWARE = 5; // 0x5
+ }
+
+ @Deprecated public class NetworkInfo implements android.os.Parcelable {
+ ctor @Deprecated public NetworkInfo(int, int, @Nullable String, @Nullable String);
+ method @Deprecated public int describeContents();
+ method @Deprecated @NonNull public android.net.NetworkInfo.DetailedState getDetailedState();
+ method @Deprecated public String getExtraInfo();
+ method @Deprecated public String getReason();
+ method @Deprecated public android.net.NetworkInfo.State getState();
+ method @Deprecated public int getSubtype();
+ method @Deprecated public String getSubtypeName();
+ method @Deprecated public int getType();
+ method @Deprecated public String getTypeName();
+ method @Deprecated public boolean isAvailable();
+ method @Deprecated public boolean isConnected();
+ method @Deprecated public boolean isConnectedOrConnecting();
+ method @Deprecated public boolean isFailover();
+ method @Deprecated public boolean isRoaming();
+ method @Deprecated public void setDetailedState(@NonNull android.net.NetworkInfo.DetailedState, @Nullable String, @Nullable String);
+ method @Deprecated public void writeToParcel(android.os.Parcel, int);
+ field @Deprecated @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkInfo> CREATOR;
+ }
+
+ @Deprecated public enum NetworkInfo.DetailedState {
+ enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState AUTHENTICATING;
+ enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState BLOCKED;
+ enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState CAPTIVE_PORTAL_CHECK;
+ enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState CONNECTED;
+ enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState CONNECTING;
+ enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState DISCONNECTED;
+ enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState DISCONNECTING;
+ enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState FAILED;
+ enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState IDLE;
+ enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState OBTAINING_IPADDR;
+ enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState SCANNING;
+ enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState SUSPENDED;
+ enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState VERIFYING_POOR_LINK;
+ }
+
+ @Deprecated public enum NetworkInfo.State {
+ enum_constant @Deprecated public static final android.net.NetworkInfo.State CONNECTED;
+ enum_constant @Deprecated public static final android.net.NetworkInfo.State CONNECTING;
+ enum_constant @Deprecated public static final android.net.NetworkInfo.State DISCONNECTED;
+ enum_constant @Deprecated public static final android.net.NetworkInfo.State DISCONNECTING;
+ enum_constant @Deprecated public static final android.net.NetworkInfo.State SUSPENDED;
+ enum_constant @Deprecated public static final android.net.NetworkInfo.State UNKNOWN;
+ }
+
+ public class NetworkRequest implements android.os.Parcelable {
+ method public boolean canBeSatisfiedBy(@Nullable android.net.NetworkCapabilities);
+ method public int describeContents();
+ method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier();
+ method public boolean hasCapability(int);
+ method public boolean hasTransport(int);
+ method public void writeToParcel(android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkRequest> CREATOR;
+ }
+
+ public static class NetworkRequest.Builder {
+ ctor public NetworkRequest.Builder();
+ method public android.net.NetworkRequest.Builder addCapability(int);
+ method public android.net.NetworkRequest.Builder addTransportType(int);
+ method public android.net.NetworkRequest build();
+ method @NonNull public android.net.NetworkRequest.Builder clearCapabilities();
+ method public android.net.NetworkRequest.Builder removeCapability(int);
+ method public android.net.NetworkRequest.Builder removeTransportType(int);
+ method @Deprecated public android.net.NetworkRequest.Builder setNetworkSpecifier(String);
+ method public android.net.NetworkRequest.Builder setNetworkSpecifier(android.net.NetworkSpecifier);
+ }
+
+ public final class Proxy {
+ ctor public Proxy();
+ method @Deprecated public static String getDefaultHost();
+ method @Deprecated public static int getDefaultPort();
+ method @Deprecated public static String getHost(android.content.Context);
+ method @Deprecated public static int getPort(android.content.Context);
+ field @Deprecated public static final String EXTRA_PROXY_INFO = "android.intent.extra.PROXY_INFO";
+ field public static final String PROXY_CHANGE_ACTION = "android.intent.action.PROXY_CHANGE";
+ }
+
+ public class ProxyInfo implements android.os.Parcelable {
+ ctor public ProxyInfo(@Nullable android.net.ProxyInfo);
+ method public static android.net.ProxyInfo buildDirectProxy(String, int);
+ method public static android.net.ProxyInfo buildDirectProxy(String, int, java.util.List<java.lang.String>);
+ method public static android.net.ProxyInfo buildPacProxy(android.net.Uri);
+ method @NonNull public static android.net.ProxyInfo buildPacProxy(@NonNull android.net.Uri, int);
+ method public int describeContents();
+ method public String[] getExclusionList();
+ method public String getHost();
+ method public android.net.Uri getPacFileUrl();
+ method public int getPort();
+ method public boolean isValid();
+ method public void writeToParcel(android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.ProxyInfo> CREATOR;
+ }
+
+ public final class RouteInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.net.IpPrefix getDestination();
+ method @Nullable public java.net.InetAddress getGateway();
+ method @Nullable public String getInterface();
+ method public boolean hasGateway();
+ method public boolean isDefaultRoute();
+ method public boolean matches(java.net.InetAddress);
+ method public void writeToParcel(android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.RouteInfo> CREATOR;
+ }
+
+ public abstract class SocketKeepalive implements java.lang.AutoCloseable {
+ method public final void close();
+ method public final void start(@IntRange(from=0xa, to=0xe10) int);
+ method public final void stop();
+ field public static final int ERROR_HARDWARE_ERROR = -31; // 0xffffffe1
+ field public static final int ERROR_INSUFFICIENT_RESOURCES = -32; // 0xffffffe0
+ field public static final int ERROR_INVALID_INTERVAL = -24; // 0xffffffe8
+ field public static final int ERROR_INVALID_IP_ADDRESS = -21; // 0xffffffeb
+ field public static final int ERROR_INVALID_LENGTH = -23; // 0xffffffe9
+ field public static final int ERROR_INVALID_NETWORK = -20; // 0xffffffec
+ field public static final int ERROR_INVALID_PORT = -22; // 0xffffffea
+ field public static final int ERROR_INVALID_SOCKET = -25; // 0xffffffe7
+ field public static final int ERROR_SOCKET_NOT_IDLE = -26; // 0xffffffe6
+ field public static final int ERROR_UNSUPPORTED = -30; // 0xffffffe2
+ }
+
+ public static class SocketKeepalive.Callback {
+ ctor public SocketKeepalive.Callback();
+ method public void onDataReceived();
+ method public void onError(int);
+ method public void onStarted();
+ method public void onStopped();
+ }
+
+ public interface TransportInfo {
+ }
+
+}
+
diff --git a/packages/Connectivity/framework/api/lint-baseline.txt b/packages/Connectivity/framework/api/lint-baseline.txt
new file mode 100644
index 0000000..2f4004a
--- /dev/null
+++ b/packages/Connectivity/framework/api/lint-baseline.txt
@@ -0,0 +1,4 @@
+// Baseline format: 1.0
+VisiblySynchronized: android.net.NetworkInfo#toString():
+ Internal locks must not be exposed (synchronizing on this or class is still
+ externally observable): method android.net.NetworkInfo.toString()
diff --git a/packages/Connectivity/framework/api/module-lib-current.txt b/packages/Connectivity/framework/api/module-lib-current.txt
new file mode 100644
index 0000000..3af855e
--- /dev/null
+++ b/packages/Connectivity/framework/api/module-lib-current.txt
@@ -0,0 +1,66 @@
+// Signature format: 2.0
+package android.net {
+
+ public final class ConnectivityFrameworkInitializer {
+ method public static void registerServiceWrappers();
+ }
+
+ public class ConnectivityManager {
+ method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
+ method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @Nullable android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback);
+ method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle);
+ }
+
+ public final class NetworkAgentConfig implements android.os.Parcelable {
+ method @Nullable public String getSubscriberId();
+ }
+
+ public static final class NetworkAgentConfig.Builder {
+ method @NonNull public android.net.NetworkAgentConfig.Builder setSubscriberId(@Nullable String);
+ }
+
+ public final class NetworkCapabilities implements android.os.Parcelable {
+ field public static final int TRANSPORT_TEST = 7; // 0x7
+ }
+
+ public final class Proxy {
+ method public static void setHttpProxyConfiguration(@Nullable android.net.ProxyInfo);
+ }
+
+ public final class TcpRepairWindow {
+ ctor public TcpRepairWindow(int, int, int, int, int, int);
+ field public final int maxWindow;
+ field public final int rcvWnd;
+ field public final int rcvWndScale;
+ field public final int rcvWup;
+ field public final int sndWl1;
+ field public final int sndWnd;
+ }
+
+ public final class TestNetworkInterface implements android.os.Parcelable {
+ ctor public TestNetworkInterface(@NonNull android.os.ParcelFileDescriptor, @NonNull String);
+ method public int describeContents();
+ method @NonNull public android.os.ParcelFileDescriptor getFileDescriptor();
+ method @NonNull public String getInterfaceName();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.TestNetworkInterface> CREATOR;
+ }
+
+ public class TestNetworkManager {
+ method @NonNull public android.net.TestNetworkInterface createTapInterface();
+ method @NonNull public android.net.TestNetworkInterface createTunInterface(@NonNull java.util.Collection<android.net.LinkAddress>);
+ method public void setupTestNetwork(@NonNull String, @NonNull android.os.IBinder);
+ method public void teardownTestNetwork(@NonNull android.net.Network);
+ field public static final String TEST_TAP_PREFIX = "testtap";
+ }
+
+ public final class VpnTransportInfo implements android.os.Parcelable android.net.TransportInfo {
+ ctor public VpnTransportInfo(int);
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.VpnTransportInfo> CREATOR;
+ field public final int type;
+ }
+
+}
+
diff --git a/packages/Connectivity/framework/api/module-lib-removed.txt b/packages/Connectivity/framework/api/module-lib-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/packages/Connectivity/framework/api/module-lib-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/packages/Connectivity/framework/api/removed.txt b/packages/Connectivity/framework/api/removed.txt
new file mode 100644
index 0000000..303a1e61
--- /dev/null
+++ b/packages/Connectivity/framework/api/removed.txt
@@ -0,0 +1,11 @@
+// Signature format: 2.0
+package android.net {
+
+ public class ConnectivityManager {
+ method @Deprecated public boolean requestRouteToHost(int, int);
+ method @Deprecated public int startUsingNetworkFeature(int, String);
+ method @Deprecated public int stopUsingNetworkFeature(int, String);
+ }
+
+}
+
diff --git a/packages/Connectivity/framework/api/system-current.txt b/packages/Connectivity/framework/api/system-current.txt
new file mode 100644
index 0000000..41ebc57
--- /dev/null
+++ b/packages/Connectivity/framework/api/system-current.txt
@@ -0,0 +1,407 @@
+// Signature format: 2.0
+package android.net {
+
+ public class CaptivePortal implements android.os.Parcelable {
+ method public void logEvent(int, @NonNull String);
+ method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void reevaluateNetwork();
+ method public void useNetwork();
+ field public static final int APP_REQUEST_REEVALUATION_REQUIRED = 100; // 0x64
+ field public static final int APP_RETURN_DISMISSED = 0; // 0x0
+ field public static final int APP_RETURN_UNWANTED = 1; // 0x1
+ field public static final int APP_RETURN_WANTED_AS_IS = 2; // 0x2
+ }
+
+ public final class CaptivePortalData implements android.os.Parcelable {
+ method public int describeContents();
+ method public long getByteLimit();
+ method public long getExpiryTimeMillis();
+ method public long getRefreshTimeMillis();
+ method @Nullable public android.net.Uri getUserPortalUrl();
+ method public int getUserPortalUrlSource();
+ method @Nullable public String getVenueFriendlyName();
+ method @Nullable public android.net.Uri getVenueInfoUrl();
+ method public int getVenueInfoUrlSource();
+ method public boolean isCaptive();
+ method public boolean isSessionExtendable();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int CAPTIVE_PORTAL_DATA_SOURCE_OTHER = 0; // 0x0
+ field public static final int CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT = 1; // 0x1
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.CaptivePortalData> CREATOR;
+ }
+
+ public static class CaptivePortalData.Builder {
+ ctor public CaptivePortalData.Builder();
+ ctor public CaptivePortalData.Builder(@Nullable android.net.CaptivePortalData);
+ method @NonNull public android.net.CaptivePortalData build();
+ method @NonNull public android.net.CaptivePortalData.Builder setBytesRemaining(long);
+ method @NonNull public android.net.CaptivePortalData.Builder setCaptive(boolean);
+ method @NonNull public android.net.CaptivePortalData.Builder setExpiryTime(long);
+ method @NonNull public android.net.CaptivePortalData.Builder setRefreshTime(long);
+ method @NonNull public android.net.CaptivePortalData.Builder setSessionExtendable(boolean);
+ method @NonNull public android.net.CaptivePortalData.Builder setUserPortalUrl(@Nullable android.net.Uri);
+ method @NonNull public android.net.CaptivePortalData.Builder setUserPortalUrl(@Nullable android.net.Uri, int);
+ method @NonNull public android.net.CaptivePortalData.Builder setVenueFriendlyName(@Nullable String);
+ method @NonNull public android.net.CaptivePortalData.Builder setVenueInfoUrl(@Nullable android.net.Uri);
+ method @NonNull public android.net.CaptivePortalData.Builder setVenueInfoUrl(@Nullable android.net.Uri, int);
+ }
+
+ public class ConnectivityManager {
+ method @NonNull @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) public android.net.SocketKeepalive createNattKeepalive(@NonNull android.net.Network, @NonNull android.os.ParcelFileDescriptor, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback);
+ method @NonNull @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull java.net.Socket, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public String getCaptivePortalServerUrl();
+ method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void getLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEntitlementResultListener);
+ method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported();
+ method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_FACTORY}) public int registerNetworkProvider(@NonNull android.net.NetworkProvider);
+ method public void registerQosCallback(@NonNull android.net.QosSocketInfo, @NonNull android.net.QosCallback, @NonNull java.util.concurrent.Executor);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEventCallback);
+ method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void requestNetwork(@NonNull android.net.NetworkRequest, int, int, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback);
+ method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_AIRPLANE_MODE, android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void setAirplaneMode(boolean);
+ method @RequiresPermission(android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE) public void setOemNetworkPreference(@NonNull android.net.OemNetworkPreferences, @Nullable java.util.concurrent.Executor, @Nullable android.net.ConnectivityManager.OnSetOemNetworkPreferenceListener);
+ method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public boolean shouldAvoidBadWifi();
+ method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void startCaptivePortalApp(@NonNull android.net.Network, @NonNull android.os.Bundle);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback, android.os.Handler);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void stopTethering(int);
+ method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_FACTORY}) public void unregisterNetworkProvider(@NonNull android.net.NetworkProvider);
+ method public void unregisterQosCallback(@NonNull android.net.QosCallback);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void unregisterTetheringEventCallback(@NonNull android.net.ConnectivityManager.OnTetheringEventCallback);
+ field public static final String EXTRA_CAPTIVE_PORTAL_PROBE_SPEC = "android.net.extra.CAPTIVE_PORTAL_PROBE_SPEC";
+ field public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT = "android.net.extra.CAPTIVE_PORTAL_USER_AGENT";
+ field public static final int TETHERING_BLUETOOTH = 2; // 0x2
+ field public static final int TETHERING_USB = 1; // 0x1
+ field public static final int TETHERING_WIFI = 0; // 0x0
+ field @Deprecated public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = 13; // 0xd
+ field @Deprecated public static final int TETHER_ERROR_NO_ERROR = 0; // 0x0
+ field @Deprecated public static final int TETHER_ERROR_PROVISION_FAILED = 11; // 0xb
+ field public static final int TYPE_NONE = -1; // 0xffffffff
+ field @Deprecated public static final int TYPE_PROXY = 16; // 0x10
+ field @Deprecated public static final int TYPE_WIFI_P2P = 13; // 0xd
+ }
+
+ public static interface ConnectivityManager.OnSetOemNetworkPreferenceListener {
+ method public void onComplete();
+ }
+
+ @Deprecated public abstract static class ConnectivityManager.OnStartTetheringCallback {
+ ctor @Deprecated public ConnectivityManager.OnStartTetheringCallback();
+ method @Deprecated public void onTetheringFailed();
+ method @Deprecated public void onTetheringStarted();
+ }
+
+ @Deprecated public static interface ConnectivityManager.OnTetheringEntitlementResultListener {
+ method @Deprecated public void onTetheringEntitlementResult(int);
+ }
+
+ @Deprecated public abstract static class ConnectivityManager.OnTetheringEventCallback {
+ ctor @Deprecated public ConnectivityManager.OnTetheringEventCallback();
+ method @Deprecated public void onUpstreamChanged(@Nullable android.net.Network);
+ }
+
+ public final class InvalidPacketException extends java.lang.Exception {
+ ctor public InvalidPacketException(int);
+ method public int getError();
+ field public static final int ERROR_INVALID_IP_ADDRESS = -21; // 0xffffffeb
+ field public static final int ERROR_INVALID_LENGTH = -23; // 0xffffffe9
+ field public static final int ERROR_INVALID_PORT = -22; // 0xffffffea
+ }
+
+ public final class IpConfiguration implements android.os.Parcelable {
+ ctor public IpConfiguration();
+ ctor public IpConfiguration(@NonNull android.net.IpConfiguration);
+ method public int describeContents();
+ method @Nullable public android.net.ProxyInfo getHttpProxy();
+ method @NonNull public android.net.IpConfiguration.IpAssignment getIpAssignment();
+ method @NonNull public android.net.IpConfiguration.ProxySettings getProxySettings();
+ method @Nullable public android.net.StaticIpConfiguration getStaticIpConfiguration();
+ method public void setHttpProxy(@Nullable android.net.ProxyInfo);
+ method public void setIpAssignment(@NonNull android.net.IpConfiguration.IpAssignment);
+ method public void setProxySettings(@NonNull android.net.IpConfiguration.ProxySettings);
+ method public void setStaticIpConfiguration(@Nullable android.net.StaticIpConfiguration);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.IpConfiguration> CREATOR;
+ }
+
+ public enum IpConfiguration.IpAssignment {
+ enum_constant public static final android.net.IpConfiguration.IpAssignment DHCP;
+ enum_constant public static final android.net.IpConfiguration.IpAssignment STATIC;
+ enum_constant public static final android.net.IpConfiguration.IpAssignment UNASSIGNED;
+ }
+
+ public enum IpConfiguration.ProxySettings {
+ enum_constant public static final android.net.IpConfiguration.ProxySettings NONE;
+ enum_constant public static final android.net.IpConfiguration.ProxySettings PAC;
+ enum_constant public static final android.net.IpConfiguration.ProxySettings STATIC;
+ enum_constant public static final android.net.IpConfiguration.ProxySettings UNASSIGNED;
+ }
+
+ public final class IpPrefix implements android.os.Parcelable {
+ ctor public IpPrefix(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int);
+ ctor public IpPrefix(@NonNull String);
+ }
+
+ public class KeepalivePacketData {
+ ctor protected KeepalivePacketData(@NonNull java.net.InetAddress, @IntRange(from=0, to=65535) int, @NonNull java.net.InetAddress, @IntRange(from=0, to=65535) int, @NonNull byte[]) throws android.net.InvalidPacketException;
+ method @NonNull public java.net.InetAddress getDstAddress();
+ method public int getDstPort();
+ method @NonNull public byte[] getPacket();
+ method @NonNull public java.net.InetAddress getSrcAddress();
+ method public int getSrcPort();
+ }
+
+ public class LinkAddress implements android.os.Parcelable {
+ ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int, int, int);
+ ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int, int, int, long, long);
+ ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int);
+ ctor public LinkAddress(@NonNull String);
+ ctor public LinkAddress(@NonNull String, int, int);
+ method public long getDeprecationTime();
+ method public long getExpirationTime();
+ method public boolean isGlobalPreferred();
+ method public boolean isIpv4();
+ method public boolean isIpv6();
+ method public boolean isSameAddressAs(@Nullable android.net.LinkAddress);
+ field public static final long LIFETIME_PERMANENT = 9223372036854775807L; // 0x7fffffffffffffffL
+ field public static final long LIFETIME_UNKNOWN = -1L; // 0xffffffffffffffffL
+ }
+
+ public final class LinkProperties implements android.os.Parcelable {
+ ctor public LinkProperties(@Nullable android.net.LinkProperties);
+ ctor public LinkProperties(@Nullable android.net.LinkProperties, boolean);
+ method public boolean addDnsServer(@NonNull java.net.InetAddress);
+ method public boolean addLinkAddress(@NonNull android.net.LinkAddress);
+ method public boolean addPcscfServer(@NonNull java.net.InetAddress);
+ method @NonNull public java.util.List<java.net.InetAddress> getAddresses();
+ method @NonNull public java.util.List<java.lang.String> getAllInterfaceNames();
+ method @NonNull public java.util.List<android.net.LinkAddress> getAllLinkAddresses();
+ method @NonNull public java.util.List<android.net.RouteInfo> getAllRoutes();
+ method @Nullable public android.net.Uri getCaptivePortalApiUrl();
+ method @Nullable public android.net.CaptivePortalData getCaptivePortalData();
+ method @NonNull public java.util.List<java.net.InetAddress> getPcscfServers();
+ method @Nullable public String getTcpBufferSizes();
+ method @NonNull public java.util.List<java.net.InetAddress> getValidatedPrivateDnsServers();
+ method public boolean hasGlobalIpv6Address();
+ method public boolean hasIpv4Address();
+ method public boolean hasIpv4DefaultRoute();
+ method public boolean hasIpv4DnsServer();
+ method public boolean hasIpv6DefaultRoute();
+ method public boolean hasIpv6DnsServer();
+ method public boolean isIpv4Provisioned();
+ method public boolean isIpv6Provisioned();
+ method public boolean isProvisioned();
+ method public boolean isReachable(@NonNull java.net.InetAddress);
+ method public boolean removeDnsServer(@NonNull java.net.InetAddress);
+ method public boolean removeLinkAddress(@NonNull android.net.LinkAddress);
+ method public boolean removeRoute(@NonNull android.net.RouteInfo);
+ method public void setCaptivePortalApiUrl(@Nullable android.net.Uri);
+ method public void setCaptivePortalData(@Nullable android.net.CaptivePortalData);
+ method public void setPcscfServers(@NonNull java.util.Collection<java.net.InetAddress>);
+ method public void setPrivateDnsServerName(@Nullable String);
+ method public void setTcpBufferSizes(@Nullable String);
+ method public void setUsePrivateDns(boolean);
+ method public void setValidatedPrivateDnsServers(@NonNull java.util.Collection<java.net.InetAddress>);
+ }
+
+ public final class NattKeepalivePacketData extends android.net.KeepalivePacketData implements android.os.Parcelable {
+ ctor public NattKeepalivePacketData(@NonNull java.net.InetAddress, int, @NonNull java.net.InetAddress, int, @NonNull byte[]) throws android.net.InvalidPacketException;
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.NattKeepalivePacketData> CREATOR;
+ }
+
+ public class Network implements android.os.Parcelable {
+ ctor public Network(@NonNull android.net.Network);
+ method public int getNetId();
+ method @NonNull public android.net.Network getPrivateDnsBypassingCopy();
+ }
+
+ public abstract class NetworkAgent {
+ ctor public NetworkAgent(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String, @NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties, int, @NonNull android.net.NetworkAgentConfig, @Nullable android.net.NetworkProvider);
+ method @Nullable public android.net.Network getNetwork();
+ method public void markConnected();
+ method public void onAddKeepalivePacketFilter(int, @NonNull android.net.KeepalivePacketData);
+ method public void onAutomaticReconnectDisabled();
+ method public void onNetworkUnwanted();
+ method public void onQosCallbackRegistered(int, @NonNull android.net.QosFilter);
+ method public void onQosCallbackUnregistered(int);
+ method public void onRemoveKeepalivePacketFilter(int);
+ method public void onSaveAcceptUnvalidated(boolean);
+ method public void onSignalStrengthThresholdsUpdated(@NonNull int[]);
+ method public void onStartSocketKeepalive(int, @NonNull java.time.Duration, @NonNull android.net.KeepalivePacketData);
+ method public void onStopSocketKeepalive(int);
+ method public void onValidationStatus(int, @Nullable android.net.Uri);
+ method @NonNull public android.net.Network register();
+ method public final void sendLinkProperties(@NonNull android.net.LinkProperties);
+ method public final void sendNetworkCapabilities(@NonNull android.net.NetworkCapabilities);
+ method public final void sendNetworkScore(@IntRange(from=0, to=99) int);
+ method public final void sendQosCallbackError(int, int);
+ method public final void sendQosSessionAvailable(int, int, @NonNull android.telephony.data.EpsBearerQosSessionAttributes);
+ method public final void sendQosSessionLost(int, int);
+ method public final void sendSocketKeepaliveEvent(int, int);
+ method public final void setUnderlyingNetworks(@Nullable java.util.List<android.net.Network>);
+ method public void unregister();
+ field public static final int VALIDATION_STATUS_NOT_VALID = 2; // 0x2
+ field public static final int VALIDATION_STATUS_VALID = 1; // 0x1
+ }
+
+ public final class NetworkAgentConfig implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getLegacyType();
+ method @NonNull public String getLegacyTypeName();
+ method public boolean isExplicitlySelected();
+ method public boolean isPartialConnectivityAcceptable();
+ method public boolean isUnvalidatedConnectivityAcceptable();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkAgentConfig> CREATOR;
+ }
+
+ public static final class NetworkAgentConfig.Builder {
+ ctor public NetworkAgentConfig.Builder();
+ method @NonNull public android.net.NetworkAgentConfig build();
+ method @NonNull public android.net.NetworkAgentConfig.Builder setExplicitlySelected(boolean);
+ method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyType(int);
+ method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyTypeName(@NonNull String);
+ method @NonNull public android.net.NetworkAgentConfig.Builder setPartialConnectivityAcceptable(boolean);
+ method @NonNull public android.net.NetworkAgentConfig.Builder setUnvalidatedConnectivityAcceptable(boolean);
+ }
+
+ public final class NetworkCapabilities implements android.os.Parcelable {
+ ctor public NetworkCapabilities(@Nullable android.net.NetworkCapabilities, boolean);
+ method @NonNull public int[] getAdministratorUids();
+ method @Nullable public String getSsid();
+ method @NonNull public int[] getTransportTypes();
+ method public boolean isPrivateDnsBroken();
+ method public boolean satisfiedByNetworkCapabilities(@Nullable android.net.NetworkCapabilities);
+ field public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28; // 0x1c
+ field public static final int NET_CAPABILITY_OEM_PAID = 22; // 0x16
+ field public static final int NET_CAPABILITY_OEM_PRIVATE = 26; // 0x1a
+ field public static final int NET_CAPABILITY_PARTIAL_CONNECTIVITY = 24; // 0x18
+ field public static final int NET_CAPABILITY_VEHICLE_INTERNAL = 27; // 0x1b
+ }
+
+ public static final class NetworkCapabilities.Builder {
+ ctor public NetworkCapabilities.Builder();
+ ctor public NetworkCapabilities.Builder(@NonNull android.net.NetworkCapabilities);
+ method @NonNull public android.net.NetworkCapabilities.Builder addCapability(int);
+ method @NonNull public android.net.NetworkCapabilities.Builder addTransportType(int);
+ method @NonNull public android.net.NetworkCapabilities build();
+ method @NonNull public android.net.NetworkCapabilities.Builder clearAll();
+ method @NonNull public android.net.NetworkCapabilities.Builder removeCapability(int);
+ method @NonNull public android.net.NetworkCapabilities.Builder removeTransportType(int);
+ method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setAdministratorUids(@NonNull int[]);
+ method @NonNull public android.net.NetworkCapabilities.Builder setLinkDownstreamBandwidthKbps(int);
+ method @NonNull public android.net.NetworkCapabilities.Builder setLinkUpstreamBandwidthKbps(int);
+ method @NonNull public android.net.NetworkCapabilities.Builder setNetworkSpecifier(@Nullable android.net.NetworkSpecifier);
+ method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setOwnerUid(int);
+ method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setRequestorPackageName(@Nullable String);
+ method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setRequestorUid(int);
+ method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkCapabilities.Builder setSignalStrength(int);
+ method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setSsid(@Nullable String);
+ method @NonNull public android.net.NetworkCapabilities.Builder setTransportInfo(@Nullable android.net.TransportInfo);
+ }
+
+ public class NetworkProvider {
+ ctor public NetworkProvider(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String);
+ method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void declareNetworkRequestUnfulfillable(@NonNull android.net.NetworkRequest);
+ method public int getProviderId();
+ method public void onNetworkRequestWithdrawn(@NonNull android.net.NetworkRequest);
+ method public void onNetworkRequested(@NonNull android.net.NetworkRequest, @IntRange(from=0, to=99) int, int);
+ field public static final int ID_NONE = -1; // 0xffffffff
+ }
+
+ public class NetworkRequest implements android.os.Parcelable {
+ method @Nullable public String getRequestorPackageName();
+ method public int getRequestorUid();
+ }
+
+ public static class NetworkRequest.Builder {
+ method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkRequest.Builder setSignalStrength(int);
+ }
+
+ public final class RouteInfo implements android.os.Parcelable {
+ ctor public RouteInfo(@Nullable android.net.IpPrefix, @Nullable java.net.InetAddress, @Nullable String, int);
+ ctor public RouteInfo(@Nullable android.net.IpPrefix, @Nullable java.net.InetAddress, @Nullable String, int, int);
+ method public int getMtu();
+ method public int getType();
+ field public static final int RTN_THROW = 9; // 0x9
+ field public static final int RTN_UNICAST = 1; // 0x1
+ field public static final int RTN_UNREACHABLE = 7; // 0x7
+ }
+
+ public abstract class SocketKeepalive implements java.lang.AutoCloseable {
+ field public static final int SUCCESS = 0; // 0x0
+ }
+
+ public final class StaticIpConfiguration implements android.os.Parcelable {
+ ctor public StaticIpConfiguration();
+ ctor public StaticIpConfiguration(@Nullable android.net.StaticIpConfiguration);
+ method public void addDnsServer(@NonNull java.net.InetAddress);
+ method public void clear();
+ method public int describeContents();
+ method @NonNull public java.util.List<java.net.InetAddress> getDnsServers();
+ method @Nullable public String getDomains();
+ method @Nullable public java.net.InetAddress getGateway();
+ method @Nullable public android.net.LinkAddress getIpAddress();
+ method @NonNull public java.util.List<android.net.RouteInfo> getRoutes(@Nullable String);
+ method public void writeToParcel(android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.StaticIpConfiguration> CREATOR;
+ }
+
+ public static final class StaticIpConfiguration.Builder {
+ ctor public StaticIpConfiguration.Builder();
+ method @NonNull public android.net.StaticIpConfiguration build();
+ method @NonNull public android.net.StaticIpConfiguration.Builder setDnsServers(@NonNull Iterable<java.net.InetAddress>);
+ method @NonNull public android.net.StaticIpConfiguration.Builder setDomains(@Nullable String);
+ method @NonNull public android.net.StaticIpConfiguration.Builder setGateway(@Nullable java.net.InetAddress);
+ method @NonNull public android.net.StaticIpConfiguration.Builder setIpAddress(@Nullable android.net.LinkAddress);
+ }
+
+ public final class TcpKeepalivePacketData extends android.net.KeepalivePacketData implements android.os.Parcelable {
+ ctor public TcpKeepalivePacketData(@NonNull java.net.InetAddress, int, @NonNull java.net.InetAddress, int, @NonNull byte[], int, int, int, int, int, int) throws android.net.InvalidPacketException;
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.TcpKeepalivePacketData> CREATOR;
+ field public final int ipTos;
+ field public final int ipTtl;
+ field public final int tcpAck;
+ field public final int tcpSeq;
+ field public final int tcpWindow;
+ field public final int tcpWindowScale;
+ }
+
+ public interface TransportInfo {
+ method public default boolean hasLocationSensitiveFields();
+ method @NonNull public default android.net.TransportInfo makeCopy(boolean);
+ }
+
+}
+
+package android.net.apf {
+
+ public final class ApfCapabilities implements android.os.Parcelable {
+ ctor public ApfCapabilities(int, int, int);
+ method public int describeContents();
+ method public static boolean getApfDrop8023Frames();
+ method @NonNull public static int[] getApfEtherTypeBlackList();
+ method public boolean hasDataAccess();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.net.apf.ApfCapabilities> CREATOR;
+ field public final int apfPacketFormat;
+ field public final int apfVersionSupported;
+ field public final int maximumApfProgramSize;
+ }
+
+}
+
+package android.net.util {
+
+ public final class SocketUtils {
+ method public static void bindSocketToInterface(@NonNull java.io.FileDescriptor, @NonNull String) throws android.system.ErrnoException;
+ method public static void closeSocket(@Nullable java.io.FileDescriptor) throws java.io.IOException;
+ method @NonNull public static java.net.SocketAddress makeNetlinkSocketAddress(int, int);
+ method @NonNull public static java.net.SocketAddress makePacketSocketAddress(int, int);
+ method @Deprecated @NonNull public static java.net.SocketAddress makePacketSocketAddress(int, @NonNull byte[]);
+ method @NonNull public static java.net.SocketAddress makePacketSocketAddress(int, int, @NonNull byte[]);
+ }
+
+}
+
diff --git a/packages/Connectivity/framework/api/system-lint-baseline.txt b/packages/Connectivity/framework/api/system-lint-baseline.txt
new file mode 100644
index 0000000..9a97707
--- /dev/null
+++ b/packages/Connectivity/framework/api/system-lint-baseline.txt
@@ -0,0 +1 @@
+// Baseline format: 1.0
diff --git a/packages/Connectivity/framework/api/system-removed.txt b/packages/Connectivity/framework/api/system-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/packages/Connectivity/framework/api/system-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/packages/Connectivity/framework/src/android/net/NetworkRequest.java b/packages/Connectivity/framework/src/android/net/NetworkRequest.java
index 4e3085f..b4a651c 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkRequest.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkRequest.java
@@ -16,6 +16,22 @@
package android.net;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -30,6 +46,8 @@
import android.text.TextUtils;
import android.util.proto.ProtoOutputStream;
+import java.util.Arrays;
+import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -154,8 +172,30 @@
* needed in terms of {@link NetworkCapabilities} features
*/
public static class Builder {
+ /**
+ * Capabilities that are currently compatible with VCN networks.
+ */
+ private static final List<Integer> VCN_SUPPORTED_CAPABILITIES = Arrays.asList(
+ NET_CAPABILITY_CAPTIVE_PORTAL,
+ NET_CAPABILITY_DUN,
+ NET_CAPABILITY_FOREGROUND,
+ NET_CAPABILITY_INTERNET,
+ NET_CAPABILITY_NOT_CONGESTED,
+ NET_CAPABILITY_NOT_METERED,
+ NET_CAPABILITY_NOT_RESTRICTED,
+ NET_CAPABILITY_NOT_ROAMING,
+ NET_CAPABILITY_NOT_SUSPENDED,
+ NET_CAPABILITY_NOT_VPN,
+ NET_CAPABILITY_PARTIAL_CONNECTIVITY,
+ NET_CAPABILITY_TEMPORARILY_NOT_METERED,
+ NET_CAPABILITY_TRUSTED,
+ NET_CAPABILITY_VALIDATED);
+
private final NetworkCapabilities mNetworkCapabilities;
+ // A boolean that represents the user modified NOT_VCN_MANAGED capability.
+ private boolean mModifiedNotVcnManaged = false;
+
/**
* Default constructor for Builder.
*/
@@ -177,6 +217,7 @@
// maybeMarkCapabilitiesRestricted() doesn't add back.
final NetworkCapabilities nc = new NetworkCapabilities(mNetworkCapabilities);
nc.maybeMarkCapabilitiesRestricted();
+ deduceNotVcnManagedCapability(nc);
return new NetworkRequest(nc, ConnectivityManager.TYPE_NONE,
ConnectivityManager.REQUEST_ID_UNSET, Type.NONE);
}
@@ -193,6 +234,9 @@
*/
public Builder addCapability(@NetworkCapabilities.NetCapability int capability) {
mNetworkCapabilities.addCapability(capability);
+ if (capability == NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED) {
+ mModifiedNotVcnManaged = true;
+ }
return this;
}
@@ -204,6 +248,9 @@
*/
public Builder removeCapability(@NetworkCapabilities.NetCapability int capability) {
mNetworkCapabilities.removeCapability(capability);
+ if (capability == NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED) {
+ mModifiedNotVcnManaged = true;
+ }
return this;
}
@@ -261,6 +308,9 @@
@NonNull
public Builder clearCapabilities() {
mNetworkCapabilities.clearAll();
+ // If the caller explicitly clear all capabilities, the NOT_VCN_MANAGED capabilities
+ // should not be add back later.
+ mModifiedNotVcnManaged = true;
return this;
}
@@ -380,6 +430,25 @@
mNetworkCapabilities.setSignalStrength(signalStrength);
return this;
}
+
+ /**
+ * Deduce the NET_CAPABILITY_NOT_VCN_MANAGED capability from other capabilities
+ * and user intention, which includes:
+ * 1. For the requests that don't have anything besides
+ * {@link #VCN_SUPPORTED_CAPABILITIES}, add the NET_CAPABILITY_NOT_VCN_MANAGED to
+ * allow the callers automatically utilize VCN networks if available.
+ * 2. For the requests that explicitly add or remove NET_CAPABILITY_NOT_VCN_MANAGED,
+ * do not alter them to allow user fire request that suits their need.
+ *
+ * @hide
+ */
+ private void deduceNotVcnManagedCapability(final NetworkCapabilities nc) {
+ if (mModifiedNotVcnManaged) return;
+ for (final int cap : nc.getCapabilities()) {
+ if (!VCN_SUPPORTED_CAPABILITIES.contains(cap)) return;
+ }
+ nc.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
+ }
}
// implement the Parcelable interface
diff --git a/packages/Connectivity/framework/src/android/net/util/MultinetworkPolicyTracker.java b/packages/Connectivity/framework/src/android/net/util/MultinetworkPolicyTracker.java
index 85e3fa3..43fffd7 100644
--- a/packages/Connectivity/framework/src/android/net/util/MultinetworkPolicyTracker.java
+++ b/packages/Connectivity/framework/src/android/net/util/MultinetworkPolicyTracker.java
@@ -40,6 +40,8 @@
import java.util.Arrays;
import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
/**
* A class to encapsulate management of the "Smart Networking" capability of
@@ -73,6 +75,32 @@
private volatile int mMeteredMultipathPreference;
private int mActiveSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ // Mainline module can't use internal HandlerExecutor, so add an identical executor here.
+ private static class HandlerExecutor implements Executor {
+ @NonNull
+ private final Handler mHandler;
+
+ HandlerExecutor(@NonNull Handler handler) {
+ mHandler = handler;
+ }
+ @Override
+ public void execute(Runnable command) {
+ if (!mHandler.post(command)) {
+ throw new RejectedExecutionException(mHandler + " is shutting down");
+ }
+ }
+ }
+
+ @VisibleForTesting
+ protected class ActiveDataSubscriptionIdChangedListener extends PhoneStateListener
+ implements PhoneStateListener.ActiveDataSubscriptionIdChangedListener {
+ @Override
+ public void onActiveDataSubscriptionIdChanged(int subId) {
+ mActiveSubId = subId;
+ reevaluateInternal();
+ }
+ }
+
public MultinetworkPolicyTracker(Context ctx, Handler handler) {
this(ctx, handler, null);
}
@@ -93,14 +121,8 @@
}
};
- ctx.getSystemService(TelephonyManager.class).listen(
- new PhoneStateListener(handler.getLooper()) {
- @Override
- public void onActiveDataSubscriptionIdChanged(int subId) {
- mActiveSubId = subId;
- reevaluateInternal();
- }
- }, PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
+ ctx.getSystemService(TelephonyManager.class).registerPhoneStateListener(
+ new HandlerExecutor(handler), new ActiveDataSubscriptionIdChangedListener());
updateAvoidBadWifi();
updateMeteredMultipathPreference();
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index f67e039..71e0910 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -84,8 +84,6 @@
<uses-permission android:name="android.permission.READ_INPUT_STATE" />
<uses-permission android:name="android.permission.SET_ORIENTATION" />
<uses-permission android:name="android.permission.INSTALL_PACKAGES" />
- <!-- TODO(b/152310230): remove once APIs are confirmed to be sufficient -->
- <uses-permission android:name="com.android.permission.USE_INSTALLER_V2" />
<uses-permission android:name="com.android.permission.USE_SYSTEM_DATA_LOADERS" />
<uses-permission android:name="android.permission.MOVE_PACKAGE" />
<uses-permission android:name="android.permission.KEEP_UNINSTALLED_PACKAGES" />
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java
index bcd28a6..53f7e44 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java
@@ -50,6 +50,4 @@
public abstract void onStateChanged(State state);
public abstract int getDetailY();
-
- public void setShowLabels(boolean show) {}
}
diff --git a/packages/SystemUI/res/drawable/control_no_favorites_background.xml b/packages/SystemUI/res/drawable/control_no_favorites_background.xml
index d895dd0..2165b12 100644
--- a/packages/SystemUI/res/drawable/control_no_favorites_background.xml
+++ b/packages/SystemUI/res/drawable/control_no_favorites_background.xml
@@ -26,12 +26,4 @@
<corners android:radius="@dimen/control_corner_radius" />
</shape>
</item>
- <item>
- <shape>
- <stroke
- android:width="1dp"
- android:color="#4DFFFFFF" />
- <corners android:radius="@dimen/control_corner_radius"/>
- </shape>
- </item>
</ripple>
diff --git a/packages/SystemUI/res/drawable/controls_dialog_bg.xml b/packages/SystemUI/res/drawable/controls_dialog_bg.xml
index cb4686dd..1ccb176 100644
--- a/packages/SystemUI/res/drawable/controls_dialog_bg.xml
+++ b/packages/SystemUI/res/drawable/controls_dialog_bg.xml
@@ -16,6 +16,6 @@
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
- <solid android:color="?android:attr/colorBackgroundFloating" />
+ <solid android:color="?android:attr/colorBackground" />
<corners android:radius="@dimen/notification_corner_radius" />
</shape>
diff --git a/packages/SystemUI/res/drawable/horizontal_ellipsis.xml b/packages/SystemUI/res/drawable/horizontal_ellipsis.xml
new file mode 100644
index 0000000..1800857
--- /dev/null
+++ b/packages/SystemUI/res/drawable/horizontal_ellipsis.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorBackgroundFloating" >
+
+ <path
+ android:pathData="M6 10c-1.1 0-2 0.9-2 2s0.9 2 2 2 2-0.9 2-2-0.9-2-2-2zm12 0c-1.1 0-2 0.9-2 2s0.9 2 2 2 2-0.9 2-2-0.9-2-2-2zm-6 0c-1.1 0-2 0.9-2 2s0.9 2 2 2 2-0.9 2-2-0.9-2-2-2z"
+ android:fillColor="@android:color/white" />
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_tile_label_divider.xml b/packages/SystemUI/res/drawable/volume_drawer_bg.xml
similarity index 61%
rename from packages/SystemUI/res/layout/qs_tile_label_divider.xml
rename to packages/SystemUI/res/drawable/volume_drawer_bg.xml
index 150a5b8..f0e2292 100644
--- a/packages/SystemUI/res/layout/qs_tile_label_divider.xml
+++ b/packages/SystemUI/res/drawable/volume_drawer_bg.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2020 The Android Open Source Project
+ ~ Copyright (C) 2021 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.
@@ -14,5 +14,9 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
-<View />
\ No newline at end of file
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:paddingMode="stack" >
+ <size android:width="@dimen/volume_ringer_drawer_item_size" />
+ <solid android:color="?android:attr/colorBackgroundFloating" />
+ <corners android:radius="@dimen/volume_ringer_drawer_item_size_half" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/volume_drawer_selection_bg.xml b/packages/SystemUI/res/drawable/volume_drawer_selection_bg.xml
new file mode 100644
index 0000000..5e7cb12
--- /dev/null
+++ b/packages/SystemUI/res/drawable/volume_drawer_selection_bg.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 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.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:paddingMode="stack" >
+ <size
+ android:height="@dimen/volume_ringer_drawer_item_size"
+ android:width="@dimen/volume_ringer_drawer_item_size" />
+ <solid android:color="?android:attr/colorAccent" />
+ <corners android:radius="@dimen/volume_ringer_drawer_item_size_half" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/volume_row_seekbar.xml b/packages/SystemUI/res/drawable/volume_row_seekbar.xml
new file mode 100644
index 0000000..b0e0ed5
--- /dev/null
+++ b/packages/SystemUI/res/drawable/volume_row_seekbar.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 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.
+ -->
+
+<!-- SeekBar drawable for volume rows. This contains a background layer (with a solid round rect,
+ and a bottom-aligned icon) and a progress layer (with an accent-colored round rect and icon)
+ that moves up and down with the progress value. -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
+ android:paddingMode="stack" >
+ <item android:id="@android:id/background"
+ android:gravity="center_vertical|fill_horizontal">
+ <layer-list>
+ <item android:id="@+id/volume_seekbar_background_solid">
+ <shape>
+ <size android:height="@dimen/volume_dialog_panel_width" />
+ <solid android:color="?android:attr/colorBackgroundFloating" />
+ <corners android:radius="@dimen/volume_dialog_panel_width_half" />
+ </shape>
+ </item>
+ <item
+ android:id="@+id/volume_seekbar_background_icon"
+ android:gravity="center_vertical|left"
+ android:height="@dimen/rounded_slider_icon_size"
+ android:width="@dimen/rounded_slider_icon_size"
+ android:left="@dimen/rounded_slider_icon_inset">
+ <rotate
+ android:fromDegrees="-270"
+ android:toDegrees="-270">
+ <!-- A placeholder drawable is required here - it'll be replaced in code. -->
+ <com.android.systemui.util.AlphaTintDrawableWrapper
+ android:drawable="@drawable/ic_volume_media"
+ android:tint="?android:attr/colorAccent" />
+ </rotate>
+ </item>
+ </layer-list>
+ </item>
+ <item android:id="@android:id/progress"
+ android:gravity="center_vertical|fill_horizontal">
+ <com.android.systemui.util.RoundedCornerProgressDrawable
+ android:drawable="@drawable/volume_row_seekbar_progress"
+ />
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/volume_row_seekbar_progress.xml b/packages/SystemUI/res/drawable/volume_row_seekbar_progress.xml
new file mode 100644
index 0000000..ef20236
--- /dev/null
+++ b/packages/SystemUI/res/drawable/volume_row_seekbar_progress.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 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.
+ -->
+
+<!-- Progress drawable for volume row SeekBars. This is the accent-colored round rect that moves up
+ and down as the progress value changes. -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
+ android:autoMirrored="true">
+ <item android:id="@+id/volume_seekbar_progress_solid">
+ <shape>
+ <size android:height="@dimen/volume_dialog_panel_width" />
+ <solid android:color="?android:attr/colorAccent" />
+ <corners android:radius="@dimen/volume_dialog_panel_width_half"/>
+ </shape>
+ </item>
+ <item
+ android:id="@+id/volume_seekbar_progress_icon"
+ android:gravity="center_vertical|right"
+ android:height="@dimen/rounded_slider_icon_size"
+ android:width="@dimen/rounded_slider_icon_size"
+ android:right="@dimen/rounded_slider_icon_inset">
+ <rotate
+ android:fromDegrees="-270"
+ android:toDegrees="-270">
+ <!-- A placeholder drawable is required here - it'll be replaced in code. -->
+ <com.android.systemui.util.AlphaTintDrawableWrapper
+ android:drawable="@drawable/ic_volume_media"
+ android:tint="?android:attr/colorBackgroundFloating" />
+ </rotate>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout-land/volume_dialog.xml b/packages/SystemUI/res/layout-land/volume_dialog.xml
index c420117..237dc02 100644
--- a/packages/SystemUI/res/layout-land/volume_dialog.xml
+++ b/packages/SystemUI/res/layout-land/volume_dialog.xml
@@ -32,105 +32,124 @@
android:gravity="right"
android:layout_gravity="right"
android:background="@android:color/transparent"
- android:paddingRight="@dimen/volume_dialog_panel_transparent_padding_right"
- android:paddingTop="@dimen/volume_dialog_panel_transparent_padding"
- android:paddingBottom="@dimen/volume_dialog_panel_transparent_padding"
+ android:paddingRight="@dimen/volume_dialog_stream_padding"
android:paddingLeft="@dimen/volume_dialog_panel_transparent_padding"
android:clipToPadding="false">
- <FrameLayout
- android:id="@+id/ringer"
- android:layout_width="@dimen/volume_dialog_ringer_size"
- android:layout_height="@dimen/volume_dialog_ringer_size"
- android:layout_marginBottom="@dimen/volume_dialog_spacer"
- android:gravity="right"
- android:layout_gravity="right"
- android:translationZ="@dimen/volume_dialog_elevation"
- android:clipToPadding="false"
- android:background="@drawable/rounded_bg_full">
- <com.android.keyguard.AlphaOptimizedImageButton
- android:id="@+id/ringer_icon"
- style="@style/VolumeButtons"
- android:background="@drawable/rounded_ripple"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:scaleType="fitCenter"
- android:padding="@dimen/volume_dialog_ringer_icon_padding"
- android:tint="@color/accent_tint_color_selector"
- android:layout_gravity="center"
- android:soundEffectsEnabled="false" />
-
- <include layout="@layout/volume_dnd_icon"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginRight="@dimen/volume_dialog_stream_padding"
- android:layout_marginTop="6dp"/>
- </FrameLayout>
-
+ <!--
+ Container for a) the ringer drawer and the caption button next to b) the volume rows.
+ -->
<LinearLayout
- android:id="@+id/main"
android:layout_width="wrap_content"
- android:minWidth="@dimen/volume_dialog_panel_width"
android:layout_height="wrap_content"
- android:layout_marginTop="68dp"
- android:gravity="right"
- android:layout_gravity="right"
- android:orientation="vertical"
- android:translationZ="@dimen/volume_dialog_elevation"
+ android:orientation="horizontal"
android:clipChildren="false"
- android:clipToPadding="false"
- android:background="@drawable/rounded_bg_full" >
- <LinearLayout
- android:id="@+id/volume_dialog_rows"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:minWidth="@dimen/volume_dialog_panel_width"
- android:gravity="center"
- android:orientation="horizontal"
- android:paddingRight="@dimen/volume_dialog_stream_padding"
- android:paddingLeft="@dimen/volume_dialog_stream_padding">
- <!-- volume rows added and removed here! :-) -->
- </LinearLayout>
- <FrameLayout
- android:id="@+id/settings_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="@drawable/rounded_bg_bottom_background">
- <com.android.keyguard.AlphaOptimizedImageButton
- android:id="@+id/settings"
- android:src="@drawable/ic_tune_black_16dp"
- android:layout_width="@dimen/volume_dialog_tap_target_size"
- android:layout_height="@dimen/volume_dialog_tap_target_size"
- android:layout_gravity="center"
- android:contentDescription="@string/accessibility_volume_settings"
- android:background="@drawable/ripple_drawable_20dp"
- android:tint="?android:attr/textColorSecondary"
- android:soundEffectsEnabled="false" />
- </FrameLayout>
- </LinearLayout>
+ android:clipToPadding="false">
- <FrameLayout
- android:id="@+id/odi_captions"
- android:layout_width="@dimen/volume_dialog_caption_size"
- android:layout_height="@dimen/volume_dialog_caption_size"
- android:layout_marginRight="68dp"
- android:gravity="right"
- android:layout_gravity="right"
- android:clipToPadding="false"
- android:translationZ="@dimen/volume_dialog_elevation"
- android:background="@drawable/rounded_bg_full">
- <com.android.systemui.volume.CaptionsToggleImageButton
- android:id="@+id/odi_captions_icon"
- android:src="@drawable/ic_volume_odi_captions_disabled"
- style="@style/VolumeButtons"
- android:background="@drawable/rounded_ripple"
- android:layout_width="match_parent"
+ <!-- The ringer drawer and the caption button. -->
+ <FrameLayout
+ android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:tint="@color/caption_tint_color_selector"
- android:layout_gravity="center"
- android:soundEffectsEnabled="false"
- sysui:optedOut="false"/>
- </FrameLayout>
+ android:paddingRight="@dimen/volume_dialog_stream_padding"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:orientation="vertical">
+
+ <include layout="@layout/volume_ringer_drawer"
+ android:layout_gravity="top|right"/>
+
+ <FrameLayout
+ android:id="@+id/odi_captions"
+ android:layout_width="@dimen/volume_dialog_caption_size"
+ android:layout_height="@dimen/volume_dialog_caption_size"
+ android:gravity="center"
+ android:layout_gravity="bottom|right"
+ android:layout_marginBottom="@dimen/volume_dialog_tap_target_size"
+ android:clipToPadding="false">
+
+ <com.android.systemui.volume.CaptionsToggleImageButton
+ android:id="@+id/odi_captions_icon"
+ android:src="@drawable/ic_volume_odi_captions_disabled"
+ style="@style/VolumeButtons"
+ android:background="@drawable/rounded_ripple"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:tint="@color/caption_tint_color_selector"
+ android:layout_gravity="center"
+ android:soundEffectsEnabled="false"
+ sysui:optedOut="false"/>
+
+ </FrameLayout>
+
+ </FrameLayout>
+
+ <FrameLayout
+ android:visibility="gone"
+ android:id="@+id/ringer"
+ android:layout_width="@dimen/volume_dialog_ringer_size"
+ android:layout_height="@dimen/volume_dialog_ringer_size"
+ android:layout_marginBottom="@dimen/volume_dialog_spacer"
+ android:gravity="right"
+ android:layout_gravity="right"
+ android:translationZ="@dimen/volume_dialog_elevation"
+ android:clipToPadding="false"
+ android:background="@drawable/rounded_bg_full">
+ <com.android.keyguard.AlphaOptimizedImageButton
+ android:id="@+id/ringer_icon"
+ style="@style/VolumeButtons"
+ android:background="@drawable/rounded_ripple"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scaleType="fitCenter"
+ android:padding="@dimen/volume_dialog_ringer_icon_padding"
+ android:tint="@color/accent_tint_color_selector"
+ android:layout_gravity="center"
+ android:soundEffectsEnabled="false" />
+
+ <include layout="@layout/volume_dnd_icon"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="@dimen/volume_dialog_stream_padding"
+ android:layout_marginTop="6dp"/>
+ </FrameLayout>
+
+ <LinearLayout
+ android:id="@+id/main"
+ android:layout_width="wrap_content"
+ android:minWidth="@dimen/volume_dialog_panel_width"
+ android:layout_height="wrap_content"
+ android:gravity="right"
+ android:layout_gravity="right"
+ android:orientation="vertical"
+ android:clipChildren="false"
+ android:clipToPadding="false">
+ <LinearLayout
+ android:id="@+id/volume_dialog_rows"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minWidth="@dimen/volume_dialog_panel_width"
+ android:gravity="center"
+ android:orientation="horizontal">
+ <!-- volume rows added and removed here! :-) -->
+ </LinearLayout>
+ <FrameLayout
+ android:id="@+id/settings_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <com.android.keyguard.AlphaOptimizedImageButton
+ android:id="@+id/settings"
+ android:src="@drawable/horizontal_ellipsis"
+ android:layout_width="@dimen/volume_dialog_tap_target_size"
+ android:layout_height="@dimen/volume_dialog_tap_target_size"
+ android:layout_gravity="center"
+ android:contentDescription="@string/accessibility_volume_settings"
+ android:background="@drawable/ripple_drawable_20dp"
+ android:tint="?android:attr/colorBackgroundFloating"
+ android:soundEffectsEnabled="false" />
+ </FrameLayout>
+ </LinearLayout>
+
+ </LinearLayout>
<ViewStub
android:id="@+id/odi_captions_tooltip_stub"
diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index 0822947..93dd1a1 100644
--- a/packages/SystemUI/res/layout/qs_footer_impl.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -81,6 +81,22 @@
android:layout_height="match_parent"
android:layout_weight="@integer/qs_footer_actions_weight"
android:gravity="center_vertical|end" >
+
+ <com.android.systemui.statusbar.AlphaOptimizedImageView
+ android:id="@+id/pm_lite"
+ android:layout_width="@dimen/qs_footer_action_button_size"
+ android:layout_height="@dimen/qs_footer_action_button_size"
+ android:background="?android:attr/selectableItemBackgroundBorderless"
+ android:clickable="true"
+ android:clipToPadding="false"
+ android:contentDescription="@string/accessibility_quick_settings_edit"
+ android:focusable="true"
+ android:padding="@dimen/qs_footer_icon_padding"
+ android:src="@*android:drawable/ic_lock_power_off"
+ android:tint="?android:attr/colorForeground"
+ android:visibility="gone"
+ />
+
<com.android.systemui.statusbar.phone.MultiUserSwitch
android:id="@+id/multi_user_switch"
android:layout_width="@dimen/qs_footer_action_button_size"
diff --git a/packages/SystemUI/res/layout/qs_paged_page_side_labels.xml b/packages/SystemUI/res/layout/qs_paged_page_side_labels.xml
index ee54f1d..c830773 100644
--- a/packages/SystemUI/res/layout/qs_paged_page_side_labels.xml
+++ b/packages/SystemUI/res/layout/qs_paged_page_side_labels.xml
@@ -14,4 +14,10 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<include layout="@layout/qs_paged_page" />
+<com.android.systemui.qs.SideLabelTileLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/tile_page"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipChildren="false"
+ android:clipToPadding="false" />
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 4ca59f5..bbb6107 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -55,12 +55,14 @@
android:id="@+id/qs_frame"
android:layout="@layout/qs_panel"
android:layout_width="@dimen/qs_panel_width"
- android:layout_height="match_parent"
+ android:layout_height="0dp"
android:clipToPadding="false"
android:clipChildren="false"
systemui:viewType="com.android.systemui.plugins.qs.QS"
systemui:layout_constraintStart_toStartOf="parent"
systemui:layout_constraintEnd_toEndOf="parent"
+ systemui:layout_constraintTop_toTopOf="parent"
+ systemui:layout_constraintBottom_toBottomOf="parent"
/>
<androidx.constraintlayout.widget.Guideline
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index 1810c19..6aac5a3 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -21,6 +21,8 @@
android:layout_height="wrap_content"
android:gravity="right"
android:layout_gravity="right"
+ android:paddingRight="@dimen/volume_dialog_stream_padding"
+ android:clipToPadding="false"
android:background="@android:color/transparent"
android:theme="@style/volume_dialog_theme">
@@ -34,13 +36,15 @@
android:layout_gravity="right"
android:background="@android:color/transparent"
android:paddingRight="@dimen/volume_dialog_panel_transparent_padding_right"
- android:paddingTop="@dimen/volume_dialog_panel_transparent_padding"
- android:paddingBottom="@dimen/volume_dialog_panel_transparent_padding"
android:paddingLeft="@dimen/volume_dialog_panel_transparent_padding"
android:orientation="vertical"
- android:clipToPadding="false">
+ android:clipToPadding="false"
+ android:clipChildren="false">
+
+ <include layout="@layout/volume_ringer_drawer" />
<FrameLayout
+ android:visibility="gone"
android:id="@+id/ringer"
android:layout_width="@dimen/volume_dialog_ringer_size"
android:layout_height="@dimen/volume_dialog_ringer_size"
@@ -77,10 +81,8 @@
android:gravity="right"
android:layout_gravity="right"
android:orientation="vertical"
- android:translationZ="@dimen/volume_dialog_elevation"
android:clipChildren="false"
- android:clipToPadding="false"
- android:background="@drawable/rounded_bg_full" >
+ android:clipToPadding="false" >
<LinearLayout
android:id="@+id/volume_dialog_rows"
android:layout_width="wrap_content"
@@ -88,24 +90,22 @@
android:minWidth="@dimen/volume_dialog_panel_width"
android:gravity="center"
android:orientation="horizontal"
- android:paddingRight="@dimen/volume_dialog_stream_padding"
- android:paddingLeft="@dimen/volume_dialog_stream_padding">
+ android:layout_marginTop="@dimen/volume_row_slider_padding_start">
<!-- volume rows added and removed here! :-) -->
</LinearLayout>
<FrameLayout
android:id="@+id/settings_container"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="@drawable/rounded_bg_bottom_background">
+ android:layout_height="wrap_content">
<com.android.keyguard.AlphaOptimizedImageButton
android:id="@+id/settings"
- android:src="@drawable/ic_tune_black_16dp"
+ android:src="@drawable/horizontal_ellipsis"
android:layout_width="@dimen/volume_dialog_tap_target_size"
android:layout_height="@dimen/volume_dialog_tap_target_size"
android:layout_gravity="center"
android:contentDescription="@string/accessibility_volume_settings"
android:background="@drawable/ripple_drawable_20dp"
- android:tint="?android:attr/textColorPrimary"
+ android:tint="?android:attr/colorBackgroundFloating"
android:soundEffectsEnabled="false" />
</FrameLayout>
</LinearLayout>
@@ -118,7 +118,6 @@
android:gravity="right"
android:layout_gravity="right"
android:clipToPadding="false"
- android:translationZ="@dimen/volume_dialog_elevation"
android:background="@drawable/rounded_bg_full">
<com.android.systemui.volume.CaptionsToggleImageButton
android:id="@+id/odi_captions_icon"
@@ -127,7 +126,7 @@
android:background="@drawable/rounded_ripple"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:tint="?android:attr/textColorPrimary"
+ android:tint="?android:attr/colorAccent"
android:layout_gravity="center"
android:soundEffectsEnabled="false"
sysui:optedOut="false"/>
diff --git a/packages/SystemUI/res/layout/volume_dialog_row.xml b/packages/SystemUI/res/layout/volume_dialog_row.xml
index b9efc5b..fda59b5 100644
--- a/packages/SystemUI/res/layout/volume_dialog_row.xml
+++ b/packages/SystemUI/res/layout/volume_dialog_row.xml
@@ -20,11 +20,12 @@
android:layout_width="@dimen/volume_dialog_panel_width"
android:clipChildren="false"
android:clipToPadding="false"
+ android:translationZ="@dimen/volume_dialog_elevation"
android:theme="@style/volume_dialog_theme">
<LinearLayout
android:layout_height="wrap_content"
- android:layout_width="match_parent"
+ android:layout_width="@dimen/volume_dialog_panel_width"
android:gravity="center"
android:layout_gravity="center"
android:orientation="vertical" >
@@ -41,21 +42,23 @@
<FrameLayout
android:id="@+id/volume_row_slider_frame"
android:layout_width="match_parent"
- android:layout_marginTop="@dimen/volume_dialog_slider_margin_top"
- android:layout_marginBottom="@dimen/volume_dialog_slider_margin_bottom"
- android:layoutDirection="rtl"
- android:layout_height="@dimen/volume_dialog_slider_height">
+ android:layout_height="@dimen/volume_row_slider_height">
+ <include layout="@layout/volume_dnd_icon"/>
<SeekBar
android:id="@+id/volume_row_slider"
+ android:paddingLeft="0dp"
+ android:paddingRight="0dp"
+ android:paddingStart="0dp"
+ android:paddingEnd="0dp"
android:clickable="true"
- android:layout_width="@dimen/volume_dialog_slider_height"
+ android:layout_width="@dimen/volume_row_slider_height"
android:layout_height="match_parent"
- android:layoutDirection="rtl"
android:layout_gravity="center"
- android:rotation="90" />
+ android:rotation="270" />
</FrameLayout>
<com.android.keyguard.AlphaOptimizedImageButton
+ android:visibility="gone"
android:id="@+id/volume_row_icon"
style="@style/VolumeButtons"
android:layout_width="@dimen/volume_dialog_tap_target_size"
@@ -66,6 +69,4 @@
android:soundEffectsEnabled="false" />
</LinearLayout>
- <include layout="@layout/volume_dnd_icon"/>
-
</FrameLayout>
diff --git a/packages/SystemUI/res/layout/volume_ringer_drawer.xml b/packages/SystemUI/res/layout/volume_ringer_drawer.xml
new file mode 100644
index 0000000..d6e1782
--- /dev/null
+++ b/packages/SystemUI/res/layout/volume_ringer_drawer.xml
@@ -0,0 +1,126 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2021 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.
+-->
+
+<!-- Contains the active ringer icon and a hidden drawer containing all three ringer options. -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:clipToPadding="false"
+ android:clipChildren="false">
+
+ <!-- Drawer view, invisible by default. -->
+ <FrameLayout
+ android:id="@+id/volume_drawer_container"
+ android:alpha="0.0"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@drawable/volume_drawer_bg"
+ android:orientation="vertical">
+
+ <!-- View that is animated to a tapped ringer selection, so it appears selected. -->
+ <FrameLayout
+ android:id="@+id/volume_drawer_selection_background"
+ android:alpha="0.0"
+ android:layout_width="@dimen/volume_ringer_drawer_item_size"
+ android:layout_height="@dimen/volume_ringer_drawer_item_size"
+ android:layout_gravity="bottom|right"
+ android:background="@drawable/volume_drawer_selection_bg" />
+
+ <LinearLayout
+ android:id="@+id/volume_drawer_options"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <FrameLayout
+ android:id="@+id/volume_drawer_vibrate"
+ android:layout_width="@dimen/volume_ringer_drawer_item_size"
+ android:layout_height="@dimen/volume_ringer_drawer_item_size"
+ android:description="@string/volume_ringer_hint_vibrate"
+ android:gravity="center">
+
+ <ImageView
+ android:id="@+id/volume_drawer_vibrate_icon"
+ android:layout_width="@dimen/volume_ringer_drawer_icon_size"
+ android:layout_height="@dimen/volume_ringer_drawer_icon_size"
+ android:layout_gravity="center"
+ android:tint="?android:attr/colorAccent"
+ android:src="@drawable/ic_volume_ringer_vibrate" />
+
+ </FrameLayout>
+
+ <FrameLayout
+ android:id="@+id/volume_drawer_mute"
+ android:layout_width="@dimen/volume_ringer_drawer_item_size"
+ android:layout_height="@dimen/volume_ringer_drawer_item_size"
+ android:description="@string/volume_ringer_hint_mute"
+ android:gravity="center">
+
+ <ImageView
+ android:id="@+id/volume_drawer_mute_icon"
+ android:layout_width="@dimen/volume_ringer_drawer_icon_size"
+ android:layout_height="@dimen/volume_ringer_drawer_icon_size"
+ android:layout_gravity="center"
+ android:tint="?android:attr/colorAccent"
+ android:src="@drawable/ic_volume_ringer_mute" />
+
+ </FrameLayout>
+
+ <FrameLayout
+ android:id="@+id/volume_drawer_normal"
+ android:layout_width="@dimen/volume_ringer_drawer_item_size"
+ android:layout_height="@dimen/volume_ringer_drawer_item_size"
+ android:description="@string/volume_ringer_hint_unmute"
+ android:gravity="center">
+
+ <ImageView
+ android:id="@+id/volume_drawer_normal_icon"
+ android:layout_width="@dimen/volume_ringer_drawer_icon_size"
+ android:layout_height="@dimen/volume_ringer_drawer_icon_size"
+ android:layout_gravity="center"
+ android:tint="?android:attr/colorAccent"
+ android:src="@drawable/ic_volume_ringer" />
+
+ </FrameLayout>
+
+ </LinearLayout>
+
+ </FrameLayout>
+
+ <!-- The current ringer selection. When the drawer is opened, this animates to the corresponding
+ position in the drawer. When the drawer is closed, it animates back. -->
+ <FrameLayout
+ android:id="@+id/volume_new_ringer_active_icon_container"
+ android:layout_width="@dimen/volume_ringer_drawer_item_size"
+ android:layout_height="@dimen/volume_ringer_drawer_item_size"
+ android:layout_gravity="bottom|right"
+ android:description="@string/volume_ringer_change"
+ android:background="@drawable/volume_drawer_selection_bg">
+
+ <ImageView
+ android:id="@+id/volume_new_ringer_active_icon"
+ android:layout_width="@dimen/volume_ringer_drawer_icon_size"
+ android:layout_height="@dimen/volume_ringer_drawer_icon_size"
+ android:layout_gravity="center"
+ android:tint="?android:attr/colorBackgroundFloating"
+ android:src="@drawable/ic_volume_media" />
+
+ </FrameLayout>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index acd671c..3bc1c80 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -265,7 +265,6 @@
<color name="control_enabled_cool_foreground">@color/GM2_blue_300</color>
<color name="control_thumbnail_tint">#33000000</color>
<color name="control_thumbnail_shadow_color">@*android:color/black</color>
- <color name="controls_lockscreen_scrim">#AA000000</color>
<!-- Docked misalignment message -->
<color name="misalignment_text_color">#F28B82</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 89c849a..ea0ea5e 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -456,10 +456,12 @@
<dimen name="volume_dialog_panel_transparent_padding">20dp</dimen>
- <dimen name="volume_dialog_stream_padding">8dp</dimen>
+ <dimen name="volume_dialog_stream_padding">12dp</dimen>
<dimen name="volume_dialog_panel_width">64dp</dimen>
+ <dimen name="volume_dialog_panel_width_half">32dp</dimen>
+
<dimen name="volume_dialog_slider_height">116dp</dimen>
<dimen name="volume_dialog_ringer_size">64dp</dimen>
@@ -486,6 +488,13 @@
<dimen name="volume_tool_tip_arrow_corner_radius">2dp</dimen>
+ <!-- Size of each item in the ringer selector drawer. -->
+ <dimen name="volume_ringer_drawer_item_size">64dp</dimen>
+ <dimen name="volume_ringer_drawer_item_size_half">32dp</dimen>
+
+ <!-- Size of the icon inside each item in the ringer selector drawer. -->
+ <dimen name="volume_ringer_drawer_icon_size">24dp</dimen>
+
<!-- Gravity for the notification panel -->
<integer name="notification_panel_layout_gravity">0x31</integer><!-- center_horizontal|top -->
@@ -966,7 +975,7 @@
<dimen name="volume_row_padding_start">4dp</dimen>
<dimen name="volume_row_header_padding_start">16dp</dimen>
<dimen name="volume_row_height">64dp</dimen>
- <dimen name="volume_row_slider_height">48dp</dimen>
+ <dimen name="volume_row_slider_height">192dp</dimen>
<dimen name="volume_row_slider_padding_start">12dp</dimen>
<dimen name="volume_expander_margin_end">2dp</dimen>
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index 3e16cd4..e551892 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -44,4 +44,6 @@
<bool name="flag_toast_style">false</bool>
<bool name="flag_navigation_bar_overlay">false</bool>
+
+ <bool name="flag_pm_lite">false</bool>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 4b95c16..5f8df5a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -959,7 +959,7 @@
<!-- QuickSettings: Secondary text for when the Dark theme or some other tile will be on until some user-selected time. [CHAR LIMIT=20] -->
<string name="quick_settings_dark_mode_secondary_label_until">Until <xliff:g id="time" example="7 am">%s</xliff:g></string>
<!-- QuickSettings: Label for the toggle that controls whether Reduce Brightness is enabled. [CHAR LIMIT=NONE] -->
- <string name="quick_settings_reduce_bright_colors_label">Reduce Brightness</string>
+ <string name="quick_settings_reduce_bright_colors_label">Reduce brightness</string>
<!-- QuickSettings: NFC tile [CHAR LIMIT=NONE] -->
<string name="quick_settings_nfc_label">NFC</string>
@@ -1054,6 +1054,9 @@
<!-- Text on keyguard screen and in Quick Settings footer indicating that user's device belongs to their organization. [CHAR LIMIT=40] -->
<string name="do_disclosure_with_name">This device belongs to <xliff:g id="organization_name" example="Foo, Inc.">%s</xliff:g></string>
+ <!-- Text on keyguard screen and in Quick Settings footer indicating that the user's device is provided by the Creditor. [CHAR LIMIT=60] -->
+ <string name="do_financed_disclosure_with_name">This device is provided by <xliff:g id="organization_name" example="Foo, Inc.">%s</xliff:g></string>
+
<!-- Shows when people have clicked on the phone icon [CHAR LIMIT=60] -->
<string name="phone_hint">Swipe from icon for phone</string>
@@ -1558,6 +1561,8 @@
<string name="volume_stream_content_description_vibrate_a11y">%1$s. Tap to set to vibrate.</string>
<string name="volume_stream_content_description_mute_a11y">%1$s. Tap to mute.</string>
+ <string name="volume_ringer_change">Tap to change ringer mode</string>
+
<!-- Hint for accessibility. For example: double tap to mute [CHAR_LIMIT=NONE] -->
<string name="volume_ringer_hint_mute">mute</string>
<!-- Hint for accessibility. For example: double tap to unmute [CHAR_LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 14b376a..2d202fb 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -663,16 +663,16 @@
</style>
<style name="Theme.SystemUI.Dialog.Control.LockScreen" parent="@android:style/Theme.DeviceDefault.Dialog.NoActionBar">
- <item name="android:windowAnimationStyle">@style/Animation.Fade</item>
+ <item name="android:windowAnimationStyle">@style/Animation.ControlDialog</item>
<item name="android:windowFullscreen">true</item>
<item name="android:windowIsFloating">false</item>
- <item name="android:windowBackground">@color/controls_lockscreen_scrim</item>
+ <item name="android:windowBackground">@null</item>
<item name="android:backgroundDimEnabled">true</item>
</style>
- <style name="Animation.Fade">
- <item name="android:windowEnterAnimation">@android:anim/fade_in</item>
- <item name="android:windowExitAnimation">@android:anim/fade_out</item>
+ <style name="Animation.ControlDialog">
+ <item name="android:windowEnterAnimation">@*android:anim/dialog_enter</item>
+ <item name="android:windowExitAnimation">@*android:anim/dialog_exit</item>
</style>
<style name="Control" />
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index d9a1eb6..a4054be 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -305,6 +305,7 @@
private boolean mLogoutEnabled;
// cached value to avoid IPCs
private boolean mIsUdfpsEnrolled;
+ private boolean mKeyguardQsUserSwitchEnabled;
// If the user long pressed the lock icon, disabling face auth for the current session.
private boolean mLockIconPressed;
private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -1916,7 +1917,7 @@
return isFaceAuthEnabledForUser(KeyguardUpdateMonitor.getCurrentUser())
&& !isUdfpsEnrolled();
}
- return true;
+ return !isKeyguardQsUserSwitchEnabled();
}
/**
@@ -1926,6 +1927,17 @@
return mIsUdfpsEnrolled;
}
+ /**
+ * @return true if the keyguard qs user switcher shortcut is enabled
+ */
+ public boolean isKeyguardQsUserSwitchEnabled() {
+ return mKeyguardQsUserSwitchEnabled;
+ }
+
+ public void setKeyguardQsUserSwitchEnabled(boolean enabled) {
+ mKeyguardQsUserSwitchEnabled = enabled;
+ }
+
private final UserSwitchObserver mUserSwitchObserver = new UserSwitchObserver() {
@Override
public void onUserSwitching(int newUserId, IRemoteCallback reply) {
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index 79f0688..2040347 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -40,6 +40,7 @@
import androidx.annotation.WorkerThread;
+import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Dumpable;
@@ -78,6 +79,7 @@
private static final boolean DEBUG = false;
private final BroadcastDispatcher mDispatcher;
+ private final Context mContext;
private final AppOpsManager mAppOps;
private final AudioManager mAudioManager;
private final LocationManager mLocationManager;
@@ -141,6 +143,7 @@
mCameraDisabled = mSensorPrivacyController.isSensorBlocked(CAMERA);
mLocationManager = context.getSystemService(LocationManager.class);
mPackageManager = context.getPackageManager();
+ mContext = context;
dumpManager.registerDumpable(TAG, this);
}
@@ -357,6 +360,15 @@
return mLocationProviderPackages.contains(packageName);
}
+ private boolean isSpeechRecognizerUsage(int opCode, String packageName) {
+ if (AppOpsManager.OP_RECORD_AUDIO != opCode) {
+ return false;
+ }
+
+ return packageName.equals(
+ mContext.getString(R.string.config_systemSpeechRecognizer));
+ }
+
// TODO ntmyren: remove after teamfood is finished
private boolean shouldShowAppPredictor(String pkgName) {
if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, "permissions_hub_2_enabled",
@@ -388,7 +400,8 @@
}
// TODO ntmyren: Replace this with more robust check if this moves beyond teamfood
if ((appOpCode == AppOpsManager.OP_CAMERA && isLocationProvider(packageName))
- || shouldShowAppPredictor(packageName)) {
+ || shouldShowAppPredictor(packageName)
+ || isSpeechRecognizerUsage(appOpCode, packageName)) {
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 6451ad9..71fba33 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -68,6 +68,7 @@
private final Context mContext;
private final FingerprintManager mFingerprintManager;
+ @NonNull private final LayoutInflater mInflater;
private final WindowManager mWindowManager;
private final DelayableExecutor mFgExecutor;
private final StatusBarStateController mStatusBarStateController;
@@ -75,10 +76,8 @@
// sensors, this, in addition to a lot of the code here, will be updated.
@VisibleForTesting final FingerprintSensorPropertiesInternal mSensorProps;
private final WindowManager.LayoutParams mCoreLayoutParams;
- private final UdfpsView mView;
- // Indicates whether the overlay is currently showing. Even if it has been requested, it might
- // not be showing.
- private boolean mIsOverlayShowing;
+
+ @Nullable private UdfpsView mView;
// Indicates whether the overlay has been requested.
private boolean mIsOverlayRequested;
// Reason the overlay has been requested. See IUdfpsOverlayController for definitions.
@@ -111,20 +110,48 @@
@Override
public void onEnrollmentProgress(int sensorId, int remaining) {
+ if (mView == null) {
+ return;
+ }
mView.onEnrollmentProgress(remaining);
}
@Override
public void onEnrollmentHelp(int sensorId) {
+ if (mView == null) {
+ return;
+ }
mView.onEnrollmentHelp();
}
@Override
public void setDebugMessage(int sensorId, String message) {
+ if (mView == null) {
+ return;
+ }
mView.setDebugMessage(message);
}
}
+ @VisibleForTesting
+ final StatusBar.ExpansionChangedListener mStatusBarExpansionListener =
+ (expansion, expanded) -> {
+ if (mView != null) {
+ mView.onExpansionChanged(expansion, expanded);
+ }
+ };
+
+ @VisibleForTesting
+ final StatusBarStateController.StateListener mStatusBarStateListener =
+ new StatusBarStateController.StateListener() {
+ @Override
+ public void onStateChanged(int newState) {
+ if (mView != null) {
+ mView.onStateChanged(newState);
+ }
+ }
+ };
+
@SuppressLint("ClickableViewAccessibility")
private final UdfpsView.OnTouchListener mOnTouchListener = (v, event) -> {
UdfpsView view = (UdfpsView) v;
@@ -157,13 +184,14 @@
@Inject
public UdfpsController(@NonNull Context context,
@Main Resources resources,
- LayoutInflater inflater,
+ @NonNull LayoutInflater inflater,
@Nullable FingerprintManager fingerprintManager,
WindowManager windowManager,
@NonNull StatusBarStateController statusBarStateController,
@Main DelayableExecutor fgExecutor,
@Nullable StatusBar statusBar) {
mContext = context;
+ mInflater = inflater;
// The fingerprint manager is queried for UDFPS before this class is constructed, so the
// fingerprint manager should never be null.
mFingerprintManager = checkNotNull(fingerprintManager);
@@ -189,15 +217,10 @@
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
mCoreLayoutParams.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
- mView = (UdfpsView) inflater.inflate(R.layout.udfps_view, null, false);
- mView.setSensorProperties(mSensorProps);
- mView.setHbmCallback(this);
-
- statusBar.addExpansionChangedListener(mView);
- statusBarStateController.addCallback(mView);
+ statusBar.addExpansionChangedListener(mStatusBarExpansionListener);
+ mStatusBarStateController.addCallback(mStatusBarStateListener);
mFingerprintManager.setUdfpsOverlayController(new UdfpsOverlayController());
- mIsOverlayShowing = false;
}
@Nullable
@@ -220,7 +243,13 @@
* @return where the UDFPS exists on the screen in pixels.
*/
public RectF getSensorLocation() {
- return mView.getSensorRect();
+ // This is currently used to calculate the amount of space available for notifications
+ // on lockscreen. Keyguard is only shown in portrait mode for now, so this will need to
+ // be updated if that ever changes.
+ return new RectF(mSensorProps.sensorLocationX - mSensorProps.sensorRadius,
+ mSensorProps.sensorLocationY - mSensorProps.sensorRadius,
+ mSensorProps.sensorLocationX + mSensorProps.sensorRadius,
+ mSensorProps.sensorLocationY + mSensorProps.sensorRadius);
}
private void showOverlay(int reason) {
@@ -298,14 +327,18 @@
private void showUdfpsOverlay(int reason) {
mFgExecutor.execute(() -> {
- if (!mIsOverlayShowing) {
+ if (mView == null) {
try {
Log.v(TAG, "showUdfpsOverlay | adding window");
+
+ mView = (UdfpsView) mInflater.inflate(R.layout.udfps_view, null, false);
+ mView.setSensorProperties(mSensorProps);
+ mView.setHbmCallback(this);
+
final UdfpsAnimation animation = getUdfpsAnimationForReason(reason);
mView.setExtras(animation, mEnrollHelper);
mWindowManager.addView(mView, computeLayoutParams(animation));
mView.setOnTouchListener(mOnTouchListener);
- mIsOverlayShowing = true;
} catch (RuntimeException e) {
Log.e(TAG, "showUdfpsOverlay | failed to add window", e);
}
@@ -334,14 +367,12 @@
private void hideUdfpsOverlay() {
mFgExecutor.execute(() -> {
- if (mIsOverlayShowing) {
+ if (mView != null) {
Log.v(TAG, "hideUdfpsOverlay | removing window");
- mView.setExtras(null, null);
- mView.setOnTouchListener(null);
// Reset the controller back to its starting state.
onFingerUp();
mWindowManager.removeView(mView);
- mIsOverlayShowing = false;
+ mView = null;
} else {
Log.v(TAG, "hideUdfpsOverlay | the overlay is already hidden");
}
@@ -388,12 +419,20 @@
// This method can be called from the UI thread.
private void onFingerDown(int x, int y, float minor, float major) {
+ if (mView == null) {
+ Log.w(TAG, "Null view in onFingerDown");
+ return;
+ }
mView.startIllumination(() ->
mFingerprintManager.onPointerDown(mSensorProps.sensorId, x, y, minor, major));
}
// This method can be called from the UI thread.
private void onFingerUp() {
+ if (mView == null) {
+ Log.w(TAG, "Null view in onFingerUp");
+ return;
+ }
mFingerprintManager.onPointerUp(mSensorProps.sensorId);
mView.stopIllumination();
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
index 6ffecdb..3997943 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
@@ -136,17 +136,13 @@
}
@Override
- public void onExpandedChanged(boolean isExpanded) {
- mNotificationShadeExpanded = isExpanded;
- }
-
- @Override
public void onStateChanged(int newState) {
mStatusBarState = newState;
}
@Override
public void onExpansionChanged(float expansion, boolean expanded) {
+ mNotificationShadeExpanded = expanded;
mAnimationView.onExpansionChanged(expansion, expanded);
}
@@ -192,10 +188,6 @@
}
}
- RectF getSensorRect() {
- return new RectF(mSensorRect);
- }
-
void setDebugMessage(String message) {
mDebugMessage = message;
postInvalidate();
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt
index cad166d..1ea1d97 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt
@@ -41,7 +41,7 @@
object ControlsAnimations {
- private const val ALPHA_EXIT_DURATION = 167L
+ private const val ALPHA_EXIT_DURATION = 183L
private const val ALPHA_ENTER_DELAY = ALPHA_EXIT_DURATION
private const val ALPHA_ENTER_DURATION = 350L - ALPHA_ENTER_DELAY
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
index 1c2f17c..2d647a9 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -313,6 +313,10 @@
setOnClickListener {
val i = Intent().apply {
component = ComponentName(context, ControlsProviderSelectorActivity::class.java)
+ putExtra(
+ ControlsUiController.BACK_TO_GLOBAL_ACTIONS,
+ backToGlobalActions
+ )
}
if (doneButton.isEnabled) {
// The user has made changes
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
index 0814774..d5e41d0 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
@@ -32,6 +32,8 @@
import com.android.systemui.R
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.controls.ui.ControlsDialog
+import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.globalactions.GlobalActionsComponent
@@ -49,13 +51,15 @@
private val listingController: ControlsListingController,
private val controlsController: ControlsController,
private val globalActionsComponent: GlobalActionsComponent,
- broadcastDispatcher: BroadcastDispatcher
+ private val broadcastDispatcher: BroadcastDispatcher,
+ private val uiController: ControlsUiController
) : LifecycleActivity() {
companion object {
private const val TAG = "ControlsProviderSelectorActivity"
}
+ private var backToGlobalActions = true
private lateinit var recyclerView: RecyclerView
private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
private val startingUser = listingController.currentUserId
@@ -101,10 +105,19 @@
}
}
requireViewById<View>(R.id.done).visibility = View.GONE
+
+ backToGlobalActions = intent.getBooleanExtra(
+ ControlsUiController.BACK_TO_GLOBAL_ACTIONS,
+ true
+ )
}
override fun onBackPressed() {
- globalActionsComponent.handleShowGlobalActionsMenu()
+ if (backToGlobalActions) {
+ globalActionsComponent.handleShowGlobalActionsMenu()
+ } else {
+ ControlsDialog(applicationContext, broadcastDispatcher).show(uiController)
+ }
animateExitAndFinish()
}
@@ -152,8 +165,13 @@
listingController.getAppLabel(it))
putExtra(Intent.EXTRA_COMPONENT_NAME, it)
putExtra(ControlsFavoritingActivity.EXTRA_FROM_PROVIDER_SELECTOR, true)
+ putExtra(
+ ControlsUiController.BACK_TO_GLOBAL_ACTIONS,
+ backToGlobalActions
+ )
}
startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this).toBundle())
+ animateExitAndFinish()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/NearestTouchFrame.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/NearestTouchFrame.java
index b1c85b5..8ff7b7a 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/NearestTouchFrame.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/NearestTouchFrame.java
@@ -84,19 +84,14 @@
}
@Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
mClickableChildren.clear();
mAttachedChildren.clear();
mTouchableRegions.clear();
addClickableChildren(this);
- cacheClosestChildLocations();
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
getLocationInWindow(mOffset);
+ cacheClosestChildLocations();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
index 2f9b17a..2ea8657 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
@@ -19,8 +19,6 @@
import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID;
import static android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID;
-import static com.android.systemui.people.PeopleSpaceUtils.getUserHandle;
-
import android.app.Activity;
import android.app.INotificationManager;
import android.app.people.IPeopleManager;
@@ -155,7 +153,7 @@
if (DEBUG) Log.d(TAG, "Caching shortcut for PeopleTile: " + tile.getId());
mLauncherApps.cacheShortcuts(tile.getPackageName(),
Collections.singletonList(tile.getId()),
- getUserHandle(tile), LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS);
+ tile.getUserHandle(), LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS);
} catch (Exception e) {
Log.w(TAG, "Exception caching shortcut:" + e);
}
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java
index 9ae7847..6f89332 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java
@@ -21,7 +21,6 @@
import android.content.pm.LauncherApps;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
-import android.os.UserHandle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -82,7 +81,7 @@
public void setOnClickListener(LauncherApps launcherApps, PeopleSpaceTile tile) {
mTileView.setOnClickListener(v ->
launcherApps.startShortcut(tile.getPackageName(), tile.getId(), null, null,
- UserHandle.getUserHandleForUid(tile.getUid())));
+ tile.getUserHandle()));
}
/** Sets the click listener of the tile directly. */
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
index 5dda23e..41080af 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
@@ -55,7 +55,6 @@
import android.os.Bundle;
import android.os.Parcelable;
import android.os.ServiceManager;
-import android.os.UserHandle;
import android.provider.ContactsContract;
import android.provider.Settings;
import android.service.notification.ConversationChannelWrapper;
@@ -160,7 +159,6 @@
List<ConversationChannelWrapper> conversations =
notificationManager.getConversations(
false).getList();
-
// Add priority conversations to tiles list.
Stream<ShortcutInfo> priorityConversations = conversations.stream()
.filter(c -> c.getNotificationChannel() != null
@@ -252,7 +250,11 @@
}
// If tile is null, we need to retrieve from persisted storage.
- if (DEBUG) Log.d(TAG, "Retrieving from storage after reboots");
+ if (DEBUG) {
+ Log.d(TAG,
+ "Retrieving from storage after reboots: " + shortcutId + " user: " + userId
+ + " pkg: " + pkg);
+ }
LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
ConversationChannel channel = peopleManager.getConversation(pkg, userId, shortcutId);
if (channel == null) {
@@ -384,7 +386,7 @@
PeopleSpaceTile tile, Map<String, NotificationEntry> visibleNotifications) {
String shortcutId = tile.getId();
String packageName = tile.getPackageName();
- int userId = UserHandle.getUserHandleForUid(tile.getUid()).getIdentifier();
+ int userId = getUserId(tile);
String key = getKey(shortcutId, packageName, userId);
if (!visibleNotifications.containsKey(key)) {
if (DEBUG) Log.d(TAG, "No existing notifications for key:" + key);
@@ -641,7 +643,8 @@
activityIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_TILE_ID, tile.getId());
activityIntent.putExtra(
PeopleSpaceWidgetProvider.EXTRA_PACKAGE_NAME, tile.getPackageName());
- activityIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_UID, tile.getUid());
+ activityIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_USER_HANDLE,
+ tile.getUserHandle());
views.setOnClickPendingIntent(R.id.item, PendingIntent.getActivity(
context,
appWidgetId,
@@ -788,7 +791,6 @@
Log.i(TAG, "ConversationChannel is null");
return null;
}
-
PeopleSpaceTile tile = new PeopleSpaceTile.Builder(channel, launcherApps).build();
if (!PeopleSpaceUtils.shouldKeepConversation(tile)) {
Log.i(TAG, "PeopleSpaceTile is not valid");
@@ -1069,11 +1071,6 @@
/** Returns the userId associated with a {@link PeopleSpaceTile} */
public static int getUserId(PeopleSpaceTile tile) {
- return getUserHandle(tile).getIdentifier();
- }
-
- /** Returns the {@link UserHandle} associated with a {@link PeopleSpaceTile} */
- public static UserHandle getUserHandle(PeopleSpaceTile tile) {
- return UserHandle.getUserHandleForUid(tile.getUid());
+ return tile.getUserHandle().getIdentifier();
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java b/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java
index 358f311..13e30f9 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java
@@ -41,7 +41,8 @@
Intent intent = getIntent();
String tileId = intent.getStringExtra(PeopleSpaceWidgetProvider.EXTRA_TILE_ID);
String packageName = intent.getStringExtra(PeopleSpaceWidgetProvider.EXTRA_PACKAGE_NAME);
- int uid = intent.getIntExtra(PeopleSpaceWidgetProvider.EXTRA_UID, 0);
+ UserHandle userHandle = intent.getParcelableExtra(
+ PeopleSpaceWidgetProvider.EXTRA_USER_HANDLE);
if (tileId != null && !tileId.isEmpty()) {
if (DEBUG) {
@@ -52,7 +53,7 @@
LauncherApps launcherApps =
getApplicationContext().getSystemService(LauncherApps.class);
launcherApps.startShortcut(
- packageName, tileId, null, null, UserHandle.getUserHandleForUid(uid));
+ packageName, tileId, null, null, userHandle);
} catch (Exception e) {
Log.e(TAG, "Exception starting shortcut:" + e);
}
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java
index 3d1055f..90baf56 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java
@@ -48,7 +48,7 @@
public static final String EXTRA_TILE_ID = "extra_tile_id";
public static final String EXTRA_PACKAGE_NAME = "extra_package_name";
- public static final String EXTRA_UID = "extra_uid";
+ public static final String EXTRA_USER_HANDLE = "extra_user_handle";
public UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java
index 80794cb..0503522 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java
@@ -123,7 +123,8 @@
fillInIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_TILE_ID, tile.getId());
fillInIntent.putExtra(
PeopleSpaceWidgetProvider.EXTRA_PACKAGE_NAME, tile.getPackageName());
- fillInIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_UID, tile.getUid());
+ fillInIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_USER_HANDLE,
+ tile.getUserHandle());
personView.setOnClickFillInIntent(R.id.item, fillInIntent);
} catch (Exception e) {
Log.e(TAG, "Couldn't retrieve shortcut information", e);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index eaf2123..fcb56a4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -71,7 +71,6 @@
private int mMinRows = 1;
private int mMaxColumns = TileLayout.NO_MAX_COLUMNS;
- private boolean mShowLabels = true;
private final boolean mSideLabels;
public PagedTileLayout(Context context, AttributeSet attrs) {
@@ -91,16 +90,6 @@
}
private int mLastMaxHeight = -1;
- @Override
- public void setShowLabels(boolean show) {
- mShowLabels = show;
- for (TileLayout p : mPages) {
- p.setShowLabels(show);
- }
- mDistributeTiles = true;
- requestLayout();
- }
-
public void saveInstanceState(Bundle outState) {
outState.putInt(CURRENT_PAGE, getCurrentItem());
}
@@ -239,7 +228,6 @@
: R.layout.qs_paged_page, this, false);
page.setMinRows(mMinRows);
page.setMaxColumns(mMaxColumns);
- page.setShowLabels(mShowLabels);
return page;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
index 16e5196..2bea72c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
@@ -16,6 +16,8 @@
package com.android.systemui.qs;
+import static com.android.systemui.qs.dagger.QSFlagsModule.PM_LITE_ENABLED;
+
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Intent;
@@ -41,6 +43,7 @@
import com.android.systemui.util.ViewController;
import javax.inject.Inject;
+import javax.inject.Named;
/**
* Controller for {@link QSFooterView}.
@@ -63,6 +66,8 @@
private final View mEdit;
private final MultiUserSwitch mMultiUserSwitch;
private final PageIndicator mPageIndicator;
+ private final View mPowerMenuLite;
+ private final boolean mShowPMLiteButton;
private final UserInfoController.OnUserInfoChangedListener mOnUserInfoChangedListener =
new UserInfoController.OnUserInfoChangedListener() {
@@ -123,7 +128,8 @@
DeviceProvisionedController deviceProvisionedController, UserTracker userTracker,
QSPanelController qsPanelController, QSDetailDisplayer qsDetailDisplayer,
QuickQSPanelController quickQSPanelController,
- TunerService tunerService, MetricsLogger metricsLogger) {
+ TunerService tunerService, MetricsLogger metricsLogger,
+ @Named(PM_LITE_ENABLED) boolean showPMLiteButton) {
super(view);
mUserManager = userManager;
mUserInfoController = userInfoController;
@@ -141,10 +147,15 @@
mEdit = mView.findViewById(android.R.id.edit);
mMultiUserSwitch = mView.findViewById(R.id.multi_user_switch);
mPageIndicator = mView.findViewById(R.id.footer_page_indicator);
+ mPowerMenuLite = mView.findViewById(R.id.pm_lite);
+ mShowPMLiteButton = showPMLiteButton;
}
@Override
protected void onViewAttached() {
+ if (mShowPMLiteButton) {
+ mPowerMenuLite.setVisibility(View.VISIBLE);
+ }
mView.addOnLayoutChangeListener(
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
mView.updateAnimator(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 0563307..7657dce 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -847,8 +847,6 @@
default void setExpansion(float expansion) {}
int getNumVisibleTiles();
-
- default void setShowLabels(boolean show) {}
}
interface OnConfigurationChangedListener {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index d60801e..eda1abb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -124,7 +124,6 @@
updateMediaDisappearParameters();
mTunerService.addTunable(mView, QS_SHOW_BRIGHTNESS);
- mTunerService.addTunable(mTunable, QS_REMOVE_LABELS);
mView.updateResources();
if (mView.isListening()) {
refreshAllTiles();
@@ -138,13 +137,6 @@
}
@Override
- boolean switchTileLayout(boolean force) {
- boolean result = super.switchTileLayout(force);
- getTileLayout().setShowLabels(mShowLabels);
- return result;
- }
-
- @Override
protected QSTileRevealController createTileRevealController() {
return mQsTileRevealControllerFactory.create(
this, (PagedTileLayout) mView.createRegularTileLayout());
@@ -152,7 +144,6 @@
@Override
protected void onViewDetached() {
- mTunerService.removeTunable(mTunable);
mTunerService.removeTunable(mView);
mView.removeOnConfigurationChangedListener(mOnConfigurationChangedListener);
if (mBrightnessMirrorController != null) {
@@ -318,22 +309,5 @@
public boolean isExpanded() {
return mView.isExpanded();
}
-
- private TunerService.Tunable mTunable = new TunerService.Tunable() {
- @Override
- public void onTuningChanged(String key, String newValue) {
- if (QS_REMOVE_LABELS.equals(key)) {
- if (!mQSLabelFlag) return;
- boolean newShowLabels = newValue == null || "0".equals(newValue);
- if (mShowLabels == newShowLabels) return;
- mShowLabels = newShowLabels;
- for (TileRecord t : mRecords) {
- t.tileView.setShowLabels(mShowLabels);
- }
- getTileLayout().setShowLabels(mShowLabels);
- mView.requestLayout();
- }
- }
- };
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index f1174fb..8ab1743 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -198,7 +198,6 @@
final TileRecord r = new TileRecord();
r.tile = tile;
r.tileView = mHost.createTileView(tile, collapsedView);
- r.tileView.setShowLabels(mShowLabels);
mView.addTile(r);
mRecords.add(r);
mCachedSpecs = getTilesSpecs();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt
index 4de4a78..c3cc3af 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt
@@ -34,8 +34,6 @@
}
}
- override fun setShowLabels(show: Boolean) { }
-
override fun isFull(): Boolean {
return mRecords.size >= maxTiles()
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
index 14cbf98..c1ce4a5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
@@ -36,7 +36,6 @@
private int mCellMarginTop;
protected boolean mListening;
protected int mMaxAllowedRows = 3;
- private boolean mShowLabels = true;
// Prototyping with less rows
private final boolean mLessRows;
@@ -57,12 +56,6 @@
}
@Override
- public void setShowLabels(boolean show) {
- mShowLabels = show;
- updateResources();
- }
-
- @Override
public int getOffsetTop(TileRecord tile) {
return getTop();
}
@@ -128,12 +121,9 @@
mMaxCellHeight = mContext.getResources().getDimensionPixelSize(R.dimen.qs_tile_height);
mCellMarginHorizontal = res.getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal);
mCellMarginVertical= res.getDimensionPixelSize(R.dimen.qs_tile_margin_vertical);
- if (!mShowLabels && mCellMarginVertical == 0) {
- mCellMarginVertical = mCellMarginHorizontal;
- }
mCellMarginTop = res.getDimensionPixelSize(R.dimen.qs_tile_margin_top);
mMaxAllowedRows = Math.max(1, getResources().getInteger(R.integer.quick_settings_max_rows));
- if (mLessRows && mShowLabels) mMaxAllowedRows = Math.max(mMinRows, mMaxAllowedRows - 1);
+ if (mLessRows) mMaxAllowedRows = Math.max(mMinRows, mMaxAllowedRows - 1);
if (updateColumns()) {
requestLayout();
return true;
@@ -194,9 +184,8 @@
+ mCellMarginVertical;
final int previousRows = mRows;
mRows = availableHeight / (getCellHeight() + mCellMarginVertical);
- final int minRows = mShowLabels ? mMinRows : mMinRows + 1;
- if (mRows < minRows) {
- mRows = minRows;
+ if (mRows < mMinRows) {
+ mRows = mMinRows;
} else if (mRows >= mMaxAllowedRows) {
mRows = mMaxAllowedRows;
}
@@ -216,7 +205,7 @@
}
protected int getCellHeight() {
- return mShowLabels ? mMaxCellHeight : mMaxCellHeight / 2;
+ return mMaxCellHeight;
}
protected void layoutTileRecords(int numRecords) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
index a567f51..0dc0b30 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
@@ -42,6 +42,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators;
import java.util.function.Consumer;
@@ -74,30 +75,25 @@
private final NetworkController.SignalCallback mSignalCallback =
new NetworkController.SignalCallback() {
@Override
- public void setMobileDataIndicators(NetworkController.IconState statusIcon,
- NetworkController.IconState qsIcon, int statusType, int qsType,
- boolean activityIn, boolean activityOut,
- CharSequence typeContentDescription,
- CharSequence typeContentDescriptionHtml, CharSequence description,
- boolean isWide, int subId, boolean roaming, boolean showTriangle) {
+ public void setMobileDataIndicators(MobileDataIndicators indicators) {
if (mProviderModel) {
return;
}
- int slotIndex = getSlotIndex(subId);
+ int slotIndex = getSlotIndex(indicators.subId);
if (slotIndex >= SIM_SLOTS) {
Log.w(TAG, "setMobileDataIndicators - slot: " + slotIndex);
return;
}
if (slotIndex == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
- Log.e(TAG, "Invalid SIM slot index for subscription: " + subId);
+ Log.e(TAG, "Invalid SIM slot index for subscription: " + indicators.subId);
return;
}
mInfos[slotIndex] = new CellSignalState(
- statusIcon.visible,
- statusIcon.icon,
- statusIcon.contentDescription,
- typeContentDescription.toString(),
- roaming
+ indicators.statusIcon.visible,
+ indicators.statusIcon.icon,
+ indicators.statusIcon.contentDescription,
+ indicators.typeContentDescription.toString(),
+ indicators.roaming
);
mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index 9fe949b..dce081f2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -25,7 +25,6 @@
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.Menu;
-import android.view.MenuItem;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.Toolbar;
@@ -48,7 +47,6 @@
public class QSCustomizer extends LinearLayout {
static final int MENU_RESET = Menu.FIRST;
- static final int MENU_REMOVE_LABELS = Menu.FIRST + 1;
static final String EXTRA_QS_CUSTOMIZING = "qs_customizing";
private final QSDetailClipper mClipper;
@@ -77,10 +75,6 @@
toolbar.getMenu().add(Menu.NONE, MENU_RESET, 0,
mContext.getString(com.android.internal.R.string.reset));
- // Prototype menu item
- toolbar.getMenu()
- .add(Menu.NONE, MENU_REMOVE_LABELS, Menu.NONE, R.string.qs_remove_labels)
- .setCheckable(true);
toolbar.setTitle(R.string.qs_edit);
mRecyclerView = findViewById(android.R.id.list);
mTransparentView = findViewById(R.id.customizer_transparent_view);
@@ -89,11 +83,6 @@
mRecyclerView.setItemAnimator(animator);
}
- MenuItem getRemoveItem() {
- return ((Toolbar) findViewById(com.android.internal.R.id.action_bar))
- .getMenu().findItem(MENU_REMOVE_LABELS);
- }
-
void updateResources() {
LayoutParams lp = (LayoutParams) mTransparentView.getLayoutParams();
lp.height = mContext.getResources().getDimensionPixelSize(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
index d4bab21..f56a2bb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
@@ -17,9 +17,7 @@
package com.android.systemui.qs.customize;
import static com.android.systemui.qs.customize.QSCustomizer.EXTRA_QS_CUSTOMIZING;
-import static com.android.systemui.qs.customize.QSCustomizer.MENU_REMOVE_LABELS;
import static com.android.systemui.qs.customize.QSCustomizer.MENU_RESET;
-import static com.android.systemui.qs.dagger.QSFlagsModule.QS_LABELS_FLAG;
import android.content.res.Configuration;
import android.os.Bundle;
@@ -38,7 +36,6 @@
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.QSEditEvent;
import com.android.systemui.qs.QSFragment;
-import com.android.systemui.qs.QSPanelController;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.statusbar.phone.LightBarController;
@@ -46,14 +43,12 @@
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.ViewController;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
-import javax.inject.Named;
/** {@link ViewController} for {@link QSCustomizer}. */
@QSScope
@@ -67,8 +62,6 @@
private final ConfigurationController mConfigurationController;
private final UiEventLogger mUiEventLogger;
private final Toolbar mToolbar;
- private final TunerService mTunerService;
- private final boolean mQsLabelsFlag;
private final OnMenuItemClickListener mOnMenuItemClickListener = new OnMenuItemClickListener() {
@Override
@@ -76,11 +69,6 @@
if (item.getItemId() == MENU_RESET) {
mUiEventLogger.log(QSEditEvent.QS_EDIT_RESET);
reset();
- } else if (item.getItemId() == MENU_REMOVE_LABELS) {
- item.setChecked(!item.isChecked());
- mTunerService.setValue(
- QSPanelController.QS_REMOVE_LABELS, item.isChecked() ? "1" : "0");
- return false;
}
return false;
}
@@ -111,20 +99,11 @@
}
};
- private final TunerService.Tunable mTunable = new TunerService.Tunable() {
- @Override
- public void onTuningChanged(String key, String newValue) {
- mToolbar.getMenu().findItem(MENU_REMOVE_LABELS)
- .setChecked(newValue != null && !("0".equals(newValue)));
- }
- };
-
@Inject
protected QSCustomizerController(QSCustomizer view, TileQueryHelper tileQueryHelper,
QSTileHost qsTileHost, TileAdapter tileAdapter, ScreenLifecycle screenLifecycle,
KeyguardStateController keyguardStateController, LightBarController lightBarController,
- ConfigurationController configurationController, UiEventLogger uiEventLogger,
- TunerService tunerService, @Named(QS_LABELS_FLAG) boolean qsLabelsFlag) {
+ ConfigurationController configurationController, UiEventLogger uiEventLogger) {
super(view);
mTileQueryHelper = tileQueryHelper;
mQsTileHost = qsTileHost;
@@ -136,21 +115,12 @@
mUiEventLogger = uiEventLogger;
mToolbar = mView.findViewById(com.android.internal.R.id.action_bar);
- mQsLabelsFlag = qsLabelsFlag;
-
- mTunerService = tunerService;
}
- @Override
- protected void onInit() {
- super.onInit();
- mView.getRemoveItem().setVisible(mQsLabelsFlag);
- }
@Override
protected void onViewAttached() {
mView.updateNavBackDrop(getResources().getConfiguration(), mLightBarController);
- mTunerService.addTunable(mTunable, QSPanelController.QS_REMOVE_LABELS);
mConfigurationController.addCallback(mConfigurationListener);
@@ -181,7 +151,6 @@
@Override
protected void onViewDetached() {
- mTunerService.removeTunable(mTunable);
mTileQueryHelper.setListener(null);
mToolbar.setOnMenuItemClickListener(null);
mConfigurationController.removeCallback(mConfigurationListener);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlagsModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlagsModule.java
index 35a8257..10192bc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlagsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlagsModule.java
@@ -21,6 +21,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.util.settings.GlobalSettings;
import javax.inject.Named;
@@ -31,6 +32,8 @@
public interface QSFlagsModule {
String QS_LABELS_FLAG = "qs_labels_flag";
String RBC_AVAILABLE = "rbc_available";
+ String PM_LITE_ENABLED = "pm_lite";
+ String PM_LITE_SETTING = "sysui_pm_lite";
@Provides
@SysUISingleton
@@ -46,4 +49,11 @@
static boolean isReduceBrightColorsAvailable(Context context) {
return ColorDisplayManager.isReduceBrightColorsAvailable(context);
}
+
+ @Provides
+ @SysUISingleton
+ @Named(PM_LITE_ENABLED)
+ static boolean isPMLiteEnabled(FeatureFlags featureFlags, GlobalSettings globalSettings) {
+ return featureFlags.isPMLiteEnabled() && globalSettings.getInt(PM_LITE_SETTING, 0) != 0;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java
index 424aafa..207b25d0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java
@@ -174,9 +174,4 @@
mLabelContainer.setClickable(false);
mLabelContainer.setLongClickable(false);
}
-
- @Override
- public void setShowLabels(boolean show) {
- mHandler.post(() -> mLabelContainer.setVisibility(show ? VISIBLE : GONE));
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt
index c98de8c..07d48f3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.tileimpl
+import android.animation.ValueAnimator
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color
@@ -31,12 +32,17 @@
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.qs.tileimpl.QSTileImpl.getColorForState
+// Placeholder
+private const val CORNER_RADIUS = 40f
+
class QSTileViewHorizontal(
context: Context,
icon: QSIconView
) : QSTileView(context, icon, false) {
private var paintDrawable: PaintDrawable? = null
+ private var paintColor = Color.TRANSPARENT
+ private var paintAnimator: ValueAnimator? = null
init {
orientation = HORIZONTAL
@@ -70,8 +76,8 @@
override fun newTileBackground(): Drawable? {
val d = super.newTileBackground()
if (paintDrawable == null) {
- paintDrawable = PaintDrawable(Color.WHITE).apply {
- setCornerRadius(50f)
+ paintDrawable = PaintDrawable(paintColor).apply {
+ setCornerRadius(CORNER_RADIUS)
}
}
if (d is RippleDrawable) {
@@ -94,9 +100,39 @@
override fun handleStateChanged(state: QSTile.State) {
super.handleStateChanged(state)
- paintDrawable?.setTint(getCircleColor(state.state))
mSecondLine.setTextColor(mLabel.textColors)
mLabelContainer.background = null
+
+ val allowAnimations = animationsEnabled() && paintColor != Color.TRANSPARENT
+ val newColor = getCircleColor(state.state)
+ if (allowAnimations) {
+ animateToNewState(newColor)
+ } else {
+ if (newColor != paintColor) {
+ clearAnimator()
+ paintDrawable?.paint?.color = newColor
+ paintDrawable?.invalidateSelf()
+ }
+ }
+ paintColor = newColor
+ }
+
+ private fun animateToNewState(newColor: Int) {
+ if (newColor != paintColor) {
+ clearAnimator()
+ paintAnimator = ValueAnimator.ofArgb(paintColor, newColor)
+ .setDuration(QSIconViewImpl.QS_ANIM_LENGTH).apply {
+ addUpdateListener { animation: ValueAnimator ->
+ paintDrawable?.paint?.color = animation.animatedValue as Int
+ paintDrawable?.invalidateSelf()
+ }
+ start()
+ }
+ }
+ }
+
+ private fun clearAnimator() {
+ paintAnimator?.cancel()?.also { paintAnimator = null }
}
override fun handleExpand(dualTarget: Boolean) {}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 1dddc45..f03ce2c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -54,6 +54,7 @@
import com.android.systemui.statusbar.policy.HotspotController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.NetworkController.WifiIndicators;
import java.util.ArrayList;
import java.util.LinkedHashMap;
@@ -269,13 +270,10 @@
private final NetworkController.SignalCallback mSignalCallback =
new NetworkController.SignalCallback() {
@Override
- public void setWifiIndicators(boolean enabled,
- NetworkController.IconState statusIcon,
- NetworkController.IconState qsIcon, boolean activityIn, boolean activityOut,
- String description, boolean isTransient, String statusLabel) {
+ public void setWifiIndicators(WifiIndicators indicators) {
// statusIcon.visible has the connected status information
- boolean enabledAndConnected =
- enabled && (qsIcon == null ? false : qsIcon.visible);
+ boolean enabledAndConnected = indicators.enabled
+ && (indicators.qsIcon == null ? false : indicators.qsIcon.visible);
if (enabledAndConnected != mWifiConnected) {
mWifiConnected = enabledAndConnected;
// Hotspot is not connected, so changes here should update
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index 720c5dc..6a574d1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -56,6 +56,7 @@
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NetworkController.IconState;
+import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
import javax.inject.Inject;
@@ -264,21 +265,17 @@
private final CallbackInfo mInfo = new CallbackInfo();
@Override
- public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,
- int qsType, boolean activityIn, boolean activityOut,
- CharSequence typeContentDescription,
- CharSequence typeContentDescriptionHtml, CharSequence description,
- boolean isWide, int subId, boolean roaming, boolean showTriangle) {
- if (qsIcon == null) {
+ public void setMobileDataIndicators(MobileDataIndicators indicators) {
+ if (indicators.qsIcon == null) {
// Not data sim, don't display.
return;
}
mInfo.dataSubscriptionName = mController.getMobileDataNetworkName();
- mInfo.dataContentDescription =
- (description != null) ? typeContentDescriptionHtml : null;
- mInfo.activityIn = activityIn;
- mInfo.activityOut = activityOut;
- mInfo.roaming = roaming;
+ mInfo.dataContentDescription = indicators.description != null
+ ? indicators.typeContentDescriptionHtml : null;
+ mInfo.activityIn = indicators.activityIn;
+ mInfo.activityOut = indicators.activityOut;
+ mInfo.roaming = indicators.roaming;
mInfo.multipleSubs = mController.getNumberSubscriptions() > 1;
refreshState(mInfo);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index 946d041..e1a1fd2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -51,7 +51,9 @@
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NetworkController.IconState;
+import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import com.android.systemui.statusbar.policy.NetworkController.WifiIndicators;
import com.android.systemui.statusbar.policy.WifiIcons;
import java.io.FileDescriptor;
@@ -234,70 +236,44 @@
@Override
- public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
- boolean activityIn, boolean activityOut, String description, boolean isTransient,
- String statusLabel) {
+ public void setWifiIndicators(WifiIndicators indicators) {
if (DEBUG) {
- Log.d(TAG, "setWifiIndicators: "
- + "enabled = " + enabled + ","
- + "statusIcon = " + (statusIcon == null ? "" : statusIcon.toString()) + ","
- + "qsIcon = " + (qsIcon == null ? "" : qsIcon.toString()) + ","
- + "activityIn = " + activityIn + ","
- + "activityOut = " + activityOut + ","
- + "description = " + description + ","
- + "isTransient = " + isTransient + ","
- + "statusLabel = " + statusLabel);
+ Log.d(TAG, "setWifiIndicators: " + indicators);
}
- mWifiInfo.mEnabled = enabled;
- if (qsIcon == null) {
+ mWifiInfo.mEnabled = indicators.enabled;
+ if (indicators.qsIcon == null) {
return;
}
- mWifiInfo.mConnected = qsIcon.visible;
- mWifiInfo.mWifiSignalIconId = qsIcon.icon;
- mWifiInfo.mWifiSignalContentDescription = qsIcon.contentDescription;
- mWifiInfo.mSsid = description;
- mWifiInfo.mActivityIn = activityIn;
- mWifiInfo.mActivityOut = activityOut;
- mWifiInfo.mIsTransient = isTransient;
- mWifiInfo.mStatusLabel = statusLabel;
+ mWifiInfo.mConnected = indicators.qsIcon.visible;
+ mWifiInfo.mWifiSignalIconId = indicators.qsIcon.icon;
+ mWifiInfo.mWifiSignalContentDescription = indicators.qsIcon.contentDescription;
+ mWifiInfo.mEnabled = indicators.enabled;
+ mWifiInfo.mSsid = indicators.description;
+ mWifiInfo.mActivityIn = indicators.activityIn;
+ mWifiInfo.mActivityOut = indicators.activityOut;
+ mWifiInfo.mIsTransient = indicators.isTransient;
+ mWifiInfo.mStatusLabel = indicators.statusLabel;
refreshState(mWifiInfo);
}
@Override
- public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,
- int qsType, boolean activityIn, boolean activityOut,
- CharSequence typeContentDescription,
- CharSequence typeContentDescriptionHtml, CharSequence description,
- boolean isWide, int subId, boolean roaming, boolean showTriangle) {
+ public void setMobileDataIndicators(MobileDataIndicators indicators) {
if (DEBUG) {
- Log.d(TAG, "setMobileDataIndicators: "
- + "statusIcon = " + (statusIcon == null ? "" : statusIcon.toString()) + ","
- + "qsIcon = " + (qsIcon == null ? "" : qsIcon.toString()) + ","
- + "statusType = " + statusType + ","
- + "qsType = " + qsType + ","
- + "activityIn = " + activityIn + ","
- + "activityOut = " + activityOut + ","
- + "typeContentDescription = " + typeContentDescription + ","
- + "typeContentDescriptionHtml = " + typeContentDescriptionHtml + ","
- + "description = " + description + ","
- + "isWide = " + isWide + ","
- + "subId = " + subId + ","
- + "roaming = " + roaming + ","
- + "showTriangle = " + showTriangle);
+ Log.d(TAG, "setMobileDataIndicators: " + indicators);
}
- if (qsIcon == null) {
+ if (indicators.qsIcon == null) {
// Not data sim, don't display.
return;
}
- mCellularInfo.mDataSubscriptionName =
- description == null ? mController.getMobileDataNetworkName() : description;
- mCellularInfo.mDataContentDescription =
- (description != null) ? typeContentDescriptionHtml : null;
- mCellularInfo.mMobileSignalIconId = qsIcon.icon;
- mCellularInfo.mQsTypeIcon = qsType;
- mCellularInfo.mActivityIn = activityIn;
- mCellularInfo.mActivityOut = activityOut;
- mCellularInfo.mRoaming = roaming;
+ mCellularInfo.mDataSubscriptionName = indicators.description == null
+ ? mController.getMobileDataNetworkName() : indicators.description;
+ mCellularInfo.mDataContentDescription = indicators.description != null
+ ? indicators.typeContentDescriptionHtml : null;
+ mCellularInfo.mMobileSignalIconId = indicators.qsIcon.icon;
+ mCellularInfo.mQsTypeIcon = indicators.qsType;
+ mCellularInfo.mActivityIn = indicators.activityIn;
+ mCellularInfo.mActivityOut = indicators.activityOut;
+ mCellularInfo.mRoaming = indicators.roaming;
mCellularInfo.mMultipleSubs = mController.getNumberSubscriptions() > 1;
refreshState(mCellularInfo);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index a6fd011..341e67c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -51,8 +51,8 @@
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NetworkController.AccessPointController;
-import com.android.systemui.statusbar.policy.NetworkController.IconState;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import com.android.systemui.statusbar.policy.NetworkController.WifiIndicators;
import com.android.systemui.statusbar.policy.WifiIcons;
import com.android.wifitrackerlib.WifiEntry;
@@ -303,22 +303,20 @@
final CallbackInfo mInfo = new CallbackInfo();
@Override
- public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
- boolean activityIn, boolean activityOut, String description, boolean isTransient,
- String statusLabel) {
- if (DEBUG) Log.d(TAG, "onWifiSignalChanged enabled=" + enabled);
- if (qsIcon == null) {
+ public void setWifiIndicators(WifiIndicators indicators) {
+ if (DEBUG) Log.d(TAG, "onWifiSignalChanged enabled=" + indicators.enabled);
+ if (indicators.qsIcon == null) {
return;
}
- mInfo.enabled = enabled;
- mInfo.connected = qsIcon.visible;
- mInfo.wifiSignalIconId = qsIcon.icon;
- mInfo.ssid = description;
- mInfo.activityIn = activityIn;
- mInfo.activityOut = activityOut;
- mInfo.wifiSignalContentDescription = qsIcon.contentDescription;
- mInfo.isTransient = isTransient;
- mInfo.statusLabel = statusLabel;
+ mInfo.enabled = indicators.enabled;
+ mInfo.connected = indicators.qsIcon.visible;
+ mInfo.wifiSignalIconId = indicators.qsIcon.icon;
+ mInfo.ssid = indicators.description;
+ mInfo.activityIn = indicators.activityIn;
+ mInfo.activityOut = indicators.activityOut;
+ mInfo.wifiSignalContentDescription = indicators.qsIcon.contentDescription;
+ mInfo.isTransient = indicators.isTransient;
+ mInfo.statusLabel = indicators.statusLabel;
if (isShowingDetail()) {
mDetailAdapter.updateItems();
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 131fde6..805ac7c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -325,6 +325,7 @@
attachWindow();
mWindow.setContentView(mScreenshotView);
+ mScreenshotView.requestApplyInsets();
mScreenshotView.takePartialScreenshot(
rect -> takeScreenshotInternal(finisher, rect));
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
index dc639dc..54b99bb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
@@ -21,24 +21,25 @@
import static java.lang.Math.min;
import static java.util.Objects.requireNonNull;
+import android.annotation.BinderThread;
import android.annotation.UiContext;
import android.app.ActivityTaskManager;
import android.content.Context;
import android.graphics.PixelFormat;
-import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
import android.media.Image;
import android.media.ImageReader;
import android.os.IBinder;
+import android.os.ICancellationSignal;
import android.os.RemoteException;
import android.util.Log;
import android.view.IScrollCaptureCallbacks;
import android.view.IScrollCaptureConnection;
import android.view.IWindowManager;
+import android.view.ScrollCaptureResponse;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.view.ScrollCaptureViewSupport;
import java.util.function.Consumer;
@@ -62,16 +63,19 @@
*/
public interface Connection {
/**
- * Session start should be deferred until UI is active because of resource allocation and
- * potential visible side effects in the target window.
- *
+ * Start a session.
+
* @param sessionConsumer listener to receive the session once active
* @param maxPages the capture buffer size expressed as a multiple of the content height
*/
+ // TODO ListenableFuture
void start(Consumer<Session> sessionConsumer, float maxPages);
/**
- * Close the connection.
+ * Close the connection. Must end capture if started to avoid potential unwanted visual
+ * artifacts.
+ *
+ * @see Session#end(Runnable)
*/
void close();
}
@@ -119,6 +123,7 @@
* @param top the top (y) position of the tile to capture, in content rect space
* @param consumer listener to be informed of the result
*/
+ // TODO ListenableFuture
void requestTile(int top, Consumer<CaptureResult> consumer);
/**
@@ -129,16 +134,31 @@
*/
int getMaxTiles();
+ /**
+ * @return the height of each image tile
+ */
int getTileHeight();
+ /**
+ * @return the height of scrollable content being captured
+ */
int getPageHeight();
+ /**
+ * @return the width of the scrollable page
+ */
int getPageWidth();
/**
+ * @return the bounds on screen of the window being captured.
+ */
+ Rect getWindowBounds();
+
+ /**
* End the capture session, return the target app to original state. The listener
* will be called when the target app is ready to before visible and interactive.
*/
+ // TODO ListenableFuture
void end(Runnable listener);
}
@@ -185,13 +205,13 @@
+ ", taskId=" + taskId + ", consumer=" + consumer + ")");
}
mWindowManagerService.requestScrollCapture(displayId, mHostWindowToken, taskId,
- new ControllerCallbacks(consumer));
+ new ClientCallbacks(consumer));
} catch (RemoteException e) {
Log.e(TAG, "Ignored remote exception", e);
}
}
- private static class ControllerCallbacks extends IScrollCaptureCallbacks.Stub implements
+ private static class ClientCallbacks extends IScrollCaptureCallbacks.Stub implements
Connection, Session, IBinder.DeathRecipient {
private IScrollCaptureConnection mConnection;
@@ -206,46 +226,63 @@
private int mTileWidth;
private Rect mRequestRect;
private boolean mStarted;
+
+ private ICancellationSignal mCancellationSignal;
+ private Rect mWindowBounds;
+ private Rect mBoundsInWindow;
private int mMaxTiles;
- private ControllerCallbacks(Consumer<Connection> connectionConsumer) {
+ private ClientCallbacks(Consumer<Connection> connectionConsumer) {
mConnectionConsumer = connectionConsumer;
}
- // IScrollCaptureCallbacks
-
+ @BinderThread
@Override
- public void onConnected(IScrollCaptureConnection connection, Rect scrollBounds,
- Point positionInWindow) throws RemoteException {
+ public void onScrollCaptureResponse(ScrollCaptureResponse response) throws RemoteException {
if (DEBUG_SCROLL) {
- Log.d(TAG, "onConnected(connection=" + connection + ", scrollBounds=" + scrollBounds
- + ", positionInWindow=" + positionInWindow + ")");
+ Log.d(TAG, "onScrollCaptureResponse(response=" + response + ")");
}
- mConnection = connection;
- mConnection.asBinder().linkToDeath(this, 0);
- mScrollBounds = scrollBounds;
- mConnectionConsumer.accept(this);
+ if (response.isConnected()) {
+ mConnection = response.getConnection();
+ mConnection.asBinder().linkToDeath(this, 0);
+ mWindowBounds = response.getWindowBounds();
+ mBoundsInWindow = response.getBoundsInWindow();
+
+ int pxPerPage = mBoundsInWindow.width() * mBoundsInWindow.height();
+ int pxPerTile = min(TILE_SIZE_PX_MAX, (pxPerPage / TILES_PER_PAGE));
+ mTileWidth = mBoundsInWindow.width();
+ mTileHeight = pxPerTile / mBoundsInWindow.width();
+ if (DEBUG_SCROLL) {
+ Log.d(TAG, "boundsInWindow: " + mBoundsInWindow);
+ Log.d(TAG, "tile size: " + mTileWidth + "x" + mTileHeight);
+ Log.d(TAG, "maxHeight: " + (mMaxTiles * mTileHeight) + "px");
+ }
+ mConnectionConsumer.accept(this);
+ }
mConnectionConsumer = null;
-
- int pxPerPage = mScrollBounds.width() * mScrollBounds.height();
- int pxPerTile = min(TILE_SIZE_PX_MAX, (pxPerPage / TILES_PER_PAGE));
- mTileWidth = mScrollBounds.width();
- mTileHeight = pxPerTile / mScrollBounds.width();
- if (DEBUG_SCROLL) {
- Log.d(TAG, "scrollBounds: " + mScrollBounds);
- Log.d(TAG, "tile dimen: " + mTileWidth + "x" + mTileHeight);
- }
}
@Override
- public void onUnavailable() throws RemoteException {
+ public void start(Consumer<Session> sessionConsumer, float maxPages) {
if (DEBUG_SCROLL) {
- Log.d(TAG, "onUnavailable");
+ Log.d(TAG, "start(sessionConsumer=" + sessionConsumer + ","
+ + " maxPages=" + maxPages + ")");
}
- // The targeted app does not support scroll capture
- // or the window could not be found... etc etc.
+ mMaxTiles = (int) Math.ceil(maxPages * TILES_PER_PAGE);
+ mReader = ImageReader.newInstance(mTileWidth, mTileHeight, PixelFormat.RGBA_8888,
+ mMaxTiles, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
+ mSessionConsumer = sessionConsumer;
+
+ try {
+ mCancellationSignal = mConnection.startCapture(mReader.getSurface());
+ mStarted = true;
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to start", e);
+ mReader.close();
+ }
}
+ @BinderThread
@Override
public void onCaptureStarted() {
if (DEBUG_SCROLL) {
@@ -256,13 +293,25 @@
}
@Override
- public void onCaptureBufferSent(long frameNumber, Rect contentArea) {
- Image image = null;
- if (frameNumber != ScrollCaptureViewSupport.NO_FRAME_PRODUCED) {
- image = mReader.acquireNextImage();
- }
+ public void requestTile(int top, Consumer<CaptureResult> consumer) {
if (DEBUG_SCROLL) {
- Log.d(TAG, "onCaptureBufferSent(frameNumber=" + frameNumber
+ Log.d(TAG, "requestTile(top=" + top + ", consumer=" + consumer + ")");
+ }
+ cancelPendingRequest();
+ mRequestRect = new Rect(0, top, mTileWidth, top + mTileHeight);
+ mResultConsumer = consumer;
+ try {
+ mCancellationSignal = mConnection.requestImage(mRequestRect);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Caught remote exception from requestImage", e);
+ }
+ }
+
+ @Override
+ public void onImageRequestCompleted(int flags, Rect contentArea) {
+ Image image = mReader.acquireLatestImage();
+ if (DEBUG_SCROLL) {
+ Log.d(TAG, "onCaptureBufferSent(flags=" + flags
+ ", contentArea=" + contentArea + ") image=" + image);
}
// Save and clear first, since the consumer will likely request the next
@@ -273,61 +322,13 @@
}
@Override
- public void onConnectionClosed() {
- if (DEBUG_SCROLL) {
- Log.d(TAG, "onConnectionClosed()");
- }
- disconnect();
- if (mShutdownListener != null) {
- mShutdownListener.run();
- mShutdownListener = null;
- }
- }
-
- // Misc
-
- private void disconnect() {
- if (mConnection != null) {
- mConnection.asBinder().unlinkToDeath(this, 0);
- }
- mConnection = null;
- }
-
- // ScrollCaptureController.Connection
-
- @Override
- public void start(Consumer<Session> sessionConsumer, float maxPages) {
- if (DEBUG_SCROLL) {
- Log.d(TAG, "start(sessionConsumer=" + sessionConsumer + ","
- + " maxPages=" + maxPages + ")"
- + " [maxHeight: " + (mMaxTiles * mTileHeight) + "px]");
- }
- mMaxTiles = (int) Math.ceil(maxPages * TILES_PER_PAGE);
- mReader = ImageReader.newInstance(mTileWidth, mTileHeight, PixelFormat.RGBA_8888,
- mMaxTiles, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
- mSessionConsumer = sessionConsumer;
- try {
- mConnection.startCapture(mReader.getSurface());
- mStarted = true;
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to start", e);
- }
- }
-
- @Override
- public void close() {
- end(null);
- }
-
- // ScrollCaptureController.Session
-
- @Override
public void end(Runnable listener) {
if (DEBUG_SCROLL) {
Log.d(TAG, "end(listener=" + listener + ")");
}
if (mStarted) {
mShutdownListener = listener;
+ mReader.close();
try {
// listener called from onConnectionClosed callback
mConnection.endCapture();
@@ -342,40 +343,37 @@
}
}
+ @BinderThread
@Override
- public int getPageHeight() {
- return mScrollBounds.height();
- }
-
- @Override
- public int getPageWidth() {
- return mScrollBounds.width();
- }
-
- @Override
- public int getTileHeight() {
- return mTileHeight;
- }
-
- @Override
- public int getMaxTiles() {
- return mMaxTiles;
- }
-
- @Override
- public void requestTile(int top, Consumer<CaptureResult> consumer) {
- if (DEBUG_SCROLL) {
- Log.d(TAG, "requestTile(top=" + top + ", consumer=" + consumer + ")");
+ public void onCaptureEnded() {
+ close();
+ if (mShutdownListener != null) {
+ mShutdownListener.run();
+ mShutdownListener = null;
}
- mRequestRect = new Rect(0, top, mTileWidth, top + mTileHeight);
- mResultConsumer = consumer;
- try {
- mConnection.requestImage(mRequestRect);
- } catch (RemoteException e) {
- Log.e(TAG, "Caught remote exception from requestImage", e);
+ }
+
+ @Override
+ public void close() {
+ if (mConnection != null) {
+ try {
+ mConnection.close();
+ } catch (RemoteException e) {
+ /* ignore */
+ }
+ disconnect();
}
}
+ // Misc
+
+ private void disconnect() {
+ if (mConnection != null) {
+ mConnection.asBinder().unlinkToDeath(this, 0);
+ }
+ mConnection = null;
+ }
+
/**
* The process hosting the window went away abruptly!
*/
@@ -386,5 +384,40 @@
}
disconnect();
}
+
+ @Override
+ public int getPageHeight() {
+ return mBoundsInWindow.height();
+ }
+
+ @Override
+ public int getPageWidth() {
+ return mBoundsInWindow.width();
+ }
+
+ @Override
+ public int getTileHeight() {
+ return mTileHeight;
+ }
+
+ public Rect getWindowBounds() {
+ return new Rect(mWindowBounds);
+ }
+
+ @Override
+ public int getMaxTiles() {
+ return mMaxTiles;
+ }
+
+ private void cancelPendingRequest() {
+ if (mCancellationSignal != null) {
+ try {
+ mCancellationSignal.cancel();
+ } catch (RemoteException e) {
+ /* ignore */
+ }
+ mCancellationSignal = null;
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
index cf77e29..1d59257 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
@@ -86,4 +86,8 @@
public boolean isNavigationBarOverlayEnabled() {
return mFlagReader.isEnabled(R.bool.flag_navigation_bar_overlay);
}
+
+ public boolean isPMLiteEnabled() {
+ return mFlagReader.isEnabled(R.bool.flag_pm_lite);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 7e2d27a..a4e97a1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar;
+import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
@@ -41,6 +42,7 @@
import android.content.IntentFilter;
import android.content.pm.UserInfo;
import android.content.res.ColorStateList;
+import android.content.res.Resources;
import android.graphics.Color;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.face.FaceManager;
@@ -277,10 +279,7 @@
// avoid calling this method since it has an IPC
if (whitelistIpcs(this::isOrganizationOwnedDevice)) {
final CharSequence organizationName = getOrganizationOwnedDeviceOrganizationName();
- final CharSequence disclosure = organizationName != null
- ? mContext.getResources().getString(R.string.do_disclosure_with_name,
- organizationName)
- : mContext.getResources().getText(R.string.do_disclosure_generic);
+ final CharSequence disclosure = getDisclosureText(organizationName);
mRotateTextViewController.updateIndication(
INDICATION_TYPE_DISCLOSURE,
new KeyguardIndication.Builder()
@@ -297,6 +296,22 @@
}
}
+ private CharSequence getDisclosureText(@Nullable CharSequence organizationName) {
+ final Resources packageResources = mContext.getResources();
+ if (organizationName == null) {
+ return packageResources.getText(R.string.do_disclosure_generic);
+ } else if (mDevicePolicyManager.isDeviceManaged()
+ && mDevicePolicyManager.getDeviceOwnerType(
+ mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser())
+ == DEVICE_OWNER_TYPE_FINANCED) {
+ return packageResources.getString(R.string.do_financed_disclosure_with_name,
+ organizationName);
+ } else {
+ return packageResources.getString(R.string.do_disclosure_with_name,
+ organizationName);
+ }
+ }
+
private void updateOwnerInfo() {
if (!isKeyguardLayoutEnabled()) {
mRotateTextViewController.hideIndication(INDICATION_TYPE_OWNER_INFO);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index f427ba9..7691761 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -17,15 +17,11 @@
package com.android.systemui.statusbar.notification.row;
-import static android.provider.Settings.Global.NOTIFICATION_BUBBLES;
-import static android.provider.Settings.Secure.SHOW_NOTIFICATION_SNOOZE;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
-import android.content.res.Resources;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
@@ -1317,7 +1313,7 @@
private boolean isBubblesEnabled() {
return Settings.Global.getInt(mContext.getContentResolver(),
- NOTIFICATION_BUBBLES, 0) == 1;
+ Settings.Global.NOTIFICATION_BUBBLES, 0) == 1;
}
/**
@@ -1373,27 +1369,26 @@
}
ImageView snoozeButton = layout.findViewById(com.android.internal.R.id.snooze_button);
View actionContainer = layout.findViewById(com.android.internal.R.id.actions_container);
- LinearLayout actionContainerLayout =
- layout.findViewById(com.android.internal.R.id.actions_container_layout);
- if (snoozeButton == null || actionContainer == null || actionContainerLayout == null) {
+ if (snoozeButton == null || actionContainer == null) {
return;
}
final boolean showSnooze = Settings.Secure.getInt(mContext.getContentResolver(),
- SHOW_NOTIFICATION_SNOOZE, 0) == 1;
- if (!showSnooze) {
+ Settings.Secure.SHOW_NOTIFICATION_SNOOZE, 0) == 1;
+ // Notification.Builder can 'disable' the snooze button to prevent it from being shown here
+ boolean snoozeDisabled = !snoozeButton.isEnabled();
+ if (!showSnooze || snoozeDisabled) {
snoozeButton.setVisibility(GONE);
return;
}
- Resources res = mContext.getResources();
- Drawable snoozeDrawable = res.getDrawable(R.drawable.ic_snooze);
+ Drawable snoozeDrawable = mContext.getDrawable(R.drawable.ic_snooze);
mContainingNotification.updateNotificationColor();
snoozeDrawable.setTint(mContainingNotification.getNotificationColor());
snoozeButton.setImageDrawable(snoozeDrawable);
final NotificationSnooze snoozeGuts = (NotificationSnooze) LayoutInflater.from(mContext)
.inflate(R.layout.notification_snooze, null, false);
- final String snoozeDescription = res.getString(
+ final String snoozeDescription = mContext.getString(
R.string.notification_menu_snooze_description);
final NotificationMenuRowPlugin.MenuItem snoozeMenuItem =
new NotificationMenuRow.NotificationMenuItem(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java
index 414d620..222735a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java
@@ -49,7 +49,7 @@
// Custom views will most likely use just white or black as their text color.
// We need to scan through and replace these colors by Material NEXT colors.
- ensureThemeOnChildren();
+ ensureThemeOnChildren(mView);
// Let's invert the notification colors when we're in night mode and
// the notification background isn't colorized.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationDecoratedCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationDecoratedCustomViewWrapper.java
index 301c372..d21ae13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationDecoratedCustomViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationDecoratedCustomViewWrapper.java
@@ -64,7 +64,7 @@
// Custom views will most likely use just white or black as their text color.
// We need to scan through and replace these colors by Material NEXT colors.
- ensureThemeOnChildren();
+ ensureThemeOnChildren(mWrappedView);
if (needsInversion(resolveBackgroundColor(), mWrappedView)) {
invertViewLuminosity(mWrappedView);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
index 0ef4c4d..89babf0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
@@ -58,9 +58,10 @@
private final Rect mTmpRect = new Rect();
protected int mBackgroundColor = 0;
- private int mLightTextColor;
- private int mDarkTextColor;
- private int mDefaultTextColor;
+ private int mMaterialTextColorPrimary;
+ private int mMaterialTextColorSecondary;
+ private int mThemedTextColorPrimary;
+ private int mThemedTextColorSecondary;
private boolean mAdjustTheme;
public static NotificationViewWrapper wrap(Context ctx, View v, ExpandableNotificationRow row) {
@@ -124,15 +125,22 @@
mBackgroundColor = backgroundColor;
mView.setBackground(new ColorDrawable(Color.TRANSPARENT));
}
- mLightTextColor = mView.getContext().getColor(
- com.android.internal.R.color.notification_primary_text_color_light);
- mDarkTextColor = mView.getContext().getColor(
- com.android.internal.R.color.notification_primary_text_color_dark);
+
+ Context materialTitleContext = new ContextThemeWrapper(mView.getContext(),
+ com.android.internal.R.style.TextAppearance_Material_Notification_Title);
+ mMaterialTextColorPrimary = Utils.getColorAttr(materialTitleContext,
+ com.android.internal.R.attr.textColor).getDefaultColor();
+ Context materialContext = new ContextThemeWrapper(mView.getContext(),
+ com.android.internal.R.style.TextAppearance_Material_Notification);
+ mMaterialTextColorSecondary = Utils.getColorAttr(materialContext,
+ com.android.internal.R.attr.textColor).getDefaultColor();
Context themedContext = new ContextThemeWrapper(mView.getContext(),
com.android.internal.R.style.Theme_DeviceDefault_DayNight);
- mDefaultTextColor = Utils.getColorAttr(themedContext,
+ mThemedTextColorPrimary = Utils.getColorAttr(themedContext,
com.android.internal.R.attr.textColorPrimary).getDefaultColor();
+ mThemedTextColorSecondary = Utils.getColorAttr(themedContext,
+ com.android.internal.R.attr.textColorSecondary).getDefaultColor();
}
protected boolean needsInversion(int defaultBackgroundColor, View view) {
@@ -210,27 +218,30 @@
return false;
}
- protected void ensureThemeOnChildren() {
- if (!mAdjustTheme || mView == null) {
+ protected void ensureThemeOnChildren(View rootView) {
+ if (!mAdjustTheme || mView == null || rootView == null) {
return;
}
// Notifications with custom backgrounds should not be adjusted
if (mBackgroundColor != Color.TRANSPARENT
- || getBackgroundColor(mView) != Color.TRANSPARENT) {
+ || getBackgroundColor(mView) != Color.TRANSPARENT
+ || getBackgroundColor(rootView) != Color.TRANSPARENT) {
return;
}
// Now let's check if there's unprotected text somewhere, and apply the theme if we find it.
- processTextColorRecursive(mView);
+ processTextColorRecursive(rootView);
}
private void processTextColorRecursive(View view) {
if (view instanceof TextView) {
TextView textView = (TextView) view;
int foreground = textView.getCurrentTextColor();
- if (foreground == mLightTextColor || foreground == mDarkTextColor) {
- textView.setTextColor(mDefaultTextColor);
+ if (foreground == mMaterialTextColorPrimary) {
+ textView.setTextColor(mThemedTextColorPrimary);
+ } else if (foreground == mMaterialTextColorSecondary) {
+ textView.setTextColor(mThemedTextColorSecondary);
}
} else if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) view;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 093f57a..83c347b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -605,6 +605,7 @@
mKeyguardQsUserSwitchEnabled =
mKeyguardUserSwitcherEnabled && mResources.getBoolean(
R.bool.config_keyguard_user_switch_opens_qs_details);
+ keyguardUpdateMonitor.setKeyguardQsUserSwitchEnabled(mKeyguardQsUserSwitchEnabled);
mView.setWillNotDraw(!DEBUG);
mLayoutInflater = layoutInflater;
mFalsingManager = falsingManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
index dacd941..9ee7b09 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
@@ -27,6 +27,8 @@
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NetworkController.IconState;
+import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators;
+import com.android.systemui.statusbar.policy.NetworkController.WifiIndicators;
import com.android.systemui.statusbar.policy.NetworkControllerImpl;
import com.android.systemui.statusbar.policy.SecurityController;
import com.android.systemui.tuner.TunerService;
@@ -143,24 +145,14 @@
}
@Override
- public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
- boolean activityIn, boolean activityOut, String description, boolean isTransient,
- String statusLabel) {
+ public void setWifiIndicators(WifiIndicators indicators) {
if (DEBUG) {
- Log.d(TAG, "setWifiIndicators: "
- + "enabled = " + enabled + ","
- + "statusIcon = " + (statusIcon == null ? "" : statusIcon.toString()) + ","
- + "qsIcon = " + (qsIcon == null ? "" : qsIcon.toString()) + ","
- + "activityIn = " + activityIn + ","
- + "activityOut = " + activityOut + ","
- + "description = " + description + ","
- + "isTransient = " + isTransient + ","
- + "statusLabel = " + statusLabel);
+ Log.d(TAG, "setWifiIndicators: " + indicators);
}
- boolean visible = statusIcon.visible && !mHideWifi;
- boolean in = activityIn && mActivityEnabled && visible;
- boolean out = activityOut && mActivityEnabled && visible;
- mIsWifiEnabled = enabled;
+ boolean visible = indicators.statusIcon.visible && !mHideWifi;
+ boolean in = indicators.activityIn && mActivityEnabled && visible;
+ boolean out = indicators.activityOut && mActivityEnabled && visible;
+ mIsWifiEnabled = indicators.enabled;
WifiIconState newState = mWifiIconState.copy();
@@ -174,10 +166,10 @@
newState.resId = R.drawable.ic_qs_no_internet_available;
} else {
newState.visible = visible;
- newState.resId = statusIcon.icon;
+ newState.resId = indicators.statusIcon.icon;
newState.activityIn = in;
newState.activityOut = out;
- newState.contentDescription = statusIcon.contentDescription;
+ newState.contentDescription = indicators.statusIcon.contentDescription;
MobileIconState first = getFirstMobileState();
newState.signalSpacerVisible = first != null && first.typeId != 0;
}
@@ -225,44 +217,28 @@
}
@Override
- public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,
- int qsType, boolean activityIn, boolean activityOut,
- CharSequence typeContentDescription,
- CharSequence typeContentDescriptionHtml, CharSequence description,
- boolean isWide, int subId, boolean roaming, boolean showTriangle) {
+ public void setMobileDataIndicators(MobileDataIndicators indicators) {
if (DEBUG) {
- Log.d(TAG, "setMobileDataIndicators: "
- + "statusIcon = " + (statusIcon == null ? "" : statusIcon.toString()) + ","
- + "qsIcon = " + (qsIcon == null ? "" : qsIcon.toString()) + ","
- + "statusType = " + statusType + ","
- + "qsType = " + qsType + ","
- + "activityIn = " + activityIn + ","
- + "activityOut = " + activityOut + ","
- + "typeContentDescription = " + typeContentDescription + ","
- + "typeContentDescriptionHtml = " + typeContentDescriptionHtml + ","
- + "description = " + description + ","
- + "isWide = " + isWide + ","
- + "subId = " + subId + ","
- + "roaming = " + roaming + ","
- + "showTriangle = " + showTriangle);
+ Log.d(TAG, "setMobileDataIndicators: " + indicators);
}
- MobileIconState state = getState(subId);
+ MobileIconState state = getState(indicators.subId);
if (state == null) {
return;
}
// Visibility of the data type indicator changed
- boolean typeChanged = statusType != state.typeId && (statusType == 0 || state.typeId == 0);
+ boolean typeChanged = indicators.statusType != state.typeId
+ && (indicators.statusType == 0 || state.typeId == 0);
- state.visible = statusIcon.visible && !mHideMobile;
- state.strengthId = statusIcon.icon;
- state.typeId = statusType;
- state.contentDescription = statusIcon.contentDescription;
- state.typeContentDescription = typeContentDescription;
- state.showTriangle = showTriangle;
- state.roaming = roaming;
- state.activityIn = activityIn && mActivityEnabled;
- state.activityOut = activityOut && mActivityEnabled;
+ state.visible = indicators.statusIcon.visible && !mHideMobile;
+ state.strengthId = indicators.statusIcon.icon;
+ state.typeId = indicators.statusType;
+ state.contentDescription = indicators.statusIcon.contentDescription;
+ state.typeContentDescription = indicators.typeContentDescription;
+ state.showTriangle = indicators.showTriangle;
+ state.roaming = indicators.roaming;
+ state.activityIn = indicators.activityIn && mActivityEnabled;
+ state.activityOut = indicators.activityOut && mActivityEnabled;
if (DEBUG) {
Log.d(TAG, "MobileIconStates: "
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
index 528c0cb..b96cb5e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
@@ -23,7 +23,9 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.statusbar.policy.NetworkController.EmergencyListener;
import com.android.systemui.statusbar.policy.NetworkController.IconState;
+import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import com.android.systemui.statusbar.policy.NetworkController.WifiIndicators;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
@@ -119,63 +121,29 @@
}
@Override
- public void setWifiIndicators(final boolean enabled, final IconState statusIcon,
- final IconState qsIcon, final boolean activityIn, final boolean activityOut,
- final String description, boolean isTransient, String secondaryLabel) {
+ public void setWifiIndicators(final WifiIndicators indicators) {
String log = new StringBuilder()
.append(SSDF.format(System.currentTimeMillis())).append(",")
- .append("setWifiIndicators: ")
- .append("enabled=").append(enabled).append(",")
- .append("statusIcon=").append(statusIcon).append(",")
- .append("qsIcon=").append(qsIcon).append(",")
- .append("activityIn=").append(activityIn).append(",")
- .append("activityOut=").append(activityOut).append(",")
- .append("description=").append(description).append(",")
- .append("isTransient=").append(isTransient).append(",")
- .append("secondaryLabel=").append(secondaryLabel)
+ .append(indicators)
.toString();
recordLastCallback(log);
post(() -> {
for (SignalCallback callback : mSignalCallbacks) {
- callback.setWifiIndicators(enabled, statusIcon, qsIcon, activityIn, activityOut,
- description, isTransient, secondaryLabel);
+ callback.setWifiIndicators(indicators);
}
});
-
-
}
@Override
- public void setMobileDataIndicators(final IconState statusIcon, final IconState qsIcon,
- final int statusType, final int qsType, final boolean activityIn,
- final boolean activityOut, final CharSequence typeContentDescription,
- CharSequence typeContentDescriptionHtml, final CharSequence description,
- final boolean isWide, final int subId, boolean roaming, boolean showTriangle) {
+ public void setMobileDataIndicators(final MobileDataIndicators indicators) {
String log = new StringBuilder()
.append(SSDF.format(System.currentTimeMillis())).append(",")
- .append("setMobileDataIndicators: ")
- .append("statusIcon=").append(statusIcon).append(",")
- .append("qsIcon=").append(qsIcon).append(",")
- .append("statusType=").append(statusType).append(",")
- .append("qsType=").append(qsType).append(",")
- .append("activityIn=").append(activityIn).append(",")
- .append("activityOut=").append(activityOut).append(",")
- .append("typeContentDescription=").append(typeContentDescription).append(",")
- .append("typeContentDescriptionHtml=").append(typeContentDescriptionHtml)
- .append(",")
- .append("description=").append(description).append(",")
- .append("isWide=").append(isWide).append(",")
- .append("subId=").append(subId).append(",")
- .append("roaming=").append(roaming).append(",")
- .append("showTriangle=").append(showTriangle)
+ .append(indicators)
.toString();
recordLastCallback(log);
post(() -> {
for (SignalCallback signalCluster : mSignalCallbacks) {
- signalCluster.setMobileDataIndicators(statusIcon, qsIcon, statusType, qsType,
- activityIn, activityOut, typeContentDescription,
- typeContentDescriptionHtml, description, isWide, subId, roaming,
- showTriangle);
+ signalCluster.setMobileDataIndicators(indicators);
}
});
}
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 1ab7652..6c097bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -59,6 +59,7 @@
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.MobileDataIndicators;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
import java.io.PrintWriter;
@@ -402,10 +403,12 @@
showDataIcon |= mCurrentState.roaming;
IconState statusIcon = new IconState(showDataIcon && !mCurrentState.airplaneMode,
getCurrentIconId(), contentDescription);
- callback.setMobileDataIndicators(statusIcon, qsIcon, typeIcon, qsTypeIcon,
+ MobileDataIndicators mobileDataIndicators = new MobileDataIndicators(
+ statusIcon, qsIcon, typeIcon, qsTypeIcon,
activityIn, activityOut, dataContentDescription, dataContentDescriptionHtml,
description, icons.isWide, mSubscriptionInfo.getSubscriptionId(),
mCurrentState.roaming, showTriangle);
+ callback.setMobileDataIndicators(mobileDataIndicators);
} else {
boolean showDataIcon = mCurrentState.dataConnected || dataDisabled;
IconState statusIcon = new IconState(
@@ -432,10 +435,12 @@
showDataIcon &= mCurrentState.isDefault || dataDisabled;
int typeIcon = (showDataIcon || mConfig.alwaysShowDataRatIcon) ? icons.dataType : 0;
boolean showTriangle = mCurrentState.enabled && !mCurrentState.airplaneMode;
- callback.setMobileDataIndicators(statusIcon, qsIcon, typeIcon, qsTypeIcon,
+ MobileDataIndicators mobileDataIndicators = new MobileDataIndicators(
+ statusIcon, qsIcon, typeIcon, qsTypeIcon,
activityIn, activityOut, dataContentDescription, dataContentDescriptionHtml,
description, icons.isWide, mSubscriptionInfo.getSubscriptionId(),
mCurrentState.roaming, showTriangle);
+ callback.setMobileDataIndicators(mobileDataIndicators);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index 0a9fead..ef2ca98 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -46,34 +46,117 @@
boolean isRadioOn();
+ /**
+ * Wrapper class for all the WiFi signals used for WiFi indicators.
+ */
+ final class WifiIndicators {
+ public boolean enabled;
+ public IconState statusIcon;
+ public IconState qsIcon;
+ public boolean activityIn;
+ public boolean activityOut;
+ public String description;
+ public boolean isTransient;
+ public String statusLabel;
+
+ public WifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
+ boolean activityIn, boolean activityOut, String description,
+ boolean isTransient, String statusLabel) {
+ this.enabled = enabled;
+ this.statusIcon = statusIcon;
+ this.qsIcon = qsIcon;
+ this.activityIn = activityIn;
+ this.activityOut = activityOut;
+ this.description = description;
+ this.isTransient = isTransient;
+ this.statusLabel = statusLabel;
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder("WifiIndicators[")
+ .append("enabled=").append(enabled)
+ .append(",statusIcon=").append(statusIcon == null ? "" : statusIcon.toString())
+ .append(",qsIcon=").append(qsIcon == null ? "" : qsIcon.toString())
+ .append(",activityIn=").append(activityIn)
+ .append(",activityOut=").append(activityOut)
+ .append(",description=").append(description)
+ .append(",isTransient=").append(isTransient)
+ .append(",statusLabel=").append(statusLabel)
+ .append(']').toString();
+ }
+ }
+
+ /**
+ * Wrapper class for all the mobile signals used for mobile data indicators.
+ */
+ final class MobileDataIndicators {
+ public IconState statusIcon;
+ public IconState qsIcon;
+ public int statusType;
+ public int qsType;
+ public boolean activityIn;
+ public boolean activityOut;
+ public CharSequence typeContentDescription;
+ public CharSequence typeContentDescriptionHtml;
+ public CharSequence description;
+ public boolean isWide;
+ public int subId;
+ public boolean roaming;
+ public boolean showTriangle;
+
+ public MobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,
+ int qsType, boolean activityIn, boolean activityOut,
+ CharSequence typeContentDescription, CharSequence typeContentDescriptionHtml,
+ CharSequence description, boolean isWide, int subId, boolean roaming,
+ boolean showTriangle) {
+ this.statusIcon = statusIcon;
+ this.qsIcon = qsIcon;
+ this.statusType = statusType;
+ this.qsType = qsType;
+ this.activityIn = activityIn;
+ this.activityOut = activityOut;
+ this.typeContentDescription = typeContentDescription;
+ this.typeContentDescriptionHtml = typeContentDescriptionHtml;
+ this.description = description;
+ this.isWide = isWide;
+ this.subId = subId;
+ this.roaming = roaming;
+ this.showTriangle = showTriangle;
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder("MobileDataIndicators[")
+ .append("statusIcon=").append(statusIcon == null ? "" : statusIcon.toString())
+ .append(",qsIcon=").append(qsIcon == null ? "" : qsIcon.toString())
+ .append(",statusType=").append(statusType)
+ .append(",qsType=").append(qsType)
+ .append(",activityIn=").append(activityIn)
+ .append(",activityOut=").append(activityOut)
+ .append(",typeContentDescription=").append(typeContentDescription)
+ .append(",typeContentDescriptionHtml=").append(typeContentDescriptionHtml)
+ .append(",description=").append(description)
+ .append(",isWide=").append(isWide)
+ .append(",subId=").append(subId)
+ .append(",roaming=").append(roaming)
+ .append(",showTriangle=").append(showTriangle)
+ .append(']').toString();
+ }
+ }
+
public interface SignalCallback {
- default void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
- boolean activityIn, boolean activityOut, String description, boolean isTransient,
- String statusLabel) {}
+ /**
+ * Callback for listeners to be able to update the state of any UI tracking connectivity of
+ * WiFi networks.
+ */
+ default void setWifiIndicators(WifiIndicators wifiIndicators) {}
/**
* Callback for listeners to be able to update the state of any UI tracking connectivity
- * @param statusIcon the icon that should be shown in the status bar
- * @param qsIcon the icon to show in Quick Settings
- * @param statusType the resId of the data type icon (e.g. LTE) to show in the status bar
- * @param qsType similar to above, the resId of the data type icon to show in Quick Settings
- * @param activityIn indicates whether there is inbound activity
- * @param activityOut indicates outbound activity
- * @param typeContentDescription the contentDescription of the data type
- * @param typeContentDescriptionHtml the (possibly HTML-styled) contentDescription of the
- * data type. Suitable for display
- * @param description description of the network (usually just the network name)
- * @param isWide //TODO: unused?
- * @param subId subscription ID for which to update the UI
- * @param roaming indicates roaming
- * @param showTriangle whether to show the mobile triangle the in status bar
+ * of Mobile networks.
*/
- default void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,
- int qsType, boolean activityIn, boolean activityOut,
- CharSequence typeContentDescription,
- CharSequence typeContentDescriptionHtml, CharSequence description,
- boolean isWide, int subId, boolean roaming, boolean showTriangle) {
- }
+ default void setMobileDataIndicators(MobileDataIndicators mobileDataIndicators) {}
default void setSubs(List<SubscriptionInfo> subs) {}
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 9f92142..8eb1e64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -165,7 +165,7 @@
private int mCurrentUserId;
private OnSubscriptionsChangedListener mSubscriptionListener;
-
+ private NetworkCapabilities mLastDefaultNetworkCapabilities;
// Handler that all broadcasts are received on.
private final Handler mReceiverHandler;
// Handler that all callbacks are made on.
@@ -315,6 +315,7 @@
public void onLost(Network network) {
mLastNetwork = null;
mLastNetworkCapabilities = null;
+ mLastDefaultNetworkCapabilities = null;
String callback = new StringBuilder()
.append(SSDF.format(System.currentTimeMillis())).append(",")
.append("onLost: ")
@@ -341,6 +342,7 @@
}
mLastNetwork = network;
mLastNetworkCapabilities = networkCapabilities;
+ mLastDefaultNetworkCapabilities = networkCapabilities;
String callback = new StringBuilder()
.append(SSDF.format(System.currentTimeMillis())).append(",")
.append("onCapabilitiesChanged: ")
@@ -959,18 +961,17 @@
private void updateConnectivity() {
mConnectedTransports.clear();
mValidatedTransports.clear();
- for (NetworkCapabilities nc :
- mConnectivityManager.getDefaultNetworkCapabilitiesForUser(mCurrentUserId)) {
- for (int transportType : nc.getTransportTypes()) {
+ if (mLastDefaultNetworkCapabilities != null) {
+ for (int transportType : mLastDefaultNetworkCapabilities.getTransportTypes()) {
if (transportType == NetworkCapabilities.TRANSPORT_CELLULAR
- && Utils.tryGetWifiInfoForVcn(nc) != null) {
+ && Utils.tryGetWifiInfoForVcn(mLastDefaultNetworkCapabilities) != null) {
mConnectedTransports.set(NetworkCapabilities.TRANSPORT_WIFI);
- if (nc.hasCapability(NET_CAPABILITY_VALIDATED)) {
+ if (mLastDefaultNetworkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) {
mValidatedTransports.set(NetworkCapabilities.TRANSPORT_WIFI);
}
} else {
mConnectedTransports.set(transportType);
- if (nc.hasCapability(NET_CAPABILITY_VALIDATED)) {
+ if (mLastDefaultNetworkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) {
mValidatedTransports.set(transportType);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
index 16998d7..8d72c9c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
@@ -38,7 +38,9 @@
import com.android.settingslib.wifi.WifiStatusTracker;
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.NetworkController.IconState;
+import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import com.android.systemui.statusbar.policy.NetworkController.WifiIndicators;
import java.io.PrintWriter;
import java.util.Objects;
@@ -113,18 +115,24 @@
mWifiTracker.isCaptivePortal ? R.drawable.ic_qs_wifi_disconnected
: getQsCurrentIconId(), contentDescription);
}
- callback.setWifiIndicators(mCurrentState.enabled, statusIcon, qsIcon,
+ WifiIndicators wifiIndicators = new WifiIndicators(
+ mCurrentState.enabled, statusIcon, qsIcon,
ssidPresent && mCurrentState.activityIn,
ssidPresent && mCurrentState.activityOut,
- wifiDesc, mCurrentState.isTransient, mCurrentState.statusLabel);
+ wifiDesc, mCurrentState.isTransient, mCurrentState.statusLabel
+ );
+ callback.setWifiIndicators(wifiIndicators);
} else {
IconState qsIcon = new IconState(mCurrentState.connected,
mWifiTracker.isCaptivePortal ? R.drawable.ic_qs_wifi_disconnected
: getQsCurrentIconId(), contentDescription);
- callback.setWifiIndicators(mCurrentState.enabled, statusIcon, qsIcon,
+ WifiIndicators wifiIndicators = new WifiIndicators(
+ mCurrentState.enabled, statusIcon, qsIcon,
ssidPresent && mCurrentState.activityIn,
ssidPresent && mCurrentState.activityOut,
- wifiDesc, mCurrentState.isTransient, mCurrentState.statusLabel);
+ wifiDesc, mCurrentState.isTransient, mCurrentState.statusLabel
+ );
+ callback.setWifiIndicators(wifiIndicators);
}
}
@@ -149,10 +157,13 @@
mCurrentState.connected, getQsCurrentIconIdForCarrierWifi(), contentDescription);
CharSequence description =
mNetworkController.getNetworkNameForCarrierWiFi(mCurrentState.subId);
- callback.setMobileDataIndicators(statusIcon, qsIcon, typeIcon, qsTypeIcon,
+ MobileDataIndicators mobileDataIndicators = new MobileDataIndicators(
+ statusIcon, qsIcon, typeIcon, qsTypeIcon,
mCurrentState.activityIn, mCurrentState.activityOut, dataContentDescription,
dataContentDescriptionHtml, description, icons.isWide,
- mCurrentState.subId, /* roaming= */ false, /* showTriangle= */ true);
+ mCurrentState.subId, /* roaming= */ false, /* showTriangle= */ true
+ );
+ callback.setMobileDataIndicators(mobileDataIndicators);
}
private int getCurrentIconIdForCarrierWifi() {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index df54eab..25345d5 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -33,7 +33,11 @@
import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.Dialog;
@@ -51,6 +55,9 @@
import android.graphics.PixelFormat;
import android.graphics.Region;
import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.graphics.drawable.RotateDrawable;
import android.media.AudioManager;
import android.media.AudioSystem;
import android.os.Debug;
@@ -81,6 +88,8 @@
import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
@@ -88,6 +97,7 @@
import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
+import com.android.systemui.Interpolators;
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
@@ -99,6 +109,8 @@
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.util.AlphaTintDrawableWrapper;
+import com.android.systemui.util.RoundedCornerProgressDrawable;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -124,8 +136,14 @@
static final int DIALOG_ODI_CAPTIONS_TOOLTIP_TIMEOUT_MILLIS = 5000;
static final int DIALOG_HOVERING_TIMEOUT_MILLIS = 16000;
+ private static final int DRAWER_ANIMATION_DURATION_SHORT = 175;
+ private static final int DRAWER_ANIMATION_DURATION = 250;
+
private final int mDialogShowAnimationDurationMs;
private final int mDialogHideAnimationDurationMs;
+ private final int mRingerDrawerItemSize;
+ private final boolean mShowVibrate;
+ private final int mRingerCount;
private final boolean mShowLowMediaVolumeIcon;
private final boolean mChangeVolumeRowTintWhenInactive;
@@ -140,6 +158,30 @@
private ViewGroup mDialogView;
private ViewGroup mDialogRowsView;
private ViewGroup mRinger;
+
+ private ViewGroup mSelectedRingerContainer;
+ private ImageView mSelectedRingerIcon;
+
+ private ViewGroup mRingerDrawerContainer;
+ private ViewGroup mRingerDrawerMute;
+ private ViewGroup mRingerDrawerVibrate;
+ private ViewGroup mRingerDrawerNormal;
+ private ImageView mRingerDrawerMuteIcon;
+ private ImageView mRingerDrawerVibrateIcon;
+ private ImageView mRingerDrawerNormalIcon;
+
+ /**
+ * View that draws the 'selected' background behind one of the three ringer choices in the
+ * drawer.
+ */
+ private ViewGroup mRingerDrawerNewSelectionBg;
+
+ private final ValueAnimator mRingerDrawerIconColorAnimator = ValueAnimator.ofFloat(0f, 1f);
+ private ImageView mRingerDrawerIconAnimatingSelected;
+ private ImageView mRingerDrawerIconAnimatingDeselected;
+
+ private boolean mIsRingerDrawerOpen = false;
+
private ImageButton mRingerIcon;
private ViewGroup mODICaptionsView;
private CaptionsToggleImageButton mODICaptionsIcon;
@@ -191,6 +233,12 @@
mContext.getResources().getInteger(R.integer.config_dialogShowAnimationDurationMs);
mDialogHideAnimationDurationMs =
mContext.getResources().getInteger(R.integer.config_dialogHideAnimationDurationMs);
+ mRingerDrawerItemSize = mContext.getResources().getDimensionPixelSize(
+ R.dimen.volume_ringer_drawer_item_size);
+ mShowVibrate = mController.hasVibrator();
+
+ // Normal, mute, and possibly vibrate.
+ mRingerCount = mShowVibrate ? 3 : 2;
}
@Override
@@ -314,6 +362,20 @@
mZenIcon = mRinger.findViewById(R.id.dnd_icon);
}
+ mSelectedRingerIcon = mDialog.findViewById(R.id.volume_new_ringer_active_icon);
+ mSelectedRingerContainer = mDialog.findViewById(
+ R.id.volume_new_ringer_active_icon_container);
+
+ mRingerDrawerMute = mDialog.findViewById(R.id.volume_drawer_mute);
+ mRingerDrawerNormal = mDialog.findViewById(R.id.volume_drawer_normal);
+ mRingerDrawerVibrate = mDialog.findViewById(R.id.volume_drawer_vibrate);
+ mRingerDrawerMuteIcon = mDialog.findViewById(R.id.volume_drawer_mute_icon);
+ mRingerDrawerVibrateIcon = mDialog.findViewById(R.id.volume_drawer_vibrate_icon);
+ mRingerDrawerNormalIcon = mDialog.findViewById(R.id.volume_drawer_normal_icon);
+ mRingerDrawerNewSelectionBg = mDialog.findViewById(R.id.volume_drawer_selection_background);
+
+ setupRingerDrawer();
+
mODICaptionsView = mDialog.findViewById(R.id.odi_captions);
if (mODICaptionsView != null) {
mODICaptionsIcon = mODICaptionsView.findViewById(R.id.odi_captions_icon);
@@ -475,40 +537,275 @@
row.anim = null;
+ final LayerDrawable seekbarDrawable =
+ (LayerDrawable) mContext.getDrawable(R.drawable.volume_row_seekbar);
+
+ final LayerDrawable seekbarBgDrawable =
+ (LayerDrawable) seekbarDrawable.findDrawableByLayerId(android.R.id.background);
+
+ row.sliderBgSolid = seekbarBgDrawable.findDrawableByLayerId(
+ R.id.volume_seekbar_background_solid);
+
+ row.sliderBgIcon = (AlphaTintDrawableWrapper)
+ ((RotateDrawable) seekbarBgDrawable.findDrawableByLayerId(
+ R.id.volume_seekbar_background_icon)).getDrawable();
+
+ final LayerDrawable seekbarProgressDrawable = (LayerDrawable)
+ ((RoundedCornerProgressDrawable) seekbarDrawable.findDrawableByLayerId(
+ android.R.id.progress)).getDrawable();
+
+ row.sliderProgressSolid = seekbarProgressDrawable.findDrawableByLayerId(
+ R.id.volume_seekbar_progress_solid);
+
+ row.sliderProgressIcon = (AlphaTintDrawableWrapper)
+ ((RotateDrawable) seekbarProgressDrawable.findDrawableByLayerId(
+ R.id.volume_seekbar_progress_icon)).getDrawable();
+
+ row.slider.setProgressDrawable(seekbarDrawable);
+ row.slider.setThumb(null);
+
row.icon = row.view.findViewById(R.id.volume_row_icon);
- row.icon.setImageResource(iconRes);
- if (row.stream != AudioSystem.STREAM_ACCESSIBILITY) {
- row.icon.setOnClickListener(v -> {
- Events.writeEvent(Events.EVENT_ICON_CLICK, row.stream, row.iconState);
- mController.setActiveStream(row.stream);
- if (row.stream == AudioManager.STREAM_RING) {
- final boolean hasVibrator = mController.hasVibrator();
- if (mState.ringerModeInternal == AudioManager.RINGER_MODE_NORMAL) {
- if (hasVibrator) {
- mController.setRingerMode(AudioManager.RINGER_MODE_VIBRATE, false);
+
+ row.setIcon(iconRes, mContext.getTheme());
+
+ if (row.icon != null) {
+ if (row.stream != AudioSystem.STREAM_ACCESSIBILITY) {
+ row.icon.setOnClickListener(v -> {
+ Events.writeEvent(Events.EVENT_ICON_CLICK, row.stream, row.iconState);
+ mController.setActiveStream(row.stream);
+ if (row.stream == AudioManager.STREAM_RING) {
+ final boolean hasVibrator = mController.hasVibrator();
+ if (mState.ringerModeInternal == AudioManager.RINGER_MODE_NORMAL) {
+ if (hasVibrator) {
+ mController.setRingerMode(AudioManager.RINGER_MODE_VIBRATE, false);
+ } else {
+ final boolean wasZero = row.ss.level == 0;
+ mController.setStreamVolume(stream,
+ wasZero ? row.lastAudibleLevel : 0);
+ }
} else {
- final boolean wasZero = row.ss.level == 0;
- mController.setStreamVolume(stream,
- wasZero ? row.lastAudibleLevel : 0);
+ mController.setRingerMode(
+ AudioManager.RINGER_MODE_NORMAL, false);
+ if (row.ss.level == 0) {
+ mController.setStreamVolume(stream, 1);
+ }
}
} else {
- mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false);
- if (row.ss.level == 0) {
- mController.setStreamVolume(stream, 1);
- }
+ final boolean vmute = row.ss.level == row.ss.levelMin;
+ mController.setStreamVolume(stream,
+ vmute ? row.lastAudibleLevel : row.ss.levelMin);
}
- } else {
- final boolean vmute = row.ss.level == row.ss.levelMin;
- mController.setStreamVolume(stream,
- vmute ? row.lastAudibleLevel : row.ss.levelMin);
- }
- row.userAttempt = 0; // reset the grace period, slider updates immediately
- });
- } else {
- row.icon.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+ row.userAttempt = 0; // reset the grace period, slider updates immediately
+ });
+ } else {
+ row.icon.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+ }
}
}
+ private void setRingerMode(int newRingerMode) {
+ Events.writeEvent(Events.EVENT_RINGER_TOGGLE, newRingerMode);
+ incrementManualToggleCount();
+ updateRingerH();
+ provideTouchFeedbackH(newRingerMode);
+ mController.setRingerMode(newRingerMode, false);
+ maybeShowToastH(newRingerMode);
+ }
+
+ private void setupRingerDrawer() {
+ mRingerDrawerContainer = mDialog.findViewById(R.id.volume_drawer_container);
+
+ if (mRingerDrawerContainer == null) {
+ return;
+ }
+
+ if (!mShowVibrate) {
+ mRingerDrawerVibrate.setVisibility(GONE);
+ }
+
+ // In portrait, add padding to the bottom to account for the height of the open ringer
+ // drawer.
+ if (!isLandscape()) {
+ mDialogView.setPadding(
+ mDialogView.getPaddingLeft(),
+ mDialogView.getPaddingTop(),
+ mDialogView.getPaddingRight(),
+ mDialogView.getPaddingBottom() + (mRingerCount - 1) * mRingerDrawerItemSize);
+ } else {
+ mDialogView.setPadding(
+ mDialogView.getPaddingLeft() + (mRingerCount - 1) * mRingerDrawerItemSize,
+ mDialogView.getPaddingTop(),
+ mDialogView.getPaddingRight(),
+ mDialogView.getPaddingBottom());
+ }
+
+ ((LinearLayout) mRingerDrawerContainer.findViewById(R.id.volume_drawer_options))
+ .setOrientation(isLandscape() ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL);
+
+ mSelectedRingerContainer.setOnClickListener(view -> {
+ if (mIsRingerDrawerOpen) {
+ hideRingerDrawer();
+ } else {
+ showRingerDrawer();
+ }
+ });
+
+ mRingerDrawerVibrate.setOnClickListener(
+ new RingerDrawerItemClickListener(RINGER_MODE_VIBRATE));
+ mRingerDrawerMute.setOnClickListener(
+ new RingerDrawerItemClickListener(RINGER_MODE_SILENT));
+ mRingerDrawerNormal.setOnClickListener(
+ new RingerDrawerItemClickListener(RINGER_MODE_NORMAL));
+
+ final int unselectedColor = Utils.getColorAccentDefaultColor(mContext);
+ final int selectedColor = Utils.getColorAttrDefaultColor(
+ mContext, android.R.attr.colorBackgroundFloating);
+
+ // Add an update listener that animates the deselected icon to the unselected color, and the
+ // selected icon to the selected color.
+ mRingerDrawerIconColorAnimator.addUpdateListener(
+ anim -> {
+ final float currentValue = (float) anim.getAnimatedValue();
+ final int curUnselectedColor = (int) ArgbEvaluator.getInstance().evaluate(
+ currentValue, selectedColor, unselectedColor);
+ final int curSelectedColor = (int) ArgbEvaluator.getInstance().evaluate(
+ currentValue, unselectedColor, selectedColor);
+
+ mRingerDrawerIconAnimatingDeselected.setColorFilter(curUnselectedColor);
+ mRingerDrawerIconAnimatingSelected.setColorFilter(curSelectedColor);
+ });
+ mRingerDrawerIconColorAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mRingerDrawerIconAnimatingDeselected.clearColorFilter();
+ mRingerDrawerIconAnimatingSelected.clearColorFilter();
+ }
+ });
+ mRingerDrawerIconColorAnimator.setDuration(DRAWER_ANIMATION_DURATION_SHORT);
+ }
+
+ private ImageView getDrawerIconViewForMode(int mode) {
+ if (mode == RINGER_MODE_VIBRATE) {
+ return mRingerDrawerVibrateIcon;
+ } else if (mode == RINGER_MODE_SILENT) {
+ return mRingerDrawerMuteIcon;
+ } else {
+ return mRingerDrawerNormalIcon;
+ }
+ }
+
+ /**
+ * Translation to apply form the origin (either top or left) to overlap the selection background
+ * with the given mode in the drawer.
+ */
+ private float getTranslationInDrawerForRingerMode(int mode) {
+ return mode == RINGER_MODE_VIBRATE
+ ? -mRingerDrawerItemSize * 2
+ : mode == RINGER_MODE_SILENT
+ ? -mRingerDrawerItemSize
+ : 0;
+ }
+
+ /** Animates in the ringer drawer. */
+ private void showRingerDrawer() {
+ // Show all ringer icons except the currently selected one, since we're going to animate the
+ // ringer button to that position.
+ mRingerDrawerVibrateIcon.setVisibility(
+ mState.ringerModeInternal == RINGER_MODE_VIBRATE ? INVISIBLE : VISIBLE);
+ mRingerDrawerMuteIcon.setVisibility(
+ mState.ringerModeInternal == RINGER_MODE_SILENT ? INVISIBLE : VISIBLE);
+ mRingerDrawerNormalIcon.setVisibility(
+ mState.ringerModeInternal == RINGER_MODE_NORMAL ? INVISIBLE : VISIBLE);
+
+ // Hide the selection background - we use this to show a selection when one is
+ // tapped, so it should be invisible until that happens. However, position it below
+ // the currently selected ringer so that it's ready to animate.
+ mRingerDrawerNewSelectionBg.setAlpha(0f);
+
+ if (!isLandscape()) {
+ mRingerDrawerNewSelectionBg.setTranslationY(
+ getTranslationInDrawerForRingerMode(mState.ringerModeInternal));
+ } else {
+ mRingerDrawerNewSelectionBg.setTranslationX(
+ getTranslationInDrawerForRingerMode(mState.ringerModeInternal));
+ }
+
+ // Move the drawer so that the top/rightmost ringer choice overlaps with the selected ringer
+ // icon.
+ if (!isLandscape()) {
+ mRingerDrawerContainer.setTranslationY(mRingerDrawerItemSize * (mRingerCount - 1));
+ } else {
+ mRingerDrawerContainer.setTranslationX(mRingerDrawerItemSize * (mRingerCount - 1));
+ }
+ mRingerDrawerContainer.setAlpha(0f);
+ mRingerDrawerContainer.setVisibility(VISIBLE);
+
+ // Animate the drawer up and visible.
+ mRingerDrawerContainer.animate()
+ .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+ // Vibrate is way farther up, so give the selected ringer icon a head start if
+ // vibrate is selected.
+ .setDuration(mState.ringerModeInternal == RINGER_MODE_VIBRATE
+ ? DRAWER_ANIMATION_DURATION_SHORT
+ : DRAWER_ANIMATION_DURATION)
+ .setStartDelay(mState.ringerModeInternal == RINGER_MODE_VIBRATE
+ ? DRAWER_ANIMATION_DURATION - DRAWER_ANIMATION_DURATION_SHORT
+ : 0)
+ .alpha(1f)
+ .translationX(0f)
+ .translationY(0f)
+ .start();
+
+ // Animate the selected ringer view up to that ringer's position in the drawer.
+ mSelectedRingerContainer.animate()
+ .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+ .setDuration(DRAWER_ANIMATION_DURATION)
+ .withEndAction(() ->
+ getDrawerIconViewForMode(mState.ringerModeInternal).setVisibility(VISIBLE));
+
+ if (!isLandscape()) {
+ mSelectedRingerContainer.animate()
+ .translationY(getTranslationInDrawerForRingerMode(mState.ringerModeInternal))
+ .start();
+ } else {
+ mSelectedRingerContainer.animate()
+ .translationX(getTranslationInDrawerForRingerMode(mState.ringerModeInternal))
+ .start();
+ }
+
+ mIsRingerDrawerOpen = true;
+ }
+
+ /** Animates away the ringer drawer. */
+ private void hideRingerDrawer() {
+ // Hide the drawer icon for the selected ringer - it's visible in the ringer button and we
+ // don't want to be able to see it while it animates away.
+ getDrawerIconViewForMode(mState.ringerModeInternal).setVisibility(INVISIBLE);
+
+ mRingerDrawerContainer.animate()
+ .alpha(0f)
+ .setDuration(DRAWER_ANIMATION_DURATION)
+ .setStartDelay(0)
+ .withEndAction(() -> mRingerDrawerContainer.setVisibility(INVISIBLE));
+
+ if (!isLandscape()) {
+ mRingerDrawerContainer.animate()
+ .translationY(mRingerDrawerItemSize * 2)
+ .start();
+ } else {
+ mRingerDrawerContainer.animate()
+ .translationX(mRingerDrawerItemSize * 2)
+ .start();
+ }
+
+ mSelectedRingerContainer.animate()
+ .translationX(0f)
+ .translationY(0f)
+ .start();
+
+ mIsRingerDrawerOpen = false;
+ }
+
public void initSettingsH() {
if (mSettingsView != null) {
mSettingsView.setVisibility(
@@ -555,12 +852,8 @@
mController.setStreamVolume(AudioManager.STREAM_RING, 1);
}
}
- Events.writeEvent(Events.EVENT_RINGER_TOGGLE, newRingerMode);
- incrementManualToggleCount();
- updateRingerH();
- provideTouchFeedbackH(newRingerMode);
- mController.setRingerMode(newRingerMode, false);
- maybeShowToastH(newRingerMode);
+
+ setRingerMode(newRingerMode);
});
}
updateRingerH();
@@ -809,6 +1102,8 @@
mDialog.dismiss();
tryToRemoveCaptionsTooltip();
mIsAnimatingDismiss = false;
+
+ hideRingerDrawer();
}, 50));
if (!isLandscape()) animator.translationX(mDialogView.getWidth() / 2.0f);
animator.start();
@@ -889,12 +1184,14 @@
switch (mState.ringerModeInternal) {
case AudioManager.RINGER_MODE_VIBRATE:
mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_vibrate);
+ mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer_vibrate);
addAccessibilityDescription(mRingerIcon, RINGER_MODE_VIBRATE,
mContext.getString(R.string.volume_ringer_hint_mute));
mRingerIcon.setTag(Events.ICON_STATE_VIBRATE);
break;
case AudioManager.RINGER_MODE_SILENT:
mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
+ mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
mRingerIcon.setTag(Events.ICON_STATE_MUTE);
addAccessibilityDescription(mRingerIcon, RINGER_MODE_SILENT,
mContext.getString(R.string.volume_ringer_hint_unmute));
@@ -904,11 +1201,13 @@
boolean muted = (mAutomute && ss.level == 0) || ss.muted;
if (!isZenMuted && muted) {
mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
+ mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL,
mContext.getString(R.string.volume_ringer_hint_unmute));
mRingerIcon.setTag(Events.ICON_STATE_MUTE);
} else {
mRingerIcon.setImageResource(R.drawable.ic_volume_ringer);
+ mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer);
if (mController.hasVibrator()) {
addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL,
mContext.getString(R.string.volume_ringer_hint_vibrate));
@@ -1075,8 +1374,6 @@
// update icon
final boolean iconEnabled = (mAutomute || ss.muteSupported) && !zenMuted;
- row.icon.setEnabled(iconEnabled);
- row.icon.setAlpha(iconEnabled ? 1 : 0.5f);
final int iconRes;
if (isRingVibrate) {
iconRes = R.drawable.ic_volume_ringer_vibrate;
@@ -1092,7 +1389,7 @@
? R.drawable.ic_volume_media_low : row.iconRes;
}
- row.icon.setImageResource(iconRes);
+ row.setIcon(iconRes, mContext.getTheme());
row.iconState =
iconRes == R.drawable.ic_volume_ringer_vibrate ? Events.ICON_STATE_VIBRATE
: (iconRes == R.drawable.ic_volume_media_bt_mute || iconRes == row.iconMuteRes)
@@ -1101,18 +1398,35 @@
|| iconRes == R.drawable.ic_volume_media_low)
? Events.ICON_STATE_UNMUTE
: Events.ICON_STATE_UNKNOWN;
- if (iconEnabled) {
- if (isRingStream) {
- if (isRingVibrate) {
- row.icon.setContentDescription(mContext.getString(
- R.string.volume_stream_content_description_unmute,
- getStreamLabelH(ss)));
- } else {
- if (mController.hasVibrator()) {
+
+ if (row.icon != null) {
+ if (iconEnabled) {
+ if (isRingStream) {
+ if (isRingVibrate) {
row.icon.setContentDescription(mContext.getString(
- mShowA11yStream
- ? R.string.volume_stream_content_description_vibrate_a11y
- : R.string.volume_stream_content_description_vibrate,
+ R.string.volume_stream_content_description_unmute,
+ getStreamLabelH(ss)));
+ } else {
+ if (mController.hasVibrator()) {
+ row.icon.setContentDescription(mContext.getString(
+ mShowA11yStream
+ ? R.string.volume_stream_content_description_vibrate_a11y
+ : R.string.volume_stream_content_description_vibrate,
+ getStreamLabelH(ss)));
+ } else {
+ row.icon.setContentDescription(mContext.getString(
+ mShowA11yStream
+ ? R.string.volume_stream_content_description_mute_a11y
+ : R.string.volume_stream_content_description_mute,
+ getStreamLabelH(ss)));
+ }
+ }
+ } else if (isA11yStream) {
+ row.icon.setContentDescription(getStreamLabelH(ss));
+ } else {
+ if (ss.muted || mAutomute && ss.level == 0) {
+ row.icon.setContentDescription(mContext.getString(
+ R.string.volume_stream_content_description_unmute,
getStreamLabelH(ss)));
} else {
row.icon.setContentDescription(mContext.getString(
@@ -1122,23 +1436,9 @@
getStreamLabelH(ss)));
}
}
- } else if (isA11yStream) {
- row.icon.setContentDescription(getStreamLabelH(ss));
} else {
- if (ss.muted || mAutomute && ss.level == 0) {
- row.icon.setContentDescription(mContext.getString(
- R.string.volume_stream_content_description_unmute,
- getStreamLabelH(ss)));
- } else {
- row.icon.setContentDescription(mContext.getString(
- mShowA11yStream
- ? R.string.volume_stream_content_description_mute_a11y
- : R.string.volume_stream_content_description_mute,
- getStreamLabelH(ss)));
- }
+ row.icon.setContentDescription(getStreamLabelH(ss));
}
- } else {
- row.icon.setContentDescription(getStreamLabelH(ss));
}
// ensure tracking is disabled if zenMuted
@@ -1167,22 +1467,29 @@
if (!useActiveColoring && !mChangeVolumeRowTintWhenInactive) {
return;
}
- final ColorStateList tint = useActiveColoring
+ final ColorStateList colorTint = useActiveColoring
? Utils.getColorAccent(mContext)
: Utils.getColorAttr(mContext, android.R.attr.colorForeground);
final int alpha = useActiveColoring
- ? Color.alpha(tint.getDefaultColor())
+ ? Color.alpha(colorTint.getDefaultColor())
: getAlphaAttr(android.R.attr.secondaryContentAlpha);
- if (tint == row.cachedTint) return;
- row.slider.setProgressTintList(tint);
- row.slider.setThumbTintList(tint);
- row.slider.setProgressBackgroundTintList(tint);
- row.slider.setAlpha(((float) alpha) / 255);
- row.icon.setImageTintList(tint);
- row.icon.setImageAlpha(alpha);
- row.cachedTint = tint;
+
+ final ColorStateList bgTint = Utils.getColorAttr(
+ mContext, android.R.attr.colorBackgroundFloating);
+
+ row.sliderProgressSolid.setTintList(colorTint);
+ row.sliderBgIcon.setTintList(colorTint);
+
+ row.sliderBgSolid.setTintList(bgTint);
+ row.sliderProgressIcon.setTintList(bgTint);
+
+ if (row.icon != null) {
+ row.icon.setImageTintList(colorTint);
+ row.icon.setImageAlpha(alpha);
+ }
+
if (row.number != null) {
- row.number.setTextColor(tint);
+ row.number.setTextColor(colorTint);
row.number.setAlpha(alpha);
}
}
@@ -1538,6 +1845,10 @@
private View view;
private TextView header;
private ImageButton icon;
+ private Drawable sliderBgSolid;
+ private AlphaTintDrawableWrapper sliderBgIcon;
+ private Drawable sliderProgressSolid;
+ private AlphaTintDrawableWrapper sliderProgressIcon;
private SeekBar slider;
private TextView number;
private int stream;
@@ -1555,5 +1866,69 @@
private int animTargetProgress;
private int lastAudibleLevel = 1;
private FrameLayout dndIcon;
+
+ void setIcon(int iconRes, Resources.Theme theme) {
+ if (icon != null) {
+ icon.setImageResource(iconRes);
+ }
+
+ sliderProgressIcon.setDrawable(view.getResources().getDrawable(iconRes, theme));
+ sliderBgIcon.setDrawable(view.getResources().getDrawable(iconRes, theme));
+ }
+ }
+
+ /**
+ * Click listener added to each ringer option in the drawer. This will initiate the animation to
+ * select and then close the ringer drawer, and actually change the ringer mode.
+ */
+ private class RingerDrawerItemClickListener implements View.OnClickListener {
+ private final int mClickedRingerMode;
+
+ RingerDrawerItemClickListener(int clickedRingerMode) {
+ mClickedRingerMode = clickedRingerMode;
+ }
+
+ @Override
+ public void onClick(View view) {
+ setRingerMode(mClickedRingerMode);
+
+ mRingerDrawerIconAnimatingSelected = getDrawerIconViewForMode(mClickedRingerMode);
+ mRingerDrawerIconAnimatingDeselected = getDrawerIconViewForMode(
+ mState.ringerModeInternal);
+
+ // Begin switching the selected icon and deselected icon colors since the background is
+ // going to animate behind the new selection.
+ mRingerDrawerIconColorAnimator.start();
+
+ mSelectedRingerContainer.setVisibility(View.INVISIBLE);
+ mRingerDrawerNewSelectionBg.setAlpha(1f);
+ mRingerDrawerNewSelectionBg.animate()
+ .setInterpolator(Interpolators.ACCELERATE_DECELERATE)
+ .setDuration(DRAWER_ANIMATION_DURATION_SHORT)
+ .withEndAction(() -> {
+ mRingerDrawerNewSelectionBg.setAlpha(0f);
+
+ if (!isLandscape()) {
+ mSelectedRingerContainer.setTranslationY(
+ getTranslationInDrawerForRingerMode(mClickedRingerMode));
+ } else {
+ mSelectedRingerContainer.setTranslationX(
+ getTranslationInDrawerForRingerMode(mClickedRingerMode));
+ }
+
+ mSelectedRingerContainer.setVisibility(VISIBLE);
+ hideRingerDrawer();
+ });
+
+ if (!isLandscape()) {
+ mRingerDrawerNewSelectionBg.animate()
+ .translationY(getTranslationInDrawerForRingerMode(mClickedRingerMode))
+ .start();
+ } else {
+ mRingerDrawerNewSelectionBg.animate()
+ .translationX(getTranslationInDrawerForRingerMode(mClickedRingerMode))
+ .start();
+ }
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 3e873d1..700f101 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -150,7 +150,10 @@
}
@Test
- public void dozeTimeTick() {
+ public void dozeTimeTick() throws RemoteException {
+ mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
+ IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD);
+ mFgExecutor.runAllReady();
mUdfpsController.dozeTimeTick();
verify(mUdfpsView).dozeTimeTick();
}
@@ -240,7 +243,8 @@
@Test
public void registersViewForCallbacks() throws RemoteException {
- verify(mStatusBarStateController).addCallback(mUdfpsView);
- verify(mStatusBar).addExpansionChangedListener(mUdfpsView);
+ verify(mStatusBarStateController).addCallback(mUdfpsController.mStatusBarStateListener);
+ verify(mStatusBar).addExpansionChangedListener(
+ mUdfpsController.mStatusBarExpansionListener);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/NearestTouchFrameTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/NearestTouchFrameTest.java
index 5c179d4..c8f223b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/NearestTouchFrameTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/NearestTouchFrameTest.java
@@ -69,7 +69,7 @@
mNearestTouchFrame.addView(left);
mNearestTouchFrame.addView(right);
- mNearestTouchFrame.onMeasure(0, 0);
+ mNearestTouchFrame.layout(0, 0, 30, 30);
MotionEvent ev = MotionEvent.obtain(0, 0, 0,
12 /* x */, 5 /* y */, 0);
@@ -86,7 +86,7 @@
mNearestTouchFrame.addView(left);
mNearestTouchFrame.addView(right);
- mNearestTouchFrame.onMeasure(0, 0);
+ mNearestTouchFrame.layout(0, 0, 30, 30);
MotionEvent ev = MotionEvent.obtain(0, 0, 0,
12 /* x */, 5 /* y */, 0);
@@ -105,7 +105,7 @@
mNearestTouchFrame.addView(left);
mNearestTouchFrame.addView(right);
- mNearestTouchFrame.onMeasure(0, 0);
+ mNearestTouchFrame.layout(0, 0, 30, 30);
// Would go to left view if attached, but goes to right instead as left should be detached.
MotionEvent ev = MotionEvent.obtain(0, 0, 0,
@@ -122,7 +122,7 @@
mNearestTouchFrame.addView(left);
mNearestTouchFrame.addView(right);
- mNearestTouchFrame.onMeasure(0, 0);
+ mNearestTouchFrame.layout(0, 0, 30, 30);
MotionEvent ev = MotionEvent.obtain(0, 0, 0,
12 /* x */, 5 /* y */, 0);
@@ -138,7 +138,7 @@
mNearestTouchFrame.addView(left);
mNearestTouchFrame.addView(right);
- mNearestTouchFrame.onMeasure(0, 0);
+ mNearestTouchFrame.layout(0, 0, 30, 30);
MotionEvent ev = MotionEvent.obtain(0, 0, 0,
18 /* x */, 5 /* y */, 0);
@@ -154,7 +154,7 @@
mNearestTouchFrame.setIsVertical(true);
mNearestTouchFrame.addView(top);
mNearestTouchFrame.addView(bottom);
- mNearestTouchFrame.onMeasure(0, 0);
+ mNearestTouchFrame.layout(0, 0, 30, 30);
MotionEvent ev = MotionEvent.obtain(0, 0, 0,
5 /* x */, 12 /* y */, 0);
@@ -170,7 +170,7 @@
mNearestTouchFrame.setIsVertical(true);
mNearestTouchFrame.addView(top);
mNearestTouchFrame.addView(bottom);
- mNearestTouchFrame.onMeasure(0, 0);
+ mNearestTouchFrame.layout(0, 0, 30, 30);
MotionEvent ev = MotionEvent.obtain(0, 0, 0,
5 /* x */, 18 /* y */, 0);
@@ -184,7 +184,7 @@
View view = mockViewAt(0, 20, 10, 10);
when(view.isAttachedToWindow()).thenReturn(false);
mNearestTouchFrame.addView(view);
- mNearestTouchFrame.onMeasure(0, 0);
+ mNearestTouchFrame.layout(0, 0, 30, 30);
MotionEvent ev = MotionEvent.obtain(0, 0, 0, 5 /* x */, 18 /* y */, 0);
mNearestTouchFrame.onTouchEvent(ev);
@@ -201,7 +201,7 @@
mNearestTouchFrame.addView(view1);
mNearestTouchFrame.addView(view2);
mNearestTouchFrame.addView(view3);
- mNearestTouchFrame.onMeasure(0, 0);
+ mNearestTouchFrame.layout(0, 0, 30, 30);
MotionEvent ev = MotionEvent.obtain(0, 0, 0, 5 /* x */, 18 /* y */, 0);
mNearestTouchFrame.onTouchEvent(ev);
@@ -213,11 +213,9 @@
public void testCachedRegionsSplit_horizontal() {
View left = mockViewAt(0, 0, 5, 20);
View right = mockViewAt(15, 0, 5, 20);
- mNearestTouchFrame.layout(0, 0, 20, 20);
-
mNearestTouchFrame.addView(left);
mNearestTouchFrame.addView(right);
- mNearestTouchFrame.onMeasure(0, 0);
+ mNearestTouchFrame.layout(0, 0, 20, 20);
Map<View, Rect> childRegions = mNearestTouchFrame.getFullTouchableChildRegions();
assertEquals(2, childRegions.size());
@@ -231,12 +229,10 @@
public void testCachedRegionsSplit_vertical() {
View top = mockViewAt(0, 0, 20, 5);
View bottom = mockViewAt(0, 15, 20, 5);
- mNearestTouchFrame.layout(0, 0, 20, 20);
- mNearestTouchFrame.setIsVertical(true);
-
mNearestTouchFrame.addView(top);
mNearestTouchFrame.addView(bottom);
- mNearestTouchFrame.onMeasure(0, 0);
+ mNearestTouchFrame.setIsVertical(true);
+ mNearestTouchFrame.layout(0, 0, 20, 20);
Map<View, Rect> childRegions = mNearestTouchFrame.getFullTouchableChildRegions();
assertEquals(2, childRegions.size());
@@ -256,6 +252,8 @@
}).when(v).getLocationInWindow(any());
when(v.isClickable()).thenReturn(true);
when(v.isAttachedToWindow()).thenReturn(true);
+ when(v.getWidth()).thenReturn(width);
+ when(v.getHeight()).thenReturn(height);
// Stupid final methods.
v.setLeft(0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
index c8e9396..2a4b41c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
@@ -464,7 +464,7 @@
new PeopleSpaceTile
.Builder(SHORTCUT_ID_1, "userName", ICON, new Intent())
.setPackageName(PACKAGE_NAME)
- .setUid(0)
+ .setUserHandle(new UserHandle(0))
.build();
PeopleSpaceTile actual = PeopleSpaceUtils
.augmentTileFromNotification(mContext, tile, sbn);
@@ -482,7 +482,7 @@
new PeopleSpaceTile
.Builder(SHORTCUT_ID_3, "userName", ICON, new Intent())
.setPackageName(PACKAGE_NAME)
- .setUid(0)
+ .setUserHandle(new UserHandle(0))
.build();
PeopleSpaceTile actual = PeopleSpaceUtils
.augmentTileFromNotification(mContext, tile, sbn);
@@ -496,7 +496,7 @@
new PeopleSpaceTile
.Builder(SHORTCUT_ID_1, "userName", ICON, new Intent())
.setPackageName(PACKAGE_NAME)
- .setUid(0)
+ .setUserHandle(new UserHandle(0))
.build();
PeopleSpaceTile actual = PeopleSpaceUtils
.augmentTileFromVisibleNotifications(mContext, tile,
@@ -511,7 +511,7 @@
new PeopleSpaceTile
.Builder(SHORTCUT_ID_4, "userName", ICON, new Intent())
.setPackageName(PACKAGE_NAME)
- .setUid(0)
+ .setUserHandle(new UserHandle(0))
.build();
PeopleSpaceTile actual = PeopleSpaceUtils
.augmentTileFromVisibleNotifications(mContext, tile,
@@ -526,7 +526,7 @@
new PeopleSpaceTile
.Builder(SHORTCUT_ID_1, "userName", ICON, new Intent())
.setPackageName(PACKAGE_NAME)
- .setUid(0)
+ .setUserHandle(new UserHandle(0))
.build();
List<PeopleSpaceTile> actualList = PeopleSpaceUtils
.augmentTilesFromVisibleNotifications(
@@ -545,13 +545,13 @@
new PeopleSpaceTile
.Builder(SHORTCUT_ID_1, "userName", ICON, new Intent())
.setPackageName(PACKAGE_NAME)
- .setUid(1)
+ .setUserHandle(new UserHandle(0))
.build();
PeopleSpaceTile tile2 =
new PeopleSpaceTile
.Builder(SHORTCUT_ID_2, "userName2", ICON, new Intent())
.setPackageName(PACKAGE_NAME)
- .setUid(0)
+ .setUserHandle(new UserHandle(0))
.build();
List<PeopleSpaceTile> actualList = PeopleSpaceUtils
.augmentTilesFromVisibleNotifications(mContext, List.of(tile1, tile2),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
index 1c8324c..800d859 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
@@ -109,7 +109,7 @@
new PeopleSpaceTile
.Builder(SHORTCUT_ID, "username", ICON, new Intent())
.setPackageName(TEST_PACKAGE_A)
- .setUid(0)
+ .setUserHandle(new UserHandle(1))
.setNotificationKey(NOTIFICATION_KEY + "1")
.setNotificationContent(NOTIFICATION_CONTENT)
.setNotificationDataUri(URI)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
index 2dfd388..d40e6a4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
@@ -89,6 +89,8 @@
private View mEdit;
@Mock
private MultiUserSwitch mMultiUserSwitch;
+ @Mock
+ private View mPowerMenuLiteView;
private QSFooterViewController mController;
@@ -111,11 +113,12 @@
when(mView.findViewById(R.id.build)).thenReturn(mBuildText);
when(mView.findViewById(android.R.id.edit)).thenReturn(mEdit);
when(mView.findViewById(R.id.multi_user_switch)).thenReturn(mMultiUserSwitch);
+ when(mView.findViewById(R.id.pm_lite)).thenReturn(mPowerMenuLiteView);
mController = new QSFooterViewController(mView, mUserManager, mUserInfoController,
mActivityStarter, mDeviceProvisionedController, mUserTracker, mQSPanelController,
new QSDetailDisplayer(), mQuickQSPanelController, mFakeTunerService,
- mMetricsLogger);
+ mMetricsLogger, false);
mController.init();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
index 1ec1da4..5a1bd5f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
@@ -36,6 +36,7 @@
import com.android.keyguard.CarrierTextController;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators;
import com.android.systemui.utils.leaks.LeakCheckedTest;
import com.android.systemui.utils.os.FakeHandler;
@@ -215,10 +216,11 @@
@Test // throws no Exception
public void testSetMobileDataIndicators_invalidSim() {
- mSignalCallback.setMobileDataIndicators(
+ MobileDataIndicators indicators = new MobileDataIndicators(
mock(NetworkController.IconState.class),
mock(NetworkController.IconState.class),
0, 0, true, true, "", "", "", true, 0, true, true);
+ mSignalCallback.setMobileDataIndicators(indicators);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
index faf43a2..1c29a81 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
@@ -46,6 +46,7 @@
import com.android.systemui.statusbar.policy.HotspotController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.NetworkController.WifiIndicators;
import org.junit.Before;
import org.junit.Test;
@@ -134,9 +135,11 @@
public void testStateUnavailable_wifiDisabled() {
NetworkController.IconState qsIcon =
new NetworkController.IconState(false, 0, "");
- mSignalCallback.setWifiIndicators(false, mock(NetworkController.IconState.class),
+ WifiIndicators indicators = new WifiIndicators(
+ false, mock(NetworkController.IconState.class),
qsIcon, false,false, "",
false, "");
+ mSignalCallback.setWifiIndicators(indicators);
mTestableLooper.processAllMessages();
assertEquals(Tile.STATE_UNAVAILABLE, mCastTile.getState().state);
@@ -146,9 +149,11 @@
public void testStateUnavailable_wifiNotConnected() {
NetworkController.IconState qsIcon =
new NetworkController.IconState(false, 0, "");
- mSignalCallback.setWifiIndicators(true, mock(NetworkController.IconState.class),
+ WifiIndicators indicators = new WifiIndicators(
+ true, mock(NetworkController.IconState.class),
qsIcon, false,false, "",
false, "");
+ mSignalCallback.setWifiIndicators(indicators);
mTestableLooper.processAllMessages();
assertEquals(Tile.STATE_UNAVAILABLE, mCastTile.getState().state);
@@ -157,9 +162,11 @@
private void enableWifiAndProcessMessages() {
NetworkController.IconState qsIcon =
new NetworkController.IconState(true, 0, "");
- mSignalCallback.setWifiIndicators(true, mock(NetworkController.IconState.class),
+ WifiIndicators indicators = new WifiIndicators(
+ true, mock(NetworkController.IconState.class),
qsIcon, false,false, "",
false, "");
+ mSignalCallback.setWifiIndicators(indicators);
mTestableLooper.processAllMessages();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java
index a75c39c..9e62a62 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java
@@ -24,6 +24,7 @@
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
import android.graphics.RenderNode;
+import android.os.ICancellationSignal;
import android.os.RemoteException;
import android.view.IScrollCaptureCallbacks;
import android.view.IScrollCaptureConnection;
@@ -46,7 +47,7 @@
}
@Override
- public void startCapture(Surface surface) {
+ public ICancellationSignal startCapture(Surface surface) {
mSurface = surface;
mHwuiContext = new HwuiContext(false, surface);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
@@ -56,27 +57,28 @@
} catch (RemoteException e) {
e.rethrowAsRuntimeException();
}
+ return null;
}
@Override
- public void requestImage(Rect rect) {
+ public ICancellationSignal requestImage(Rect rect) {
Canvas canvas = mHwuiContext.lockCanvas(rect.width(), rect.height());
mPaint.setColor(mColors[mNextColor]);
canvas.drawRect(rect, mPaint);
mNextColor = (mNextColor++) % mColors.length;
- long frameNumber = mSurface.getNextFrameNumber();
mHwuiContext.unlockAndPost(canvas);
try {
- mCallbacks.onCaptureBufferSent(frameNumber, rect);
+ mCallbacks.onImageRequestCompleted(0, rect);
} catch (RemoteException e) {
e.rethrowAsRuntimeException();
}
+ return null;
}
@Override
- public void endCapture() {
+ public ICancellationSignal endCapture() {
try {
- mCallbacks.onConnectionClosed();
+ mCallbacks.onCaptureEnded();
} catch (RemoteException e) {
e.rethrowAsRuntimeException();
} finally {
@@ -84,6 +86,12 @@
mSurface = null;
mCallbacks = null;
}
+ return null;
+ }
+
+ @Override
+ public void close() throws RemoteException {
+
}
// From android.view.Surface, but issues render requests synchronously with waitForPresent(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java
index 580f800..802b462 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java
@@ -29,7 +29,6 @@
import static java.util.Objects.requireNonNull;
import android.content.Context;
-import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.os.RemoteException;
@@ -37,6 +36,7 @@
import android.view.Display;
import android.view.IScrollCaptureCallbacks;
import android.view.IWindowManager;
+import android.view.ScrollCaptureResponse;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -82,10 +82,11 @@
public void testBasicClientFlow() throws RemoteException {
doAnswer((Answer<Void>) invocation -> {
IScrollCaptureCallbacks cb = invocation.getArgument(3);
- cb.onConnected(
- new FakeScrollCaptureConnection(cb),
- /* scrollBounds */ new Rect(0, 0, 100, 100),
- /* positionInWindow */ new Point(0, 0));
+ cb.onScrollCaptureResponse(new ScrollCaptureResponse.Builder()
+ .setBoundsInWindow(new Rect(0, 0, 100, 100))
+ .setWindowBounds(new Rect(0, 0, 100, 100))
+ .setConnection(new FakeScrollCaptureConnection(cb))
+ .build());
return null;
}).when(mWm).requestScrollCapture(/* displayId */ anyInt(), /* token */ isNull(),
/* taskId */ anyInt(), any(IScrollCaptureCallbacks.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureTest.java
index 2b3ca7c..6564d58 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureTest.java
@@ -19,15 +19,14 @@
import static org.junit.Assert.fail;
import android.content.Intent;
-import android.graphics.Point;
import android.graphics.Rect;
import android.os.RemoteException;
import android.testing.AndroidTestingRunner;
import android.util.Log;
import android.view.Display;
import android.view.IScrollCaptureCallbacks;
-import android.view.IScrollCaptureConnection;
import android.view.IWindowManager;
+import android.view.ScrollCaptureResponse;
import android.view.WindowManagerGlobal;
import androidx.test.filters.SmallTest;
@@ -67,31 +66,27 @@
wms.requestScrollCapture(Display.DEFAULT_DISPLAY, null, -1,
new IScrollCaptureCallbacks.Stub() {
@Override
- public void onConnected(
- IScrollCaptureConnection connection, Rect scrollBounds,
- Point positionInWindow) {
- Log.d(TAG,
- "client connected: " + connection + "[scrollBounds= "
- + scrollBounds + ", "
- + "positionInWindow=" + positionInWindow + "]");
+ public void onScrollCaptureResponse(ScrollCaptureResponse response)
+ throws RemoteException {
+ Log.d(TAG, "onScrollCaptureResponse: " + response);
latch.countDown();
}
@Override
- public void onUnavailable() {
- }
-
- @Override
public void onCaptureStarted() {
}
@Override
- public void onCaptureBufferSent(long frameNumber, Rect capturedArea) {
+ public void onImageRequestCompleted(int i, Rect rect)
+ throws RemoteException {
+
}
@Override
- public void onConnectionClosed() {
+ public void onCaptureEnded() throws RemoteException {
+
}
+
});
} catch (RemoteException e) {
Log.e(TAG, "request failed", e);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index ce14bca..7ff056e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar;
+import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT;
+import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_DISCLOSURE;
@@ -40,6 +42,7 @@
import android.app.admin.DevicePolicyManager;
import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.UserInfo;
@@ -94,8 +97,12 @@
private static final String ORGANIZATION_NAME = "organization";
+ private static final ComponentName DEVICE_OWNER_COMPONENT = new ComponentName("com.android.foo",
+ "bar");
+
private String mDisclosureWithOrganization;
private String mDisclosureGeneric;
+ private String mFinancedDisclosureWithOrganization;
@Mock
private DevicePolicyManager mDevicePolicyManager;
@@ -156,6 +163,8 @@
mDisclosureWithOrganization = mContext.getString(R.string.do_disclosure_with_name,
ORGANIZATION_NAME);
mDisclosureGeneric = mContext.getString(R.string.do_disclosure_generic);
+ mFinancedDisclosureWithOrganization = mContext.getString(
+ R.string.do_financed_disclosure_with_name, ORGANIZATION_NAME);
when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
when(mKeyguardUpdateMonitor.isScreenOn()).thenReturn(true);
@@ -165,6 +174,11 @@
.thenReturn(mIndicationAreaBottom);
when(mIndicationArea.findViewById(R.id.keyguard_indication_text)).thenReturn(mTextView);
+ when(mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser())
+ .thenReturn(DEVICE_OWNER_COMPONENT);
+ when(mDevicePolicyManager.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
+ .thenReturn(DEVICE_OWNER_TYPE_DEFAULT);
+
mWakeLock = new WakeLockFake();
mWakeLockBuilder = new WakeLockFake.Builder(mContext);
mWakeLockBuilder.setWakeLock(mWakeLock);
@@ -372,6 +386,22 @@
}
@Test
+ public void disclosure_deviceOwner_financedDeviceWithOrganizationName() {
+ createController();
+
+ when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
+ when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(ORGANIZATION_NAME);
+ when(mDevicePolicyManager.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
+ .thenReturn(DEVICE_OWNER_TYPE_FINANCED);
+ sendUpdateDisclosureBroadcast();
+
+ verify(mRotateTextViewController).updateIndication(eq(INDICATION_TYPE_DISCLOSURE),
+ mKeyguardIndicationCaptor.capture(), eq(false));
+ assertThat(mKeyguardIndicationCaptor.getValue().getMessage())
+ .isEqualTo(mFinancedDisclosureWithOrganization);
+ }
+
+ @Test
public void transientIndication_holdsWakeLock_whenDozing() {
createController();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java
index 67c1a08..2418243 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java
@@ -30,7 +30,9 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.policy.NetworkController.EmergencyListener;
import com.android.systemui.statusbar.policy.NetworkController.IconState;
+import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import com.android.systemui.statusbar.policy.NetworkController.WifiIndicators;
import com.android.systemui.tests.R;
import org.junit.Before;
@@ -86,28 +88,24 @@
boolean out = true;
String description = "Test";
String secondaryLabel = "Secondary label";
- mHandler.setWifiIndicators(enabled, status, qs, in, out, description, true, secondaryLabel);
+ WifiIndicators indicators = new WifiIndicators(
+ enabled, status, qs, in, out, description, true, secondaryLabel);
+ mHandler.setWifiIndicators(indicators);
waitForCallbacks();
- ArgumentCaptor<Boolean> enableArg = ArgumentCaptor.forClass(Boolean.class);
- ArgumentCaptor<IconState> statusArg = ArgumentCaptor.forClass(IconState.class);
- ArgumentCaptor<IconState> qsArg = ArgumentCaptor.forClass(IconState.class);
- ArgumentCaptor<Boolean> inArg = ArgumentCaptor.forClass(Boolean.class);
- ArgumentCaptor<Boolean> outArg = ArgumentCaptor.forClass(Boolean.class);
- ArgumentCaptor<String> descArg = ArgumentCaptor.forClass(String.class);
- ArgumentCaptor<Boolean> isTransient = ArgumentCaptor.forClass(Boolean.class);
- ArgumentCaptor<String> secondary = ArgumentCaptor.forClass(String.class);
- Mockito.verify(mSignalCallback).setWifiIndicators(enableArg.capture(),
- statusArg.capture(), qsArg.capture(), inArg.capture(), outArg.capture(),
- descArg.capture(), isTransient.capture(), secondary.capture());
- assertEquals(enabled, (boolean) enableArg.getValue());
- assertEquals(status, statusArg.getValue());
- assertEquals(qs, qsArg.getValue());
- assertEquals(in, (boolean) inArg.getValue());
- assertEquals(out, (boolean) outArg.getValue());
- assertEquals(description, descArg.getValue());
- assertTrue(isTransient.getValue());
- assertEquals(secondaryLabel, secondary.getValue());
+ ArgumentCaptor<WifiIndicators> indicatorArg =
+ ArgumentCaptor.forClass(WifiIndicators.class);
+ Mockito.verify(mSignalCallback).setWifiIndicators(indicatorArg.capture());
+ WifiIndicators expected = indicatorArg.getValue();
+
+ assertEquals(enabled, expected.enabled);
+ assertEquals(status, expected.statusIcon);
+ assertEquals(qs, expected.qsIcon);
+ assertEquals(in, expected.activityIn);
+ assertEquals(out, expected.activityOut);
+ assertEquals(description, expected.description);
+ assertTrue(expected.isTransient);
+ assertEquals(secondaryLabel, expected.statusLabel);
}
@Test
@@ -124,37 +122,30 @@
boolean wide = true;
int subId = 5;
boolean roaming = true;
- mHandler.setMobileDataIndicators(status, qs, type, qsType, in, out, typeDescription,
+ MobileDataIndicators indicators = new MobileDataIndicators(
+ status, qs, type, qsType, in, out, typeDescription,
typeDescriptionHtml, description, wide, subId, roaming, true);
+ mHandler.setMobileDataIndicators(indicators);
waitForCallbacks();
- ArgumentCaptor<IconState> statusArg = ArgumentCaptor.forClass(IconState.class);
- ArgumentCaptor<IconState> qsArg = ArgumentCaptor.forClass(IconState.class);
- ArgumentCaptor<Integer> typeIconArg = ArgumentCaptor.forClass(Integer.class);
- ArgumentCaptor<Integer> qsTypeIconArg = ArgumentCaptor.forClass(Integer.class);
- ArgumentCaptor<Boolean> inArg = ArgumentCaptor.forClass(Boolean.class);
- ArgumentCaptor<Boolean> outArg = ArgumentCaptor.forClass(Boolean.class);
- ArgumentCaptor<CharSequence> typeContentArg = ArgumentCaptor.forClass(CharSequence.class);
- ArgumentCaptor<CharSequence> typeContentHtmlArg =
- ArgumentCaptor.forClass(CharSequence.class);
- ArgumentCaptor<CharSequence> descArg = ArgumentCaptor.forClass(CharSequence.class);
- ArgumentCaptor<Boolean> wideArg = ArgumentCaptor.forClass(Boolean.class);
- ArgumentCaptor<Integer> subIdArg = ArgumentCaptor.forClass(Integer.class);
- Mockito.verify(mSignalCallback).setMobileDataIndicators(statusArg.capture(),
- qsArg.capture(), typeIconArg.capture(), qsTypeIconArg.capture(), inArg.capture(),
- outArg.capture(), typeContentArg.capture(), typeContentHtmlArg.capture(),
- descArg.capture(), wideArg.capture(), subIdArg.capture(), eq(roaming), eq(true));
- assertEquals(status, statusArg.getValue());
- assertEquals(qs, qsArg.getValue());
- assertEquals(type, (int) typeIconArg.getValue());
- assertEquals(qsType, (int) qsTypeIconArg.getValue());
- assertEquals(in, (boolean) inArg.getValue());
- assertEquals(out, (boolean) outArg.getValue());
- assertEquals(typeDescription, typeContentArg.getValue());
- assertEquals(typeDescriptionHtml, typeContentHtmlArg.getValue());
- assertEquals(description, descArg.getValue());
- assertEquals(wide, (boolean) wideArg.getValue());
- assertEquals(subId, (int) subIdArg.getValue());
+ ArgumentCaptor<MobileDataIndicators> indicatorArg =
+ ArgumentCaptor.forClass(MobileDataIndicators.class);
+ Mockito.verify(mSignalCallback).setMobileDataIndicators(indicatorArg.capture());
+ MobileDataIndicators expected = indicatorArg.getValue();
+
+ assertEquals(status, expected.statusIcon);
+ assertEquals(qs, expected.qsIcon);
+ assertEquals(type, expected.statusType);
+ assertEquals(qsType, expected.qsType);
+ assertEquals(in, expected.activityIn);
+ assertEquals(out, expected.activityOut);
+ assertEquals(typeDescription, expected.typeContentDescription);
+ assertEquals(typeDescriptionHtml, expected.typeContentDescriptionHtml);
+ assertEquals(description, expected.description);
+ assertEquals(wide, expected.isWide);
+ assertEquals(subId, expected.subId);
+ assertTrue(expected.roaming);
+ assertTrue(expected.showTriangle);
}
@SuppressWarnings("unchecked")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index 89cc2b5..e52b926 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -21,9 +21,9 @@
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isA;
@@ -76,6 +76,7 @@
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
import com.android.systemui.statusbar.policy.NetworkController.IconState;
+import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
import org.junit.After;
@@ -173,8 +174,6 @@
mMockSubDefaults = mock(SubscriptionDefaults.class);
mNetCapabilities = new NetworkCapabilities();
when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(true);
- when(mMockCm.getDefaultNetworkCapabilitiesForUser(0)).thenReturn(
- new NetworkCapabilities[] { mNetCapabilities });
when(mMockTm.createForSubscriptionId(anyInt())).thenReturn(mMockTm);
doAnswer(invocation -> {
int rssi = invocation.getArgument(0);
@@ -257,8 +256,11 @@
ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
verify(mMockCm, atLeastOnce())
.registerDefaultNetworkCallback(callbackArg.capture(), isA(Handler.class));
- mDefaultCallbackInWifiTracker = callbackArg.getAllValues().get(0);
- mDefaultCallbackInNetworkController = callbackArg.getAllValues().get(1);
+ int captureSize = callbackArg.getAllValues().size();
+ assertTrue(captureSize > 1);
+ assertEquals(captureSize % 2, 0);
+ mDefaultCallbackInWifiTracker = callbackArg.getAllValues().get(captureSize - 2);
+ mDefaultCallbackInNetworkController = callbackArg.getAllValues().get(captureSize - 1);
assertNotNull(mDefaultCallbackInWifiTracker);
assertNotNull(mDefaultCallbackInNetworkController);
verify(mMockCm, atLeastOnce()).registerNetworkCallback(
@@ -307,14 +309,18 @@
setLevel(DEFAULT_LEVEL);
updateDataConnectionState(TelephonyManager.DATA_CONNECTED,
TelephonyManager.NETWORK_TYPE_UMTS);
+ setConnectivityViaCallbackInNetworkController(
+ NetworkCapabilities.TRANSPORT_CELLULAR, true, true, null);
setConnectivityViaBroadcast(
- NetworkCapabilities.TRANSPORT_CELLULAR, true, true);
+ NetworkCapabilities.TRANSPORT_CELLULAR, true, true);
}
public void setConnectivityViaBroadcastForVcn(
int networkType, boolean validated, boolean isConnected, VcnTransportInfo info) {
mNetCapabilities.setTransportInfo(info);
setConnectivityCommon(networkType, validated, isConnected);
+ mDefaultCallbackInNetworkController.onCapabilitiesChanged(
+ mock(Network.class), new NetworkCapabilities(mNetCapabilities));
Intent i = new Intent(ConnectivityManager.INET_CONDITION_ACTION);
mNetworkController.onReceive(mContext, i);
}
@@ -322,6 +328,8 @@
public void setConnectivityViaBroadcast(
int networkType, boolean validated, boolean isConnected) {
setConnectivityCommon(networkType, validated, isConnected);
+ mDefaultCallbackInNetworkController.onCapabilitiesChanged(
+ mock(Network.class), new NetworkCapabilities(mNetCapabilities));
Intent i = new Intent(ConnectivityManager.INET_CONDITION_ACTION);
mNetworkController.onReceive(mContext, i);
}
@@ -487,28 +495,21 @@
protected void verifyLastQsMobileDataIndicators(boolean visible, int icon, int typeIcon,
boolean dataIn, boolean dataOut) {
- ArgumentCaptor<IconState> iconArg = ArgumentCaptor.forClass(IconState.class);
- ArgumentCaptor<Integer> typeIconArg = ArgumentCaptor.forClass(Integer.class);
- ArgumentCaptor<Boolean> dataInArg = ArgumentCaptor.forClass(Boolean.class);
- ArgumentCaptor<Boolean> dataOutArg = ArgumentCaptor.forClass(Boolean.class);
+ ArgumentCaptor<MobileDataIndicators> indicatorsArg =
+ ArgumentCaptor.forClass(MobileDataIndicators.class);
verify(mCallbackHandler, Mockito.atLeastOnce()).setMobileDataIndicators(
- any(),
- iconArg.capture(),
- anyInt(),
- typeIconArg.capture(), dataInArg.capture(), dataOutArg.capture(),
- any(CharSequence.class), any(CharSequence.class), any(CharSequence.class),
- anyBoolean(), anyInt(), anyBoolean(), anyBoolean());
- IconState iconState = iconArg.getValue();
+ indicatorsArg.capture());
+ MobileDataIndicators expected = indicatorsArg.getValue();
int state = SignalDrawable.getState(icon, CellSignalStrength.getNumSignalStrengthLevels(),
false);
- assertEquals("Visibility in, quick settings", visible, iconState.visible);
- assertEquals("Signal icon in, quick settings", state, iconState.icon);
- assertEquals("Data icon in, quick settings", typeIcon, (int) typeIconArg.getValue());
+ assertEquals("Visibility in, quick settings", visible, expected.qsIcon.visible);
+ assertEquals("Signal icon in, quick settings", state, expected.qsIcon.icon);
+ assertEquals("Data icon in, quick settings", typeIcon, expected.qsType);
assertEquals("Data direction in, in quick settings", dataIn,
- (boolean) dataInArg.getValue());
+ expected.activityIn);
assertEquals("Data direction out, in quick settings", dataOut,
- (boolean) dataOutArg.getValue());
+ expected.activityOut);
}
protected void verifyLastMobileDataIndicators(boolean visible, int icon, int typeIcon) {
@@ -522,44 +523,35 @@
protected void verifyLastMobileDataIndicators(boolean visible, int icon, int typeIcon,
boolean roaming, boolean inet) {
- ArgumentCaptor<IconState> iconArg = ArgumentCaptor.forClass(IconState.class);
- ArgumentCaptor<Integer> typeIconArg = ArgumentCaptor.forClass(Integer.class);
+ ArgumentCaptor<MobileDataIndicators> indicatorsArg =
+ ArgumentCaptor.forClass(MobileDataIndicators.class);
// TODO: Verify all fields.
verify(mCallbackHandler, Mockito.atLeastOnce()).setMobileDataIndicators(
- iconArg.capture(),
- any(),
- typeIconArg.capture(),
- anyInt(), anyBoolean(), anyBoolean(),
- any(CharSequence.class), any(CharSequence.class), any(),
- anyBoolean(), anyInt(), eq(roaming), anyBoolean());
- IconState iconState = iconArg.getValue();
+ indicatorsArg.capture());
+ MobileDataIndicators expected = indicatorsArg.getValue();
int state = icon == -1 ? 0
: SignalDrawable.getState(icon, CellSignalStrength.getNumSignalStrengthLevels(),
!inet);
- assertEquals("Signal icon in status bar", state, iconState.icon);
- assertEquals("Data icon in status bar", typeIcon, (int) typeIconArg.getValue());
- assertEquals("Visibility in status bar", visible, iconState.visible);
+ assertEquals("Signal icon in status bar", state, expected.statusIcon.icon);
+ assertEquals("Data icon in status bar", typeIcon, expected.statusType);
+ assertEquals("Visibility in status bar", visible, expected.statusIcon.visible);
}
protected void verifyLastMobileDataIndicatorsForVcn(boolean visible, int level, int typeIcon,
boolean inet) {
- ArgumentCaptor<IconState> iconArg = ArgumentCaptor.forClass(IconState.class);
- ArgumentCaptor<Integer> typeIconArg = ArgumentCaptor.forClass(Integer.class);
+ ArgumentCaptor<MobileDataIndicators> indicatorsArg =
+ ArgumentCaptor.forClass(MobileDataIndicators.class);
verify(mCallbackHandler, Mockito.atLeastOnce()).setMobileDataIndicators(
- iconArg.capture(),
- any(),
- typeIconArg.capture(),
- anyInt(), anyBoolean(), anyBoolean(),
- any(CharSequence.class), any(CharSequence.class), any(),
- anyBoolean(), anyInt(), anyBoolean(), anyBoolean());
- IconState iconState = iconArg.getValue();
+ indicatorsArg.capture());
+
+ MobileDataIndicators expected = indicatorsArg.getValue();
int state = SignalDrawable.getState(
level, CellSignalStrength.getNumSignalStrengthLevels(), !inet);
- assertEquals("Signal icon in status bar", state, iconState.icon);
- assertEquals("Data icon in status bar", typeIcon, (int) typeIconArg.getValue());
- assertEquals("Visibility in status bar", visible, iconState.visible);
+ assertEquals("Signal icon in status bar", state, expected.statusIcon.icon);
+ assertEquals("Data icon in status bar", typeIcon, expected.statusType);
+ assertEquals("Visibility in status bar", visible, expected.statusIcon.visible);
}
protected void verifyLastMobileDataIndicators(boolean visible, int icon, int typeIcon,
@@ -580,6 +572,8 @@
boolean qsVisible, int qsIcon, int qsTypeIcon, boolean dataIn, boolean dataOut,
boolean cutOut, CharSequence typeContentDescription,
CharSequence typeContentDescriptionHtml) {
+ ArgumentCaptor<MobileDataIndicators> indicatorsArg =
+ ArgumentCaptor.forClass(MobileDataIndicators.class);
ArgumentCaptor<IconState> iconArg = ArgumentCaptor.forClass(IconState.class);
ArgumentCaptor<Integer> typeIconArg = ArgumentCaptor.forClass(Integer.class);
ArgumentCaptor<IconState> qsIconArg = ArgumentCaptor.forClass(IconState.class);
@@ -592,17 +586,9 @@
ArgumentCaptor.forClass(CharSequence.class);
verify(mCallbackHandler, Mockito.atLeastOnce()).setMobileDataIndicators(
- iconArg.capture(),
- qsIconArg.capture(),
- typeIconArg.capture(),
- qsTypeIconArg.capture(),
- dataInArg.capture(),
- dataOutArg.capture(),
- typeContentDescriptionArg.capture(),
- typeContentDescriptionHtmlArg.capture(),
- any(), anyBoolean(), anyInt(), anyBoolean(), anyBoolean());
+ indicatorsArg.capture());
- IconState iconState = iconArg.getValue();
+ MobileDataIndicators expected = indicatorsArg.getValue();
int numSignalStrengthBins = CellSignalStrength.getNumSignalStrengthLevels();
if (mMobileSignalController.mInflateSignalStrengths) {
@@ -610,29 +596,28 @@
icon++;
}
int state = SignalDrawable.getState(icon, numSignalStrengthBins, cutOut);
- assertEquals("Data icon in status bar", typeIcon, (int) typeIconArg.getValue());
- assertEquals("Signal icon in status bar", state, iconState.icon);
- assertEquals("Visibility in status bar", visible, iconState.visible);
+ assertEquals("Data icon in status bar", typeIcon, expected.statusType);
+ assertEquals("Signal icon in status bar", state, expected.statusIcon.icon);
+ assertEquals("Visibility in status bar", visible, expected.statusIcon.visible);
- iconState = qsIconArg.getValue();
if (visible) {
- assertEquals("Visibility in quick settings", qsVisible, iconState.visible);
- assertEquals("Signal icon in quick settings", state, iconState.icon);
+ assertEquals("Visibility in quick settings", qsVisible, expected.qsIcon.visible);
+ assertEquals("Signal icon in quick settings", state, expected.qsIcon.icon);
} else {
- assertEquals("Cellular is not default", null, iconState);
+ assertEquals("Cellular is not default", null, expected.qsIcon);
}
- assertEquals("Data icon in quick settings", qsTypeIcon, (int) qsTypeIconArg.getValue());
+ assertEquals("Data icon in quick settings", qsTypeIcon, expected.qsType);
assertEquals("Data direction in in quick settings", dataIn,
- (boolean) dataInArg.getValue());
+ expected.activityIn);
assertEquals("Data direction out in quick settings", dataOut,
- (boolean) dataOutArg.getValue());
+ expected.activityOut);
if (typeContentDescription != null) { // Only check if it was provided
assertEquals("Type content description", typeContentDescription,
- typeContentDescriptionArg.getValue());
+ expected.typeContentDescription);
}
if (typeContentDescriptionHtml != null) { // Only check if it was provided
assertEquals("Type content description (html)", typeContentDescriptionHtml,
- typeContentDescriptionHtmlArg.getValue());
+ expected.typeContentDescriptionHtml);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
index fc1a08ac..c6812a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
@@ -3,7 +3,6 @@
import static junit.framework.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -21,7 +20,7 @@
import android.testing.TestableLooper.RunWithLooper;
import com.android.settingslib.mobile.TelephonyIcons;
-import com.android.systemui.statusbar.policy.NetworkController.IconState;
+import com.android.systemui.statusbar.policy.NetworkController.WifiIndicators;
import org.junit.Before;
import org.junit.Test;
@@ -316,44 +315,42 @@
}
protected void verifyLastQsDataDirection(boolean in, boolean out) {
- ArgumentCaptor<Boolean> inArg = ArgumentCaptor.forClass(Boolean.class);
- ArgumentCaptor<Boolean> outArg = ArgumentCaptor.forClass(Boolean.class);
+ ArgumentCaptor<WifiIndicators> indicatorsArg =
+ ArgumentCaptor.forClass(WifiIndicators.class);
Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setWifiIndicators(
- anyBoolean(), any(), any(), inArg.capture(), outArg.capture(), any(), anyBoolean(),
- any());
- assertEquals("WiFi data in, in quick settings", in, (boolean) inArg.getValue());
- assertEquals("WiFi data out, in quick settings", out, (boolean) outArg.getValue());
+ indicatorsArg.capture());
+ WifiIndicators expected = indicatorsArg.getValue();
+ assertEquals("WiFi data in, in quick settings", in, expected.activityIn);
+ assertEquals("WiFi data out, in quick settings", out, expected.activityOut);
}
protected void verifyLastQsWifiIcon(boolean enabled, boolean connected, int icon,
String description) {
- ArgumentCaptor<IconState> iconArg = ArgumentCaptor.forClass(IconState.class);
- ArgumentCaptor<Boolean> enabledArg = ArgumentCaptor.forClass(Boolean.class);
- ArgumentCaptor<String> descArg = ArgumentCaptor.forClass(String.class);
+ ArgumentCaptor<WifiIndicators> indicatorsArg =
+ ArgumentCaptor.forClass(WifiIndicators.class);
Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setWifiIndicators(
- enabledArg.capture(), any(), iconArg.capture(), anyBoolean(),
- anyBoolean(), descArg.capture(), anyBoolean(), any());
- IconState iconState = iconArg.getValue();
- assertEquals("WiFi enabled, in quick settings", enabled, (boolean) enabledArg.getValue());
- assertEquals("WiFI desc (ssid), in quick settings", description, descArg.getValue());
+ indicatorsArg.capture());
+ WifiIndicators expected = indicatorsArg.getValue();
+ assertEquals("WiFi enabled, in quick settings", enabled, expected.enabled);
+ assertEquals("WiFI desc (ssid), in quick settings", description, expected.description);
if (enabled && connected) {
- assertEquals("WiFi connected, in quick settings", connected, iconState.visible);
- assertEquals("WiFi signal, in quick settings", icon, iconState.icon);
+ assertEquals("WiFi connected, in quick settings", connected, expected.qsIcon.visible);
+ assertEquals("WiFi signal, in quick settings", icon, expected.qsIcon.icon);
} else {
- assertEquals("WiFi is not default", null, iconState);
+ assertEquals("WiFi is not default", null, expected.qsIcon);
}
}
protected void verifyLastWifiIcon(boolean visible, int icon) {
- ArgumentCaptor<IconState> iconArg = ArgumentCaptor.forClass(IconState.class);
+ ArgumentCaptor<WifiIndicators> indicatorsArg =
+ ArgumentCaptor.forClass(WifiIndicators.class);
Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setWifiIndicators(
- anyBoolean(), iconArg.capture(), any(), anyBoolean(), anyBoolean(),
- any(), anyBoolean(), any());
- IconState iconState = iconArg.getValue();
- assertEquals("WiFi visible, in status bar", visible, iconState.visible);
- assertEquals("WiFi signal, in status bar", icon, iconState.icon);
+ indicatorsArg.capture());
+ WifiIndicators expected = indicatorsArg.getValue();
+ assertEquals("WiFi visible, in status bar", visible, expected.statusIcon.visible);
+ assertEquals("WiFi signal, in status bar", icon, expected.statusIcon.icon);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index c085689..3cea175 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -22,6 +22,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.app.KeyguardManager;
@@ -37,6 +38,8 @@
import androidx.test.filters.SmallTest;
+import com.android.systemui.Prefs;
+import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.plugins.VolumeDialogController.State;
@@ -58,6 +61,11 @@
public class VolumeDialogImplTest extends SysuiTestCase {
VolumeDialogImpl mDialog;
+ View mActiveRinger;
+ View mDrawerContainer;
+ View mDrawerVibrate;
+ View mDrawerMute;
+ View mDrawerNormal;
@Mock
VolumeDialogController mController;
@@ -80,6 +88,17 @@
mDialog.init(0, null);
State state = createShellState();
mDialog.onStateChangedH(state);
+
+ mActiveRinger = mDialog.getDialogView().findViewById(
+ R.id.volume_new_ringer_active_icon_container);
+ mDrawerContainer = mDialog.getDialogView().findViewById(R.id.volume_drawer_container);
+ mDrawerVibrate = mDrawerContainer.findViewById(R.id.volume_drawer_vibrate);
+ mDrawerMute = mDrawerContainer.findViewById(R.id.volume_drawer_mute);
+ mDrawerNormal = mDrawerContainer.findViewById(R.id.volume_drawer_normal);
+
+ Prefs.putInt(mContext,
+ Prefs.Key.SEEN_RINGER_GUIDANCE_COUNT,
+ VolumePrefs.SHOW_RINGER_TOAST_COUNT + 1);
}
private State createShellState() {
@@ -207,6 +226,48 @@
verify(mController, never()).vibrate(any());
}
+ @Test
+ public void testSelectVibrateFromDrawer() {
+ final State initialUnsetState = new State();
+ initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL;
+ mDialog.onStateChangedH(initialUnsetState);
+
+ mActiveRinger.performClick();
+ mDrawerVibrate.performClick();
+
+ // Make sure we've actually changed the ringer mode.
+ verify(mController, times(1)).setRingerMode(
+ AudioManager.RINGER_MODE_VIBRATE, false);
+ }
+
+ @Test
+ public void testSelectMuteFromDrawer() {
+ final State initialUnsetState = new State();
+ initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL;
+ mDialog.onStateChangedH(initialUnsetState);
+
+ mActiveRinger.performClick();
+ mDrawerMute.performClick();
+
+ // Make sure we've actually changed the ringer mode.
+ verify(mController, times(1)).setRingerMode(
+ AudioManager.RINGER_MODE_SILENT, false);
+ }
+
+ @Test
+ public void testSelectNormalFromDrawer() {
+ final State initialUnsetState = new State();
+ initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE;
+ mDialog.onStateChangedH(initialUnsetState);
+
+ mActiveRinger.performClick();
+ mDrawerNormal.performClick();
+
+ // Make sure we've actually changed the ringer mode.
+ verify(mController, times(1)).setRingerMode(
+ AudioManager.RINGER_MODE_NORMAL, false);
+ }
+
/*
@Test
public void testContentDescriptions() {
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index f4138d1..542d527 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -44,6 +44,7 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
@@ -91,7 +92,6 @@
import android.net.IDnsResolver;
import android.net.INetd;
import android.net.INetworkActivityListener;
-import android.net.INetworkManagementEventObserver;
import android.net.INetworkMonitor;
import android.net.INetworkMonitorCallbacks;
import android.net.INetworkPolicyListener;
@@ -194,6 +194,7 @@
import com.android.internal.util.LocationPermissionChecker;
import com.android.internal.util.MessageUtils;
import com.android.modules.utils.BasicShellCommandHandler;
+import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.LinkPropertiesUtils.CompareOrUpdateResult;
import com.android.net.module.util.LinkPropertiesUtils.CompareResult;
@@ -214,7 +215,6 @@
import com.android.server.connectivity.PermissionMonitor;
import com.android.server.connectivity.ProxyTracker;
import com.android.server.connectivity.QosCallbackTracker;
-import com.android.server.net.BaseNetworkObserver;
import com.android.server.net.NetworkPolicyManagerInternal;
import com.android.server.utils.PriorityDump;
@@ -332,6 +332,7 @@
private INetworkStatsService mStatsService;
private NetworkPolicyManager mPolicyManager;
private NetworkPolicyManagerInternal mPolicyManagerInternal;
+ private final NetdCallback mNetdCallback;
/**
* TestNetworkService (lazily) created upon first usage. Locked to prevent creation of multiple
@@ -1204,6 +1205,13 @@
mNetworkActivityTracker = new LegacyNetworkActivityTracker(mContext, mHandler, mNMS, mNetd);
+ mNetdCallback = new NetdCallback();
+ try {
+ mNetd.registerUnsolicitedEventListener(mNetdCallback);
+ } catch (RemoteException | ServiceSpecificException e) {
+ loge("Error registering event listener :" + e);
+ }
+
mSettingsObserver = new SettingsObserver(mContext, mHandler);
registerSettingsCallbacks();
@@ -1241,6 +1249,7 @@
private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) {
final NetworkCapabilities netCap = new NetworkCapabilities();
netCap.addCapability(NET_CAPABILITY_INTERNET);
+ netCap.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
netCap.removeCapability(NET_CAPABILITY_NOT_VPN);
netCap.setSingleUid(uid);
return netCap;
@@ -1255,6 +1264,7 @@
int transportType, NetworkRequest.Type type) {
final NetworkCapabilities netCap = new NetworkCapabilities();
netCap.addCapability(NET_CAPABILITY_INTERNET);
+ netCap.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
netCap.setRequestorUidAndPackageName(Process.myUid(), mContext.getPackageName());
if (transportType > TYPE_NONE) {
netCap.addTransportType(transportType);
@@ -8649,6 +8659,14 @@
notifyDataStallSuspected(p, network.getNetId());
}
+ private class NetdCallback extends BaseNetdUnsolicitedEventListener {
+ @Override
+ public void onInterfaceClassActivityChanged(boolean isActive, int timerLabel,
+ long timestampNs, int uid) {
+ mNetworkActivityTracker.setAndReportNetworkActive(isActive, timerLabel, timestampNs);
+ }
+ }
+
private final LegacyNetworkActivityTracker mNetworkActivityTracker;
/**
@@ -8659,7 +8677,6 @@
private static final int NO_UID = -1;
private final Context mContext;
private final INetd mNetd;
- private final INetworkManagementService mNMS;
private final RemoteCallbackList<INetworkActivityListener> mNetworkActivityListeners =
new RemoteCallbackList<>();
// Indicate the current system default network activity is active or not.
@@ -8682,41 +8699,27 @@
LegacyNetworkActivityTracker(@NonNull Context context, @NonNull Handler handler,
@NonNull INetworkManagementService nms, @NonNull INetd netd) {
mContext = context;
- mNMS = nms;
mNetd = netd;
mHandler = handler;
- try {
- mNMS.registerObserver(mDataActivityObserver);
- } catch (RemoteException e) {
- loge("Error registering observer :" + e);
- }
}
- // TODO: Migrate away the dependency with INetworkManagementEventObserver.
- private final INetworkManagementEventObserver mDataActivityObserver =
- new BaseNetworkObserver() {
- @Override
- public void interfaceClassDataActivityChanged(int transportType, boolean active,
- long tsNanos, int uid) {
- sendDataActivityBroadcast(transportTypeToLegacyType(transportType), active,
- tsNanos);
- synchronized (mActiveIdleTimers) {
- mNetworkActive = active;
- // If there are no idle timers, it means that system is not monitoring
- // activity, so the system default network for those default network
- // unspecified apps is always considered active.
- //
- // TODO: If the mActiveIdleTimers is empty, netd will actually not send
- // any network activity change event. Whenever this event is received,
- // the mActiveIdleTimers should be always not empty. The legacy behavior
- // is no-op. Remove to refer to mNetworkActive only.
- if (mNetworkActive || mActiveIdleTimers.isEmpty()) {
- mHandler.sendMessage(
- mHandler.obtainMessage(EVENT_REPORT_NETWORK_ACTIVITY));
- }
- }
- }
- };
+ public void setAndReportNetworkActive(boolean active, int transportType, long tsNanos) {
+ sendDataActivityBroadcast(transportTypeToLegacyType(transportType), active, tsNanos);
+ synchronized (mActiveIdleTimers) {
+ mNetworkActive = active;
+ // If there are no idle timers, it means that system is not monitoring
+ // activity, so the system default network for those default network
+ // unspecified apps is always considered active.
+ //
+ // TODO: If the mActiveIdleTimers is empty, netd will actually not send
+ // any network activity change event. Whenever this event is received,
+ // the mActiveIdleTimers should be always not empty. The legacy behavior
+ // is no-op. Remove to refer to mNetworkActive only.
+ if (mNetworkActive || mActiveIdleTimers.isEmpty()) {
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_REPORT_NETWORK_ACTIVITY));
+ }
+ }
+ }
// The network activity should only be updated from ConnectivityService handler thread
// when mActiveIdleTimers lock is held.
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 5e61f94..7cd49497 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -15127,13 +15127,6 @@
}
@Override
- public void setPendingIntentWhitelistDuration(IIntentSender target, IBinder allowlistToken,
- long duration, int type) {
- mPendingIntentController.setPendingIntentAllowlistDuration(target, allowlistToken,
- duration, type, REASON_UNKNOWN, "");
- }
-
- @Override
public int getPendingIntentFlags(IIntentSender target) {
return mPendingIntentController.getPendingIntentFlags(target);
}
diff --git a/services/core/java/com/android/server/am/TEST_MAPPING b/services/core/java/com/android/server/am/TEST_MAPPING
index 4061df4..03eddc9 100644
--- a/services/core/java/com/android/server/am/TEST_MAPPING
+++ b/services/core/java/com/android/server/am/TEST_MAPPING
@@ -44,6 +44,23 @@
"exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
+ },
+ {
+ "file_patterns": ["Battery[^/]*\\.java", "MeasuredEnergy[^/]*\\.java"],
+ "name": "FrameworksCoreTests",
+ "options": [
+ { "include-filter": "com.android.internal.os.BatteryStatsTests" },
+ { "exclude-annotation": "com.android.internal.os.SkipPresubmit" }
+ ]
+ },
+ {
+ "file_patterns": ["Battery[^/]*\\.java", "MeasuredEnergy[^/]*\\.java"],
+ "name": "FrameworksServicesTests",
+ "options": [
+ { "include-filter": "com.android.server.am.BatteryStatsServiceTest" },
+ { "include-filter": "com.android.server.am.MeasuredEnergySnapshotTest" },
+ { "include-filter": "com.android.server.am.BatteryExternalStatsWorkerTest" }
+ ]
}
],
"postsubmit": [
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 322c210..2de709e 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -246,8 +246,8 @@
ClipData primaryClip;
/** UID that set {@link #primaryClip}. */
int primaryClipUid = android.os.Process.NOBODY_UID;
- /** Application label of the app that set {@link #primaryClip}. */
- CharSequence mPrimaryClipAppLabel;
+ /** Package of the app that set {@link #primaryClip}. */
+ String mPrimaryClipPackage;
final HashSet<String> activePermissionOwners
= new HashSet<String>();
@@ -528,20 +528,9 @@
}
}
- // Retrieve the app label of the source of the clip data
- CharSequence sourceAppLabel = null;
- if (clip != null && sourcePackage != null) {
- try {
- sourceAppLabel =
- mPm.getApplicationLabel(mPm.getApplicationInfo(sourcePackage, 0));
- } catch (PackageManager.NameNotFoundException e) {
- // leave label as null
- }
- }
-
// Update this user
final int userId = UserHandle.getUserId(uid);
- setPrimaryClipInternal(getClipboard(userId), clip, uid, sourceAppLabel);
+ setPrimaryClipInternal(getClipboard(userId), clip, uid, sourcePackage);
// Update related users
List<UserInfo> related = getRelatedProfiles(userId);
@@ -576,7 +565,7 @@
UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, id);
if (canCopyIntoProfile) {
setPrimaryClipInternal(
- getClipboard(id), clip, uid, sourceAppLabel);
+ getClipboard(id), clip, uid, sourcePackage);
}
}
}
@@ -590,7 +579,7 @@
}
private void setPrimaryClipInternal(PerUserClipboard clipboard, @Nullable ClipData clip,
- int uid, @Nullable CharSequence sourceAppLabel) {
+ int uid, @Nullable String sourcePackage) {
revokeUris(clipboard);
clipboard.activePermissionOwners.clear();
if (clip == null && clipboard.primaryClip == null) {
@@ -599,10 +588,10 @@
clipboard.primaryClip = clip;
if (clip != null) {
clipboard.primaryClipUid = uid;
- clipboard.mPrimaryClipAppLabel = sourceAppLabel;
+ clipboard.mPrimaryClipPackage = sourcePackage;
} else {
clipboard.primaryClipUid = android.os.Process.NOBODY_UID;
- clipboard.mPrimaryClipAppLabel = null;
+ clipboard.mPrimaryClipPackage = null;
}
if (clip != null) {
final ClipDescription description = clip.getDescription();
@@ -887,12 +876,23 @@
return;
}
+ // Retrieve the app label of the source of the clip data
+ CharSequence sourceAppLabel = null;
+ if (clipboard.mPrimaryClipPackage != null) {
+ try {
+ sourceAppLabel = mPm.getApplicationLabel(
+ mPm.getApplicationInfo(clipboard.mPrimaryClipPackage, 0));
+ } catch (PackageManager.NameNotFoundException e) {
+ // leave label as null
+ }
+ }
+
try {
CharSequence callingAppLabel = mPm.getApplicationLabel(
mPm.getApplicationInfo(callingPackage, 0));
String message;
- if (clipboard.mPrimaryClipAppLabel != null) {
- message = callingAppLabel + " pasted from " + clipboard.mPrimaryClipAppLabel;
+ if (sourceAppLabel != null) {
+ message = callingAppLabel + " pasted from " + sourceAppLabel;
} else {
message = callingAppLabel + " pasted from clipboard";
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 2005a74..86142bc 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -469,18 +469,10 @@
DisplayDeviceConfig displayDeviceConfig = logicalDisplay
.getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig();
- // TODO: (b/178183143) Ensure that the ddc is not null
- if (displayDeviceConfig != null) {
- mBrightnessRampRateFastDecrease = displayDeviceConfig.getBrightnessRampFastDecrease();
- mBrightnessRampRateFastIncrease = displayDeviceConfig.getBrightnessRampFastIncrease();
- mBrightnessRampRateSlowDecrease = displayDeviceConfig.getBrightnessRampSlowDecrease();
- mBrightnessRampRateSlowIncrease = displayDeviceConfig.getBrightnessRampSlowIncrease();
- } else {
- mBrightnessRampRateFastDecrease = 1.0f;
- mBrightnessRampRateFastIncrease = 1.0f;
- mBrightnessRampRateSlowDecrease = 1.0f;
- mBrightnessRampRateSlowIncrease = 1.0f;
- }
+ mBrightnessRampRateFastDecrease = displayDeviceConfig.getBrightnessRampFastDecrease();
+ mBrightnessRampRateFastIncrease = displayDeviceConfig.getBrightnessRampFastIncrease();
+ mBrightnessRampRateSlowDecrease = displayDeviceConfig.getBrightnessRampSlowDecrease();
+ mBrightnessRampRateSlowIncrease = displayDeviceConfig.getBrightnessRampSlowIncrease();
mSkipScreenOnBrightnessRamp = resources.getBoolean(
com.android.internal.R.bool.config_skipScreenOnBrightnessRamp);
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 48f335d..3709963 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -638,11 +638,7 @@
mInfo.flags |= DisplayDeviceInfo.FLAG_TRUSTED;
mInfo.brightnessMinimum = PowerManager.BRIGHTNESS_MIN;
mInfo.brightnessMaximum = PowerManager.BRIGHTNESS_MAX;
- if (mDisplayDeviceConfig != null) {
- mInfo.brightnessDefault = mDisplayDeviceConfig.getBrightnessDefault();
- } else {
- mInfo.brightnessDefault = 0.5f;
- }
+ mInfo.brightnessDefault = getDisplayDeviceConfig().getBrightnessDefault();
}
return mInfo;
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 1e66589..87e63eb 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -5414,37 +5414,36 @@
@BinderThread
@ShellCommandResult
private int onCommandWithSystemIdentity(@Nullable String cmd) {
- if ("get-last-switch-user-id".equals(cmd)) {
- return mService.getLastSwitchUserId(this);
- }
-
- // For existing "adb shell ime <command>".
- if ("ime".equals(cmd)) {
- final String imeCommand = getNextArg();
- if (imeCommand == null || "help".equals(imeCommand) || "-h".equals(imeCommand)) {
- onImeCommandHelp();
- return ShellCommandResult.SUCCESS;
+ switch (TextUtils.emptyIfNull(cmd)) {
+ case "get-last-switch-user-id":
+ return mService.getLastSwitchUserId(this);
+ case "ime": { // For "adb shell ime <command>".
+ final String imeCommand = TextUtils.emptyIfNull(getNextArg());
+ switch (imeCommand) {
+ case "":
+ case "-h":
+ case "help":
+ return onImeCommandHelp();
+ case "list":
+ return mService.handleShellCommandListInputMethods(this);
+ case "enable":
+ return mService.handleShellCommandEnableDisableInputMethod(this, true);
+ case "disable":
+ return mService.handleShellCommandEnableDisableInputMethod(this, false);
+ case "set":
+ return mService.handleShellCommandSetInputMethod(this);
+ case "reset":
+ return mService.handleShellCommandResetInputMethod(this);
+ case "tracing":
+ return mService.handleShellCommandTraceInputMethod(this);
+ default:
+ getOutPrintWriter().println("Unknown command: " + imeCommand);
+ return ShellCommandResult.FAILURE;
+ }
}
- switch (imeCommand) {
- case "list":
- return mService.handleShellCommandListInputMethods(this);
- case "enable":
- return mService.handleShellCommandEnableDisableInputMethod(this, true);
- case "disable":
- return mService.handleShellCommandEnableDisableInputMethod(this, false);
- case "set":
- return mService.handleShellCommandSetInputMethod(this);
- case "reset":
- return mService.handleShellCommandResetInputMethod(this);
- case "tracing":
- return mService.handleShellCommandTraceInputMethod(this);
- default:
- getOutPrintWriter().println("Unknown command: " + imeCommand);
- return ShellCommandResult.FAILURE;
- }
+ default:
+ return handleDefaultCommands(cmd);
}
-
- return handleDefaultCommands(cmd);
}
@BinderThread
@@ -5461,7 +5460,9 @@
}
}
- private void onImeCommandHelp() {
+ @BinderThread
+ @ShellCommandResult
+ private int onImeCommandHelp() {
try (IndentingPrintWriter pw =
new IndentingPrintWriter(getOutPrintWriter(), " ", 100)) {
pw.println("ime <command>:");
@@ -5516,6 +5517,7 @@
pw.decreaseIndent();
}
+ return ShellCommandResult.SUCCESS;
}
}
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 57e9fc9..2920ddb 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -52,6 +52,7 @@
import android.location.GnssMeasurementCorrections;
import android.location.GnssMeasurementRequest;
import android.location.IGeocodeListener;
+import android.location.IGnssAntennaInfoListener;
import android.location.IGnssMeasurementsListener;
import android.location.IGnssNavigationMessageListener;
import android.location.IGnssNmeaListener;
@@ -463,7 +464,7 @@
.build();
final LocationProviderManager manager = getOrAddLocationProviderManager(name);
manager.setMockProvider(new MockLocationProvider(properties,
- CallerIdentity.fromContext(mContext), /*locationTags*/ null));
+ CallerIdentity.fromContext(mContext), Collections.emptySet()));
}
}
@@ -930,6 +931,22 @@
}
@Override
+ public void addGnssAntennaInfoListener(IGnssAntennaInfoListener listener, String packageName,
+ @Nullable String attributionTag, String listenerId) {
+ if (mGnssManagerService != null) {
+ mGnssManagerService.addGnssAntennaInfoListener(listener, packageName, attributionTag,
+ listenerId);
+ }
+ }
+
+ @Override
+ public void removeGnssAntennaInfoListener(IGnssAntennaInfoListener listener) {
+ if (mGnssManagerService != null) {
+ mGnssManagerService.removeGnssAntennaInfoListener(listener);
+ }
+ }
+
+ @Override
public void addProviderRequestListener(IProviderRequestListener listener) {
for (LocationProviderManager manager : mProviderManagers) {
manager.addProviderRequestListener(listener);
@@ -1153,7 +1170,7 @@
@Override
public void addTestProvider(String provider, ProviderProperties properties,
- List<String> locationTags, String packageName, String attributionTag) {
+ List<String> extraAttributionTags, String packageName, String attributionTag) {
// unsafe is ok because app ops will verify the package name
CallerIdentity identity = CallerIdentity.fromBinderUnsafe(packageName, attributionTag);
if (!mInjector.getAppOpsHelper().noteOp(AppOpsManager.OP_MOCK_LOCATION, identity)) {
@@ -1162,7 +1179,7 @@
final LocationProviderManager manager = getOrAddLocationProviderManager(provider);
manager.setMockProvider(new MockLocationProvider(properties, identity,
- (locationTags != null) ? new ArraySet<>(locationTags) : null));
+ new ArraySet<>(extraAttributionTags)));
}
@Override
diff --git a/services/core/java/com/android/server/location/LocationShellCommand.java b/services/core/java/com/android/server/location/LocationShellCommand.java
index 21a9b04..5dc3ed8 100644
--- a/services/core/java/com/android/server/location/LocationShellCommand.java
+++ b/services/core/java/com/android/server/location/LocationShellCommand.java
@@ -26,6 +26,8 @@
import java.io.PrintWriter;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
import java.util.Objects;
/**
@@ -50,15 +52,11 @@
switch (cmd) {
case "is-location-enabled": {
- int userId = parseUserId();
- boolean enabled = mService.isLocationEnabledForUser(userId);
- getOutPrintWriter().println(enabled);
+ handleIsLocationEnabled();
return 0;
}
case "set-location-enabled": {
- int userId = parseUserId();
- boolean enabled = Boolean.parseBoolean(getNextArgRequired());
- mService.setLocationEnabledForUser(enabled, userId);
+ handleSetLocationEnabled();
return 0;
}
case "providers": {
@@ -73,36 +71,23 @@
private int parseProvidersCommand(String cmd) {
switch (cmd) {
case "add-test-provider": {
- String provider = getNextArgRequired();
- ProviderProperties properties = parseTestProviderProviderProperties();
- mService.addTestProvider(provider, properties, /*locationTags*/ null,
- mContext.getOpPackageName(), mContext.getFeatureId());
+ handleAddTestProvider();
return 0;
}
case "remove-test-provider": {
- String provider = getNextArgRequired();
- mService.removeTestProvider(provider, mContext.getOpPackageName(),
- mContext.getFeatureId());
+ handleRemoveTestProvider();
return 0;
}
case "set-test-provider-enabled": {
- String provider = getNextArgRequired();
- boolean enabled = Boolean.parseBoolean(getNextArgRequired());
- mService.setTestProviderEnabled(provider, enabled, mContext.getOpPackageName(),
- mContext.getFeatureId());
+ handleSetTestProviderEnabled();
return 0;
}
case "set-test-provider-location": {
- String provider = getNextArgRequired();
- Location location = parseTestProviderLocation(provider);
- mService.setTestProviderLocation(provider, location, mContext.getOpPackageName(),
- mContext.getFeatureId());
+ handleSetTestProviderLocation();
return 0;
}
case "send-extra-command": {
- String provider = getNextArgRequired();
- String command = getNextArgRequired();
- mService.sendExtraCommand(provider, command, null);
+ handleSendExtraCommand();
return 0;
}
default:
@@ -110,21 +95,47 @@
}
}
- private int parseUserId() {
- final String option = getNextOption();
- if (option != null) {
- if (option.equals("--user")) {
- return UserHandle.parseUserArg(getNextArgRequired());
- } else {
- throw new IllegalArgumentException(
- "Expected \"--user\" option, but got \"" + option + "\" instead");
- }
- }
+ private void handleIsLocationEnabled() {
+ int userId = UserHandle.USER_CURRENT_OR_SELF;
- return UserHandle.USER_CURRENT_OR_SELF;
+ do {
+ String option = getNextOption();
+ if (option == null) {
+ break;
+ }
+ if ("--user".equals(option)) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ } else {
+ throw new IllegalArgumentException("Unknown option: " + option);
+ }
+ } while (true);
+
+ getOutPrintWriter().println(mService.isLocationEnabledForUser(userId));
}
- private ProviderProperties parseTestProviderProviderProperties() {
+ private void handleSetLocationEnabled() {
+ boolean enabled = Boolean.parseBoolean(getNextArgRequired());
+
+ int userId = UserHandle.USER_CURRENT_OR_SELF;
+
+ do {
+ String option = getNextOption();
+ if (option == null) {
+ break;
+ }
+ if ("--user".equals(option)) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ } else {
+ throw new IllegalArgumentException("Unknown option: " + option);
+ }
+ } while (true);
+
+ mService.setLocationEnabledForUser(enabled, userId);
+ }
+
+ private void handleAddTestProvider() {
+ String provider = getNextArgRequired();
+
boolean requiresNetwork = false;
boolean requiresSatellite = false;
boolean requiresCell = false;
@@ -135,8 +146,13 @@
int powerRequirement = Criteria.POWER_LOW;
int accuracy = Criteria.ACCURACY_FINE;
- String option = getNextOption();
- while (option != null) {
+ List<String> extraAttributionTags = Collections.emptyList();
+
+ do {
+ String option = getNextOption();
+ if (option == null) {
+ break;
+ }
switch (option) {
case "--requiresNetwork": {
requiresNetwork = true;
@@ -174,12 +190,15 @@
accuracy = Integer.parseInt(getNextArgRequired());
break;
}
+ case "--extraAttributionTags": {
+ extraAttributionTags = Arrays.asList(getNextArgRequired().split(","));
+ break;
+ }
default:
throw new IllegalArgumentException(
"Received unexpected option: " + option);
}
- option = getNextOption();
- }
+ } while(true);
ProviderProperties properties = new ProviderProperties.Builder()
.setHasNetworkRequirement(requiresNetwork)
@@ -192,30 +211,50 @@
.setPowerUsage(powerRequirement)
.setAccuracy(accuracy)
.build();
-
- return properties;
+ mService.addTestProvider(provider, properties, extraAttributionTags,
+ mContext.getOpPackageName(), mContext.getAttributionTag());
}
- private Location parseTestProviderLocation(String provider) {
- boolean hasLatitude = false;
- boolean hasLongitude = false;
+ private void handleRemoveTestProvider() {
+ String provider = getNextArgRequired();
+ mService.removeTestProvider(provider, mContext.getOpPackageName(),
+ mContext.getAttributionTag());
+ }
+
+ private void handleSetTestProviderEnabled() {
+ String provider = getNextArgRequired();
+ boolean enabled = Boolean.parseBoolean(getNextArgRequired());
+ mService.setTestProviderEnabled(provider, enabled, mContext.getOpPackageName(),
+ mContext.getAttributionTag());
+ }
+
+ private void handleSetTestProviderLocation() {
+ String provider = getNextArgRequired();
+
+ boolean hasLatLng = false;
Location location = new Location(provider);
location.setAccuracy(DEFAULT_TEST_LOCATION_ACCURACY);
location.setTime(System.currentTimeMillis());
+ location.setElapsedRealtimeNanos(System.nanoTime());
- String option = getNextOption();
- while (option != null) {
+ do {
+ String option = getNextOption();
+ if (option == null) {
+ break;
+ }
switch (option) {
case "--location": {
String[] locationInput = getNextArgRequired().split(",");
if (locationInput.length != 2) {
- throw new IllegalArgumentException(
- "Unexpected location format: " + Arrays.toString(locationInput));
+ throw new IllegalArgumentException("Location argument must be in the form "
+ + "of \"<LATITUDE>,<LONGITUDE>\", not "
+ + Arrays.toString(locationInput));
}
location.setLatitude(Double.parseDouble(locationInput[0]));
location.setLongitude(Double.parseDouble(locationInput[1]));
+ hasLatLng = true;
break;
}
case "--accuracy": {
@@ -227,15 +266,22 @@
break;
}
default:
- throw new IllegalArgumentException(
- "Received unexpected option: " + option);
+ throw new IllegalArgumentException("Unknown option: " + option);
}
- option = getNextOption();
+ } while (true);
+
+ if (!hasLatLng) {
+ throw new IllegalArgumentException("Option \"--location\" is required");
}
- location.setElapsedRealtimeNanos(System.nanoTime());
+ mService.setTestProviderLocation(provider, location, mContext.getOpPackageName(),
+ mContext.getAttributionTag());
+ }
- return location;
+ private void handleSendExtraCommand() {
+ String provider = getNextArgRequired();
+ String command = getNextArgRequired();
+ mService.sendExtraCommand(provider, command, null);
}
@Override
@@ -245,24 +291,29 @@
pw.println(" help or -h");
pw.println(" Print this help text.");
pw.println(" is-location-enabled [--user <USER_ID>]");
- pw.println(" Gets the master location switch enabled state.");
- pw.println(" set-location-enabled [--user <USER_ID>] true|false");
- pw.println(" Sets the master location switch enabled state.");
+ pw.println(" Gets the master location switch enabled state. If no user is specified,");
+ pw.println(" the current user is assumed.");
+ pw.println(" set-location-enabled true|false [--user <USER_ID>]");
+ pw.println(" Sets the master location switch enabled state. If no user is specified,");
+ pw.println(" the current user is assumed.");
pw.println(" providers");
+ pw.println(" The providers command is followed by a subcommand, as listed below:");
+ pw.println();
pw.println(" add-test-provider <PROVIDER> [--requiresNetwork] [--requiresSatellite]");
pw.println(" [--requiresCell] [--hasMonetaryCost] [--supportsAltitude]");
pw.println(" [--supportsSpeed] [--supportsBearing]");
pw.println(" [--powerRequirement <POWER_REQUIREMENT>]");
+ pw.println(" [--extraAttributionTags <TAG>,<TAG>,...]");
pw.println(" Add the given test provider. Requires MOCK_LOCATION permissions which");
pw.println(" can be enabled by running \"adb shell appops set <uid>");
pw.println(" android:mock_location allow\". There are optional flags that can be");
- pw.println(" used to configure the provider properties. If no flags are included,");
- pw.println(" then default values will be used.");
+ pw.println(" used to configure the provider properties and additional arguments. If");
+ pw.println(" no flags are included, then default values will be used.");
pw.println(" remove-test-provider <PROVIDER>");
pw.println(" Remove the given test provider.");
pw.println(" set-test-provider-enabled <PROVIDER> true|false");
pw.println(" Sets the given test provider enabled state.");
- pw.println(" set-test-provider-location <PROVIDER> [--location <LATITUDE>,<LONGITUDE>]");
+ pw.println(" set-test-provider-location <PROVIDER> --location <LATITUDE>,<LONGITUDE>");
pw.println(" [--accuracy <ACCURACY>] [--time <TIME>]");
pw.println(" Set location for given test provider. Accuracy and time are optional.");
pw.println(" send-extra-command <PROVIDER> <COMMAND>");
diff --git a/services/core/java/com/android/server/location/gnss/GnssAntennaInfoProvider.java b/services/core/java/com/android/server/location/gnss/GnssAntennaInfoProvider.java
new file mode 100644
index 0000000..1967e02
--- /dev/null
+++ b/services/core/java/com/android/server/location/gnss/GnssAntennaInfoProvider.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2021 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.location.gnss;
+
+import static com.android.server.location.gnss.GnssManagerService.TAG;
+
+import android.annotation.Nullable;
+import android.location.GnssAntennaInfo;
+import android.location.IGnssAntennaInfoListener;
+import android.location.util.identity.CallerIdentity;
+import android.os.Binder;
+import android.os.IBinder;
+
+import com.android.server.location.gnss.hal.GnssNative;
+import com.android.server.location.listeners.BinderListenerRegistration;
+import com.android.server.location.listeners.ListenerMultiplexer;
+import com.android.server.location.listeners.ListenerRegistration;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Antenna info HAL module and listener multiplexer.
+ */
+public class GnssAntennaInfoProvider extends
+ ListenerMultiplexer<IBinder, IGnssAntennaInfoListener,
+ ListenerRegistration<IGnssAntennaInfoListener>, Void> implements
+ GnssNative.BaseCallbacks, GnssNative.AntennaInfoCallbacks {
+
+ /**
+ * Registration object for GNSS listeners.
+ */
+ protected class AntennaInfoListenerRegistration extends
+ BinderListenerRegistration<Void, IGnssAntennaInfoListener> {
+
+ protected AntennaInfoListenerRegistration(CallerIdentity callerIdentity,
+ IGnssAntennaInfoListener listener) {
+ super(null, callerIdentity, listener);
+ }
+
+ @Override
+ protected GnssAntennaInfoProvider getOwner() {
+ return GnssAntennaInfoProvider.this;
+ }
+ }
+
+ private final GnssNative mGnssNative;
+
+ private volatile @Nullable List<GnssAntennaInfo> mAntennaInfos;
+
+ GnssAntennaInfoProvider(GnssNative gnssNative) {
+ mGnssNative = gnssNative;
+ mGnssNative.addBaseCallbacks(this);
+ mGnssNative.addAntennaInfoCallbacks(this);
+ }
+
+ @Nullable List<GnssAntennaInfo> getAntennaInfos() {
+ return mAntennaInfos;
+ }
+
+ @Override
+ public String getTag() {
+ return TAG;
+ }
+
+ public boolean isSupported() {
+ return mGnssNative.isAntennaInfoSupported();
+ }
+
+ public void addListener(CallerIdentity callerIdentity, IGnssAntennaInfoListener listener) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ putRegistration(listener.asBinder(),
+ new AntennaInfoListenerRegistration(callerIdentity, listener));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ public void removeListener(IGnssAntennaInfoListener listener) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ removeRegistration(listener.asBinder());
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ protected boolean registerWithService(Void merged,
+ Collection<ListenerRegistration<IGnssAntennaInfoListener>> listenerRegistrations) {
+ return true;
+ }
+
+ @Override
+ protected void unregisterWithService() {}
+
+ @Override
+ protected boolean isActive(ListenerRegistration<IGnssAntennaInfoListener> registration) {
+ return true;
+ }
+
+ @Override
+ protected Void mergeRegistrations(
+ Collection<ListenerRegistration<IGnssAntennaInfoListener>> listenerRegistrations) {
+ return null;
+ }
+
+ @Override
+ public void onHalStarted() {
+ mGnssNative.startAntennaInfoListening();
+ }
+
+ @Override
+ public void onHalRestarted() {
+ mGnssNative.startAntennaInfoListening();
+ }
+
+ @Override
+ public void onReportAntennaInfo(List<GnssAntennaInfo> antennaInfos) {
+ if (antennaInfos.equals(mAntennaInfos)) {
+ return;
+ }
+
+ mAntennaInfos = antennaInfos;
+ deliverToListeners(listener -> {
+ listener.onGnssAntennaInfoChanged(antennaInfos);
+ });
+ }
+}
diff --git a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
index 87e6ef4..5e6ae68 100644
--- a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
@@ -229,7 +229,7 @@
* registrations will be treated as inactive and the backing service will never be registered.
*
*/
- protected boolean isServiceSupported() {
+ public boolean isSupported() {
return true;
}
@@ -276,7 +276,7 @@
@Override
protected boolean isActive(GnssListenerRegistration registration) {
- if (!isServiceSupported()) {
+ if (!isSupported()) {
return false;
}
@@ -339,7 +339,7 @@
@Override
protected void onRegister() {
- if (!isServiceSupported()) {
+ if (!isSupported()) {
return;
}
@@ -356,7 +356,7 @@
@Override
protected void onUnregister() {
- if (!isServiceSupported()) {
+ if (!isSupported()) {
return;
}
@@ -404,7 +404,7 @@
@Override
protected String getServiceState() {
- if (!isServiceSupported()) {
+ if (!isSupported()) {
return "unsupported";
} else {
return super.getServiceState();
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index 29da177..1df29ab 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -103,6 +103,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
@@ -372,7 +373,7 @@
public GnssLocationProvider(Context context, Injector injector, GnssNative gnssNative,
GnssMetrics gnssMetrics) {
super(FgThread.getExecutor(), CallerIdentity.fromContext(context), PROPERTIES,
- /*locationTags*/ null);
+ Collections.emptySet());
mContext = context;
mGnssNative = gnssNative;
diff --git a/services/core/java/com/android/server/location/gnss/GnssManagerService.java b/services/core/java/com/android/server/location/gnss/GnssManagerService.java
index 8312c63..bcdfed4 100644
--- a/services/core/java/com/android/server/location/gnss/GnssManagerService.java
+++ b/services/core/java/com/android/server/location/gnss/GnssManagerService.java
@@ -27,6 +27,7 @@
import android.location.GnssCapabilities;
import android.location.GnssMeasurementCorrections;
import android.location.GnssMeasurementRequest;
+import android.location.IGnssAntennaInfoListener;
import android.location.IGnssMeasurementsListener;
import android.location.IGnssNavigationMessageListener;
import android.location.IGnssNmeaListener;
@@ -49,7 +50,6 @@
import com.android.server.location.injector.Injector;
import java.io.FileDescriptor;
-import java.util.ArrayList;
import java.util.List;
/** Manages Gnss providers and related Gnss functions for LocationManagerService. */
@@ -68,11 +68,11 @@
private final GnssNmeaProvider mGnssNmeaProvider;
private final GnssMeasurementsProvider mGnssMeasurementsProvider;
private final GnssNavigationMessageProvider mGnssNavigationMessageProvider;
+ private final GnssAntennaInfoProvider mGnssAntennaInfoProvider;
private final IGpsGeofenceHardware mGnssGeofenceProxy;
private final GnssGeofenceHalModule mGeofenceHalModule;
private final GnssCapabilitiesHalModule mCapabilitiesHalModule;
- private final GnssAntennaInfoHalModule mAntennaInfoHalModule;
private final GnssMetrics mGnssMetrics;
@@ -89,11 +89,11 @@
mGnssNmeaProvider = new GnssNmeaProvider(injector, mGnssNative);
mGnssMeasurementsProvider = new GnssMeasurementsProvider(injector, mGnssNative);
mGnssNavigationMessageProvider = new GnssNavigationMessageProvider(injector, mGnssNative);
+ mGnssAntennaInfoProvider = new GnssAntennaInfoProvider(mGnssNative);
mGnssGeofenceProxy = new GnssGeofenceProxy(mGnssNative);
mGeofenceHalModule = new GnssGeofenceHalModule(mGnssNative);
mCapabilitiesHalModule = new GnssCapabilitiesHalModule(mGnssNative);
- mAntennaInfoHalModule = new GnssAntennaInfoHalModule(mGnssNative);
// allow gnss access to begin - we must assume that callbacks can start immediately
mGnssNative.register();
@@ -140,7 +140,7 @@
* Get GNSS antenna information.
*/
public @Nullable List<GnssAntennaInfo> getGnssAntennaInfos() {
- return mAntennaInfoHalModule.getAntennaInfos();
+ return mGnssAntennaInfoProvider.getAntennaInfos();
}
/**
@@ -242,6 +242,24 @@
}
/**
+ * Adds a GNSS antenna info listener.
+ */
+ public void addGnssAntennaInfoListener(IGnssAntennaInfoListener listener, String packageName,
+ @Nullable String attributionTag, String listenerId) {
+
+ CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag,
+ listenerId);
+ mGnssAntennaInfoProvider.addListener(identity, listener);
+ }
+
+ /**
+ * Removes a GNSS antenna info listener.
+ */
+ public void removeGnssAntennaInfoListener(IGnssAntennaInfoListener listener) {
+ mGnssAntennaInfoProvider.removeListener(listener);
+ }
+
+ /**
* Send Ni Response, indicating a location request initiated by a network carrier.
*/
public void sendNiResponse(int notifId, int userResponse) {
@@ -263,25 +281,34 @@
ipw.println("Capabilities: " + mGnssNative.getCapabilities());
- List<GnssAntennaInfo> infos = mAntennaInfoHalModule.getAntennaInfos();
- if (infos != null) {
- ipw.println("Antenna Infos: " + infos);
+ if (mGnssStatusProvider.isSupported()) {
+ ipw.println("Status Provider:");
+ ipw.increaseIndent();
+ mGnssStatusProvider.dump(fd, ipw, args);
+ ipw.decreaseIndent();
}
- ipw.println("Measurements Provider:");
- ipw.increaseIndent();
- mGnssMeasurementsProvider.dump(fd, ipw, args);
- ipw.decreaseIndent();
+ if (mGnssMeasurementsProvider.isSupported()) {
+ ipw.println("Measurements Provider:");
+ ipw.increaseIndent();
+ mGnssMeasurementsProvider.dump(fd, ipw, args);
+ ipw.decreaseIndent();
+ }
- ipw.println("Navigation Message Provider:");
- ipw.increaseIndent();
- mGnssNavigationMessageProvider.dump(fd, ipw, args);
- ipw.decreaseIndent();
+ if (mGnssNavigationMessageProvider.isSupported()) {
+ ipw.println("Navigation Message Provider:");
+ ipw.increaseIndent();
+ mGnssNavigationMessageProvider.dump(fd, ipw, args);
+ ipw.decreaseIndent();
+ }
- ipw.println("Status Provider:");
- ipw.increaseIndent();
- mGnssStatusProvider.dump(fd, ipw, args);
- ipw.decreaseIndent();
+ if (mGnssAntennaInfoProvider.isSupported()) {
+ ipw.println("Navigation Message Provider:");
+ ipw.increaseIndent();
+ ipw.println("Antenna Infos: " + mGnssAntennaInfoProvider.getAntennaInfos());
+ mGnssAntennaInfoProvider.dump(fd, ipw, args);
+ ipw.decreaseIndent();
+ }
GnssPowerStats powerStats = mGnssNative.getPowerStats();
if (powerStats != null) {
@@ -399,53 +426,4 @@
}
}
}
-
- private class GnssAntennaInfoHalModule implements GnssNative.BaseCallbacks,
- GnssNative.AntennaInfoCallbacks {
-
- private final GnssNative mGnssNative;
-
- private volatile @Nullable List<GnssAntennaInfo> mAntennaInfos;
-
- GnssAntennaInfoHalModule(GnssNative gnssNative) {
- mGnssNative = gnssNative;
- mGnssNative.addBaseCallbacks(this);
- mGnssNative.addAntennaInfoCallbacks(this);
- }
-
- @Nullable List<GnssAntennaInfo> getAntennaInfos() {
- return mAntennaInfos;
- }
-
- @Override
- public void onHalStarted() {
- mGnssNative.startAntennaInfoListening();
- }
-
- @Override
- public void onHalRestarted() {
- mGnssNative.startAntennaInfoListening();
- }
-
- @Override
- public void onReportAntennaInfo(List<GnssAntennaInfo> antennaInfos) {
- if (antennaInfos.equals(mAntennaInfos)) {
- return;
- }
-
- mAntennaInfos = antennaInfos;
-
- long ident = Binder.clearCallingIdentity();
- try {
- Intent intent = new Intent(LocationManager.ACTION_GNSS_ANTENNA_INFOS_CHANGED)
- .putParcelableArrayListExtra(LocationManager.EXTRA_GNSS_ANTENNA_INFOS,
- new ArrayList<>(antennaInfos))
- .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- }
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
index 305bc9b..b3119d7 100644
--- a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
@@ -99,7 +99,7 @@
}
@Override
- protected boolean isServiceSupported() {
+ public boolean isSupported() {
return mGnssNative.isMeasurementSupported();
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java b/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java
index ff9ad65..e9fce05 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java
@@ -70,7 +70,7 @@
}
@Override
- protected boolean isServiceSupported() {
+ public boolean isSupported() {
return mGnssNative.isNavigationMessageCollectionSupported();
}
diff --git a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
index 7e2f089..f275663 100644
--- a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
+++ b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
@@ -699,11 +699,11 @@
}
/**
- * Returns true if antenna info listening is supported.
+ * Returns true if antenna info is supported.
*/
- public boolean isAntennaInfoListeningSupported() {
+ public boolean isAntennaInfoSupported() {
Preconditions.checkState(mRegistered);
- return mGnssHal.isAntennaInfoListeningSupported();
+ return mGnssHal.isAntennaInfoSupported();
}
/**
@@ -1259,7 +1259,7 @@
return native_stop_navigation_message_collection();
}
- protected boolean isAntennaInfoListeningSupported() {
+ protected boolean isAntennaInfoSupported() {
return native_is_antenna_info_supported();
}
diff --git a/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java b/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java
index 9ff6e6b..ba7f44f 100644
--- a/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java
@@ -28,6 +28,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.Collections;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
@@ -66,9 +67,10 @@
/**
* Default state value for a location provider that is disabled with no properties and an
- * empty provider package list.
+ * empty extra attribution tag set.
*/
- public static final State EMPTY_STATE = new State(false, null, null, null);
+ public static final State EMPTY_STATE = new State(false, null, null,
+ Collections.emptySet());
/**
* The provider's allowed state.
@@ -85,14 +87,18 @@
*/
@Nullable public final CallerIdentity identity;
- @Nullable public final Set<String> locationTags;
+ /**
+ * A set of attribution tags also associated with this provider - these attribution tags may
+ * be afforded special privileges.
+ */
+ public final Set<String> extraAttributionTags;
private State(boolean allowed, ProviderProperties properties, CallerIdentity identity,
- Set<String> locationTags) {
+ Set<String> extraAttributionTags) {
this.allowed = allowed;
this.properties = properties;
this.identity = identity;
- this.locationTags = locationTags;
+ this.extraAttributionTags = Objects.requireNonNull(extraAttributionTags);
}
/**
@@ -102,7 +108,7 @@
if (allowed == this.allowed) {
return this;
} else {
- return new State(allowed, properties, identity, locationTags);
+ return new State(allowed, properties, identity, extraAttributionTags);
}
}
@@ -113,7 +119,7 @@
if (Objects.equals(properties, this.properties)) {
return this;
} else {
- return new State(allowed, properties, identity, locationTags);
+ return new State(allowed, properties, identity, extraAttributionTags);
}
}
@@ -124,18 +130,18 @@
if (Objects.equals(identity, this.identity)) {
return this;
} else {
- return new State(allowed, properties, identity, locationTags);
+ return new State(allowed, properties, identity, extraAttributionTags);
}
}
/**
- * Returns a state the same as the current but with location tags set as specified.
+ * Returns a state the same as the current but with extra attribution tags set as specified.
*/
- public State withLocationTags(@Nullable Set<String> locationTags) {
- if (Objects.equals(locationTags, this.locationTags)) {
+ public State withExtraAttributionTags(Set<String> extraAttributionTags) {
+ if (extraAttributionTags.equals(this.extraAttributionTags)) {
return this;
} else {
- return new State(allowed, properties, identity, locationTags);
+ return new State(allowed, properties, identity, extraAttributionTags);
}
}
@@ -151,12 +157,12 @@
State state = (State) o;
return allowed == state.allowed && properties == state.properties
&& Objects.equals(identity, state.identity)
- && Objects.equals(locationTags, state.locationTags);
+ && extraAttributionTags.equals(state.extraAttributionTags);
}
@Override
public int hashCode() {
- return Objects.hash(allowed, properties, identity, locationTags);
+ return Objects.hash(allowed, properties, identity, extraAttributionTags);
}
}
@@ -213,14 +219,14 @@
* An optional identity and properties may be provided to initialize the location provider.
*/
protected AbstractLocationProvider(Executor executor, @Nullable CallerIdentity identity,
- @Nullable ProviderProperties properties, @Nullable Set<String> locationTags) {
+ @Nullable ProviderProperties properties, Set<String> extraAttributionTags) {
Preconditions.checkArgument(identity == null || identity.getListenerId() == null);
- mExecutor = executor;
+ mExecutor = Objects.requireNonNull(executor);
mInternalState = new AtomicReference<>(new InternalState(null,
State.EMPTY_STATE
.withIdentity(identity)
- .withProperties(properties).withLocationTags(locationTags))
- );
+ .withProperties(properties)
+ .withExtraAttributionTags(extraAttributionTags)));
mController = new Controller();
}
@@ -292,7 +298,7 @@
}
/**
- * Call this method to report a change in provider packages.
+ * Call this method to report a change in the provider's identity.
*/
protected void setIdentity(@Nullable CallerIdentity identity) {
Preconditions.checkArgument(identity == null || identity.getListenerId() == null);
@@ -300,10 +306,10 @@
}
/**
- * Call this method to report a change in provider location tags.
+ * Call this method to report a change in the provider's extra attribution tags.
*/
- protected void setLocationTags(@Nullable Set<String> locationTags) {
- setState(state -> state.withLocationTags(locationTags));
+ protected void setExtraAttributionTags(Set<String> extraAttributionTags) {
+ setState(state -> state.withExtraAttributionTags(extraAttributionTags));
}
/**
diff --git a/services/core/java/com/android/server/location/provider/DelegateLocationProvider.java b/services/core/java/com/android/server/location/provider/DelegateLocationProvider.java
index 49f6e64..423f833 100644
--- a/services/core/java/com/android/server/location/provider/DelegateLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/DelegateLocationProvider.java
@@ -24,6 +24,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.Collections;
import java.util.concurrent.Executor;
/**
@@ -40,7 +41,7 @@
private boolean mInitialized = false;
DelegateLocationProvider(Executor executor, AbstractLocationProvider delegate) {
- super(executor, null, null, null);
+ super(executor, null, null, Collections.emptySet());
mDelegate = delegate;
}
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 1ecf3ee..fef30f9 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -2265,8 +2265,8 @@
onEnabledChanged(UserHandle.USER_ALL);
}
- if (!Objects.equals(oldState.locationTags, newState.locationTags)) {
- if (mOnLocationTagsChangeListener != null) {
+ if (mOnLocationTagsChangeListener != null) {
+ if (!oldState.extraAttributionTags.equals(newState.extraAttributionTags)) {
if (oldState.identity != null) {
FgThread.getHandler().sendMessage(PooledLambda.obtainMessage(
OnProviderLocationTagsChangeListener::onLocationTagsChanged,
@@ -2280,7 +2280,7 @@
OnProviderLocationTagsChangeListener::onLocationTagsChanged,
mOnLocationTagsChangeListener, new LocationTagInfo(
newState.identity.getUid(), newState.identity.getPackageName(),
- newState.locationTags)
+ newState.extraAttributionTags)
));
}
}
diff --git a/services/core/java/com/android/server/location/provider/MockLocationProvider.java b/services/core/java/com/android/server/location/provider/MockLocationProvider.java
index 7660f56..52b04d4 100644
--- a/services/core/java/com/android/server/location/provider/MockLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/MockLocationProvider.java
@@ -40,9 +40,9 @@
@Nullable private Location mLocation;
public MockLocationProvider(ProviderProperties properties, CallerIdentity identity,
- @Nullable Set<String> locationTags) {
+ Set<String> extraAttributionTags) {
// using a direct executor is ok because this class has no locks that could deadlock
- super(DIRECT_EXECUTOR, identity, properties, locationTags);
+ super(DIRECT_EXECUTOR, identity, properties, extraAttributionTags);
}
/** Sets the allowed state of this mock provider. */
diff --git a/services/core/java/com/android/server/location/provider/MockableLocationProvider.java b/services/core/java/com/android/server/location/provider/MockableLocationProvider.java
index 4ffa9a5..8193644 100644
--- a/services/core/java/com/android/server/location/provider/MockableLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/MockableLocationProvider.java
@@ -31,6 +31,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.Collections;
/**
* Represents a location provider that may switch between a mock implementation and a real
@@ -75,7 +76,7 @@
public MockableLocationProvider(Object ownerLock) {
// using a direct executor is acceptable because all inbound calls are delegated to the
// actual provider implementations which will use their own executors
- super(DIRECT_EXECUTOR, null, null, null);
+ super(DIRECT_EXECUTOR, null, null, Collections.emptySet());
mOwnerLock = ownerLock;
mRequest = ProviderRequest.EMPTY_REQUEST;
}
diff --git a/services/core/java/com/android/server/location/provider/PassiveLocationProvider.java b/services/core/java/com/android/server/location/provider/PassiveLocationProvider.java
index ee9d35d..68ede88 100644
--- a/services/core/java/com/android/server/location/provider/PassiveLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/PassiveLocationProvider.java
@@ -30,6 +30,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.Collections;
/**
* A passive location provider reports locations received from other providers
@@ -48,7 +49,7 @@
public PassiveLocationProvider(Context context) {
// using a direct executor is ok because this class has no locks that could deadlock
super(DIRECT_EXECUTOR, CallerIdentity.fromContext(context), PROPERTIES,
- /*locationTags*/ null);
+ Collections.emptySet());
setAllowed(true);
}
diff --git a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
index f00478a..44b62b3 100644
--- a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
@@ -18,7 +18,6 @@
import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
@@ -44,6 +43,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Objects;
@@ -52,8 +52,8 @@
*/
public class ProxyLocationProvider extends AbstractLocationProvider {
- private static final String KEY_LOCATION_TAGS = "android:location_allow_listed_tags";
- private static final String LOCATION_TAGS_SEPARATOR = ";";
+ private static final String KEY_EXTRA_ATTRIBUTION_TAGS = "android:location_allow_listed_tags";
+ private static final String EXTRA_ATTRIBUTION_TAGS_SEPARATOR = ";";
/**
* Creates and registers this proxy. If no suitable service is available for the proxy, returns
@@ -90,7 +90,7 @@
int nonOverlayPackageResId) {
// safe to use direct executor since our locks are not acquired in a code path invoked by
// our owning provider
- super(DIRECT_EXECUTOR, null, null, null);
+ super(DIRECT_EXECUTOR, null, null, Collections.emptySet());
mContext = context;
mServiceWatcher = new ServiceWatcher(context, action, this::onBind,
@@ -100,16 +100,6 @@
mRequest = ProviderRequest.EMPTY_REQUEST;
}
- private void updateLocationTagInfo(@NonNull BoundService boundService) {
- if (boundService.metadata != null) {
- final String tagsList = boundService.metadata.getString(KEY_LOCATION_TAGS);
- if (tagsList != null) {
- final String[] tags = tagsList.split(LOCATION_TAGS_SEPARATOR);
- setLocationTags(new ArraySet<>(tags));
- }
- }
- }
-
private boolean checkServiceResolves() {
return mServiceWatcher.checkServiceResolves();
}
@@ -120,14 +110,22 @@
synchronized (mLock) {
mProxy = new Proxy();
mService = boundService.component;
+
+ // update extra attribution tag info from manifest
+ if (boundService.metadata != null) {
+ String tagsList = boundService.metadata.getString(KEY_EXTRA_ATTRIBUTION_TAGS);
+ if (tagsList != null) {
+ setExtraAttributionTags(
+ new ArraySet<>(tagsList.split(EXTRA_ATTRIBUTION_TAGS_SEPARATOR)));
+ }
+ }
+
provider.setLocationProviderManager(mProxy);
ProviderRequest request = mRequest;
if (!request.equals(ProviderRequest.EMPTY_REQUEST)) {
provider.setRequest(request);
}
-
- updateLocationTagInfo(boundService);
}
}
diff --git a/services/core/java/com/android/server/locksettings/OWNERS b/services/core/java/com/android/server/locksettings/OWNERS
index 08b8a8c..7577ee5 100644
--- a/services/core/java/com/android/server/locksettings/OWNERS
+++ b/services/core/java/com/android/server/locksettings/OWNERS
@@ -1,3 +1,4 @@
jaggies@google.com
kchyn@google.com
rubinxu@google.com
+xunchang@google.com
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index f8cb2e4..274344a 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -64,6 +64,8 @@
import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
+import static android.os.PowerWhitelistManager.REASON_NOTIFICATION_SERVICE;
+import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
import static android.os.UserHandle.USER_NULL;
import static android.os.UserHandle.USER_SYSTEM;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
@@ -6001,10 +6003,11 @@
for (int i = 0; i < intentCount; i++) {
PendingIntent pendingIntent = notification.allPendingIntents.valueAt(i);
if (pendingIntent != null) {
- am.setPendingIntentWhitelistDuration(pendingIntent.getTarget(),
+ am.setPendingIntentAllowlistDuration(pendingIntent.getTarget(),
ALLOWLIST_TOKEN, duration,
- BroadcastOptions.TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED
- );
+ TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
+ REASON_NOTIFICATION_SERVICE,
+ "NotificationManagerService");
am.setPendingIntentAllowBgActivityStarts(pendingIntent.getTarget(),
ALLOWLIST_TOKEN, (FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER
| FLAG_SERVICE_SENDER));
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 2812830..a5e28f1 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -530,14 +530,6 @@
throw new SecurityException("User restriction prevents installing");
}
- if (params.dataLoaderParams != null
- && mContext.checkCallingOrSelfPermission(Manifest.permission.USE_INSTALLER_V2)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("You need the "
- + "com.android.permission.USE_INSTALLER_V2 permission "
- + "to use a data loader");
- }
-
// INSTALL_REASON_ROLLBACK allows an app to be rolled back without requiring the ROLLBACK
// capability; ensure if this is set as the install reason the app has one of the necessary
// signature permissions to perform the rollback.
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 7bf3c5c..460b2f2 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -3547,14 +3547,12 @@
@Override
public DataLoaderParamsParcel getDataLoaderParams() {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.USE_INSTALLER_V2, null);
return params.dataLoaderParams != null ? params.dataLoaderParams.getData() : null;
}
@Override
public void addFile(int location, String name, long lengthBytes, byte[] metadata,
byte[] signature) {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.USE_INSTALLER_V2, null);
if (!isDataLoaderInstallation()) {
throw new IllegalStateException(
"Cannot add files to non-data loader installation session.");
@@ -3587,7 +3585,6 @@
@Override
public void removeFile(int location, String name) {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.USE_INSTALLER_V2, null);
if (!isDataLoaderInstallation()) {
throw new IllegalStateException(
"Cannot add files to non-data loader installation session.");
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 8123951..a4c0655 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2604,10 +2604,22 @@
final ArrayList<ResolveInfo> matchAllList = new ArrayList<>();
final ArrayList<ResolveInfo> undefinedList = new ArrayList<>();
+ // Blocking instant apps is usually done in applyPostResolutionFilter, but since
+ // domain verification can resolve to a single result, which can be an instant app,
+ // it will then be filtered to an empty list in that method. Instead, do blocking
+ // here so that instant apps can be ignored for approval filtering and a lower
+ // priority result chosen instead.
+ final boolean blockInstant = intent.isWebIntent() && areWebInstantAppsDisabled(userId);
+
final int count = candidates.size();
// First, try to use approved apps.
for (int n = 0; n < count; n++) {
ResolveInfo info = candidates.get(n);
+ if (blockInstant && (info.isInstantAppAvailable
+ || isInstantApp(info.activityInfo.packageName, userId))) {
+ continue;
+ }
+
// Add to the special match all list (Browser use case)
if (info.handleAllWebDataURI) {
matchAllList.add(info);
@@ -5092,23 +5104,12 @@
InstallArgs args = data.args;
PackageInstalledInfo parentRes = data.res;
- final boolean grantPermissions = (args.installFlags
- & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0;
final boolean killApp = (args.installFlags
& PackageManager.INSTALL_DONT_KILL_APP) == 0;
final boolean virtualPreload = ((args.installFlags
& PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0);
- final String[] grantedPermissions = args.installGrantPermissions;
- final List<String> whitelistedRestrictedPermissions = ((args.installFlags
- & PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS) != 0
- && parentRes.pkg != null)
- ? parentRes.pkg.getRequestedPermissions()
- : args.whitelistedRestrictedPermissions;
- int autoRevokePermissionsMode = args.autoRevokePermissionsMode;
- handlePackagePostInstall(parentRes, grantPermissions,
- killApp, virtualPreload, grantedPermissions,
- whitelistedRestrictedPermissions, autoRevokePermissionsMode,
+ handlePackagePostInstall(parentRes, killApp, virtualPreload,
didRestore, args.installSource.installerPackageName, args.observer,
args.mDataLoaderType);
@@ -5387,11 +5388,8 @@
}
}
- private void handlePackagePostInstall(PackageInstalledInfo res, boolean grantPermissions,
- boolean killApp, boolean virtualPreload,
- String[] grantedPermissions, List<String> allowlistedRestrictedPermissions,
- int autoRevokePermissionsMode,
- boolean launchedForRestore, String installerPackage,
+ private void handlePackagePostInstall(PackageInstalledInfo res, boolean killApp,
+ boolean virtualPreload, boolean launchedForRestore, String installerPackage,
IPackageInstallObserver2 installObserver, int dataLoaderType) {
boolean succeeded = res.returnCode == PackageManager.INSTALL_SUCCEEDED;
final boolean update = res.removedInfo != null && res.removedInfo.removedPackage != null;
@@ -5422,29 +5420,6 @@
res.removedInfo.sendPackageRemovedBroadcasts(killApp, false /*removedBySystem*/);
}
- final PermissionManagerServiceInternal.PackageInstalledParams.Builder
- permissionParamsBuilder =
- new PermissionManagerServiceInternal.PackageInstalledParams.Builder();
- final List<String> grantedPermissionsList;
- if (grantPermissions) {
- if (grantedPermissions != null) {
- permissionParamsBuilder.setGrantedPermissions(Arrays.asList(
- grantedPermissions));
- } else {
- permissionParamsBuilder.setGrantedPermissions(
- res.pkg.getRequestedPermissions());
- }
- }
- if (allowlistedRestrictedPermissions != null) {
- permissionParamsBuilder.setAllowlistedRestrictedPermissions(
- allowlistedRestrictedPermissions);
- }
- permissionParamsBuilder.setAutoRevokePermissionsMode(autoRevokePermissionsMode);
- for (final int userId : res.newUsers) {
- mPermissionManager.onPackageInstalled(res.pkg, permissionParamsBuilder.build(),
- userId);
- }
-
final String installerPackageName =
res.installerPackageName != null
? res.installerPackageName
@@ -18445,6 +18420,37 @@
}
mSettings.writeKernelMappingLPr(ps);
+
+ final PermissionManagerServiceInternal.PackageInstalledParams.Builder
+ permissionParamsBuilder =
+ new PermissionManagerServiceInternal.PackageInstalledParams.Builder();
+ final boolean grantPermissions = (installArgs.installFlags
+ & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0;
+ if (grantPermissions) {
+ final List<String> grantedPermissions =
+ installArgs.installGrantPermissions != null
+ ? Arrays.asList(installArgs.installGrantPermissions)
+ : pkg.getRequestedPermissions();
+ permissionParamsBuilder.setGrantedPermissions(grantedPermissions);
+ }
+ final boolean allowlistAllRestrictedPermissions =
+ (installArgs.installFlags
+ & PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS) != 0;
+ final List<String> allowlistedRestrictedPermissions =
+ allowlistAllRestrictedPermissions ? pkg.getRequestedPermissions()
+ : installArgs.whitelistedRestrictedPermissions;
+ if (allowlistedRestrictedPermissions != null) {
+ permissionParamsBuilder.setAllowlistedRestrictedPermissions(
+ allowlistedRestrictedPermissions);
+ }
+ final int autoRevokePermissionsMode = installArgs.autoRevokePermissionsMode;
+ permissionParamsBuilder.setAutoRevokePermissionsMode(autoRevokePermissionsMode);
+ for (int currentUserId : allUsersList) {
+ if (ps.getInstalled(currentUserId)) {
+ mPermissionManager.onPackageInstalled(pkg, permissionParamsBuilder.build(),
+ currentUserId);
+ }
+ }
}
res.name = pkgName;
res.uid = pkg.getUid();
@@ -22673,7 +22679,7 @@
}
final Intent intent = getHomeIntent();
final List<ResolveInfo> resolveInfos = queryIntentActivitiesInternal(intent, null,
- PackageManager.GET_META_DATA, userId);
+ MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userId);
final ResolveInfo preferredResolveInfo = findPreferredActivityNotLocked(
intent, null, 0, resolveInfos, 0, true, false, false, userId);
final String packageName = preferredResolveInfo != null
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index a83a3f8..38e100e 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -787,6 +787,10 @@
return firstInstallTime;
}
+ public String getName() {
+ return name;
+ }
+
protected PackageSettingBase updateFrom(PackageSettingBase other) {
super.copyFrom(other);
setPath(other.getPath());
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index bb4ec16..a604afc 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -19,15 +19,22 @@
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.Person;
+import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.AppSearchSession;
+import android.app.appsearch.PackageIdentifier;
+import android.app.appsearch.SetSchemaRequest;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.LocusId;
+import android.content.pm.AppSearchPerson;
+import android.content.pm.AppSearchShortcutInfo;
import android.content.pm.PackageInfo;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.content.res.Resources;
import android.graphics.drawable.Icon;
+import android.os.Binder;
import android.os.PersistableBundle;
import android.text.format.Formatter;
import android.util.ArrayMap;
@@ -39,9 +46,11 @@
import android.util.TypedXmlSerializer;
import android.util.Xml;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
+import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import com.android.server.pm.ShortcutService.DumpFilter;
@@ -64,8 +73,12 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.function.Consumer;
+import java.util.function.Function;
import java.util.function.Predicate;
/**
@@ -155,6 +168,16 @@
private long mLastKnownForegroundElapsedTime;
+ private final Object mLock = new Object();
+
+ /**
+ * All external packages that have gained access to the shortcuts from this package
+ */
+ private final Map<String, PackageIdentifier> mPackageIdentifiers = new ArrayMap<>(0);
+
+ @GuardedBy("mLock")
+ private AppSearchSession mAppSearchSession;
+
private ShortcutPackage(ShortcutUser shortcutUser,
int packageUserId, String packageName, ShortcutPackageInfo spi) {
super(shortcutUser, packageUserId, packageName,
@@ -2140,6 +2163,15 @@
}
}
+ void updateVisibility(String packageName, byte[] certificate, boolean visible) {
+ if (visible) {
+ mPackageIdentifiers.put(packageName, new PackageIdentifier(packageName, certificate));
+ } else {
+ mPackageIdentifiers.remove(packageName);
+ }
+ resetAppSearch(null);
+ }
+
private boolean verifyRanksSequential(List<ShortcutInfo> list) {
boolean failed = false;
@@ -2153,4 +2185,128 @@
}
return failed;
}
+
+ private void runInAppSearch(
+ Function<SearchSessionObservable, Consumer<AppSearchSession>>... observers) {
+ if (mShortcutUser == null) {
+ Slog.w(TAG, "shortcut user is null");
+ return;
+ }
+ synchronized (mLock) {
+ if (mAppSearchSession != null) {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ final SearchSessionObservable upstream =
+ new SearchSessionObservable(mAppSearchSession, latch);
+ for (Function<SearchSessionObservable, Consumer<AppSearchSession>> observer
+ : observers) {
+ upstream.map(observer);
+ }
+ upstream.next();
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ ConcurrentUtils.waitForCountDownNoInterrupt(latch, 500,
+ "timeout accessing shortcut");
+ } else {
+ resetAppSearch(observers);
+ }
+ }
+ }
+
+ private void resetAppSearch(
+ Function<SearchSessionObservable, Consumer<AppSearchSession>>... observers) {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final AppSearchManager.SearchContext searchContext =
+ new AppSearchManager.SearchContext.Builder()
+ .setDatabaseName(getPackageName()).build();
+ mShortcutUser.runInAppSearch(searchContext, result -> {
+ if (!result.isSuccess()) {
+ Slog.e(TAG, "error getting search session during lazy init, "
+ + result.getErrorMessage());
+ latch.countDown();
+ return;
+ }
+ // TODO: Flatten callback chain with proper async framework
+ final SearchSessionObservable upstream =
+ new SearchSessionObservable(result.getResultValue(), latch)
+ .map(this::setupSchema);
+ if (observers != null) {
+ for (Function<SearchSessionObservable, Consumer<AppSearchSession>> observer
+ : observers) {
+ upstream.map(observer);
+ }
+ }
+ upstream.map(observable -> session -> {
+ mAppSearchSession = session;
+ observable.next();
+ });
+ upstream.next();
+ });
+ ConcurrentUtils.waitForCountDownNoInterrupt(latch, 1500,
+ "timeout accessing shortcut during lazy initialization");
+ }
+
+ /**
+ * creates the schema for shortcut in the database
+ */
+ private Consumer<AppSearchSession> setupSchema(SearchSessionObservable observable) {
+ return session -> {
+ SetSchemaRequest.Builder schemaBuilder = new SetSchemaRequest.Builder()
+ .addSchemas(AppSearchPerson.SCHEMA, AppSearchShortcutInfo.SCHEMA);
+ for (PackageIdentifier pi : mPackageIdentifiers.values()) {
+ schemaBuilder = schemaBuilder
+ .setSchemaTypeVisibilityForPackage(
+ AppSearchPerson.SCHEMA_TYPE, true, pi)
+ .setSchemaTypeVisibilityForPackage(
+ AppSearchShortcutInfo.SCHEMA_TYPE, true, pi);
+ }
+ session.setSchema(schemaBuilder.build(), mShortcutUser.mExecutor, result -> {
+ if (!result.isSuccess()) {
+ observable.error("failed to instantiate app search schema: "
+ + result.getErrorMessage());
+ return;
+ }
+ observable.next();
+ });
+ };
+ }
+
+ /**
+ * TODO: Replace this temporary implementation with proper async framework
+ */
+ private class SearchSessionObservable {
+
+ final AppSearchSession mSession;
+ final CountDownLatch mLatch;
+ final ArrayList<Consumer<AppSearchSession>> mObservers = new ArrayList<>(1);
+
+ SearchSessionObservable(@NonNull final AppSearchSession session,
+ @NonNull final CountDownLatch latch) {
+ mSession = session;
+ mLatch = latch;
+ }
+
+ SearchSessionObservable map(
+ Function<SearchSessionObservable, Consumer<AppSearchSession>> observer) {
+ mObservers.add(observer.apply(this));
+ return this;
+ }
+
+ void next() {
+ if (mObservers.isEmpty()) {
+ mLatch.countDown();
+ return;
+ }
+ mObservers.remove(0).accept(mSession);
+ }
+
+ void error(@Nullable final String errorMessage) {
+ if (errorMessage != null) {
+ Slog.e(TAG, errorMessage);
+ }
+ mLatch.countDown();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 4d8abea..209a143 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -2150,6 +2150,15 @@
}
@Override
+ public void updateShortcutVisibility(String callingPkg, String packageName, byte[] certificate,
+ boolean visible, int userId) {
+ synchronized (mLock) {
+ getPackageShortcutsForPublisherLocked(callingPkg, userId)
+ .updateVisibility(packageName, certificate, visible);
+ }
+ }
+
+ @Override
public boolean requestPinShortcut(String packageName, ShortcutInfo shortcut,
IntentSender resultIntent, int userId) {
Objects.requireNonNull(shortcut);
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index 3e3aa67..6cbc47f 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -18,9 +18,14 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.AppSearchSession;
import android.content.pm.ShortcutManager;
import android.metrics.LogMaker;
+import android.os.Binder;
import android.os.FileUtils;
+import android.os.UserHandle;
import android.text.TextUtils;
import android.text.format.Formatter;
import android.util.ArrayMap;
@@ -32,6 +37,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.server.FgThread;
import com.android.server.pm.ShortcutService.DumpFilter;
import com.android.server.pm.ShortcutService.InvalidFileFormatException;
@@ -45,6 +51,7 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Objects;
+import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
@@ -111,6 +118,8 @@
}
final ShortcutService mService;
+ final AppSearchManager mAppSearchManager;
+ final Executor mExecutor;
@UserIdInt
private final int mUserId;
@@ -132,6 +141,9 @@
public ShortcutUser(ShortcutService service, int userId) {
mService = service;
mUserId = userId;
+ mAppSearchManager = service.mContext.createContextAsUser(UserHandle.of(userId), 0)
+ .getSystemService(AppSearchManager.class);
+ mExecutor = FgThread.getExecutor();
}
public int getUserId() {
@@ -693,4 +705,18 @@
logger.write(logMaker.setType(MetricsEvent.SHORTCUTS_CHANGED_SHORTCUT_COUNT)
.setSubtype(totalSharingShortcutCount));
}
+
+ void runInAppSearch(@NonNull final AppSearchManager.SearchContext searchContext,
+ @NonNull final Consumer<AppSearchResult<AppSearchSession>> callback) {
+ if (mAppSearchManager == null) {
+ Slog.e(TAG, "app search manager is null");
+ return;
+ }
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ mAppSearchManager.createSearchSession(searchContext, mExecutor, callback);
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 24c27be..c75dd27 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -983,7 +983,11 @@
@Override
public UserInfo getProfileParent(@UserIdInt int userId) {
- checkManageUsersPermission("get the profile parent");
+ if (!hasManageUsersOrPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)) {
+ throw new SecurityException(
+ "You need MANAGE_USERS or INTERACT_ACROSS_USERS permission to get the "
+ + "profile parent");
+ }
synchronized (mUsersLock) {
return getProfileParentLU(userId);
}
@@ -1531,11 +1535,14 @@
@Override
public String getUserName() {
- if (!hasManageUsersOrPermission(android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED)) {
- throw new SecurityException("You need MANAGE_USERS or GET_ACCOUNTS_PRIVILEGED "
- + "permissions to: get user name");
+ final int callingUid = Binder.getCallingUid();
+ if (!hasManageOrCreateUsersPermission()
+ || hasPermissionGranted(
+ android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED, callingUid)) {
+ throw new SecurityException("You need MANAGE_USERS or CREATE_USERS or "
+ + "GET_ACCOUNTS_PRIVILEGED permissions to: get user name");
}
- final int userId = UserHandle.getUserId(Binder.getCallingUid());
+ final int userId = UserHandle.getUserId(callingUid);
synchronized (mUsersLock) {
UserInfo userInfo = userWithName(getUserInfoLU(userId));
return userInfo == null ? "" : userInfo.name;
@@ -3287,7 +3294,7 @@
* as well as for {@link UserManager#USER_TYPE_FULL_RESTRICTED}.
*/
@Override
- public UserInfo createProfileForUserWithThrow(String name, @NonNull String userType,
+ public UserInfo createProfileForUserWithThrow(@Nullable String name, @NonNull String userType,
@UserInfoFlag int flags, @UserIdInt int userId, @Nullable String[] disallowedPackages)
throws ServiceSpecificException {
checkManageOrCreateUsersPermission(flags);
@@ -3868,7 +3875,7 @@
* @hide
*/
@Override
- public UserInfo createRestrictedProfileWithThrow(String name, int parentUserId) {
+ public UserInfo createRestrictedProfileWithThrow(@Nullable String name, int parentUserId) {
checkManageOrCreateUsersPermission("setupRestrictedProfile");
final UserInfo user = createProfileForUserWithThrow(
name, UserManager.USER_TYPE_FULL_RESTRICTED, 0, parentUserId, null);
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 44a2187..e3ccb75 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -1277,12 +1277,7 @@
newFlags |= (flags & PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT);
// If we are allowlisting the permission, update the exempt flag before grant.
- // If the permission can't be allowlisted by an installer, skip it here because
- // this is where the platform takes the role of the installer for exempting
- // preinstalled apps.
- if (whitelistRestrictedPermissions && pm.isPermissionRestricted(permission)
- && !pm.getPermissionInfo(permission).isInstallerExemptIgnored()) {
-
+ if (whitelistRestrictedPermissions && pm.isPermissionRestricted(permission)) {
pm.updatePermissionFlags(permission, pkg,
PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT,
PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT, user);
diff --git a/services/core/java/com/android/server/pm/permission/Permission.java b/services/core/java/com/android/server/pm/permission/Permission.java
index 32bee58..ac50f29 100644
--- a/services/core/java/com/android/server/pm/permission/Permission.java
+++ b/services/core/java/com/android/server/pm/permission/Permission.java
@@ -239,10 +239,6 @@
return (mPermissionInfo.flags & PermissionInfo.FLAG_IMMUTABLY_RESTRICTED) != 0;
}
- public boolean isInstallerExemptIgnored() {
- return (mPermissionInfo.flags & PermissionInfo.FLAG_INSTALLER_EXEMPT_IGNORED) != 0;
- }
-
public boolean isSignature() {
return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
== PermissionInfo.PROTECTION_SIGNATURE;
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 7a936ec..2dfb6ff 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -24,14 +24,12 @@
import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISALLOWED;
import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISCOURAGED;
import static android.content.pm.PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE;
import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION;
import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE;
import static android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME;
import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
@@ -771,10 +769,6 @@
isRuntimePermission = bp.isRuntime();
- if (bp.isInstallerExemptIgnored()) {
- flagValues &= ~FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
- }
-
final UidPermissionState uidState = getUidStateLocked(pkg, userId);
if (uidState == null) {
Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId);
@@ -1018,8 +1012,7 @@
Preconditions.checkFlagsArgument(flags,
PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
| PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM
- | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER
- | PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE);
+ | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER);
Preconditions.checkArgumentNonNegative(userId, null);
if (UserHandle.getCallingUserId() != userId) {
@@ -1043,9 +1036,9 @@
final boolean isCallerInstallerOnRecord =
mPackageManagerInt.isCallerInstallerOfRecord(pkg, callingUid);
- if ((flags & (PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM
- | PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE)) != 0 && !isCallerPrivileged) {
- throw new SecurityException("Querying system or role allowlist requires "
+ if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) != 0
+ && !isCallerPrivileged) {
+ throw new SecurityException("Querying system allowlist requires "
+ Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
}
@@ -1087,9 +1080,6 @@
if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER) != 0) {
queryFlags |= FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
}
- if ((flags & PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE) != 0) {
- queryFlags |= FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT;
- }
ArrayList<String> allowlistedPermissions = null;
@@ -1182,8 +1172,7 @@
Preconditions.checkFlagsArgument(flags,
PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
| PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM
- | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER
- | PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE);
+ | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER);
Preconditions.checkArgument(Integer.bitCount(flags) == 1);
Preconditions.checkArgumentNonNegative(userId, null);
@@ -1209,10 +1198,8 @@
final boolean isCallerInstallerOnRecord =
mPackageManagerInt.isCallerInstallerOfRecord(pkg, callingUid);
- if ((flags & (PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM
- | PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE)) != 0
- && !isCallerPrivileged) {
- throw new SecurityException("Modifying system or role allowlist requires "
+ if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) != 0 && !isCallerPrivileged) {
+ throw new SecurityException("Modifying system allowlist requires "
+ Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
}
@@ -3718,15 +3705,6 @@
}
}
break;
- case FLAG_PERMISSION_ALLOWLIST_ROLE: {
- mask |= FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT;
- if (permissions != null && permissions.contains(permissionName)) {
- newFlags |= FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT;
- } else {
- newFlags &= ~FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT;
- }
- }
- break;
}
}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java
index 080de73..e3cf67c 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java
@@ -42,6 +42,8 @@
private static final Pattern DOMAIN_NAME_WITH_WILDCARD =
Pattern.compile("(\\*\\.)?" + Patterns.DOMAIN_NAME.pattern());
+ private static final int MAX_DOMAINS_BYTE_SIZE = 1024 * 1024;
+
@NonNull
private final PlatformCompat mPlatformCompat;
@@ -71,7 +73,7 @@
* <li>- Only IntentFilter.SCHEME_HTTP and/or IntentFilter.SCHEME_HTTPS,
* with no other schemes</li>
* </ul>
- *
+ * <p>
* On prior versions of Android, Intent.CATEGORY_BROWSABLE was not a requirement, other
* schemes were allowed, and setting autoVerify to true in any intent filter would implicitly
* pretend that all intent filters were set to autoVerify="true".
@@ -86,8 +88,8 @@
}
/**
- * Effectively {@link #collectAllWebDomains(AndroidPackage)}, but requires
- * {@link IntentFilter#getAutoVerify()} == true.
+ * Effectively {@link #collectAllWebDomains(AndroidPackage)}, but requires {@link
+ * IntentFilter#getAutoVerify()} == true.
*/
@NonNull
public ArraySet<String> collectAutoVerifyDomains(@NonNull AndroidPackage pkg) {
@@ -100,24 +102,21 @@
boolean restrictDomains =
DomainVerificationUtils.isChangeEnabled(mPlatformCompat, pkg, RESTRICT_DOMAINS);
- ArraySet<String> domains = new ArraySet<>();
-
if (restrictDomains) {
- collectDomains(domains, pkg, checkAutoVerify);
+ return collectDomainsInternal(pkg, checkAutoVerify);
} else {
- collectDomainsLegacy(domains, pkg, checkAutoVerify);
+ return collectDomainsLegacy(pkg, checkAutoVerify);
}
-
- return domains;
}
- /** @see #RESTRICT_DOMAINS */
- private void collectDomainsLegacy(@NonNull Set<String> domains,
- @NonNull AndroidPackage pkg, boolean checkAutoVerify) {
+ /**
+ * @see #RESTRICT_DOMAINS
+ */
+ private ArraySet<String> collectDomainsLegacy(@NonNull AndroidPackage pkg,
+ boolean checkAutoVerify) {
if (!checkAutoVerify) {
// Per-domain user selection state doesn't have a V1 equivalent on S, so just use V2
- collectDomains(domains, pkg, false);
- return;
+ return collectDomainsInternal(pkg, false);
}
List<ParsedActivity> activities = pkg.getActivities();
@@ -140,39 +139,54 @@
}
if (!needsAutoVerify) {
- return;
+ return new ArraySet<>();
}
}
- for (int activityIndex = 0; activityIndex < activitiesSize; activityIndex++) {
+ ArraySet<String> domains = new ArraySet<>();
+ int totalSize = 0;
+ boolean underMaxSize = true;
+ for (int activityIndex = 0; activityIndex < activitiesSize && underMaxSize;
+ activityIndex++) {
ParsedActivity activity = activities.get(activityIndex);
List<ParsedIntentInfo> intents = activity.getIntents();
int intentsSize = intents.size();
- for (int intentIndex = 0; intentIndex < intentsSize; intentIndex++) {
+ for (int intentIndex = 0; intentIndex < intentsSize && underMaxSize; intentIndex++) {
ParsedIntentInfo intent = intents.get(intentIndex);
if (intent.handlesWebUris(false)) {
int authorityCount = intent.countDataAuthorities();
for (int index = 0; index < authorityCount; index++) {
String host = intent.getDataAuthority(index).getHost();
if (isValidHost(host)) {
+ totalSize += byteSizeOf(host);
+ underMaxSize = totalSize < MAX_DOMAINS_BYTE_SIZE;
domains.add(host);
}
}
}
}
}
+
+ return domains;
}
- /** @see #RESTRICT_DOMAINS */
- private void collectDomains(@NonNull Set<String> domains,
- @NonNull AndroidPackage pkg, boolean checkAutoVerify) {
+ /**
+ * @see #RESTRICT_DOMAINS
+ */
+ private ArraySet<String> collectDomainsInternal(@NonNull AndroidPackage pkg,
+ boolean checkAutoVerify) {
+ ArraySet<String> domains = new ArraySet<>();
+ int totalSize = 0;
+ boolean underMaxSize = true;
+
List<ParsedActivity> activities = pkg.getActivities();
int activitiesSize = activities.size();
- for (int activityIndex = 0; activityIndex < activitiesSize; activityIndex++) {
+ for (int activityIndex = 0; activityIndex < activitiesSize && underMaxSize;
+ activityIndex++) {
ParsedActivity activity = activities.get(activityIndex);
List<ParsedIntentInfo> intents = activity.getIntents();
int intentsSize = intents.size();
- for (int intentIndex = 0; intentIndex < intentsSize; intentIndex++) {
+ for (int intentIndex = 0; intentIndex < intentsSize && underMaxSize; intentIndex++) {
ParsedIntentInfo intent = intents.get(intentIndex);
if (checkAutoVerify && !intent.getAutoVerify()) {
continue;
@@ -198,14 +212,27 @@
// app developer by declaring a separate intent-filter. This may not be worth
// fixing.
int authorityCount = intent.countDataAuthorities();
- for (int index = 0; index < authorityCount; index++) {
+ for (int index = 0; index < authorityCount && underMaxSize; index++) {
String host = intent.getDataAuthority(index).getHost();
if (isValidHost(host)) {
+ totalSize += byteSizeOf(host);
+ underMaxSize = totalSize < MAX_DOMAINS_BYTE_SIZE;
domains.add(host);
}
}
}
}
+
+ return domains;
+ }
+
+ /**
+ * Ballpark the size of domains to avoid a ridiculous amount of domains that could slow
+ * down client-server communication.
+ */
+ private int byteSizeOf(String string) {
+ // Use the same method from core for the data objects so that restrictions are consistent
+ return android.content.pm.verify.domain.DomainVerificationUtils.estimatedByteSizeOf(string);
}
/**
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java
index 275dd053..ed37fa0 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java
@@ -185,6 +185,29 @@
return !mCallback.filterAppAccess(packageName, callingUid, targetUserId);
}
+ /**
+ * Querying for the owners of a domain. Because this API cannot filter the returned list of
+ * packages, enforces {@link android.Manifest.permission.QUERY_ALL_PACKAGES}, but also enforces
+ * {@link android.Manifest.permission.INTERACT_ACROSS_USERS} because each user has a different
+ * state.
+ */
+ public void assertOwnerQuerent(int callingUid, @UserIdInt int callingUserId,
+ @UserIdInt int targetUserId) {
+ final int callingPid = Binder.getCallingPid();
+ if (callingUserId != targetUserId) {
+ mContext.enforcePermission(android.Manifest.permission.INTERACT_ACROSS_USERS,
+ callingPid, callingUid, "Caller is not allowed to query other users");
+ }
+
+ mContext.enforcePermission(android.Manifest.permission.QUERY_ALL_PACKAGES,
+ callingPid, callingUid, "Caller " + callingUid + " does not hold "
+ + android.Manifest.permission.QUERY_ALL_PACKAGES);
+
+ mContext.enforcePermission(
+ android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION,
+ callingPid, callingUid, "Caller is not allowed to query user selections");
+ }
+
public interface Callback {
/**
* @return true if access to the given package should be filtered and the method failed as
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java
index 8aa6337..6f28107 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java
@@ -20,13 +20,14 @@
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.verify.domain.DomainOwner;
+import android.content.pm.verify.domain.DomainSet;
+import android.content.pm.verify.domain.DomainVerificationInfo;
import android.content.pm.verify.domain.DomainVerificationManager.InvalidDomainSetException;
import android.content.pm.verify.domain.DomainVerificationManagerImpl;
-import android.content.pm.verify.domain.DomainVerificationInfo;
import android.content.pm.verify.domain.DomainVerificationUserSelection;
import android.content.pm.verify.domain.IDomainVerificationManager;
import android.os.ServiceSpecificException;
-import android.util.ArraySet;
import java.util.List;
import java.util.UUID;
@@ -61,11 +62,11 @@
}
@Override
- public void setDomainVerificationStatus(String domainSetId, List<String> domains,
+ public void setDomainVerificationStatus(String domainSetId, @NonNull DomainSet domainSet,
int state) {
try {
mService.setDomainVerificationStatus(UUID.fromString(domainSetId),
- new ArraySet<>(domains), state);
+ domainSet.getDomains(), state);
} catch (Exception e) {
throw rethrow(e);
}
@@ -82,11 +83,11 @@
}
@Override
- public void setDomainVerificationUserSelection(String domainSetId, List<String> domains,
+ public void setDomainVerificationUserSelection(String domainSetId, @NonNull DomainSet domainSet,
boolean enabled, @UserIdInt int userId) {
try {
mService.setDomainVerificationUserSelection(UUID.fromString(domainSetId),
- new ArraySet<>(domains), enabled, userId);
+ domainSet.getDomains(), enabled, userId);
} catch (Exception e) {
throw rethrow(e);
}
@@ -103,6 +104,17 @@
}
}
+ @Nullable
+ @Override
+ public List<DomainOwner> getOwnersForDomain(@NonNull String domain,
+ @UserIdInt int userId) {
+ try {
+ return mService.getOwnersForDomain(domain, userId);
+ } catch (Exception e) {
+ throw rethrow(e);
+ }
+ }
+
private RuntimeException rethrow(Exception exception) throws RuntimeException {
if (exception instanceof InvalidDomainSetException) {
int packedErrorCode = DomainVerificationManagerImpl.ERROR_INVALID_DOMAIN_SET;
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
index 8dcb45f..b58c1ff 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -17,6 +17,7 @@
package com.android.server.pm.verify.domain;
import static java.util.Collections.emptyList;
+import static java.util.Collections.emptySet;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -30,6 +31,7 @@
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.parsing.component.ParsedActivity;
+import android.content.pm.verify.domain.DomainOwner;
import android.content.pm.verify.domain.DomainVerificationInfo;
import android.content.pm.verify.domain.DomainVerificationManager;
import android.content.pm.verify.domain.DomainVerificationState;
@@ -291,6 +293,8 @@
throws InvalidDomainSetException, NameNotFoundException {
mEnforcer.assertApprovedVerifier(callingUid, mProxy);
synchronized (mLock) {
+ List<String> verifiedDomains = new ArrayList<>();
+
DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains,
true /* forAutoVerify */, callingUid, null /* userId */);
ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
@@ -301,8 +305,17 @@
continue;
}
+ if (DomainVerificationManager.isStateVerified(state)) {
+ verifiedDomains.add(domain);
+ }
+
stateMap.put(domain, state);
}
+
+ int size = verifiedDomains.size();
+ for (int index = 0; index < size; index++) {
+ removeUserSelectionsForDomain(verifiedDomains.get(index));
+ }
}
mConnection.scheduleWriteSettings();
@@ -387,6 +400,20 @@
}
}
+ private void removeUserSelectionsForDomain(@NonNull String domain) {
+ synchronized (mLock) {
+ final int size = mAttachedPkgStates.size();
+ for (int index = 0; index < size; index++) {
+ DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
+ SparseArray<DomainVerificationUserState> array = pkgState.getUserSelectionStates();
+ int arraySize = array.size();
+ for (int arrayIndex = 0; arrayIndex < arraySize; arrayIndex++) {
+ array.valueAt(arrayIndex).removeHost(domain);
+ }
+ }
+ }
+ }
+
@Override
public void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName,
boolean allowed) throws NameNotFoundException {
@@ -470,19 +497,59 @@
InvalidDomainSetException.REASON_ID_INVALID);
}
+ DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains,
+ false /* forAutoVerify */, callingUid, userId);
+ DomainVerificationUserState userState = pkgState.getOrCreateUserSelectionState(userId);
+
+ // Disable other packages if approving this one. Note that this check is only done for
+ // enabling. This allows an escape hatch in case multiple packages somehow get selected.
+ // They can be disabled without blocking in a circular dependency.
if (enabled) {
+ // Cache the approved packages from the 1st pass because the search is expensive
+ ArrayMap<String, List<String>> domainToApprovedPackages = new ArrayMap<>();
+
for (String domain : domains) {
- if (!getApprovedPackages(domain, userId, APPROVAL_LEVEL_LEGACY_ALWAYS + 1,
- mConnection::getPackageSettingLocked).first.isEmpty()) {
+ if (userState.getEnabledHosts().contains(domain)) {
+ continue;
+ }
+
+ Pair<List<String>, Integer> packagesToLevel = getApprovedPackages(domain,
+ userId, APPROVAL_LEVEL_NONE + 1, mConnection::getPackageSettingLocked);
+ int highestApproval = packagesToLevel.second;
+ if (highestApproval > APPROVAL_LEVEL_SELECTION) {
throw new InvalidDomainSetException(domainSetId, null,
InvalidDomainSetException.REASON_UNABLE_TO_APPROVE);
}
+
+ domainToApprovedPackages.put(domain, packagesToLevel.first);
+ }
+
+ // The removal for other packages must be done in a 2nd pass after it's determined
+ // that no higher priority owners exist for all of the domains in the set.
+ int mapSize = domainToApprovedPackages.size();
+ for (int mapIndex = 0; mapIndex < mapSize; mapIndex++) {
+ String domain = domainToApprovedPackages.keyAt(mapIndex);
+ List<String> approvedPackages = domainToApprovedPackages.valueAt(mapIndex);
+ int approvedSize = approvedPackages.size();
+ for (int approvedIndex = 0; approvedIndex < approvedSize; approvedIndex++) {
+ String approvedPackage = approvedPackages.get(approvedIndex);
+ DomainVerificationPkgState approvedPkgState =
+ mAttachedPkgStates.get(approvedPackage);
+ if (approvedPkgState == null) {
+ continue;
+ }
+
+ DomainVerificationUserState approvedUserState =
+ approvedPkgState.getUserSelectionState(userId);
+ if (approvedUserState == null) {
+ continue;
+ }
+
+ approvedUserState.removeHost(domain);
+ }
}
}
- DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains,
- false /* forAutoVerify */, callingUid, userId);
- DomainVerificationUserState userState = pkgState.getOrCreateUserSelectionState(userId);
if (enabled) {
userState.addHosts(domains);
} else {
@@ -600,32 +667,113 @@
throw DomainVerificationUtils.throwPackageUnavailable(packageName);
}
- ArrayMap<String, Boolean> hostToUserSelectionMap = new ArrayMap<>();
+ ArraySet<String> webDomains = mCollector.collectAllWebDomains(pkg);
+ int webDomainsSize = webDomains.size();
- ArraySet<String> domains = mCollector.collectAllWebDomains(pkg);
- int domainsSize = domains.size();
- for (int index = 0; index < domainsSize; index++) {
- hostToUserSelectionMap.put(domains.valueAt(index), false);
- }
-
+ Map<String, Integer> domains = new ArrayMap<>(webDomainsSize);
+ ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
DomainVerificationUserState userState = pkgState.getUserSelectionState(userId);
- boolean linkHandlingAllowed = true;
- if (userState != null) {
- linkHandlingAllowed = userState.isLinkHandlingAllowed();
- ArraySet<String> enabledHosts = userState.getEnabledHosts();
- int hostsSize = enabledHosts.size();
- for (int index = 0; index < hostsSize; index++) {
- hostToUserSelectionMap.put(enabledHosts.valueAt(index), true);
+ Set<String> enabledHosts = userState == null ? emptySet() : userState.getEnabledHosts();
+
+ for (int index = 0; index < webDomainsSize; index++) {
+ String host = webDomains.valueAt(index);
+ Integer state = stateMap.get(host);
+
+ int domainState;
+ if (state != null && DomainVerificationManager.isStateVerified(state)) {
+ domainState = DomainVerificationUserSelection.DOMAIN_STATE_VERIFIED;
+ } else if (enabledHosts.contains(host)) {
+ domainState = DomainVerificationUserSelection.DOMAIN_STATE_SELECTED;
+ } else {
+ domainState = DomainVerificationUserSelection.DOMAIN_STATE_NONE;
}
+
+ domains.put(host, domainState);
}
+ boolean linkHandlingAllowed = userState == null || userState.isLinkHandlingAllowed();
+
return new DomainVerificationUserSelection(pkgState.getId(), packageName,
- UserHandle.of(userId), linkHandlingAllowed, hostToUserSelectionMap);
+ UserHandle.of(userId), linkHandlingAllowed, domains);
}
}
@NonNull
@Override
+ public List<DomainOwner> getOwnersForDomain(@NonNull String domain) {
+ return getOwnersForDomain(domain, mConnection.getCallingUserId());
+ }
+
+ public List<DomainOwner> getOwnersForDomain(@NonNull String domain, @UserIdInt int userId) {
+ mEnforcer.assertOwnerQuerent(mConnection.getCallingUid(), mConnection.getCallingUserId(),
+ userId);
+
+ SparseArray<List<String>> levelToPackages = new SparseArray<>();
+
+ // First, collect the raw approval level values
+ synchronized (mLock) {
+ final int size = mAttachedPkgStates.size();
+ for (int index = 0; index < size; index++) {
+ DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
+ String packageName = pkgState.getPackageName();
+ PackageSetting pkgSetting = mConnection.getPackageSettingLocked(packageName);
+ if (pkgSetting == null) {
+ continue;
+ }
+
+ int level = approvalLevelForDomain(pkgSetting, domain, userId, domain);
+ if (level <= APPROVAL_LEVEL_NONE) {
+ continue;
+ }
+ List<String> list = levelToPackages.get(level);
+ if (list == null) {
+ list = new ArrayList<>();
+ levelToPackages.put(level, list);
+ }
+ list.add(packageName);
+ }
+ }
+
+ final int size = levelToPackages.size();
+ if (size == 0) {
+ return emptyList();
+ }
+
+ // Then sort them ascending by first installed time, with package name as the tie breaker
+ for (int index = 0; index < size; index++) {
+ levelToPackages.valueAt(index).sort((first, second) -> {
+ PackageSetting firstPkgSetting = mConnection.getPackageSettingLocked(first);
+ PackageSetting secondPkgSetting = mConnection.getPackageSettingLocked(second);
+
+ long firstInstallTime =
+ firstPkgSetting == null ? -1L : firstPkgSetting.getFirstInstallTime();
+ long secondInstallTime =
+ secondPkgSetting == null ? -1L : secondPkgSetting.getFirstInstallTime();
+
+ if (firstInstallTime != secondInstallTime) {
+ return (int) (firstInstallTime - secondInstallTime);
+ }
+
+ return first.compareToIgnoreCase(second);
+ });
+ }
+
+ List<DomainOwner> owners = new ArrayList<>();
+ for (int index = 0; index < size; index++) {
+ int level = levelToPackages.keyAt(index);
+ boolean overrideable = level <= APPROVAL_LEVEL_SELECTION;
+ List<String> packages = levelToPackages.valueAt(index);
+ int packagesSize = packages.size();
+ for (int packageIndex = 0; packageIndex < packagesSize; packageIndex++) {
+ owners.add(new DomainOwner(packages.get(packageIndex), overrideable));
+ }
+ }
+
+ return owners;
+ }
+
+ @NonNull
+ @Override
public UUID generateNewId() {
// TODO(b/159952358): Domain set ID collisions
return UUID.randomUUID();
@@ -634,7 +782,7 @@
@Override
public void migrateState(@NonNull PackageSetting oldPkgSetting,
@NonNull PackageSetting newPkgSetting) {
- String pkgName = newPkgSetting.name;
+ String pkgName = newPkgSetting.getName();
boolean sendBroadcast;
synchronized (mLock) {
@@ -730,7 +878,7 @@
// gains or loses all domains.
UUID domainSetId = newPkgSetting.getDomainSetId();
- String pkgName = newPkgSetting.name;
+ String pkgName = newPkgSetting.getName();
boolean sendBroadcast = true;
@@ -1347,7 +1495,7 @@
@Override
public int approvalLevelForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent,
@PackageManager.ResolveInfoFlags int resolveInfoFlags, @UserIdInt int userId) {
- String packageName = pkgSetting.name;
+ String packageName = pkgSetting.getName();
if (!DomainVerificationUtils.isDomainVerificationIntent(intent, resolveInfoFlags)) {
if (DEBUG_APPROVAL) {
debugApproval(packageName, intent, userId, false, "not valid intent");
@@ -1364,7 +1512,7 @@
*/
private int approvalLevelForDomain(@NonNull PackageSetting pkgSetting, @NonNull String host,
@UserIdInt int userId, @NonNull Object debugObject) {
- String packageName = pkgSetting.name;
+ String packageName = pkgSetting.getName();
final AndroidPackage pkg = pkgSetting.getPkg();
// Should never be null, but if it is, skip this and assume that v2 is enabled
diff --git a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java
index 2246864..8fbb33a 100644
--- a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java
+++ b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java
@@ -58,6 +58,11 @@
return this;
}
+ public DomainVerificationUserState removeHost(String host) {
+ mEnabledHosts.remove(host);
+ return this;
+ }
+
public DomainVerificationUserState removeHosts(@NonNull ArraySet<String> newHosts) {
mEnabledHosts.removeAll(newHosts);
return this;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index dd9619a..f40f4a9 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -40,8 +40,11 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.activityTypeToString;
+import static android.app.WindowConfiguration.isSplitScreenWindowingMode;
import static android.app.servertransaction.TransferSplashScreenViewStateItem.ATTACH_TO;
import static android.app.servertransaction.TransferSplashScreenViewStateItem.HANDOVER_TO;
import static android.content.Intent.ACTION_MAIN;
@@ -209,6 +212,7 @@
import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_SOLID_COLOR;
+import static com.android.server.wm.WindowManagerService.MIN_TASK_LETTERBOX_ASPECT_RATIO;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
import static com.android.server.wm.WindowState.LEGACY_POLICY_VISIBILITY;
@@ -557,7 +561,7 @@
/**
* The precomputed display insets for resolving configuration. It will be non-null if
- * {@link #shouldUseSizeCompatMode} returns {@code true}.
+ * {@link #shouldCreateCompatDisplayInsets} returns {@code true}.
*/
private CompatDisplayInsets mCompatDisplayInsets;
@@ -648,6 +652,18 @@
*/
private Rect mSizeCompatBounds;
+ // Whether this activity is in size compatibility mode because its bounds don't fit in parent
+ // naturally.
+ private boolean mInSizeCompatModeForBounds = false;
+
+ // Whether this activity is letterboxed for fixed orientation. If letterboxed due to fixed
+ // orientation then aspect ratio restrictions are also already respected.
+ // This happens when an activity has fixed orientation which doesn't match orientation of the
+ // parent because a display is ignoring orientation request or fixed to user rotation.
+ // See WindowManagerService#getIgnoreOrientationRequest and
+ // WindowManagerService#getFixedToUserRotation for more context.
+ private boolean mIsLetterboxedForFixedOrientationAndAspectRatio = false;
+
// activity is not displayed?
// TODO: rename to mNoDisplay
@VisibleForTesting
@@ -1296,9 +1312,6 @@
// TODO(b/36505427): Maybe this call should be moved inside
// updateOverrideConfiguration()
newTask.updateOverrideConfigurationFromLaunchBounds();
- // Make sure override configuration is up-to-date before using to create window
- // controller.
- updateSizeCompatMode();
// When an activity is started directly into a split-screen fullscreen root task, we
// need to update the initial multi-window modes so that the callbacks are scheduled
// correctly when the user leaves that mode.
@@ -6718,12 +6731,8 @@
}
if (onDescendantOrientationChanged(this)) {
- // The app is just becoming visible, and the parent Task has updated with the
- // orientation request. Update the size compat mode.
- updateSizeCompatMode();
- // WM Shell can override WM Core positioning (e.g. for letterboxing) so ensure
- // that WM Shell is called when an activity becomes visible. Without this, WM Core
- // will handle positioning instead of WM Shell when an app is reopened.
+ // WM Shell can show additional UI elements, e.g. a restart button for size compat mode
+ // so ensure that WM Shell is called when an activity becomes visible.
task.dispatchTaskInfoChangedIfNeeded(/* force= */ true);
}
}
@@ -6788,7 +6797,10 @@
* density than its parent or its bounds don't fit in parent naturally.
*/
boolean inSizeCompatMode() {
- if (mCompatDisplayInsets == null || !shouldUseSizeCompatMode()
+ if (mInSizeCompatModeForBounds) {
+ return true;
+ }
+ if (mCompatDisplayInsets == null || !shouldCreateCompatDisplayInsets()
// The orientation is different from parent when transforming.
|| isFixedRotationTransforming()) {
return false;
@@ -6798,70 +6810,30 @@
// The app bounds hasn't been computed yet.
return false;
}
-
final Configuration parentConfig = getParent().getConfiguration();
// Although colorMode, screenLayout, smallestScreenWidthDp are also fixed, generally these
// fields should be changed with density and bounds, so here only compares the most
// significant field.
- if (parentConfig.densityDpi != getConfiguration().densityDpi) {
- return true;
- }
-
- final Rect parentAppBounds = parentConfig.windowConfiguration.getAppBounds();
- final int appWidth = appBounds.width();
- final int appHeight = appBounds.height();
- final int parentAppWidth = parentAppBounds.width();
- final int parentAppHeight = parentAppBounds.height();
- if (parentAppWidth == appWidth && parentAppHeight == appHeight) {
- // Matched the parent bounds.
- return false;
- }
- if (parentAppWidth > appWidth && parentAppHeight > appHeight) {
- // Both sides are smaller than the parent.
- return true;
- }
- if (parentAppWidth < appWidth || parentAppHeight < appHeight) {
- // One side is larger than the parent.
- return true;
- }
-
- // The rest of the condition is that only one side is smaller than the parent, but it still
- // needs to exclude the cases where the size is limited by the fixed aspect ratio.
- if (info.maxAspectRatio > 0) {
- final float aspectRatio = (0.5f + Math.max(appWidth, appHeight))
- / Math.min(appWidth, appHeight);
- if (aspectRatio >= info.maxAspectRatio) {
- // The current size has reached the max aspect ratio.
- return false;
- }
- }
- if (info.minAspectRatio > 0) {
- // The activity should have at least the min aspect ratio, so this checks if the parent
- // still has available space to provide larger aspect ratio.
- final float parentAspectRatio = (0.5f + Math.max(parentAppWidth, parentAppHeight))
- / Math.min(parentAppWidth, parentAppHeight);
- if (parentAspectRatio <= info.minAspectRatio) {
- // The long side has reached the parent.
- return false;
- }
- }
- return true;
+ return parentConfig.densityDpi != getConfiguration().densityDpi;
}
/**
* Indicates the activity will keep the bounds and screen configuration when it was first
* launched, no matter how its parent changes.
*
+ * <p>If {@true}, then {@link CompatDisplayInsets} will be created in {@link
+ * #resolveOverrideConfiguration} to "freeze" activity bounds and insets.
+ *
* @return {@code true} if this activity is declared as non-resizable and fixed orientation or
* aspect ratio.
*/
- boolean shouldUseSizeCompatMode() {
+ boolean shouldCreateCompatDisplayInsets() {
if (info.supportsSizeChanges() != ActivityInfo.SIZE_CHANGES_UNSUPPORTED) {
return false;
}
if (inMultiWindowMode() || getWindowConfiguration().hasWindowDecorCaption()) {
final ActivityRecord root = task != null ? task.getRootActivity() : null;
- if (root != null && root != this && !root.shouldUseSizeCompatMode()) {
+ if (root != null && root != this && !root.shouldCreateCompatDisplayInsets()) {
// If the root activity doesn't use size compatibility mode, the activities above
// are forced to be the same for consistent visual appearance.
return false;
@@ -6883,25 +6855,11 @@
}
// TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
- private void updateSizeCompatMode() {
- if (mCompatDisplayInsets != null || !shouldUseSizeCompatMode()) {
+ private void updateCompatDisplayInsets(@Nullable Rect fixedOrientationBounds) {
+ if (mCompatDisplayInsets != null || !shouldCreateCompatDisplayInsets()) {
// The override configuration is set only once in size compatibility mode.
return;
}
- final Configuration parentConfig = getParent().getConfiguration();
- if (!hasProcess() && !isConfigurationCompatible(parentConfig)) {
- // Don't compute when launching in fullscreen and the fixed orientation is not the
- // current orientation. It is more accurately to compute the override bounds from
- // the updated configuration after the fixed orientation is applied.
- return;
- }
-
- if (task == null || (!handlesOrientationChangeFromDescendant()
- && task.getLastTaskBoundsComputeActivity() != this)) {
- // Don't compute when Task hasn't computed its bounds for this app, because the Task can
- // be letterboxed, and its bounds may not be accurate until then.
- return;
- }
Configuration overrideConfig = getRequestedOverrideConfiguration();
final Configuration fullConfig = getConfiguration();
@@ -6924,17 +6882,18 @@
}
// The role of CompatDisplayInsets is like the override bounds.
- mCompatDisplayInsets = new CompatDisplayInsets(mDisplayContent, this);
+ mCompatDisplayInsets =
+ new CompatDisplayInsets(mDisplayContent, this, fixedOrientationBounds);
}
@VisibleForTesting
void clearSizeCompatMode() {
+ mInSizeCompatModeForBounds = false;
mSizeCompatScale = 1f;
mSizeCompatBounds = null;
mCompatDisplayInsets = null;
- // Recompute from Task because letterbox can also happen on Task level.
- task.onRequestedOverrideConfigurationChanged(task.getRequestedOverrideConfiguration());
+ onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration());
}
@Override
@@ -6969,23 +6928,41 @@
mTmpConfig.updateFrom(resolvedConfig);
newParentConfiguration = mTmpConfig;
}
+
+ final int windowingMode = getWindowingMode();
+ // TODO(b/181207944): Consider removing the if condition and always run
+ // resolveFixedOrientationConfiguration() since this should be applied for all cases.
+ if (isSplitScreenWindowingMode(windowingMode)
+ || windowingMode == WINDOWING_MODE_MULTI_WINDOW
+ || windowingMode == WINDOWING_MODE_FULLSCREEN) {
+ resolveFixedOrientationConfiguration(newParentConfiguration);
+ }
+ final Rect fixedOrientationBounds = isLetterboxedForFixedOrientationAndAspectRatio()
+ ? new Rect(resolvedConfig.windowConfiguration.getBounds()) : null;
+
if (mCompatDisplayInsets != null) {
resolveSizeCompatModeConfiguration(newParentConfiguration);
- } else {
- if (inMultiWindowMode()) {
- // We ignore activities' requested orientation in multi-window modes. Task level may
- // take them into consideration when calculating bounds.
- resolvedConfig.orientation = Configuration.ORIENTATION_UNDEFINED;
- // If the activity has requested override bounds, the configuration needs to be
- // computed accordingly.
- if (!matchParentBounds()) {
- task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration);
- }
- } else {
- resolveFullscreenConfiguration(newParentConfiguration);
+ } else if (inMultiWindowMode()) {
+ // We ignore activities' requested orientation in multi-window modes. They may be
+ // taken into consideration in resolveFixedOrientationConfiguration call above.
+ resolvedConfig.orientation = Configuration.ORIENTATION_UNDEFINED;
+ // If the activity has requested override bounds, the configuration needs to be
+ // computed accordingly.
+ if (!matchParentBounds()) {
+ task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration);
}
+ // If activity in fullscreen mode is letterboxed because of fixed orientation then bounds
+ // are already calculated in resolveFixedOrientationConfiguration.
+ } else if (!isLetterboxedForFixedOrientationAndAspectRatio()) {
+ resolveFullscreenConfiguration(newParentConfiguration);
}
+ if (mVisibleRequested) {
+ updateCompatDisplayInsets(fixedOrientationBounds);
+ }
+
+ // TODO(b/175212232): Consolidate position logic from each "resolve" method above here.
+
// Assign configuration sequence number into hierarchy because there is a different way than
// ensureActivityConfiguration() in this class that uses configuration in WindowState during
// layout traversals.
@@ -6994,6 +6971,109 @@
}
/**
+ * Whether this activity is letterboxed for fixed orientation. If letterboxed due to fixed
+ * orientation then aspect ratio restrictions are also already respected.
+ *
+ * <p>This happens when an activity has fixed orientation which doesn't match orientation of the
+ * parent because a display setting 'ignoreOrientationRequest' is set to true. See {@link
+ * WindowManagerService#getIgnoreOrientationRequest} for more context.
+ */
+ boolean isLetterboxedForFixedOrientationAndAspectRatio() {
+ return mIsLetterboxedForFixedOrientationAndAspectRatio;
+ }
+
+ /**
+ * Computes bounds (letterbox or pillarbox) when the parent doesn't handle the orientation
+ * change and the requested orientation is different from the parent.
+ *
+ * <p>If letterboxed due to fixed orientation then aspect ratio restrictions are also applied
+ * in this methiod.
+ */
+ private void resolveFixedOrientationConfiguration(@NonNull Configuration newParentConfig) {
+ mIsLetterboxedForFixedOrientationAndAspectRatio = false;
+ if (handlesOrientationChangeFromDescendant()) {
+ // No need to letterbox because of fixed orientation. Display will handle
+ // fixed-orientation requests.
+ return;
+ }
+
+ final Rect resolvedBounds =
+ getResolvedOverrideConfiguration().windowConfiguration.getBounds();
+ final int parentOrientation = newParentConfig.orientation;
+
+ // If the activity requires a different orientation (either by override or activityInfo),
+ // make it fit the available bounds by scaling down its bounds.
+ final int forcedOrientation = getRequestedConfigurationOrientation();
+ if (forcedOrientation == ORIENTATION_UNDEFINED || forcedOrientation == parentOrientation) {
+ return;
+ }
+
+ if (mCompatDisplayInsets != null && !mCompatDisplayInsets.mIsInFixedOrientationLetterbox) {
+ // App prefers to keep its original size.
+ // If the size compat is from previous fixed orientation letterboxing, we may want to
+ // have fixed orientation letterbox again, otherwise it will show the size compat
+ // restart button even if the restart bounds will be the same.
+ return;
+ }
+
+ final Rect parentBounds = newParentConfig.windowConfiguration.getBounds();
+ final int parentWidth = parentBounds.width();
+ final int parentHeight = parentBounds.height();
+ float aspect = Math.max(parentWidth, parentHeight)
+ / (float) Math.min(parentWidth, parentHeight);
+
+ // Adjust the fixed orientation letterbox bounds to fit the app request aspect ratio in
+ // order to use the extra available space.
+ final float maxAspectRatio = info.maxAspectRatio;
+ final float minAspectRatio = info.minAspectRatio;
+ if (aspect > maxAspectRatio && maxAspectRatio != 0) {
+ aspect = maxAspectRatio;
+ } else if (aspect < minAspectRatio) {
+ aspect = minAspectRatio;
+ }
+
+ // Override from config_letterboxAspectRatio or via ADB with set-letterbox-aspect-ratio.
+ // TODO(b/175212232): Rename getTaskLetterboxAspectRatio and all related methods since fixed
+ // orientation letterbox is on the activity level now.
+ final float letterboxAspectRatioOverride = mWmService.getTaskLetterboxAspectRatio();
+ // Activity min/max aspect ratio restrictions will be respected by the activity-level
+ // letterboxing (size-compat mode). Therefore this override can control the maximum screen
+ // area that can be occupied by the app in the letterbox mode.
+ aspect = letterboxAspectRatioOverride > MIN_TASK_LETTERBOX_ASPECT_RATIO
+ ? letterboxAspectRatioOverride : aspect;
+
+ // Store the current bounds to be able to revert to size compat mode values below if needed.
+ Rect mTmpFullBounds = new Rect(resolvedBounds);
+ if (forcedOrientation == ORIENTATION_LANDSCAPE) {
+ final int height = (int) Math.rint(parentWidth / aspect);
+ final int top = parentBounds.centerY() - height / 2;
+ resolvedBounds.set(parentBounds.left, top, parentBounds.right, top + height);
+ } else {
+ final int width = (int) Math.rint(parentHeight / aspect);
+ final int left = parentBounds.centerX() - width / 2;
+ resolvedBounds.set(left, parentBounds.top, left + width, parentBounds.bottom);
+ }
+
+ if (mCompatDisplayInsets != null) {
+ mCompatDisplayInsets.getBoundsByRotation(
+ mTmpBounds, newParentConfig.windowConfiguration.getRotation());
+ if (resolvedBounds.width() != mTmpBounds.width()
+ || resolvedBounds.height() != mTmpBounds.height()) {
+ // The app shouldn't be resized, we only do fixed orientation letterboxing if the
+ // compat bounds are also from the same fixed orientation letterbox. Otherwise,
+ // clear the fixed orientation bounds to show app in size compat mode.
+ resolvedBounds.set(mTmpFullBounds);
+ return;
+ }
+ }
+
+ // Calculate app bounds using fixed orientation bounds because they will be needed later
+ // for comparison with size compat app bounds in {@link resolveSizeCompatModeConfiguration}.
+ task.computeConfigResourceOverrides(getResolvedOverrideConfiguration(), newParentConfig);
+ mIsLetterboxedForFixedOrientationAndAspectRatio = true;
+ }
+
+ /**
* Resolves the configuration of activity in fullscreen mode. If the bounds are restricted by
* aspect ratio, the position will be centered horizontally in parent's app bounds to balance
* the visual appearance. The policy of aspect ratio has higher priority than the requested
@@ -7037,6 +7117,18 @@
private void resolveSizeCompatModeConfiguration(Configuration newParentConfiguration) {
final Configuration resolvedConfig = getResolvedOverrideConfiguration();
final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
+
+ // When an activity needs to be letterboxed because of fixed orientation, use fixed
+ // orientation bounds (stored in resolved bounds) instead of parent bounds since the
+ // activity will be displayed within them even if it is in size compat mode. They should be
+ // saved here before resolved bounds are overridden below.
+ final Rect containerBounds = isLetterboxedForFixedOrientationAndAspectRatio()
+ ? new Rect(resolvedBounds)
+ : newParentConfiguration.windowConfiguration.getBounds();
+ final Rect containerAppBounds = isLetterboxedForFixedOrientationAndAspectRatio()
+ ? new Rect(getResolvedOverrideConfiguration().windowConfiguration.getAppBounds())
+ : newParentConfiguration.windowConfiguration.getAppBounds();
+
final int requestedOrientation = getRequestedConfigurationOrientation();
final boolean orientationRequested = requestedOrientation != ORIENTATION_UNDEFINED;
final int orientation = orientationRequested
@@ -7095,7 +7187,7 @@
// Below figure is an example that puts an activity which was launched in a larger container
// into a smaller container.
// The outermost rectangle is the real display bounds.
- // "@" is the parent app bounds.
+ // "@" is the container app bounds (parent bounds or fixed orientation bouds)
// "#" is the {@code resolvedBounds} that applies to application.
// "*" is the {@code mSizeCompatBounds} that used to show on screen if scaled.
// ------------------------------
@@ -7111,19 +7203,18 @@
// The application is still layouted in "#" since it was launched, and it will be visually
// scaled and positioned to "*".
+ final Rect resolvedAppBounds = resolvedConfig.windowConfiguration.getAppBounds();
+
// Calculates the scale and offset to horizontal center the size compatibility bounds into
// the region which is available to application.
- final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
- final Rect parentAppBounds = newParentConfiguration.windowConfiguration.getAppBounds();
- final Rect resolvedAppBounds = resolvedConfig.windowConfiguration.getAppBounds();
final int contentW = resolvedAppBounds.width();
final int contentH = resolvedAppBounds.height();
- final int viewportW = parentAppBounds.width();
- final int viewportH = parentAppBounds.height();
+ final int viewportW = containerAppBounds.width();
+ final int viewportH = containerAppBounds.height();
// Only allow to scale down.
mSizeCompatScale = (contentW <= viewportW && contentH <= viewportH)
? 1f : Math.min((float) viewportW / contentW, (float) viewportH / contentH);
- final int screenTopInset = parentAppBounds.top - parentBounds.top;
+ final int screenTopInset = containerAppBounds.top - containerBounds.top;
final boolean topNotAligned = screenTopInset != resolvedAppBounds.top - resolvedBounds.top;
if (mSizeCompatScale != 1f || topNotAligned) {
if (mSizeCompatBounds == null) {
@@ -7143,8 +7234,9 @@
final int offsetX = getHorizontalCenterOffset(
(int) viewportW, (int) (contentW * mSizeCompatScale));
// Above coordinates are in "@" space, now place "*" and "#" to screen space.
- final int screenPosX = (fillContainer ? parentBounds.left : parentAppBounds.left) + offsetX;
- final int screenPosY = parentBounds.top;
+ final int screenPosX = (fillContainer
+ ? containerBounds.left : containerAppBounds.left) + offsetX;
+ final int screenPosY = containerBounds.top;
if (screenPosX != 0 || screenPosY != 0) {
if (mSizeCompatBounds != null) {
mSizeCompatBounds.offset(screenPosX, screenPosY);
@@ -7154,6 +7246,52 @@
final int dy = screenPosY - resolvedBounds.top;
offsetBounds(resolvedConfig, dx, dy);
}
+
+ mInSizeCompatModeForBounds =
+ isInSizeCompatModeForBounds(resolvedAppBounds, containerAppBounds);
+ }
+
+ private boolean isInSizeCompatModeForBounds(final Rect appBounds, final Rect containerBounds) {
+ final int appWidth = appBounds.width();
+ final int appHeight = appBounds.height();
+ final int containerAppWidth = containerBounds.width();
+ final int containerAppHeight = containerBounds.height();
+
+ if (containerAppWidth == appWidth && containerAppHeight == appHeight) {
+ // Matched the container bounds.
+ return false;
+ }
+ if (containerAppWidth > appWidth && containerAppHeight > appHeight) {
+ // Both sides are smaller than the container.
+ return true;
+ }
+ if (containerAppWidth < appWidth || containerAppHeight < appHeight) {
+ // One side is larger than the container.
+ return true;
+ }
+
+ // The rest of the condition is that only one side is smaller than the container, but it
+ // still needs to exclude the cases where the size is limited by the fixed aspect ratio.
+ if (info.maxAspectRatio > 0) {
+ final float aspectRatio = (0.5f + Math.max(appWidth, appHeight))
+ / Math.min(appWidth, appHeight);
+ if (aspectRatio >= info.maxAspectRatio) {
+ // The current size has reached the max aspect ratio.
+ return false;
+ }
+ }
+ if (info.minAspectRatio > 0) {
+ // The activity should have at least the min aspect ratio, so this checks if the
+ // container still has available space to provide larger aspect ratio.
+ final float containerAspectRatio =
+ (0.5f + Math.max(containerAppWidth, containerAppHeight))
+ / Math.min(containerAppWidth, containerAppHeight);
+ if (containerAspectRatio <= info.minAspectRatio) {
+ // The long side has reached the parent.
+ return false;
+ }
+ }
+ return true;
}
/** @return The horizontal offset of putting the content in the center of viewport. */
@@ -7305,7 +7443,8 @@
final Task rootTask = getRootTask();
final float minAspectRatio = info.minAspectRatio;
- if (task == null || rootTask == null || (inMultiWindowMode() && !shouldUseSizeCompatMode())
+ if (task == null || rootTask == null
+ || (inMultiWindowMode() && !shouldCreateCompatDisplayInsets())
|| (maxAspectRatio == 0 && minAspectRatio == 0)
|| isInVrUiMode(getConfiguration())) {
// We don't enforce aspect ratio if the activity task is in multiwindow unless it
@@ -7443,8 +7582,6 @@
if (displayChanged) {
mLastReportedDisplayId = newDisplayId;
}
- // TODO(b/36505427): Is there a better place to do this?
- updateSizeCompatMode();
// Short circuit: if the two full configurations are equal (the common case), then there is
// nothing to do. We test the full configuration instead of the global and merged override
@@ -7723,11 +7860,6 @@
// Reset the existing override configuration so it can be updated according to the latest
// configuration.
clearSizeCompatMode();
- if (mVisibleRequested) {
- // Configuration will be ensured when becoming visible, so if it is already visible,
- // then the manual update is needed.
- updateSizeCompatMode();
- }
if (!attachedToProcess()) {
return;
@@ -8134,8 +8266,11 @@
private final int mHeight;
/** Whether the {@link Task} windowingMode represents a floating window*/
final boolean mIsFloating;
- /** Whether the {@link Task} is letterboxed when the unresizable activity is first shown. */
- final boolean mIsTaskLetterboxed;
+ /**
+ * Whether is letterboxed because of fixed orientation when the unresizable activity is
+ * first shown.
+ */
+ final boolean mIsInFixedOrientationLetterbox;
/**
* The nonDecorInsets for each rotation. Includes the navigation bar and cutout insets. It
* is used to compute the appBounds.
@@ -8149,7 +8284,8 @@
final Rect[] mStableInsets = new Rect[4];
/** Constructs the environment to simulate the bounds behavior of the given container. */
- CompatDisplayInsets(DisplayContent display, ActivityRecord container) {
+ CompatDisplayInsets(DisplayContent display, ActivityRecord container,
+ @Nullable Rect fixedOrientationBounds) {
mIsFloating = container.getWindowConfiguration().tasksAreFloating();
if (mIsFloating) {
final Rect containerBounds = container.getWindowConfiguration().getBounds();
@@ -8162,24 +8298,34 @@
mNonDecorInsets[rotation] = emptyRect;
mStableInsets[rotation] = emptyRect;
}
- mIsTaskLetterboxed = false;
+ mIsInFixedOrientationLetterbox = false;
return;
}
final Task task = container.getTask();
- mIsTaskLetterboxed = task != null && task.isTaskLetterboxed();
+
+ mIsInFixedOrientationLetterbox = fixedOrientationBounds != null;
// Store the bounds of the Task for the non-resizable activity to use in size compat
// mode so that the activity will not be resized regardless the windowing mode it is
// currently in.
- final WindowContainer filledContainer = task != null ? task : display;
- final Point dimensions = getRotationZeroDimensions(filledContainer);
+ // When an activity needs to be letterboxed because of fixed orientation, use fixed
+ // orientation bounds instead of task bounds since the activity will be displayed
+ // within these even if it is in size compat mode.
+ final Rect filledContainerBounds = mIsInFixedOrientationLetterbox
+ ? fixedOrientationBounds
+ : task != null ? task.getBounds() : display.getBounds();
+ final int filledContainerRotation = task != null
+ ? task.getConfiguration().windowConfiguration.getRotation()
+ : display.getConfiguration().windowConfiguration.getRotation();
+ final Point dimensions = getRotationZeroDimensions(
+ filledContainerBounds, filledContainerRotation);
mWidth = dimensions.x;
mHeight = dimensions.y;
// Bounds of the filled container if it doesn't fill the display.
final Rect unfilledContainerBounds =
- filledContainer.getBounds().equals(display.getBounds()) ? null : new Rect();
+ filledContainerBounds.equals(display.getBounds()) ? null : new Rect();
final DisplayPolicy policy = display.getDisplayPolicy();
for (int rotation = 0; rotation < 4; rotation++) {
mNonDecorInsets[rotation] = new Rect();
@@ -8199,9 +8345,9 @@
// The insets is based on the display, but the container may be smaller than the
// display, so update the insets to exclude parts that are not intersected with the
// container.
- unfilledContainerBounds.set(filledContainer.getBounds());
+ unfilledContainerBounds.set(filledContainerBounds);
display.rotateBounds(
- filledContainer.getConfiguration().windowConfiguration.getRotation(),
+ filledContainerRotation,
rotation,
unfilledContainerBounds);
updateInsetsForBounds(unfilledContainerBounds, dw, dh, mNonDecorInsets[rotation]);
@@ -8214,9 +8360,7 @@
* the display is rotated, we can calculate the bounds by rotating the dimensions.
* @see #getBoundsByRotation
*/
- private static Point getRotationZeroDimensions(WindowContainer container) {
- final Rect bounds = container.getBounds();
- final int rotation = container.getConfiguration().windowConfiguration.getRotation();
+ private static Point getRotationZeroDimensions(final Rect bounds, int rotation) {
final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
final int width = bounds.width();
final int height = bounds.height();
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index f64f04c..32152ec 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -2686,8 +2686,9 @@
boolean newImmersiveMode = isImmersiveMode(win);
if (oldImmersiveMode != newImmersiveMode) {
mLastImmersiveMode = newImmersiveMode;
- final String pkg = win.getOwningPackage();
- mImmersiveModeConfirmation.immersiveModeChangedLw(pkg, newImmersiveMode,
+ // The immersive confirmation window should be attached to the immersive window root.
+ final int rootDisplayAreaId = win.getRootDisplayArea().mFeatureId;
+ mImmersiveModeConfirmation.immersiveModeChangedLw(rootDisplayAreaId, newImmersiveMode,
mService.mPolicy.isUserSetupComplete(),
isNavBarEmpty(disableFlags));
}
diff --git a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
index 9286a46..567b6c2 100644
--- a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
+++ b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
@@ -19,9 +19,11 @@
import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED;
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.content.BroadcastReceiver;
@@ -32,10 +34,12 @@
import android.graphics.PixelFormat;
import android.graphics.drawable.ColorDrawable;
import android.os.Binder;
+import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -43,6 +47,7 @@
import android.util.Slog;
import android.view.Display;
import android.view.Gravity;
+import android.view.IWindowManager;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
@@ -50,6 +55,7 @@
import android.view.WindowInsets;
import android.view.WindowInsets.Type;
import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
@@ -67,6 +73,8 @@
private static final boolean DEBUG = false;
private static final boolean DEBUG_SHOW_EVERY_TIME = false; // super annoying, use with caution
private static final String CONFIRMED = "confirmed";
+ private static final int IMMERSIVE_MODE_CONFIRMATION_WINDOW_TYPE =
+ WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL;
private static boolean sConfirmed;
@@ -78,7 +86,15 @@
private ClingWindowView mClingWindow;
private long mPanicTime;
+ /** The last {@link WindowManager} that is used to add the confirmation window. */
+ @Nullable
private WindowManager mWindowManager;
+ /**
+ * The WindowContext that is registered with {@link #mWindowManager} with options to specify the
+ * {@link RootDisplayArea} to attach the confirmation window.
+ */
+ @Nullable
+ private Context mWindowContext;
// Local copy of vr mode enabled state, to avoid calling into VrManager with
// the lock held.
private boolean mVrModeEnabled;
@@ -132,7 +148,7 @@
}
}
- void immersiveModeChangedLw(String pkg, boolean isImmersiveMode,
+ void immersiveModeChangedLw(int rootDisplayAreaId, boolean isImmersiveMode,
boolean userSetupComplete, boolean navBarEmpty) {
mHandler.removeMessages(H.SHOW);
if (isImmersiveMode) {
@@ -143,7 +159,9 @@
&& !navBarEmpty
&& !UserManager.isDeviceInDemoMode(mContext)
&& (mLockTaskState != LOCK_TASK_MODE_LOCKED)) {
- mHandler.sendEmptyMessageDelayed(H.SHOW, mShowDelayMs);
+ final Message msg = mHandler.obtainMessage(H.SHOW);
+ msg.arg1 = rootDisplayAreaId;
+ mHandler.sendMessageDelayed(msg, mShowDelayMs);
}
} else {
mHandler.sendEmptyMessage(H.HIDE);
@@ -175,7 +193,8 @@
private void handleHide() {
if (mClingWindow != null) {
if (DEBUG) Slog.d(TAG, "Hiding immersive mode confirmation");
- getWindowManager().removeView(mClingWindow);
+ // We don't care which root display area the window manager is specifying for removal.
+ getWindowManager(FEATURE_UNDEFINED).removeView(mClingWindow);
mClingWindow = null;
}
}
@@ -184,7 +203,7 @@
final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT,
- WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL,
+ IMMERSIVE_MODE_CONFIRMATION_WINDOW_TYPE,
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
@@ -353,23 +372,57 @@
* DO HOLD THE WINDOW MANAGER LOCK WHEN CALLING THIS METHOD
* The reason why we add this method is to avoid the deadlock of WMG->WMS and WMS->WMG
* when ImmersiveModeConfirmation object is created.
+ *
+ * @return the WindowManager specifying with the {@code rootDisplayAreaId} to attach the
+ * confirmation window.
*/
- private WindowManager getWindowManager() {
- if (mWindowManager == null) {
- mWindowManager = (WindowManager)
- mContext.getSystemService(Context.WINDOW_SERVICE);
+ private WindowManager getWindowManager(int rootDisplayAreaId) {
+ if (mWindowManager == null || mWindowContext == null) {
+ // Create window context to specify the RootDisplayArea
+ final Bundle options = getOptionsForWindowContext(rootDisplayAreaId);
+ mWindowContext = mContext.createWindowContext(
+ IMMERSIVE_MODE_CONFIRMATION_WINDOW_TYPE, options);
+ mWindowManager = mWindowContext.getSystemService(WindowManager.class);
+ return mWindowManager;
}
+
+ // Update the window context and window manager to specify the RootDisplayArea
+ final Bundle options = getOptionsForWindowContext(rootDisplayAreaId);
+ final IWindowManager wms = WindowManagerGlobal.getWindowManagerService();
+ try {
+ wms.registerWindowContextListener(mWindowContext.getWindowContextToken(),
+ IMMERSIVE_MODE_CONFIRMATION_WINDOW_TYPE, mContext.getDisplayId(), options);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+
return mWindowManager;
}
- private void handleShow() {
+ /**
+ * Returns options that specify the {@link RootDisplayArea} to attach the confirmation window.
+ * {@code null} if the {@code rootDisplayAreaId} is {@link FEATURE_UNDEFINED}.
+ */
+ @Nullable
+ private Bundle getOptionsForWindowContext(int rootDisplayAreaId) {
+ // In case we don't care which root display area the window manager is specifying.
+ if (rootDisplayAreaId == FEATURE_UNDEFINED) {
+ return null;
+ }
+
+ final Bundle options = new Bundle();
+ options.putInt(DisplayAreaPolicyBuilder.KEY_ROOT_DISPLAY_AREA_ID, rootDisplayAreaId);
+ return options;
+ }
+
+ private void handleShow(int rootDisplayAreaId) {
if (DEBUG) Slog.d(TAG, "Showing immersive mode confirmation");
mClingWindow = new ClingWindowView(mContext, mConfirm);
// show the confirmation
WindowManager.LayoutParams lp = getClingWindowLayoutParams();
- getWindowManager().addView(mClingWindow, lp);
+ getWindowManager(rootDisplayAreaId).addView(mClingWindow, lp);
}
private final Runnable mConfirm = new Runnable() {
@@ -396,7 +449,7 @@
public void handleMessage(Message msg) {
switch(msg.what) {
case SHOW:
- handleShow();
+ handleShow(msg.arg1);
break;
case HIDE:
handleHide();
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 29c7ff1..7085156 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -35,7 +35,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.activityTypeToString;
-import static android.app.WindowConfiguration.isSplitScreenWindowingMode;
import static android.app.WindowConfiguration.windowingModeToString;
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -148,7 +147,6 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ROOT_TASK;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.MIN_TASK_LETTERBOX_ASPECT_RATIO;
import static com.android.server.wm.WindowManagerService.dipToPixel;
import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_BEFORE_ANIM;
@@ -597,10 +595,6 @@
@Nullable
private ActivityRecord mResumedActivity = null;
- /** Last activity that is used to compute the Task bounds. */
- @Nullable
- private ActivityRecord mLastTaskBoundsComputeActivity;
-
private boolean mForceShowForAllUsers;
/** When set, will force the task to report as invisible. */
@@ -1496,11 +1490,6 @@
}
void cleanUpActivityReferences(ActivityRecord r) {
- // mLastTaskBoundsComputeActivity is set at leaf Task
- if (mLastTaskBoundsComputeActivity == r) {
- mLastTaskBoundsComputeActivity = null;
- }
-
// mPausingActivity is set at leaf task
if (mPausingActivity != null && mPausingActivity == r) {
mPausingActivity = null;
@@ -2863,7 +2852,6 @@
private void resolveLeafOnlyOverrideConfigs(Configuration newParentConfig,
Rect previousBounds) {
- mLastTaskBoundsComputeActivity = getTopNonFinishingActivity(false /* includeOverlays */);
int windowingMode =
getResolvedOverrideConfiguration().windowConfiguration.getWindowingMode();
@@ -2877,7 +2865,8 @@
getResolvedOverrideConfiguration().windowConfiguration.getBounds();
if (windowingMode == WINDOWING_MODE_FULLSCREEN) {
- computeFullscreenBounds(outOverrideBounds, newParentConfig);
+ // Use empty bounds to indicate "fill parent".
+ outOverrideBounds.setEmpty();
// The bounds for fullscreen mode shouldn't be adjusted by minimal size. Otherwise if
// the parent or display is smaller than the size, the content may be cropped.
return;
@@ -2888,21 +2877,6 @@
computeFreeformBounds(outOverrideBounds, newParentConfig);
return;
}
-
- if (isSplitScreenWindowingMode(windowingMode)
- || windowingMode == WINDOWING_MODE_MULTI_WINDOW) {
- // This is to compute whether the task should be letterboxed to handle non-resizable app
- // in multi window. There is no split screen only logic.
- computeLetterboxBounds(outOverrideBounds, newParentConfig);
- }
- }
-
- /** Computes bounds for {@link WindowConfiguration#WINDOWING_MODE_FULLSCREEN}. */
- @VisibleForTesting
- void computeFullscreenBounds(@NonNull Rect outBounds, @NonNull Configuration newParentConfig) {
- // In FULLSCREEN mode, always start with empty bounds to indicate "fill parent".
- outBounds.setEmpty();
- computeLetterboxBounds(outBounds, newParentConfig);
}
/** Computes bounds for {@link WindowConfiguration#WINDOWING_MODE_FREEFORM}. */
@@ -2934,94 +2908,6 @@
}
}
- /**
- * Computes bounds (letterbox or pillarbox) when the parent doesn't handle the orientation
- * change and the requested orientation is different from the parent.
- */
- private void computeLetterboxBounds(@NonNull Rect outBounds,
- @NonNull Configuration newParentConfig) {
- if (handlesOrientationChangeFromDescendant()) {
- // No need to letterbox at task level. Display will handle fixed-orientation requests.
- return;
- }
-
- final int parentOrientation = newParentConfig.orientation;
- // Use the top activity as the reference of orientation. Don't include overlays because
- // it is usually not the actual content or just temporarily shown.
- // E.g. ForcedResizableInfoActivity.
- final ActivityRecord refActivity = getTopNonFinishingActivity(false /* includeOverlays */);
-
- // If the task or the reference activity requires a different orientation (either by
- // override or activityInfo), make it fit the available bounds by scaling down its bounds.
- final int overrideOrientation = getRequestedOverrideConfiguration().orientation;
- final int forcedOrientation =
- (overrideOrientation != ORIENTATION_UNDEFINED || refActivity == null)
- ? overrideOrientation : refActivity.getRequestedConfigurationOrientation();
- if (forcedOrientation == ORIENTATION_UNDEFINED || forcedOrientation == parentOrientation) {
- return;
- }
-
- final ActivityRecord.CompatDisplayInsets compatDisplayInsets =
- refActivity == null ? null : refActivity.getCompatDisplayInsets();
- if (compatDisplayInsets != null && !compatDisplayInsets.mIsTaskLetterboxed) {
- // App prefers to keep its original size.
- // If the size compat is from previous task letterboxing, we may want to have task
- // letterbox again, otherwise it will show the size compat restart button even if the
- // restart bounds will be the same.
- return;
- }
-
- final Rect parentBounds = newParentConfig.windowConfiguration.getBounds();
- final int parentWidth = parentBounds.width();
- final int parentHeight = parentBounds.height();
- float aspect = Math.max(parentWidth, parentHeight)
- / (float) Math.min(parentWidth, parentHeight);
-
- // Adjust the Task letterbox bounds to fit the app request aspect ratio in order to use the
- // extra available space.
- if (refActivity != null) {
- final float maxAspectRatio = refActivity.info.maxAspectRatio;
- final float minAspectRatio = refActivity.info.minAspectRatio;
- if (aspect > maxAspectRatio && maxAspectRatio != 0) {
- aspect = maxAspectRatio;
- } else if (aspect < minAspectRatio) {
- aspect = minAspectRatio;
- }
- }
-
- // Override from config_letterboxAspectRatio or via ADB with set-letterbox-aspect-ratio.
- final float letterboxAspectRatioOverride = mWmService.getTaskLetterboxAspectRatio();
- // Activity min/max aspect ratio restrictions will be respected by the activity-level
- // letterboxing (size-compat mode). Therefore this override can control the maximum screen
- // area that can be occupied by the app in the letterbox mode.
- aspect = letterboxAspectRatioOverride > MIN_TASK_LETTERBOX_ASPECT_RATIO
- ? letterboxAspectRatioOverride : aspect;
-
- // Store the current bounds to be able to revert to size compat mode values below if needed.
- mTmpFullBounds.set(outBounds);
- if (forcedOrientation == ORIENTATION_LANDSCAPE) {
- final int height = (int) Math.rint(parentWidth / aspect);
- final int top = parentBounds.centerY() - height / 2;
- outBounds.set(parentBounds.left, top, parentBounds.right, top + height);
- } else {
- final int width = (int) Math.rint(parentHeight / aspect);
- final int left = parentBounds.centerX() - width / 2;
- outBounds.set(left, parentBounds.top, left + width, parentBounds.bottom);
- }
-
- if (compatDisplayInsets != null) {
- compatDisplayInsets.getBoundsByRotation(
- mTmpBounds, newParentConfig.windowConfiguration.getRotation());
- if (outBounds.width() != mTmpBounds.width()
- || outBounds.height() != mTmpBounds.height()) {
- // The app shouldn't be resized, we only do task letterboxing if the compat bounds
- // is also from the same task letterbox. Otherwise, clear the task bounds to show
- // app in size compat mode.
- outBounds.set(mTmpFullBounds);
- }
- }
- }
-
Rect updateOverrideConfigurationFromLaunchBounds() {
// If the task is controlled by another organized task, do not set override
// configurations and let its parent (organized task) to control it;
@@ -3036,11 +2922,6 @@
return bounds;
}
- @Nullable
- ActivityRecord getLastTaskBoundsComputeActivity() {
- return mLastTaskBoundsComputeActivity;
- }
-
/** Updates the task's bounds and override configuration to match what is expected for the
* input root task. */
void updateOverrideConfigurationForRootTask(Task inRootTask) {
@@ -3939,12 +3820,6 @@
|| activityType == ACTIVITY_TYPE_ASSISTANT;
}
- boolean isTaskLetterboxed() {
- // No letterbox for multi window root task
- return !matchParentBounds()
- && (getWindowingMode() == WINDOWING_MODE_FULLSCREEN || !isRootTask());
- }
-
@Override
boolean fillsParent() {
// From the perspective of policy, we still want to report that this task fills parent
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index 106db0b..82d9c21 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -234,19 +234,28 @@
}
}
- // STEP 2.3: Adjust launch parameters as needed for freeform display. We enforce the policy
- // that legacy (pre-D) apps and those apps that can't handle multiple screen density well
- // are forced to be maximized. The rest of this step is to define the default policy when
- // there is no initial bounds or a fully resolved current params from callers.
+ // STEP 2.3: Adjust launch parameters as needed for freeform display. We enforce the
+ // policies related to unresizable apps here. If an app is unresizable and the freeform
+ // size-compat mode is enabled, it can be launched in freeform depending on other properties
+ // such as orientation. Otherwise, the app is forcefully launched in maximized. The rest of
+ // this step is to define the default policy when there is no initial bounds or a fully
+ // resolved current params from callers.
if (display.inFreeformWindowingMode()) {
if (launchMode == WINDOWING_MODE_PINNED) {
if (DEBUG) appendLog("picture-in-picture");
- } else if (!mSupervisor.mService.mSizeCompatFreeform && !root.isResizeable()) {
- // We're launching an activity in size-compat mode and they aren't allowed in
- // freeform, so force it to be maximized.
- launchMode = WINDOWING_MODE_FULLSCREEN;
- outParams.mBounds.setEmpty();
- if (DEBUG) appendLog("forced-maximize");
+ } else if (!root.isResizeable()) {
+ if (shouldLaunchUnresizableAppInFreeform(root, suggestedDisplayArea)) {
+ launchMode = WINDOWING_MODE_FREEFORM;
+ if (outParams.mBounds.isEmpty()) {
+ getTaskBounds(root, display, layout, launchMode, hasInitialBounds,
+ outParams.mBounds);
+ }
+ if (DEBUG) appendLog("unresizable-freeform");
+ } else {
+ launchMode = WINDOWING_MODE_FULLSCREEN;
+ outParams.mBounds.setEmpty();
+ if (DEBUG) appendLog("unresizable-forced-maximize");
+ }
}
} else {
if (DEBUG) appendLog("non-freeform-display");
@@ -322,7 +331,6 @@
}
getTaskBounds(root, display, layout, resolvedMode, hasInitialBounds, outParams.mBounds);
}
-
return RESULT_CONTINUE;
}
@@ -562,6 +570,28 @@
outBounds.offset(xOffset, yOffset);
}
+ private boolean shouldLaunchUnresizableAppInFreeform(ActivityRecord activity,
+ TaskDisplayArea displayArea) {
+ // TODO(176061101): Migrate |mSizeCompatFreeform| to |mSupportsNonResizableMultiWindow|.
+ if (!mSupervisor.mService.mSizeCompatFreeform || activity.isResizeable()) {
+ return false;
+ }
+ final DisplayContent display = displayArea.getDisplayContent();
+ if (display == null) {
+ return false;
+ }
+
+ final int displayOrientation = orientationFromBounds(displayArea.getBounds());
+ final int activityOrientation = resolveOrientation(activity, display,
+ displayArea.getBounds());
+ if (displayArea.getWindowingMode() == WINDOWING_MODE_FREEFORM
+ && displayOrientation != activityOrientation) {
+ return true;
+ }
+
+ return false;
+ }
+
/**
* Resolves activity requested orientation to 4 categories:
* 1) {@link ActivityInfo#SCREEN_ORIENTATION_LOCKED} indicating app wants to lock down
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index cca85b2..c0ccd81 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -253,6 +253,7 @@
import android.view.MotionEvent;
import android.view.PointerIcon;
import android.view.RemoteAnimationAdapter;
+import android.view.ScrollCaptureResponse;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
@@ -7159,12 +7160,14 @@
}
final long token = Binder.clearCallingIdentity();
try {
+ ScrollCaptureResponse.Builder responseBuilder = new ScrollCaptureResponse.Builder();
synchronized (mGlobalLock) {
DisplayContent dc = mRoot.getDisplayContent(displayId);
if (dc == null) {
ProtoLog.e(WM_ERROR,
"Invalid displayId for requestScrollCapture: %d", displayId);
- callbacks.onUnavailable();
+ responseBuilder.setDescription(String.format("bad displayId: %d", displayId));
+ callbacks.onScrollCaptureResponse(responseBuilder.build());
return;
}
WindowState topWindow = null;
@@ -7173,17 +7176,20 @@
}
WindowState targetWindow = dc.findScrollCaptureTargetWindow(topWindow, taskId);
if (targetWindow == null) {
- callbacks.onUnavailable();
+ responseBuilder.setDescription("findScrollCaptureTargetWindow returned null");
+ callbacks.onScrollCaptureResponse(responseBuilder.build());
return;
}
- // Forward to the window for handling.
try {
+ // Forward to the window for handling, which will respond using the callback.
targetWindow.mClient.requestScrollCapture(callbacks);
} catch (RemoteException e) {
ProtoLog.w(WM_ERROR,
"requestScrollCapture: caught exception dispatching to window."
+ "token=%s", targetWindow.mClient.asBinder());
- callbacks.onUnavailable();
+ responseBuilder.setWindowTitle(targetWindow.getName());
+ responseBuilder.setDescription(String.format("caught exception: %s", e));
+ callbacks.onScrollCaptureResponse(responseBuilder.build());
}
}
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 9ae5beb..1fc7041 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3827,13 +3827,21 @@
/** @return true when the window should be letterboxed. */
boolean isLetterboxedAppWindow() {
// Fullscreen mode but doesn't fill display area.
- return (!inMultiWindowMode() && !matchesDisplayAreaBounds())
- // Activity in size compat.
- || (mActivityRecord != null && mActivityRecord.inSizeCompatMode())
- // Task letterboxed.
- || (getTask() != null && getTask().isTaskLetterboxed())
- // Letterboxed for display cutout.
- || isLetterboxedForDisplayCutout();
+ if (!inMultiWindowMode() && !matchesDisplayAreaBounds()) {
+ return true;
+ }
+ if (mActivityRecord != null) {
+ // Activity in size compat.
+ if (mActivityRecord.inSizeCompatMode()) {
+ return true;
+ }
+ // Letterbox for fixed orientation.
+ if (mActivityRecord.isLetterboxedForFixedOrientationAndAspectRatio()) {
+ return true;
+ }
+ }
+ // Letterboxed for display cutout.
+ return isLetterboxedForDisplayCutout();
}
/** Returns {@code true} if the window is letterboxed for the display cutout. */
diff --git a/services/net/Android.bp b/services/net/Android.bp
index c68004a..b01e425 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -52,6 +52,7 @@
libs: [
"unsupportedappusage",
"framework-wifi-util-lib",
+ "framework-connectivity"
],
static_libs: [
// All the classes in netd_aidl_interface must be jarjar so they do not conflict with the
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt
index deb3147..9447f39 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt
@@ -16,8 +16,9 @@
package com.android.server.pm.test.verify.domain
-import android.content.pm.verify.domain.DomainVerificationRequest
+import android.content.pm.verify.domain.DomainSet
import android.content.pm.verify.domain.DomainVerificationInfo
+import android.content.pm.verify.domain.DomainVerificationRequest
import android.content.pm.verify.domain.DomainVerificationUserSelection
import android.os.Parcel
import android.os.Parcelable
@@ -27,6 +28,7 @@
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import java.util.UUID
+import kotlin.random.Random
@RunWith(Parameterized::class)
class DomainVerificationCoreApiTest {
@@ -40,18 +42,25 @@
assertThat(value).containsExactlyEntriesIn(other)
}
+ private val massiveSet by lazy {
+ val fragmentOf21 = ".com.example.test.app"
+ val list = mutableListOf("prefix$fragmentOf21")
+ var totalSize = 0
+ // Slightly overshoot a size of 1MB
+ while (totalSize < (1024 * 512)) {
+ val nextValue = "${list.last()}$fragmentOf21"
+ totalSize += nextValue.length
+ list += nextValue
+ }
+ list.toSet()
+ }
+
@JvmStatic
- @Parameterized.Parameters
+ @Parameterized.Parameters(name = "{0}")
fun parameters() = arrayOf(
Parameter(
- initial = {
- DomainVerificationRequest(
- setOf(
- "com.test.pkg.one",
- "com.test.pkg.two"
- )
- )
- },
+ testName = "DomainVerificationRequest",
+ initial = { DomainVerificationRequest(massiveSet) },
unparcel = { DomainVerificationRequest.CREATOR.createFromParcel(it) },
assertion = { first, second ->
assertAll<DomainVerificationRequest, Set<String>>(first, second,
@@ -61,15 +70,12 @@
}
),
Parameter(
+ testName = "DomainVerificationInfo",
initial = {
DomainVerificationInfo(
UUID.fromString("703f6d34-6241-4cfd-8176-2e1d23355811"),
"com.test.pkg",
- mapOf(
- "example.com" to 0,
- "example.org" to 1,
- "example.new" to 1000
- )
+ massiveSet.withIndex().associate { it.value to it.index }
)
},
unparcel = { DomainVerificationInfo.CREATOR.createFromParcel(it) },
@@ -86,17 +92,15 @@
}
),
Parameter(
+ testName = "DomainVerificationUserSelection",
initial = {
DomainVerificationUserSelection(
UUID.fromString("703f6d34-6241-4cfd-8176-2e1d23355811"),
"com.test.pkg",
UserHandle.of(10),
true,
- mapOf(
- "example.com" to true,
- "example.org" to false,
- "example.new" to true
- )
+ massiveSet.withIndex()
+ .associate { it.value to (it.index % 3) }
)
},
unparcel = { DomainVerificationUserSelection.CREATOR.createFromParcel(it) },
@@ -114,21 +118,35 @@
first, second, { it.isLinkHandlingAllowed },
{ it.component4() }, IS_EQUAL_TO
)
- assertAll<DomainVerificationUserSelection, Map<String, Boolean>>(
- first, second, { it.hostToUserSelectionMap },
+ assertAll<DomainVerificationUserSelection, Map<String, Int>>(
+ first, second, { it.hostToStateMap },
{ it.component5() }, IS_MAP_EQUAL_TO
)
}
+ ),
+ Parameter(
+ testName = "DomainSet",
+ initial = { DomainSet(massiveSet) },
+ unparcel = { DomainSet.CREATOR.createFromParcel(it) },
+ assertion = { first, second ->
+ assertAll<DomainSet, Set<String>>(
+ first, second,
+ { it.domains }, assertion = IS_EQUAL_TO
+ )
+ }
)
)
class Parameter<T : Parcelable>(
+ val testName: String,
val initial: () -> T,
val unparcel: (Parcel) -> T,
private val assertion: (first: T, second: T) -> Unit
) {
@Suppress("UNCHECKED_CAST")
fun assert(first: Any, second: Any) = assertion(first as T, second as T)
+
+ override fun toString() = testName
}
private fun <T> assertAll(vararg values: T, block: (value: T, other: T) -> Unit) {
@@ -141,11 +159,17 @@
first: T,
second: T,
fieldValue: (T) -> V,
- componentValue: (T) -> V,
+ componentValue: ((T) -> V)? = null,
assertion: (value: V, other: V) -> Unit
) {
- val values = arrayOf<Any>(fieldValue(first), fieldValue(second),
- componentValue(first), componentValue(second))
+ val values = mutableListOf<Any>(fieldValue(first), fieldValue(second))
+ .apply {
+ componentValue?.let {
+ add(it(first))
+ add(it(second))
+ }
+ }
+ .toTypedArray()
values.indices.drop(1).forEach {
@Suppress("UNCHECKED_CAST")
assertion(values[0] as V, values[it] as V)
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
index 2d23fb4..89394837 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
@@ -244,6 +244,14 @@
service(Type.LEGACY_QUERENT, "getLegacyUserState") {
getLegacyState(it.targetPackageName, it.userId)
},
+ service(Type.OWNER_QUERENT, "getOwnersForDomain") {
+ // Re-use package name, since the result itself isn't relevant
+ getOwnersForDomain(it.targetPackageName)
+ },
+ service(Type.OWNER_QUERENT_USER, "getOwnersForDomainUserId") {
+ // Re-use package name, since the result itself isn't relevant
+ getOwnersForDomain(it.targetPackageName, it.userId)
+ },
)
}
@@ -327,6 +335,7 @@
domainSetId
)
) {
+ whenever(getName()) { packageName }
whenever(getPkg()) { mockPkg(packageName) }
whenever(this.domainSetId) { domainSetId }
whenever(userState) {
@@ -357,6 +366,8 @@
Type.SELECTOR_USER -> approvedUserSelector(verifyCrossUser = true)
Type.LEGACY_QUERENT -> legacyQuerent()
Type.LEGACY_SELECTOR -> legacyUserSelector()
+ Type.OWNER_QUERENT -> ownerQuerent(verifyCrossUser = false)
+ Type.OWNER_QUERENT_USER -> ownerQuerent(verifyCrossUser = true)
}.run { /*exhaust*/ }
}
@@ -628,6 +639,80 @@
runTestCases(callingUserId, notCallingUserId, throws = false)
}
+ private fun ownerQuerent(verifyCrossUser: Boolean) {
+ val allowQueryAll = AtomicBoolean(false)
+ val allowUserSelection = AtomicBoolean(false)
+ val allowInteractAcrossUsers = AtomicBoolean(false)
+ val context: Context = mockThrowOnUnmocked {
+ initPermission(
+ allowQueryAll,
+ android.Manifest.permission.QUERY_ALL_PACKAGES
+ )
+ initPermission(
+ allowUserSelection,
+ android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION
+ )
+ initPermission(
+ allowInteractAcrossUsers,
+ android.Manifest.permission.INTERACT_ACROSS_USERS
+ )
+ }
+ val target = params.construct(context)
+
+ fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) {
+ // Owner querent makes no distinction by UID
+ val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID
+ if (throws) {
+ allUids.forEach {
+ assertFails {
+ runMethod(target, it, visible = true, callingUserId, targetUserId)
+ }
+ }
+ } else {
+ allUids.forEach {
+ runMethod(target, it, visible = true, callingUserId, targetUserId)
+ }
+ }
+ }
+
+ val callingUserId = 0
+ val notCallingUserId = 1
+
+ runTestCases(callingUserId, callingUserId, throws = true)
+ if (verifyCrossUser) {
+ runTestCases(callingUserId, notCallingUserId, throws = true)
+ }
+
+ allowQueryAll.set(true)
+
+ runTestCases(callingUserId, callingUserId, throws = true)
+ if (verifyCrossUser) {
+ runTestCases(callingUserId, notCallingUserId, throws = true)
+ }
+
+ allowUserSelection.set(true)
+
+ runTestCases(callingUserId, callingUserId, throws = false)
+ if (verifyCrossUser) {
+ runTestCases(callingUserId, notCallingUserId, throws = true)
+ }
+
+ allowQueryAll.set(false)
+
+ runTestCases(callingUserId, callingUserId, throws = true)
+ if (verifyCrossUser) {
+ runTestCases(callingUserId, notCallingUserId, throws = true)
+ }
+
+ allowQueryAll.set(true)
+ allowInteractAcrossUsers.set(true)
+
+ runTestCases(callingUserId, callingUserId, throws = false)
+ if (verifyCrossUser) {
+ runTestCases(callingUserId, notCallingUserId, throws = false)
+ }
+ }
+
private fun Context.initPermission(boolean: AtomicBoolean, permission: String) {
whenever(enforcePermission(eq(permission), anyInt(), anyInt(), anyString())) {
if (!boolean.get()) {
@@ -694,6 +779,12 @@
LEGACY_QUERENT,
// Holding the legacy preferred apps permission
- LEGACY_SELECTOR
+ LEGACY_SELECTOR,
+
+ // Holding user setting permission, but not targeting a package
+ OWNER_QUERENT,
+
+ // Holding user setting permission, but not targeting a package, but targeting cross user
+ OWNER_QUERENT_USER,
}
}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerUserSelectionOverrideTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerUserSelectionOverrideTest.kt
new file mode 100644
index 0000000..48056a2
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerUserSelectionOverrideTest.kt
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2021 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.pm.test.verify.domain
+
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.pm.parsing.component.ParsedActivity
+import android.content.pm.parsing.component.ParsedIntentInfo
+import android.content.pm.verify.domain.DomainVerificationManager
+import android.content.pm.verify.domain.DomainVerificationUserSelection
+import android.os.Build
+import android.os.PatternMatcher
+import android.os.Process
+import android.util.ArraySet
+import androidx.test.InstrumentationRegistry
+import com.android.server.pm.PackageSetting
+import com.android.server.pm.parsing.pkg.AndroidPackage
+import com.android.server.pm.verify.domain.DomainVerificationService
+import com.android.server.testutils.mockThrowOnUnmocked
+import com.android.server.testutils.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyLong
+import org.mockito.ArgumentMatchers.anyString
+import java.util.UUID
+
+class DomainVerificationManagerUserSelectionOverrideTest {
+
+ companion object {
+ private const val PKG_ONE = "com.test.one"
+ private const val PKG_TWO = "com.test.two"
+ private val UUID_ONE = UUID.fromString("1b041c96-8d37-4932-a858-561bfac5947c")
+ private val UUID_TWO = UUID.fromString("a3389c16-7f9f-4e86-85e3-500d1249c74c")
+
+ private val DOMAIN_ONE =
+ DomainVerificationManagerUserSelectionOverrideTest::class.java.packageName
+
+ private const val STATE_NONE = DomainVerificationUserSelection.DOMAIN_STATE_NONE
+ private const val STATE_SELECTED = DomainVerificationUserSelection.DOMAIN_STATE_SELECTED
+ private const val STATE_VERIFIED = DomainVerificationUserSelection.DOMAIN_STATE_VERIFIED
+ }
+
+ private val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE)
+ private val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO)
+
+ fun makeManager(): DomainVerificationManager =
+ DomainVerificationService(mockThrowOnUnmocked {
+ // Assume the test has every permission necessary
+ whenever(enforcePermission(anyString(), anyInt(), anyInt(), anyString()))
+ whenever(checkPermission(anyString(), anyInt(), anyInt())) {
+ PackageManager.PERMISSION_GRANTED
+ }
+ }, mockThrowOnUnmocked {
+ whenever(linkedApps) { ArraySet<String>() }
+ }, mockThrowOnUnmocked {
+ whenever(isChangeEnabled(anyLong(), any())) { true }
+ }).apply {
+ setConnection(mockThrowOnUnmocked {
+ whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false }
+ whenever(scheduleWriteSettings())
+
+ // Need to provide an internal UID so some permission checks are ignored
+ whenever(callingUid) { Process.ROOT_UID }
+ whenever(callingUserId) { 0 }
+ whenever(getPackageSettingLocked(PKG_ONE)) { pkg1 }
+ whenever(getPackageSettingLocked(PKG_TWO)) { pkg2 }
+ whenever(getPackageLocked(PKG_ONE)) { pkg1.getPkg() }
+ whenever(getPackageLocked(PKG_TWO)) { pkg2.getPkg() }
+ })
+ addPackage(pkg1)
+ addPackage(pkg2)
+
+ // Starting state for all tests is to have domain 1 enabled for the first package
+ setDomainVerificationUserSelection(UUID_ONE, setOf(DOMAIN_ONE), true)
+
+ assertThat(stateFor(PKG_ONE, DOMAIN_ONE)).isEqualTo(STATE_SELECTED)
+ }
+
+ fun mockPkgSetting(pkgName: String, domainSetId: UUID) = mockThrowOnUnmocked<PackageSetting> {
+ val pkg = mockThrowOnUnmocked<AndroidPackage> {
+ whenever(packageName) { pkgName }
+ whenever(targetSdkVersion) { Build.VERSION_CODES.S }
+
+ val activityList = listOf(
+ ParsedActivity().apply {
+ addIntent(
+ ParsedIntentInfo().apply {
+ autoVerify = true
+ addAction(Intent.ACTION_VIEW)
+ addCategory(Intent.CATEGORY_BROWSABLE)
+ addCategory(Intent.CATEGORY_DEFAULT)
+ addDataScheme("http")
+ addDataScheme("https")
+ addDataPath("/sub", PatternMatcher.PATTERN_LITERAL)
+ addDataAuthority(DOMAIN_ONE, null)
+ }
+ )
+ addIntent(
+ ParsedIntentInfo().apply {
+ autoVerify = true
+ addAction(Intent.ACTION_VIEW)
+ addCategory(Intent.CATEGORY_BROWSABLE)
+ addCategory(Intent.CATEGORY_DEFAULT)
+ addDataScheme("http")
+ addDataPath("/sub2", PatternMatcher.PATTERN_LITERAL)
+ addDataAuthority("example2.com", null)
+ }
+ )
+ },
+ )
+
+ whenever(activities) { activityList }
+ }
+
+ whenever(getPkg()) { pkg }
+ whenever(getName()) { pkgName }
+ whenever(this.domainSetId) { domainSetId }
+ whenever(getInstantApp(anyInt())) { false }
+ whenever(firstInstallTime) { 0L }
+ }
+
+ @Test
+ fun anotherPackageTakeoverSuccess() {
+ val manager = makeManager()
+
+ // Attempt override by package 2
+ manager.setDomainVerificationUserSelection(UUID_TWO, setOf(DOMAIN_ONE), true)
+
+ // 1 loses approval
+ assertThat(manager.stateFor(PKG_ONE, DOMAIN_ONE)).isEqualTo(STATE_NONE)
+
+ // 2 gains approval
+ assertThat(manager.stateFor(PKG_TWO, DOMAIN_ONE)).isEqualTo(STATE_SELECTED)
+
+ // 2 is the only owner
+ assertThat(manager.getOwnersForDomain(DOMAIN_ONE).map { it.packageName })
+ .containsExactly(PKG_TWO)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun anotherPackageTakeoverFailure() {
+ val manager = makeManager()
+
+ // Verify 1 to give it a higher approval level
+ manager.setDomainVerificationStatus(UUID_ONE, setOf(DOMAIN_ONE),
+ DomainVerificationManager.STATE_SUCCESS)
+ assertThat(manager.stateFor(PKG_ONE, DOMAIN_ONE)).isEqualTo(STATE_VERIFIED)
+ assertThat(manager.getOwnersForDomain(DOMAIN_ONE).map { it.packageName })
+ .containsExactly(PKG_ONE)
+
+ // Attempt override by package 2
+ manager.setDomainVerificationUserSelection(UUID_TWO, setOf(DOMAIN_ONE), true)
+ }
+
+ private fun DomainVerificationManager.stateFor(pkgName: String, host: String) =
+ getDomainVerificationUserSelection(pkgName)!!.hostToStateMap[host]
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt
index a76d8ce..439048c 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt
@@ -34,7 +34,7 @@
operator fun DomainVerificationUserSelection.component2() = packageName
operator fun DomainVerificationUserSelection.component3() = user
operator fun DomainVerificationUserSelection.component4() = isLinkHandlingAllowed
-operator fun DomainVerificationUserSelection.component5() = hostToUserSelectionMap
+operator fun DomainVerificationUserSelection.component5() = hostToStateMap
operator fun DomainVerificationPersistence.ReadResult.component1() = active
operator fun DomainVerificationPersistence.ReadResult.component2() = restored
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
index 48518f46..010eacf 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
@@ -237,6 +237,7 @@
TEST_UUID
)
) {
+ whenever(getName()) { TEST_PKG }
whenever(getPkg()) { mockPkg() }
whenever(domainSetId) { TEST_UUID }
whenever(userState) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
index 775276b..f7f5928 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
@@ -19,6 +19,7 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
@@ -611,6 +612,7 @@
private static NetworkCapabilities createCapabilities() {
return new NetworkCapabilities().addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
.addCapability(NET_CAPABILITY_VALIDATED);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java
index 69fe140..e0c8b09 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java
@@ -500,7 +500,7 @@
}
@Override
- protected boolean isAntennaInfoListeningSupported() {
+ protected boolean isAntennaInfoSupported() {
return mIsAntennaInfoListeningSupported;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index c4c9ad0..1b58e92 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -353,7 +353,7 @@
@Test
public void testGetLastLocation_ClearOnMockRemoval() {
MockLocationProvider mockProvider = new MockLocationProvider(PROPERTIES, PROVIDER_IDENTITY,
- null);
+ Collections.emptySet());
mockProvider.setAllowed(true);
mManager.setMockProvider(mockProvider);
@@ -1049,7 +1049,7 @@
private final ArrayList<Runnable> mFlushCallbacks = new ArrayList<>();
TestProvider(ProviderProperties properties, CallerIdentity identity) {
- super(DIRECT_EXECUTOR, identity, properties, null);
+ super(DIRECT_EXECUTOR, identity, properties, Collections.emptySet());
}
public void setProviderAllowed(boolean allowed) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java
index e8a0bb5..cf5db2e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java
@@ -43,6 +43,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Collections;
+
@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -72,7 +74,7 @@
.setAccuracy(ACCURACY_FINE)
.build(),
CallerIdentity.forTest(0, 1, "testpackage", "test"),
- null);
+ Collections.emptySet());
mProvider = new MockableLocationProvider(lock);
mProvider.getController().setListener(mListener);
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/test/FakeProvider.java b/services/tests/mockingservicestests/src/com/android/server/location/test/FakeProvider.java
index a1eadbe..2bc1268 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/test/FakeProvider.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/test/FakeProvider.java
@@ -23,6 +23,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.Collections;
public class FakeProvider extends AbstractLocationProvider {
@@ -40,7 +41,7 @@
private final FakeProviderInterface mFakeInterface;
public FakeProvider(FakeProviderInterface fakeInterface) {
- super(Runnable::run, null, null, null);
+ super(Runnable::run, null, null, Collections.emptySet());
mFakeInterface = fakeInterface;
}
diff --git a/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java
index 488e5cd..1870df9 100644
--- a/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java
@@ -30,6 +30,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -59,6 +60,7 @@
}
@Test
+ @Ignore("b/180015146")
public void testAwaitCompletion() throws Exception {
final CountDownLatch readyLatch = new CountDownLatch(2);
final CountDownLatch startLatch = new CountDownLatch(1);
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index e46ab6b..029e9a3 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -45,6 +45,13 @@
import android.app.IUidObserver;
import android.app.Person;
import android.app.admin.DevicePolicyManager;
+import android.app.appsearch.AppSearchBatchResult;
+import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.IAppSearchBatchResultCallback;
+import android.app.appsearch.IAppSearchManager;
+import android.app.appsearch.IAppSearchResultCallback;
+import android.app.appsearch.PackageIdentifier;
import android.app.role.OnRoleHoldersChangedListener;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ActivityNotFoundException;
@@ -78,6 +85,7 @@
import android.os.Bundle;
import android.os.FileUtils;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
import android.os.PersistableBundle;
import android.os.Process;
@@ -150,6 +158,8 @@
return mMockUserManager;
case Context.DEVICE_POLICY_SERVICE:
return mMockDevicePolicyManager;
+ case Context.APP_SEARCH_SERVICE:
+ return new AppSearchManager(getTestContext(), mMockAppSearchManager);
case Context.ROLE_SERVICE:
// RoleManager is final and cannot be mocked, so we only override the inject
// accessor methods in ShortcutService.
@@ -159,6 +169,11 @@
}
@Override
+ public String getOpPackageName() {
+ return getTestContext().getOpPackageName();
+ }
+
+ @Override
public String getSystemServiceName(Class<?> serviceClass) {
return getTestContext().getSystemServiceName(serviceClass);
}
@@ -601,6 +616,123 @@
}
}
+ protected class MockAppSearchManager implements IAppSearchManager {
+
+ protected Map<String, List<PackageIdentifier>> mSchemasPackageAccessible =
+ new ArrayMap<>(1);
+
+ @Override
+ public void setSchema(String packageName, String databaseName, List<Bundle> schemaBundles,
+ List<String> schemasNotPlatformSurfaceable,
+ Map<String, List<Bundle>> schemasPackageAccessibleBundles, boolean forceOverride,
+ int userId, IAppSearchResultCallback callback) throws RemoteException {
+ for (Map.Entry<String, List<Bundle>> entry :
+ schemasPackageAccessibleBundles.entrySet()) {
+ final String key = entry.getKey();
+ final List<PackageIdentifier> packageIdentifiers;
+ if (!mSchemasPackageAccessible.containsKey(key)) {
+ packageIdentifiers = new ArrayList<>(entry.getValue().size());
+ mSchemasPackageAccessible.put(key, packageIdentifiers);
+ } else {
+ packageIdentifiers = mSchemasPackageAccessible.get(key);
+ }
+ for (int i = 0; i < entry.getValue().size(); i++) {
+ packageIdentifiers.add(new PackageIdentifier(entry.getValue().get(i)));
+ }
+ }
+ callback.onResult(AppSearchResult.newSuccessfulResult(null));
+ }
+
+ @Override
+ public void getSchema(String packageName, String databaseName, int userId,
+ IAppSearchResultCallback callback) throws RemoteException {
+ ignore(callback);
+ }
+
+ @Override
+ public void putDocuments(String packageName, String databaseName,
+ List<Bundle> documentBundles, int userId, IAppSearchBatchResultCallback callback)
+ throws RemoteException {
+ ignore(callback);
+ }
+
+ @Override
+ public void getDocuments(String packageName, String databaseName, String namespace,
+ List<String> uris, Map<String, List<String>> typePropertyPaths, int userId,
+ IAppSearchBatchResultCallback callback) throws RemoteException {
+ ignore(callback);
+ }
+
+ @Override
+ public void query(String packageName, String databaseName, String queryExpression,
+ Bundle searchSpecBundle, int userId, IAppSearchResultCallback callback)
+ throws RemoteException {
+ ignore(callback);
+ }
+
+ @Override
+ public void globalQuery(String packageName, String queryExpression, Bundle searchSpecBundle,
+ int userId, IAppSearchResultCallback callback) throws RemoteException {
+ ignore(callback);
+ }
+
+ @Override
+ public void getNextPage(long nextPageToken, int userId, IAppSearchResultCallback callback)
+ throws RemoteException {
+ ignore(callback);
+ }
+
+ @Override
+ public void invalidateNextPageToken(long nextPageToken, int userId) throws RemoteException {
+
+ }
+
+ @Override
+ public void reportUsage(String packageName, String databaseName, String namespace,
+ String uri, long usageTimeMillis, int userId, IAppSearchResultCallback callback)
+ throws RemoteException {
+ ignore(callback);
+ }
+
+ @Override
+ public void removeByUri(String packageName, String databaseName, String namespace,
+ List<String> uris, int userId, IAppSearchBatchResultCallback callback)
+ throws RemoteException {
+ ignore(callback);
+ }
+
+ @Override
+ public void removeByQuery(String packageName, String databaseName, String queryExpression,
+ Bundle searchSpecBundle, int userId, IAppSearchResultCallback callback)
+ throws RemoteException {
+ ignore(callback);
+ }
+
+ @Override
+ public void persistToDisk(int userId) throws RemoteException {
+
+ }
+
+ @Override
+ public void initialize(int userId, IAppSearchResultCallback callback)
+ throws RemoteException {
+ ignore(callback);
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+
+ private void ignore(IAppSearchResultCallback callback) throws RemoteException {
+ callback.onResult(AppSearchResult.newSuccessfulResult(null));
+ }
+
+ private void ignore(IAppSearchBatchResultCallback callback) throws RemoteException {
+ callback.onResult(new AppSearchBatchResult.Builder().build());
+ }
+ }
+
public static class ShortcutActivity extends Activity {
}
@@ -652,6 +784,7 @@
protected PackageManagerInternal mMockPackageManagerInternal;
protected UserManager mMockUserManager;
protected DevicePolicyManager mMockDevicePolicyManager;
+ protected MockAppSearchManager mMockAppSearchManager;
protected UserManagerInternal mMockUserManagerInternal;
protected UsageStatsManagerInternal mMockUsageStatsManagerInternal;
protected ActivityManagerInternal mMockActivityManagerInternal;
@@ -801,6 +934,7 @@
mMockPackageManagerInternal = mock(PackageManagerInternal.class);
mMockUserManager = mock(UserManager.class);
mMockDevicePolicyManager = mock(DevicePolicyManager.class);
+ mMockAppSearchManager = new MockAppSearchManager();
mMockUserManagerInternal = mock(UserManagerInternal.class);
mMockUsageStatsManagerInternal = mock(UsageStatsManagerInternal.class);
mMockActivityManagerInternal = mock(ActivityManagerInternal.class);
diff --git a/services/tests/servicestests/src/com/android/server/pm/OWNERS b/services/tests/servicestests/src/com/android/server/pm/OWNERS
index d825dfd..e15b5f5 100644
--- a/services/tests/servicestests/src/com/android/server/pm/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/pm/OWNERS
@@ -1 +1,3 @@
include /services/core/java/com/android/server/pm/OWNERS
+
+per-file *Shortcut* = file:/core/java/android/content/pm/SHORTCUT_OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest12.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest12.java
new file mode 100644
index 0000000..b17085e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest12.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2021 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.pm;
+
+import android.app.appsearch.PackageIdentifier;
+import android.content.pm.AppSearchShortcutInfo;
+
+import java.util.Random;
+
+/**
+ * Tests for {@link android.app.appsearch.AppSearchManager} and relevant APIs in ShortcutManager.
+ *
+ atest -c com.android.server.pm.ShortcutManagerTest12
+ */
+public class ShortcutManagerTest12 extends BaseShortcutManagerTest {
+
+ public void testUpdateShortcutVisibility_updatesShortcutSchema() {
+
+ final byte[] cert = new byte[20];
+ new Random().nextBytes(cert);
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ mManager.updateShortcutVisibility(CALLING_PACKAGE_2, cert, true);
+ assertTrue(mMockAppSearchManager.mSchemasPackageAccessible.containsKey(
+ AppSearchShortcutInfo.SCHEMA_TYPE));
+ assertTrue(mMockAppSearchManager.mSchemasPackageAccessible.get(
+ AppSearchShortcutInfo.SCHEMA_TYPE).get(0).equals(
+ new PackageIdentifier(CALLING_PACKAGE_2, cert)));
+ });
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/OWNERS b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/OWNERS
index 816bc6b..33385af 100644
--- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/OWNERS
@@ -1 +1 @@
-include /core/java/android/media/soundtrigger/OWNERS
+include /media/aidl/android/media/soundtrigger_middleware/OWNERS
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 488e629..990927b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -555,9 +555,10 @@
activity.setRequestedOrientation(
isScreenPortrait ? SCREEN_ORIENTATION_PORTRAIT : SCREEN_ORIENTATION_LANDSCAPE);
- // Asserts it has orientation derived from bounds.
- assertEquals(isScreenPortrait ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT,
+ // Asserts it has orientation derived requested orientation (fixed orientation letterbox).
+ assertEquals(isScreenPortrait ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE,
activity.getConfiguration().orientation);
+ assertTrue(activity.isLetterboxedForFixedOrientationAndAspectRatio());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
index f91c9d0..e9c356d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
@@ -171,7 +171,7 @@
final Rect activityBounds = new Rect(mFirstActivity.getBounds());
// DAG is portrait (860x1200), so Task and Activity fill DAG.
- assertThat(mFirstTask.isTaskLetterboxed()).isFalse();
+ assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
assertThat(taskBounds).isEqualTo(dagBounds);
assertThat(activityBounds).isEqualTo(taskBounds);
@@ -194,8 +194,8 @@
final Rect activityConfigBounds =
new Rect(mFirstActivity.getConfiguration().windowConfiguration.getBounds());
- // DAG is landscape (1200x860), Task fills parent
- assertThat(mFirstTask.isTaskLetterboxed()).isFalse();
+ // DAG is landscape (1200x860), no fixed orientation letterbox
+ assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
assertThat(mFirstActivity.inSizeCompatMode()).isTrue();
assertThat(newDagBounds.width()).isEqualTo(dagBounds.height());
assertThat(newDagBounds.height()).isEqualTo(dagBounds.width());
@@ -211,7 +211,7 @@
}
@Test
- public void testLaunchLandscapeApp_taskIsLetterboxInDisplayAreaGroup() {
+ public void testLaunchLandscapeApp_activityIsLetterboxForFixedOrientationInDisplayAreaGroup() {
mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
@@ -221,17 +221,18 @@
final Rect taskBounds = new Rect(mFirstTask.getBounds());
final Rect activityBounds = new Rect(mFirstActivity.getBounds());
- // DAG is portrait (860x1200), so Task is letterbox (860x[860x860/1200=616])
- assertThat(mFirstTask.isTaskLetterboxed()).isTrue();
+ // DAG is portrait (860x1200), and activity is letterboxed for fixed orientation
+ // (860x[860x860/1200=616]). Task fills DAG.
+ assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isTrue();
assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
- assertThat(taskBounds.width()).isEqualTo(dagBounds.width());
- assertThat(taskBounds.height())
+ assertThat(taskBounds).isEqualTo(dagBounds);
+ assertThat(activityBounds.width()).isEqualTo(dagBounds.width());
+ assertThat(activityBounds.height())
.isEqualTo(dagBounds.width() * dagBounds.width() / dagBounds.height());
- assertThat(activityBounds).isEqualTo(taskBounds);
}
@Test
- public void testLaunchLandscapeApp_taskLetterboxBecomesActivityLetterboxAfterRotation() {
+ public void testLaunchLandscapeApp_fixedOrientationLetterboxBecomesSizeCompatAfterRotation() {
mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
@@ -245,9 +246,8 @@
final Rect newTaskBounds = new Rect(mFirstTask.getBounds());
final Rect newActivityBounds = new Rect(mFirstActivity.getBounds());
- // DAG is landscape (1200x860), Task fills parent
- // Task letterbox size
- assertThat(mFirstTask.isTaskLetterboxed()).isFalse();
+ // DAG is landscape (1200x860), no fixed orientation letterbox
+ assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
assertThat(mFirstActivity.inSizeCompatMode()).isTrue();
assertThat(newDagBounds.width()).isEqualTo(dagBounds.height());
assertThat(newDagBounds.height()).isEqualTo(dagBounds.width());
@@ -311,7 +311,7 @@
}
@Test
- public void testResizableFixedOrientationApp_taskLevelLetterboxing() {
+ public void testResizableFixedOrientationApp_fixedOrientationLetterboxing() {
mFirstRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */);
mSecondRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */);
@@ -324,7 +324,7 @@
assertThat(mDisplay.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_LANDSCAPE);
assertThat(mFirstRoot.getConfiguration().orientation).isEqualTo(ORIENTATION_PORTRAIT);
assertThat(mFirstActivity.getConfiguration().orientation).isEqualTo(ORIENTATION_PORTRAIT);
- assertThat(mFirstTask.isTaskLetterboxed()).isFalse();
+ assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
// Launch portrait on second DAG
@@ -336,13 +336,13 @@
assertThat(mDisplay.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_PORTRAIT);
assertThat(mSecondRoot.getConfiguration().orientation).isEqualTo(ORIENTATION_LANDSCAPE);
assertThat(mSecondActivity.getConfiguration().orientation).isEqualTo(ORIENTATION_LANDSCAPE);
- assertThat(mSecondTask.isTaskLetterboxed()).isFalse();
+ assertThat(mSecondActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
assertThat(mSecondActivity.inSizeCompatMode()).isFalse();
// First activity is letterboxed in portrait as requested.
assertThat(mFirstRoot.getConfiguration().orientation).isEqualTo(ORIENTATION_LANDSCAPE);
assertThat(mFirstActivity.getConfiguration().orientation).isEqualTo(ORIENTATION_PORTRAIT);
- assertThat(mFirstTask.isTaskLetterboxed()).isTrue();
+ assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isTrue();
assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index e843dd7..b73c664 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -23,6 +23,7 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
@@ -43,7 +44,6 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.same;
@@ -126,6 +126,7 @@
// Put app window into freeform and then make it a compat app.
final Rect bounds = new Rect(100, 100, 400, 600);
mTask.setBounds(bounds);
+
prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
assertEquals(bounds, mActivity.getBounds());
@@ -194,12 +195,7 @@
new TestDisplayContent.Builder(mAtm, 1000, 2000)
.setDensityDpi(200).build();
- mActivity = new ActivityBuilder(mAtm)
- .setTask(mTask)
- .setResizeMode(RESIZE_MODE_UNRESIZEABLE)
- .setMaxAspectRatio(1.5f)
- .build();
- mActivity.mVisibleRequested = true;
+ prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED);
final Rect originalBounds = new Rect(mActivity.getBounds());
final int originalDpi = mActivity.getConfiguration().densityDpi;
@@ -566,18 +562,18 @@
.setResizeMode(ActivityInfo.RESIZE_MODE_UNRESIZEABLE)
.setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
.build();
- assertTrue(activity.shouldUseSizeCompatMode());
+ assertTrue(activity.shouldCreateCompatDisplayInsets());
// The non-resizable activity should not be size compat because it is on a resizable task
// in multi-window mode.
mTask.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
- assertFalse(activity.shouldUseSizeCompatMode());
+ assertFalse(activity.shouldCreateCompatDisplayInsets());
// The non-resizable activity should not be size compat because the display support
// changing windowing mode from fullscreen to freeform.
mTask.mDisplayContent.setDisplayWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
mTask.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
- assertFalse(activity.shouldUseSizeCompatMode());
+ assertFalse(activity.shouldCreateCompatDisplayInsets());
}
@Test
@@ -597,7 +593,7 @@
SizeCompatTests.class.getName()))
.setUid(android.os.Process.myUid())
.build();
- assertFalse(activity.shouldUseSizeCompatMode());
+ assertFalse(activity.shouldCreateCompatDisplayInsets());
}
@Test
@@ -653,7 +649,7 @@
}
@Test
- public void testDisplayIgnoreOrientationRequest_fixedOrientationAppLaunchedInTaskLetterbox() {
+ public void testDisplayIgnoreOrientationRequest_fixedOrientationAppLaunchedLetterbox() {
// Set up a display in landscape and ignoring orientation request.
setUpDisplaySizeWithApp(2800, 1400);
mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
@@ -662,7 +658,6 @@
prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);
final Rect displayBounds = new Rect(mActivity.mDisplayContent.getBounds());
- final Rect taskBounds = new Rect(mTask.getBounds());
final Rect activityBounds = new Rect(mActivity.getBounds());
// Display shouldn't be rotated.
@@ -670,19 +665,19 @@
mActivity.mDisplayContent.getLastOrientation());
assertTrue(displayBounds.width() > displayBounds.height());
- // App should launch in task level letterboxing.
- assertTrue(mTask.isTaskLetterboxed());
+ // App should launch in fixed orientation letterbox.
+ assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
assertFalse(mActivity.inSizeCompatMode());
- assertEquals(taskBounds, activityBounds);
- // Task bounds should be 700x1400 with the ratio as the display.
- assertEquals(displayBounds.height(), taskBounds.height());
+ // Activity bounds should be 700x1400 with the ratio as the display.
+ assertEquals(displayBounds.height(), activityBounds.height());
assertEquals(displayBounds.height() * displayBounds.height() / displayBounds.width(),
- taskBounds.width());
+ activityBounds.width());
}
@Test
- public void testDisplayIgnoreOrientationRequest_taskLetterboxBecameSizeCompatAfterRotate() {
+ public void
+ testDisplayIgnoreOrientationRequest_orientationLetterboxBecameSizeCompatAfterRotate() {
// Set up a display in landscape and ignoring orientation request.
setUpDisplaySizeWithApp(2800, 1400);
mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
@@ -700,7 +695,7 @@
assertTrue(displayBounds.width() < displayBounds.height());
// App should be in size compat.
- assertFalse(mTask.isTaskLetterboxed());
+ assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
assertScaled();
assertEquals(activityBounds.width(), newActivityBounds.width());
assertEquals(activityBounds.height(), newActivityBounds.height());
@@ -719,7 +714,7 @@
Rect activityBounds = new Rect(mActivity.getBounds());
// App should launch in fullscreen.
- assertFalse(mTask.isTaskLetterboxed());
+ assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
assertFalse(mActivity.inSizeCompatMode());
assertEquals(displayBounds, activityBounds);
@@ -731,7 +726,7 @@
assertTrue(displayBounds.width() > displayBounds.height());
// App should be in size compat.
- assertFalse(mTask.isTaskLetterboxed());
+ assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
assertScaled();
// App bounds should be 700x1400 with the ratio as the display.
@@ -741,7 +736,7 @@
}
@Test
- public void testDisplayIgnoreOrientationRequest_newLaunchedOrientationAppInTaskLetterbox() {
+ public void testDisplayIgnoreOrientationRequest_newLaunchedOrientationAppInLetterbox() {
// Set up a display in landscape and ignoring orientation request.
setUpDisplaySizeWithApp(2800, 1400);
final DisplayContent display = mActivity.mDisplayContent;
@@ -750,7 +745,7 @@
// Portrait fixed app without max aspect.
prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);
- assertTrue(mTask.isTaskLetterboxed());
+ assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
assertFalse(mActivity.inSizeCompatMode());
// Launch another portrait fixed app.
@@ -765,19 +760,19 @@
// Update with new activity requested orientation and recompute bounds with no previous
// size compat cache.
verify(mTask).onDescendantOrientationChanged(same(newActivity));
- verify(mTask).computeFullscreenBounds(any(), any());
final Rect displayBounds = new Rect(display.getBounds());
final Rect taskBounds = new Rect(mTask.getBounds());
final Rect newActivityBounds = new Rect(newActivity.getBounds());
- // Task and app bounds should be 700x1400 with the ratio as the display.
- assertTrue(mTask.isTaskLetterboxed());
+ // Task and display bounds should be equal while activity should be letterboxed and
+ // has 700x1400 bounds with the ratio as the display.
+ assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
assertFalse(newActivity.inSizeCompatMode());
- assertEquals(taskBounds, newActivityBounds);
- assertEquals(displayBounds.height(), taskBounds.height());
+ assertEquals(taskBounds, displayBounds);
+ assertEquals(displayBounds.height(), newActivityBounds.height());
assertEquals(displayBounds.height() * displayBounds.height() / displayBounds.width(),
- taskBounds.width());
+ newActivityBounds.width());
}
@Test
@@ -790,7 +785,7 @@
// Portrait fixed app without max aspect.
prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);
- assertTrue(mTask.isTaskLetterboxed());
+ assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
assertFalse(mActivity.inSizeCompatMode());
// Launch another portrait fixed app with max aspect ratio as 1.3.
@@ -806,21 +801,20 @@
// Update with new activity requested orientation and recompute bounds with no previous
// size compat cache.
verify(mTask).onDescendantOrientationChanged(same(newActivity));
- verify(mTask).computeFullscreenBounds(any(), any());
final Rect displayBounds = new Rect(display.getBounds());
final Rect taskBounds = new Rect(mTask.getBounds());
final Rect newActivityBounds = new Rect(newActivity.getBounds());
- // Task bounds should be (1400 / 1.3 = 1076)x1400 with the app requested ratio.
- assertTrue(mTask.isTaskLetterboxed());
- assertEquals(displayBounds.height(), taskBounds.height());
- assertEquals((long) Math.rint(taskBounds.height() / newActivity.info.maxAspectRatio),
- taskBounds.width());
+ // Task bounds should fill parent bounds.
+ assertEquals(displayBounds, taskBounds);
- // App bounds should be fullscreen in Task bounds.
+ // Activity bounds should be (1400 / 1.3 = 1076)x1400 with the app requested ratio.
+ assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
assertFalse(newActivity.inSizeCompatMode());
- assertEquals(taskBounds, newActivityBounds);
+ assertEquals(displayBounds.height(), newActivityBounds.height());
+ assertEquals((long) Math.rint(newActivityBounds.height() / newActivity.info.maxAspectRatio),
+ newActivityBounds.width());
}
@Test
@@ -834,26 +828,23 @@
prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);
clearInvocations(mActivity);
- assertTrue(mTask.isTaskLetterboxed());
+ assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
assertFalse(mActivity.inSizeCompatMode());
- assertEquals(mTask.getLastTaskBoundsComputeActivity(), mActivity);
// Rotate display to portrait.
rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
// App should be in size compat.
- assertFalse(mTask.isTaskLetterboxed());
+ assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
assertScaled();
- assertEquals(mTask.getLastTaskBoundsComputeActivity(), mActivity);
final Rect activityBounds = new Rect(mActivity.getBounds());
mTask.resumeTopActivityUncheckedLocked(null /* prev */, null /* options */);
// App still in size compat, and the bounds don't change.
verify(mActivity, never()).clearSizeCompatMode();
- assertFalse(mTask.isTaskLetterboxed());
+ assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
assertScaled();
- assertEquals(mTask.getLastTaskBoundsComputeActivity(), mActivity);
assertEquals(activityBounds, mActivity.getBounds());
}
@@ -867,22 +858,22 @@
// Portrait fixed app.
prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);
- // In Task letterbox
- assertTrue(mTask.isTaskLetterboxed());
+ // In fixed orientation letterbox
+ assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
assertFalse(mActivity.inSizeCompatMode());
// Rotate display to portrait.
rotateDisplay(display, ROTATION_90);
// App should be in size compat.
- assertFalse(mTask.isTaskLetterboxed());
+ assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
assertScaled();
// Rotate display to landscape.
rotateDisplay(display, ROTATION_180);
// In Task letterbox
- assertTrue(mTask.isTaskLetterboxed());
+ assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
assertFalse(mActivity.inSizeCompatMode());
}
@@ -898,22 +889,22 @@
// Landscape fixed app.
prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_LANDSCAPE);
- // In Task letterbox
- assertTrue(mTask.isTaskLetterboxed());
+ // In fixed orientation letterbox
+ assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
assertFalse(mActivity.inSizeCompatMode());
// Rotate display to portrait.
rotateDisplay(display, ROTATION_90);
// App should be in size compat.
- assertFalse(mTask.isTaskLetterboxed());
+ assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
assertScaled();
// Rotate display to landscape.
rotateDisplay(display, ROTATION_180);
- // In Task letterbox
- assertTrue(mTask.isTaskLetterboxed());
+ // In fixed orientation letterbox
+ assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
assertFalse(mActivity.inSizeCompatMode());
}
@@ -972,21 +963,21 @@
addWindowToActivity(mActivity);
mActivity.mRootWindowContainer.performSurfacePlacement();
- // Split screen is also in portrait [1000,1400], so Task should be in letterbox, and
- // activity fills task.
- assertEquals(ORIENTATION_LANDSCAPE, mTask.getConfiguration().orientation);
+ // Split screen is also in portrait [1000,1400], so activty should be in fixed orientation
+ // letterbox.
+ assertEquals(ORIENTATION_PORTRAIT, mTask.getConfiguration().orientation);
assertEquals(ORIENTATION_LANDSCAPE, mActivity.getConfiguration().orientation);
assertFitted();
- assertTrue(mTask.isTaskLetterboxed());
+ assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
- // Letterbox should fill the gap between the split screen and the letterboxed task.
+ // Letterbox should fill the gap between the split screen and the letterboxed activity.
final Rect primarySplitBounds = new Rect(organizer.mPrimary.getBounds());
- final Rect letterboxedTaskBounds = new Rect(mTask.getBounds());
- assertTrue(primarySplitBounds.contains(letterboxedTaskBounds));
- assertEquals(new Rect(letterboxedTaskBounds.left - primarySplitBounds.left,
- letterboxedTaskBounds.top - primarySplitBounds.top,
- primarySplitBounds.right - letterboxedTaskBounds.right,
- primarySplitBounds.bottom - letterboxedTaskBounds.bottom),
+ final Rect letterboxedBounds = new Rect(mActivity.getBounds());
+ assertTrue(primarySplitBounds.contains(letterboxedBounds));
+ assertEquals(new Rect(letterboxedBounds.left - primarySplitBounds.left,
+ letterboxedBounds.top - primarySplitBounds.top,
+ primarySplitBounds.right - letterboxedBounds.right,
+ primarySplitBounds.bottom - letterboxedBounds.bottom),
mActivity.getLetterboxInsets());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index 4984799..f8346efb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -683,12 +683,12 @@
}
@Test
- public void testLaunchesAppInWindowOnFreeformDisplay() {
+ public void testLaunchesPortraitSizeCompatOnFreeformLandscapeDisplayWithFreeformSizeCompat() {
mAtm.mSizeCompatFreeform = true;
final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
- Rect expectedLaunchBounds = new Rect(0, 0, 200, 100);
+ Rect expectedLaunchBounds = new Rect(0, 0, 100, 200);
final ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
@@ -699,6 +699,7 @@
mCurrent.mBounds.set(expectedLaunchBounds);
mActivity.info.resizeMode = RESIZE_MODE_UNRESIZEABLE;
+ mActivity.info.screenOrientation = SCREEN_ORIENTATION_PORTRAIT;
assertEquals(RESULT_CONTINUE,
new CalculateRequestBuilder().setOptions(options).calculate());
@@ -710,6 +711,38 @@
}
@Test
+ public void testLaunchesLandscapeSizeCompatOnFreeformLandscapeDisplayWithFreeformSizeCompat() {
+ mAtm.mSizeCompatFreeform = true;
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
+ WINDOWING_MODE_FREEFORM);
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
+ mActivity.info.resizeMode = RESIZE_MODE_UNRESIZEABLE;
+ mActivity.info.screenOrientation = SCREEN_ORIENTATION_LANDSCAPE;
+ assertEquals(RESULT_CONTINUE,
+ new CalculateRequestBuilder().setOptions(options).calculate());
+
+ assertEquivalentWindowingMode(WINDOWING_MODE_FULLSCREEN, mResult.mWindowingMode,
+ WINDOWING_MODE_FREEFORM);
+ }
+
+ @Test
+ public void testLaunchesPortraitUnresizableOnFreeformDisplayWithFreeformSizeCompat() {
+ mAtm.mSizeCompatFreeform = true;
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
+ WINDOWING_MODE_FREEFORM);
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
+ mActivity.info.resizeMode = RESIZE_MODE_UNRESIZEABLE;
+ mActivity.info.screenOrientation = SCREEN_ORIENTATION_PORTRAIT;
+ assertEquals(RESULT_CONTINUE,
+ new CalculateRequestBuilder().setOptions(options).calculate());
+
+ assertEquivalentWindowingMode(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode,
+ WINDOWING_MODE_FREEFORM);
+ }
+
+ @Test
public void testSkipsForceMaximizingAppsOnNonFreeformDisplay() {
final ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
index 0eb8c8d..d853b93 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
@@ -266,8 +266,9 @@
root.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT);
assertEquals(root, task.getRootActivity());
assertEquals(SCREEN_ORIENTATION_PORTRAIT, task.getRootActivity().getOrientation());
- assertThat(task.getBounds().width()).isLessThan(task.getBounds().height());
- assertEquals(fullScreenBounds.height(), task.getBounds().height());
+ // Portrait orientation is enforced on activity level. Task should fill fullscreen bounds.
+ assertThat(task.getBounds().height()).isLessThan(task.getBounds().width());
+ assertEquals(fullScreenBounds, task.getBounds());
// Top activity gets used
final ActivityRecord top = new ActivityBuilder(mAtm).setTask(task).setParentTask(stack)
@@ -286,8 +287,11 @@
// Fix the display orientation to portrait which is 90 degrees for the test display.
dr.setUserRotation(USER_ROTATION_FREE, ROTATION_90);
- assertThat(task.getBounds().width()).isGreaterThan(task.getBounds().height());
- assertEquals(fullScreenBoundsPort.width(), task.getBounds().width());
+ // Fixed orientation request should be resolved on activity level. Task fills display
+ // bounds.
+ assertThat(task.getBounds().height()).isGreaterThan(task.getBounds().width());
+ assertThat(top.getBounds().width()).isGreaterThan(top.getBounds().height());
+ assertEquals(fullScreenBoundsPort, task.getBounds());
// in FREEFORM, no constraint
final Rect freeformBounds = new Rect(display.getBounds());
@@ -297,10 +301,11 @@
task.setBounds(freeformBounds);
assertEquals(freeformBounds, task.getBounds());
- // FULLSCREEN letterboxes bounds
+ // FULLSCREEN letterboxes bounds on activity level, no constraint on task level.
stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
- assertThat(task.getBounds().width()).isGreaterThan(task.getBounds().height());
- assertEquals(fullScreenBoundsPort.width(), task.getBounds().width());
+ assertThat(task.getBounds().height()).isGreaterThan(task.getBounds().width());
+ assertThat(top.getBounds().width()).isGreaterThan(top.getBounds().height());
+ assertEquals(fullScreenBoundsPort, task.getBounds());
// FREEFORM restores bounds as before
stack.setWindowingMode(WINDOWING_MODE_FREEFORM);
@@ -327,9 +332,10 @@
assertEquals(fullScreenBounds, task.getBounds());
- // Setting app to fixed portrait fits within parent
+ // Setting app to fixed portrait fits within parent on activity level. Task fills parent.
root.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT);
- assertThat(task.getBounds().width()).isLessThan(task.getBounds().height());
+ assertThat(root.getBounds().width()).isLessThan(root.getBounds().height());
+ assertEquals(task.getBounds(), fullScreenBounds);
assertEquals(SCREEN_ORIENTATION_PORTRAIT, task.getOrientation());
}
@@ -424,7 +430,8 @@
// to the input bounds.
final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build();
final ActivityRecord.CompatDisplayInsets compatIntsets =
- new ActivityRecord.CompatDisplayInsets(display, activity);
+ new ActivityRecord.CompatDisplayInsets(
+ display, activity, /* fixedOrientationBounds= */ null);
task.computeConfigResourceOverrides(inOutConfig, parentConfig, compatIntsets);
assertEquals(largerLandscapeBounds, inOutConfig.windowConfiguration.getAppBounds());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 1c0f640..c3eb5c4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -257,24 +257,4 @@
task.resolveOverrideConfiguration(parentConfig);
assertThat(resolvedOverride.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
}
-
- @Test
- public void testCleanUpActivityReferences_clearLastTaskBoundsComputeActivity() {
- final Task rootTask = createTaskStackOnDisplay(mDisplayContent);
- final Task leafTask = createTaskInStack(rootTask, 0 /* userId */);
- final ActivityRecord activity2 = createActivityRecord(mDisplayContent, leafTask);
- final ActivityRecord activity1 = createActivityRecord(mDisplayContent, leafTask);
- activity1.finishing = false;
- leafTask.resolveOverrideConfiguration(rootTask.getConfiguration());
-
- assertEquals(activity1, leafTask.getLastTaskBoundsComputeActivity());
-
- leafTask.cleanUpActivityReferences(activity2);
-
- assertNotNull(leafTask.getLastTaskBoundsComputeActivity());
-
- leafTask.cleanUpActivityReferences(activity1);
-
- assertNull(leafTask.getLastTaskBoundsComputeActivity());
- }
}
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
index 5cf8de8..f20ee7e 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -138,6 +138,24 @@
* }
* }
* }
+ *
+ * </pre>
+ * <p id="companionInCallService">
+ * <h3>Access to InCallService for Wearable Devices</h3>
+ * <ol>
+ * If your app is a third-party companion app and wants to access InCallService APIs, what your
+ * app could do are:
+ * <p>
+ * <ol>
+ * <li> Declare MANAGE_ONGOING_CALLS permission in your manifest
+ * <li> Associate with a physical wearable device via the
+ * {@link android.companion.CompanionDeviceManager} API as a companion app. See:
+ * https://developer.android.com/guide/topics/connectivity/companion-device-pairing
+ * <li> Implement this InCallService with BIND_INCALL_SERVICE permission
+ * </ol>
+ * </ol>
+ * <p>
+ *
* </pre>
* <p id="incomingCallNotification">
* <h3>Showing the Incoming Call Notification</h3>
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 472d639..17749e8 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -1702,22 +1702,22 @@
}
/**
- * Returns whether the caller has {@link InCallService} access for companion apps.
- *
- * A companion app is an app associated with a physical wearable device via the
- * {@link android.companion.CompanionDeviceManager} API.
+ * Returns whether the caller has {@link android.Manifest.permission#MANAGE_ONGOING_CALLS}
+ * permission. The permission can be obtained by associating with a physical wearable device
+ * via the {@link android.companion.CompanionDeviceManager} API as a companion app. If the
+ * caller app has the permission, it has {@link InCallService} access to manage ongoing calls.
*
* @return {@code true} if the caller has {@link InCallService} access for
* companion app; {@code false} otherwise.
*/
- public boolean hasCompanionInCallServiceAccess() {
+ public boolean hasManageOngoingCallsPermission() {
ITelecomService service = getTelecomService();
if (service != null) {
try {
- return service.hasCompanionInCallServiceAccess(
+ return service.hasManageOngoingCallsPermission(
mContext.getOpPackageName());
} catch (RemoteException e) {
- Log.e(TAG, "RemoteException calling hasCompanionInCallServiceAccess().", e);
+ Log.e(TAG, "RemoteException calling hasManageOngoingCallsPermission().", e);
if (!isSystemProcess()) {
e.rethrowAsRuntimeException();
}
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index 88ef1b0..eb106b5 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -179,9 +179,9 @@
boolean isInCall(String callingPackage, String callingFeatureId);
/**
- * @see TelecomServiceImpl#hasCompanionInCallServiceAccess
+ * @see TelecomServiceImpl#hasManageOngoingCallsPermission
*/
- boolean hasCompanionInCallServiceAccess(String callingPackage);
+ boolean hasManageOngoingCallsPermission(String callingPackage);
/**
* @see TelecomServiceImpl#isInManagedCall
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 4b57fcb..b52f4919 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -33,7 +33,11 @@
import android.service.carrier.CarrierService;
import android.telecom.TelecomManager;
import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.ImsRegistrationAttributes;
import android.telephony.ims.ImsSsData;
+import android.telephony.ims.SipDelegateManager;
+import android.telephony.ims.feature.MmTelFeature;
+import android.telephony.ims.feature.RcsFeature;
import com.android.internal.telephony.ICarrierConfigLoader;
import com.android.telephony.Rlog;
@@ -3672,14 +3676,13 @@
/** Prefix of all ImsServiceEntitlement.KEY_* constants. */
public static final String KEY_PREFIX = "imsserviceentitlement.";
-
/** The address of the entitlement configuration server. */
- public static final String KEY_AES_URL_STRING = KEY_PREFIX + "aes_url_string";
-
+ public static final String KEY_ENTITLEMENT_SERVER_URL_STRING =
+ KEY_PREFIX + "entitlement_server_url_string";
private static PersistableBundle getDefaults() {
PersistableBundle defaults = new PersistableBundle();
- defaults.putString(KEY_AES_URL_STRING, "");
+ defaults.putString(KEY_ENTITLEMENT_SERVER_URL_STRING, "");
return defaults;
}
}
@@ -4007,6 +4010,43 @@
KEY_PREFIX + "enable_presence_publish_bool";
/**
+ * Each string in this array contains a mapping between the service-id and version portion
+ * of the service-description element and the associated IMS feature tag(s) that are
+ * associated with each element (see RCC.07 Table 7).
+ * <p>
+ * Each string contains 3 parts, which define the mapping between service-description and
+ * feature tag(s) that must be present in the IMS REGISTER for the RCS service to be
+ * published as part of the RCS PUBLISH procedure:
+ * [service-id]|[version]|[desc]|[feature_tag];[feature_tag];...
+ * <ul>
+ * <li>[service-id]: the service-id element associated with the RCS capability.</li>
+ * <li>[version]: The version element associated with that service-id</li>
+ * <li>[desc]: The optional desecription element associated with that service-id</li>
+ * <li>[feature_tag];[feature_tag]: The list of all feature tags associated with this
+ * capability that MUST ALL be present in the IMS registration for this this
+ * capability to be published to the network.</li>
+ * </ul>
+ * <p>
+ * Features managed by the framework will be considered capable when the ImsService reports
+ * that those services are capable via the
+ * {@link MmTelFeature#notifyCapabilitiesStatusChanged(MmTelFeature.MmTelCapabilities)} or
+ * {@link RcsFeature#notifyCapabilitiesStatusChanged(RcsFeature.RcsImsCapabilities)} APIs.
+ * For RCS services not managed by the framework, the capability of these services are
+ * determined by looking at the feature tags associated with the IMS registration using the
+ * {@link ImsRegistrationAttributes} API and mapping them to the service-description map.
+ * <p>
+ * The framework contains a default value of this key, which is based off of RCC.07
+ * specification. Capabilities based of carrier extensions may be added to this list on a
+ * carrier-by-carrier basis as required in order to support additional services in the
+ * PUBLISH. If this list contains a service-id and version that overlaps with the default,
+ * it will override the framework default.
+ * @hide
+ */
+ @SystemApi
+ public static final String KEY_PUBLISH_SERVICE_DESC_FEATURE_TAG_MAP_OVERRIDE_STRING_ARRAY =
+ KEY_PREFIX + "publish_service_desc_feature_tag_map_override_string_array";
+
+ /**
* Flag indicating whether or not this carrier supports the exchange of phone numbers with
* the carrier's RCS presence server in order to retrieve the RCS capabilities of requested
* contacts used in the RCS User Capability Exchange (UCE) procedure. See RCC.71, section 3
@@ -4065,6 +4105,8 @@
defaults.putInt(KEY_WIFI_OFF_DEFERRING_TIME_MILLIS_INT, 4000);
defaults.putBoolean(KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, false);
defaults.putBoolean(KEY_ENABLE_PRESENCE_PUBLISH_BOOL, false);
+ defaults.putStringArray(KEY_PUBLISH_SERVICE_DESC_FEATURE_TAG_MAP_OVERRIDE_STRING_ARRAY,
+ new String[] {});
defaults.putBoolean(KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL, false);
defaults.putBoolean(KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL, false);
defaults.putBoolean(KEY_ENABLE_PRESENCE_GROUP_SUBSCRIBE_BOOL, true);
diff --git a/telephony/java/android/telephony/SignalStrengthUpdateRequest.java b/telephony/java/android/telephony/SignalStrengthUpdateRequest.java
index af67ed2..fe7e5976 100644
--- a/telephony/java/android/telephony/SignalStrengthUpdateRequest.java
+++ b/telephony/java/android/telephony/SignalStrengthUpdateRequest.java
@@ -187,7 +187,7 @@
return mIsSystemThresholdReportingRequestedWhileIdle;
}
- /*
+ /**
* @return the live token of the request
*
* @hide
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index f64f428..61e809b 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -29,6 +29,7 @@
import android.annotation.LongDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
@@ -8579,6 +8580,9 @@
*/
@Deprecated
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ @RequiresFeature(
+ enforcement = "android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported",
+ value = TelephonyManager.CAPABILITY_ALLOWED_NETWORK_TYPES_USED)
@SystemApi
public boolean setAllowedNetworkTypes(@NetworkTypeBitMask long allowedNetworkTypes) {
try {
@@ -8670,6 +8674,9 @@
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ @RequiresFeature(
+ enforcement = "android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported",
+ value = TelephonyManager.CAPABILITY_ALLOWED_NETWORK_TYPES_USED)
public void setAllowedNetworkTypesForReason(@AllowedNetworkTypesReason int reason,
@NetworkTypeBitMask long allowedNetworkTypes) {
if (!isValidAllowedNetworkTypesReason(reason)) {
@@ -8708,6 +8715,9 @@
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @RequiresFeature(
+ enforcement = "android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported",
+ value = TelephonyManager.CAPABILITY_ALLOWED_NETWORK_TYPES_USED)
public @NetworkTypeBitMask long getAllowedNetworkTypesForReason(
@AllowedNetworkTypesReason int reason) {
if (!isValidAllowedNetworkTypesReason(reason)) {
diff --git a/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java b/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java
index 5eb75e7..bdf628b 100644
--- a/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java
+++ b/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java
@@ -21,6 +21,7 @@
import android.annotation.StringDef;
import android.annotation.SystemApi;
import android.net.Uri;
+import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
@@ -39,6 +40,15 @@
public final class RcsContactPresenceTuple implements Parcelable {
/**
+ * The service ID used to indicate that service discovery via presence is available.
+ * <p>
+ * See RCC.07 v5.0 specification for more information.
+ * @hide
+ */
+ public static final String SERVICE_ID_PRESENCE =
+ "org.3gpp.urn:urn-7:3gpp-application.ims.iari.rcse.dp";
+
+ /**
* The service ID used to indicate that MMTEL service is available.
* <p>
* See the GSMA RCC.07 specification for more information.
@@ -329,6 +339,13 @@
public @NonNull @DuplexMode List<String> getUnsupportedDuplexModes() {
return Collections.unmodifiableList(mUnsupportedDuplexModeList);
}
+
+ @Override
+ public String toString() {
+ return "servCaps{" + "a=" + mIsAudioCapable + ", v=" + mIsVideoCapable
+ + ", supported=" + mSupportedDuplexModeList + ", unsupported="
+ + mUnsupportedDuplexModeList + '}';
+ }
}
/**
@@ -487,4 +504,36 @@
public @Nullable ServiceCapabilities getServiceCapabilities() {
return mServiceCapabilities;
}
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder("{");
+ if (Build.IS_ENG) {
+ builder.append("u=");
+ builder.append(mContactUri);
+ } else {
+ builder.append("u=");
+ builder.append(mContactUri != null ? "XXX" : "null");
+ }
+ builder.append(", id=");
+ builder.append(mServiceId);
+ builder.append(", v=");
+ builder.append(mServiceVersion);
+ builder.append(", s=");
+ builder.append(mStatus);
+ if (mTimestamp != null) {
+ builder.append(", timestamp=");
+ builder.append(mTimestamp);
+ }
+ if (mServiceDescription != null) {
+ builder.append(", servDesc=");
+ builder.append(mServiceDescription);
+ }
+ if (mServiceCapabilities != null) {
+ builder.append(", servCaps=");
+ builder.append(mServiceCapabilities);
+ }
+ builder.append("}");
+ return builder.toString();
+ }
}
diff --git a/telephony/java/android/telephony/ims/RcsContactUceCapability.java b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
index fe85502..9299fed 100644
--- a/telephony/java/android/telephony/ims/RcsContactUceCapability.java
+++ b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
@@ -21,6 +21,7 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.net.Uri;
+import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
@@ -344,4 +345,39 @@
public @NonNull Uri getContactUri() {
return mContactUri;
}
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder("RcsContactUceCapability");
+ if (mCapabilityMechanism == CAPABILITY_MECHANISM_PRESENCE) {
+ builder.append("(presence) {");
+ } else if (mCapabilityMechanism == CAPABILITY_MECHANISM_OPTIONS) {
+ builder.append("(options) {");
+ } else {
+ builder.append("(?) {");
+ }
+ if (Build.IS_ENG) {
+ builder.append("uri=");
+ builder.append(mContactUri);
+ } else {
+ builder.append("uri (isNull)=");
+ builder.append(mContactUri != null ? "XXX" : "null");
+ }
+ builder.append(", sourceType=");
+ builder.append(mSourceType);
+ builder.append(", requestResult=");
+ builder.append(mRequestResult);
+
+ if (mCapabilityMechanism == CAPABILITY_MECHANISM_PRESENCE) {
+ builder.append(", presenceTuples={");
+ builder.append(mPresenceTuples);
+ builder.append("}");
+ } else if (mCapabilityMechanism == CAPABILITY_MECHANISM_OPTIONS) {
+ builder.append(", featureTags={");
+ builder.append(mFeatureTags);
+ builder.append("}");
+ }
+
+ return builder.toString();
+ }
}
diff --git a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
index cc050be..34984e0 100644
--- a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
@@ -37,6 +37,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
+import java.util.Arrays;
import java.util.HashMap;
/**
@@ -368,7 +369,13 @@
}
private void onNotifyRcsAutoConfigurationReceived(byte[] config, boolean isCompressed) {
- mRcsConfigData = isCompressed ? RcsConfig.decompressGzip(config) : config;
+ // cache uncompressed config
+ config = isCompressed ? RcsConfig.decompressGzip(config) : config;
+ if (Arrays.equals(mRcsConfigData, config)) {
+ return;
+ }
+ mRcsConfigData = config;
+
// can be null in testing
if (mRcsCallbacks != null) {
mRcsCallbacks.broadcastAction(c -> {
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 0048d53..e87f3d9 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2325,6 +2325,16 @@
void triggerRcsReconfiguration(int subId);
/**
+ * Enables or disables the test mode for RCS VoLTE single registration.
+ */
+ void setRcsSingleRegistrationTestModeEnabled(boolean enabled);
+
+ /**
+ * Gets the test mode for RCS VoLTE single registration.
+ */
+ boolean getRcsSingleRegistrationTestModeEnabled();
+
+ /**
* Overrides the config of RCS VoLTE single registration enabled for the device.
*/
void setDeviceSingleRegistrationEnabledOverride(String enabled);
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
index 38a88d3..26afb79 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
@@ -153,31 +153,11 @@
@Presubmit
@Test
- fun navBarLayerIsAlwaysVisible() {
- Assume.assumeFalse(testSpec.isRotated)
- testSpec.navBarLayerIsAlwaysVisible()
- }
-
- @FlakyTest
- @Test
- fun navBarLayerIsAlwaysVisible_Flaky() {
- Assume.assumeTrue(testSpec.isRotated)
- testSpec.navBarLayerIsAlwaysVisible()
- }
+ fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible()
@Presubmit
@Test
- fun statusBarLayerIsAlwaysVisible() {
- Assume.assumeFalse(testSpec.isRotated)
- testSpec.statusBarLayerIsAlwaysVisible()
- }
-
- @FlakyTest
- @Test
- fun statusBarLayerIsAlwaysVisible_Flaky() {
- Assume.assumeTrue(testSpec.isRotated)
- testSpec.statusBarLayerIsAlwaysVisible()
- }
+ fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible()
@Presubmit
@Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
index 476708c..2c4c627 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
@@ -17,9 +17,8 @@
package com.android.server.wm.flicker.ime
import android.app.Instrumentation
-import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
import android.view.Surface
-import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
@@ -85,55 +84,55 @@
}
}
- @Postsubmit
+ @Presubmit
@Test
fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible()
- @Postsubmit
+ @Presubmit
@Test
fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
- @Postsubmit
+ @Presubmit
@Test
fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry(listOf(IME_WINDOW_TITLE))
- @Postsubmit
+ @Presubmit
@Test
fun imeAppWindowIsAlwaysVisible() = testSpec.imeAppWindowIsAlwaysVisible(testApp)
- @Postsubmit
+ @Presubmit
@Test
fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible()
- @Postsubmit
+ @Presubmit
@Test
fun statusBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible()
- @Postsubmit
+ @Presubmit
@Test
fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation)
- @FlakyTest
+ @Presubmit
@Test
fun navBarLayerRotatesAndScales() =
testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation)
- @FlakyTest
+ @Presubmit
@Test
fun statusBarLayerRotatesScales() =
testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation)
- @Postsubmit
+ @Presubmit
@Test
fun visibleLayersShownMoreThanOneConsecutiveEntry() =
testSpec.visibleLayersShownMoreThanOneConsecutiveEntry()
- @Postsubmit
+ @Presubmit
@Test
fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible()
- @Postsubmit
+ @Presubmit
@Test
fun imeAppLayerIsAlwaysVisible() = testSpec.imeAppLayerIsAlwaysVisible(testApp)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
index ac140f5..2bcdcd9 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
@@ -17,7 +17,7 @@
package com.android.server.wm.flicker.ime
import android.app.Instrumentation
-import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
@@ -91,63 +91,54 @@
}
}
- @Postsubmit
+ @Presubmit
@Test
fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible()
- @Postsubmit
+ @Presubmit
@Test
fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
- @Postsubmit
+ @Presubmit
@Test
fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry(listOf(IME_WINDOW_TITLE))
- @Postsubmit
+ @Presubmit
@Test
fun imeWindowBecomesInvisible() = testSpec.imeWindowBecomesInvisible()
- @Postsubmit
+ @Presubmit
@Test
fun imeAppWindowBecomesInvisible() = testSpec.imeAppWindowBecomesInvisible(testApp)
- @Postsubmit
+ @Presubmit
@Test
fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible()
- @Postsubmit
+ @Presubmit
@Test
fun statusBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible()
- @Postsubmit
+ @Presubmit
@Test
fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation,
Surface.ROTATION_0)
- @Postsubmit
+ @Presubmit
@Test
fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible()
- @Postsubmit
+ @Presubmit
@Test
fun imeAppLayerBecomesInvisible() = testSpec.imeAppLayerBecomesInvisible(testApp)
- @Postsubmit
+ @Presubmit
@Test
- fun navBarLayerRotatesAndScales() {
- Assume.assumeFalse(testSpec.isRotated)
+ fun navBarLayerRotatesAndScales() =
testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0)
- }
- @FlakyTest
- @Test
- fun navBarLayerRotatesAndScales_Flaky() {
- Assume.assumeTrue(testSpec.isRotated)
- testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0)
- }
-
- @Postsubmit
+ @Presubmit
@Test
fun statusBarLayerRotatesScales() {
Assume.assumeFalse(testSpec.isRotated)
@@ -171,7 +162,8 @@
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(repetitions = 5)
+ .getConfigNonRotationTests(repetitions = 5,
+ supportedRotations = listOf(Surface.ROTATION_0))
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
index c7a5178..008ba2f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
@@ -17,7 +17,7 @@
package com.android.server.wm.flicker.ime
import android.app.Instrumentation
-import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
@@ -89,43 +89,43 @@
}
}
- @Postsubmit
+ @Presubmit
@Test
fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible()
- @Postsubmit
+ @Presubmit
@Test
fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
- @Postsubmit
+ @Presubmit
@Test
fun imeWindowBecomesVisible() = testSpec.imeWindowBecomesVisible()
- @Postsubmit
+ @Presubmit
@Test
fun appWindowAlwaysVisibleOnTop() = testSpec.appWindowAlwaysVisibleOnTop(testApp.`package`)
- @Postsubmit
+ @Presubmit
@Test
fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible()
- @Postsubmit
+ @Presubmit
@Test
fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible()
- @Postsubmit
+ @Presubmit
@Test
fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation)
- @Postsubmit
+ @Presubmit
@Test
fun imeLayerBecomesVisible() = testSpec.imeLayerBecomesVisible()
- @Postsubmit
+ @Presubmit
@Test
fun layerAlwaysVisible() = testSpec.layerAlwaysVisible(testApp.`package`)
- @Postsubmit
+ @Presubmit
@Test
fun navBarLayerRotatesAndScales() {
Assume.assumeFalse(testSpec.isRotated)
@@ -139,7 +139,7 @@
testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation)
}
- @Postsubmit
+ @Presubmit
@Test
fun statusBarLayerRotatesScales() {
Assume.assumeFalse(testSpec.isRotated)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index e2a258a..18fac6a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -17,7 +17,6 @@
package com.android.server.wm.flicker.launch
import android.app.Instrumentation
-import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.FlakyTest
@@ -148,17 +147,35 @@
@Test
fun focusChanges() = testSpec.focusChanges("NexusLauncherActivity", testApp.`package`)
- @Postsubmit
+ @Presubmit
@Test
- fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
+ Assume.assumeFalse(testSpec.isRotated)
testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry()
+ }
@FlakyTest
@Test
- fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- testSpec.visibleLayersShownMoreThanOneConsecutiveEntry()
+ fun visibleWindowsShownMoreThanOneConsecutiveEntry_Flaky() {
+ Assume.assumeTrue(testSpec.isRotated)
+ testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry()
+ }
- @FlakyTest(bugId = 141361128)
+ @Presubmit
+ @Test
+ fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ Assume.assumeFalse(testSpec.isRotated)
+ testSpec.visibleLayersShownMoreThanOneConsecutiveEntry()
+ }
+
+ @FlakyTest
+ @Test
+ fun visibleLayersShownMoreThanOneConsecutiveEntry_Flaky() {
+ Assume.assumeTrue(testSpec.isRotated)
+ testSpec.visibleLayersShownMoreThanOneConsecutiveEntry()
+ }
+
+ @Presubmit
@Test
fun noUncoveredRegions() = testSpec.noUncoveredRegions(Surface.ROTATION_0,
testSpec.config.endRotation)
diff --git a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
index dc9e587..e1da3d0 100644
--- a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
+++ b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
@@ -17,6 +17,7 @@
package com.android.server;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
@@ -84,6 +85,7 @@
final String typeName = ConnectivityManager.getNetworkTypeName(type);
mNetworkCapabilities = (ncTemplate != null) ? ncTemplate : new NetworkCapabilities();
mNetworkCapabilities.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
+ mNetworkCapabilities.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
mNetworkCapabilities.addTransportType(transport);
switch (transport) {
case TRANSPORT_ETHERNET:
diff --git a/tests/net/java/android/net/util/MultinetworkPolicyTrackerTest.kt b/tests/net/java/android/net/util/MultinetworkPolicyTrackerTest.kt
new file mode 100644
index 0000000..9b0cfa9
--- /dev/null
+++ b/tests/net/java/android/net/util/MultinetworkPolicyTrackerTest.kt
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.util
+
+import android.content.Context
+import android.content.res.Resources
+import android.net.ConnectivityManager.MULTIPATH_PREFERENCE_HANDOVER
+import android.net.ConnectivityManager.MULTIPATH_PREFERENCE_PERFORMANCE
+import android.net.ConnectivityManager.MULTIPATH_PREFERENCE_RELIABILITY
+import android.net.util.MultinetworkPolicyTracker.ActiveDataSubscriptionIdChangedListener
+import android.provider.Settings
+import android.provider.Settings.Global.NETWORK_AVOID_BAD_WIFI
+import android.provider.Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.TelephonyManager
+import android.test.mock.MockContentResolver
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.internal.R
+import com.android.internal.util.test.FakeSettingsProvider
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.argThat
+import org.mockito.Mockito.any
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+/**
+ * Tests for [MultinetworkPolicyTracker].
+ *
+ * Build, install and run with:
+ * atest android.net.util.MultinetworkPolicyTrackerTest
+ */
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class MultinetworkPolicyTrackerTest {
+ private val resources = mock(Resources::class.java).also {
+ doReturn(0).`when`(it).getInteger(R.integer.config_networkAvoidBadWifi)
+ }
+ private val telephonyManager = mock(TelephonyManager::class.java)
+ private val subscriptionManager = mock(SubscriptionManager::class.java).also {
+ doReturn(null).`when`(it).getActiveSubscriptionInfo(anyInt())
+ }
+ private val resolver = MockContentResolver().apply {
+ addProvider(Settings.AUTHORITY, FakeSettingsProvider()) }
+ private val context = mock(Context::class.java).also {
+ doReturn(Context.TELEPHONY_SERVICE).`when`(it)
+ .getSystemServiceName(TelephonyManager::class.java)
+ doReturn(telephonyManager).`when`(it).getSystemService(Context.TELEPHONY_SERVICE)
+ doReturn(subscriptionManager).`when`(it)
+ .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)
+ doReturn(resolver).`when`(it).contentResolver
+ doReturn(resources).`when`(it).resources
+ doReturn(it).`when`(it).createConfigurationContext(any())
+ Settings.Global.putString(resolver, NETWORK_AVOID_BAD_WIFI, "1")
+ }
+ private val tracker = MultinetworkPolicyTracker(context, null /* handler */)
+
+ private fun assertMultipathPreference(preference: Int) {
+ Settings.Global.putString(resolver, NETWORK_METERED_MULTIPATH_PREFERENCE,
+ preference.toString())
+ tracker.updateMeteredMultipathPreference()
+ assertEquals(preference, tracker.meteredMultipathPreference)
+ }
+
+ @Test
+ fun testUpdateMeteredMultipathPreference() {
+ assertMultipathPreference(MULTIPATH_PREFERENCE_HANDOVER)
+ assertMultipathPreference(MULTIPATH_PREFERENCE_RELIABILITY)
+ assertMultipathPreference(MULTIPATH_PREFERENCE_PERFORMANCE)
+ }
+
+ @Test
+ fun testUpdateAvoidBadWifi() {
+ Settings.Global.putString(resolver, NETWORK_AVOID_BAD_WIFI, "0")
+ assertTrue(tracker.updateAvoidBadWifi())
+ assertFalse(tracker.avoidBadWifi)
+
+ doReturn(1).`when`(resources).getInteger(R.integer.config_networkAvoidBadWifi)
+ assertTrue(tracker.updateAvoidBadWifi())
+ assertTrue(tracker.avoidBadWifi)
+ }
+
+ @Test
+ fun testOnActiveDataSubscriptionIdChanged() {
+ val testSubId = 1000
+ val subscriptionInfo = SubscriptionInfo(testSubId, ""/* iccId */, 1/* iccId */,
+ "TMO"/* displayName */, "TMO"/* carrierName */, 1/* nameSource */, 1/* iconTint */,
+ "123"/* number */, 1/* roaming */, null/* icon */, "310"/* mcc */, "210"/* mnc */,
+ ""/* countryIso */, false/* isEmbedded */, null/* nativeAccessRules */,
+ "1"/* cardString */)
+ doReturn(subscriptionInfo).`when`(subscriptionManager).getActiveSubscriptionInfo(testSubId)
+
+ // Modify avoidBadWifi and meteredMultipathPreference settings value and local variables in
+ // MultinetworkPolicyTracker should be also updated after subId changed.
+ Settings.Global.putString(resolver, NETWORK_AVOID_BAD_WIFI, "0")
+ Settings.Global.putString(resolver, NETWORK_METERED_MULTIPATH_PREFERENCE,
+ MULTIPATH_PREFERENCE_PERFORMANCE.toString())
+
+ val listenerCaptor = ArgumentCaptor.forClass(
+ ActiveDataSubscriptionIdChangedListener::class.java)
+ verify(telephonyManager, times(1))
+ .registerPhoneStateListener(any(), listenerCaptor.capture())
+ val listener = listenerCaptor.value
+ listener.onActiveDataSubscriptionIdChanged(testSubId)
+
+ // Check it get resource value with test sub id.
+ verify(subscriptionManager, times(1)).getActiveSubscriptionInfo(testSubId)
+ verify(context).createConfigurationContext(argThat { it.mcc == 310 && it.mnc == 210 })
+
+ // Check if avoidBadWifi and meteredMultipathPreference values have been updated.
+ assertFalse(tracker.avoidBadWifi)
+ assertEquals(MULTIPATH_PREFERENCE_PERFORMANCE, tracker.meteredMultipathPreference)
+ }
+}
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 6de1075..1cfc3f9 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -2797,6 +2797,10 @@
NetworkCapabilities filter = new NetworkCapabilities();
filter.addCapability(capability);
+ // Add NOT_VCN_MANAGED capability into filter unconditionally since some request will add
+ // NOT_VCN_MANAGED automatically but not for NetworkCapabilities,
+ // see {@code NetworkCapabilities#deduceNotVcnManagedCapability} for more details.
+ filter.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
final HandlerThread handlerThread = new HandlerThread("testNetworkFactoryRequests");
handlerThread.start();
final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
@@ -4144,6 +4148,7 @@
handlerThread.start();
NetworkCapabilities filter = new NetworkCapabilities()
.addTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
.addCapability(NET_CAPABILITY_INTERNET);
final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
mServiceContext, "testFactory", filter, mCsHandlerThread);
@@ -6048,6 +6053,7 @@
.addTransportType(TRANSPORT_CELLULAR)
.addCapability(NET_CAPABILITY_INTERNET)
.addCapability(NET_CAPABILITY_NOT_CONGESTED)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
.setLinkDownstreamBandwidthKbps(10);
final NetworkCapabilities wifiNc = new NetworkCapabilities()
.addTransportType(TRANSPORT_WIFI)
@@ -6056,6 +6062,7 @@
.addCapability(NET_CAPABILITY_NOT_ROAMING)
.addCapability(NET_CAPABILITY_NOT_CONGESTED)
.addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
.setLinkUpstreamBandwidthKbps(20);
mCellNetworkAgent.setNetworkCapabilities(cellNc, true /* sendToConnectivityService */);
mWiFiNetworkAgent.setNetworkCapabilities(wifiNc, true /* sendToConnectivityService */);
@@ -7748,19 +7755,13 @@
mWiFiNetworkAgent.removeCapability(testCap);
callbackWithCap.expectAvailableCallbacksValidated(mCellNetworkAgent);
callbackWithoutCap.expectCapabilitiesWithout(testCap, mWiFiNetworkAgent);
- // TODO: Test default network changes for NOT_VCN_MANAGED once the default request has
- // it.
- if (testCap == NET_CAPABILITY_TRUSTED) {
- verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId));
- reset(mMockNetd);
- }
+ verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId));
+ reset(mMockNetd);
mCellNetworkAgent.removeCapability(testCap);
callbackWithCap.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
callbackWithoutCap.assertNoCallback();
- if (testCap == NET_CAPABILITY_TRUSTED) {
- verify(mMockNetd).networkClearDefault();
- }
+ verify(mMockNetd).networkClearDefault();
mCm.unregisterNetworkCallback(callbackWithCap);
mCm.unregisterNetworkCallback(callbackWithoutCap);